chore: 이전 세션이 남긴 변경을 커밋한다

이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다.
사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다.

대부분은 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>
This commit is contained in:
DongHyeonka
2026-09-17 11:02:02 +09:00
co-authored by Claude Opus 5
parent 2109f726fe
commit ab59130196
1524 changed files with 3160026 additions and 8369 deletions
@@ -93,5 +93,24 @@ mailto:…
| `list items must contain exactly one paragraph` | 목록을 중첩했다 |
| `unsafe link URL: …` | 허용되지 않는 스킴이나 `//` 주소 |
| `duplicate explicit ID: …` | 같은 id를 두 번 썼다 |
| `unknown inline directive: 27` | **문단에 시각을 그냥 썼다** — 아래를 본다 |
거절 메시지에는 줄·칸 번호가 붙는다. 그 줄을 먼저 본다.
## 문단 안의 시각은 백틱으로 감싼다
`:` 뒤에 글자가 붙으면 파서가 인라인 directive 로 읽는다. 그래서 문단에 `08:20:27` 을
그냥 쓰면 `:27` 에서 막힌다. `00/12:00:00` 같은 설정값도 같다.
```text
새 인증서가 08:20:27 에 기록됐다. ← FAIL unknown inline directive: 27
새 인증서가 `08:20:27` 에 기록됐다. ← PASS
```
**이것이 조용한 결함이 되는 경로가 있다.** 막히면 시각을 빼고 넘어가게 되고, 그러면
검사기는 통과하는데 **기록에서 수치가 사라진다.** 실제로 한 회차에서 두 편이 각각
시각 둘과 타이머 설정값을 빼고 통과시켰다. 수치·날짜·시각은 보호 구간이다 —
**빼지 말고 감싼다.**
표와 코드블록 안은 걸리지 않는다. 그래서 같은 프로젝트의 다른 기록이 통과하는 것이
「이 문법이 괜찮다」는 뜻이 아니다 — 그쪽은 시각이 전부 표 안에 있었을 뿐이다.
@@ -9,11 +9,11 @@
### 1. 자리가 있는가
`assets` 는 본문이 있는 종류만 갖는다 — **CaseConcept**. Reference·Question·Decision 의
칸은 평문으로 렌더링돼 그림이 들어갈 자리가 없다.
`assets` 는 본문이 있는 종류만 갖는다 — **Case·Concept·Setup**. Reference·Question·Decision 의
칸은 평문으로 렌더링돼 그림이 들어갈 곳이 없다.
파생 기록에 그림이 필요해 보이면 그 그림은 **짝이 되는 CaseConcept 의 것**이다. 거기 담고
`관계`로 가리킨다. 담을 Case 나 Concept 이 없으면 그 그림은 아직 집이 없다 — 계약의
파생 기록에 그림이 필요해 보이면 그 그림은 **짝이 되는 Case·Concept·Setup 의 것**이다. 거기 담고
`관계`로 가리킨다. 담을 기록이 없으면 그 그림은 아직 집이 없다 — 계약의
`assetLedger.unassigned` 에 그렇게 적고, 새 글감을 세울지는 따로 판단한다.
### 2. 표가 아닌가
@@ -40,11 +40,17 @@
| **Case** | 이 요청 한 번이 어떤 순서로 무엇을 지나갔나 | `sequence` · `component-flow` |
| **Case** (경계가 논지일 때) | 무엇이 어느 경계 안에 있고 무엇이 밖에 있나 | `two-zone-pipeline` |
| **Concept** | 남의 것이 어떤 순서·구조로 동작하나 | `sequence` · `component-flow` · `ports-adapters` |
| **Setup** | (아직 못 적는다 — 아래) | — |
Case 는 **내가 돌려서 본 것**이라 대개 순서가 논지다. Concept 은 **남의 것이 어떻게 동작하는지**라
구조나 변환 사슬이 논지다. 어느 쪽이든 「무엇이 무엇으로 바뀌는가」를 못 적으면 아직 그릴 것이
없다는 뜻이다.
**Setup 은 담을 곳만 있고 본보기가 없다.** 본문 파서가 Case 와 같아서 그림이 렌더링되기는 한다.
다만 2026-09-12 에 Studio 의 환경 구성 문서가 0건이라, 위 두 줄처럼 「반복된 물음」을 셀 자료가
없다. 이 줄은 그림을 그리지 말라는 뜻이 아니라 **아직 아무도 안 그려 봤다**는 뜻이다. 그릴 때는
세 관문만 그대로 지나고, 무엇을 그렸는지 이 표에 적어 둔다.
**한 절에 그림 하나.** 같은 절에 구조 그림과 흐름 그림을 둘 다 넣으면 독자가 어느 쪽을 먼저
읽어야 하는지 알 수 없다. 둘 다 필요하면 절을 나눈다.
@@ -1,6 +1,6 @@
# 코드·표·다이어그램·이미지
본문(`bodyMarkdown`)에만 해당한다. 본문이 있는 종류는 CaseConcept 이다.
본문(`bodyMarkdown`)에만 해당한다. 본문이 있는 종류는 Case·Concept·Setup 셋이다.
## 코드블록
@@ -99,6 +99,7 @@ Reference의 칸도 반쯤만 맞는 기록이 나온다. 종류를 먼저 정
|---|---|---|
| 재현한 관측 하나 | 조건을 갖추고 돌려서 얻은 수치·실행계획·로그 | Case |
| 남의 것의 동작 하나 | 문서와 코드를 읽어 정리한 규칙·흐름 | Concept |
| 남이 따라 할 절차 하나 | 그 환경을 처음 세우는 명령과 구성 값 | Setup |
| 반복 적용할 기준 하나 | 다음에도 같게 하기로 한 규칙 | Reference |
| 프로젝트가 고른 방향 하나 | 대안을 두고 정한 것 | Decision |
| 닫히지 않은 판단 하나 | 아직 모르는 것과 무엇을 하면 닫히는지 | Question |
@@ -107,6 +108,8 @@ Reference의 칸도 반쯤만 맞는 기록이 나온다. 종류를 먼저 정
순서대로 묻는다. 먼저 걸리는 것이 그 글감의 종류다.
0. **남이 자기 기계에서 따라 할 절차인가** → Setup. 나머지 다섯은 끝난 일을 적고 이것만 실행할
순서를 적는다. 명령과 구성 값이 SSOT에 있어야 하고, 프로젝트를 반드시 고른다
1. **내가 돌려서 얻은 결과인가** → Case. 수치·실행계획·로그가 SSOT에 있어야 한다. 없으면 Case가 아니다
2. **남의 것이 어떻게 동작하는지인가** → Concept. 무엇을 보고 썼는지(버전)가 있어야 한다
3. **다음에도 같게 하기로 한 규칙인가** → Reference
@@ -131,7 +134,7 @@ Reference 하나로 나누고 `관계`로 잇는다.
미리 알아야 하는 구조가 있을 때만 Concept을 만든다. 메커니즘처럼 보이는 절을 훑어 채우면 어느
기록도 필요로 하지 않는 개념이 쌓인다.
**주제 하나에 독자 질문 하나.** Studio는 주제 아래에 섯 종류를 나눠 보여 준다. 그 주제의
**주제 하나에 독자 질문 하나.** Studio는 주제 아래에 섯 종류를 나눠 보여 준다. 그 주제의
기록들이 함께 답하는 물음을 한 줄로 적고, 그 물음에 답하지 않는 글감은 다른 주제로 옮긴다.
주제 slug는 Studio의 것을 그대로 쓴다.
@@ -157,7 +160,7 @@ Reference 하나로 나누고 `관계`로 잇는다.
"status": "게시 전", "studioId": "32d0be7d-…", "assets": 1, "evidence": 3 },
{ "title": "아직 쓰지 않은 글감" }
],
"concept": [], "reference": [], "question": [], "decision": []
"concept": [], "setup": [], "reference": [], "question": [], "decision": []
}
}
}
@@ -1,24 +1,41 @@
# 섯 종류의 칸과 게시 조건
# 섯 종류의 칸과 게시 조건
칸 이름은 Studio 편집 화면 그대로, 상한과 필수 여부는 `studio-api.openapi.yaml` 그대로다.
계약 필드명을 괄호에 적는다. 계약은 `tech-log-frontend/src/features/tech-log/contracts/studio/`
에 있고, 이 파일과 계약이 어긋나면 계약이 맞다.
`RecordKind`섯이다 — `CASE` · `REFERENCE` · `QUESTION` · `CONCEPT` · `PROJECT_DECISION`.
`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` | 기준 버전 · 본문 |
| Question | `QUESTION` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
| Decision | `PROJECT_DECISION` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
| **검증 기록** (Case) | `CASE` | 문제 · 결론 · 환경 · 재현 · 본문 |
| **적용 기준** (Reference) | `REFERENCE` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
| **동작 원리** (Concept) | `CONCEPT` | 기준 버전 · 본문 |
| **환경 구성** (Setup) | `SETUP` | 버전 · 본문 |
| **열린 질문** (Question) | `QUESTION` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
| **설계 결정** (Decision) | `PROJECT_DECISION` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
**화면 이름은 여섯 다 한글이다.** 2026-09-12 에 `/studio/documents/new` 에서 읽었고 작업본
목록(`/studio/documents`)의 종류 필터도 같은 여섯 이름을 쓴다. 이 문서의 절 제목과 산문은
괄호 안의 이름을 쓴다 — 폴더 이름과 frontmatter 의 `kind` 가 그쪽이기 때문이다.
여기에 아래 공통 칸이 더해진다.
## 공통 (섯 종류 모두 — `WorkingCopyInputBase`)
## 공통 (섯 종류 모두 — `WorkingCopyInputBase`)
| 칸 | 필드 | 상한 | 게시 조건 |
|---|---|---|---|
@@ -27,7 +44,7 @@
| 요약 | `summary` | **2,000자** | 경고. 목록 카드에는 약 90자까지 보인다 |
| Topic | `topicId` | — | 경고 |
| 축 | `variantIds` | 8개 | 선택. 고르지 않으면 그 주제의 **공통 기록**이 된다 |
| Project | `projectId` | — | `PROJECT_DECISION` 게시 시 필수 |
| Project | `projectId` | — | `PROJECT_DECISION``SETUP`은 필수 |
| 관계 | `relations` | 20개 | `PROJECT_DECISION`**1개 이상 필수**. 그 종류에서는 절 이름이 「근거」다 |
편집 화면에서 축은 체크박스 묶음으로 나오고 묶음 이름이 「축 — 고르지 않으면 이 주제의 공통
@@ -67,8 +84,8 @@ evidence:
- ../../../final/evidence/explain/highlights-child-plan-A.txt
```
**`assets` 는 본문이 있는 CaseConcept 에만 둔다.** 나머지 세 종류는 칸이 평문으로
렌더링돼 그림을 표시할 자리가 없다. 그림이 필요한 내용은 Case 나 Concept 에 담고 `관계`
**`assets` 는 본문이 있는 Case·Concept·Setup 에만 둔다.** 나머지 세 종류는 칸이 평문으로
렌더링돼 그림을 표시할 곳이 없다. 그림이 필요한 내용은 본문이 있는 종류에 담고 `관계`
가리킨다.
`assets` 는 Studio 에 올릴 파일이다. 아직 안 올렸으면 key 가 파일 이름과 같고, 올린 뒤에는
@@ -80,13 +97,35 @@ evidence:
## 평문 칸 쓰는 법
본문(`bodyMarkdown`)을 뺀 모든 칸은 평문이다. 백틱·파이프·`#`는 글자 그대로 보이고 줄바꿈만
살아난다. 그 줄바꿈이 유일한 서식이므로 아끼지 않는다.
본문(`bodyMarkdown`)을 뺀 모든 칸은 **마크다운 블록 파서를 거치지 않는다.** 그렇다고 전부
글자 그대로 나오는 것은 아니다. 렌더러가 이 칸들만 따로 그리고(`tech-log-frontend`
`presentation/shared/public-render/prose-text.tsx`), 거기서 셋이 살아난다.
**무엇을 지우나.** 백틱·별표·코드펜스는 글자 그대로 보인다. 식별자는 남기고 표시 문자만 뺀다.
`InboxCleanupJob:56` 은 InboxCleanupJob:56 으로, `**this is the parameter**` 는 그 문장만 남긴다.
코드펜스 안이 `이름 : 값` 꼴이면 펜스 줄만 지우면 그대로 읽힌다. 줄바꿈이 유일한 서식이므로
문단 사이 빈 줄은 지킨다.
| 이 칸에서 | 어떻게 되나 |
|---|---|
| 백틱 쌍 | 인라인 `<code>`**살아난다.** 빼지 않는다 |
| 백틱이 홀수 개 | 짝이 안 맞으므로 원문 그대로 둔다 — 반쯤 해석하지 않는다 |
| 빈 줄 | 문단이 갈린다 |
| 한 줄 바꿈 | `<br>` 로 그 자리에 남는다 |
| 별표·파이프·`#`·코드펜스·인용 표지 `>` | **글자 그대로 보인다.** 이것들만 뺀다 |
**옛 판을 기억하지 마라.** 이 칸들은 오래 진짜 평문으로 나갔고 백틱이 백틱째 화면에
나왔다 — 어떤 Reference 는 한 문서에 백틱이 32개였고 그 원문이 카드와 검색 결과까지
퍼졌다. 그건 **고쳐진 버그**다. 지금 백틱을 빼면 식별자가 본문과 같은 민무늬로 나온다.
**무엇을 지우나.** 별표·코드펜스·`>` 는 글자 그대로 보인다. 식별자는 남기고 표시 문자만 뺀다.
`**this is the parameter**` 는 그 문장만 남긴다. 코드펜스 안이 `이름 : 값` 꼴이면 펜스 줄만
지우면 그대로 읽힌다. 백틱은 그대로 두고, 문단 사이 빈 줄도 지킨다.
**SSOT 를 그대로 옮긴 인용도 `>` 를 못 쓴다.** 인용이라는 것을 표지로 나타낼 방법이 이 칸에는
없다 — `>` 도, 들여쓰기도 안 산다. 표지를 빼고 한 문단으로 두거나, 인용이 꼭 인용으로 보여야
하면 본문이 있는 종류로 옮긴다. 「」 를 새로 씌우지 않는다. 옮긴 글자는 보호 구간이라 그대로다.
**코드펜스를 뗄 때 언어 표시 줄을 같이 지운다.** ` ```text ` 에서 펜스만 지우면 `text` 한 줄이
남고, 그 낱말이 화면에 그대로 나온다. 실제로 한 기록에서 그렇게 남아 있었다.
**칸이 어떻게 보이는지는 렌더러가 정본이다.** 이 파일이 아니다. 여기 적힌 것과 화면이
다르면 `prose-text.tsx``public-record-renderer.tsx` 를 열어서 가른다.
관계(`근거`) 절은 평문 칸이 아니다. 기록을 거는 목록이라 `- **제목**` 표기를 그대로 둔다.
@@ -116,25 +155,26 @@ issuer · audience : 검증
| 검증 환경 | `environment` | 런타임·버전·DB·도구 |
| 재현 조건 | `reproduction` | 다른 사람이 같은 결과를 얻는 방법 |
| 마지막 검증일 | `lastVerifiedOn` | 실제로 확인한 날. 30일 지나면 경고 |
| 본문 Markdown | `bodyMarkdown` | 10만 자. 본문을 갖는 종류 중 하나 |
| 본문 Markdown | `bodyMarkdown` | 10만 자. 본문을 갖는 종류 중 하나 |
공개 화면에서 `검증 환경``재현 조건``environmentSummary` 배열에 그 순서로 실린다.
## Concept — 남의 것이 어떻게 동작하는지
`새 문서` 화면에서 이 종류만 이름이 한글이다. **개념」을 고른다.** 나머지 넷은 Case·Reference·
Question·Decision 으로 적혀 있다. 화면 설명은 「개념의 내부 동작과 아키텍처를 처음부터 풀어 씁니다」다.
`새 문서` 화면에서 **동작 원리」를 고른다.** 화면 설명은 「개념의 내부 동작과 아키텍처를 처음부터
풀어 씁니다.」다. 전에 이 절은 「이 종류만 이름이 한글이다」라고 적었는데 2026-09-12 에는 여섯 다
한글이었다.
| 칸 | 필드 | 비고 |
|---|---|---|
| 본문 Markdown | `bodyMarkdown` | 10만 자. **Case 함께 본문을 갖는 종류 중 하나** |
| 본문 Markdown | `bodyMarkdown` | 10만 자. **Case·Setup 과 함께 본문을 갖는 종류 중 하나** |
| 기준 버전 | `basisVersion` | 120자. 무엇을 보고 쓴 글인지 한 줄 |
칸이 둘뿐이다. 문제도 결론도 재현 조건도 없다. 내가 재현한 결과가 아니라 **이미 그렇게 동작하는
것**을 적기 때문이다. `Authorization Code Flow 가 code 를 한 번 더 교환하는 이유`, `Forward-Auth
subrequest 가 실어 보내는 것` 같은 것이 여기 온다.
**`lastVerifiedOn` 이 없고 `basisVersion`그 자리를 대신한다.** 개념은 날짜로 낡지 않고 버전으로
**`lastVerifiedOn` 이 없고 `basisVersion`낡음을 말한다.** 개념은 날짜로 낡지 않고 버전으로
낡기 때문이다. `Keycloak 26.7.0 identity brokering`, `Kubernetes 1.31` 처럼 적는다. 비워도 게시된다.
공개 주소는 `/concepts/{slug}` 다.
@@ -143,8 +183,7 @@ subrequest 가 실어 보내는 것` 같은 것이 여기 온다.
검증도 막지 않는다(2026-09-04 에 임시 개념 기록을 만들어 비운 채로 게시해 확인했다). 그래도
채우는 편이 낫다 — 개념이 언제 낡았는지 읽는 사람이 알 방법이 이 칸뿐이다.
편집 화면 오른쪽 `작업 상태` 이 종류를 「동작 원리」라고 부른다. `새 문서` 의 「개념」과 같은
것이다.
편집 화면 오른쪽 `작업 상태` 이 종류를 「동작 원리」라고 부른다.
Case 와 헷갈리면 **내가 무엇을 했는지**를 묻는다. 내가 돌려 보고 수치를 얻었으면 Case, 남의 문서와
코드를 읽고 동작을 정리했으면 Concept 이다.
@@ -169,6 +208,87 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
시점`, `커밋 증거 상태 전이`, `fenced lease 와 CAS`, `gRPC flow control 과 backpressure`.
여러 Case 가 같은 선수 지식을 요구할 때 그것을 한 번만 설명하려고 만드는 자리다.
## Setup — 남이 따라 할 절차 (`SETUP`)
화면 이름은 「환경 구성」이고 설명은 「이 기록을 자기 손으로 재현하는 절차를 남깁니다.」다.
편집 화면은 구역 둘로 나뉜다 — 「기본 정보」와 「환경 구성」(eyebrow `SETUP`).
| 화면 이름 | 필드 | 상한·모양 |
|---|---|---|
| 고정한 버전 | `pinnedVersions` | 배열 30개. 줄마다 `이름`(1~60자) + `버전`(1~40자) 입력 둘. 「버전 추가」 버튼으로 늘린다 |
| 절차 Markdown | `bodyMarkdown` | 10만 자 |
화면에 붙은 도움말을 그대로 옮기면 이렇다.
- 고정한 버전 : `“Keycloak” / “26.7.0” 처럼 적습니다. 비우면 화면에 표를 그리지 않습니다.`
- 절차 Markdown : `“##” 소제목이 목차가 됩니다. 명령은 코드블록으로 적어야 그대로 복사됩니다.`
`SetupInput.required``[kind, bodyMarkdown, pinnedVersions]` 다.
**작업본을 만들면 본문이 비어 있지 않다.** Studio 가 절 뼈대를 미리 넣어 준다.
```text
## 실행 절차
## 구성 값
## 확인 방법
```
계약의 `bodyMarkdown` 설명은 「실행 절차·구성 값·확인 방법을 `##` 절로 적는다. **절 이름을
강제하지 않는다 — 프로젝트마다 셋업의 모양이 다르다.**」다. 뼈대는 출발점이고, 절 이름은 그
프로젝트가 쓰는 말로 바꿔도 저장과 게시가 막히지 않는다.
### 왜 이 종류가 따로 있나
다른 다섯은 끝난 일을 적고 환경 구성만 남이 따라 할 절차를 적는다. 편집 화면 주석
(`presentation/studio/components/setup-fields.tsx:44-52`)이 그 차이를 적어 두었다.
> 다른 다섯 종류는 끝난 일을 적는다. 이 종류만 읽는 사람이 그대로 따라 하는 절차를 적으므로,
> 본문에 명령과 표가 들어간다 — Case 의 「검증 환경」 같은 평문 한 칸으로는 담기지 않는다.
> … 버전만 본문 밖에 둔다. 이 절차가 어느 버전 위에서 성립했는지는 그 기록의 유효 범위이고,
> 목록과 머리말이 본문을 열지 않고 보여 줘야 하는 값이기 때문이다.
그래서 칸이 둘뿐인데도 Concept 과 다르게 쓴다. 명령·표·그림은 본문에 넣고 버전만 본문 밖에
남긴다. 개념의 「기준 버전」도 같은 이유로 본문 밖에 있고, 다른 점은 셋업의 버전이 여럿이라는
데 있다.
**검증일 칸이 없다.** Case 의 `lastVerifiedOn` 도 Reference 의 `verifiedOn` 도 이 종류에는 없다.
공개 계약의 `SetupDetailResponse` 가 왜인지 적는다.
> 환경 구성은 끝난 일이 아니라 따라 하는 절차다. 낡음은 검증일이 아니라
> `pinnedVersions` 가 말한다 — 어느 버전 위에서 이 절차가 성립했는지가 유효 범위다.
> 주제는 없을 수 있다. 주제 없는 셋업은 그 프로젝트의 공통 구성이다.
### 프로젝트는 필수, 주제는 선택
`PROJECT_DECISION` 말고 프로젝트를 요구하는 종류가 하나 더 있다.
```text
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 — 반복 적용할 기준
| 칸 | 필드 | 비고 |
@@ -217,6 +337,10 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
## 종류 고르기
```text
남이 그대로 따라 할 절차를 적나 ── 예 ──→ Setup
아니오 (끝난 일을 적는다)
내가 직접 돌려 보고 결과를 얻었나 ── 예 ──→ Case
아니오
@@ -236,6 +360,11 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
└──→ Reference
```
첫 갈래가 「끝난 일을 적나, 남이 따라 할 절차를 적나」다. 나머지 다섯은 이미 끝난 일을 적고,
Setup 만 읽는 사람이 자기 기계에서 실행할 순서를 적는다. 편집 화면 주석이 그 경계를 「Case 의
「검증 환경」 같은 평문 한 칸으로는 담기지 않는다」로 적는다 — 명령이 여러 줄이고 그대로
복사돼야 하면 Case 의 평문 칸이 아니라 Setup 의 본문에 들어간다.
Case 와 Concept 이 가장 자주 헷갈린다. **수치와 재현 조건이 있으면 Case**, 읽고 정리한 동작이면
Concept 이다. Concept 과 Reference 는 「무엇이 그런가」와 「우리가 어떻게 할 것인가」로 갈린다 —
`PKCE 가 code 가로채기를 막는 방식`은 Concept, `우리는 public client 에 PKCE 를 필수로 쓴다`
@@ -27,6 +27,25 @@
- [ ] Decision에 근거 기록이 1개 이상 연결됐다
- [ ] Decision의 영향에 감수한 비용이 있다
- [ ] Reference의 예시가 문장이다. 코드를 밀어 넣지 않았다
- [ ] Setup이 끝난 일이 아니라 남이 따라 할 순서를 적었다
- [ ] Setup의 명령이 코드블록에 있다. 산문에 섞지 않았다
- [ ] Setup의 `pinnedVersions`가 채워졌다. 본문에 날짜를 적어 검증일을 대신하지 않았다
## 환경 구성의 명령 (`writing-practitioner-guides`)
명령을 어떤 형태로 쓸지는 그 스킬이 정한다. 여기서는 그 결과가 본문에 남았는지만 센다.
- [ ] 사람이 내용을 읽고 고쳐야 하는 설정 파일을 에디터로 열게 했다. `printf >`·`echo >>`·`cat <<EOF` 로 만들지 않았다
- [ ] 한 명령에 여러 작업이 겹치지 않았다. 접속·리다이렉션·권한·파일 생성을 한 줄에 묶지 않았다
- [ ] 그 도메인의 전용 검증 명령이 있는데 `python3 -c` 로 대신하지 않았다
- [ ] 다시 따라 해도 같은 줄이 또 붙지 않는다. 두 번 실행하면 늘어나는 명령이 없다
- [ ] 조회·진단·실행은 운영자가 쓰는 CLI 를 그대로 썼다. `grep`·`virsh`·`systemctl` 을 에디터로 바꾸지 않았다
- [ ] 단계마다 목적·행동·예상 결과·왜 필요한가·문제가 생기면이 같은 순서로 있다
- [ ] 셸이 여럿이면 코드블록마다 `label` 로 어디서 치는지 적었다
- [ ] 자리표시자가 없다. 값을 찾는 명령이 함께 있다
- [ ] 앞 단계가 찍은 값을 옮겨 넣는 자리만 예외다. 그 자리는 `{{NAME}}` 으로 적었다. 한글로 감싸지도 `${NAME}` 으로 적지도 않았다
- [ ] 비밀은 길이나 존재 여부까지만 확인하고 값을 찍지 않았다
- [ ] 명령의 형태를 고쳤으면 SSOT 를 먼저 고쳤다. `check_evidence.mjs` 가 통과한다
## 설명
@@ -80,7 +99,7 @@
- [ ] 테스트를 말할 때 무엇을 단정하는지 적었다
- [ ] 어미·절·안내 문장의 수치는 참고만 했다. 맞추려고 문장을 넣지 않았다
## 본문 (Case)
## 본문 (Case · Concept · Setup)
- [ ] raw HTML·각주·체크박스·중첩 목록·취소선이 없다
- [ ] 표를 파이프로 썼다. 감쌌다면 세 속성이 다 있다
@@ -99,7 +118,8 @@
## 평문 칸
- [ ] 본문 밖 칸에 백틱·파이프가 없다
- [ ] 본문 밖 칸에 별표·파이프·`#`·코드펜스·인용 표지 `>` 가 없다 (**백틱은 괜찮다** — 인라인 `<code>` 로 산다)
- [ ] 코드펜스를 뗀 자리에 언어 표시(` ```text ``text`)가 남지 않았다
- [ ] 나열을 쉼표로 잇지 않고 `이름 : 값`으로 끊었다
- [ ] 있음·없음을 `o`·`x`로 적었다
- [ ] 한 문장이 화면에서 두 줄을 넘지 않는다
@@ -107,7 +127,7 @@
## 연결
- [ ] Topic이 지정됐다
- [ ] Project가 필요하면 지정됐고, 그 Project에 slug가 있다
- [ ] Project가 필요하면 지정됐고, 그 Project에 slug가 있다 (Decision과 Setup은 필수다)
- [ ] 관계의 대상이 실제로 있는 공개 기록이다
- [ ] 코드·표·그림이 필요한 내용을 Reference나 Question에 밀어 넣지 않았다
- [ ] 본문이 없는 세 종류에 `assets`를 선언하지 않았다
@@ -73,7 +73,7 @@ section it names exists, and the diagram stage cannot translate the anchor into
## Topics
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the five
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the six
record kinds.
```json
@@ -81,13 +81,13 @@ record kinds.
"topic": "oauth-oidc-auth-boundary",
"title": "OAuth 자격증명과 세션의 보관 경계",
"readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
"kinds": { "case": [], "concept": [], "reference": [], "question": [], "decision": [] }
"kinds": { "case": [], "concept": [], "setup": [], "reference": [], "question": [], "decision": [] }
}
```
Every node in the Topic must help answer the reader question. Two Topics do not share a
question; one Topic does not need two. Empty kinds are allowed — do not manufacture nodes
to fill all five.
to fill all six.
## Candidates
@@ -178,6 +178,26 @@ be stale.
A Concept exists because a Case, Decision, or Question needs it to be understood. Absence,
call-counts, unwired subsystems, analysis scope, and coverage ledgers are not Concepts.
### Setup
`slug` · `readiness` · `source` · `classification` · `pinned-versions` · `relations`.
Setup is the one kind that does not report a finished result. It is a procedure a reader
runs on their own machine, so `pinned-versions` states the versions the procedure was
established on — the same job `basis-version` does for a Concept, except there is more than
one of them. There is no verification date for this kind.
A Setup node also needs a project. `SETUP` and `PROJECT_DECISION` are the two kinds Studio
refuses to save without one; a Topic is optional, and a Setup with no Topic reads as that
project's shared configuration.
`verify-tech-log-tree.py` checks this kind like the others: `REQUIRED_FIELDS["setup"]` names
the six fields above and `GENERATABLE["setup"]` is `READY`. The kind list those tables are
keyed on lives in `techlog.KINDS`, which also decides which `<topic>/<kind>/` folders
`build-tech-log-tree.py` scans. Add a kind in one place and the tables that key off it go
quiet rather than failing — a kind missing from `REQUIRED_FIELDS` is not an error, it is a
node nobody asks anything of.
### Reference
`slug` · `readiness` · `source` · `classification` · `scope` · `exceptions` · `relations`.
@@ -7,18 +7,21 @@
Question 4·Decision 2), n+1liner 24건(Case 5·Reference 7·Question 5·Decision 7).
「대개 이렇게 쓴다」는 말은 그 47건이 그렇게 돼 있다는 뜻이다.
**Setup 은 그 47건에 없다.** 2026-09-12 에 Studio 의 환경 구성 문서가 0건이었다. 그래서 Setup
절은 계약과 편집 화면에서 읽어 썼다. 나머지 다섯처럼 올라간 기록을 세지 않았다.
`clean-architecture-backend-template` 의 949건은 다른 절 이름으로 쓰여 있었고, 지금 이 기준으로
다시 판정하는 중이다. 아직 기준을 만족한 상태가 아니므로 그 949건을 본보기로 삼지 않는다. 올라간
적 없는 초안이 아니라 **올라간 것**이 기준이다.
## 파일 뼈대 — 섯 종류가 같다
## 파일 뼈대 — 섯 종류가 같다
```markdown
---
id · kind · slug · title · topic · topicName · project · status · studio
source · sourceRevision
(종류별) basisVersion · decisionStatus · questionStatus · lastVerifiedOn
(있으면) evidence · assets — assets 는 Case Concept 만
(종류별) basisVersion · decisionStatus · questionStatus · lastVerifiedOn · pinnedVersions
(있으면) evidence · assets — assets 는 Case · Concept · Setup
---
# 제목
@@ -27,7 +30,7 @@ source · sourceRevision
## 관계 ← Decision 만 「근거」다
## <칸 이름> ← 종류마다 다르다
## 본문 ← Case · Concept 만
## 본문 ← Case · Concept · Setup
<!-- body:start -->
...
<!-- body:end -->
@@ -110,6 +113,159 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
「확인했다」는 Case 의 말이다. Concept 은 「~한다」로 적는다. 규격이 정한 것과 그 구현이 그렇게 한
것을 구분한다.
## Setup — 0건
칸은 `관계` · `본문` 둘뿐이고, 고정한 버전은 frontmatter 의 `pinnedVersions` 에 있다. 계약의
`PinnedVersion``name``version` 을 나눠 담는다 — 이름 1~60자, 버전 1~40자, 30개까지.
```yaml
pinnedVersions:
- name: Keycloak
version: 26.7.0
```
**절 이름을 강제하지 않는다.** 계약이 그렇게 적는다 — 「실행 절차·구성 값·확인 방법을 `##`
절로 적는다. 절 이름을 강제하지 않는다 — 프로젝트마다 셋업의 모양이 다르다.」 그래도 빈 본문에서
시작하지는 않는다. 작업본을 만들면 Studio 가 절 셋을 미리 넣어 주므로 거기서 출발한다.
```markdown
## 실행 절차
## 구성 값
## 확인 방법
```
**명령은 코드블록으로 적는다.** 절차 Markdown 칸의 도움말이 이유를 적는다 —
`“##” 소제목이 목차가 됩니다. 명령은 코드블록으로 적어야 그대로 복사됩니다.` 읽는 사람이
자기 기계에서 실행하므로, 산문에 섞어 적으면 복사할 때 프롬프트 기호와 설명이 함께 붙는다.
Case 와 무엇이 다른지는 **읽는 사람이 무엇을 하는가**로 갈린다. Case 의 「재현 조건」은 내가 잰
값을 남이 다시 얻는 순서이고, Setup 의 본문은 그 환경을 처음 세우는 절차다. Case 는 평문 한 칸에
그 순서를 담지만 Setup 은 본문을 쓰므로 명령·표·그림이 들어간다.
**검증일을 쓰지 않는다.** 이 종류에는 그 칸이 없다. 절차가 어느 버전 위에서 성립했는지는
`pinnedVersions` 가 말하므로, 본문에 「2026-09-12 기준」 같은 날짜를 적어 대신하지 않는다.
`project` 는 비울 수 없다. `topic` 은 비워도 되고, 비우면 그 프로젝트의 공통 구성으로 읽힌다.
### 본문을 쓰기 전에 `writing-practitioner-guides` 를 연다
`Skill` 도구로 `writing-practitioner-guides` 를 부른다. 명령을 어떤 형태로 쓸지는 그 스킬이
정한다 — 한 줄에 어느 계층까지 담는지, 어떤 도구를 먼저 잡는지, 출력을 읽는 형태와 값 하나만
뽑는 형태를 어떻게 가르는지, 넓은 명령에서 좁은 명령으로 내려가는 순서, 무엇이 보이면 멈추고
다시 쓰는지가 거기 적혀 있다. 그 규칙을 이 문서로 옮겨 적지 않는다. 같은 규칙이 두 곳에 있으면
한쪽만 고쳐지고 둘이 갈린다.
다른 다섯 종류에는 이 절차가 없다. 나머지는 끝난 일을 적으므로 명령이 나와도 그때 무엇을 쳤는지
보여 주는 인용이고, 읽는 사람이 자기 기계에서 그것을 치지 않는다. 환경 구성은 읽는 사람이 그대로
따라 치므로 명령의 형태가 내용의 일부다. `echo 'export ...' >> ~/.bashrc` 는 결과를 만들지만
읽는 사람이 `~/.bashrc` 를 한 번도 열어 보지 못하고, 같은 가이드를 다시 따라 하면 같은 줄이
하나 더 붙는다.
기계적으로 바꾸는 방향도 틀린다. 조회·진단·실행은 운영자가 쓰는 CLI 를 그대로 쓴다 —
`grep`·`lsmod`·`virsh`·`systemctl`·`journalctl`·`kubectl` 이 들어갔다는 것 자체는 문제가
아니다. 사람이 내용을 읽고 고쳐야 하는 설정 파일을 만드는 대목에서만 에디터로 연다.
### 단계 하나의 모양
`writing-practitioner-guides` 의 「Shape of one step」은 상태를 읽는 단계의 모양이다. 환경 구성의
본문은 상태를 바꾸는 단계가 대부분이고, 그쪽은 칸이 다섯이다.
```text
### N. <이 단계가 무엇을 만드는가>
목적 한 줄. 이 단계가 끝나면 무엇이 달라지나
행동 번호를 매긴 명령. 한 번호에 한 가지 일
예상 결과 그때 화면에 나오는 것
왜 필요한가 건너뛰거나 어긋나면 무엇이 깨지나
문제가 생기면 어느 명령부터 다시 보나
```
번호는 행동을 세려고 매긴다. 한 번호가 접속과 파일 생성과 권한 설정을 함께 하면 읽는 사람은
어디까지 왔는지 셀 수 없고, 실패해도 그 줄의 어느 대목에서 실패했는지 모른다. 다섯 칸이 단계마다
같은 순서로 오면 처음 따라 하는 사람이 단계마다 같은 곳에서 같은 것을 찾는다.
채운 예는 `writing-practitioner-guides` 에 있다 — libvirt 연결 URI 를 `qemu:///system` 으로
고정하는 단계다.
### 자리표시자
**원칙은 자리표시자를 두지 않는 것이다.** `writing-practitioner-guides` 의 「No placeholders」가
이유를 적는다 — `<토큰>` 이라고 적어 두면 그 값을 어디서 가져오는지가 문서 밖으로 나간다. 값을
뽑는 명령을 먼저 주는 것이 먼저다.
**예외는 하나다 — 같은 기록의 앞 단계가 화면에 찍은 값을 뒤 단계에 옮겨 넣을 때.** 세션 `sid`
로그인할 때마다 새로 생기고 클라이언트 UUID 는 렐름을 만들 때 정해져서 읽는 사람의 실험대에서
다르다. 앞 단계가 탐침 파드 안에서 돌았으면 그 셸의 변수가 뒤 단계의 셸에 없어서 변수로 넘길
수도 없다. 값을 만드는 명령은 이미 같은 문서 안에 있으므로 전역 스킬의 빨간 깃발
(`<placeholder>` with no command that produces it)에는 걸리지 않는다.
**그때 쓰는 꼴은 `{{NAME}}` 하나다.** `NAME` 은 대문자로 시작하고 대문자·숫자·밑줄만 쓴다.
```bash label="[kc-lab-1] ② 읽은 값을 그대로 넣어 행을 찾는다"
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
-c "select user_session_id, created_on, last_session_refresh from offline_user_session
where offline_flag='0' and user_session_id='{{SID}}'"
```
**`${SID}` 로 쓰지 않는다.** 같은 가이드들이 `$SID`·`$K0`·`$TOK`·`$PW` 를 진짜 셸 변수로 쓴다.
자리표시자를 셸 변수 꼴로 적으면 읽는 사람이 「이 변수는 이미 셸에 있다」로 읽고 그대로 붙여
넣는다. `{{ }}` 는 셸 문법이 아니라서 붙여 넣으면 반드시 틀리고, 틀린 자리가 화면에 보인다.
**한글로 감싸지 않는다.** `'<① 이 찍은 sid>'` 는 사람에게는 읽히지만 검사기가 그 줄을 흐름도로
오인하기 쉽고, 무엇보다 자리표시자 바깥의 SQL 이 SSOT 와 갈려도 드러나지 않는다. 어느 단계가 그
값을 찍었는지는 코드블록 바로 위 산문에 적는다 — 「sid 는 ③ 이 `SID=` 로 화면에 찍은 값을 옮겨
넣는다」처럼.
**검사기는 그 자리만 와일드카드로 본다.** `{{SID}}` 는 따옴표도 공백도 넘지 않는 값 하나로
열리고 나머지는 한 글자씩 SSOT 와 대조된다. 그래서 `user_session_id` 를 `user_session_idx` 로
잘못 적으면 그대로 걸린다.
### 이 저장소에서만 걸리는 것 셋
전역 스킬은 Tech Log 의 검사기도 SSOT 도 모른다.
**① 명령의 형태를 고치려면 SSOT 를 먼저 고친다.** `check_evidence.mjs` 가 본문 코드블록의 줄을
`final/document.md` 와 대조한다. `bash`·`yaml`·`nginx` 처럼 언어를 적은 펜스는 줄 단위로 보고,
`text`·`txt`·`console`·`diff` 펜스와 언어를 안 적은 펜스는 그 안의 경로·URL·점 있는 식별자만
본다. 그래서 `sudo nano /etc/letsencrypt/cloudflare.ini` 를 SSOT 에 없는 채로 넣으면 「인용한
코드가 SSOT 에 없다」로 막힌다.
대조에서 빠지는 줄도 있다. 20자 미만인 줄, `#`·`//`·`|`·`>` 로 시작하는 줄, 그리고 한글과 흐름
글리프(``·``·``·``)가 함께 있는 줄 — 필자가 그린 흐름도 — 을 건너뛴다. `nano ~/.bashrc` 는
14자라 대조 없이 통과한다. 통과했다는 것과 SSOT 에 있다는 것은 다르므로 짧은 명령도 사람이
SSOT 에서 찾아 대조한다.
**한글이 섞였다는 것만으로는 안 빠진다.** 예전에는 그랬고, 그래서 자리표시자를 한글로 감싼
`psql -c "…"` 한 줄이 통째로 대조에서 빠졌다. 지금은 흐름 글리프까지 있어야 흐름도로 본다.
순서는 SSOT 가 먼저다. 구축 절차를 담은 부(virtualization 은 제6부 §184~§194)에 사람이 치는
형태를 적고, 그 형태도 원 가이드나 저장소에서 확인한 뒤에 적는다. 기록을 먼저 고치고 검사기가
막을 때 SSOT 를 맞추면 SSOT 가 근거이기를 그만두고 기록의 사본이 된다. CLAUDE.md 의 「보강은
상류 원문으로 한다」가 같은 말이다.
형태를 바꾸는 것과 사실을 바꾸는 것은 다르다. 원 가이드가 `printf ... > meta-kc-lab-1` 로
적었다면 그 파일에 무엇이 들어가는지는 원문이 이미 갖고 있으므로, SSOT 에는 그 내용을 파일
목록으로 옮기고 파일을 여는 명령을 앞에 둔다. 원문이 만들지 않은 파일이나 재지 않은 출력을
새로 만들지 않는다.
**② 셸이 여럿인 실험대에서는 코드블록마다 어디서 치는지 붙인다.** 산문에 한 번 적어 두면 따라
하는 도중에는 안 보인다. 본문 파서가 코드블록의 `label` 을 받으므로 거기에 적는다.
```bash label="[lab host] 저장소 루트에서 친다"
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
```
virtualization 의 SSOT §185 ③ 이 그 표시를 다섯으로 정해 두었다 — `[워크스테이션]` ·
`[lab host]` · `[kc-lab-edge]` · `[kc-lab-1]` · `[kc-lab-2]`. 표시가 없으면 같은 명령이 다른
기계에서 다른 결과를 낸다.
**③ 비밀은 길이와 존재 여부까지만 적는다.** 값을 찍는 명령을 본문에 두지 않는다. 토큰이 필요한
단계는 값을 찾는 명령을 주고 `echo "${#TOKEN} 자"` 로 끝낸다. `final/evidence/` 에 올리는 원문에도
값이 들어가지 않도록 명령을 짜는 것이 먼저다. 터미널 렌더러가 Bearer·Cookie·token·password 를
`[REDACTED]` 로 바꾸지만 그 앞에서 막는다.
## Reference — 14건
칸은 `관계` · `목적` · `규칙` · `적용 조건` · `예외` · `예시`. 14건 모두 여섯 칸을 채웠다. 본문이 없다.
@@ -124,7 +280,8 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
규칙 제목만 읽어도 무엇을 금지하는지 알아야 한다. 「지표를 정확히 읽는다」보다
「지표 이름이 뜻하는 것을 그대로 읽는다」가 낫다. 코드가 필요하면 그 코드가 있는 Case 를 만들고
`관계`로 가리킨다 — Reference 칸은 평문이라 백틱이 글자로 보인다.
`관계`로 가리킨다 — Reference 칸은 마크다운 블록 파서를 안 거쳐서 코드펜스가 글자로 보인다.
낱말 하나짜리 식별자는 백틱으로 감싸면 인라인 `<code>` 로 살아난다. 여러 줄짜리 코드가 문제다.
## Question — 9건
@@ -151,7 +308,7 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
## Decision — 9건
칸은 **`근거`** · `결정문` · `판단 이유` · `영향`. **다른 과 달리 관계 절 이름이 「근거」다.**
칸은 **`근거`** · `결정문` · `판단 이유` · `영향`. **다른 다섯과 달리 관계 절 이름이 「근거」다.**
`decisionStatus` 는 frontmatter 에 있다. 근거가 1개 이상 없으면 게시가 거절된다.
| 칸 | 무엇을 |
@@ -163,14 +320,14 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
대안을 적고 왜 고르지 않았는지 적는다. 대안이 없으면 결정이 아니라 사실이다. 자료가 뒷받침하지
않는 이유를 만들지 않는다.
## 본문이 있는 종류의 공통 규칙
## 본문이 있는 종류의 공통 규칙
- **그림이 필요한지와 무엇을 그릴지는 `choosing-a-diagram.md` 가 정한다** — 종류마다 그림이
답해야 할 물음이 다르다. Case 는 순서, Concept 은 구조나 변환 사슬이 대개 논지다
- 그림은 `technical-visualizer` 로 만든다. 손으로 SVG 를 그리지 않는다
- 그림 안에는 이름만 넣는다. 문장은 `<desc>` 와 옆 문단에 둔다
- 그림과 증거는 frontmatter 의 `assets` · `evidence` 로 잇는다. 같은 파일을 기록 옆에 복사하지 않는다
- `assets` 는 본문이 있는 종류만 갖는다. Reference·Question·Decision 은 그림을 렌더링할 자리가
- `assets` 는 본문이 있는 종류만 갖는다. Reference·Question·Decision 은 그림을 렌더링할 곳이
없어서 선언해도 화면에 나오지 않는다
- 본문은 `<!-- body:start -->` 와 `<!-- body:end -->` 사이다. 그 밖은 Studio 로 가지 않는다