이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다. 사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다. 대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 — final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인 final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다. 삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다. 그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개, writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물, scripts/check-ssot-facts.py 와 그 시험이 들어 있다. 이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
21 KiB
여섯 종류의 칸과 게시 조건
칸 이름은 Studio 편집 화면 그대로, 상한과 필수 여부는 studio-api.openapi.yaml 그대로다.
계약 필드명을 괄호에 적는다. 계약은 tech-log-frontend/src/features/tech-log/contracts/studio/
에 있고, 이 파일과 계약이 어긋나면 계약이 맞다.
RecordKind 는 여섯이다 — CASE · REFERENCE · QUESTION · PROJECT_DECISION · CONCEPT ·
SETUP (studio-api.openapi.yaml:838-840).
이 목록을 손으로 옮길 때마다 종류가 빠졌다. 이 문서도 한동안 「다섯이다」라고 적고 SETUP
을 뺐다. 프론트엔드에서 먼저 같은 일이 났고 소스에 적혀 있다
(application/ports/studio-gateway.ts:8-12).
종류는 계약의
RecordKind를 그대로 쓴다. 여기 손으로 적어 두었던 동안 개념과 환경 구성이 빠져 있었고, 작업본 목록의 종류 필터는 그 둘을 아예 고를 수 없었다 — 손으로 나열한 목록에 새 종류를 빠뜨리는 일이 이 저장소에서 반복됐다.
일곱 번째가 생기면 같은 일이 난다. 이 문서를 고칠 때는 기억으로 세지 말고
studio-api.openapi.yaml 의 RecordKind 를 열어 몇 줄인지부터 센다.
종류별 칸 (새 문서 화면이 적어 주는 그대로)
| 화면 이름 | 계약 kind |
그 종류만의 칸 |
|---|---|---|
| 검증 기록 (Case) | CASE |
문제 · 결론 · 환경 · 재현 · 본문 |
| 적용 기준 (Reference) | REFERENCE |
목적 · 규칙 · 적용 조건 · 예외 · 예시 |
| 동작 원리 (Concept) | CONCEPT |
기준 버전 · 본문 |
| 환경 구성 (Setup) | SETUP |
버전 · 본문 |
| 열린 질문 (Question) | QUESTION |
상태 · 사실 · 가정 · 미지수 · 선택지 |
| 설계 결정 (Decision) | PROJECT_DECISION |
상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
화면 이름은 여섯 다 한글이다. 2026-09-12 에 /studio/documents/new 에서 읽었고 작업본
목록(/studio/documents)의 종류 필터도 같은 여섯 이름을 쓴다. 이 문서의 절 제목과 산문은
괄호 안의 이름을 쓴다 — 폴더 이름과 frontmatter 의 kind 가 그쪽이기 때문이다.
여기에 아래 공통 칸이 더해진다.
공통 (여섯 종류 모두 — WorkingCopyInputBase)
| 칸 | 필드 | 상한 | 게시 조건 |
|---|---|---|---|
| 제목 | title |
120자 | 필수 — 없으면 게시 거절 |
| slug | slug |
3~100자, ^[a-z0-9]+(?:-[a-z0-9]+)*$ |
필수 — 비우면 제목에서 만든다 |
| 요약 | summary |
2,000자 | 경고. 목록 카드에는 약 90자까지 보인다 |
| Topic | topicId |
— | 경고 |
| 축 | variantIds |
8개 | 선택. 고르지 않으면 그 주제의 공통 기록이 된다 |
| Project | projectId |
— | PROJECT_DECISION과 SETUP은 필수 |
| 관계 | relations |
20개 | PROJECT_DECISION은 1개 이상 필수. 그 종류에서는 절 이름이 「근거」다 |
편집 화면에서 축은 체크박스 묶음으로 나오고 묶음 이름이 「축 — 고르지 않으면 이 주제의 공통 기록이 됩니다」다. 보이는 축은 고른 Topic 이 정한다 — OAuth/OIDC 인증 경계를 고르면 SPA·Mediator·BFF·Forward-Auth 가 나온다.
축(variantIds)은 주제 안의 접근·구조다. 인증 경계 주제의 축은 SPA·Mediator·BFF·Forward-Auth,
조회 성능 주제의 축은 조회 전략이다. 한 기록이 여러 축에 걸릴 수 있다 — PKCE 는 SPA 와 BFF 양쪽에
관계된다. 아무 축도 고르지 않으면 「공통」 축이 따로 있는 것이 아니라 그 주제의 공통 기록으로 읽힌다.
slug를 비우면 제목에서 만든다. 한글 제목도 로마자로 옮겨 유효한 slug가 된다. 직접 쓸 때는 영문 소문자·숫자·하이픈만 쓴다.
Project에 slug가 없으면 공개 화면에 프로젝트가 표시되지 않는다. 공개 계약의
ProjectSummary가 slug와 path를 요구하기 때문이다. 주제·프로젝트 화면에서 확인한다.
frontmatter 의 topic
폴더 이름과 같은 slug 를 적는다. 화면에 보이는 한글 주제 이름은 topicName 에 따로 둔다.
topic 이 표시 이름이면 폴더와 대조할 수 없다.
topic: oauth-oidc-auth-boundary
topicName: OAuth/OIDC 인증 경계
기록이 가리키는 로컬 파일
기록은 docs/<프로젝트>/tech-log-studio/<주제>/<종류>/ 에 있고, 그림과 증거는 같은 프로젝트의
final/ 에 있다. 같은 파일을 양쪽에 두지 않고 frontmatter 로 잇는다.
assets:
- key: eager-lazy-query-sequence # 본문의 :::evidence key 와 같은 값
file: ../../../final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg
evidence:
- ../../../final/evidence/explain/highlights-child-plan-A.txt
assets 는 본문이 있는 Case·Concept·Setup 에만 둔다. 나머지 세 종류는 칸이 평문으로
렌더링돼 그림을 표시할 곳이 없다. 그림이 필요한 내용은 본문이 있는 종류에 담고 관계로
가리킨다.
assets 는 Studio 에 올릴 파일이다. 아직 안 올렸으면 key 가 파일 이름과 같고, 올린 뒤에는
서버가 준 <이름>-<해시8> 로 바뀐다. Studio 에 넣을 때 이 목록을 보고 Asset 을 올리고,
본문의 :::evidence key 를 서버가 준 키로 바꾼다.
evidence 는 그 기록이 인용한 측정 자료다. 실행계획·csv·터미널 기록·스크린샷이 여기 온다.
본문에 값을 옮겨 적었으면 그 값이 어느 파일에서 나왔는지 이 줄이 말해 준다.
평문 칸 쓰는 법
본문(bodyMarkdown)을 뺀 모든 칸은 마크다운 블록 파서를 거치지 않는다. 그렇다고 전부
글자 그대로 나오는 것은 아니다. 렌더러가 이 칸들만 따로 그리고(tech-log-frontend 의
presentation/shared/public-render/prose-text.tsx), 거기서 셋이 살아난다.
| 이 칸에서 | 어떻게 되나 |
|---|---|
| 백틱 쌍 | 인라인 <code> 로 살아난다. 빼지 않는다 |
| 백틱이 홀수 개 | 짝이 안 맞으므로 원문 그대로 둔다 — 반쯤 해석하지 않는다 |
| 빈 줄 | 문단이 갈린다 |
| 한 줄 바꿈 | <br> 로 그 자리에 남는다 |
별표·파이프·#·코드펜스·인용 표지 > |
글자 그대로 보인다. 이것들만 뺀다 |
옛 판을 기억하지 마라. 이 칸들은 오래 진짜 평문으로 나갔고 백틱이 백틱째 화면에 나왔다 — 어떤 Reference 는 한 문서에 백틱이 32개였고 그 원문이 카드와 검색 결과까지 퍼졌다. 그건 고쳐진 버그다. 지금 백틱을 빼면 식별자가 본문과 같은 민무늬로 나온다.
무엇을 지우나. 별표·코드펜스·> 는 글자 그대로 보인다. 식별자는 남기고 표시 문자만 뺀다.
**this is the parameter** 는 그 문장만 남긴다. 코드펜스 안이 이름 : 값 꼴이면 펜스 줄만
지우면 그대로 읽힌다. 백틱은 그대로 두고, 문단 사이 빈 줄도 지킨다.
SSOT 를 그대로 옮긴 인용도 > 를 못 쓴다. 인용이라는 것을 표지로 나타낼 방법이 이 칸에는
없다 — > 도, 들여쓰기도 안 산다. 표지를 빼고 한 문단으로 두거나, 인용이 꼭 인용으로 보여야
하면 본문이 있는 종류로 옮긴다. 「」 를 새로 씌우지 않는다. 옮긴 글자는 보호 구간이라 그대로다.
코드펜스를 뗄 때 언어 표시 줄을 같이 지운다. ```text 에서 펜스만 지우면 text 한 줄이
남고, 그 낱말이 화면에 그대로 나온다. 실제로 한 기록에서 그렇게 남아 있었다.
칸이 어떻게 보이는지는 렌더러가 정본이다. 이 파일이 아니다. 여기 적힌 것과 화면이
다르면 prose-text.tsx 와 public-record-renderer.tsx 를 열어서 가른다.
관계(근거) 절은 평문 칸이 아니다. 기록을 거는 목록이라 - **제목** 표기를 그대로 둔다.
나열은 이름 : 값으로 끊는다. 쉼표로 이으면 읽는 사람이 항목을 세어야 한다.
쓰지 않는다
access token 300초, refresh token rotation과 재사용 허용 0회, 그리고 issuer·audience 검증이다
쓴다
access token : 300초
refresh token rotation, 재사용 허용 : x
issuer · audience : 검증
있음·없음은 o·x로 적는다. 확인함·확인 안 함보다 훑을 때 빨리 잡힌다.
한 문장이 화면에서 두 줄을 넘으면 끊는다. 편집 화면의 칸은 좁고 공개 화면은 넓다. 여기서 한 줄로 보이는 문장이 저기서는 덩어리가 된다. 절차·조건을 한 문단에 이어 쓰지 않는다.
Case — 문제를 재현하고 검증한 결론
| 칸 | 필드 | 비고 |
|---|---|---|
| 문제 | problem |
무엇이 왜 문제였나 |
| 결론 | conclusion |
검증으로 확정한 것 |
| 검증 환경 | environment |
런타임·버전·DB·도구 |
| 재현 조건 | reproduction |
다른 사람이 같은 결과를 얻는 방법 |
| 마지막 검증일 | lastVerifiedOn |
실제로 확인한 날. 30일 지나면 경고 |
| 본문 Markdown | bodyMarkdown |
10만 자. 본문을 갖는 세 종류 중 하나 |
공개 화면에서 검증 환경과 재현 조건은 environmentSummary 배열에 그 순서로 실린다.
Concept — 남의 것이 어떻게 동작하는지
새 문서 화면에서 「동작 원리」를 고른다. 화면 설명은 「개념의 내부 동작과 아키텍처를 처음부터
풀어 씁니다.」다. 전에 이 절은 「이 종류만 이름이 한글이다」라고 적었는데 2026-09-12 에는 여섯 다
한글이었다.
| 칸 | 필드 | 비고 |
|---|---|---|
| 본문 Markdown | bodyMarkdown |
10만 자. Case·Setup 과 함께 본문을 갖는 세 종류 중 하나 |
| 기준 버전 | basisVersion |
120자. 무엇을 보고 쓴 글인지 한 줄 |
칸이 둘뿐이다. 문제도 결론도 재현 조건도 없다. 내가 재현한 결과가 아니라 이미 그렇게 동작하는
것을 적기 때문이다. Authorization Code Flow 가 code 를 한 번 더 교환하는 이유, Forward-Auth subrequest 가 실어 보내는 것 같은 것이 여기 온다.
lastVerifiedOn 이 없고 basisVersion 이 낡음을 말한다. 개념은 날짜로 낡지 않고 버전으로
낡기 때문이다. Keycloak 26.7.0 identity brokering, Kubernetes 1.31 처럼 적는다. 비워도 게시된다.
공개 주소는 /concepts/{slug} 다.
기준 버전을 비워도 게시된다. 계약의 required 에 들어 있지만 빈 문자열을 허용하고, 게시
검증도 막지 않는다(2026-09-04 에 임시 개념 기록을 만들어 비운 채로 게시해 확인했다). 그래도
채우는 편이 낫다 — 개념이 언제 낡았는지 읽는 사람이 알 방법이 이 칸뿐이다.
편집 화면 오른쪽 작업 상태 도 이 종류를 「동작 원리」라고 부른다.
Case 와 헷갈리면 내가 무엇을 했는지를 묻는다. 내가 돌려 보고 수치를 얻었으면 Case, 남의 문서와 코드를 읽고 동작을 정리했으면 Concept 이다.
Concept 이 아닌 것
Concept 은 어떤 Case 를 이해하려면 먼저 알아야 하는 구조다. 그 Case 가 없으면 Concept 도 없다. 분석하면서 알게 된 사실을 종류가 마땅치 않아 여기 넣지 않는다.
| 이런 제목 | 실제로는 |
|---|---|
| 호출자가 없다 · 프로덕션에서 실행되지 않는다 | 부재는 Case 의 관측이다 |
| 구현 클래스 51개를 전부 읽었다 · 재현에 쓴 레인 | 분석 범위·방법. SSOT 의 coverage 원장에 남는다 |
| 보류한 항목과 보류한 이유 | 분석 진행 기록. 같은 곳에 남는다 |
grep refs=0 은 시작점이지 결론이 아니다 |
분석 방법론. Reference 로 쓸 수 있으면 Reference 다 |
| (8.4) 문서/구현 드리프트 — … · Confirmed — … | 분석 문서의 절 제목을 그대로 옮긴 것 |
한두 문장으로 Case 안에서 설명되는 것도 Concept 이 아니다. 없애고 Case 의 한 절로 넣어도 이해가 그대로면 독립 기록으로 만들지 않는다.
Concept 이 되는 것은 이런 것들이다 — Spring 조립의 세 경로, @ConditionalOnBean 의 평가 시점, 커밋 증거 상태 전이, fenced lease 와 CAS, gRPC flow control 과 backpressure.
여러 Case 가 같은 선수 지식을 요구할 때 그것을 한 번만 설명하려고 만드는 자리다.
Setup — 남이 따라 할 절차 (SETUP)
화면 이름은 「환경 구성」이고 설명은 「이 기록을 자기 손으로 재현하는 절차를 남깁니다.」다.
편집 화면은 구역 둘로 나뉜다 — 「기본 정보」와 「환경 구성」(eyebrow SETUP).
| 화면 이름 | 필드 | 상한·모양 |
|---|---|---|
| 고정한 버전 | pinnedVersions |
배열 30개. 줄마다 이름(1버전(1 |
| 절차 Markdown | bodyMarkdown |
10만 자 |
화면에 붙은 도움말을 그대로 옮기면 이렇다.
- 고정한 버전 :
“Keycloak” / “26.7.0” 처럼 적습니다. 비우면 화면에 표를 그리지 않습니다. - 절차 Markdown :
“##” 소제목이 목차가 됩니다. 명령은 코드블록으로 적어야 그대로 복사됩니다.
SetupInput.required 는 [kind, bodyMarkdown, pinnedVersions] 다.
작업본을 만들면 본문이 비어 있지 않다. Studio 가 절 뼈대를 미리 넣어 준다.
## 실행 절차
## 구성 값
## 확인 방법
계약의 bodyMarkdown 설명은 「실행 절차·구성 값·확인 방법을 ## 절로 적는다. 절 이름을
강제하지 않는다 — 프로젝트마다 셋업의 모양이 다르다.」다. 뼈대는 출발점이고, 절 이름은 그
프로젝트가 쓰는 말로 바꿔도 저장과 게시가 막히지 않는다.
왜 이 종류가 따로 있나
다른 다섯은 끝난 일을 적고 환경 구성만 남이 따라 할 절차를 적는다. 편집 화면 주석
(presentation/studio/components/setup-fields.tsx:44-52)이 그 차이를 적어 두었다.
다른 다섯 종류는 끝난 일을 적는다. 이 종류만 읽는 사람이 그대로 따라 하는 절차를 적으므로, 본문에 명령과 표가 들어간다 — Case 의 「검증 환경」 같은 평문 한 칸으로는 담기지 않는다. … 버전만 본문 밖에 둔다. 이 절차가 어느 버전 위에서 성립했는지는 그 기록의 유효 범위이고, 목록과 머리말이 본문을 열지 않고 보여 줘야 하는 값이기 때문이다.
그래서 칸이 둘뿐인데도 Concept 과 다르게 쓴다. 명령·표·그림은 본문에 넣고 버전만 본문 밖에 남긴다. 개념의 「기준 버전」도 같은 이유로 본문 밖에 있고, 다른 점은 셋업의 버전이 여럿이라는 데 있다.
검증일 칸이 없다. Case 의 lastVerifiedOn 도 Reference 의 verifiedOn 도 이 종류에는 없다.
공개 계약의 SetupDetailResponse 가 왜인지 적는다.
환경 구성은 끝난 일이 아니라 따라 하는 절차다. 낡음은 검증일이 아니라
pinnedVersions가 말한다 — 어느 버전 위에서 이 절차가 성립했는지가 유효 범위다. 주제는 없을 수 있다. 주제 없는 셋업은 그 프로젝트의 공통 구성이다.
프로젝트는 필수, 주제는 선택
PROJECT_DECISION 말고 프로젝트를 요구하는 종류가 하나 더 있다.
if (input.kind === "SETUP" && !project) fail("환경 구성은 프로젝트에 속합니다. 기본 정보에서 프로젝트를 골라 주세요.");
domain/content-format/project-public-render-model.ts:245 다.
주제는 비워도 된다. 비우면 그 프로젝트의 공통 구성으로 읽힌다. 다만 2026-09-12 에 빈 초안의
미리보기는 1:1 TOPIC catalog entry is required 로 막혔다 — 미리보기를 보려면 Topic 을 고른다.
본문 파서와 공개 주소
본문 파서는 Case 와 같다(presentation/public/components/setup-document-page.tsx:11).
환경 구성의 본문도 Case 와 같은 파서를 탄다 —
##소제목이 목차가 되고:::evidence가…
그래서 코드블록·표·다이어그램·이미지를 쓸 수 있다.
- 공개 상세 :
/setups/{slug}(contracts/tech-log-route-contract.ts:31, 라우트 제목 「환경 구성」) - 공개 목록 :
/explore/setups— 설명 「이 기록을 자기 손으로 재현하는 절차를 남깁니다.」 (presentation/public/pages/explore-kind-page.tsx:17)
Studio 에 환경 구성 문서는 아직 0건이다. 2026-09-12 에 /studio/documents?kind=SETUP 이
「0개 중 0개 표시 중」이었다. 종류는 있는데 한 번도 쓰이지 않았다. 위의 칸 설명은 계약과 편집
화면에서 읽었고, 올라간 기록에서 확인하지 않았다.
Reference — 반복 적용할 기준
| 칸 | 필드 | 비고 |
|---|---|---|
| 목적 | purpose |
이 기준이 무엇을 막는가 |
| 규칙 | rules[] |
제목(120자) + 본문. 평문 |
| 적용 조건 | applyWhen[] |
언제 적용되는가 |
| 예외 | exceptions[] |
적용되지 않는 경우 |
| 예시 | examples[] |
짧은 문장. 코드가 아니다 |
| 마지막 검증일 | verifiedOn |
규칙 본문에 코드를 쓰고 싶으면 그 코드가 있는 Case를 만들고 관계로 가리킨다.
Question — 아직 닫히지 않은 판단
| 칸 | 필드 | 비고 |
|---|---|---|
| 질문 상태 | questionStatus |
OPEN / RESOLVED / 미정 |
| 사실 | facts[] |
확인된 것 |
| 가정 | assumptions[] |
확인하지 않고 전제한 것 |
| 미지수 | unknowns[] |
OPEN이면 1개 이상 필수 |
| 제약 | constraints[] |
선택을 좁히는 조건 |
| 선택지 | options[] |
제목(120자) + 설명. 50개까지 |
| 다음 검증 | nextValidation |
무엇을 하면 판단이 끝나는가 |
OPEN인데 해결 내용을 채우면 게시가 거절된다. 상태와 내용이 어긋나기 때문이다.
사실과 가정을 섞지 않는다. 확인했으면 사실, 아니면 가정이다. 그 구분이 이 종류의 존재 이유다.
Decision — 프로젝트가 정한 방향 (PROJECT_DECISION)
| 칸 | 필드 | 비고 |
|---|---|---|
| 결정 상태 | decisionStatus |
PROPOSED / ADOPTED / 미정 |
| 결정일 | decidedOn |
|
| 결정문 | statement |
무엇을 정했는가. 한 문장 |
| 판단 이유 | rationale |
왜 그렇게 정했는가 |
| 영향 | consequences[] |
이 결정으로 감수하는 것 |
| 근거 기록 | relations |
1개 이상 필수 |
근거가 없는 Decision은 게시되지 않는다(DECISION_EVIDENCE_REQUIRED). 무엇을 보고 정했는지
가리키지 못하면 그것은 결정이 아니라 선언이다.
영향에는 좋은 것만 적지 않는다. 감수한 비용이 빠지면 다음 사람이 같은 판단을 다시 못 한다.
종류 고르기
남이 그대로 따라 할 절차를 적나 ── 예 ──→ Setup
│
아니오 (끝난 일을 적는다)
│
내가 직접 돌려 보고 결과를 얻었나 ── 예 ──→ Case
│
아니오
│
남의 것이 어떻게 동작하는지 적나 ── 예 ──→ Concept
│
아니오
│
판단이 끝났나 ──── 아니오 ──→ Question
│
예
│
프로젝트의 방향인가 ── 예 ──→ Decision
│
아니오
│
└──→ Reference
첫 갈래가 「끝난 일을 적나, 남이 따라 할 절차를 적나」다. 나머지 다섯은 이미 끝난 일을 적고, Setup 만 읽는 사람이 자기 기계에서 실행할 순서를 적는다. 편집 화면 주석이 그 경계를 「Case 의 「검증 환경」 같은 평문 한 칸으로는 담기지 않는다」로 적는다 — 명령이 여러 줄이고 그대로 복사돼야 하면 Case 의 평문 칸이 아니라 Setup 의 본문에 들어간다.
Case 와 Concept 이 가장 자주 헷갈린다. 수치와 재현 조건이 있으면 Case, 읽고 정리한 동작이면
Concept 이다. Concept 과 Reference 는 「무엇이 그런가」와 「우리가 어떻게 할 것인가」로 갈린다 —
PKCE 가 code 가로채기를 막는 방식은 Concept, 우리는 public client 에 PKCE 를 필수로 쓴다는
Reference 다.
한 자료가 여러 종류에 걸치면 나눈다. 예를 들어 N+1을 재현해 고쳤고 그 과정에서 조회 기준을 굳혔다면, Case 하나와 Reference 하나를 만들고 서로 관계로 잇는다. 한 기록에 몰아넣으면 Case의 칸도 Reference의 칸도 반쯤만 맞는다.