# notes — insuniverse-docs-50-domain 마일스톤 1 (착수 게이트) 구현

계획서: `.claude/plans/insuniverse-docs-50-domain-m1.plan.md`

> 이 파일이 `/mccp:prp-implement` 리뷰 기록의 자리다. 계획서 본문에 쓰면
> `mccp-plan-codex` receipt 의 `plan_hash` 가 stale 로 떨어진다.

## Codex Implementation Review

- 호출: `node C:/Users/madsc/.claude/plugins/cache/mccp/mccp/1.32.1/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, v0.2.2)
- 라운드 수: 1 (`MCCP_REVIEW_SINGLE_PASS=deadline_pressure` 가 상한을 1 로 고정 — `effectiveRoundCap` 실측 `{cap:1, pinned:true}`)
- 실행 형태: 전경 Bash 는 10분 상한에 걸려 exit 143 으로 죽었다. 배경 실행으로 옮겨 완주했다(소요 약 8분). 이 저장소의 Codex 라운드는 8~14분이 정상 구간이다
- 합치 결론: 1건 · **전건 기각(구조적 산물)**. `blocking=false` · `classification=ok` · `codex_verdict=divergent` (raw `needs-attention`)
- YAGNI Triage:
  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | I1 게이트 하네스가 워킹트리에 없어 검사 `#1`~`#11`·`check-all`·`--selfprobe` 를 하나도 리뷰할 수 없다 | HIGH | REJECTED_BY_DESIGN | **게이트 순서의 산물이지 계획·구현의 결함이 아니다.** Implement-Codex 게이트는 Phase 2.5, 즉 Phase 3 EXECUTE **이전**에 돈다. 그 시점에 Task 1 산출물이 존재하면 그것이야말로 게이트 위반이다. 같은 지적이 계획 단계에서 6라운드에 걸쳐 7회 났고(계획서 관측 13~18 의 F1·F3·F6·G2·H4·M1·M3·N3·K3) 전건 범위 밖으로 판정됐다. 근거는 CLAUDE.md §1 의 200행 규약과 선례 4건 |
- Deferred to backlog: 0
- Open Questions: 없음 (auto-CRITICAL 카탈로그 해당 없음 — 보안 경계·원자적 상태·스키마 파괴 어느 것도 아니다)
- Codex session 참조: threadId `01a0364b-889f-7161-b449-a5331d60ef07`

### 구현 후 재실행 (advisory — receipt 라운드가 아니다)

R1 이 구조적으로 아무것도 보지 못했으므로 Phase 3 완료 후 같은 래퍼를 **산출 파일 경로를 명시해** 다시 돌렸다.
receipt 의 `codex_verdict` 는 갱신하지 않는다 — 라운드 상한이 1 이고 receipt 는 게이트 시점의 라운드를 봉인한다.

- threadId `01a03677-a023-7d40-83f9-5b4c114c3ff6` · `blocking=false` · `classification=ok` · raw verdict `needs-attention`
- 5건 전부 **신규 실질**이고 전건 수용했다. R1 과 달리 이번엔 실제 코드를 봤다.

| # | Finding | Severity | Verdict | 흡수 내용 |
|---|---|---|---|---|
| P1 | `check5` 가 backlog 인수분 **삭제**를 승인한다 — 분모를 워킹트리에서 내므로 행을 지우면 분모·분자가 함께 준다 | HIGH | ACCEPT_NOW | 저장소가 이미 답을 갖고 있었다 — `verify.sh` 의 `fixtask.cohort_m8` 은 착수 시점 집합을 골든에 동결해 분모로 쓴다(M8 관측 5). 같은 형태로 **HEAD 원장**을 동결 분모로 삼고, 행 식별자를 `날짜\|심각도\|출처\|발견 앞 80자` 로 잡아 마커 추가에 불변이게 했다. HEAD 7건 전건 잔존을 매 실행 확인한다 |
| P2 | `fenceBody` 가 표제부터 **EOF 까지** 첫 펜스를 찾아, 그 절이 비어도 뒤 절의 미끼 펜스를 물어 온다 | HIGH | ACCEPT_NOW | 절 경계(`endRe`)를 인자로 받고 절 안의 펜스 경계가 **정확히 2개**일 것을 요구한다. `--selfprobe` 의 변이는 모든 펜스를 지우므로 이 경로를 못 봤다 — 반사실로 실제 이동 변이를 만들어 붉힘을 확인했다 |
| P3 | `check9` 의 `classOf` 가 `templates.length > 1` 을 전부 `all` 로 접어, 3종 중 2종만 덮는 규칙이 `20종 전부` 와 맞아떨어진다. 게다가 대조 루프가 0회 돌아도 초록이다 | HIGH | ACCEPT_NOW | `all` 을 **알려진 template 전수와의 정확 집합 일치**로 좁히고(집합은 도구 출력에서 유도), 대조한 규칙 집합이 신설 규칙 전수와 같은지를 마지막에 단언한다 |
| P4 | `check8` 이 lint 의 **종료코드를 버린다**. 대조군이 rc≠0 이어도, 처치군이 rc=0 이어도 통과했다. 처치군 위반도 `>=1` 이라 무관한 위반이 섞여도 통과 | HIGH | ACCEPT_NOW | 대조군 rc=0 · 처치군 rc≠0 · 처치군 위반 **정확히 1건** 을 단언하고, 위반 건수를 못 읽으면 추정 경로로 빠지지 않고 실패한다. 더해 처치군 config 가 **지정 키 하나만** 잃었는지를 최상위 키 집합 차집합으로 확인한다 |
| P5 | `--selfprobe` 의 호출부 축이 소비자를 **중복 제거한다** — `#7/prd-규칙표` 는 `check7`·`check9` 둘의 것인데 이름 집합이 하나로 접힌다. 그 상태에서 어떤 검사가 이미 쓰이는 다른 호출부로 갈아타도 양쪽 차집합이 빈다 | MEDIUM | ACCEPT_NOW | 등록부에 `by` (소비 함수 이름 전수)를 두고 소스에서 **소비자와 함께** 재유도해 이름으로 대조한다. 출력에도 `호출부[소비자]` 로 낸다 |

**흡수 5건 전건 반사실 검증** — 결함을 다시 심으면 그 검사가 붉어지는지 실행으로 확인했다(5/5). 원본은 바이트 백업 후 digest 로 복원을 확인했다.

| 반사실 | 게이트 출력 |
|---|---|
| HEAD 인수분 1행 삭제 | `#5 FAIL: HEAD 동결 원장에 있고 현재에 없는 인수분 1건 — 2026-08-06\|MEDIUM\|…` |
| §MVP 펜스를 뒤 절로 이동(미끼) | `#6/prd-mvp 추출 실패 — 절 안에 코드펜스가 없다` |
| 규칙표 규칙 이름 4종 변조 | `#9 FAIL: 부류 대조를 못 한 신설 규칙 4건 — flow-exits ia-exits history-columns flow-refs` |
| `dropKey` 가 두 키를 지우게 변조 | `#8 FAIL: 처치군 config 가 지정 키 말고 다른 것도 잃었다 — exit_kinds surface_exit_kinds` |
| `check9` 를 다른 호출부로 갈아탐 | `#13 FAIL: 호출부 소비자 불일치 2건 — #7/prd-규칙표: 선언 [check7 check9] / 실제 [check7] / #10/prd-마일스톤: …` |

**P3 의 절반은 반사실로 못 덮었다** — "3종 중 2종만 덮는 규칙" 을 만들려면 `lint-docs.mjs` 를 고쳐야 하고 이 PRD 에 그 위임이 없다. 실측으로 현재 부분 적용 규칙은 0건이다(`RULE_SCOPES` 에서 `1 < templates.length < 3` 인 원소 0). 그래서 그 분기는 **집합 동일성으로 구성상 닫혀 있고 반사실 실행 기록은 없다** — 숨기지 않고 적는다.

### 사후 로컬 코드 리뷰 (advisory — receipt 라운드가 아니다)

게이트 `#1`~`#13` 전건 초록 상태에서 `/mccp:code-review` 로컬 모드를 1회 더 돌렸고 6건이 나와 전건 흡수했다(H2 는 사용자 판정 대기로 기록 유지). 최상위 건은 `--selfprobe` 의 공용 표 파서 가드가 **대입문 형태의 우회를 못 잡던 것**이다 — 계수만 보고 위치를 안 봤다. 상세·반사실 3건은 구현 리포트 §I4 에.

### Security Reviewer

> security-reviewer unavailable, skipped (auto-fallback): 이 세션은 Agent tool 사용이 금지돼 있고 계획서 UI9 가 서브에이전트·workflow 를 같은 이유로 배제한다. `Task tool` 을 부르는 것 자체가 사용자 제약 위반이다.

이 강등은 receipt 에 `security_skipped=true` 로 실린다. `/mccp:pr` 의 validator 는 그 값을 **blocking** 으로 읽는다 —
이 마일스톤의 PR 은 사람이 그 사실을 보고 판정한다.

보안 표면 자체가 이 마일스톤에 없다는 사실은 별도로 기록한다: 산출은 (1) 저장소 추적 파일만 파싱하는 일회용 게이트
하네스와 (2) 두 원장의 판정 기록뿐이고, 인증·암호·비밀·입력 검증·주입·SSRF·경로 탈출·권한 상승 어느 표면도 건드리지
않는다. 계획서 관측 16 의 J1·J2 가 같은 축을 이미 다뤘다 — 파싱 대상 3종은 전부 이 저장소 저자가 쓰는 추적 파일이며
신뢰 경계를 넘지 않는다. 다만 **그 판단은 사람의 것이지 이 절의 것이 아니다.**

### Design Review

> impeccable 트리거 미발화 — `impeccable-detect.js detect --mode implement` 가 `skill_available=true` · `design_signal=false` · `silent_skip_reason=no-signal`.

렌더 표면이 0 이다(`.tsx`/`.css`/`.html` 변경 없음). 이 마일스톤은 `docs/` 아래에 파일을 만들지 않는 것이 성질이므로
설계 표면이 없는 것이 정상이다 — 검사 `#11` 이 그 부재를 진다. receipt 에 `impeccable_silent_skip=true` 로 실린다.
critique 루프·stage routing·design-grounding capture 는 트리거가 없어 전부 돌지 않았다.

### Security Reviewer — 후속 라운드 (2026-08-25)

위 강등을 해소했다. **서브에이전트를 부르지 않고** 메인 세션이 `origin/main...33cce9a` diff 전건(10파일 +2729/-63)을
직접 읽어 판정했다 — UI9 가 배제한 것은 `Task tool` 호출이지 리뷰 자체가 아니다.

verdict **APPROVE** — CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1. receipt: `security-reviewer/insuniverse-docs-50-domain-m1.json`
(round 1 · converged · `sha256:03dde82a…`). 판정 근거 전문은 그 receipt 의 `resolution.accepted` 에 있다. 요지:

- 하위 프로세스 호출이 `run()` 한 자리를 지나며 `spawnSync` + `shell:false` + argv 배열 — 셸 주입면 0 (34·137행)
- git 가변 인자는 `"HEAD:" + f` 접두(507·749행), `-C` 뒤는 값 위치 — 옵션 위조 불가
- 동적 `RegExp` 3자리(707·975·1028행) 삽입값이 전부 상단 상수 배열 — ReDoS·패턴 주입 입력면 없음
- 쓰기 전건 `os.tmpdir()/m1-50domain-gates` 의 `mkdtempSync` 안. 워킹트리 쓰기 0. `rmSync` 2자리 다 TMPROOT 자식
- 네트워크 0 · 신규 의존 0 · 자격증명 스캔 0건
- LOW 1건 미조치: `sweepTemps` 의 10분 mtime 임계가 10분 넘게 도는 동시 실행 픽스처를 지울 수 있다. 파괴 범위가
  자기 네임스페이스로 닫혀 있고 결과가 남의 검사 플레이크(가용성)라 권한·기밀·무결성 축이 아니다.

**implement receipt 의 `security_skipped=true` 는 그대로 둔다.** 그 파일은 `33cce9a` 에 추적돼 있고
`store.js assertNoTrackedOverwrite` 가 제자리 재발행을 거부한다(실측: `disk=sha256:bbb8617e… new=sha256:f224f9de…`).
새 슬러그 재앵커는 `mccp-plan-codex` 도 같은 슬러그로 필요한데 그것은 `/mccp:plan` 재실행으로만 나온다
(`write.js` 155~162행 — intent 결정은 CLI 플래그로 못 준다). 계획서가 안 바뀐 창에 plan 리뷰를 통째로 다시 도는 값이
없다. 선례 `10-index-git-m8` 이 같은 상황을 `MCCP_FORCE_PR_WITHOUT_SECURITY_REVIEWER` 로 닫았고 그 pr-codex receipt 에
`security_force_override=true` + 사유가 실렸다. 이 마일스톤도 같은 경로를 쓴다.

> **정정 (`3d53e55`, 2026-08-25) — 위 문단의 결론은 틀렸다.** `MCCP_FORCE_PR_WITHOUT_SECURITY_REVIEWER`
> 경로는 이 차단을 못 연다. `validate-cmd.js` 442행은 선행 게이트의 `meta.security_skipped` 를 **무조건**
> blocking 으로 밀고 그 env 를 읽지 않는다. 별도 `security-reviewer` gate receipt 를 소비하는 경로도 없다.
> 실측: env 를 세팅한 채 `validate --command mccp:pr` -> `ok=false blocking=1` 그대로.
> 해소는 `git rm --cached` 로 언트래킹한 뒤 두 receipt 를 **라운드 2 로 재발행**한 것이다 (선례 `61f9e1a`).
> head_sha 는 `a3cf48b` 로 재결박되며, 라운드 2 의 대상이 실제로 그 HEAD 의 델타이므로 결박이 사실과 맞는다.
> 같이 발견한 것: `.claude/settings.local.json` 에 `MCCP_SKIP_RECEIPT=1` 이 상시 export 돼 있었고, 그 상태로
> 발행한 receipt 는 `meta.skipped=true` 로 **전건 non-approving** 이 된다(첫 재발행이 실제로 그렇게 막혔다).
> 그 파일에서 `MCCP_SKIP_RECEIPT` 와 `MCCP_FORCE_PR_WITHOUT_SECURITY_REVIEWER` 를 둘 다 지웠다 — 일회용
> 우회를 상시 export 하는 것을 command body 가 명시적으로 금한다.

## origin/main merge (6b9aefe) 와 검사 `#1` 기준선 결함

`/mccp:pr` 앞에 `origin/main` 을 merge 했다(behind 12 · ahead 1). 충돌은 `codex-findings-backlog.md` 한 건이고 양쪽
모두 말미 추가라 날짜 오름차순으로 합쳤다 — 행 손실 0 (235행 중 마커 3행 제거 = 232행, 양쪽 신규 전건 잔존).

**merge 가 검사 `#1` 을 깼다.** `#1` 은 lint glob 아래 `.md` **총수**를 리터럴 `0` 과 비교했는데, 그 `0` 은 계획 시점
main 의 스냅샷이었다. 형제 축 40-runtime 이 `docs/05-schedules/04-runtime-topology.md` 를 main 에 올리자
(PR #15 · `d061991`) 이 브랜치가 `docs/` 를 한 자도 건드리지 않았는데도 merge 만으로 `#1 FAIL` 이 났다. CLAUDE.md
INV4(검사 없는 리터럴 기준선 금지)가 겨냥한 바로 그 형태이며, 층위 6(게이트가 보는 대상)이다.

고쳤다 — 기준선을 `git ls-tree -r --name-only origin/main docs/` 에서 기계로 뜨고 **워킹트리 − 기준선 차집합**이
비어 있는지 본다. 술어가 "총수 0" 에서 "이 브랜치 신설 0" 으로 바뀌었고, 형제 축 소유분은 이름으로 출력에 낸다
(그 파일들의 내용은 한 자도 안 본다 — 이 검사의 사각지대라 숨기지 않는다).

- 기대: `#1 ok  서브모듈 3/3 동결 일치 · lint glob .md 신설 0건 (origin/main 기준선 1 · 워킹트리 1) · *Status: DRAFT 1행`
- 음성: `docs/20-site/__probe.md` 를 만들고 `check1` 실행 → `#1 FAIL: 이 브랜치가 lint glob 아래 .md 를 1건 신설했다 — docs/20-site/__probe.md` · exit 1 (실행 확인, 프로브 즉시 삭제)

merge + 수정 후 `check-all` 11/11 초록 · `--selfprobe` 초록 · `lint-plan.mjs` exit 0.
`plan_hash` 는 움직이지 않는다 — 고친 것은 `gates.mjs` 이고 계획서 본문은 무변경이다.

## PR-Codex 라운드 1 (2026-08-25) — 흡수 2 · 봉인 재제기 3

`origin/main` merge(`0f69f8c`, behind 18 · 60-history-deprecated M1 PR #16 흡수) 뒤 `/mccp:pr` 진입.
`derive-decision` 이 슬러그를 `insuniverse-docs-60-history-deprecated-m1` 로 잘못 떨어뜨렸다 —
브랜치 슬러그 `50-domain` 에 정확 일치 receipt 가 없어 **직전에 merge 로 들어온 형제 축의 implement
receipt** 로 폴백한다. `insuniverse-docs-50-domain-m1` 로 손수 고정했다.

cross-gate dedupe `skip_safe=false` (plan·implement 둘 다 `codex_verdict=divergent`) — PR-Codex 전건 실행.
raw verdict `needs-attention` · `NO-SHIP` · 5건(HIGH 4 · MEDIUM 1). 락 정상 해제(`mutations: []`).

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| PR R1 | (없음 — UI9) | Codex | 6·8 · 6 | F1 · F2 | F3 · F4 · F5 |

### 흡수 F1 (HIGH · 층위 6+8) — 검사 `#11` 이 branch diff 를 안 본다

`git status --porcelain` + `git diff HEAD` 만 봤다. 커밋하는 순간 판정 범위가 비고 술어가 공허해진다.
**실측**: clean HEAD 에서 `origin/main` 대비 11파일이 바뀐 상태인데 `#11 ok … 편집 파일 0종만 ()` 으로
통과했다. 계획서 `# 기대:` 는 `편집 파일 3종만` 인데 그 불일치를 잡는 자리가 없다 — 검사가 자기 술어를
잃고도 초록을 낸다(층위 8: 검사 자신의 사각지대).

`branchChanged()` 로 옮겼다 — `merge-base(origin/main, HEAD)..HEAD` 의 `A/M/D/R` 전건(rename 은 양쪽 다
담는다. 새 이름만 담으면 금지 경로에서 옮겨 나간 삭제가 안 보인다) ∪ 미커밋분. 2-dot 이 아니라 합류점
기준이라 merge 로 들어온 형제 축 `docs/**` 가 거짓 FAIL 을 내지 않는다. `golden` 별도 검사는 지웠다 —
`docs/` 아래라 `docsDirty` 가 이름으로 잡는다(같은 사실을 두 번 세지 않는다). 판정 범위 건수를 출력에 낸다.

- 기대: `#11 ok  docs/ 변경 0 · golden 무변동 · 편집 파일 3종만 (…)` + `판정 범위: 8a477c9f..HEAD ∪ 워킹트리 = 11건`
- 음성 A (미커밋 신설): `docs/20-site/__probe.md` 생성 → `#11 FAIL: docs/ 변경 1건 / docs/20-site/` · exit 1
- 음성 B (**커밋된 신설** — 새로 닫힌 구멍): 같은 파일을 커밋 → `#11 FAIL: docs/ 변경 1건 / docs/20-site/__probe.md` · exit 1
- 음성 C (**커밋된 삭제** — 새로 닫힌 구멍): `git rm docs/00-index.md` 커밋 → `#11 FAIL: docs/ 변경 1건 / docs/00-index.md` · exit 1

세 프로브 모두 실행 확인 후 `git reset --hard` 로 원복했다. B·C 는 구 코드에서 **전부 초록**이었다.

### 흡수 F2 (HIGH · 층위 6) — backlog 동결 분모가 자기 자신이다

분모를 `git show HEAD:` 로 떴다. HEAD 는 이 창이 커밋할 때마다 함께 움직이므로 이 창이 넣은 행이 분모에
들어간다 — **자기 자신이 기준선**이다. 실측: `origin/main` 7행 vs HEAD 11행인데 11행 전건이 "이미 동결된
입력" 으로 승인됐다. 더 나쁜 형태는 상속 행을 지운 커밋이다 — 분모·분자가 함께 줄어 `lost` 가 침묵한다.

분모를 `merge-base(origin/main, HEAD)` blob 으로 옮겼다. 이 창이 아니라 **인수한 것**이 분모다.

- 기대: `#5 ok  50-domain 행 11건 전건 판정 (8a477c9f 동결 7 전건 잔존 + 이 창 신규 4) · …`
- 음성 D (미커밋 삭제): 인수분 1행 삭제 → `#5 FAIL: 기준선 동결 원장에 있고 현재에 없는 인수분 1건 — 2026-08-05|MEDIUM|.claude/plans/insuniverse-docs-11-index-code.plan.md` · exit 1
- 음성 F (**커밋된 삭제** — F2 의 실제 실패 모양): 같은 행을 커밋으로 삭제 → 위와 같은 FAIL · exit 1.
  **반사실 대조**: 그 커밋 뒤 구 분모(`HEAD`)는 10, 신 분모(`merge-base`)는 7 이다 — 구 코드는 `lost=0` 으로 침묵한다.

### 기각 F3 (HIGH) — UI5 · UI9 위반

봉인 재제기다. 계획서 §Codex Adversarial Review 라운드 1 이 이미 판정했다: `.gates.mjs` 생성은
`REJECTED_BY_DESIGN`(UI5 의 지시대상은 읽기 전용으로 여는 제품 소스이고, 그 구제 조항 "발견한 버그는
findings 에 기록만" 이 범위를 고정한다 · CLAUDE.md §1 이 200행 초과 Validation 에 요구하는 도구 · 선례 4건 ·
라벨 dispute). L2 다중 에이전트 패널 6라운드는 `ACCEPT_NOW` — **위반 사실을 인정하고 사용자에게 보고했으며
그 기록을 승인 근거로 쓰지 않는다**(receipt 는 Codex 라운드가 냈다). 새 사실 0건이라 판정을 뒤집지 않는다.

### 기각 F4 (HIGH) — PRD 표제 `20종` vs onboarding `src` 9건 `18종`

봉인이다. PRD §회부 발신 `R-50-1` 이 이 파손을 **게이트 미탐지까지 포함해** 그대로 기록하고 있고
(`verify.sh` 의 onboarding 레코드 검사는 `src` 접두와 `§` 뒤 비어 있지 않음만 본다), 계획서 §NOT Building
283행이 "다른 소유" 로 배제했다. `docs/_meta/onboarding-questions.yaml` 은 `00-protocol`(archived) 배타 소유이고
그 경로의 개정 위임을 받은 활성 PRD 가 없다 — 그것이 계획서 OQ 의 열린 항목이다. Codex 의 대안("표제 변경을
이 PR 에서 출하하지 마라")은 회부 기구를 우회하는 것이라 저장소 규약과 어긋난다.

### 기각 F5 (MEDIUM) — O-5 닫히기 전 `divergent` 병기 결정

PRD 125행은 같은 문장 안에서 UI4 를 그대로 복창한다: "**표시 형태는 아직 정해지지 않았다** — 회부 O-5 가
닫히기 전까지 `divergent` 병기의 형태를 이 PRD 가 발명하지 않는다." UI4 가 금한 것은 **형태**이고 이 문장이
정한 것은 소비 여부(어휘 5종 중 `agree` 만 본문, `divergent` 는 기록)다. 계획서 OQ · Risks · NOT Building
세 자리가 같은 유보를 반복한다. **사용자 판정 대기 항목으로 남긴다** — 뒤집으려면 O-5 판정이 먼저다.

## PR-Codex 라운드 2 (2026-08-25) — 흡수 1 · 재제기 0

focus 에 **봉인·흡수·잔여 3목록**을 박아 넣었다. 라운드 1 의 기각 3건(F3 UI5·UI9 · F4 온보딩 `src` ·
F5 O-5 병기)이 **한 건도 재제기되지 않았다** — 그 대신 라운드 1 흡수가 **연 새 구멍** 하나를 냈다.

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| PR R2 | (없음 — UI9) | Codex | 6 | R2-F1 | (없음) |

### 흡수 R2-F1 (HIGH · conf 0.98 · 층위 6) — staged rename 이 금지 경로 삭제를 숨긴다

라운드 1 의 F1 흡수가 만든 **비대칭**이다. 커밋분은 `--name-status -M` 루프가 rename 의 양쪽 경로를
담는데, 미커밋분을 받는 `porcelain()` 은 ` -> ` 로 잘라 **목적지만** 남겼다. 그래서 금지 경로에서
허용 경로로 staged rename 하면 목적지만 범위에 들어가고 원본의 삭제가 통째로 사라진다.

**실측 (수정 전)**: `git mv docs/00-index.md .claude/notes/__probe-moved.md` staged 상태에서
`#11 ok  docs/ 변경 0 · … 편집 파일 3종만` 으로 **통과**했다. `docs/00-index.md` 가 사라졌는데도.

`porcelain()` 을 `git status --porcelain -z` 로 옮기고 R/C 의 **원본과 목적지를 둘 다** 낸다.
`-z` 는 인용(`"경로"`)을 끄고 rename 을 `XY <new>\0<old>\0` 두 토큰으로 주므로 ` -> ` 문자열 탐색에
기대지 않는다 — 앞선 판은 인용된 경로에서도 깨졌다.

- 음성 G (**새로 닫힌 구멍**): `git mv docs/00-index.md .claude/notes/__probe-moved.md` staged →
  `#11 FAIL: docs/ 변경 1건 / docs/00-index.md` · exit 1 (수정 전에는 rc=0 이었다)
- 음성 H: `git mv docs/00-index.md docs/00-index-moved.md` staged →
  `#11 FAIL: docs/ 변경 1건 / docs/00-index-moved.md` · exit 1

두 프로브 모두 실행 확인 후 `git reset` + `git checkout` 으로 원복했다.

> **층위 8 관측** — 라운드 1 의 흡수가 라운드 2 의 지적을 낳았다. 범위 게이트를 넓힐 때 커밋 축만
> 넓히고 워킹트리 축을 그대로 두면 두 축의 파싱 규약이 갈라진다. 두 축은 같은 함수를 지나야 한다.

### 정정 — 위 R2 절의 "수정" 은 `a7f6893` 에 담기지 않았다

**위 R2 절과 `a7f6893` 커밋 메시지는 없는 수정을 기술했다.** 그 커밋의 gates.mjs blob 은 부모와 같은
`5cb1b37a` 이고 변경된 파일은 notes 하나뿐이다. 원인은 그 회차 음성 프로브의 정리 명령이다 —
`git reset HEAD -- . ; git checkout -- .` 이 프로브 파일뿐 아니라 **워킹트리 전체**를 인덱스로 되돌려
커밋 전의 미커밋 수정을 지웠다. 그 뒤에 돈 프로브 H(`docs/` 내부 rename)는 목적지도 `docs/` 아래라
**구 코드로도 실패하는 비판별 프로브**였고, 그래서 되돌림이 출력에 드러나지 않았다.

실제 수정은 `c09336b` 이다. 프로브는 **커밋 뒤 커밋된 트리에서** 돌렸고 정리 범위를 프로브가 만든
경로로 좁혔다.

## PR-Codex 라운드 3 (2026-08-25) — 흡수 1 · 재제기 0

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| PR R3 | (없음 — UI9) | Codex | 2 | R3-F1 | (없음) |

### 흡수 R3-F1 (HIGH · conf 1.0 · 층위 2) — 커밋 기록과 HEAD 구현이 모순된다

Codex 가 blob 해시를 대조해 잡았다: `a7f6893` 의 부모와 HEAD 양쪽 gates.mjs 가 `5cb1b37a` 로 같고
`porcelain()` 은 여전히 비-NUL 출력을 줄 단위로 자르며 rename 목적지만 낸다. **커밋 메시지에 적힌
음성 프로브 결과가 HEAD 구현으로 재현되지 않는다** — 층위 2(주장 ≠ 검사)의 가장 나쁜 형태다.

수정 후 판별 프로브를 **같은 워킹트리 상태에서 두 구현으로** 돌려 반사실을 냈다:

- 상태: `git mv docs/00-index.md .claude/notes/__probe-moved.md` (staged, `R  docs/00-index.md -> …`)
- 신 코드 `c09336b`: `#11 FAIL: docs/ 변경 1건 / docs/00-index.md` · **exit 1**
- 구 코드 `a7f6893` (같은 상태, 같은 명령): `#11 ok  docs/ 변경 0 · … 편집 파일 3종만` · **exit 0**

blob 대조 — **불변 커밋 참조로 적는다.** `HEAD`·`HEAD^` 는 다음 커밋 한 번에 뜻이 바뀌어 감사 증거가
재현되지 않는다(PR-Codex R4 가 잡았다: `e5b4370` 시점에 `HEAD^` 는 이미 `c09336b` 이고 두 blob 이 같다):

| 커밋 | `gates.mjs` blob |
|---|---|
| `a7f6893` (R2 회차 — 수정 미포함) | `5cb1b37a` |
| `c09336b` (R3 흡수 — 실제 수정) | `ab23d20f` |

> **층위 8 관측 2** — 이 회차의 진짜 결함은 게이트가 아니라 **프로브 절차**였다. `git checkout -- .`
> 로 정리하는 프로브는 자기가 검증하려던 수정을 지운다. 그리고 비판별 프로브(구 코드로도 실패하는
> 것)를 함께 돌리면 그 사실이 출력에 안 나타난다. 두 규칙: (1) 프로브는 **커밋 뒤** 커밋된 트리에서
> 돌린다. (2) 정리 범위는 프로브가 만든 경로로 좁힌다. (3) 흡수 프로브는 **구 구현과 대조**해
> 판별력을 보인다.

## PR-Codex 라운드 4 (2026-08-25) — 흡수 1 · 재제기 0

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| PR R4 | (없음 — UI9) | Codex | 1 | R4-F1 | (없음) |

### 흡수 R4-F1 (MEDIUM · conf 1.0 · 층위 1) — 정정 증거가 가변 ref 에 걸려 있다

R3 정정이 blob 대조를 `HEAD^ 5cb1b37a -> HEAD ab23d20f` 로 적었다. 그 문장을 담은 커밋(`e5b4370`)이
찍히는 순간 `HEAD^` 는 `c09336b` 이 되고 두 blob 이 같아진다 — **거짓 기록을 정정하는 문장이 다시
재현 불가능한 참조에 기댔다.** 실측: `e5b4370:gates.blob == c09336b:gates.blob == ab23d20f`.

불변 커밋 참조 표로 바꿨다 (`a7f6893 -> 5cb1b37a` · `c09336b -> ab23d20f`, `git rev-parse` 로 재확인).
같은 절의 반사실 라벨 `a7f6893^` 도 `a7f6893` 로 고쳤다 — 추출한 blob 은 `a7f6893` 의 것이다.

> **규칙 4 (위 층위 8 관측 2 에 이어)** — 감사 증거에 `HEAD`·`HEAD^`·브랜치명을 쓰지 않는다.
> 기록하는 순간의 뜻과 읽는 순간의 뜻이 다르다. 커밋 SHA 로 못 박는다.

## PR-Codex 라운드 5 (2026-08-25) — 지적 0건 · 종료

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| PR R5 | (없음 — UI9) | Codex | — | (없음) | (없음) |

raw verdict **`approve`** · `codex_actionable_findings=false` · `lock_exit_ok=true` · `mutations: []`.
합치 결론: "Round 5 delta 는 `security_skipped` 해소 경로를 사실에 맞게 정정하며 `3d53e55` 의 라운드 2
receipts 와 일치한다. 새로 입증 가능한 ship-blocking 결함은 없다."

### 종료 판정 — 건수가 아니라 층위 곡선으로

| 라운드 | 지적 | 층위 | 성질 |
|---|---|---|---|
| R1 | 5 (실질 2) | 6 · 8 | 게이트가 보는 대상 — 범위·분모가 자기 자신에 걸렸다 |
| R2 | 1 | 6 | R1 흡수가 연 비대칭 (커밋 축만 넓히고 워킹트리 축을 두었다) |
| R3 | 1 | 2 | 주장 ≠ 검사 — 커밋 기록이 없는 수정을 기술했다 |
| R4 | 1 | 1 | 사실 오류 — 감사 증거가 가변 ref 에 걸렸다 |
| R5 | 0 | — | `approve` |

층위가 6 → 2 → 1 로 **내려갔고** R5 에서 0 이다. 봉인 3건(UI5·UI9 · 온보딩 `src` · O-5 병기)은
R2~R5 네 라운드 동안 **한 번도 재제기되지 않았다** — focus 에 봉인·흡수·잔여 3목록을 박은 효과다.

> **이 루프의 진짜 수확은 게이트가 아니라 절차 규칙 4개다.** (1) 프로브는 커밋 뒤 커밋된 트리에서
> 돌린다. (2) 정리 범위는 프로브가 만든 경로로 좁힌다 — `git checkout -- .` 는 검증하려던 수정을
> 지운다. (3) 흡수 프로브는 구 구현과 대조해 판별력을 보인다. (4) 감사 증거에 `HEAD`·`HEAD^`·
> 브랜치명을 쓰지 않는다. 넷 다 R3·R4 가 실측으로 강제한 것이다.

## 이력 재작성 (2026-08-25) — pre-push HISTORY-leak 게이트가 잡은 실 누출 1건

`/mccp:pr` Phase 3.1 이 `origin/main..HEAD` 의 신규 blob 전수 스캔에서 누출 1건을 냈다:

```
LEAK .claude/reviews/plan-review-insuniverse-docs-50-domain-m1.md:16 [repo-root]
```

L2 패널 산출물 안에 저장소 루트 절대경로(`C:\Users\<user>\_projects\insuniverse-temp\.worktrees\…`)가
2곳 박혀 있었다. `scan_errors=0` 이라 서브모듈 gitlink 오탐이 아니고, 스캐너에 CLI allowlist 도 없다.
**새 커밋으로 지워도 안 된다** — 스캐너는 범위 안 모든 신규 blob 을 보므로 조상 blob 이 그대로 걸린다.

`git filter-branch --index-filter` 로 그 한 파일만 리닥트하며 14커밋(merge 2)을 다시 썼다. 재스캔
`leaks=0 · scan_errors=0 · exit 0`. 재작성 전후 트리 차이는 리닥트 2행뿐이다(`git diff --stat` 1파일 ±2).
검증: `check-all` 11/11 · `--selfprobe` ok · `plan_hash` `61656f9b` 무변동 · `gates.mjs` blob 2종
(`5cb1b37a`·`ab23d20f`) 불변.

**재작성이 SHA 를 전부 바꿨다.** 이 파일의 참조는 아래 표대로 갱신했다. **커밋 메시지 본문의 SHA 는
갱신할 수 없다** — 고치려면 다시 재작성해야 하고 그러면 또 바뀐다(자기참조). 메시지가 가리키는 구 SHA 는
이 표로만 해석된다.

| 구 SHA (dangling) | 신 SHA | 커밋 |
|---|---|---|
| `b972b56` | `33cce9a` | feat: 착수 게이트 |
| `f85d4af` | `6b9aefe` | Merge origin/main |
| `c20b625` | `e6e1bab` | fix: 검사 #1 기준선 + security 라운드 봉인 |
| `39f25aa` | `0f69f8c` | Merge origin/main |
| `d4f3543` | `5c89b59` | PR-Codex R1 F1·F2 흡수 |
| `f14c18b` | `a3cf48b` | R1 판정 기록 |
| `941c54d` | `3d53e55` | receipt 라운드 2 재발행 |
| `aa0d838` | `a7f6893` | R2 흡수 (수정 미포함 — R3 이 잡음) |
| `3869d53` | `c09336b` | R3 흡수 (실제 porcelain 수정) |
| `d1c10f7` | `e5b4370` | R3 판정 + R2 기록 정정 |
| `b417eec` | `f91ffc1` | R4 흡수 |
| `83278e2` | `d285315` | security_skipped 경로 정정 |
| `92aa381` | `7ace949` | R5 종료 판정 |
| `5f40443` | `9befa72` | evidence commit (구 pr-codex receipt) |

재작성으로 receipt 3종(`mccp-implement-codex` · `security-reviewer` · `mccp-pr-codex`)의 `head_sha` 가
없는 커밋을 가리키게 됐다. 셋 다 새 HEAD 에 재발행한다 — 그것이 이 재작성의 마무리다.

> **규칙 5** — 리뷰 패널·리포트 산출물은 커밋 전에 절대경로를 훑는다. Phase 3.1 은 **푸시 직전**에
> 걸리고, 그 시점의 유일한 출구가 이력 재작성이다. 비용이 14커밋 재작성 + receipt 3종 재발행이다.
