# M6 구현 노트 — 관계 축 계약 M6 (문서 트리 개정)

계획서: `.claude/plans/insuniverse-docs-13-relation-contract-m6.plan.md`

리뷰 기록을 **계획서가 아니라 여기에** 둔다 — 계획서 본문을 고치면 `plan_hash` 가 움직여
`mccp-plan-codex` receipt 가 stale 로 떨어진다(저장소 실측 이력).

## Codex Implementation Review

- 호출: `node C:/Users/madsc/.claude/plugins/cache/mccp/mccp/1.29.0/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, v0.2.2)
- 라운드 수: 1 (`MCCP_REVIEW_SINGLE_PASS=deadline_pressure` 로 cap=1 고정 — `.claude/settings.json` 의 의도된 설정이며 그 사실은 backlog `2026-08-19 MEDIUM` 행으로 이미 기록돼 있다. 승격 조건도 미충족이다 — 4건 전부 MEDIUM 이라 `ACCEPT_NOW ∧ severity ∈ {CRITICAL,HIGH}` 가 0건)
- 합치 결론: 4건 전부 실질이고 **전부 같은 부류다 — 게이트가 산출물의 한 축만 보고, 그 옆축은 무엇을 써도 초록이다.** 네 건 다 **산출물 축은 ACCEPT_NOW 로 이번 창에서 닫고, 게이트 축은 DEFER 한다.** 게이트를 고치려면 계획서 §Validation 을 편집해야 하고 그 순간 `plan_hash` 가 움직여 `mccp-plan-codex` receipt 가 stale 이 된다 — implement 단계가 할 수 없는 일이며, 이것이 이 분리의 기제다.

- YAGNI Triage:

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 IA 행 문면이 미정이고 어떤 게이트도 설명 열을 안 본다 | MEDIUM | ACCEPT_NOW (산출물) / DEFER (게이트) | 두 파서 다 `.md` 토큰만 뽑아 설명이 서로 달라도 통과한다. **정본·사본의 IA 행 2개를 설명 열까지 바이트 동일**로 넣고 구현 시점에 raw 행 대조로 확인한다. 게이트 `#1` 에 raw 행 비교를 더하는 것은 계획서 편집 |
  | F2 `#2` 가 `cr == 0` 을 성공으로 받아 CRLF 전면 정규화를 통과시킨다 | MEDIUM | ACCEPT_NOW (산출물) / DEFER (게이트) | 사실이다. 착수 시점 `docs/00-index.md` 는 전 행 CRLF 이므로 **구현 시점에 `cr == tot ∧ tot > 0` 을 직접 단언**하고 편집 전후 CRLF 행 수를 대조한다. `cr == 0` 분기 제거는 계획서 편집 |
  | F3 fix-task 투영이 임의 문면이어도 `#6` 이 통과한다 | MEDIUM | ACCEPT_NOW (산출물) / DEFER (게이트) | `#6` 은 파일명 3개의 부분문자열만 본다 — 한 문장에 세 이름을 몰아넣어도 통과. M6 에는 M4·M5 의 `<plan>.gates.mjs materialize-fixtask` 같은 materializer 가 없다. **투영을 backlog 3행에서 1:1 로 유도**하고 항목마다 날짜·심각도·인수처를 backlog 행과 같은 값으로 적는다. 헤더에 "기계 생성물" 이라 적지 않는다 — M6 은 생성기가 없고 거짓이 된다 |
  | F4 findings 기록이 누락되거나 6항목이 비어도 M6 게이트가 안 잡는다 | MEDIUM | ACCEPT_NOW (산출물) / DEFER (게이트) | `verify.sh` 의 findings 검사는 **이미 있는 항목의 구조**만 본다. M6 acceptance 에 "신규 항목 1건" 을 요구하는 검사가 없다. **6항목을 README §기록 형식 그대로 채우고** `근거 앵커` 에 동결 SHA 를 병기한다. 신규 항목 유일성 게이트는 계획서 편집 |

- 산출물 축 흡수의 반사실 (구현 중 실행)
  - F1 — 정본·사본에서 `00-ia.md` 를 포함하는 raw 행을 뽑아 `diff` 가 빈 출력임을 확인한다. 설명을 한쪽만 바꾸면 그 diff 가 비지 않는다 (게이트 `#1` 은 여전히 통과하므로 이 대조가 유일한 관측이다)
  - F2 — 편집 전 `cr`/`tot` 를 기록하고 편집 후 `cr == tot ∧ tot == 편집전 tot + 2` 를 확인한다. LF 로 정규화되면 `cr` 이 0 이 되어 뒤집힌다
  - F3 — 투영 3항목의 `(날짜, 심각도, 경로, 인수처)` 를 backlog 3행에서 뽑은 같은 튜플과 눈으로 대조한다
  - F4 — `verify.sh --check` 의 findings 검사가 신규 항목까지 6항목 구조로 통과하는지 본다 (게이트 `#4` 안에 포함)

- Deferred to backlog: 4 — 네 건의 **게이트 축**. 전부 `.claude/plans/insuniverse-docs-13-relation-contract-m6.plan.md` §Validation 이 인수처이고 발동 신호는 같다: 이 계획서가 다음에 `/mccp:plan` 으로 재진입하는 시점이거나, 같은 검사를 재사용 검증기로 승격하는 시점. → `.claude/plans/codex-findings-backlog.md` + `.claude/state/fix-task.d/insuniverse-docs-13-relation-contract-m6.md`
- Open Questions: 없음
- Codex session 참조: `01a01c88-0d3c-7d43-95e6-58b0cca51319`

### Security Reviewer

해당 없음 — 산출물이 마크다운 트리 2행 · 계수 산문 · golden 동결값 · 기록 3종이고
auth/crypto/secrets/입력검증/injection/SSRF/경로순회/권한상승 어디에도 닿지 않는다.
자동 fallback 이 아니라 **적용 대상 아님**이므로 `security_skipped` 를 세우지 않는다.

### Design Review

`impeccable-detect --mode implement` → `design_signal: false` · `silent_skip: true` ·
`reason: no-signal`. `skill_available` 은 true 이므로 silent-skip 행이며 receipt 에
`--impeccable-silent-skip --impeccable-silent-skip-reason no-signal` 로 정직하게 적는다.
계획서 §Design Critique 가 같은 판정을 이미 내렸다(렌더 표면 0).

---

## Local code-review 흡수 (`/mccp:code-review`, 미커밋 변경 전수)

M5·M6 미커밋 변경 전수를 로컬 모드로 리뷰해 8건을 냈고 **전건 수용**했다. 산출물은 전부
`.claude/templates/doc-kit/**` · `docs/_meta/verify/verify.sh` · `.gitignore` 이며
계획서 본문은 건드리지 않았다(`plan_hash` 보존).

| # | 심각도 | 지적 | 흡수 | 반사실 |
|---|---|---|---|---|
| 1 | HIGH | `template-config.schema.md` 가 `deferred_ref_targets` 원소의 상대경로를 **참조 문서 기준**이라 적었다. 코드(`deferKey(ctx.configDir, …)`)·README·config 주석 셋은 **config 파일 기준**이다 | 스키마 문면을 코드 쪽으로 정정하고 두 기준이 갈리는 이유·함정을 명시 | fixture 대조: config 원소 `../99-deprecated/…` ↔ 문서 참조 `../../99-deprecated/…` 가 일치한다 = 기준이 서로 다르다 |
| 2 | MEDIUM | 주석은 "(3) 경계 역전은 refreeze 와 무관하게 돈다" 인데 `if (stale.length && !refreeze)` 가 결과를 버려 재동결을 못 막았다. 같은 블록의 manifest 부분읽기 검사도 refreeze 에서 꺼져 있었다 | `stale`(재동결이 해소) / `hardStale`(재동결이 해소 못함) 두 통으로 분리. 경계 역전과 manifest 손실 증거를 `hardStale` 로 옮겨 refreeze 에서도 exit 3 | B2 되감은 manifest + `--refreeze` → exit 3 `경계 역전 … 재동결이 해소하지 못한다`. B1 동결에만 있는 저장소 + `--refreeze` → exit 3. B0 정상 + `--refreeze` → 재현 대조까지 도달(다른 경로임을 확인) |
| 3 | MEDIUM | `merge-base --is-ancestor` 의 exit 128(객체 없음·shallow)을 exit 1 과 접어 실행 환경 결함을 `경계 역전`(exit 3)으로 오진단. git stderr 도 버렸다 | `code === 1` 만 경계 역전, 그 밖은 git stderr 를 실어 `gaps`(exit 1) | A 가짜 head 40hex → exit 1 `조상 판정을 돌릴 수 없다 (git exit 128) — fatal: Not a valid commit name …` |
| 4 | MEDIUM | README 규칙 수 4개가 "게이트가 유도해 대조한다" 고 적었으나 `RULE_SCOPES` 소비자는 M5 일회용 하네스뿐. `verify.sh` 는 0건 참조 — 마일스톤이 닫히면 같은 부패가 재개된다 | 대조를 `--selftest` 로 옮겼다(= `verify.sh` template-config 게이트가 매번 도는 상시 지점). 분류 합 == 전수 검사도 함께. `RULE_SCOPES`/`RULE_NAMES` 정의를 진입점 가드 **위로** 이동(TDZ 회피) | README 총수를 20→21 로 훼손 → `FAIL README 규칙 총수 — 산문 21 / 유도 20`, selftest exit 1. 원복 후 exit 0 |
| 5 | LOW | `.claude/tmp/` 가 미추적 + gitignore 밖이라 스크래치 JSON 2건이 `git add -A` 에 딸려간다 | `.gitignore` 에 `.claude/tmp/` 추가 | `git check-ignore -v` 가 `.gitignore:16` 을 낸다 |
| 6 | LOW | `--probe=history` 의 `matched` 가 접두 **집합** 크기라 같은 접두가 두 행에 있으면 `rows ≠ matched+orphan+unknown+ambiguous` | `matched` 를 행 단위 계수로 | 동결 16행 그대로 일치(중복 접두 0건이라 값 불변) — 계약만 복원 |
| 7 | LOW | `tc_guard` 가 maplist·blocklist 문면을 합쳐 `statuses` 위반에도 "블록형" 이라고만 낸다 | `case "$kind"` 로 형태 문면 분리 | `bash -n` 통과, `shape` 를 `local` 에 등록 |
| 8 | LOW | `insideRoot` 의 realpath 실패(권한·IO)를 경계 위반으로 보고 — 고칠 곳이 문서가 아닌데 문서를 가리킨다 | `rootBoundaryError` 신설(통과면 null, 아니면 **사유 문자열**). fail-closed 는 유지하고 사유만 갈랐다. 호출부 10곳이 사유를 그대로 싣는다 | selftest `bad-ref-escape` · `bad-config-escape` 여전히 지정 규칙으로 FAIL |

- 재검증: `lint-docs.mjs --selftest` exit 0 (README 대조 6단언 신규) · `verify.sh --check --bootstrap` exit 0 · `bash -n verify.sh` · `node --check`
- M5 하네스 `check-all`: `#1`~`#11` 통과, `#12` FAIL. **이번 수정과 무관하다** — `#12` 는 `golden.pre-m5` 를 기준선으로 `index.entry_count`·`doc_count`·`tree_sha256` 불변을 요구하는데, 그 3키는 M6 가 `docs/00-index.md` 에 IA 2행을 넣으며 51→53 으로 바꾼 값이다(M6 계획서 게이트 `#4` 가 "변동이 3키뿐" 으로 승인한 바로 그 delta). 마일스톤을 넘어간 일회용 하네스가 낡은 기준선을 보는 알려진 조건이며, 이번 창의 산출물은 golden 도 `00-index.md` 도 건드리지 않았다.
