docs(TechLog): 주제 7~10 을 다시 쓴다
주소가 게시 시점에 굳어 저장되는 구조, 축 링크를 두 번 옮긴 순서, 한글 slug 가 간헐적으로 보인 두 가지 어긋남을 표로 갈랐다. 화면이 실패를 없음으로 그릴 때 작성 도구에서 왜 더 오래 숨는지, Promise.all 이 거절과 던짐에서 다른 경로를 타는 이유를 채웠다. CSS module 이 왜 전역 규칙에 닿지 않는지, 403 과 404 가 원인을 어떻게 좁혔는지도 적었다. link-audit.py 를 감사 Case 의 evidence 로 걸어 배정한 증거 하나를 메웠다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
193da20d09
commit
b1653dbba8
+12
-4
@@ -60,23 +60,31 @@ tech-log-design-package : 71bab4c · b93d62a
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 앵커로 옮겼더니 자기 자신을 가리켰다
|
||||
## 주소만 바뀌고 화면은 그대로였다
|
||||
|
||||
주제 화면의 네 줄은 링크로 그려져 있었는데 눌러도 아무 일이 없었다.
|
||||
|
||||
처음에 축의 주소를 `/topics/{주제}/{축}` 이라 적어 두었는데 그런 화면이 없었다. 그래서 축의 주소를 주제 화면 안의 앵커로 바꿨다.
|
||||
|
||||
주제 화면에서 그 링크를 누르면 주소에 앵커가 붙는다. 화면은 이미 그 주제 화면이므로 아무것도 바뀌지 않는다.
|
||||
주제 화면에서 그 링크를 누르면 주소에 앵커가 붙는다. 화면은 이미 그 주제 화면이므로 라우터가 다시 그릴 것이 없고, 앵커가 가리키는 element 도 없으니 스크롤도 일어나지 않는다.
|
||||
|
||||
## 축에 자기 화면을 줬다
|
||||
|
||||
목록 조회에 축 필터를 더하고 `record_variant` 로 거른다. 축 slug 는 주제 안에서만 유일하므로 주제까지 맞춰야 하고, 주제를 빼면 다른 주제의 같은 이름 축까지 걸린다.
|
||||
목록 조회에 축 필터를 더하고 기록이 어느 축에 걸리는지를 담은 표로 거른다.
|
||||
|
||||
축 slug 는 주제 안에서만 유일하므로 조회에서 주제까지 함께 맞춘다. 주제를 빼면 다른 주제의 같은 이름 축이 함께 걸린다 — 인증 경계 주제의 `bff` 와 다른 주제의 `bff` 가 구분되지 않는다.
|
||||
|
||||
## 배포 순서로 만든 2차 사고
|
||||
|
||||
> **이 건에서 제가 만든 2차 사고:** 축 화면을 만들고 **백엔드를 프론트보다 먼저 배포했습니다.** nginx 설정은 라우트 계약에서 생성되므로, 프론트가 배포되기 전까지 `/topics/x/y` 는 404 입니다. 서버는 이미 그 주소를 내보내고 있었고, 사용자는 네 링크가 전부 404 인 화면을 봤습니다. **순서가 있습니다 — 새 라우트는 프론트가 먼저입니다.**
|
||||
|
||||
고치기 전과 고친 뒤가 사용자에게는 같아 보였다. 앞에서는 눌러도 아무 일이 없었고, 뒤에서는 눌러서 404 를 만났다.
|
||||
|
||||
## 주제가 없는 기록
|
||||
|
||||
같은 시기에 주제 없이 게시된 기록이 이름 없는 주제 링크를 달고 있었다. 문서 머리말의 breadcrumb 과 탐색의 「주제 없음」 묶음 둘 다였다. 프로젝트 조각은 처음부터 조건부였는데 주제 쪽만 아니었다.
|
||||
같은 시기에 주제 없이 게시된 기록이 이름 없는 주제 링크를 달고 있었다. 문서 머리말의 breadcrumb 과 탐색의 「주제 없음」 묶음 둘 다였다.
|
||||
|
||||
프로젝트 조각은 처음부터 조건부였다. 주제 쪽만 조건이 없었고, 그 차이가 두 화면에서 같은 모양으로 나타났다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+14
-6
@@ -64,23 +64,31 @@ tech-log-frontend : 5cffe30 · 7093d84
|
||||
|
||||
## 간헐적으로 보인 이유
|
||||
|
||||
slug 생성이 `[a-z0-9]` 만 남기고 나머지를 버렸다. 한글 이름은 통째로 사라지므로 이름에 영문이 얼마나 섞였는지에 따라 결과가 갈린다.
|
||||
slug 생성이 영문 소문자와 숫자만 남기고 나머지를 버렸다. 한글 이름은 통째로 사라지므로, 결과가 이름에 영문이 얼마나 섞였는지에 따라 갈린다.
|
||||
|
||||
영문이 하나도 없으면 빈 문자열이 되어 폼이 요청 전에 거절한다. 영문이 앞에 붙어 있으면 그 부분만 남으므로 뒤가 다른 두 이름이 같은 slug 가 된다.
|
||||
| 이름 | 옛 규칙이 만든 slug | 무엇이 일어났나 |
|
||||
|---|---|---|
|
||||
| `인증` | (빈 문자열) | 폼이 요청 전에 거절 |
|
||||
| `Redis 캐시` | `redis` | 만들어짐 |
|
||||
| `Redis 클러스터` | `redis` | 두 번째가 충돌 |
|
||||
|
||||
`Redis 캐시` 와 `Redis 클러스터` 가 둘 다 `redis` 였다. 두 번째를 만들 때 충돌이 났고, 사용자에게는 「가끔 안 된다」로 보였다.
|
||||
사용자는 둘 다 만났다. 어느 쪽도 「한글이 버려졌다」로 보이지 않고, 하나는 폼 오류로 하나는 중복 오류로 나타난다.
|
||||
|
||||
> 규칙은 간헐적이었던 적이 없다. **보이지 않았을 뿐이다** — slug 생성이 `[a-z0-9]` 만 남기고 나머지를 버려서, 한글 이름은 아무것도 기여하지 못했다.
|
||||
|
||||
## 산술 분해로 로마자를 만든다
|
||||
|
||||
한글 음절은 초성·중성·종성이 정해진 순서로 조합된 코드다. 음절 코드에서 세 값을 산술로 분해할 수 있으므로 변환표가 필요 없고 결과가 결정적이다.
|
||||
한글 음절은 초성·중성·종성이 정해진 순서로 조합된 코드다. 음절 코드에서 세 값을 산술로 분해할 수 있으므로 변환표가 필요 없고, 같은 입력에 늘 같은 결과가 나온다.
|
||||
|
||||
`백엔드 아키텍처` → `baekendeu-akitekcheo`
|
||||
```text
|
||||
백엔드 아키텍처 → baekendeu-akitekcheo
|
||||
```
|
||||
|
||||
## 음운 변화 규칙을 뺀 이유
|
||||
|
||||
국어의 로마자 표기법에는 자모 대응 외에 음운 변화 규칙이 있다. 그것을 넣지 않았다.
|
||||
|
||||
slug 는 읽는 것이지 발음하는 것이 아니다. 음운 변화를 적용하면 같은 이름이 앞뒤 글자에 따라 다른 slug 가 되고, 그러면 같은 이름을 두 번 만들 때 결과가 갈린다.
|
||||
slug 는 읽는 것이지 발음하는 것이 아니다. 음운 변화를 적용하면 같은 글자가 앞뒤에 무엇이 오느냐에 따라 다르게 옮겨지고, 그러면 이름의 일부만 바뀌어도 앞쪽 slug 가 달라진다. 결정적이지 않은 slug 는 주소로 쓸 수 없다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+21
-7
@@ -14,6 +14,7 @@ evidence:
|
||||
- ../../../final/evidence/raw/db/decision-path-after-v15.txt
|
||||
- ../../../final/evidence/raw/api/decision-anchor-fixed.txt
|
||||
- ../../../final/evidence/raw/audit/dead-link-sweep.txt
|
||||
- ../../../final/evidence/raw/audit/link-audit.py
|
||||
sourceRevision: tech-log@2026-09-02
|
||||
source:
|
||||
- final/document.md#§9.2
|
||||
@@ -78,23 +79,34 @@ tech-log-frontend : fe6b56a
|
||||
:::evidence key="decision-path-404" alt="계약·게시 시점 경로 생성·저장 테이블·조회 시점 경로 생성·방문자·공개 라우트 여섯 참가자 사이의 순서도" caption=" " zoom="true"
|
||||
:::
|
||||
|
||||
계약은 결정의 공개 주소가 앵커라고 규정한다. 게시 시점의 `PublicPaths.forKind` 는 그 대신 경로를 만들어 `public_resource_projection` 에 저장한다. 조회 시점의 `PublicSql.pathOf` 가 저장된 주소를 읽고 방문자에게 링크로 내보낸다. 방문자가 그 주소를 요청하면 공개 라우트에는 목록 하나뿐이라 맞는 라우트가 없다.
|
||||
계약은 결정의 공개 주소가 목록 위의 앵커라고 규정한다. 게시 시점의 경로 생성기는 그 대신 목록 아래에 slug 를 붙인 경로를 만들어 공개 투영에 저장한다. 조회 시점의 다른 생성기가 저장된 주소를 읽어 방문자에게 링크로 내보낸다. 방문자가 그 주소를 요청하면 공개 라우트에는 목록 하나뿐이라 맞는 라우트가 없다.
|
||||
|
||||
## 계약은 이미 맞게 적혀 있었다
|
||||
계약은 고칠 것이 없었다. 공개 주소가 앵커라는 것이 이미 계약에 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
|
||||
공개 주소가 `#{slug}` 앵커라는 것이 계약에 이미 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
## 화면 코드에는 흔적이 없다
|
||||
|
||||
이 부류는 저장소를 훑어서는 나오지 않는다. 주소는 게시할 때 서버가 만들어 데이터베이스에 저장한 문자열이고, 화면은 그것을 받아 그대로 링크로 그린다. 코드에 `to=` 나 `href=` 리터럴로 남아 있지 않다.
|
||||
|
||||
감사 스크립트가 그 이유를 적어 두었다.
|
||||
|
||||
> 주소는 게시 시점에 서버가 만들어 DB(`public_resource_projection.navigation_path`)에 저장한 문자열이다. 그래서 저장소 안의 `to=` / `href=` 리터럴만 훑는 감사로는 잡히지 않는다 — 실제로 결정 링크가 그렇게 숨어 있었다.
|
||||
|
||||
## 저장된 행까지 고쳐야 한다
|
||||
|
||||
주소가 게시 시점에 굳어져 저장되므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. V15 마이그레이션에서 저장된 행도 고쳤다.
|
||||
주소가 게시 시점에 굳어지므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. 마이그레이션에서 저장된 행을 함께 고쳤다.
|
||||
|
||||
`public_route.slug` 도 손봤다. 마지막 슬래시 뒤를 자르면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
공개 라우트의 slug 를 읽는 규칙도 함께 손봤다. 마지막 슬래시 뒤를 자르는 방식이면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
|
||||
목록 항목이 앵커를 달 수 있도록 계약에 slug 를 더하고, 화면이 그 slug 를 element id 로 달고 앵커로 들어오면 데이터를 받아 그린 뒤 스크롤하게 했다.
|
||||
|
||||
## 두 겹 가드
|
||||
|
||||
`PublicPathsTest` 는 백엔드에서 종류마다 만들어 낸 경로가 실제 공개 라우트 패턴에 맞는지 본다.
|
||||
| 어디 | 무엇을 보나 | 이 부류가 또 나면 |
|
||||
|---|---|---|
|
||||
| 백엔드 `PublicPathsTest` | 종류마다 만들어 낸 경로가 실제 공개 라우트 패턴에 맞는가 | 만드는 쪽에서 멈춘다 |
|
||||
| 프론트 `resolvesToPublicRoute` | 서버가 준 주소가 라우트 표에 있는가 | 링크로 그리지 않아 방문자가 404 를 만나지 않는다 |
|
||||
|
||||
`resolvesToPublicRoute` 는 프론트에서 라우트 계약이 준 표에 서버가 준 주소를 맞춰 보고, 맞는 라우트가 없으면 링크로 그리지 않는다. 이 부류가 또 생겨도 방문자가 404 를 만나지는 않는다. 틀린 주소가 만들어지는 것 자체는 백엔드 쪽 검사가 잡는다.
|
||||
만드는 쪽에만 두면 이미 저장된 주소를 못 잡고, 그리는 쪽에만 두면 링크가 안 그려지는 것으로 끝나 원인이 남는다.
|
||||
|
||||
## 배포 뒤 전수 감사
|
||||
|
||||
@@ -103,6 +115,8 @@ tech-log-frontend : fe6b56a
|
||||
:::evidence key="dead-link-sweep" alt="서버가 내보내는 주소 35개를 전수로 훑은 감사 출력" caption=" " zoom="false"
|
||||
:::
|
||||
|
||||
같은 방식으로 다시 검사하는 스크립트를 증거와 함께 남겼다. 다음에 라우트를 더하면 그 스크립트를 다시 돌린다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
이 감사는 서버가 내보내는 주소만 본다. 본문 안에 작성자가 손으로 쓴 링크는 대상이 아니다.
|
||||
|
||||
Reference in New Issue
Block a user