docs(TechLog): 주제 셋을 스킬대로 다시 쓰고 SSOT 를 저장소 실물로 고친다
건너뛴 참조 다섯을 읽고 나서 다시 썼다 — from-ssot-to-records.md 의 「그림과 증거는 배정 대상이다」, code-tables-diagrams.md 의 표·코드 규칙, explaining.md 의 「이름을 댔으면 왜 있는지도 댄다」. **SSOT 를 먼저 고쳤다.** §3.3 의 코드블록이 저장소와 달랐다 — PATH_PREFIX_KINDS 는 Record 표가 아니라 튜플 배열이고, 진짜 경로 표는 EXPLORE_KIND_PATHS 다. 저장소에서 확인해 실물로 바꾸고, javadoc 이 적어 둔 이유를 함께 옮겼다. §16.7 에 BRANCH_FIELDS 와 pathOf 실물을, §4.3 에 ContractRouteCoverageTest 의 javadoc 과 면제 상수 둘을 더했다. 62,643 → 65,737 자. **계약에 ssot-assets·ssot-evidence 를 배정했다.** 그 절차를 건너뛰어서 SSOT 가 이미 가진 그림과 측정이 글감에 배정되지 않은 채였다. TechLog 12 글감, keycloak-session-store 는 그림 21장·증거 19건을 배정하고 붙일 글감이 없는 그림 4장은 이유를 계약에 적었다. 배정하자 검사기가 「배정한 증거를 기록이 쓰지 않는다」 4건을 드러냈다. **주제 셋을 다시 썼다.** hand-listed-kinds 중앙값 1,925 → 3,760 자 declared-but-not-implemented → 2,608 자 values-lost-between-boundaries → 2,602 자 게시된 기록은 keycloak 4,546 · n+1liner 3,190 이다. 표와 코드를 SSOT 에서 옮기고, Reference 에 담을 수 없던 표(§5.5 의 여덟 자리)를 짝이 되는 Case 로 내렸다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6feee5ba57
commit
a8ce0dda07
@@ -188,18 +188,48 @@ const path = kind === "CASE" ? "/cases/"
|
||||
|
||||
### 3.3 고친 방법 — 표로 바꾸고 컴파일러에게 맡긴다
|
||||
|
||||
삼항 사슬을 `Record<Kind, Value>` 로 바꿨습니다.
|
||||
삼항 사슬을 `Record<RecordKind, Value>` 로 바꿨습니다. 종류별 목록 주소가 그 예입니다
|
||||
(`presentation/shared/document-kind-labels.ts`):
|
||||
|
||||
```ts
|
||||
// 종류가 늘면 이 자리가 비어 있다고 컴파일러가 잡는다
|
||||
const PATH_PREFIX_KINDS: Record<PublicRecord["kind"], string> = {
|
||||
CASE: "/cases/",
|
||||
REFERENCE: "/references/",
|
||||
QUESTION: "/questions/",
|
||||
CONCEPT: "/concepts/",
|
||||
export const EXPLORE_KIND_PATHS: Record<RecordKind, string> = {
|
||||
CASE: "/explore/cases",
|
||||
CONCEPT: "/explore/concepts",
|
||||
REFERENCE: "/explore/references",
|
||||
QUESTION: "/explore/questions",
|
||||
PROJECT_DECISION: "/projects",
|
||||
};
|
||||
```
|
||||
|
||||
같은 파일의 javadoc 이 이 표가 왜 한 곳에 있는지 적어 두었습니다:
|
||||
|
||||
> 이 대응이 세 화면에 흩어져 있었고 셋 다 개념을 빠뜨렸다 — 홈의 「종류별로 읽기」에는 개념이
|
||||
> 아예 없었고, 문서 머리말의 종류 링크는 삼항의 마지막 else 를 타 개념 문서에서 `/projects` 로
|
||||
> 갔다. `/explore/concepts` 는 처음부터 열려 있었는데 그리로 가는 길이 없었다.
|
||||
>
|
||||
> 결정은 프로젝트 안에서만 읽히므로 자기 목록이 없다. 그 자리를 `/projects` 로 두는 것은
|
||||
> 빠뜨린 것이 아니라 그렇게 정한 것이고, 표에 적혀 있으니 다음 사람이 구분할 수 있다.
|
||||
|
||||
**표로 바꿀 수 없는 자리도 있습니다.** 공개 주소에서 종류를 거꾸로 알아내는 자리
|
||||
(`public-document-header.tsx`)는 키가 종류가 아니라 주소 앞머리라서 `Record<RecordKind, _>` 가
|
||||
성립하지 않습니다. 배열로 두고 못 찾은 것을 조각으로 가릅니다:
|
||||
|
||||
```ts
|
||||
const PATH_PREFIX_KINDS: ReadonlyArray<readonly [string, TargetKind]> = [
|
||||
["/cases/", "CASE"],
|
||||
["/references/", "REFERENCE"],
|
||||
["/questions/", "QUESTION"],
|
||||
["/concepts/", "CONCEPT"],
|
||||
];
|
||||
|
||||
function targetKindOf(path: string): TargetKind {
|
||||
const matched = PATH_PREFIX_KINDS.find(([prefix]) => path.startsWith(prefix));
|
||||
if (matched) return matched[1];
|
||||
// 결정은 프로젝트 화면 안의 앵커로 산다. 그래서 앞머리가 아니라 조각으로 가른다.
|
||||
return path.includes("/decisions#") ? "PROJECT_DECISION" : "PROJECT";
|
||||
}
|
||||
```
|
||||
|
||||
백엔드에서는 **sealed switch 를 식(expression)으로** 쓴 자리가 이 일을 이미 하고 있었습니다.
|
||||
`fa5158d`(개념 종류 추가) 커밋 메시지에 그 효과가 적혀 있습니다:
|
||||
|
||||
@@ -272,7 +302,27 @@ const PATH_PREFIX_KINDS: Record<PublicRecord["kind"], string> = {
|
||||
### 4.3 재발 방지 — 계약↔컨트롤러 전수 대조
|
||||
|
||||
`ContractRouteCoverageTest`(백엔드)를 세웠습니다. `@RestController` 들을 리플렉션으로 훑어
|
||||
매핑을 모으고, 계약이 선언한 경로와 대조합니다.
|
||||
매핑을 모으고, 계약이 선언한 경로와 대조합니다. 클래스 javadoc 이 이 검사가 왜 생겼는지를
|
||||
적어 두었습니다:
|
||||
|
||||
> `listStudioQuestions` 와 `listStudioProjectDecisions` 는 계약에 있고 모델도 생성됐는데
|
||||
> 컨트롤러가 없었다. 생성 모델 검사(`verifyManagementGeneratedModels`)는 schema 와 property 만
|
||||
> 보므로 이 구멍을 잡지 못한다. 프론트는 계약을 믿고 불렀고 서버는 404 를 돌려줬으며, 화면은
|
||||
> 그것을 「이 프로젝트에 열린 질문이 없습니다」로 그렸다 — 실제로는 넷이 있었다.
|
||||
>
|
||||
> 기대 목록을 손으로 적지 않고 계약에서 읽는다. 연산을 더하고 컨트롤러를 잊으면 여기서 멈춘다.
|
||||
|
||||
면제는 상수 둘로 명시합니다. 대조에서 빠지는 것이 코드에 이름으로 남습니다:
|
||||
|
||||
```java
|
||||
private static final Set<String> ELSEWHERE = Set.of("getPublicMedia");
|
||||
private static final Set<String> SUPERSEDED_BY_WORKING_COPY_API =
|
||||
Set.of(
|
||||
"acceptProjectDecision",
|
||||
"addQuestionUpdate",
|
||||
"archiveCase",
|
||||
…);
|
||||
```
|
||||
|
||||
- 작업본 API 로 대체된 **옛 연산 51개**는 `SUPERSEDED_BY_WORKING_COPY_API` 로 명시해 둡니다 —
|
||||
"구현하지 않기로 한 것"과 "빠뜨린 것"은 다릅니다
|
||||
@@ -1307,10 +1357,45 @@ UNION ALL SELECT 1 FROM project_decision WHERE source_case_id = :id
|
||||
경로가 `null` 로 나갑니다. 실제로 CONCEPT 이 여기서 빠져 있었습니다(§3.2 의 13번). 지금은
|
||||
`PublicPathsTest` 가 막지만, `pathOf` 만 쓰는 경로(홈 focus 의 `recentDecision`)에는 그
|
||||
테스트가 닿지 않습니다.
|
||||
|
||||
```java
|
||||
static String pathOf(String resourceType, String slug, String projectSlug) {
|
||||
return switch (resourceType) {
|
||||
case "CASE" -> "/cases/" + slug;
|
||||
case "REFERENCE" -> "/references/" + slug;
|
||||
case "QUESTION" -> "/questions/" + slug;
|
||||
case "CONCEPT" -> "/concepts/" + slug;
|
||||
case "PROJECT" -> "/projects/" + slug;
|
||||
case "PROJECT_DECISION" ->
|
||||
projectSlug == null ? null : "/projects/" + projectSlug + "/decisions#" + slug;
|
||||
case "RELEASE" -> "/releases/" + slug;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
```
|
||||
- **`validate-working-copy.ts` 의 `stringFields`** — 아직 삼항 사슬입니다. 다만 배타적 사슬이
|
||||
아니라 가산형이라 종류를 빠뜨리면 "잘못된 분기로 떨어진다"가 아니라 "그 종류의 추가 칸을
|
||||
검사하지 않는다"가 됩니다. 덜 위험하지만 조용하기는 마찬가지입니다.
|
||||
|
||||
같은 함수 안에서 두 목록의 상태가 갈립니다. 유형별 칸은 표로 바뀌었고 문자열 칸은 사슬로
|
||||
남았습니다:
|
||||
|
||||
```ts
|
||||
const BRANCH_FIELDS: Record<RecordKind, string[]> = {
|
||||
CASE: ["problem", "conclusion", "environment", "reproduction", "lastVerifiedOn", "bodyMarkdown"],
|
||||
CONCEPT: ["bodyMarkdown", "basisVersion"],
|
||||
REFERENCE: ["purpose", "rules", "applyWhen", "exceptions", "examples", "verifiedOn"],
|
||||
QUESTION: ["questionStatus", "facts", "assumptions", "unknowns", "constraints", "options", "nextValidation", "resolution"],
|
||||
PROJECT_DECISION: ["decisionStatus", "decidedOn", "statement", "rationale", "consequences"],
|
||||
};
|
||||
```
|
||||
|
||||
그 표 위의 주석이 왜 바꿨는지를 적어 두었습니다:
|
||||
|
||||
> 유형별 칸. 삼항 사슬이던 동안 개념이 어디에도 없어서, 개념 작업본은 「알 수 없는 종류」로
|
||||
> 거절되고 그 문서의 모든 칸이 「허용되지 않은 속성」이 됐다 — 손으로 나열한 목록에 새 종류를
|
||||
> 빠뜨리는 일이 이 저장소에서 반복됐다. `Record<종류, …>` 로 두면 컴파일러가 빈 자리를 잡는다.
|
||||
|
||||
즉 §3 의 「표로 바꿨다」는 대부분 사실이지만 전부는 아닙니다.
|
||||
|
||||
### 16.8 검토용 스크린샷 3장이 저장소에 커밋돼 있다
|
||||
|
||||
Reference in New Issue
Block a user