# Notes: ops-20-statements M4 출항 재앵커

**Plan**: `.claude/plans/ops-20-statements-m4-ship.plan.md`
**Decision slug**: `ops-20-statements-m4-ship`

이 파일에 게이트 리뷰를 적는 이유는 편의가 아니다. 계획서 본문은 `mccp-plan-codex` receipt 의
`plan_hash` 가 무는 대상이고, 절을 하나라도 더하면 그 receipt 가 즉시 stale 로 떨어져 체인이
끊긴다. 착수 시점의 이 축이 정확히 그 상태였다 — 앞 라운드가 계획서를 봉인한 뒤 본문을
고쳐 `plan_hash` 가 `sha256:48edc0ad…` 대 `sha256:23debd50…` 로 갈려 있었다. 그래서 이번
라운드는 **계획서를 한 바이트도 고치지 않고** 게이트만 다시 돌렸고, 리뷰 기록은 전부 여기로
온다. 계획서의 `## Codex Adversarial Review` 절이 "미기록 — 게이트 실행 전" 으로 남아 있는
것은 누락이 아니라 그 결박의 결과다.

## 라운드 캡

- `effectiveRoundCap` 실측: `{"cap":1,"pinned":true,"reason":"deadline_pressure","pinnedBy":"single-pass"}`
- 근거: `MCCP_REVIEW_SINGLE_PASS=deadline_pressure` 가 환경에 서 있다. 이 축은 그 변수를
  해제하지 않는다. 따라서 두 게이트 모두 **R2 는 존재하지 않는다.**
- `codex-policy seal` 실측: `codex_disabled=false`. M3 재앵커와 다른 점이 이것이다 — 그 축은
  Codex 가 꺼진 채 돌아 교차모델 검토가 0회였고, 이 축은 두 게이트에서 Codex 가 실제로
  발언했다.

## Plan 게이트 (`mccp-plan-codex`)

- 호출: `node <mccp>/scripts/lib/plan-codex-runner.js --plan .claude/plans/ops-20-statements-m4-ship.plan.md
  --decision ops-20-statements-m4-ship --arbiter-mode author --impeccable-silent-skip
  --impeccable-silent-skip-reason no-signal`
- 리뷰 경로: `MCCP_PLAN_REVIEW=codex` · `MCCP_INTENT_ARBITER=author` (UI4 — multi-agent 패널은
  Agent 도구를 요구한다). L2 패널은 이 축에서 돌지 않았고 receipt 에 `review_*` 필드가 없다.
- 라운드 수: 1
- 결과: `exitCode 0` · `intent_gate_verdict=preserved` · `resolution.codex_verdict=divergent`
- 봉인된 `plan_hash`: `sha256:23debd50c89618376eb08f4b0e512eeb6877bbe9bbb0cab57278753937e72ea2`
  — `hash-plan` 재도출값과 같다. 이 축의 실질 목적이 이 한 줄이다.
- `intent_adjudication_counts`: `total 3 · conflict 0 · none 3 · overrides 0 ·
  by_verdict {ACCEPT_NOW: 2, DEFER_TO_BACKLOG: 1}`
- `intent_claim_counts`: `claimed 3 · reviewer_only 2 · agree_none 1 · author_conflict 0`

### 합치 결론

Codex 가 3건을 냈고 전건 명시 판정했다. 두 건은 이 실행이 그 자리에서 이행했고 한 건은
회부했다. `codex_verdict` 는 `divergent` 로 봉인된다 — 마지막 라운드가 무언가를 냈다는 뜻이지
결함이 아니고, plan 게이트에서 그 값은 blocking 이 아니다. cross-gate dedupe 가 그 값에
fail-closed 이므로 `/mccp:pr` 에서 PR-Codex 가 그대로 다시 돈다.

### YAGNI Triage

| Finding | Severity | Verdict | Why |
|---|---|---|---|
| F0 현재 plan receipt 가 계획서와 이미 불일치한다 | HIGH | ACCEPT_NOW | 사실이고, 지적이 권고한 처치가 곧 이 실행이다 — 계획서를 고치지 않은 채 게이트를 다시 돌려 receipt 를 재발급했고 `hash-plan` 재도출로 정합을 확인했다 |
| F1 새 슬러그 치환 목록이 Phase 5.1 재도출을 누락한다 | MEDIUM | ACCEPT_NOW | 사실이다. `/mccp:pr` Phase 5.1 의 `task_completed` emit 이 여섯 번째 재도출이고 이 브랜치에서 그 값은 우산 슬러그로 떨어진다. 실행에서 흡수했다 — 아래 §슬러그 고정 6지점 |
| F2 receipt 변경 검사기가 실행 실패를 정상 상태로 처리한다 | MEDIUM | DEFER_TO_BACKLOG | 결함은 사실이나 문면 수정이 방금 봉인한 `plan_hash` 를 그 자리에서 어긋나게 한다. 런타임 위험은 아래 §상쇄로 갚고 문면은 회부했다 |

리뷰어는 F0 · F1 에 `UI3` 라벨을 달았다. 방향이 반대다 — UI3 은 "새 슬러그로 정식 재발급하라"
는 **이행 요구**이고 두 발견은 그 요구가 아직 덜 지켜졌다는 관측이므로 제약과 충돌하지 않고
오히려 지지한다. 그래서 `intent_conflict` 는 `none` 이고 `intent_dispute_reason` 으로 그
판단을 적었다. F2 는 리뷰어도 `none` 이라 이견이 없다.

### 슬러그 고정 6지점 (F1 흡수)

계획서 §슬러그 교체 기전은 다섯 자리를 적었다. F1 이 지적한 여섯 번째를 더해 이 실행이
`ops-20-statements-m4-ship` 으로 고정한 자리는 다음이다.

| Phase | 무엇 |
|---|---|
| 1.6 | chain-check 선행 검사 |
| 2.5.2 | cross-gate dedupe |
| 2.5.7 | finalize — receipt 를 쓰는 자리 |
| 2.5.8 | code-review chain-check |
| 2.5.9 | ship-verdict 읽기 |
| **5.1** | `msw-event emit --kind task_completed --work-unit` — **F1 이 더한 자리** |

`derive-decision --command mccp:pr` 는 이 브랜치에서 `ops-20-statements` 를 낸다(실측). 여섯
자리 전부 유도값을 쓰지 않고 손으로 고정했다.

### 상쇄 (F2 회부에 대한 대가)

문면은 고치지 않았으므로 런타임에서 갚는다 — 계획서 §Validation 6 과 10 의 receipt 변경
검사기를 돌릴 때 parser 호출 뒤의 "무조건 성공" 꼬리를 떼고 종료 코드를 보존한 채 실행한다.
빈 출력의 비영 종료는 parser failure 로 이름 대고 죽인다. 그래야 degraded 상태가 "변경 없음"
으로 읽히지 않는다.

## Implement 게이트 (`mccp-implement-codex`)

- 호출: `node <mccp>/scripts/lib/codex-invoke.js adversarial-review --base origin/feat/ops-doc-reconcile`
  (fail-closed Bash 래퍼)
- 라운드 수: 1 (캡 1)
- 결과: `classification: ok` · `blocking: false` · 벽시계 637s
- Codex raw verdict: **`needs-attention`** — receipt 는 그것을 `divergent` 로 접어 봉인한다.
  raw 문면이 필요하면 정본은 러너 산출 JSON 이다.
- 합치 결론: Codex 는 "No ship" 을 냈고 4건을 이름으로 댔다. 네 건 **전부** `ops/**` 안이다 —
  `ops/statements/check-statements.mjs` 2건, `ops/statements/_harness/m4.sh` 2건. 이 축은 그
  산출을 재구현하지 않는 배관이고(UI8), 고치면 계획서 Validation 7 · 12 · 13b 가 요구하는
  `ops/**` 무변경이 그 자리에서 깨진다. 그래서 전건 backlog 회부다. 지적을 접은 것이 아니라
  **소유 축이 다르다** — 네 건 모두 base 로 나가는 M4 델타에 이미 실려 있고, 조치는
  `ops/statements/**` 를 여는 별도 결정에서 한다.

### YAGNI Triage

| Finding | Severity | Verdict | Why |
|---|---|---|---|
| F0 snapdir 자동 정리가 하네스가 만들지 않은 디렉터리를 지울 수 있다 | HIGH | DEFER_TO_BACKLOG | 사실. `mkdir -p` 로 기존 경로를 소유로 받고 실패 시 `rm -rf` 가 돈다. 같은 헬퍼가 다섯 하네스에 복제. `ops/**` 라 이 축 밖 |
| F1 carryover 바이트 봉인이 빈 줄과 잘못된 닫는 펜스를 받는다 | MEDIUM | DEFER_TO_BACKLOG | 사실. 리뷰어가 두 변형 다 rc 0 · `verdict ok` 로 실측. `ops/**` 라 이 축 밖 |
| F2 호스트 경로 게이트가 읽을 수 없는 소유 파일을 깨끗한 것으로 본다 | MEDIUM | DEFER_TO_BACKLOG | 사실. EACCES 에도 rc 0 이고 심볼릭 링크는 통째로 빠진다. `ops/**` 라 이 축 밖 |
| F3 하네스가 추적 findings 파일 복원 실패를 조용히 무시한다 | MEDIUM | DEFER_TO_BACKLOG | 사실. `cp` 뒤 무조건 성공 꼬리라 반쪽 복원이 가려진다. `ops/**` 라 이 축 밖 |

- Deferred to backlog: **5** — plan 게이트 1건 + implement 게이트 4건. 전건
  `.claude/plans/codex-findings-backlog.md` 의 기계 구획(`| Date | Severity | Source plan | Finding |`
  헤더 아래)에 행으로 적재했다.
- Open Questions: 없음. 다만 implement 게이트의 `divergent` 는 숨기지 않는다 — 이 PR 이
  내보내는 M4 구현에는 위 4건이 남아 있고, 그 사실이 receipt 와 backlog 양쪽에 적혀 있다.
- security 축: `security_skipped` · `security_force_override` 둘 다 **부재(false)**. 이 축의
  Files to Change 는 receipt 3본과 이 notes 이고 어느 것도 인증 · 세션 · 암호 · 비밀 · 입력검증
  · 주입 · 경로탐색 · 권한상승 표면이 아니라 subagent 를 띄울 조건이 성립하지 않았다. 그것이
  UI4 와 모순 없이 성립하는 유일한 경로이며, 스키마에 "돌았다" 를 뜻하는 양성 필드가 없으므로
  승인 상태는 두 부정 표기의 동시 부재로만 표현된다.

## cross-gate dedupe 를 적용하지 않은 이유

`dedupe` 실측 산출이 그대로 근거다.

```
skip_safe: false
reason: "residual files present (16)"
convergence.plan_codex_receipt.codex_verdict:      "divergent"
convergence.implement_codex_receipt.codex_verdict: "divergent"
```

두 축이 각각 독립으로 막는다. (1) 계획서의 Files to Change 는 receipt 3본과 notes 1본인데
`origin/feat/ops-doc-reconcile...HEAD` 는 16파일이라 부분집합이 아니다 — 그 16은 M4 구현이
이미 HEAD 에 들여온 것이고 이 축이 새로 만드는 것이 아니지만, dedupe 규칙은 "무엇이 새로운가"
가 아니라 "diff 가 계획 범위 안인가" 를 묻는다. (2) 두 선행 게이트의 `codex_verdict` 가 전부
`divergent` 라 dedupe 는 그 값에 fail-closed 다. 따라서 PR-Codex 는 전체 diff 를 다시 본다 —
이중 리뷰가 조용히 우회되지 않는다는 뜻이고, 그 성질이 이 축이 원하는 것이다.
