Files
document-haness/.agents/skills/writing-tech-log-records/references/tech-log-tree-contract.md
T
DongHyeonkaandClaude Opus 5 ab59130196 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>
2026-09-17 11:02:02 +09:00

12 KiB

Tech Log Tree Contract

tech-log-tree.json is the explicit boundary between deep project analysis and Tech Log record generation. It is the decomposition contract and the index at once, and it is the source of truth. There is one file, so nothing can disagree with it.

A finished tech-log-studio/ holds tech-log-tree.json and the record folders. Nothing else.

A project whose index predates this contract fails verification with one error until it is migrated. The per-field checks stay off for such a project — "not written yet" must not read as "written wrong" — but non-adoption itself is counted, because a warning lets an old index avoid every check indefinitely.

Required top level

  • schemaVersion
  • project
  • ssot and ssotSha256 — the hash prevents treating a tree derived from old material as current
  • sourceRevision
  • generatedAt
  • sourceRepositorypath, revision, and verified: which checkout the analysis read, which commit the document describes, and how that was confirmed. Leave revision null rather than inventing one; the verifier warns instead of accepting a made-up label. When the work is spread over branches rather than one line of commits, use revisions — a label to commit map — and pin every tip the document describes
  • candidateScope — which part of the SSOT candidates may come from
  • contract — the decomposition rules, readinessValues, dispositionValues
  • topics, candidates, counts, unlisted

Candidate scope

A folded final/document.md carries the integrated analysis, the module analyses, and the analysis material in one file. Only the first is candidate material.

"candidateScope": {
  "document": "final/document.md",
  "sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
  "excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
}

document names the SSOT and must match ssot. sections names the candidate scope, and excluded names the parts that are evidence rather than candidates. A node may cite an anchor from an excluded part in source; it may not exist because of one.

excludedAnchorPattern is optional and is the only field the verifier can act on. Without it the rule above is a sentence nobody enforces — the check that a node did not come only from outside the scope is switched off entirely.

"candidateScope": {
  "document": "final/document.md",
  "sections": ["§3", "§4"],
  "excluded": ["제2부 — 모듈 분석 전문"],
  "excludedAnchorPattern": "#a[0-9]+$"
}

It is a regular expression matched against each source anchor. A node whose anchors all match it is an error: it was promoted from evidence, not from the candidate scope.

Anchors resolve to real sections

source and sourceRefs anchors are checked against the SSOT's own headings when the project writes them as heading slugs (#검토한-선택지와-막힌-지점-ap1 = the h2 slug plus a discriminator). Keep one anchor style per project. A project that numbers its anchors (#§1.1, #10-2, #a18) gets a warning instead — the verifier cannot tell whether the section it names exists, and the diagram stage cannot translate the anchor into a techviz prepare --heading value without a person reading it.

Topics

Each Topic has topic (its key), title, readerQuestion, and kinds with the six record kinds.

"oauth-oidc-auth-boundary": {
  "topic": "oauth-oidc-auth-boundary",
  "title": "OAuth 자격증명과 세션의 보관 경계",
  "readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
  "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 six.

Candidates

Everything the analysis found lives in candidates with its disposition — .agents/skills/deriving-tech-log-root-tree/references/candidate-disposition.md. Only PROMOTE candidates become nodes under topics, and the relation runs both ways: a PROMOTE candidate whose target is not a node, and a node no PROMOTE candidate points at, are both contract errors.

dispositionReview records whether a person re-read the candidate under the independence test. PENDING means it reached the tree by recall alone, and a PENDING candidate is an error, not a warning — a record written over an unreviewed tree inherits the over-classification the disposition step exists to catch. Write records only for nodes whose candidate is PROMOTE and CONFIRMED.

Written by hand, refreshed by script

readiness · source · code · evidence · classification · relations and the rest of each kind's fields are written by a person. build-tech-log-tree.py never touches them. It refreshes only what it can read from the record files — file, publication, status, studioId, assets, assetFiles, evidenceFiles — and lists records that have no node in unlisted.

ssot-assets · ssot-evidence

The SSOT is not only final/document.md. final/assets/ holds diagrams that were already drawn, each with its canonical final/.techviz/<name>/, and final/evidence/ holds measurements that were already run. Neither produces candidates — both are assigned to candidates that already exist, and these two fields hold the assignment.

"ssot-assets": ["ap3-bff-session-flow"],
"ssot-evidence": ["raw/explain/l3-cartesian-join-plan.txt"]

ssot-assets names diagrams by file stem; the file must exist somewhere under final/assets/. ssot-evidence takes paths relative to final/evidence/. Both are written by a person and both are optional — a node that needs no picture and cites no measurement leaves them out.

What they are not optional about is follow-through. Once a node is assigned a diagram and its record is written, the record's assets must point at that file and its evidence at that path; verify-tech-log-tree.py reports the gap as an error. Assigning and then not using is the failure these fields exist to catch — without them a writer draws the picture again instead of finding the one that is already there.

verify-project-layout.py counts the other direction: SSOT diagrams and raw evidence that no record cites at all. Some of that count is correct — a four-pattern comparison diagram belongs to a Reference, and Reference has no body to render it in. The count is meant to be explained, not driven to zero.

assetLedger

That explanation lives at the top level of the index, next to candidateScope. It names what was assigned and, for everything left over, why it is left over.

"assetLedger": {
  "assigned": ["ap3-bff-session-flow", "ap3-csrf-boundary"],
  "unassigned": [
    {"asset": ["four-pattern-request-boundaries"],
     "reason": "네 패턴을 비교하는 그림이라 붙을 자리가 Reference 인데 Reference 에는 본문이 없다"}
  ]
}

A diagram left out for a reason is a normal outcome, the same way KEEP_IN_SSOT is. What is not normal is a leftover nobody looked at — that is the state where the next writer draws the picture again. Write the ledger when the count first appears, not when it grows.

Case

slug · readiness · source · classification · missing-verification · relations, plus code/evidence when the conclusion depends on them.

One problem, an observation or reproduction, a diagnosis, a conclusion that closes. Several observations that answer the same question with the same conclusion are one Case with a table or sub-sections, not several partial Cases.

Concept

slug · readiness · source · basis-version · classification · relations.

basis-version names what the explanation was written against — Keycloak 26.7.0 identity brokering, Spring Boot 3.3 auto-configuration. A Concept without it cannot be known to 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.

A Reference must be useful beyond retelling one Case. If removing the originating project's names leaves no rule, it is still a Case.

Open Question

slug · readiness: OPEN · source · known · unknown · next-verification · decision-criterion · relations.

Do not create a Question when the analysis already contains a verified answer. Move the material to Case/Reference/Decision and update the tree first.

Decision

slug · readiness · decision-status · source · decision-evidence · grounds · classification · relations.

decision-status is PROPOSED, ADOPTED, SUPERSEDED, or NOT_DECIDED. A NOT_DECIDED candidate uses NEEDS_DECISION and is not written as a Decision.

Readiness semantics

readiness is about evidence, not about publication. Whether a record has been written, and whether it has been saved into Studio, are separate facts that the generated index carries as file and publication. A published record with thin evidence is still NEEDS_EVIDENCE.

readiness meaning generation
READY grounded enough for the kind allowed
OPEN legitimate unresolved Question allowed as Open Question
NEEDS_EVIDENCE material assertion still lacks verification blocked
NEEDS_DECISION direction sounds plausible but the project has not decided blocked
BLOCKED sources are incomplete or contradictory blocked

REJECTED is not a readiness. Whether a candidate becomes a record at all is a disposition, and it lives in candidates, not on the node.

Derivation rules

  1. Discover candidates from final/document.md only. It is the whole analysis, folded in — there is no analysis/ folder to search in a finished project.
  2. Give every candidate a disposition before writing any node. KEEP_IN_SSOT is a normal outcome, and a decomposition that excludes nothing has not selected anything.
  3. Apply the independence test: if folding the record into a related Case or Concept as one section changes nothing, it is not an independent record.
  4. Take Cases, References, Decisions, and Questions first; add Concepts backwards from what those four require.
  5. Prefer several narrowly grounded Cases over one broad Case combining unrelated incidents — but merge observations that share a question and a conclusion.
  6. Extract Decisions only from explicit choice evidence: ADR, commit/history, configuration plus recorded rationale, issue/PR decision, or a user-supplied decision record.
  7. A node may relate to several siblings, but each record has one primary purpose.
  8. If new runtime evidence changes the answer, update the analysis and revise the tree before editing downstream records.

Generation and verification

python3 scripts/build-tech-log-tree.py <project>    # 파생 칸을 다시 채운다
python3 scripts/verify-tech-log-tree.py <project>   # error 0 이어야 한다