# 계획 노트 — drug-diag-api 마일스톤 8 (약효분류 드릴다운)

**Plan**: `.claude/plans/drug-diag-api-m8.plan.md`
**Decision slug**: `drug-diag-api-m8`
**Review record**: `.claude/reviews/plan-review-drug-diag-api-m8.md`
**Receipt**: `.claude/receipts/mccp-plan-codex/drug-diag-api-m8.json`

> 이 파일이 판정 이력의 정본이다. 계획서 본문에 쓰면 `plan_hash` 가 움직여
> receipt 가 stale 로 떨어진다(형제 마일스톤 다섯과 같은 자리).

## 이 라운드의 입력 — 디자인 안 A

사용자가 목업(`.samples/스크린샷 2026-09-11 133100.png` · 안 A「모달 교체」)을 이 라운드의
참조로 지정했다. 그 파일은 `.gitignore:56` 으로 **저장소 밖**이라 게이트가 못 읽으므로,
목업을 증거로 인용하지 않고 **목업이 요구하는 길이 열려 있는지를 추적 파일에서 실측**해
관측 L 로 옮겨 적었다.

목업 대조에서 개정 전 계획서와 **정면으로 어긋난 자리 둘**이 나왔다:

1. **뒤로가기 위치** — 목업은 모달 헤더 왼쪽(`‹ 목록`), 개정 전 D2 는 본문 최상단.
2. **3단계 헤더 제목** — 목업은 분류명으로 교체, 개정 전 D2 는 「상병명이 두 단계 내내 남는다」.

**PRD 가 이미 목업 쪽이었다.** `.claude/prds/drug-diag-api.prd.md:74` 의 8행이
「상세는 **헤더의 「목록」** 으로 되돌아간다」고 적는다. 곧 개정 전 D2 는 목업뿐 아니라
자기 PRD 와도 어긋나 있었고, 그 어긋남은 이번 목업 대조로 처음 드러났다.

원인은 관측 D 의 **귀결**이 과했던 것이다. 관측 D 가 확인한 사실(`GroupDetailModal.tsx`
바이트 동결 · 소비자 5건)은 참이지만, 그 동결이 지키려던 계약은 「형제 탭 넷이 안 움직인다」
였다. 관측 L 이 그 계약은 바이트 동일 없이도 선다는 것을 실측했다 —
`shared/ui/Modal.tsx:27`(`backFn?` 선언) · `:101`(미전달 시 노드 0건) · `style.css:1296`
(`.back-btn { left:1.6rem }`) · `style_new.css:1457`(`h2 { max-width: calc(100% - 10rem) }`).
곧 바이트 동결은 계약의 **대리값**이었고 그 대리가 디자인을 기각하고 있었다(층위 6).

## R1 판정 원장

- **모드**: `MCCP_PLAN_REVIEW=hybrid` · `MCCP_PLAN_REVIEW_L3=1` · cap **1** (봉인 `enforce`)
- **L1**: `converged` (C6 인용 1건을 개정 중 밟아 전체 경로로 정정한 뒤 통과)
- **L2**: 4/4 **fail** · findings 13건 · quorum 3/4 미달
- **L3**: **발화하지 않았다** — `invoked:false` `classification=exit-nonzero exit=1`.
  같은 저장소의 과거 L3 호출은 `classification=ok` 였으므로 항구적 환경 결함이 아니라
  이 호출의 실패다. `rounds_so_far=1 cap=1` 로 재호출 경로가 닫혔다.
  **곧 이 판정에 교차모델 확인은 0건이고 `review_source=multi-agent` 다.**
- **판정**: `divergent` · block=true · wall-clock 399초

| 라운드 | A (L2 패널 4관점) | B (L3 Codex) | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R1 | architect·security·test·invariant 전건 fail (HIGH 6 · MEDIUM 5 · LOW 2) | 미발화 (`exit-nonzero`) | 1·2·3·4·5·6·7 | 0 — cap 소진으로 R2 없음 | 1 (분류 C) |

### findings 13건과 층위

| # | 관점 | 심각도 | 지적 | 층위 | 분류 |
|---|---|---|---|---|---|
| 1 | architect | HIGH | `#7` 술어 1「diff +3/-0」이 올바른 구현에서 하드 실패 — `GroupDetailModal.tsx:29` 의 `button={{ closeFn }}` 한 줄이 반드시 삭제로 나온다 | 5 | A |
| 2 | security | HIGH | `#7` 앵커가 고정 SHA 가 아니라 움직이는 `HEAD` — 구현 커밋 뒤 자기 자신과 대조해 공허 통과 | 6 | A |
| 3 | test | HIGH | 위와 같은 축 · `#11` 까지 같은 구멍임을 명시 | 6 | A |
| 4 | invariant | HIGH | 위와 같은 축 · `#7` 다섯 술어 전건 + `#11` 이 자동 통과함을 가장 넓게 적음 | 6 | A |
| 5 | test | HIGH | `#9` 의 `ANS007 .back-btn 0건` 이 공허 통과 — 모달이 안 열려도 0건. D7 이 「`#9` 가 사각지대를 받는다」고 한 주장 자체가 미검증 | 7 | A |
| 6 | invariant | HIGH | 디스패처가 미지 서브커맨드를 거부하지 않음 — 오타 하나면 검사 0건에 rc=0 · 「전건 통과」 | 3 | A |
| 7 | architect | MEDIUM | M5a `#8` 이 바이트 대조 뒤 `die()` 라, 이 창이 그 파일을 고치면 같은 검사의 뒤쪽 팔 둘(처분표 부활 · `.ans003-thead`)이 영구 도달 불가 | 6 | A |
| 8 | architect | MEDIUM | 이 창 **2단계**의 `.back-btn` 0건을 세는 축이 없다 — `onBack` 무조건 전달 구현이 `#3`·`#9` 를 전부 통과 | 2 | A |
| 9 | security | MEDIUM | `m8.fixture.json` 「실환자 값 0건」이 자기 단언 · 대응 검사 없음 (m5b 에서 같은 축이 분류 B 로 이월돼 미해소) | 2 | A |
| 10 | invariant | MEDIUM | rc=2(skip)의 합법 조건 미정의 — 게이트 기계 자신의 고장이 skip 으로 흐를 경로 | 4 | A |
| 11 | test | MEDIUM | Acceptance 「2단계 화면의 `.gdm-title` 0건」이 `#4` 의 정적 스캔으로 성립 불가 — `BucketSection` 이 그 클래스를 계속 낸다 | 5 | A |
| 12 | architect | LOW | Patterns to Mirror · Task Mirror 의 행 번호가 실제와 2행씩 어긋남 (M5b 커밋 드리프트) | 1 | A |
| 13 | test | LOW | `#2` 의 fixture 가 손으로 만든 표본이라 「분포 재현」이 자기충족 | 6 | C |

**A 12건이 다음 창의 입력이다.** 그중 셋(#1·#2~#4·#5)은 **이번 개정이 새로 들인 결함**이다 —
바이트 동결을 계약으로 내린 대가가 D7 이 계산한 것보다 컸다. D7 은 「대가는 층위 2 이고
막을 축 넷을 이름으로 적는다」고 썼는데, 실제로 나온 것은 층위 5·6·7 이었다.

**기각 1건(#13)** — 계획서 Acceptance 가 이미 `(미검증)` 으로 적은 사각지대다. 실환자 응답을
게이트에 실을 수 없다는 제약의 귀결이고 살아 있는 결함 0건.

## 이 라운드가 남긴 것

- 미해소 13건 전건 → `.claude/plans/codex-findings-backlog.md` (분류 A/C · 판정 사유 · 증거 동반)
- receipt → `converged=false` · `codex_verdict=divergent` · `open_questions` 11건.
  **`converged` 로 위장하지 않았다**(전역 CLAUDE.md §mccp).
- `MCCP_SKIP_INTENT_GATE` 감사 override 1건 — L3 미발화로 심판할 Codex finding 이 0건인데
  `review_mode=hybrid` 만으로 intent 게이트가 걸렸다. 차단 판정은 그대로 봉인됐고
  `intent_gate_verdict=incomplete` 로 남아 cross-gate dedupe 는 닫힌 채다.

## 다음 창이 확인할 것

이 receipt 는 `/mccp:prp-implement` 를 **막지 않는다**(plan-codex 는 lenient 게이트라
validate 가 receipt 의 실재·신선도·변조만 본다 — 실측 `validate --command mccp:prp-implement`
→ `ok:true`). 실제 차단은 `/mccp:pr` 의 ship gate 에서 걸린다 —
`pr-ship-gate.js:46` 의 `SHIP_VERDICTS = ['converged','skipped']` 에 `divergent` 가 없다.
곧 **A 12건을 닫지 않은 채 구현에 들어가면 PR 단계에서 멈춘다.**

---

## Codex Implementation Review

> **이 절이 계획서가 아니라 notes 에 있는 이유.** 계획서 본문은 `mccp-plan-codex` receipt 의
> `plan_hash`(`sha256:bed9580a…`)에 결박돼 있고, 한 글자라도 고치면 그 receipt 가 stale 이 되어
> `validate --command mccp:prp-implement` 가 `ok:false` exit 2 를 내고 **Phase 3 진입 자체가 막힌다**.
> 가정이 아니라 실측이다 — 형제 마일스톤 m5b 가 정확히 그 상태이고(`140fd15b` 대 `64bcaa57`)
> 같은 명령을 그 slug 로 돌리면 `stale: [{gate_id: "mccp-plan-codex", reason: "plan file hash differs
> from receipt"}]` 가 나온다. mccp 명령 본문이 「plan 또는 notes」를 허용하므로 notes 를 쓴다.

- 호출: `node /home/madsc/.claude/plugins/cache/mccp/mccp/1.33.6/scripts/lib/codex-invoke.js adversarial-review`
- 라운드 수: 1 (R2 없음 — Codex 비활성이면 R2 는 존재하지 않는다)
- 합치 결론: **Codex 는 발화하지 않았다.** `classification=disabled` · `blocking=false` · `durationMs=1`.

### Codex 미발화의 실제 원인 — `disabled` 가 가리는 사실을 여기 적는다

receipt 의 `codex_verdict` 는 `skipped` 로 남지만 그 값이 사실의 전부가 아니다. 순서는 이렇다:

1. 2.5.0 이 `codex_disabled=false` 로 봉인하고 2.5.3 이 정상 호출했다 →
   `classification=exit-nonzero` · `exit=12` · `blocking=true`.
2. 원인을 직접 재현했다 — `codex exec --sandbox read-only "reply with exactly: PING-OK"` →
   `ERROR: You've hit your usage limit. … try again at Sep 15th, 2026 10:37 AM.`
   곧 **환경 결함이 아니라 계정 사용량 한도**이고, 오늘(2026-09-11) 기준 4일간 복구되지 않는다.
   같은 원인이 이 마일스톤 plan 게이트의 L3 미발화(`classification=exit-nonzero exit=1`)도 설명한다.
3. 운영자가 선택지 셋(advisory 진행 · 9/15 대기 · `MCCP_CODEX_DISABLED=1`) 중 **셋째**를 골랐고,
   그 정책으로 2.5.0 을 다시 봉인해 `classification=disabled` 를 얻었다.

**`skipped` 는 ship gate 를 통과하는 값이고 `unavailable` 은 아니다** — 곧 이 선택은 차단을 하나
치운다. 그 사실을 숨기지 않고 여기 적는다(전역 CLAUDE.md §mccp 「`converged` 로 위장하지 않는다」의
같은 정신). 다만 이 결정으로 실제 차단이 늘거나 줄지는 않는다: `mccp-plan-codex` receipt 가 이미
`codex_verdict=divergent` 이고 `pr-ship-gate.js` 의 `SHIP_VERDICTS = ['converged','skipped']` 에
`divergent` 가 없으므로, `/mccp:pr` 은 이 창의 Codex 판정과 무관하게 막혀 있다.

### Security Reviewer

`mccp:security-reviewer` 를 착수 전 제안(픽스처 · 게이트 스크립트 셸 표면 · 공용 모달 prop 확장)에
돌렸다. 결과 **2 CRITICAL · 1 HIGH · 1 MEDIUM · 0 LOW**.

| Finding | Severity | Verdict | Why |
|---|---|---|---|
| S1 — `m8.fixture.json` 의 「실환자 값 0건」이 자기 단언이고 `#2` 에 그것을 보는 술어가 없다. `provider`·`treatmentStartDate`·`dxCode`·`rationale` 이 실응답에서 손으로 옮겨진다 | CRITICAL | **ACCEPT_NOW** | 픽스처를 닫힌 합성 형식으로 짓고 `#2` 가 그 형식을 기계로 문다 — 실코드 denylist + 필드별 allowlist. 이 창이 닫는다 |
| S2 — 계획서 §GROUND 본문이 실상병코드 셋(`M766`·`J304`·`E785`)과 실분류명(`진해거담제`)을 담은 채 커밋된다. 어떤 게이트도 계획서 산문을 스캔하지 않는다 | HIGH | **DEFER_TO_BACKLOG** | 지적은 참이다 — 실측으로 그 넷은 HEAD 이력에 **0건**이고 새로 들어가는 값이다. 그러나 제안된 정정(본문 편집)이 `plan_hash` 결박으로 **기계적으로 불가**하고(위 상자), 노출의 형태는 식별자·날짜·병의원 없는 상병코드뿐이라 재식별 경로가 서지 않는다. 위험이 실제로 큰 조합(`provider`+`treatmentStartDate`+`dxCode`)은 S1 이 닫는다. **커밋 전에 사람이 마지막 스위치를 잡는다** |
| S3 — `#7`·`#11` 의 앵커가 `git show HEAD:` 라 구현 커밋 뒤 자기 자신과 대조해 다섯 술어가 전건 공허 통과한다 | CRITICAL | **ACCEPT_NOW** | L2 패널 셋(security·test·invariant HIGH)이 독립으로 같은 것을 찾은 축이다(backlog `92f8043e`·`d45716bb`·`b18672fb`). `m8.anchor.json` 에 SHA 를 동결하고 `#7` 이 그 blob 을 읽는다. 더해 **앵커 blob 의 `onBack` 계수 0건**을 술어로 둔다 — 앵커를 구현 커밋으로 옮기면 그 술어가 붉는다 |
| S4 — `gates.mjs` 가 `spawnSync(..., {shell:false})` 규약을 계약으로 안 지고 있다. m5c 는 자기 안의 `execSync` 금지 계수를 스스로 센다 | MEDIUM | **ACCEPT_NOW** | 싸다. `run()`/`git()` 래퍼를 m5c 손금대로 쓰고 `#7` 에 자기 코드의 `execSync`·`execFileSync` 계수 0건 술어를 접는다 |

- Deferred to backlog: **1** → `.claude/plans/codex-findings-backlog.md`
- Open Questions: S2 — severity HIGH (커밋 전 사람 판단 대기)
- Codex session 참조: 없음 (`classification=disabled`)

### 이 창이 착수 전 실측으로 뒤집은 계획서 서술 넷

계획서는 `plan_hash` 결박이라 고칠 수 없으므로 **게이트와 이 노트가 정정을 진다**. 넷 다
계획서가 주장했고 실행이 반증했다.

| # | 계획서 서술 | 실측 | 이 창의 처분 |
|---|---|---|---|
| 1 | `#7` 술어 1 — `GroupDetailModal.tsx` diff 가 **`+3/-0`** (「삭제 0줄」) | `GroupDetailModal.tsx:29` 가 이미 `<Modal.Head button={{ closeFn: closeModal }}>` 다. `backFn` 을 더하려면 그 줄이 **반드시 삭제 1줄**로 나온다 | `#7` 이 **`+3/-1`** 을 물고, 삭제된 1줄이 **정확히 그 `closeFn` 줄**인 것을 함께 문다. backlog `id=1ec2af8d` 가 맞았다 |
| 2 | Task 5 — 「앵커는 이 창 착수 시점의 `git show HEAD:` 내용이다」 | 코드 대상이 전부 **`insuniverse-site` 서브모듈** 안이다. superproject 에서 `git show SHA:insuniverse-site/front/…` 는 gitlink 라 해석되지 않는다 | 앵커를 서브모듈 SHA `6f9bfac5…` 로 잡고 `git -C insuniverse-site show <sha>:front/…` 로 읽는다. `m8.anchor.json` 에 동결 |
| 3 | Task 6 — 형제 탭 **`ANS007`** 을 렌더해 `.back-btn` 0건을 센다 | `ANS007` 은 `useParams` + 자기 API 를 타므로 새 모킹이 필요하고, **모달이 안 열려도 0건**이라 공허 통과한다(backlog `id=1fb7e6b5`) | 표본을 `ANS007` 이 아니라 **`GroupDetailModal` 자신**으로 바꾼다 — `onBack` 없이 렌더해 `.back-btn` 0건 **이면서 제목이 실제로 서 있는 것**을 함께 세면 공허가 닫히고, 공용 컴포넌트 자신이라 형제 넷을 표본이 아니라 **정의로** 덮는다. 정적 축(소비자 넷의 `onBack` 전달 0건)은 `#7` 이 그대로 진다 |
| 4 | Acceptance — 「2단계 화면의 `.gdm-title` 이 0건 — 게이트 `#4`」 | `BucketSection` 이 같은 파일에서 `gdm-title` 을 계속 내므로 **파일 단위 정적 계수는 0 이 될 수 없다**(backlog `id=38be5e81`) | 그 술어를 `#4`(정적)에서 `#9`(렌더)로 옮긴다. `#4` 는 증명 가능한 정적 축만 진다 |

### 게이트 기계 자신의 결함 둘 — 이 창이 닫는다

| # | 결함 | 처분 |
|---|---|---|
| 5 | 디스패처가 미지 서브커맨드를 거부하지 않는다 — `bash gates.sh check1O` 가 검사 0건에 `[gates] 전건 통과` rc=0 을 낸다(backlog `id=326cd7df`) | 알려진 라벨 집합과 대조하고 미지 인자는 rc=1 로 떨군다 |
| 6 | `run_check` 가 rc=2 를 합법 skip 으로 받는데 **어떤 검사도 skip 의 합법 조건을 정의하지 않는다** — 기계 자신의 고장이 skip 으로 샌다(backlog `id=bacaa006`) | 이 하네스에는 합법 skip 이 **0건**이다. rc=2 수용 팔을 지우고 입력 부재를 하드 실패로 둔다(m5a 선례: 「네트워크 부재도 하드 실패다」) |

### 실측 하나 더 — `git diff` 가 difft 를 탄다

`git config --get diff.external` → `difft`. 곧 본문을 파싱하는 자리에서 `--no-ext-diff` 를 빼면
unified diff 가 아니라 difft 출력이 와서 **조용한 0건**이 된다. 이 창의 게이트와 mccp 의
design-grounding 캡처 양쪽에서 그 플래그를 명시한다.

### 2.5.5b — 감사 override 를 켜지 않은 판단

검출기는 `skill_available=1 · design_signal=0 · silent_skip_reason=no-signal` 을 냈다. 착수 전이라
diff 가 비어 있어서이지 이 창에 디자인 표면이 없어서가 아니다 — 그 자리를 위해 mccp 는
`MCCP_DESIGN_INTENT_REASON` 감사 override 를 두고, 형제 m5b 가 실제로 그것을 썼다. 이유 문자열을
지어 strict 검증까지 통과시켜 놓고(`{"ok":true}`) **켜지 않았다.** 근거 셋:

1. **pre-EXECUTE critique 가 볼 대상이 없다.** routing 이 `renderingSurface=false` 를 내고
   `layout`·`adapt` 를 포함한 refine 전건이 `recommend` 로 강등된다. 남는 `invoke` 는
   `critique`·`audit` 둘인데, 이 창의 디자인 판단은 목업 5안 비교로 이미 한 번 거쳤고(안 A 채택)
   그 산출이 D1~D7 의 입력이다. 계획서는 `plan_hash` 결박이라 critique 결과를 본문에 반영할
   수도 없다.
2. **잃는 것은 Phase 3.7 하나인데 그 성질을 이 창의 게이트가 이미 진다.** 2.5.5c 캡처가
   트리거에 걸려 있고 3.7 은 캡처 아티팩트가 있어야 돈다. 3.7 이 기계로 보는 것은 H15
   — 렌더 표면의 헤딩 깊이 ≤ 3 이다. 같은 성질을 `#4` 가 `h4` 신설 0건으로 직접 문다(이 창의
   architect 지적 「4번째 중첩 단계를 만들면 안 된다」와 같은 축이고, 계획서 파일 주석이
   「`h4` 로 내리면 네 번째 단계가 생긴다」로 이미 규범을 적어 둔 자리다).
3. **진짜 자리는 EXECUTE 뒤다.** Phase 3.6 은 트리거를 **자기가 다시 유도**하므로, 구현이
   `.tsx`·`.css` 를 움직인 뒤에는 override 없이도 검출기가 양성이 된다.

곧 receipt 에는 `impeccable_silent_skip=true · reason=no-signal` 이 사실대로 실린다. 이 값은
「이 창에 디자인 표면이 없다」가 아니라 「착수 전 diff 에 없다」는 뜻이며, 그 구별을 여기 적는다.

### Design Finish — impeccable critique (EXECUTE 뒤)

2.5.5b 에서 「Phase 3.6 은 트리거를 자기가 다시 유도하므로 EXECUTE 뒤에는 override 없이도
검출기가 양성이 된다」고 적었다. **그 예측이 틀렸다.** EXECUTE 뒤에도 `design_signal=false` 다 —
`impeccable-detect.js` 가 **superproject 의 diff** 를 읽는데 이 창의 코드는 전부
`insuniverse-site` **서브모듈** 안이라 상위에서는 gitlink 한 줄만 보인다. 서브모듈 안에서 직접
세면 rendered surface 는 4파일이다(`style_new.css` · `ANS003B.test.tsx` · `ANS003BDiagnosis.tsx` ·
`GroupDetailModal.tsx`). 검출기의 사각지대이지 이 창에 디자인 표면이 없는 것이 아니다.

미루기로 한 근거가 사라졌으므로 그 자리를 그 시점에 돌렸다. `critique` 플레이북이 A·B 두
서브에이전트를 **필수**로 요구하므로(단일 컨텍스트는 degraded run) 둘을 격리해서 띄웠다.

- **Assessment B (검출기 + 증거)**: `impeccable detect` → **exit 0 · findings 0**. 브라우저는
  인증에서 막혔다(실측: dev 스택은 살아 있다 — basti 13306 · server 7200 · vite 5298 ·
  단일포트 프록시 5399 — 그러나 `/auth/login` 으로 리다이렉트). 곧 **실렌더 폭 · 읽기 폭
  상한의 실제 효과 · 렌더 픽셀 대비**는 이 창에서 `(미검증)` 이다. 정적 측정은 `#6` 과 일치.
- **Assessment A (디자인 리뷰)**: Nielsen **32/40**, design-specificity 「grounded, not generic」.

| # | 지적 | 판정 |
|---|---|---|
| A4 | 모달 뒤로가기에 **접근가능한 이름 0건**, 계획서의 「`aria-label` 이 이름을 진다」가 거짓 | **ACCEPT_NOW** (아래) |
| A1 | 읽기 폭 상한이 근거로 든 빈 자리를 부분만 없앤다 — 원인은 `.gdm-card-row` 의 `space-between` | DEFER → backlog `id=m8-reading-cap-leaves-dead-space` |
| A2 | 3단계 부제가 `DESIGN.md:203` 의 1.6 Floor Rule 아래(1.4rem)에서 읽기 필수 정보를 진다 | DEFER → backlog `id=m8-subtitle-below-type-floor` |
| A3 | 2단계 카드 다수에서 강조색이 사라진다 — 설계가 인정한 교환 | DEFER → backlog `id=m8-accent-fades-at-level-2` |

**A4 — 계획서의 명시적 배제를 사람이 열었다.**

실측 셋으로 확정했다: `Modal.tsx:75-81` 의 버튼에 `aria-label` 없음 · `Svg.tsx:210` 의
`BackIcon` 이 `<title>` 없는 순수 `<svg>` · 착수 앵커 기준 저장소 전역에서 `backFn` 을 넘기는
호출부 **0건**. 곧 WCAG 4.1.2 위반 컨트롤을 **이 마일스톤이 앱에 처음 도달 가능하게 만든다** —
「선재 결함」이 아니다. 계획서 §NOT Building 은 그 자리를 「아이콘만으로 서고 `aria-label` 이
이름을 진다」로 닫았고 그 문장이 거짓이었다: 배제가 **거짓 전제 위에** 내려져 있었다.

혼자 고치지 않고 운영자에게 올렸다. 판정은 **「이 창에서 한 줄 고친다」**.

그래서 `#7` 술어 ⓸ 를 **바이트 동결에서 계약으로** 내렸다 — D7 이 `GroupDetailModal` 에 대해
편 논증과 같은 자리이고, 여기서도 대리값이 고쳐야 할 것을 막고 있었다. 막을 축 둘을 이름으로
적는다: ⓐ `ModalBackButton` 블록 **밖**이 앵커와 바이트 동일(특히 `ModalCloseButton` — 이쪽은
형제 넷이 **실제로 렌더**하므로 그 넷의 출력 불변이 여기 걸려 있다) · ⓑ 블록 안은 `aria-label`
한 줄뿐(+1/-1). 음성 3건을 실행해 확인했고, a11y 회귀는 `#7`(정적)과 `#9`(렌더,
`getByRole("button", { name: "뒤로" })`)가 **둘 다** 잡는다 — 클래스 계수는 이름이 없어도
통과하므로 그 축을 못 잡는다.

`audit` 은 돌지 않았다. finish 라우팅 신탁이 `skipped:true` 를 냈고(위의 검출기 사각지대),
`critique` 하나만 호출된 것이 receipt 의 `impeccable_commands_routed` 에 그대로 실린다.
