# 마일스톤 6 리뷰 원장 — 수치 한도 세 문서

계획서: `.claude/plans/insuniverse-docs-30-contracts-limits-m6.plan.md`
브랜치: `30-contracts-limits` · 착수 HEAD `b28ae10`

이 파일이 구현 라운드의 판정 정본이다. **계획서 본문에 쓰지 않는다** — `mccp-plan-codex`
receipt 가 `plan_hash` 로 계획서를 결박하고 있어 본문을 고치면 그 봉인이 stale 로 떨어진다.

## Codex Implementation Review

- 호출: `node C:/Users/madsc/.claude/plugins/cache/mccp/mccp/1.33.1/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, v0.2.2)
- 라운드 수: 1 (`MCCP_REVIEW_SINGLE_PASS=deadline_pressure` 로 캡이 1 에 pin — `effectiveRoundCap` 출력 `{"cap":1,"pinned":true,"pinnedBy":"single-pass"}`)
- classification `ok` · blocking `false` · 구조화 verdict `needs-attention` → 게이트 verdict `divergent`
  (`codex-review-payload.deriveGateVerdict` · `source: structured`)
- 합치 결론: **"하네스가 초록인 채로 틀린 한도를 실을 수 있다"** 세 갈래. 세 건 다 집합 대조의
  성질에 대한 지적이며 세 건 다 R1 에서 하네스 설계를 바꿔 흡수했다. 캡이 1 이라 R2 는 존재하지
  않는다 — 흡수는 저작으로 닫고 확증 라운드는 서지 않았다. receipt 의 `codex_verdict=divergent`
  가 그 사실을 든다.
- YAGNI Triage:

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 기획 절 수치가 값 게이트 밖 | HIGH | ACCEPT_NOW | UI3 이 "얼마까지" 를 기획 절에 쓰라고 하는데 `#8` 이 강제 지점 절만 봤다. 독자가 읽는 수치가 무검증이면 이 문서의 하중이 통째로 무검증이다 |
  | F2 무순 집합이 슬롯 배정을 잃는다 | HIGH | ACCEPT_NOW | `{6분,3분,7분}` 을 단계끼리 맞바꿔도 집합·앵커·층이 전부 같다. 실제로 이 마일스톤의 dispatcher 세 창이 그 형태다 |
  | F3 기본값을 운영값으로 인증한다 | HIGH | ACCEPT_NOW | 세 인증 창이 env 로 덮이는데(G18) `?? 우변` 만 뽑으면 "기본값 6분" 과 "운영 한도 6분" 이 같은 초록이다. UI13 이 운영값 미확보를 못박은 것과 정면으로 어긋난다 |

- Deferred to backlog: 0 → `.claude/plans/codex-findings-backlog.md`
- Open Questions: 없음. 자동 CRITICAL 목록(비밀 노출 · 데이터 손실 · 되돌릴 수 없는 마이그레이션 ·
  인증 우회 · 외부 목적지 변경 · 암호키 취급) 해당 0건.
- design signal 0 — `impeccable-detect` 가 `design_signal:false` · `silent_skip_reason:no-signal`.
  산출물이 `.md` 세 건과 `.mjs` 한 건이라 렌더 표면이 없다. receipt 로 `--impeccable-silent-skip` 만 전달.
- security-reviewer 미호출 — 이 마일스톤의 산출물은 문서와 검사 하네스이고 인증·암호·비밀·입력
  검증 코드를 만들지 않는다. 2.5.5 의 승격 조건에 해당하지 않아 `security_skipped` 를 달지 않는다.

### F1~F3 흡수 — 하네스 설계 변경 (반사실 검증은 `--selfprobe` 와 검사 `#12`)

세 지적이 같은 뿌리를 갖는다: **집합 대조는 원소가 어디에 속하는지를 증명하지 못한다.** 흡수는
집합을 슬롯 결박 튜플로 바꾸는 하나의 변경이다.

| # | 흡수 전 | 흡수 후 |
|---|---|---|
| F1 | `#8` 이 `## 강제 지점 (개발)` 의 값만 읽는다 | `#8` 이 `## 정책 (기획)`·`## 부록` 서술의 **독자 노출 수치도 전건** 읽고, 그 정책의 소스 유도값에서 만든 **허용 표시 집합**(원값 · 쉼표 표기 · 초 · 분 · 일 환산)에 드는지 본다. 하나라도 밖이면 FAIL. *(R1 시점의 "전건" 은 단위 여덟 종이 붙은 수치에 한정돼 있었다 — R2 M1 이 그 한정을 풀었다)* |
| F2 | 정책마다 값 집합 · 층 집합을 각각 비교 | 정책마다 **슬롯 튜플** `(slot, value, layer, anchor, provenance)` 을 비교한다. 슬롯 이름은 소스 심볼(`RESERVE_TTL_MS` 등)이고 문서가 `값 — <slot>:` 자리에 그 이름을 쓴다. 두 단계 값을 맞바꾸면 슬롯–값 쌍이 깨진다 |
| F3 | `?? 우변` 을 값으로 뽑고 끝 | 슬롯마다 `provenance` 를 소스 구조에서 재유도한다 — 초기자에 `process.env.` 가 있으면 `env-default`, 없으면 `literal`. `env-default` 슬롯의 문서 값 행은 `설정 미지정 시` 표지를 **필수**로 갖고 `literal` 슬롯은 그 표지를 **금지**한다. 양방향이라 표지를 남발해도 떨어진다 |

`#12`(`--selfprobe`) 가 세 흡수마다 음성 프로브를 하나씩 갖는다 — 기획 절 수치를 하나 바꾼 사본 ·
두 슬롯 값을 맞바꾼 사본 · `env-default` 표지를 지운 사본이 각각 거부되는지.

> **R2 정정 (2026-08-28).** 위 문단은 R1 시점에 **거짓이었다.** 그때의 프로브 셋은 로직을 프로브
> 안에 다시 적은 닫힌 함수였고 사본을 만들지도 축 함수를 부르지도 않았다. R2 에서 `#8` 의 축을
> `auditSlotBinding` · `auditProseNumbers` · `auditEnvMarker` 로 뽑고 프로브가 그 본문을 부르게
> 바꾼 뒤에야 위 서술이 참이 됐다. 반사실은 §R2 표에 있다.

## 계획 대비 이탈 — 실측이 §GROUND 를 두 자리에서 반증했다

계획 §GROUND G16·G17 이 도달성을 부분적으로 잘못 적었다. `releases.yaml`
`reachability.unreached` 와 동결 SHA blame 을 대조한 실측이 근거다. **버그로 판정하지 않고 사실로
기록한다**(UI11). 이탈은 정책 배치와 status 에만 미치고 산출물 3건과 하네스 1건은 그대로다.

| 자리 | 계획의 서술 | 실측 | 처리 |
|---|---|---|---|
| `treat-monitor.service.ts` 캐시 창 | 값 결속 표가 *"도달 창 선언들"* 을 본문 정책 `limits:monitor-cache-windows` 의 분모로 든다 | 이 파일의 창 선언 **여섯 전건이 미도달**이다 — 100 · 103 · 715 · 716 · 717 · 981 행. 도달 창이 0건이다 | `limits:monitor-cache-windows` 를 부록 `staged-only` 로 옮겨 여섯 선언을 전건 담고, 계획의 `limits:daily-monitor-window`(715·717 만 담던 부록 항목)를 이 정책에 흡수한다. 비는 본문 자리는 `limits:provider-setting-cache-window`(`treat-provider.service.ts:17`, 도달)가 채운다. **02 의 본문 5 · 부록 2 계수는 그대로다** |
| site 재시도·폴백 상수 | `limits:client-retry-caps` 를 본문 `active-prod` 로 두고 *"site 세 화면의 재시도·폴링 상수"* 를 분모로 든다 | 그 일곱 선언 중 **다섯이 미도달**이다 — `QueuePage.tsx` 17·18 · `Step2Page.tsx` 40·41·42. 도달한 것은 폴링 주기 둘뿐이다(`QueuePage.tsx:16` · `B2cQueuePage.tsx:15`) | 정책을 축으로 가른다. `limits:client-poll-cadence`(본문 `active-prod`, 폴링 주기 둘)와 `limits:client-retry-caps`(부록 `staged-only`, 재시도 횟수 상한 둘). 03 이 본문 2 · 부록 5 로 정책 하나가 는다 |

정책이 하나 늘어 전체 18 → 19 다. 계획서 §정책 id 집합 표는 *"이 계획의 정의"* 이지 정본 파일에서
베낀 값이 아니므로 INV4 대상이 아니고, 이 표가 그 정의의 개정본이다.

**섞인 정책을 만들지 않는 이유**: 검사 `#5` 가 `active-prod` → 앵커 전건 도달 · `staged-only` →
앵커 전건 미도달을 양방향으로 문다. 도달과 미도달이 섞인 정책은 두 규칙 어느 쪽도 만족하지 못한다.
계획이 겨냥한 *"부록을 채우려고 도달 정책을 미배포로 적는다"* 와 그 거울상을 같은 검사가 막는다.

### 검사 `#16` 수치 축의 사각지대 — 실측으로 좁혔다

계획 Acceptance 는 *"세 문서의 앵커 집합과 수치 집합이 쌍마다 교집합 0"* 을 요구한다. 앵커 축은
전 정책에 그대로 선다. **수치 축은 본문 정책으로 한정한다** — 실측상 부록 정책끼리 밀리초 값이
우연히 겹치기 때문이다(`60000` 이 관리자 대시보드 캐시 창이자 인증 분당 창의 창 길이, `300000`
이 인증 재개 창이자 기업 캐시 창). 서로 다른 한도가 같은 라운드 값을 갖는 것은 재서술이 아니다.

축을 그대로 두면 참인 문서가 떨어지고, 축을 지우면 재서술이 안 잡힌다. 앵커 축이 *"같은 한도를 두
번 적었다"* 를 전 정책에서 잡고, 수치 축은 본문에서만 선다. **그 사각지대를 `#16` 이 출력에
이름으로 드러낸다**(층위 8).

## R2 — `/mccp:code-review` (로컬 모드) 흡수

R1 이 닫힌 뒤 `/mccp:code-review` 를 워킹트리 전건에 돌렸다. 지적 13건, 전건 흡수, 기각 0.
**세 건이 R1 의 흡수 자체를 쳤다** — R1 이 "흡수했다" 고 적은 자리가 실제로는 검사되지 않았다.

| # | 층위 | 지적 | 흡수 | 반사실 |
|---|---|---|---|---|
| H1 | 7 | `#12` 의 흡수 프로브 3종이 하네스 본문을 부르지 않는다. 로직을 프로브 안에 다시 적었다 | `#8` 의 축을 `auditSlotBinding` · `auditProseNumbers` · `auditEnvMarker` 로 뽑고 프로브가 실제 정책·실제 문서 블록으로 그 본문을 부르게 했다. 프로브마다 **원본 통과 확인 → 변조 사본 거부** 두 단계 | 아래 표 |
| H2 | 6 | `column-type` 매처의 `int\(` 가 앵커 없어 `bigint(`·`smallint(` 도 매치. 정책의 논지인 *"상한 = 칸 폭"* 을 게이트가 못 본다 | 형 이름을 `(?<![A-Za-z_$])(tinyint\|smallint\|mediumint\|int\|bigint)\(` 로 포획해 `intType` 으로 싣고, `INT_BITS` 에서 `2^bits-1` · `2^(bits-1)-1` 을 계산해 상한 상수와 대조하는 `#8` 축 4 신설 | 아래 표 `h2` |
| M1 | 8 | `#8` 축 3 이 단위 여덟 종 붙은 수치만 본다. `묶음 크기가 1이라` · `설정이 없을 때의 값은 0이므로` — **두 정책의 정의값 자체**가 축 밖 | 축 3 을 산문의 **모든 수치**로 넓히고, 차수 접미(`차·단계·번째·항·호`)만 제외해 그 건수를 출력에 이름으로 드러냈다. 32건 → 35건 + 차수 1건 공개 | `#8` 출력 |
| M2 | 6 | 주장 `db-width-cap` 의 유도가 *"DB 제약 층을 가진 D1 정책 수"* 인데 문장은 **상한 개수**를 센다. 둘 다 1 이라 우연히 맞았다 | 유도를 `widthBoundCaps().filter(matchesUnsignedMax)` 로 바꿔 문면과 같은 것을 세게 했다. 게이트(축 4)와 주장이 같은 출력을 쓴다 | `#14` |
| M3 | 2 | implement receipt 의 `resolution.accepted` 가 비어 F1~F3 판정이 기계가 읽는 자리에 없다. 하류 PR-Codex dedupe 에 줄 컨텍스트가 없다 | receipt 를 `--findings-file` · `--resolution-file` 로 재발급해 F1~F3 + R2 흡수를 실었다 | receipt 본문 |
| M4 | 1 | `constInitializer` 의 정규식 이스케이프가 문자 클래스를 `]` 에서 닫아 **어떤 식별자에도 발화하지 않는다**. 실측 — `"A$B"` 가 무변경 통과 | 파일에 `reEsc` 하나만 두고 사본 셋을 전부 그것으로 바꿨다 | — (fail-closed 였으므로 회귀 아님) |
| L1 | 2 | `symbolUseForms` 는 7종인데 주석·실패 메시지가 "여섯" | 메시지 계수를 배열 길이에서 뽑고 주석에 7번째를 적었다 | — |
| L2 | — | `check4` 의 `extra` 가 죽은 코드. 누적 `seen` 을 분모로 쓰려던 흔적 | 지우고 정책별 `own` 만 남겼다 | — |
| L3 | 8 | `check10` 이 부록 위반도 "기획 절" 로 보고하고, 표 행을 말없이 건너뛴다 | 절 이름을 메시지에 싣고, 훑은 서술 행 수와 축 밖 표 행 수를 출력에 공개 | `#10` 출력 |
| L4 | 4 | `check3`·`check10` 의 `try/catch` 가 GateFail 사유를 안 가리고 삼킨다 | 삼킬 사유를 메시지로 좁히고 나머지는 다시 던진다 | — |
| L5 | 7 | 여섯 주장 중 `staged-only-in-d3` 만 문서 자기 프론트매터에서 유도 | 유도에 `reachedProd` 재확인을 더해 소스로 접지했다 | `#14` |
| L6 | — | `check15` 의 안쪽 `r` 이 바깥 행 변수를 가리고, 조회 목록에 주 저장소가 중복 | `cand` 로 개명 + 목록 중복 제거 | — |
| L7 | — | `allowedDisplay` 의 `Number.isInteger(n) ? String(n) : String(n)` 양 갈래 동일 | 지웠다 | — |

### H1·H2 반사실 — 축을 본문에서 하나씩 깨고 `--selfprobe` 에 물었다

수정 **전**: 세 축을 전부 지운 사본에서 `check-all` 이 rc 0, `#12` 가 `hold` 3종. 흡수가 주장으로만
남아 있었다는 뜻이다.

수정 **후** (변조 사본 4종 · 각각 하나의 축만 깬다):

| 변조 | `#12` F1 | F2 | F3 | H2 | selfprobe rc |
|---|---|---|---|---|---|
| `allowedDisplay` 가 전건 수용 | **BROKEN** | hold | hold | hold | 1 |
| 슬롯 결박 값 비교 삭제 | hold | **BROKEN** | hold | hold | 1 |
| 출처 비교 삭제 | hold | hold | **BROKEN** | hold | 1 |
| `INT_BITS.int` 를 64 로 | hold | hold | hold | **BROKEN** | 1 |

각 변조가 **자기 행만** 뒤집는다 — 교차 오염 0. `h2` 는 `check8` 도 rc 1 로 같이 떨어진다
(축 4 가 본문에서 직접 물기 때문). `f1`·`f2`·`f3` 에서 `check8` 이 rc 0 인 것은 정상이다 — 문서가
참이므로 축을 지워도 떨어질 것이 없다. **그 자리를 보는 것이 `#12` 의 존재 이유다.**

## 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R1 | (미실시 — 이 세션은 Agent 툴 금지) | Codex `needs-attention` HIGH 3 | 6 · 7 | F1 · F2 · F3 전건 | 0 |
| R2 | `/mccp:code-review` 로컬 모드 (HIGH 2 · MEDIUM 4 · LOW 7) | (미실시) | 1 · 2 · 4 · 6 · 7 · 8 | 13건 전건 | 0 |

**Reviewer A 는 이 세션에서 서지 않았다.** Agent 툴과 Workflow 사용이 금지돼 있어 L2 패널과
`santa-loop` 을 둘 다 껐다. 계획서 §Codex Adversarial Review 가 같은 제약을 적었고 마일스톤 2~5 도
같았다. 교차 확증은 `/mccp:pr` 시점의 PR-Codex 가 든다 — `codex_verdict=divergent` 라 하류 dedupe 가
fail-closed 이므로 그 게이트는 반드시 발화한다.

## main 병합 관측 (2026-08-28 · `ef37a8e`)

`origin/main` 이 merge-base `b28ae10` 에서 `eeb75d3` 로 37커밋 전진해 병합했다(rebase 아님 —
receipt 3종이 `head_sha` 결박이다). 병합을 PR 발행보다 **먼저** 둔 것은 backlog `60m2` 행이 관측한
post-PR stale-head(재앵커 불가)를 애초에 만들지 않기 위해서다. 들여온 형제 창 산출은
`20-foundation-access` M5 · `40-runtime-vendor-legal` M6 · `50-domain` M6 ·
`60-history-deprecated` M2c·M3 이고 `docs/**` 신설 9건이다(`02-access/04-authz-db-menu` ·
`02-access/05-admin-view-token` · `20-site/01-analysis-flow` · `20-site/02-alimtalk-certify` ·
`20-site/03-subscription-payment` · `20-site/04-mypage-account` · `20-site/05-content-board` ·
`90-history/03-divergence` · `_meta/term-requests/50-domain`).

### 충돌 0건

`git merge` 가 자동 병합으로 끝났다(`git diff --name-only --diff-filter=U` 0건). 앞선 창들이
충돌을 낸 두 자리가 이번에는 한쪽만 움직였다:

| 축 | merge-base `b28ae10` | 병합 전 이쪽 `21fff33` | 병합 전 main `eeb75d3` | 병합 후 |
|---|---|---|---|---|
| `codex-findings-backlog.md` 표 행 | 127 | 127 (이 창 델타 0) | 156 (형제 29행) | 156 |
| `golden.txt` `fixtask.findings_headings` | `20-foundation-access=3 60-history-deprecated=9` | 좌동 (이 창 델타 0) | `=4 … =12` | `=4 … =12` |

이 창은 `findings/30-contracts-limits.md` 를 개설하지 않았고 backlog 에도 행을 얹지 않았다.
그래서 두 자리 모두 **한쪽만 움직인 병합**이고 양측 보존 판단이 필요 없었다.

### golden 스냅샷 재지정 — 착수 SHA 가 아니라 병합해 들인 main 팁으로

`#1` 은 `docs/_meta/verify/golden.txt` 와 착수 시점 사본
(`.claude/notes/…-m6.golden-baseline.txt`)의 **바이트 동일**을 요구한다. 병합 직후 이 검사가
`golden 변동` 으로 떨어졌다 — 92행 `fixtask.findings_headings` 한 줄이다.

귀속을 기계로 갈랐다. `git show 21fff33:docs/_meta/verify/golden.txt` 92행이 **옛 스냅샷과 같고**,
`git show origin/main:…` 92행이 **병합 후 값과 같다**. 그리고 워크트리 golden 과
`origin/main` 의 golden 이 `cmp -s` 로 **바이트 동일**이다 — 즉 이 창의 golden 델타는 0 이고
움직인 것은 전부 형제 창이 main 에 얹은 것이다.

그래서 스냅샷을 병합해 들인 main 팁의 golden 으로 재지정했다. `#1` 이 지키려던 불변식은
"착수 시점 값이 영원히 유지된다" 가 아니라 **"이 마일스톤이 golden 을 움직이지 않는다"** 이고,
병합 후 그 불변식의 올바른 분모는 병합해 들인 main 상태다. 착수 SHA 값을 고집하면 형제
마일스톤의 델타가 이 창의 위반으로 계상된다(backlog `pinned-base-sha` 축과 같은 성질).

`cp` 대상이 워크트리 golden 인 것은 안전하다 — 위 `cmp -s` 가 그것이 `origin/main` 블롭과 같음을
먼저 증명했다. 자기가 방금 저작한 상태를 기준선으로 삼는 경로(`check1` 주석이 경고하는 자리)가
아니다.

### 병합 후 하네스 — 16/16 유지

`node .claude/plans/insuniverse-docs-30-contracts-limits-m6.gates.mjs check-all --expect complete`
rc 0. `#1` 이 `golden 무변동 · findings 30-contracts-limits 미개설 · check PASS`, `#4` 앵커 74건,
`#8` 슬롯 74쌍, `#12` 추출기 17종 · 흡수 반사실 4종 `hold`. 형제 문서 9건이 들어왔지만
`#3` lint 대상은 39건으로 이 창의 신설 3건을 그대로 포함하고 `#6`·`#7` 횡단 누출도 0 이다.

`--expect in-progress` 로 부르면 `#11` 이 `실측=complete` 로 떨어진다. 마일스톤 6 은 이미 종료
전이돼 있으므로(§Delivery Milestones 6행 · `*Status:` 토큰 두 자리) `--expect complete` 가
이 창의 올바른 호출이다.

## PR-Codex 라운드 1 흡수 (2026-08-28, 병합 후 `5b6b7c1`)

`/mccp:pr` 의 PR-Codex 가 `needs-attention` 으로 5건을 냈다(raw verdict — receipt 는 `divergent` 로
접어 봉인한다. 정본은 `codex-result-2.json`). **다섯 건을 전부 동결 SHA 의 blob 으로 대조했다** —
문면 규칙에서 연역하지 않고 소스를 읽었다.

| # | 심각도 | 대조 판정 | 근거 |
|---|---|---|---|
| F1 | HIGH | 사실 | `certify-rate-limit.guard.ts` 49행 세기와 50행 만료가 왕복 두 번이다. 세기 성공 + 만료 실패면 만료 없는 값이 남고, 이후 초과분의 예외는 61행에서 재전파돼 계속 막는다 |
| F2 | HIGH | 사실 (갈래 단서) | `treat.service.ts` 588~593행 `finally` 가 응답 직후 점유를 푼다. 다만 개인 직접 갈래에서는 그 해제를 건너뛴다 — 문서가 갈래 없이 일반화했다 |
| F3 | HIGH | 사실 | `money.service.ts` 98~108행 가드의 호출부가 16자리, 인라인 비교가 2자리다. 문서가 등록한 `money.repository.ts:486` 은 기업충전 승인 한 경로다 |
| F4 | MEDIUM | 사실 | `treat.repository.ts` 48~52행 묶음 반복이 129~139행 트랜잭션 **안**이다. "앞 묶음은 별개" 가 거짓 |
| F5 | HIGH | **세 갈래** | check3 배치 = 사실(아래) · check5 = 부분 · check15 = 오독(아래) |

### 흡수 — 문서 넷

F1·F4 는 `03-rate-limits-and-batch.md`, F2 는 `02-ttl-and-timeouts.md`, F3 는
`01-amounts-and-quotas.md` 다. 넷 다 **코드를 고치지 않고 서술을 사실로 정정했다**(UI11). F1 은
부록 산문에 부분 실패 관측을 더하고 예외·폴백 표에 행 하나를 얹었다 — 그 행의 근거 앵커
`certify-rate-limit.guard.ts:50` 은 이미 프론트매터 앵커 집합 안이라 `#4` 의 양방향이 그대로 선다.
F3 은 강제 지점 서술을 등록 경로 하나로 좁히고 경로별 분포를 프론트매터 `limitations` 로 남겼다 —
슬롯을 늘리지 않았으므로 `#4`·`#8`·`#9` 의 74쌍 분모가 안 움직인다.

### 흡수 — F5 는 한 갈래만 수용한다

**check3 배치 양방향 = 수용.** `lint-docs.mjs` 968행 `placement` 규칙이 이미 양방향이다 —
`body` 배치에서 `inAppendix.has` 와 `!inBody.has` 를 둘 다 보고 `appendix` 배치에서 `inBody.has` 를
본다. 하네스의 재검사는 같은 계약의 **두 번째 판정기**였고 UI2 가 금지한 자리다. 지웠다.

지우기만 하면 위임이 무검사와 구별되지 않는다. 그래서 `#3` 에 **위임처 실재** 축을 남겼다 —
키트에 `placement`·`unknown-reason`·`history-columns` 세 규칙이 선언돼 있는지를 문다. 이 축이 무는
것은 규칙의 **존재**뿐이고 그것이 옳게 도는지는 lint 의 `--selftest` 몫이다. 그 경계를 검사 주석과
계획서 `# 기대:` 에 함께 적었다.

음성 실측: 키트의 `rule("placement"` 를 `rule("placement_RENAMED"` 로 바꾼 사본에서
`#3 FAIL: 위임한 lint 규칙이 키트에 없다 1건 — placement` 로 rc 1, 복원 후 rc 0, 키트 워킹트리 무변동.

**check15 연혁 = 기각(오독).** lint `history-columns` 는 헤더 이름·열 수·날짜 형식·SHA 의 hex 모양만
본다. `#15` 는 그 SHA 가 동결 저장소에 **실재하는지**, 날짜가 그 커밋과 맞는지, `last_verified` 가
동결 head 의 접두인지를 본다. 축이 다르다 — 계획서 §Validation `#15` 기대가 *"lint 는 열 이름과
형식만 보고 값의 실재는 보지 않는다"* 로 이미 그 구분을 적었다.

**check5 미확인 사유 = 부분, 유지.** `#5` 의 본체는 status 와 prod 도달성의 양방향 대조이며 blame
을 동결 저장소에서 돌린다 — lint 가 볼 수 없는 축이다. 겹치는 것은 `unknown_reason` **존재**
한 조각뿐이고 `#5` 는 거기에 길이 하한을 더해 강화한다. 판정기 이중화가 아니라 같은 필드에 대한
다른 강도라 남겼다.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R3 | (미실시) | PR-Codex `needs-attention` HIGH 4 · MEDIUM 1 | 1 · 6 | F1~F4 전건 · F5 중 check3 | F5 중 check15(오독) · check5(축 다름) |

`plan_hash` 가 `f1e8c557` 에서 `6806a822` 로 움직였다 — `#3` 의 `# 기대:`·`# 음성:` 을 실측과
맞췄기 때문이다. 앞 게이트 두 장이 stale 이 되므로 새 슬러그로 재봉인한다. 구 슬러그 두 장은
손대지 않는다 — 역사 기록이고 stale 로 읽히는 것이 정직하다.

## plan-codex 재봉인 (2026-08-28, `c3fe145` · 슬러그 `…-m6-reseal`)

PR-Codex R1 흡수가 계획서 §Validation `#3` 주석을 건드려 `plan_hash` 가 움직였고 앞 게이트 두 장이
stale 이 됐다. 새 슬러그로 재봉인했다 — 구 슬러그 두 장은 손대지 않는다. 봉인값은
`plan_hash=6bec9d00` · `head=c3fe145` · `intent_gate_verdict=preserved` ·
`codex_verdict=divergent`(마지막 라운드가 지적을 냈다는 뜻이지 결함이 아니다).

### 라운드 셋이 필요했던 이유 — 둘은 도구 함정, 하나는 실질

**R1·R2 는 receipt 를 못 남겼다.** 둘 다 `intent_gate_verdict=incomplete` ·
`reason: timed out … waiting for adjudication`. 원인은 판정 지연이 아니다 — R2 는 창이 열린 지
몇 분 만에 판정을 썼는데도 55분을 다 소진했다. `plan-codex-runner.js` 의 `waitForAdjudication` 은
`p.adjudication` **최종 경로**만 폴링하는데 arbiter 프롬프트는 그 경로에 `.tmp` 를 붙여 쓰라고
지시한다. `.tmp` 를 최종 이름으로 옮기는 주체는 subagent 모드에만 있고 author 모드에는 없다.
R3 은 러너를 띄우는 래퍼가 그 결번을 맡아 `.tmp` 를 같은 디렉터리에서 `mv` 했고, 그러자
`R3_ADJUDICATION_MOVED` 직후 봉인까지 갔다.

**세 라운드의 지적은 같은 축을 세 번 쳤고 셋 다 사실이었다.**

| 라운드 | 지적 | 판정 | 흡수 |
|---|---|---|---|
| R1 | 재봉인 receipt 가 수정 전 커밋을 봉인한다 | 기각(설계상) | 가리킨 것은 구 슬러그 receipt 다. 그 head 봉인은 그때의 사실이고 권고는 이 실행 자체였다 |
| R1 | `#3` 주석이 실제 출력과 다르다 | 수용 | lint stdout 은 캡처만 하고 전달하지 않으므로 `OK — …` 행은 안 나온다. 대상도 39건이지 3건이 아니고 음성 인용은 실패 행 접미를 빠뜨렸다 |
| R1 | 위임 선언 실재만으로는 무검사를 못 막는다 | 수용 | 픽스처 보유를 세어 없는 규칙을 이름으로 공개. 픽스처는 더하지 않는다(UI9 — 키트는 `doc-kit` 소유) |
| R2 | 픽스처 보유를 소스 전체 문자열 빈도로 센다 | 수용 | `placement` 는 프론트매터 키 목록에도 나와 픽스처를 다 지워도 빈도가 남았다. 분모를 `SELFTEST.fail` 매핑으로 좁혔다 |
| R3 | 그 추출기가 주석 상태를 못 가른다 | 수용 | 맞지 않는 행을 건너뛰므로 블록 주석 안의 매핑을 살아 있는 것으로 계상했다. 문법을 fail-closed 로 좁혔다 |

**같은 부류를 세 번 냈다.** 대리 지표(문자열 빈도) → 매핑으로 좁힘 → 파서가 fail-open. 공통
원인은 하나다 — **축을 바꿀 때마다 그 축 자신의 음성을 먼저 만들지 않았다.** R1 은 위임처 축의
음성을 rc 로만 만들었고(공개 축은 rc 로 안 뒤집힌다), R2 는 닫는 줄 삭제 하나만 selfprobe 에
얹었다. R3 흡수에서 반례를 셋으로 늘린 것이 그 교정이다.

### 최종 형태

`#3` 은 (1) lint 대상 집합에 신설 3건 포함, (2) 전수 lint rc 0 · 파일 수 일치, (3) 위임 3규칙의
키트 선언 실재, (4) `SELFTEST.fail` 매핑 기준 픽스처 보유 여부와 **없는 규칙의 이름 공개** 를 문다.
(4)는 실패가 아니라 공개다 — 사각지대를 산문이 아니라 게이트 출력이 들게 하고, 키트 소유자에게
넘길 근거도 그 출력이다(층위 8).

`parseSelftestExpectedRules` 는 빈 줄 · 온전한 `//` 주석 · 정규 매핑 · 닫는 줄 넷만 허용하고
나머지는 즉시 실패다. 주석을 **해석하지 않고** 해석 못 하는 입력을 거부한다 — 키트가 그 문법을
쓰기 시작하면 이 검사가 떨어져서 사람이 본다.

### 음성 실측 (계획서 봉인 후라 여기에 기록한다)

계획서 `.md` 는 `6bec9d00` 으로 동결됐다. 아래 관측은 그 뒤에 나온 것이라 본문이 아니라 원장에
남긴다 — 저장소 규약(리뷰 기록은 notes).

| 변조 | 결과 |
|---|---|
| 키트의 `rule("placement"` 를 `rule("placement_RENAMED"` 로 | `#3 FAIL: 위임한 lint 규칙이 키트에 없다 1건 — placement · 위임이 무검사가 됐다` rc 1 |
| `"placement"` 를 값으로 갖는 `SELFTEST.fail` 행 3개 삭제 | `픽스처 보유 2종 · 없음 1종` 이 `보유 1종 · 없음 2종 (placement · unknown-reason)` 으로 (rc 는 양쪽 0 — 공개 축이라 rc 로 안 뒤집힌다) |
| `bad-placement-body.md` 매핑을 블록 주석으로 감쌈 | `#3 FAIL: SELFTEST.fail 에 블록 주석 표기가 있다 (1636행)` rc 1 — **R3 F1 이 지목한 반례이며 흡수 전에는 `보유` 로 조용히 통과했다** |
| selfprobe 3종 (닫는줄 · 블록주석 · 인라인주석) | `empty=reject mutate=reject` 전건. 추출기 20종 |

세 변조 모두 복원 후 rc 0 이고 `git status .claude/templates/doc-kit/` 무변동이다 — 키트를 실제로
고치지 않았다(UI9).

## PR-Codex 라운드 2 흡수 (2026-08-28, 슬러그 `…-m6-reseal`, HEAD `4d31783`)

`needs-attention` 으로 3건. 앞 라운드와 겹치지 않는 새 축이고 **셋 다 동결 blob 대조로 참**이다.

| # | 심각도 | 대조 판정 | 근거 |
|---|---|---|---|
| F1 | HIGH | 사실 | `auditProseNumbers` 가 정책 **전체** 허용 표시형 집합의 멤버십만 본다. 같은 정책 안에서 두 수를 맞바꿔도 집합이 같아 통과한다 — Codex 가 `dispatch-stage-windows` 의 6분·3분 교환으로 실증했다 |
| F2 | HIGH | 사실 | `certify-rate-limit.guard.ts` 37~46행. 전화번호가 요청 **본문** 값이고 관문이 인증보다 앞에 선다. `id = phone \|\| ip` 라 접속 주소는 번호가 빌 때만 걸린다 — 번호를 돌리면 한도가 켜져 있어도 세는 자리가 매번 새로 시작한다 |
| F3 | MEDIUM | 사실 | `QueuePage.tsx` 259~262행 정리 경로가 이탈을 부르지 않는다(주석: 새로고침과 앱 복귀를 구별 못 한다). `B2cQueuePage.tsx` 174행만 unload 에서 시도한다 |

### F1 이 이 마일스톤 자신의 논지를 뒤집던 자리다

계획서와 이 원장이 **"비교 단위는 값 집합도 층 집합도 아니라 슬롯 튜플"** 이라고 적었는데(Codex R1
F2 흡수), 그 교정은 `## 강제 지점` 슬롯 행에만 적용됐고 **독자가 실제로 읽는 기획 산문은 집합인
채로 남았다.** 층위 2 — 산문이 주장하는데 대응 게이트가 그 층위에 없었다.

흡수: `#8` 에 축 5(산문 표지 결박)를 신설했다. 표지↔슬롯을 등록부에 묶고 표지 뒤 첫 수치를 그
**슬롯 하나**의 유도 표시형과 대조한다. 표지는 산문에 실제로 있는 구절이지 슬롯 이름이 아니다 —
UI3 가 기획 절의 코드 심볼을 금지하므로 슬롯 이름은 그 절에 등장할 수 없다(실측: 9정책 전건에서
슬롯 이름 산문 등장 0).

**대상의 분모를 코드가 센다.** 산문 수치가 2종 이상인 정책을 `proseSwapRisk` 가 세고, 그런 정책에
결박 선언이 없으면 실패다. 등록부에 안 적는 것으로 면제되면 이 축이 자기에게 fail-open 이다 —
재봉인 R2·R3 에서 두 번 걸린 바로 그 부류다.

실측:

```
정상 : #8 ok  … 칸 폭 결박 2건 (int 32비트) · 산문 표지 결박 24건 (교환 위험 9정책 전건 선언)
교환 : #8 FAIL: limits:dispatch-stage-windows 표지 '**1차 호출 백업 창**' 에 붙은 수치 `3` 가
       슬롯 RESERVE_TTL_MS 의 유도 표시형이 아니다 (허용 360000 · 360,000 · 360 · 6)
```

`#12` 에 상시 반사실 `R2F1 산문 표지 교환 거부` 로 등록했다(흡수 반사실 4종 → 5종). **이번에는 축을
신설하면서 그 축 자신의 음성을 같이 만들었다** — 재봉인 세 라운드가 반복해서 걸린 자리라 그것이
교정이다.

### F2·F3 은 문서 서술 정정

둘 다 코드를 고치지 않는다(UI11). F2 는 신뢰 경계를 부록 산문에 적고 프론트매터 `limitations` 와
예외·폴백 표에 각각 실었다 — 근거 앵커는 `certify-rate-limit.guard.ts:45`(키 선택 자리)다. F3 은
두 대기열을 갈라 서술했다: 일반은 이탈을 부르지 않고 두 창의 만료로 정리되며, 개인 직접 갈래만
창을 떠날 때 한 번 시도하되 그것도 보장이 아니다.

### 계획서 `#8` 기대가 이미 어긋나 있었다

`#8 ok  <k>정책 값 집합 일치` 로 적혀 있었는데 실제 출력은 축 넷을 싣고 있었다. Codex 가 아직
치지 않은 자리였고, 축 5 를 더하면서 함께 정정했다 — 알고도 두는 쪽이 더 나쁘다.

정정하니 Validation 셸이 **PL9(200행 초과)** 에 걸려 `#3`·`#15`·`#16` 주석을 압축했다. 내용은
유지하고 줄만 접었다(211 → 201행, 마지막 빈 줄 포함).

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R4 | (미실시) | PR-Codex `needs-attention` HIGH 2 · MEDIUM 1 | 1 · 2 | F1~F3 전건 | 0 |

`plan_hash` 가 `6bec9d00` → `79ef76fe` 로 움직였다. 재봉인 라운드가 또 필요하다 — 재봉인 자체가
라운드라는 성질이 이 창에서 네 번째로 확인됐다.

## 재봉인 R4 흡수 — 축 5 의 분모가 정책이었다 (2026-08-28, 슬러그 `…-m6-reseal2`)

`plan-codex …-m6-reseal2` 가 `plan_hash=79ef76fe` · `head=3661c61` · `intent=preserved` 로 봉인됐고
지적 1건(HIGH, conf 0.99)을 냈다. **지적은 정확했고 내가 앞 라운드에 만든 자리였다.**

축 5 는 정책마다 결박 선언이 하나라도 있으면 통과했다. 그래서 표지 뒤 **첫** 수치만 묶이고, 뒤따르는
설명이 다시 든 수치는 축 3(집합 멤버십)으로 되돌아갔다 — Codex 가 `02-ttl-and-timeouts.md` 후속
설명의 6분과 170초를 맞바꿔 `check8` rc 0 과 `--selfprobe` 전건 통과를 실증했다.

더 나쁜 것은 출력 문면이었다. `교환 위험 9정책 전건 선언` 은 **정책 단위로만 참**이고 수치 단위로는
거짓이라 실제 커버리지를 과장했다 — 완전성 주장을 그것을 세는 코드 없이 쓴 자리이고, INV4 가
겨냥하는 부류다.

### 흡수 — 분모를 수치 출현으로

`proseNumberOccurrences` 가 산문의 수치 **자리**를 열거하고(차수 접미 제외), 결박마다 그 표지 뒤
**첫 미점유 출현**을 덮는다. 덮이지 않은 출현이 남으면 값과 함께 실패한다. 표지 둘이 같은 출현을
무는 것도 실패다. 출력도 `결박 수 / 출현 수` 로 바꿔 과장을 없앴다.

배선 직후 실측이 Codex 가 지목한 자리를 그대로 냈다:

```
#8 FAIL: 교환 위험 정책의 산문 수치 중 슬롯에 결박되지 않은 출현 4건 —
         limits:certify-window-triplet `6` · `6` · `170` · limits:certify-throttle-fails-open `1`
```

네 건을 닫은 방법이 둘로 갈린다. `certify-window-triplet` 의 중복 6분 둘은 **산문에서 수치를 지우고**
이름으로 가리켰다(`6분은 …` → `위에 적은 그 기간은 …`) — 같은 값을 반복해 적을 이유가 없었다.
170초와 `certify-throttle-fails-open` 의 1분은 독자에게 필요한 자리라 표지를 새로 묶었다
(`**번호 유효 시간**` · `여러 번 부르는 것을 막는 관문이 있다.`).

```
#8 ok  … 산문 표지 결박 26/26건 (교환 위험 9정책의 수치 출현 전건)
```

### 반례 둘, 상시로

| 반례 | 결과 | 흡수 전 |
|---|---|---|
| `dispatch-stage-windows` 기획 산문의 두 창 수 교환 | `표지 '**1차 호출 백업 창**' 에 붙은 수치 \`3\` 가 슬롯 RESERVE_TTL_MS 의 유도 표시형이 아니다` rc 1 | 축 3 만 있던 시절 통과 |
| `certify-window-triplet` **후속 설명** 수치 교환 | `표지 '**일회용 번호 창**' 에 붙은 수치 \`6\` …` rc 1 | 분모가 정책이던 시절 통과 |

`#12` 에 `R2F1 산문 표지 교환 거부` 와 `R4F1 미결박 출현 0` 둘 다 등록했다(흡수 반사실 6종).
`R4F1` 은 값이 아니라 **미결박 출현이 0인가** 를 보고, 반사실로 등록부에서 결박 하나를 지운 사본이
loose 1건을 내는지 확인한다.

### PL9 를 두 번째로 쳤다

흡수마다 Validation 주석이 자라 200행 규약을 두 번 넘겼다(211 → 201 → 205 → 200). 이번에는 라운드별
통과 이력 서술을 계획서에서 덜어 원장으로 옮겼다 — 계획서 주석은 기대의 **형태**와 음성 하나면
되고, 어느 라운드가 무엇을 통과시켰는지는 여기 기록이다. 같은 압박이 또 오면 그때는 Validation
블록을 스크립트 파일로 빼는 쪽이 맞다(CLAUDE.md §1).

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R5 | (미실시) | plan-codex 재봉인 HIGH 1 | 6 · 7 | 분모를 정책 → 수치 출현 · 출력 문면 과장 제거 · 반사실 2종 | 0 |

**같은 부류가 이 창에서 네 번 나왔다** — 대리 지표(문자열 빈도) · 파서 fail-open · 정책 단위 분모 ·
출력 문면 과장. 넷 다 "검사가 자기 커버리지를 스스로 주장하는데 그 주장에 검사가 없다"(층위 7)이다.
교정은 매번 같았다: 분모를 코드가 세게 하고 그 수를 출력에 싣는다.

## 재봉인 R5 흡수 — 분류와 커버리지가 다른 분모를 썼다 (2026-08-28, 슬러그 `…-m6-reseal3`)

`plan-codex …-m6-reseal3` 이 `plan_hash=60ef99db` · `head=9f73b39` · `intent=preserved` 로 봉인됐고
지적 1건(MEDIUM, conf 0.99)을 냈다. **또 앞 라운드 흡수가 남긴 불일치였다.**

`proseSwapRisk` 는 파이프로 시작하는 표 행을 빼고 수치를 세는데 `auditProseBinding` 은 산문 전체를
평탄화해 표 행의 수치까지 출현으로 셌다. 같은 축의 분류와 커버리지가 서로 다른 집합에서 나왔다.
게다가 `check8` 이 분자(`bound`)를 정책 전건에서, 분모(`occTotal`)를 위험 정책에서만 누적했다 —
**`26/26` 이 맞아떨어진 것은 계약이 아니라 우연이다.** 그 틈으로 표 형태로 적힌 제약은 결박되지 않은
수치를 남긴 채 위험 분류에서 빠져 통과할 수 있었다.

### 흡수 셋

1. `proseSurface` 하나가 평탄화 · 출현 열거 · 표 행 제외 · 위험 판정을 전부 낸다. 분류와 커버리지가
   같은 표면에서 나온다. 표 행을 뺄지는 **한 곳에서** 정한다.
2. `bound !== occTotal` 이면 **검사가 실패한다.** 비율의 두 항이 갈리는 것 자체를 게이트가 문다 —
   앞 형태에서는 그 불일치가 출력에 초록으로 실렸다.
3. 표 행을 뺀 **사실을 출력에 드러낸다**(`표 행 <n>줄은 축 밖`). 조용히 빼면 그 자체가 사각지대다.

```
#8 ok  … 산문 표지 결박 26/26건 (교환 위험 9정책 · 표 행 0줄은 축 밖)
```

반사실 `R5F1 표 행 배제 일관` 을 신설했다 — 수치를 든 표 행 셋을 더한 사본에서 출현 수 · 위험 판정 ·
미결박 · 결박 수가 **전부 불변**인지 본다. 한쪽만 표를 세면 이 넷 중 하나가 뒤집힌다. 반사실 7종.
죽은 `proseSwapRisk` 호출부(R4F1 프로브)도 같이 정리했다 — 함수를 지우고 호출을 남기면 프로브가
통째로 죽는다.

### 같은 부류가 다섯 번째다 — 층위를 올린다

| 라운드 | 자리 | 공통 성질 |
|---|---|---|
| 재봉인 R2 | 픽스처 보유를 소스 전체 문자열 빈도로 셈 | 대리 지표가 대상과 다름 |
| 재봉인 R3 | 그 추출기가 주석 상태를 못 가름 | 파서가 fail-open |
| PR-Codex R2 F1 | 산문 수치를 정책 전체 집합으로만 봄 | 분모가 슬롯이 아님 |
| 재봉인 R4 | 커버리지 분모가 정책 단위 | 분모가 출현이 아님 |
| 재봉인 R5 | 분류와 커버리지가 다른 표면 | 분자·분모가 다른 집합 |

다섯 다 **"검사가 자기 커버리지를 주장하는데 그 주장의 분모가 검사 대상과 어긋난다"**(층위 7)이다.
교정도 매번 같았다 — 분모를 코드가 세게 하고, 그 수를 출력에 싣고, 두 항이 갈리면 실패시킨다.
R5 의 `bound !== occTotal` 은 그 교정을 **일반화한 형태**다: 앞 넷은 개별 자리를 고쳤고 이것은
"두 항이 같은 표면에서 나왔는가" 를 검사로 요구한다.

CLAUDE.md §2 의 *같은 축 3회면 층위 올려라* 에 도달했다. 다음 라운드가 또 이 부류면 개별 흡수를
멈추고 backlog 로 올린다 — 문서 정확성 지적은 PR-Codex R1·R2 에서 7건으로 끝났고 그 뒤로 0건이다.

### PL9 세 번째

Validation 셸이 세 번째로 200행을 넘어 축 1~4 서술을 접었다(202 → 200). 계획서 주석은 기대의
**형태**와 음성 하나가 계약이고 서사는 원장 몫이라는 원칙을 세 번 적용했다. **다음에 또 넘으면
그때는 접지 말고 블록을 스크립트 파일로 뺀다**(CLAUDE.md §1) — 세 번 깎았다는 것 자체가 그 규약이
가리키는 상태다.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R6 | (미실시) | plan-codex 재봉인 MEDIUM 1 | 7 · 8 | 표면 단일화 · 분자·분모 동치 요구 · 표 행 제외 공개 · 반사실 1종 | 0 |

## 재봉인 R6 흡수 — 공개 집계가 분기 안에 있었고, 프로브가 새 결함을 찾았다 (2026-08-28, `…-m6-reseal4`)

`plan-codex …-m6-reseal4` 가 `plan_hash=6086ead0` · `head=e945a80` · `intent=preserved` 로 봉인됐고
지적 1건(MEDIUM, conf 0.99)을 냈다.

**지적 자체는 두 행이었다.** `proseSurface` 는 정책 전건의 `tableLines` 를 돌려주는데 `check8` 이
`risky` 분기 안에서만 누적했다. 그래서 산문에 수치가 없고 표에만 있는 정책은 비위험으로 분류돼
표를 실제로 빼고도 출력이 `표 행 0줄` 이라고 말한다 — **공개하려고 만든 자리가 제외 사실을 숨긴다.**
앞 라운드에 *조용히 빼면 그 자체가 사각지대* 라고 적어 놓고 그 원칙을 자기 집계에서 어겼다.

Codex 가 `R5F1` 프로브도 이것을 못 잡는다고 함께 지적했고 맞다 — 그 프로브는 위험 정책 하나의
`auditProseBinding` **반환값만** 보므로 `check8` 의 누적 분기를 밟지 않는다.

### 프로브를 쓰다가 여덟 번째 자리가 나왔다

`R6F1` 을 `#8` 출력 문자열을 직접 읽는 형태로 만들고 비위험 정책에 **수치 든 표**를 주입했더니
프로브가 `BROKEN` 으로 떨어졌다. 원인이 진짜였다 — **축 3(`auditProseNumbers`)은 표 행을 보고
축 5 는 뺀다.** 주입한 `4242` 를 축 3 이 허용 표시형 밖으로 잡아 `check8` 이 죽었고, 그래서 프로브가
출력을 못 읽었다. `#10` 순도 축은 또 표 행을 뺀다. 같은 문서를 판정하는 세 축이 서로 다른 것을 세고
있었다.

### 개별 수정이 아니라 일반화로 닫았다

축 3 · 축 5 · 제외 집계 셋이 **`proseSurface` 한 표면**에서 나온다. 표 행을 뺄지는 한 곳에서 정하고
그 기준이 `#10` 과 같다. `tableSkipped` 누적은 위험 분기 **밖**으로 옮겨 정책 전건에서 센다 — 결박
분자·분모는 위험 집합에서만 누적한다(그 둘은 서로의 짝이고 비위험을 섞으면 R5 에서 닫은 불일치가
되살아난다).

```
#8 ok  … 서술 수치 35건 … 산문 표지 결박 26/26건 (교환 위험 9정책 · 표 행 0줄은 축 밖)
--selfprobe ok  추출기 20종 … 흡수 반사실 8종 hold
```

서술 수치 35건 불변 — 산문 블록에 원래 표가 0줄이라 회귀가 없다. `R6F1` 은 `hold` 로 뒤집혔다.

### backlog 행을 넣지 않는 이유

이 저장소는 backlog 행의 이름이 동결 코호트에 합류해 `golden` 을 움직이고, 그러면 `#1` 이 깨진다.
그래서 일반화 기록은 여기 원장에 남긴다 — 리뷰 기록은 notes 라는 저장소 규약과도 같은 방향이다.

**남은 일반화 (이 마일스톤 범위 밖).** 출력이 인용하는 모든 수를 **그 수가 세는 집합과 함께 선언**하고
둘의 대응을 검사가 요구하는 형태. `#8` 의 `bound !== occTotal` 이 그 형태의 한 사례이고, 이번
`tableSkipped` 는 아직 짝이 없는 수다(제외된 줄 수를 세지만 그 줄들이 어느 집합에서 빠졌는지를
검사가 다시 묻지 않는다). 하네스 구조 변경이라 다음 창의 몫이다.

### 같은 부류 여덟 번, 층위를 올린 결과

| # | 자리 | 닫은 방식 |
|---|---|---|
| 1 | 픽스처 보유를 소스 전체 문자열 빈도로 셈 | 분모를 `SELFTEST.fail` 매핑으로 |
| 2 | 그 추출기가 주석 상태를 못 가름 | 문법 fail-closed |
| 3 | 산문 수치를 정책 전체 집합으로만 봄 | 표지↔슬롯 결박 |
| 4 | 커버리지 분모가 정책 단위 | 분모를 수치 출현으로 |
| 5 | 분류와 커버리지가 다른 표면 | `proseSurface` 단일화 + 분자·분모 동치 요구 |
| 6 | 공개 집계가 위험 분기 안 | 누적을 분기 밖으로 |
| 7 | 축 3 은 표 행을 보고 축 5 는 뺌 | 축 3 도 같은 표면으로 |
| 8 | 프로브가 반환값만 보고 누적 분기를 안 밟음 | `#8` 출력 문자열을 읽는 프로브로 |

여덟 다 **검사가 자기 커버리지를 주장하는데 그 주장의 분모가 검사 대상과 어긋난다**(층위 7)이다.
6·7·8 은 한 라운드에서 함께 나왔고, 7·8 은 **지적이 아니라 프로브를 쓰다가** 나왔다 — 반사실을
실제로 돌리는 것이 지적보다 먼저 찾는다는 증거다.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R7 | (미실시) | plan-codex 재봉인 MEDIUM 1 | 7 · 8 | 집계를 분기 밖으로 · 축 3 을 같은 표면으로 · 출력을 읽는 반사실 1종 | 0 |

## 재봉인 R7 흡수 — 앞 라운드 흡수가 만든 회귀, 그리고 반사실이 결함을 굳힌 자리 (2026-08-28, `…-m6-reseal5`)

`plan-codex …-m6-reseal5` 가 `plan_hash=5ac95a28` · `head=2f1a565` · `intent=preserved` 로 봉인됐고
지적 1건(MEDIUM, conf 0.99, `reviewer_claim=UI7`)을 냈다.

**앞 라운드에 내가 만든 회귀다.** R6 에서 축 3 을 `proseSurface().flat` 에 태웠는데 그 표면은 파이프로
시작하는 행을 지운다 — 축 3 의 판정 **범위가 좁아졌다.** `#10` 순도 축도 표 행을 건너뛰므로, 정책
기획·부록 절의 표에만 적힌 수치는 어떤 슬롯 허용값에도 없어도 **어느 검사도 보지 않는다.**

더 나쁜 자리가 하나 더 있었다. 내 `R6F1` 프로브가 허용되지 않는 `4242` 를 주입하고 `check8` **성공**을
요구했다 — 반사실이 그 누락을 계약으로 굳혔다. 반사실이 결함을 고정하는 모양은 이 창에서 처음이다.
반사실을 늘리는 것이 항상 안전하지는 않다는 실측이며, 프로브가 무엇을 **성공으로 요구하는가**를
따로 봐야 한다는 뜻이다.

### 좁히지 않고 막았다

흡수는 축 3 을 되돌리는 쪽이 아니라 **수치 표를 금지하는 쪽**이다. 기획·부록 서술 절의 표 행에
수치가 있으면 `proseSurface` 가 실패시킨다. 근거는 이 문서의 형식 계약이다 — 그 절은 서술이고 값과
앵커는 `## 강제 지점 (개발)` 이 든다. 표에 수치를 적는 것 자체가 규약 밖이므로, 표를 표면에서 빼는
설계가 그제야 안전해진다(수치 표가 존재할 수 없으므로 축 3 이 못 보는 수치도 없다).

오늘 실측 `tableSkipped` 가 0 이라 회귀 비용은 0 이다.

### 프로브를 집계 축과 거부 축으로 갈랐다

| 프로브 | 무는 것 | 표 |
|---|---|---|
| `R5F1` | 표 행 배제의 **일관성** — 출현 수·위험 판정·미결박·결박 수가 전부 불변인가 | 비수치 |
| `R6F1` | 제외 **집계**가 `#8` 출력에 실리는가 (비위험 정책에서도) | 비수치 |
| `R7F1` | 수치 표 **거부** — 수치 행은 실패, 비수치 행은 통과 (**양방향**) | 둘 다 |

`R7F1` 이 양방향인 이유: 한 방향만 보면 **모든 표를 거부하는 구현**도 통과한다. 그 구현은 배제
일관성 축(`R5F1`·`R6F1`)의 전제를 없애므로 구별되어야 한다.

`R5F1` 은 이 흡수 직후 `BROKEN` 으로 떨어졌다 — 그것도 수치 표를 쓰고 있었다. 같은 원인이고 같이
고쳤다. 반사실 9종 전건 `hold`.

### `intent_conflict` 라벨은 `none` + dispute

리뷰어가 이 finding 에 `UI7` 을 달았는데 방향이 반대다. `intent_conflict` 는 finding 이 **어떤 제약과
충돌하는지**를 적는 자리인데, 이 finding 은 UI7 과 충돌하지 않고 **UI7 을 집행한다** — 검사가
결속하지 않는 수치 규범을 만들지 말라는 것이 UI7 이고, 지적은 표 안의 수치가 아무 검사에도 결속되지
않은 채 통과한다는 것이다. 위반 상태는 흡수 **전** 코드이고 finding 은 그것을 없애라고 요구한다.
그래서 `none` 으로 적고 `intent_dispute_reason` 에 방향을 설명했다. 이 저장소에서 반복 관측된
모양이다 — 리뷰어는 **이행을 요구하는 UI** 를 라벨로 단다.

### PL9 네 번째 — 이번엔 receipt 에 부채로 박았다

Validation 셸이 201행이 돼 `#11` 음성 두 줄을 한 줄로 접었다. 네 번 다 접어서 넘겼고 그것은 지연이지
해소가 아니다. plan-kit 이 `good-external-mjs` 픽스처로 **지시하는 경로**는 검사 본문을 계획서 밖
스크립트로 빼고 계획은 출력만 인용하는 배치다.

이번에는 그 부채를 산문 약속이 아니라 **implement receipt 의 `open_questions` 에 MEDIUM 으로** 넣었다.
앞선 두 번은 원장 산문으로만 적었고, 산문 약속은 다음 라운드가 읽지 않는다.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R8 | (미실시) | plan-codex 재봉인 MEDIUM 1 | 6 · 7 · 8 | 수치 표 거부 · 프로브를 집계/거부로 분리 · R5F1 동반 수정 | 0 (라벨 방향만 dispute) |

## 재봉인 R8 흡수 + Validation 추출 (2026-08-28, `…-m6-reseal6`)

`plan-codex …-m6-reseal6` 이 `plan_hash=58eb1491` · `head=b8d894a` · `intent=preserved` 로 봉인됐고
지적 1건(MEDIUM, conf 0.99, `reviewer_claim=UI7`)을 냈다.

### 지적 — 블록 색인이 버리는 자리

`parseBlocks` 는 id 로 색인하므로 첫 `###` 이전 행을 버린다. 그래서 `## 정책 (기획)` 이나 `## 부록`
바로 다음이자 첫 정책 H3 **이전**에 둔 수치는 축 3 도 `#10` 도 보지 않는다. 앞 라운드에서 수치 표를
막았지만 그 막은 **정책 블록 안쪽에만** 쳐져 있었다. `R7F1` 프로브도 `docProseBlock` 반환에만
주입하므로 이 위치를 밟지 않는다.

집행 전 실측: 문서 셋의 그 구간은 수치 **0건**이다(부록 도입 산문 3행 포함, 총 13행). 규칙이 기존
문서를 고치지 않는다.

**결박 축이 아니라 거부 축에 넣었다.** 부록에는 정당한 도입 산문이 실재하고 그 산문의 수치는 소유
정책이 없어 **정의상 결속 불가능**하다. 결속 불가능한 수치 규범을 만들지 않는 것이 UI7 이므로 그
자리에는 수치가 오면 안 된다 — 표 행이든 산문이든 같다. 행 수는 `#10` 출력에 싣는다.

프로브는 권고대로 **문서 수준**으로 올렸다. `R8F1` 은 문서 원문의 첫 H3 앞에 표를 꽂고 `#10` 과 `#8`
이 **둘 다** 떨어지는지 보며, 같은 자리의 비수치 표는 통과해야 한다(양방향 — 그 구간을 통째로 막는
구현과 구별). 반사실 10종.

`intent_conflict` 는 또 `none` + dispute 다. 리뷰어가 두 라운드 연속 **이행을 요구하는 UI** 를
라벨로 달았다.

### PL9 다섯 번째 — 접지 않고 추출했다

앞 라운드에서 이 부채를 **receipt `open_questions` 에 MEDIUM 으로** 박았다. 산문 약속은 다음 라운드가
읽지 않으므로 기계가 읽는 자리에 둔 것이고, 이번에 그 자리에서 갚았다.

배치는 plan-kit 픽스처 `good-external.plan.md` 가 지시하는 그대로다 — 계획서 `## Validation` 은
호출과 종료코드 계약만 싣고, `# 검사 #N` · `# 기대:` · `# 음성:` 마커 203행은
`insuniverse-docs-30-contracts-limits-m6.gates.sh` 로 옮겼다. 검사 구현은 그대로 `.gates.mjs` 헬퍼다.

### 추출이 곧바로 층위 5 를 드러냈다

옮겨 놓고 계획서가 인용하는 명령 그대로 돌리자 167행에서 죽었다 — `check11 --expect in-progress` 인데
마일스톤은 이미 `complete` 이고 `set -euo pipefail` 이 거기서 끊는다.

**이 블록은 한 번도 끝까지 돌아본 적이 없다.** 인라인 펜스일 때는 아무도 통째로 실행하지 않고
`gates.mjs check-all` 만 돌렸기 때문이다. 원인은 블록이 **시점 하나가 아니라는** 것이다 — 위 검사들은
착수~Task 6 구간, 아래 봉인 절은 Task 7 이 상태를 뒤집은 뒤. 기대 상태를 상수로 박으면 봉인 후
재실행이 죽는다. 기본값을 봉인 상태로 두고 구현 중 상태는 `M6_EXPECT` 로 부르게 했다.

추출 후 첫 완주: `rc 0` · `#N ok` 31행 · 실제 FAIL 0건 · `#11` 두 자리 모두 `complete` ·
`--selfprobe ok` 반사실 10종. (`grep FAIL` 이 1건을 세는 것은 `--help` 출력의 종료코드 표 행이다.)

**추출은 PL9 를 피하려는 형식 조치가 아니었다.** 인라인 펜스가 숨기고 있던 실행 불가를 드러냈고,
그것이 CLAUDE.md §1 이 200행 한도를 둔 이유이기도 하다 — 돌려볼 수 없는 크기의 셸은 검사가 아니다.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R9 | (미실시) | plan-codex 재봉인 MEDIUM 1 | 5 · 6 · 8 | 서술 절 머리 수치 거부 · 문서 수준 반사실 · Validation 추출 · 실행 불가 해소 | 0 (라벨 방향만 dispute) |

---

## 재봉인 R9 흡수 + main 병합 (2026-08-31, `…-m6-reseal7`)

`plan-codex …-m6-reseal7` 이 `plan_hash=0f7ee7e5` · `head=a944631` · `intent=preserved` 로 봉인됐고
지적 1건(MEDIUM, `cited_path=.claude/plans/insuniverse-docs-30-contracts-limits-m6.gates.sh`)을 냈다.

### 지적 — `--help` 의 인자 계약이 두 검사에서만 성립했다

`runOne` 이 `DISPATCH[name](argv)` 로 `argv` 를 무조건 넘긴다. 인자를 선언한 함수는 `check1` ·
`check11` 둘뿐이고 나머지 열넷은 매개변수가 없으므로 넘어온 `argv` 를 **아무도 보지 않는다**.

실측(수정 전): `node …gates.mjs check6 --없는인자` → `#6 ok` · rc 0. `--help` 가 종료코드 표에
약속한 `64 알 수 없는 인자` 는 인자를 받는 두 검사에서만 참이었다. 이 자리는 오타 난 옵션을
영원히 삼킨다 — `gates.sh` 가 인용하는 호출에 옵션을 잘못 적어도 초록이 나오므로, 지적이
`gates.sh` 를 가리킨 것이 정확하다. 층위 3(명령 ≠ 게이트)이자 층위 8(하네스 자기 사각지대)이다.

### 거부 판정은 arity 에서 유도한다

인자를 받는 검사 목록을 상수 집합으로 복제하면 새 인자 검사를 더할 때 두 곳이 어긋난다(층위 7 —
검사 자신에 대한 주장에 검사가 없다). `DISPATCH[name].length === 0` 은 **이미 그 사실**이고 함수
정의와 같이 움직인다.

반사실 3종(실행 확인):

| 호출 | 수정 전 | 수정 후 |
|---|---|---|
| `check6 --없는인자` | `#6 ok` rc 0 | `알 수 없는 인자: check6 은 인자를 받지 않는다` rc 64 |
| `check6` | `#6 ok` rc 0 | `#6 ok` rc 0 (회귀 없음) |
| `check1 --없는인자` | rc 64 | rc 64 (자체 파서 경로 유지) |

### main 병합 — 충돌 0건

`origin/main` 15커밋(50-domain M7 · 70-viewer M4)을 병합했다. 충돌 0건 — 양측 델타가 겹치지
않는다(형제 마일스톤 산출물 · `docs/viewer/build-docs.mjs` 는 이 축이 손대지 않는다).

병합 후 실측: `gates.sh` 완주 `rc 0` · `#1` **golden 무변동**(기준선 재지정 불필요) · `#N ok`
16검사 · `--selfprobe ok` 반사실 10종.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R10 | (미실시) | plan-codex 재봉인 MEDIUM 1 | 3 · 7 · 8 | 잉여 인자 거부를 arity 에서 유도 · 반사실 3종 | 0 |

### 종료 판정

R5~R10 여섯 라운드가 전부 **건수 1 · 심각도 MEDIUM**(R5 만 HIGH)이고, 축은 매 라운드 옮겨
갔으나 마지막 두 라운드는 하네스 **자기 계약**(실행 불가 · 인자 계약)으로 내려왔다 — 문서·게이트
본체가 아니라 도구의 가장자리다. 지적 부류가 마른 것으로 보고 이 마일스톤의 plan-codex 루프를
닫는다(`.claude/meta` 종료 신호: 층위 곡선). 남은 것은 PR 게이트다.

---

## PR-Codex R3 흡수 (2026-08-31, `…-m6-reseal7`)

병합 뒤 PR-Codex 를 base `origin/main` 으로 돌렸다. `verdict=needs-attention` · HIGH 3 · MEDIUM 1.
**네 건 모두 인용을 실측으로 대조했고 전건 사실이었다** — 봉인된 반려의 재제기가 아니다.

`--focus` 에 봉인 4건·닫힌 축 R4~R9·불변식을 실어 보낸 결과 재제기는 0건이었다.

### F1 (HIGH) — 예외 표가 자기 문서의 서술을 반증한다

`02-ttl-and-timeouts.md` 예외 표의 `창을 닫음` 행이 *"이탈 신호로 즉시 제거된다"* 라고 단정하는데,
같은 문서 서술 절은 *"일반 대기열은 즉시 빠지지 않는다 — 이탈을 부르지 않고 두 창의 만료에 맡긴다"*
로 적고 개인 직접 갈래만 최선의 시도로 이탈한다고 쓴다. 한 문서 안의 정면 모순이며, 운영·지원이
닫힌 화면의 점유가 즉시 풀린다고 오판하는 자리다.

흡수: 그 행을 **두 갈래로 쪼갰다**. 근거는 프론트매터에 이미 있는 앵커로만 붙였다 —
`treat-queue.service.ts:12`·`:16`(두 창)과 `:76`(이탈 호출). 앵커 집합이 안 움직이므로 `#4`
양방향은 무변동이고, 실제로 `#4 ok  슬롯 앵커 74건 · 고유 74건` 이 그대로다.

### F2 (HIGH) — 시간 값 검사가 단위를 버린다 · 60배 틀린 한도가 초록

`slotDisplayForms` 는 밀리초 슬롯 하나에서 원값·초·분·시 환산을 **단위 없는 숫자 문자열**로
합치고, `auditProseNumbers` 는 인접 단위를 읽지 않는다. `6분` 을 `360분`(초 환산값)이나
`6초`(분 환산값)로 바꾼 사본이 축 3·축 5 를 모두 통과했다.

흡수: `slotDisplayUnitForms`·`allowedDisplayUnits` 를 신설해 `값 단위` 쌍을 만들고
`auditProseNumbers` 가 수치 뒤 단위어를 함께 읽어 대조한다. 단위어가 갈리는 `count` 축
(건·개·회·자리)은 결박하지 않는다 — 결속 불가능한 규범을 만들지 않는 UI7 과 같은 판단이다.
새 축의 분자는 `#8` 출력에 `단위 결박 26건` 으로 공개된다.

반사실 `R3F2`: 숫자는 그대로 두고 단위만 바꾼 사본에서 `#8` 이 떨어진다. 같은 정책의 다른
슬롯이 그 표기를 정당하게 허용하면 반례에서 제외한다 — 그러지 않으면 프로브가 정상 통과를
실패로 읽는다.

### F3 (HIGH) — `active-prod` 가 도달성만 보고 UI10 을 놓친다

`check5` 의 `active-prod` 분기는 앵커 blame 이 배포에 닿았는지만 확인한다. 설정이 없으면 관문을
통과시키는 정책도 코드는 배포돼 있으므로, 나중에 stg 값만 보고 status 를 뒤집으면 그대로 통과한다.

**첫 집행은 너무 넓었다.** "설정으로 덮이는 슬롯이 있으면 활성 선언 불가" 로 두자
`limits:certify-window-triplet` 이 떨어졌다 — 그 정책은 설정이 없어도 양수 기본값이 쓰여 창이
그대로 닫히므로 활성 선언이 정직하다. 판정선은 *덮이는가* 가 아니라 **코드 기본값이 한도를
켜는가** 다. 동결 소스 실측:

| 슬롯 | 설정 부재 시 기본값 | 한도가 켜지는가 |
|---|---|---|
| `CERTIFY_CLAIM_TTL_MS` 외 2 | 양수(밀리초) | 켜진다 — 활성 선언 가능 |
| `RATE_LIMIT_CERTIFY_PER_MIN` | `0` | 안 켜진다 — 관문이 통과로 열린다 |
| `TREAT_POOL` | 문자열 | 한도 수치가 아니다 |

흡수: `active-prod` 는 env 슬롯의 기본값이 **양수일 때만** 허용한다. `#5` 출력에
`설정 기본값이 한도를 안 켜는 정책 2건은 활성 선언 불가` 로 분모를 공개한다. 현재 문서 셋은
이미 그 둘을 `conditional-unknown` 으로 적고 있어 문서 수정이 없다 — 규칙이 기존 판정을
사후 승인하는 것이 아니라, 그 판정을 뒤집는 경로를 막는다.

반사실 `R3F3`: 기본값이 한도를 안 켜는 정책의 프론트매터 status 를 `active-prod` 로 뒤집은
사본에서 `#5` 가 떨어진다. 프론트매터는 `docCache` 와 별도로 `fmCache` 에 물리므로 사본 주입 때
그것도 비운다 — 안 비우면 프로브가 원본을 읽고 자기 자신을 통과시킨다.

### F4 (MEDIUM) — R9 흡수가 닿지 않은 자리

R9 는 `runOne` 에 arity 판정을 넣었는데 `--help` · `check12` · `--selfprobe` · `check-all` 은
`runOne` **앞에서** 갈라져 그 판정이 닿지 않는다. 실측(수정 전) 넷 다 rc 0.

흡수: `noArgs` · `parseExpect` 로 인자 파서를 하나로 모으고 특수 명령도 그 파서를 지나게 했다.
`check-all` 의 `--expect` 는 이제 누락·중복·값 없음을 전부 이름 있는 실패로 낸다. 실측(수정 후):

| 호출 | 수정 전 | 수정 후 |
|---|---|---|
| `--help --없는인자` | rc 0 | rc 64 |
| `check12 --없는인자` | rc 0 | rc 64 |
| `--selfprobe --없는인자` | rc 0 | rc 64 |
| `check-all --expect complete --없는인자` | rc 0 | rc 64 |
| `check-all` (무인자) | rc 64 (`--expect 누락`) | rc 64 (같음, 메시지 명시) |
| `--help` · `check6` · `check-all --expect complete` | rc 0 | rc 0 (회귀 없음) |

반사실 `R3F4` 는 `noArgs`·`parseExpect` 를 직접 부른다. `ArgError` 는 `GateFail` 이 아니므로
`throwsGateFail` 이 물지 않는다 — `throwsArgError` 를 따로 뒀다.

### 흡수 후 실측

`gates.sh` 완주 `rc 0` · 16검사 전건 `ok` · `--selfprobe ok` 반사실 **13종**(10종 + R3F2·R3F3·R3F4) ·
`lint-plan.mjs` rc 0 · `#1` golden 무변동 · `#4` 앵커 74건 무변동.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R11 | (미실시) | PR-Codex `needs-attention` HIGH 3 · MEDIUM 1 | 1 · 3 · 6 · 8 | F1~F4 전건 · 반사실 3종 신설 | 0 |

층위가 R10 의 3·7·8 에서 **1(사실 오류)·6(게이트가 보는 대상)** 으로 다시 올라갔다. 마르지
않았다는 신호이므로 종료 판정은 이 라운드에서 내리지 않는다 — 재실행에 맡긴다.

---

## PR-Codex R4 흡수 (2026-08-31, `…-m6-reseal7`)

R3 흡수 뒤 재실행. `verdict=needs-attention` · HIGH 1. **봉인 4건과 R3 흡수 4건의 재제기는 0건**
— `--focus` 에 봉인·직전 흡수·닫힌 축 세 목록을 실은 결과다. 지적은 전부 **새 축 자신**을 향했다.

### F1 (HIGH) — 새 축이 자기 목록의 밖을 못 본다

R3F2 가 넣은 단위 판정의 방아쇠가 **뒤에 붙은 낱말**이었다: `PROSE_UNIT` 이 매치될 때만 물었다.
그래서 목록에 없는 단위(`6개월` · `6회`)나 단위를 아예 지운 `6` 은 그대로 숫자 축으로 떨어져
통과한다. 축을 신설한 라운드가 그 축의 사각지대를 같이 만들었다 — 층위 8 의 교과서적 형태다.

흡수: 방아쇠를 **수치 자신**으로 뒤집었다. `allowUnits` 에서 수만 뽑아 `unitBearing` 집합을
만들고, 산문의 수가 그 집합에 들면 단위를 **달아야 하고** 그 단위가 맞아야 한다. 단위 슬롯이
아닌 수(`count` 축 — 건·개·회·자리로 서술이 갈린다)는 이 축이 물지 않는다.

**같은 수정이 R3 집행의 과대 적용도 걷어냈다.** 앞 형태는 "단위어가 붙었고 정책에 단위 표시형이
있으면" 물었으므로, 단위 슬롯 값이 아닌 수에 단위어가 붙은 서술(예: count 값 뒤의 `일`)까지
떨어뜨릴 수 있었다. 방아쇠를 수치로 옮기면 그 자리는 정의상 사라진다.

반사실 `R3F2` 를 셋으로 넓혔다 — (가) 아는 단위로 교체 (나) 목록 밖 단위 `개월` (다) 단위 삭제.
셋 다 `#8` 이 떨어져야 hold 다.

실측: `#8` 의 `단위 결박 26건` 무변동 — 규칙을 조인 뒤에도 기존 문서의 결박 수가 그대로다.
새 규칙이 기존 서술을 고치지 않았다는 뜻이고, 이 축이 잡는 것은 앞으로 들어올 오기다.

### 흡수 후 실측

`gates.sh` 완주 `rc 0` · `#N ok` 31행 · `FAIL 0` · `--selfprobe ok` 반사실 13종 hold.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R12 | (미실시) | PR-Codex `needs-attention` HIGH 1 | 8 | 단위 판정 방아쇠를 낱말에서 수치로 · 반사실 3분기 | 0 |

층위가 R11 의 1·3·6·8 에서 **8 하나로** 좁혀졌다. 지적이 문서·정책에서 하네스 자기 계약으로
내려왔고 건수도 4 → 1 이다. 마르는 곡선이다.

---

## PR-Codex R5 흡수 (2026-08-31, `…-m6-reseal7`)

`verdict=needs-attention` · HIGH 1 · MEDIUM 2. **봉인 4건과 R3·R4 흡수분의 재제기는 0건.**
세 건 다 인용을 실측으로 대조했고 전건 사실이었다.

반사실 접두를 `PC5` 로 쓴다 — `R5` 는 이미 재봉인 R5 가 가져갔다.

### F1 (HIGH) — 축 5 의 대상 판정이 산문 쪽에 서 있다

축 5(표지 결박)의 대상은 `proseSurface().risky`, 곧 **산문의** 수 종류가 둘 이상인가로 정해진다.
그래서 여러 층이 같은 값이던 정책은 결박 대상이 아니다. 층 하나가 갈라지고 강제 지점 표만
갱신되면, 허용 합집합에는 구·신 값이 다 들어가고 산문의 옛 수 하나는 그대로 통과한다. 문서는
모든 층이 같은 한도라고 계속 말하면서 `#8` 은 초록이다.

실측 — 지금 이 자리에 있는 정책 넷(값 슬롯 ≥ 2, 값 전건 동일):

| 정책 | 값 슬롯 | 소스 서로 다른 값 |
|---|---|---|
| `limits:charge-floor-three-layers` | 넷 | 하나 |
| `limits:age-floor` | 둘 | 하나 |
| `limits:attachment-count` | 둘 | 하나 |
| `limits:client-poll-cadence` | 둘 | 하나 |

흡수: **축 7 값 분포**를 신설했다. 판정을 소스 쪽 분포에 건다 — 소스의 서로 다른 값 수가 산문의
서로 다른 수보다 많으면 접힌 것이다. 산문이 무엇을 들었는지로 대상을 정하면 산문이 자기 검사
여부를 정하게 된다는 것이 F1 이 가리킨 자리 자체다.

**두 번 좁혔다.**

1. 부호를 접는다. 첫 집행에서 `limits:balance-cap-at-write` 가 떨어졌다 — 소스 3종
   `[4294967295 · 2147483647 · -2147483647]` 대 산문 2종. 상·하한이 같은 크기라 산문의 수 하나가
   두 슬롯을 **정직하게** 덮는다. 표시형 축(`slotDisplayForms`)이 이미 `Math.abs` 로 서 있으므로
   같은 기준을 쓴다.
2. 수치 0건 서술은 축 밖이다. 그다음 `limits:vendor-timeout-table-is-reference-only` 가 떨어졌다 —
   소스 1종 대 산문 0종. 값을 일부러 안 쓰는 참조표의 올바른 상태가 0건이고(UI7), 그 자리는
   축 3 이 이미 든다.

반사실 `PC5F1`: 산문의 두 수 중 하나를 다른 하나로 덮어 종류를 하나 줄인 사본에서 `#8` 이
떨어진다. 실제 회귀는 소스가 갈라지는 쪽이지만 동결 blob 은 못 바꾸므로 같은 부등식을 반대편에서
깬다. `#8` 출력에 `값 분포 대조 17정책`.

### F2 (MEDIUM) — 참조 전용 정책이 강제 지점으로 계상된다

`check13` 이 저장소별로 **구조가 맞는** 슬롯을 셌다. 강제 층이 `없음` 뿐인
`limits:vendor-timeout-table-is-reference-only` 도 server 슬롯이 둘이므로 `강제 지점` 이어야
통과했다. 같은 문서와 `#9`(`참조표 강제 자리 0`)가 그 표는 아무것도 강제하지 않는다고 선언하므로
산출물이 자기모순이었고, 독자는 참조 자료를 런타임 제한으로 오인한다.

흡수: **강제 층을 가진** 슬롯만 센다(`layer` 가 없거나 `없음` 이면 제외). 문서 적용 범위 행의
server 칸을 `—` 로 내리고, 그 절의 산문을 *"강제 자리를 가진 정책은 전건 서버 한 저장소"* 로
정정하며 세 열이 모두 빈 행의 존재를 문면에 적었다. `#13` 출력이 참조 전용 정책 수를 낸다.
`#9`·`#14`(`reference-only-table=하나`) 무변동.

`없음` 문자열이 `#9`·`#13`·주장 셋에서 쓰이므로 `LAYER_NONE` 상수로 모았다.

반사실 `PC5F2`: 그 행을 `강제 지점` 으로 되돌린 사본에서 `#13` 이 떨어진다.

### F3 (MEDIUM) — 초과 시 화면 동작 서술이 소스와 다르다

문서는 *"화면은 자기 재시도 상한까지 시도한 뒤 포기한다"* 로 적었다. 동결 소스 실측:

- `certify-rate-limit.guard.ts` 는 초과 시 `HttpException('요청이 많습니다…', TOO_MANY_REQUESTS)`
  를 던진다 — 본문이 문자열이고 `retryable` 필드가 없다.
- `treatApi.ts` 의 분류는 `!error?.response || data?.retryable === true || status >= 500` 이다.
  429 는 셋 다 아니다.
- 따라서 `Step2Page.tsx` 는 `isRetryableTreatError` 분기를 타지 않고 문자열 분기로 떨어져
  실패 모달을 **한 번** 띄우고 대기 상태를 푼다.
- 재시도 상한 `CERTIFY_RESUME_MAX_ERRORS` 는 `pollResume` 의 `catch` 에서만 센다 — 인증 재개
  상태 조회 오류 경로다.

흡수: 코드는 안 고친다(UI11). 문면을 사실대로 정정하고 재시도 상한의 적용 범위를 명시했다.

### 흡수 후 실측

`gates.sh` 완주 `rc 0` · `#N ok` 31행 · `FAIL 0` · `--selfprobe ok` 반사실 **15종** hold ·
`lint-plan.mjs` rc 0.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R13 | (미실시) | PR-Codex `needs-attention` HIGH 1 · MEDIUM 2 | 1 · 2 · 6 | F1~F3 전건 · 반사실 2종 신설 | 0 |

층위가 R12 의 8 하나에서 다시 **1·2·6** 으로 올라갔다. 건수도 1 → 3 이다. **마르지 않았다.**
직전 라운드에서 내린 "마르는 곡선" 판단은 한 라운드짜리 표본이었고, 이 라운드가 그것을 반증한다.

---

## PR-Codex R6 흡수 (2026-08-31, `…-m6-reseal7`)

`verdict=needs-attention` · HIGH 2 · MEDIUM 1. 재제기 0건. 세 건 다 동결 소스·파일 이력으로
대조했고 전건 사실이었다. **이 라운드로 흡수를 닫고 감사된 override 로 출하한다** — 판단 근거는
아래 §종료 판정.

### F1 (HIGH) — 관리자 변경은 캐시 창을 기다리지 않는다

`limits:provider-setting-cache-window` 서술이 *"관리자가 설정을 바꾸면 그만큼 지나야 반영된다"*
로 적었다. 동결 소스 실측 — `treat-provider.service.ts` 의 `update()` 는 저장 직후
`this.invalidate()` 를 부른다. 관리자 화면 경로는 다음 요청이 저장소를 다시 읽는다. 15초 지연이
남는 것은 캐시를 버릴 기회가 없는 경로 밖 변경뿐이다.

운영·지원이 관리자 변경 뒤의 옛 값을 "정상적인 반영 지연" 으로 정규화하게 만드는 자리다.

흡수: 문면을 두 경로로 갈라 정정했다.

**앵커 신설은 시도했다가 되돌렸다.** `update()` 의 무효화 호출을 `symbol-use` 슬롯으로 넣으면
`#4` 가 `invalidate 가 소비 형태 7종 중 어느 것도 아니다` 로 떨어진다(실측). 목록에 무인자 메서드
호출 형태를 더할 수는 있으나, 이 슬롯 모델이 묶는 것은 **값과 강제 층**이고 캐시 무효화는 둘 다
아니다 — 창의 *적용 범위*이지 한도의 강제 자리가 아니다. 모델을 왜곡해 축을 넓히는 대신 사실을
문면에 적었다(UI11). **이 문장은 게이트가 물지 않는다** — 그 사실을 여기 적어 다음 라운드가
층위 2 로 다시 집도록 남긴다.

### F2 (HIGH) — 풀 용량 출처가 폴백으로 서술됐다

`limits:provider-pool-budget-shape` 가 설정 문자열을 용량의 출처처럼 적었다. 실측 —
`getPool()` 은 `repo.findEnabledPool()` 의 활성 행을 먼저 쓰고, 행이 비었거나 조회가 실패했을
때만 `envPool()` 로 내려간다. 설정을 고쳐도 저장소에 행이 있으면 실효 용량이 안 바뀐다.

흡수: 문자열을 **폴백**으로 명시하고, 우선순위와 그 해석은 운영 문서 축임을 적었다(UI16 — 상수
정의는 이 PRD, 의미 해석은 40). 앵커는 늘리지 않는다 — 우선순위를 이 문서가 결박하면 UI16 을
스스로 깬다.

### F3 (MEDIUM) — 구현 리포트가 낡은 리비전을 검증한다

리포트가 `21fff33` 이후 한 번도 안 움직였다(`git log -1 -- <report>` 로 확인). 기록된 값은
lint 32건 · 추출기 17종 · 반사실 4종 · `.gates.mjs` 2374행이고 팁은 39 · 20 · 15 · 3242 이며
`gates.sh` 256행이 아예 없다. 출하 승인 근거가 봉인 대상과 다른 리비전을 가리킨다.

흡수: 착수 표를 그대로 두되 그것이 착수 시점 실측임을 명시하고, **브랜치 팁 재검증** 절을 신설해
두 리비전을 나란히 실었다. 리포트 파일은 lint glob(`docs/**`) 밖이고 어느 검사도 읽지 않으므로
이 절을 더하는 편집이 그 수를 바꾸지 않는다 — 그 사실도 절 안에 적었다.

### 흡수 후 실측

`gates.sh` 완주 `rc 0` · `#N ok` 31행 · `FAIL 0` · lint 대상 39건 · 추출기 20종 ·
`--selfprobe ok` 반사실 15종 hold.

### 라운드 표

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R14 | (미실시) | PR-Codex `needs-attention` HIGH 2 · MEDIUM 1 | 1 · 2 | F1~F3 전건 (앵커 신설은 시도 후 철회) | 0 |

### 종료 판정 — 수렴이 아니라 결정으로 닫는다

이 세션의 PR-Codex 네 라운드:

| 라운드 | 지적 | 층위 | 재제기 |
|---|---|---|---|
| R11 | 4 (HIGH 3) | 1 · 3 · 6 · 8 | 0 |
| R12 | 1 (HIGH 1) | 8 | 0 |
| R13 | 3 (HIGH 1) | 1 · 2 · 6 | 0 |
| R14 | 3 (HIGH 2) | 1 · 2 | 0 |

`--focus` 에 봉인·흡수·불변식 세 목록을 실은 뒤 **재제기는 네 라운드 연속 0건**이다. 그러나
건수도 층위도 마르지 않는다 — 표면(문서 3건 + 3242행 하네스 + 리포트)이 넓어 매 라운드 새 축이
나온다. R12 뒤에 적은 "마르는 곡선" 판단은 한 라운드짜리 표본이었고 R13·R14 가 반증했다.

수렴을 기다리는 대신 **사용자 결정으로 닫는다**(CLAUDE.md §4 — 마지막 스위치는 사람이 잡는다).
봉인된 `divergent` verdict 는 고쳐 쓰지 않는다: 하류 dedupe 는 fail-closed 로 남고, 다음
`/mccp:pr` 이 PR-Codex 를 다시 발화시키며, PR 본문이 이의를 문면에 싣는다.
