# 관리자 RBAC 마일스톤 7b (보완 admin-rbac-m7b) 기록

## 마감 요약 (2026-10-20)

- 재판정 (같은 날 마감 재실행, fable · codex): 판정 분열 6건과 채택 근거는 끝 절 「## 마감 재판정」. 결론은 같다. 마일스톤 7 은 in-progress 를 유지하고 코드는 바꾸지 않았다.
- 판정: 마일스톤 7 은 `in-progress` 로 둔다. complete 로 올리지 않았다. PRD 미달 7줄의 처리는 충족 2 (5 · 7), 부분 3 (1 · 2 · 6), 미충족 2 (3 · 4) 다. 미충족 2줄은 이 계획이 충족하지 않기로 한 항목이고, 부분 3줄의 나머지도 코드 · 격리 환경으로 할 수 있는 몫이라 complete 표기는 위장이 된다.
- 「PRD 미달」 (백로그 1683–1688 행, 보완 계획 admin-rbac-m7bb 입력. 기존 행 1652–1658 은 추가 전용 규칙에 따라 고치지 않았다):
  1. A. 메뉴 파생 효과 없음. 응답 경로 배선은 끝났으나 전환 스위치 `MENU_SOURCE_DERIVED` 가 OFF 다 (정책 §3). Outcome 「메뉴는 권한에서 파생한다」 는 M9 ON 전까지 발생하지 않는다. (행 1683)
  2. B. 증명 행 「역할별 메뉴 집합 행 대조」 미충족. 검사 #4 는 합성 골든 1건만 본다. 격리 합성 대조는 하지 않았다. 운영 대조는 사용자 몫이다. (행 1684)
  3. C. 버튼 필터 미구현. 대상 목록은 M9 판단 표 · M10 화면 입력이라는 이연 판단만 있다. (행 1685)
  4. D. 영업 · 기업 상수 메뉴 미이관. `insuniverse-admin/src/layouts/AdminLayout.tsx:387-422` 의 `salesMenus` · `corpMenus` 가 그대로다. 세션 원천 신설이 필요하다. (행 1686)
  5. E. 착수 조건 「옛 번들 403 처리 확인 (219 catch 중 권한 거부 분기)」 의 분류 미완. 관찰은 로컬 고정 빌드에 `GET /admin/user` 한 호출을 주입한 1건뿐이고, 권한 거부 분기 대상 catch 분류는 하지 않았다. 재계수는 `.catch(` 382 곳만 정했다. (행 1687)
  6. 잔여 HIGH 표지. 판정 조회 실패 시 일반 관리자 사이드바가 막히는 건은 기존 행 1677 에 있다. 이 계획이 흡수하지 않았고 (계획 동결), 다음 계획 입력 표지로 행 1688 을 더했다.
- 원 미달 7줄 처리: 1 부분 (배선만, 스위치 OFF) · 2 부분 (합성 골든 1건) · 3 미충족 · 4 미충족 · 5 충족 (E0607 표지, 관리자 프런트 · 사이트 admin-view 3화면) · 6 부분 (관찰 1건) · 7 충족 (`permissionDenied('permission')`, 검사 #2).
- 게이트 결과:
  - 계획 게이트 (라운드 상한 3/3): R1 L3 divergent · R2 L1 · L2 converged, L3 divergent · R3 L1 · L2 converged, L3 divergent (needs-attention, high 1 · medium 1). 계획 receipt 는 없다. 구현 진입은 skip-receipt (명령 단위 환경 변수) 로 열었다. 사용자 지시 원문은 `verify-user-directive.sh` 로 확인했다 (세션 기록 2026-10-19T02:21Z, exit 0).
  - 구현 receipt: `mccp-implement-codex`, divergent, `security_skipped`, impeccable `no-signal`.
  - 검사 #1–#13 재실행 (마감 시점 부모 `d8a76cf` · 서버 `ec01f883` · 관리자 `a9159d2a` · 사이트 `fc8872a1`): 전부 rc 0. 검사 #3 · #4 제목 9건 passed, 실행 실패 0. #5 제목 10건 ok. #6 제목 3건 ok, 타입 검사 줄은 적중 0 (커밋 뒤 실행이라 편차 1 은 해소). #7 vitest 1 passed. #8 `blank_screen=true` · `forced_hold_recorded=true`. #9 재계수 382. #10 RESOLVED-IN 8. #11 5개 파일. #12 plan-kit lint ok. #13 ok.
- code-review (라운드 1, 레인 code-review · security · typescript): 지적 15건 · 흡수 7 · 기각 6 · 백로그 2 (HIGH 1 · MEDIUM 1). CRITICAL 0. 미해소 HIGH 는 판정 조회 실패 건 (행 1677 · 1688).
- 최종 리뷰 (라운드 1, fable · codex): 지적 8행 (HIGH 2행은 같은 건) · 흡수 4 · 부분 흡수 1 (검사 #12 재동결) · 이연 3 · 기각 0. CRITICAL 0. 라운드 요약 (호출자 전달값): `[{"r":1,"found":8,"absorbed":4,"high_absorbed":0,"high_open":1,"codex":"ok"}]`. 보안 검토는 security-reviewer 를 부를 수 없어 (편차 6) 최종 리뷰 두 리뷰어가 전부다.
- 약어 풀이: 검사 #N = 게이트 스크립트의 N번 검사 · L1 = 계획서 형식 검사 · L2 = 4역할 관점 패널 · L3 = Codex 교차 리뷰 · skip-receipt = 계획 receipt 를 쓰지 않고 환경 변수로 구현 진입을 여는 처리 · 정책 = `.claude/notes/admin-rbac-run-policy.md`.
- (미검증) · 사용자 몫:
  1. 실 배포 번들과 열린 탭의 403 처리. 관찰은 로컬 빌드 (`146ed4d6` 펼침) 뿐이다. 관찰 결과가 `blank_screen=true` 이므로, 새 번들 배포를 확인하기 전까지 M9 영역 상수를 ON 으로 바꾸지 않는다 (정책 §2 M7 행).
  2. 운영 `tn_menu` · `tn_menu_auth_map` 과 역할별 사이드바 대조 (stg 읽기, 사용자 몫).
  3. 관리자 프런트 렌더: 메뉴 장애 안내 · 권한 거부 줄 · `/403` 이 먼저 실행되지 않는지. 자동 검사가 없다. 백로그 1667 의 해소 표시는 렌더 확인 뒤에만 한다.
  4. 사이트 admin-view 3화면의 실제 렌더. 자동 검사는 블록 컴포넌트 vitest 1건뿐이다.
  5. `MENU_SOURCE_DERIVED` ON 전환 (M9 체크리스트). 앞 계획의 편차 1 · 3 (스위치 OFF · 상수 메뉴) 과 편차 8 (`isSuper` 조회 실패를 fail-closed 로 둘지 비-200 으로 낼지) 은 사용자 확인 대상이며 확인 기록이 없다.
  6. 증명 #12 (`probe-compare.mjs` 의 `ME_KEYS` 네 키) 재동결: 격리 스택을 다시 돌린 뒤에만 한다 (M9 전).
  7. 계획 receipt 부재 (skip-receipt 경로) 와 구현 receipt 의 대상 (구현 전 diff, 편차 7). 최종 리뷰가 다시 본 것이 전부다.
  8. stg 병합 sha 대조 (정책 §1-1). 이 마감의 착수 기준은 로컬 HEAD 다.
  9. 설계 게이트 `no-signal` 은 서브모듈 UI diff 를 detector 가 못 보는 공백이다. 설계 점검은 계획서 「Design Critique」 절이 한다.
  10. 백로그 1593–1633 (M6 측정 · 강제 · 트랜잭션) 은 이 마일스톤 밖이며 열려 있다.
- 다음 마일스톤이 알아야 할 것:
  1. 연쇄 막힘: PRD 상 M8 착수 조건은 「M7 닫힘」, M9 는 「M7 완료」, M10 은 「M9 완료」다. M7 이 `in-progress` 인 동안 M8 이상은 착수 조건을 채우지 못한다. M7 을 닫으려면 A–E 를 다음 계획에서 채우거나, 사용자가 M7 의 범위를 다시 정해야 한다 (미결).
  2. 다음 계획 입력: 백로그 1683–1688 (admin-rbac-m7bb 표지) · 1677 · 1667 · 1668 · 1680–1682. 1677 의 흡수 방향은 두 가지다. 판정이 실패해도 비최고관리자 권한과 메뉴를 읽고 최고관리자 판정만 「불가」로 두거나, 메뉴 실패 시 재시도한다. 어느 쪽이든 계획 Task 3(a) 와 검사 #3 제목을 함께 바꿔야 한다 (계획 동결).
  3. 1668: 요청별 거부 상태가 필요하다. 같은 화면의 car-basic 은 `analysis.car.read`, detail · aggregate 는 `analyze-admin.read` 라서 한 요청의 성공이 다른 요청의 거부를 가린다.
  4. 응답 형상: `/admin/me` 는 여섯 키다 (`superAvailable` · `menusAvailable` 추가). 증명 #12 는 붉다 (재동결은 격리 스택 뒤).
  5. 게이트 주의: 검사 #6 의 타입 검사 줄은 커밋된 diff 만 본다 (편차 1). 커밋 전 워킹트리 실행은 이 줄에서 빈 값으로 실패한다.
  6. 옛 번들 관찰: 메뉴 경로 주입은 403 분기를 타지 않았다 (m7 편차 20 정정). 권한 거부 관찰은 데이터 API (`GET /admin/user`) 에 주입해야 한다.
  7. 기준선: 관리자 `tsc -p tsconfig.node.json` 오류 764 = 기준 764, 변경 파일 0. 서버 tsc 오류 23 (전부 spec, 기준 23). 검사 #9 의 `.catch(` 재계수 382 (규칙 고정).
  8. 착수 기준 (마감 시점 HEAD): 서버 `ec01f883` · 관리자 `a9159d2a` · 사이트 `fc8872a1` (feat/admin-rbac). 부모는 마감 커밋 뒤 HEAD. 이 계획의 착수 기준은 서버 `b9f0823a` · 관리자 `4cb92d8e` · 사이트 `6de0a9e9` · 부모 `acdfd06` 였다.

## 계획 기록

- 계획서: `.claude/PRPs/plans/admin-rbac-m7b.plan.md` · 사이드카: `.claude/PRPs/plans/admin-rbac-m7b.gates.sh`
- 상태: 계획 단계. 마일스톤 7 은 이 계획 뒤에도 `in-progress` 다 (미달 3 · 4 미충족).
- 착수 sha: server `b9f0823a`, admin `4cb92d8e`, site `6de0a9e9`, 부모 `acdfd06`. 선행 기준은 서버 `ea3c0359` (정책 §1-1).

## 계획 시점 관측

- 선행 병합 sha 8건 조상 확인 (검사 `#1`, rc 0).
- 서버 jest 기준선: `admin-me.service.spec.ts` · `menu-derivation.spec.ts` 2 suite 16 test 녹색.
- 구현 전 붉어야 하는 검사: `#2` (plain ForbiddenException 1건), `#8` (결과 파일 없음), `#10` (RESOLVED-IN 0건).
- 구현 전 녹색: `#11` (호스트 경로 0건).

## 계획 게이트

- 상태: 계획 게이트 라운드 상한 3/3 에 도달했다. 마지막 라운드 R3 는 판정 divergent 로 끝났고, 사용자 지시에 따라 skip-receipt 로 넘겼다. 계획서 (`.claude/PRPs/plans/admin-rbac-m7b.plan.md`) 는 고치지 않았다.
- 판정 근거: L1 (구조 검사) converged · L2 (관점 패널 4명: architect · security · test · invariant) converged, 4명 모두 pass · L3 (Codex 외부 리뷰) divergent (needs-attention, classification=ok, 사용 한도 문구 없음). 판정 파일 `.claude/state/plan-review/decision.json` · 리뷰 기록 `.claude/reviews/plan-review-admin-rbac-m7b.md`.
- CRITICAL 유무: 없음. R3 의 최고 심각도는 HIGH 1건이다.
- 사용자 지시 원문 (2026-10-19, 세션 기록을 `verify-user-directive.sh` 로 확인 · exit 0):
  > Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.
- 우회 확인: `MCCP_SKIP_RECEIPT=1 node …/receipt/cli.js preflight --command mccp:prp-implement --decision admin-rbac-m7b --plan .claude/PRPs/plans/admin-rbac-m7b.plan.md` 결과 `ok: true, bypassed: true` (exit 0, 2026-10-20). 환경 변수는 이 명령에만 걸었고 세션 · 설정 파일에는 두지 않았다. plan 게이트 receipt 는 쓰지 않았다 (`receipt_path` 없음).
- backlog 이관: HIGH 1건 · MEDIUM 3건 · LOW 6건 (`.claude/plans/codex-findings-backlog.md` 1667–1676행). HIGH 줄에는 `→ 다음 계획 반영 대상` 표지를 달았다.
  - HIGH (1667행): 메뉴 장애 상태가 재조회 성공 뒤에도 풀리지 않고, 기존 `/403` Navigate 가 장애 안내보다 먼저 실행될 수 있다 (L3).
- 이 계획으로 구현에 들어갈지는 사용자 판단 몫이다. 구현 진입 검증은 위 preflight 로 통과함을 확인했다.

### 라운드 표

| 라운드 | 판정 (L1 · L2 · L3) | 흡수 | 기각 · 이연 |
|---|---|---|---|
| R1 | L3 divergent (L1 · L2 기록 없음) | L3 HIGH 2건 (커밋 `9dcb073` 메시지 기준) | 기록 없음 |
| R2 | converged · converged · divergent | L3 HIGH 1건 (커밋 `28c1ee9` 메시지 기준) | architect LOW 레지스트리 중복 기각 (계획 Task 6(d)) |
| R3 (이번 판, 마지막) | converged · converged · divergent | 없음 (계획 미수정) | backlog 10건 (HIGH 1 · MEDIUM 3 · LOW 6) |

### R3 지적별 처리

층위 값은 리뷰 전 자가 1패스 체크리스트 (CLAUDE.md §2) 의 층 번호다. 1 = 사실 오류 · 설계 결함, 2 = 주장 ≠ 검사, 6 = 게이트가 보는 대상.

| 렌즈 | 심각도 | 층위 | 요지 | 처리 |
|---|---|---|---|---|
| L3 codex | HIGH | 1 | 메뉴 장애 상태가 재조회 성공 뒤에도 풀리지 않음 · 기존 `/403` Navigate 가 장애 안내보다 먼저 실행될 수 있음 | backlog (다음 계획 반영) |
| L3 codex | MEDIUM | 1 | `permissionDenied` 플래그가 탭 · 대상 변경이나 성공 응답 뒤에도 해제되지 않음 | backlog |
| L2 test | MEDIUM | 2 | 게이트 #5 는 `menuDecision(` 문자열만 봐서 `menuUnavailable` 분기 위치를 못 잡음 (`gates.sh:167`) | backlog |
| L2 test | MEDIUM | 2 | 게이트 #6 은 개수만 봐서 E0607 이 로그아웃 분기를 타지 않는지 못 잡음 (`gates.sh:194-200`) | backlog |
| L2 test | LOW | 2 | 게이트 #7 이 `setPermissionDenied(true)` 를 분기에 묶지 못함 (`gates.sh:224-228`) | backlog |
| L2 test | LOW | 6 | 게이트 #8 의 E0607 검사가 `GET /admin/user` 를 특정하지 않고, 스크립트 자기 보고 불리언을 읽음 (`gates.sh:251`) | backlog |
| L2 security | LOW | 6 | 누출 게이트 #11 이 사용자명 · 머신명 등 다른 절대 경로 루트를 못 잡음 (`gates.sh:288-295`) | backlog |
| L2 security | LOW | 1 | 옛 서버에 `superAvailable` 키가 없으면 `isSuper` 만 믿고 `super` 로 표시 (표시만, 권한 영향 없음) | backlog |
| L2 invariant | LOW | 6 | `jest_titles` 가 같은 제목의 마지막 상태만 남겨 중복 제목에서 실패가 가려질 수 있음 (`gates.sh:44`) | backlog |
| L2 invariant | LOW | 2 | `superDecision` 의 「키 없음 + `isSuper` true → super」 판정 근거가 계획서에 없음 | backlog |

## 재계수 (검사 #9 의 규칙)

- 규칙: `grep -rho '\.catch('` · `insuniverse-admin/src` · `--include` ts · tsx.
- `.catch(` 호출 382 곳 (규칙으로 다시 센 값). 앞 노트 (`admin-rbac-m7.md`) 의 「381」은 같은 대상을 다른 규칙으로 센 값이다. 정정한다.
- `try { … } catch (` 블록 수와 「403」 포함 파일 수는 이번 계획에서 세지 않는다 (권한 거부 분기 기준을 세우지 않았다).

## 이연 · 미충족 (사용자 확인 대상)

1. 메뉴 파생 스위치 `MENU_SOURCE_DERIVED` 는 OFF 로 둔다 (정책 §3). ON 전환은 M9 체크리스트에서 한다. 사용자 확인 필요.
2. 버튼 필터 (PRD 미달 3) 미충족. 대상 목록은 M9 판단 표 · M10 화면 입력.
3. 영업 · 기업 상수 메뉴 (PRD 미달 4) 미충족. 세션 원천 신설이 필요하다 (`AdminLayout.tsx:387-422`).
4. 역할별 메뉴 집합의 운영 행 대조 (미검증, 사용자 몫). 합성 골든만 검사 `#4` 가 본다.
5. 실 배포 번들 · 열린 탭의 403 처리 (미검증). 옛 번들 관찰은 로컬 고정 빌드 (`146ed4d6`) 다.
6. 이연한 HIGH 줄은 계획서 「이월 지적 반영」 표에 줄 번호별로 적었다 (m6 는 `admin-rbac-m6b`, m1 · m2 · m4 · m5 는 각 재동결 · M9 입력).

## 구현 뒤 채울 항목

- 검사 `#3` · `#4` · `#5` · `#6` · `#7` 실행 결과.
- 옛 번들 관찰 결과 파일 `.claude/meta/2026-10-20-admin-rbac-m7b/old-bundle-403-data.result.json` 의 요지 (`#8`).
- 구현 편차와 리뷰 기록 (코드 리뷰는 별도 세션, UI3).

## Codex Implementation Review

- 호출: `codex-invoke.js adversarial-review` (fail-closed Bash 래퍼), 라운드 수 1 (캡 3 봉인 · 2.5.4 기본 R1 만).
- 초점: 구현 전 결정 (서버 `menu/` · `auth/admin/corp-readonly`, 관리자 `src/api` · `src/shared/api` · `AdminLayout`, 사이트 `admin-view`). 실제 호출은 부모 브랜치 대 `main` 의 diff 에 대해 돌았다 (서브모듈 포인터 diff 만 보임). 이 판정은 구현 착수 전 상태를 본다.
- 판정: structured verdict `needs-attention` → gate verdict `divergent` (source=structured, classification=ok, blocking=false).
- 합치 결론: 지적 2건 (둘 다 HIGH) 은 계획서 Task 3 (판정 조회 분리 · `superAvailable`) 과 Task 5 (c) (폴백 `meFallback` · `menusAvailable`) 가 이미 흡수한 항목이다. 구현에서 그 두 곳을 채운다.
- YAGNI Triage:

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 `admin-me.service.ts` 판정 조회 실패가 `isSuper:false` 로 나감 | HIGH | ACCEPT_NOW | Task 3 (a) · 검사 `#3`. `isSuperAdminStrict` + `superAvailable` |
  | F2 `menuApi.ts` 폴백 두 호출 실패가 빈 메뉴로 나가 `/403` | HIGH | ACCEPT_NOW | Task 5 (c) · 검사 `#5` (`meFallback(null)`) · 제목 `meFallback: 두 메뉴 호출이 모두 실패하면 …` |

- Deferred to backlog: 0 (두 건은 이 구현이 해소한다). 확인 실패 시 백로그 HIGH 줄로 되돌린다.
- Open Questions: 없음 (auto-CRITICAL 항목 없음, 2.5.5 통과).
- Security Reviewer: Task 도구가 이 실행 단위에 없어 security-reviewer 를 부를 수 없다. 2.5.5 fallback 을 적용한다: `> security-reviewer unavailable, skipped (auto-fallback): Task tool not available in this implement run`. 인증 · 권한 영역을 건드리므로 receipt 는 `security_skipped` 를 싣는다 (`--security-skipped`). 최종 리뷰 (fable · codex) 가 이 공백을 다시 본다.
- Impeccable 설계 게이트: `impeccable-detect` 가 `design_signal=false` (`no-signal`, silent_skip) 를 냈다. 서브모듈 안의 UI diff 를 detector 가 보지 못하는 알려진 공백 (메모 mccp-impeccable-detect-blind-to-submodules) 이다. 계획서 `## Design Critique` 가 설계 신호 1 을 이미 점검했다. receipt 는 `--impeccable-silent-skip --impeccable-silent-skip-reason no-signal` 을 싣는다. 디자인 근거 캡처 (2.5.5c) 와 Phase 3.6 · 3.7 은 트리거가 없어 건너뛴다.
- 편차 (구현 순서): 본문은 2.5.6 receipt 를 Phase 3 앞에 쓰게 한다. 이 실행은 Codex 판정 (2.5.3) 뒤 receipt 쓰기 (2.5.6) 전에 서버 가드 · `admin-me` · `menu-derivation` 을 이미 고쳤다. 판정 대상 diff 는 구현 전 상태다. 이 순서 편차는 receipt 의 `codex_verdict=divergent` 를 바꾸지 않는다고 판단했다 (fail-closed 쪽 값).

## 구현 receipt 와 게이트 순서

- `mccp-implement-codex` receipt 는 `write` 가 env 없이 성공했다 (`divergent`, `security_skipped`, `impeccable_silent_skip=no-signal`). plan 게이트 receipt 는 없다.
- `validate --command mccp:prp-implement` 는 `missing: mccp-plan-codex` 로 exit 2 를 낸다. 이 명령은 `MCCP_SKIP_RECEIPT` 를 보지 않는다. receipt preflight 는 `MCCP_SKIP_RECEIPT=1` 에서 `ok: true, bypassed: true` 다 (사용자 지시 2026-10-19 의 skip-receipt 경로). 구현 진입은 이 preflight 로 열었다. env 는 명령 단위로만 걸었고 세션 · 설정에는 두지 않았다.
- 구현 편차 1: 본문의 2.5.6 receipt 는 Phase 3 앞이다. 이 실행은 Codex 판정 (2.5.3) 뒤, receipt 전에 서버 가드 · `admin-me` · `menu-derivation` 을 이미 고쳤다. receipt 의 `codex_verdict=divergent` 는 구현 전 상태를 본 값이다. 최종 리뷰가 다시 본다.
- Phase 3.5 (ultracode 위임) · 3.6 (디자인 마무리) · 3.7 (디자인 근거 검증) · 2.5.5c (근거 캡처) 는 트리거가 없어 건너뛴다 (`impeccable-detect`: `design_signal=false`).
- Phase 3.6 · 3.7 receipt restamp 는 트리거 부재로 실행하지 않았다.

## 구현 검사 실측 (2026-10-20 구현 뒤)

| 검사 | 결과 | 비고 |
|---|---|---|
| `#1` | ok | 조상 8건 |
| `#2` | ok | `new ForbiddenException` 0건, `permissionDenied('permission'` 1건 |
| `#3` | ok (제목 9건 passed) | admin-me · corp-readonly 가드 spec |
| `#4` | ok (제목 9건 passed) | 스위치 OFF 1행, spec 밖 `true` 0건 |
| `#5` | ok (제목 10건 ok) | `meDecision.test.ts` · `superDecision(` · `menuDecision(` · `meFallback(null)` |
| `#6` | 게이트 자체가 실패 (아래 편차 1) | 제목 3건은 ok (`errorCode.test.ts`) |
| `#7` | ok | 사이트 vitest 1건 · 화면 블록 3파일 |
| `#8` | ok | `blank_screen=true` · `logout_triggered=false` |
| `#9` | ok | `.catch(` 382 곳 |
| `#10` | ok | RESOLVED-IN 8 · 삭제 행 0 |
| `#11` | ok | 파일 5개 |
| `#12` | ok | plan-kit lint |
| `#13` | ok | 원장 구획 · 노트 |

## 구현 편차

1. 검사 `#6` 의 타입 검사 줄 (`scripts/type-check-changed.mjs 4cb92d8e`) 은 커밋된 diff (`merge-base..HEAD`) 만 본다. 커밋하지 않는 실행에서는 「변경된 .ts/.tsx 가 없다」로 빈 값을 내 게이트가 실패한다. 게이트 스크립트는 고치지 않았다 (커밋 뒤에는 이 검사가 의미를 가진다). 대신 `insuniverse-admin` 의 `tsc -p tsconfig.node.json` 을 워킹트리에서 돌렸다: 오류 764 건 = 기준선 764 건, 건드린 파일에 오류 0 건. 이 대체 검사는 커밋 뒤 다시 돌린다.
2. 메뉴 장애 안내는 계획 Task 5 (d) 의 「`if (filteredMenus === null)` 앞」이 아니라 `Navigate` 검사 앞에 둔다. 이전 `filteredMenus` 가 남아 있어도 `/403` 으로 가지 않게 하기 위해서다. 재조회가 성공하면 `setMenuUnavailable(false)` 로 장애 상태를 푼다. 이 두 자리는 백로그 2026-10-20 HIGH 1667 (장애 상태가 풀리지 않음 · `/403` 이 먼저 실행됨) 을 코드로 겨냥한 것이다. 렌더는 검증하지 않았다 (미검증). 백로그 줄은 고치지 않았다 (해소 표시는 렌더 확인 뒤).
3. 사이트 `PermissionDeniedBlock.tsx` 와 테스트에 `import React from "react"` 를 넣었다. 이 프로젝트의 site tsconfig 는 JSX 에 React 가져오기를 요구한다 (TS2686).
4. 사이트 권한 거부 플래그 `permissionDenied` 는 탭 · 대상 변경 (`analysisTypeAttr` · id 가 바뀌는 조회 effect) 에서 내린다 (코드 리뷰 R1 흡수). 같은 탭을 다시 조회해 성공해도 내려가지 않는 잔여는 그대로 남으며 백로그 MEDIUM 행 (2026-10-20, 「성공 응답 뒤에도 해제되지 않음」) 이 연다. 이 조회 effect 의 `loading` 가드 앞에서는 내리지 않는다 (가드에 걸리는 전환에서는 이전 거부 표지가 남을 수 있다 — 미검증).
5. `meFallback` 은 제네릭 기본값 `never` 를 쓴다 (`meFallback(null)` 호출 형태를 게이트가 문자열로 보기 때문).
6. security-reviewer 를 부르지 못했다 (이 실행 단위에 Task 도구가 없다). 2.5.5 fallback 으로 receipt 에 `security_skipped` 를 실었다. 인증 · 권한 경로 변경의 보안 검토는 최종 리뷰 (fable · codex) 몫이다.
7. 본문 2.5.3 Codex 판정은 구현 전 branch diff 에 대해 돌았다. 지적 2건 (HIGH, HIGH) 은 계획 Task 3 (판정 조회 분리) 과 Task 5 (c) (폴백 분리) 가 이미 흡수한 항목이라 R2 는 돌리지 않았다. 구현에서 두 자리를 채웠다.
8. 옛 번들 관찰 빌드는 `146ed4d6` 를 `git archive` 로 펼쳐 로컬 `vite build` 했다. `node_modules` 는 워크트리 설치본을 심볼릭 링크했다 (옛 lockfile 로 설치하지 않았다). 실 배포 번들과 같다고 주장하지 않는다 (미검증).
9. 사이드카 `admin-rbac-m7.gates.sh` 의 검사 `#3` 보정 (커밋 `edd6491`) 은 계획 Files to Change 밖이다. 최종 리뷰 R1 (LOW) 로 공시한다. 검사 강도는 줄지 않았다 (corp-readonly 긍정 단언 3줄을 더했다).

## 옛 번들 403 관찰 (검사 `#8`, substitute)

- 결과 파일: `.claude/meta/2026-10-20-admin-rbac-m7b/old-bundle-403-data.result.json`. 주입 스크립트: 같은 디렉터리 `old-bundle-403-inject.mjs` (`script_sha256` 이 이 파일의 해시다).
- 시드: 세션 저장소 키 `ins` 에 합성 관리자 상태 (옛 번들 `entities/user/model/store.ts` 의 persist 형식). 합성 값만 쓴다.
- 대조 실행 (주입 없음): `/user/user` 가 그려졌다 (검색 라벨 · 엑셀 버튼 확인). `GET /admin/user` 는 200 이고 데이터 행 1건이 보였다.
- 주입 실행: `GET /admin/user` 만 403 `{"code":"E0607","reason":"permission"}` 으로 바꿨다. 화면 골격은 그려졌고 (`page_rendered=true`) 데이터 행은 0 건이었다 (`blank_screen=true`). 로그아웃 신호와 로그인 화면 이동은 없었다 (`logout_triggered=false`).
- 해석: 옛 번들의 `userApi.getUsers` 가 `.catch(() => ({ list: [], count: 0 }))` 로 403 을 빈 목록으로 삼킨다. 권한 거부가 사용자에게는 「데이터 없음」으로만 보인다. 이 관찰은 이 빌드에서 본 것이다. 실 배포 번들과 열린 탭은 보지 않았다 (미검증). 결과 파일의 sha 묶음은 스크립트와의 연결이지 재실행의 증명이 아니다 (미검증).
- 정책 결론 (최종 리뷰 R1 흡수): `blank_screen=true` 이므로 정책 §2 M7 행의 「구 번들이 실패하면 M9 강제 보류」 조건이 성립한다. 새 번들 배포를 확인하기 전까지 M9 에서 영역 상수를 ON 으로 바꾸지 않는다. 검사 `#8` 이 이 결론의 노트 기록을 강제한다.
- 메뉴 경로 주입은 쓰지 않았다. 메뉴 응답은 두 실행에서 같다.

## 사용자 확인 목록 (미검증 · 사용자 몫)

1. 메뉴 파생 스위치 `MENU_SOURCE_DERIVED` 는 OFF 로 커밋한다 (정책 §3). ON 전환은 M9 체크리스트에서 한다.
2. 버튼 필터 (PRD 미달 3) 와 영업 · 기업 상수 메뉴 (PRD 미달 4) 는 이 계획에서 충족하지 않는다. M7 은 `in-progress` 로 둔다.
3. 역할별 메뉴 집합의 운영 `tn_menu` · `tn_menu_auth_map` 대조 (stg 읽기는 사용자 몫).
4. 실 배포 번들 · 열린 탭의 403 처리 (로컬 고정 빌드 관찰만 있다).
5. 관리자 프런트 렌더 (메뉴 장애 안내 · 권한 거부 줄 · `/403` 이 먼저 실행되지 않는지) 는 자동 검사가 없다. 정적 검사와 순수 판정 테스트만 있다.
6. 사이트 admin-view 3개 화면의 실제 페이지 렌더는 자동 검사가 없다 (블록 컴포넌트 vitest 1건만 있다).
7. 백로그 2026-10-20 HIGH 1667 (메뉴 장애 상태 해제 · `/403` 순서) 은 코드로 겨냥했다. 렌더 확인 전까지 백로그 줄은 그대로 둔다.
8. (M9 입력 · 코드 리뷰 R1) `/admin/me` 의 최고관리자 판정 조회가 실패하면 (`adm_is_super` 칸이 없는 DB) 일반 관리자 전원의 사이드바가 「메뉴를 불러오지 못했습니다」로 막힌다. 계획 Task 3(a) 가 이 동작을 정했고 검사 `#3` 제목이 고정한다. 다음 계획에서 고친다 (백로그 2026-10-20 HIGH 행). M9 스위치 ON 체크리스트 · Risks 에 이 blast radius 를 넣는다.
10. (M9 입력 · 최종 리뷰 R1) M7 증명 `#12` (`.claude/meta/2026-10-20-admin-rbac-m7/probe-compare.mjs:78`) 는 `/admin/me` 를 네 키로 동결했다. M7b 응답은 여섯 키다 (`superAvailable` · `menusAvailable` 추가). 그래서 M7 증명을 지금 다시 돌리면 `#12` 가 붉다. 재동결은 격리 스택을 다시 돌린 뒤에만 한다 (백로그 2026-10-20 MEDIUM 행, 미검증).
9. (M9 입력 · 코드 리뷰 R1) 전역 인터셉터의 권한 배너는 무음 호출 (Alarm 30초 폴링) 과 조직관리자 쓰기 차단에도 뜬다 (백로그 2026-10-20 MEDIUM 행). M9 가 `alarm-admin.read` 를 강제하기 전에 배너 opt-out 과 조직관리자 배너 정책을 정한다.

## 코드 리뷰

R1 (code-review · 레인 code-review · security · typescript). 판정은 리뷰 지적을 기준으로 하고, 흡수는 수정 뒤 검사로 확인했다. 정렬은 심각도 순이다.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | typescript | HIGH | 최고관리자 판정 조회 실패가 일반 관리자 전원의 사이드바를 막는다 | 백로그 (HIGH · 다음 계획 반영 대상) | 판정 실패 시 나머지를 묻지 않는 것이 계획 Task 3(a) 와 검사 `#3` 제목 (「메뉴를 묻지 않는다 (음성)」) 으로 고정돼 있어 계획 수정 없이는 못 고친다 (계획서 동결). 구버전에서는 거짓으로 접혀 일반 관리자가 메뉴를 받았다. `insuniverse-server/apps/api/src/modules/menu/admin-me.service.ts:73-86` · `insuniverse-server/apps/api/src/modules/admin/super-admin.query.ts:88-92` · `insuniverse-admin/src/layouts/AdminLayout.tsx:379` | 1 |
| R1 | security | MEDIUM | M7 사이드카 검사 `#3` 이 M7b 뒤 붉다 (호출 파일 7 고정 · corp-readonly 호출 0) | 흡수 | `bash .claude/PRPs/plans/admin-rbac-m7.gates.sh 3` 수정 전 rc 1 (`permissionDenied 호출 파일=8 (기대 7)`), 수정 뒤 rc 0 (`check 3 ok`). 기대값을 8 로 올리고 corp-readonly 검사를 `permission` 호출 1 · 상수 메시지로 바꿨다 | 2 |
| R1 | code-review | MEDIUM | 전역 인터셉터가 E0607 마다 권한 배너를 연다 (무음 호출 · 조직관리자 쓰기 차단) | 백로그 (신규 MEDIUM 행) | 지금 활성인 효과는 조직관리자 쓰기 차단 한 가지다. 폴링 효과는 M9 강제 뒤에 생긴다. opt-out 은 382 곳 `.catch` 점검과 배너 정책이 함께 필요해 이 판에서 흡수하지 않았다. `insuniverse-admin/src/shared/api/index.ts:53-57` · `insuniverse-admin/src/features/alarm/ui/Alarm.tsx:137-139` · `insuniverse-admin/src/api/alarmApi.ts:70-74` | 1 |
| R1 | code-review | MEDIUM | 백로그 1659 행의 RESOLVED-IN 이 스위치 OFF 상태의 PRD 미달 1 을 해소로 적는다 | 흡수 (정정 행 추가) | 백로그는 추가 전용이라 기존 행은 고치지 않았다 (`git diff` 삭제 행 0). 끝에 MEDIUM 정정 행을 더해 「배선 완료 · 효과 M9 ON 대기 · PRD 미달 행은 열림」을 적었다. `insuniverse-server/apps/api/src/modules/menu/menu-permission-map.ts:15` | 2 |
| R1 | code-review | MEDIUM | `permissionDenied` 플래그가 탭 · 대상 변경 뒤에도 남는다 (새 사용자 가시 회귀) | 흡수 (부분) | 3개 페이지의 탭 변경 effect 에 `setPermissionDenied(false)` 를 넣었다. 수정 전 0건 → 수정 뒤 3건 (`grep -c`). 같은 탭 재조회 성공 뒤 해제는 기존 백로그 MEDIUM 행 (「성공 응답 뒤에도 해제되지 않음」) 이 잔여로 연다. 런타임 렌더는 검사가 없다 (미검증). `insuniverse-site/front/src/pages/admin-view/AdminViewMemberAnalysisPage.tsx:448-451` (Client · B2C 같은 자리) | 1 |
| R1 | typescript | LOW | `flattenUrls` 가 `any[]` 로 노드 형상을 다시 선언한다 | 흡수 | 타입을 `MenuNode` (`menu-derivation.ts:60`) 로 바꿨다. 서버 tsc 오류 23 건 = 기준 23, 변경 파일 오류 0. 검사 `#3` · `#4` jest 녹색 (스위치 ON 경로 포함). `insuniverse-server/apps/api/src/modules/menu/admin-me.service.ts:12` | 1 |
| R1 | typescript | LOW | `pruneMenuTree` 이중 캐스트가 좁은 `T` 를 거짓으로 받는다 | 기각 | 캐스트는 제네릭 반환형을 맞추는 자리다. 호출자는 `MenuService` 가 주는 `any` 트리만 넘기고 (`admin-me.service.ts` 의 `menus`), 스위치 ON 에서만 돈다. 제네릭 계약을 바꾸면 호출 계약이 함께 바뀐다. `insuniverse-server/apps/api/src/modules/menu/menu-derivation.ts:67-68` | — |
| R1 | security | LOW | 옛 번들 관찰 스크립트의 접두 검사 (`startsWith(DIST)`) 가 형제 디렉터리를 통과시킨다 | 기각 | 1회 관찰용이며 `127.0.0.1` 임시 포트에서만 떠 있다. 결과 파일이 스크립트 해시 (`script_sha256`) 와 묶여 있어 고치면 기록의 연결이 깨진다. 재사용할 때 고친다. `.claude/meta/2026-10-20-admin-rbac-m7b/old-bundle-403-inject.mjs:53` | 1 |
| R1 | code-review | LOW | `PermissionDeniedBlock` 에 스타일이 없어 맨 문단으로 보인다 | 흡수 | 전용 클래스 정의가 없음을 확인했다 (`permission-denied-block` 은 src · public css 어디에도 없다). 빈 상태 화면의 `NoData` (`.nodata`, `public/css/style.css` 정의) 양식으로 바꿨다. 검사 `#7` vitest 1건 녹색. 렌더는 미검증. `insuniverse-site/front/src/pages/admin-view/PermissionDeniedBlock.tsx` | 6 |
| R1 | code-review | LOW | `PermissionDeniedNotice` 가 공용 `Button` 대신 원시 `button` 을 쓰고 편차로 적히지 않았다 | 흡수 | `Button.Secondary` (`src/shared/ui/Button.tsx`) 로 바꿨다. 관리자 tsc 오류 764 건 = 기준 764 건, 변경 파일 오류 0. `insuniverse-admin/src/shared/ui/PermissionDeniedNotice.tsx` | 2 |
| R1 | code-review | LOW | 노트 `#6` 행이 가리키는 편차 번호가 틀렸다 (편차 2 → 1) | 흡수 | 구현 검사 실측 표의 `#6` 행을 「아래 편차 1」로 고쳤다 (편차 1 이 tsc 기준 대조). | — |
| R1 | code-review | LOW | `menuApi` 폴백 (두 호출 모두 실패) 의 배선에 런타임 테스트가 없다 | 기각 | 배선은 Axios 인스턴스 (`@/shared/api`) 에 묶여 Node 러너가 못 돈다 (INV1 · 새 도구 금지). 검사 `#5` 가 `meFallback(null)` 문자열로 배선을 고정한다. 배선을 순수 모듈로 빼면 검사 `#5` 와 계획 Task 5(c) 문구가 함께 바뀐다. 실제 배선은 미검증 (확인 목록 5). `insuniverse-admin/src/api/menuApi.ts:42-52` | 5 |
| R1 | code-review | LOW | `getMe` 가 약 80행 단일 메서드가 되었다 | 기각 | 계획 Task 3(b) 가 확정한 순서 (판정 → 권한 → 메뉴 → 파생 → 응답) 를 그대로 둔다. 분기마다 제목 검사가 있고 (검사 `#3` 9건 passed) 동작 변화가 없다. | — |
| R1 | code-review · typescript | LOW | 노드 테스트 2파일 (`meDecision.test.ts` · `errorCode.test.ts`) 이 npm 스크립트에 묶이지 않는다 (중복 2건 병합) | 기각 | `insuniverse-admin/package.json` 의 scripts 에 test 가 없다. 계획 Files to Change 밖이고 검사 `#5` · `#6` 이 직접 돌린다. 스크립트 한 줄 추가는 다음 계획 입력 (새 의존 0). | 5 |
| R1 | typescript | LOW | `isPermissionDenied` 플래그는 읽는 곳이 없다 | 기각 | `insuniverse-admin/src/shared/api/index.ts:55` 가 유일한 쓰기다. 계획 Task 6(b) 가 이 플래그를 명시했고, 소비자는 M9 · M10 화면 입력이다. 지금 빼면 계획 편차가 하나 늘어난다. | 1 |

### 집계 (R1)

- 흡수 7 (정정 행 · 사이드카 · 탭 변경 해제 · `MenuNode` · 스타일 · 공용 버튼 · 편차 번호). 같은 항목 안에서 부분 흡수 2건 (플래그 잔여 · 정정 범위) 은 잔여를 백로그 기존 행으로 넘긴다.
- 기각 6 (배선 테스트 · `getMe` 길이 · 캐스트 · 스크립트 경로 · 노드 테스트 스크립트 · 미사용 플래그).
- 백로그 신규 2 (HIGH 1 · MEDIUM 1). HIGH 는 `→ 다음 계획 반영 대상` 표지. 백로그 정정 행 1 (MEDIUM) 도 추가했다.
- HIGH 흡수 0 · HIGH 미해소 1 · CRITICAL 미해소 0.

### 재실행 결과 (수정 뒤)

- 검사 `#3` · `#4`: ok (jest 제목 9건 passed). 검사 `#5`: ok (제목 10건 ok). 검사 `#7`: ok (vitest 1 passed).
- 검사 `#6`: 실패 (편차 1 과 같은 원인: 타입 검사 줄이 커밋 diff 만 봐서 빈 값). 이 판에서 새로 생긴 실패가 아니다. 나머지 줄 (이벤트 1 · 발행 1 · `classifyErrorCode(` · 제목 3건) 은 통과.
- 서버 tsc 오류 23 건 (기준 23). 관리자 `tsc -p tsconfig.node.json` 오류 764 건 (기준 764). 변경 파일에 오류 0.
- M7 사이드카 검사 `#3`: 수정 전 rc 1, 수정 뒤 rc 0.

## 최종 리뷰

R1 (최종 리뷰 · fable · codex). 두 리뷰어가 같은 판정 조회 실패 건을 HIGH 로 냈다. 판정은 리뷰 지적을 기준으로 하고, 흡수는 수정 뒤 검사로 확인했다. 정렬은 심각도 순이다.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable | HIGH | 판정 조회 실패가 일반 관리자 전원의 관리자 화면을 막는다 | 이연 (백로그 1677 기존 행 · 다음 계획 반영 대상 · 재기록 안 함) | 판정 실패 시 메뉴를 묻지 않는 동작은 계획 Task 3(a) 와 검사 `#3` 제목 「메뉴를 묻지 않는다 (음성)」 이 고정한다. 흡수하려면 계획 본문과 검사 제목을 같이 바꿔야 한다 (계획 동결). `insuniverse-server/apps/api/src/modules/menu/admin-me.service.ts:82-92` | 1 |
| R1 | codex | HIGH | 같은 지적 (판정 실패 때 일반 관리자 화면 차단, 메모리 내 재현에서 메뉴 호출 0회) | 이연 (위 fable 행과 같은 건 · 백로그 1677) | 판정 근거는 위 행과 같다. codex 재현은 판정 실패 경로에서 메뉴 호출이 0회인 것을 확인했다 | 1 |
| R1 | codex | MEDIUM | `jest_titles` 가 실행 종료 상태를 버려, 지정 제목 밖의 실패 (같은 spec 의 다른 테스트) 에도 검사 `#3` · `#4` 가 통과한다 | 흡수 | 게이트가 실행 실패 합계 (`numFailedTests` + 실패 suite + 런타임 오류 suite) 0 을 요구하도록 바꿨다 (`admin-rbac-m7b.gates.sh` `jest_titles`). 반사실: admin-me spec 에 실패 테스트 1건을 넣은 사본에서 옛 게이트 rc 0 (재현) · 새 게이트 rc 1 · spec 복구 뒤 새 게이트 rc 0. 같은 제목의 중복 실패 (백로그 1675) 도 같은 합계에 잡힌다 | 6 |
| R1 | codex | MEDIUM | 권한 거부 표지가 같은 요청의 재조회 성공 뒤에도 남는다 (member · B2C · client 세 화면 공통) | 이연 (백로그 1668 기존 행 · 정정 행 추가) | 단순 성공 해제는 쓰지 않는다. 같은 화면의 car-basic 요청은 `analysis.car.read` 이고 detail · aggregate 는 `analyze-admin.read` 라 (`route-permission-map.ts:67-71`), 한 요청의 성공이 다른 요청의 거부를 가린다. 요청별 거부 상태가 필요하고 렌더 검증이 없다 (미검증). `insuniverse-site/front/src/pages/admin-view/AdminViewMemberAnalysisPage.tsx:226` · `:263` · `:297` | 1 |
| R1 | fable | MEDIUM | `blank_screen=true` 관찰의 M9 강제 보류 결론이 노트에 없고, 게이트 `#8` 이 M7 `#13` 의 규칙을 떨어뜨렸다 | 흡수 | 게이트 `#8` 에 「blank_screen 또는 logout_triggered 면 노트에 M9 강제 보류」 규칙을 넣었다 (`forced_hold_recorded`). 노트 「옛 번들 403 관찰」 에 결론 줄을 넣었다. 반사실: 줄이 없을 때 `#8` rc 1 (실측) · 줄을 넣은 뒤 rc 0 | 2 |
| R1 | fable | MEDIUM | 백로그 1663 의 RESOLVED-IN 이 어댑터 `isSuper` 읽기까지 해소로 적었다 | 흡수 (정정 행 추가) | `permission-adapter.spec.ts` 의 `isSuper` 는 0건이다. `permission.adapter.ts:41-46` 의 `isSuper` dep 은 어떤 spec 도 타지 않는다. 1663 행은 고치지 않고 정정 행을 더했다 | 2 |
| R1 | fable | MEDIUM | M7 증명 `#12` (probe-compare) 가 네 키로 동결돼 M7b 뒤 재실행이 붉다 | 부분 흡수 (노트 사용자 확인 10 공시 · 재동결은 백로그 정정 행) | `.claude/meta/2026-10-20-admin-rbac-m7/probe-compare.mjs:78` 의 `ME_KEYS` 는 네 키다. 응답은 여섯 키다. 재동결은 격리 스택을 다시 돌리기 전에 값을 바꾸지 않는다 (미검증) | 2 |
| R1 | fable | LOW | 사이드카 `#3` 보정이 계획 Files to Change 밖이다 | 흡수 (공시) | 「구현 편차」 9 에 적었다. 검사 강도는 줄지 않았다 (corp-readonly 긍정 단언 추가) | — |

### 집계 (R1)

- 흡수 4 (jest 게이트 종료 상태 · 옛 번들 보류 규칙 · 1663 정정 행 · 사이드카 공시). 부분 흡수 1 (M7 `#12`). 이연 3 (HIGH 2 는 같은 건 · MEDIUM 1 은 백로그 1668). 기각 0.
- 백로그 신규 행 3 (전부 MEDIUM): M7 `#12` 재동결 · 1668 정정 (요청별 상태 필요) · 1663 정정. HIGH 신규 행은 없다 (1677 기존 행 재사용).
- HIGH 흡수 0 · HIGH 미해소 1 (1677, 판정 실패 시 일반 관리자 차단) · CRITICAL 미해소 0.
- 서브모듈 코드 변경 0. 이 라운드는 게이트 · 노트 · 백로그만 바꿨다.

### 재실행 결과 (수정 뒤)

- 검사 `#1`~`#13` 전체: 게이트 rc 0 (수정 뒤 전 검사 재실행, 2026-10-20).
- 검사 `#8`: 노트 줄을 넣기 전 rc 1 · 넣은 뒤 rc 0.
- 반사실 (검사 `#3` 의 jest 종료 상태): 실패 테스트 1건 주입 → 옛 게이트 rc 0 · 새 게이트 rc 1 · 복구 뒤 새 게이트 rc 0. 서버 spec 은 `git checkout` 으로 원상 복구했다 (서버 워킹트리 깨끗함 확인).
- 사이트 · 관리자 코드를 바꾸지 않았으므로 해당 vitest · tsc 검사는 다시 돌리지 않았다 (변경 없음).

## 마감 재검증

- 기준: 최종 HEAD (부모 `9225275` · 서버 `ec01f883` · 관리자 `a9159d2a` · 사이트 `fc8872a1`, 전부 커밋된 상태, 워킹트리 깨끗함). 사이드카 `.claude/PRPs/plans/admin-rbac-m7b.gates.sh` 를 검사 번호별로 한 번씩 실행했다. 격리 실행은 쓰지 않았다.
- 검사별 결과 (rc · 핵심 출력):
  - #1 rc 0 · 착수 sha 와 선행 병합 sha 가 각 HEAD 의 조상이다.
  - #2 rc 0 · 가드 쓰기 차단이 E0607 · reason permission 을 쓴다.
  - #3 rc 0 · 제목 9건 passed · 실행 실패 0.
  - #4 rc 0 · 제목 9건 passed · 실행 실패 0.
  - #5 rc 0 · 제목 10건 ok.
  - #6 rc 0 · 제목 3건 ok · 타입 검사 줄 변경 9파일 · 변경된 줄 적중 0건 · 기준선 추정 0건.
  - #7 rc 0 · 사이트 vitest 1건 passed.
  - #8 rc 0 · blank_screen=true · forced_hold_recorded=true. 기록된 결과 파일의 구조 · script sha 를 읽는 검사이며 주입 실행을 다시 하지는 않았다.
  - #9 rc 0 · `.catch(` 재계수 382 (노트 고정 규칙과 일치).
  - #10 rc 0 · RESOLVED-IN 8행.
  - #11 rc 0 · 대상 파일 5개 · 호스트 경로 · 세션 흔적 0건.
  - #12 rc 0 · plan-kit lint ok.
  - #13 rc 0 · 산출 기록 · 원장 구획 · 노트 존재.
  - 종합: 13개 전부 rc 0 (앞 마감 기록과 같다).
- 응답 불변 (#8 류): 통과. 옛 번들 403 관찰 결과의 bundle sha · seed · 주입 스크립트 sha · 주입 실행의 GET /admin/user 403 (E0607) · 대조 실행의 GET /admin/user 200 과 page_rendered=true 가 전부 맞다. 관찰 자체는 「M9 강제 보류」 결론을 노트에 남긴 상태다 (blank_screen=true).
- 반사실 (구현 전 붉음 기록 인용, 이번에 재실행하지 않았다): 계획 시점 관측에서 #2 는 plain ForbiddenException 1건 rc 1, #8 은 결과 파일 없음 rc 1, #11 은 메타 디렉터리 없음 rc 1 이었다. 현재 HEAD 에서는 셋 다 rc 0 이다. 즉 구현 전 붉음이 구현 뒤 녹색으로 뒤집혔다. #3 · #4 · #5 · #6 의 제목 검사도 구현 전에는 붉었다 (계획 시점 기록).
- 미검증: 실 배포 번들 · 운영 행 대조 · 관리자 프런트 렌더 · 사이트 admin-view 실제 렌더 (각 사용자 몫, 노트 「마감 요약」 (미검증) 목록과 같다).
- 재확인 (마감 재판정 시점): 사용자 지시 원문 `verify-user-directive.sh` exit 0 (user/external). 게이트 #9 rc 0 (재계수 382) · #11 rc 0 (파일 5) · #13 rc 0. 서브모듈 세 곳 HEAD 는 위 기준과 같고 워킹트리는 깨끗하다.

## 마감 재판정

- 기준과 판정 입력: 앞 「## 마감 재검증」 과 같은 HEAD. fable 판정(I1–I6)과 codex 판정(I1–I6)을 나란히 두고 채택 기준은 「근거가 있는 쪽」 (PRD 원문 · 정책 표 · 계획 동결 여부) 이다.

| 항목 | fable | codex | 채택 | 근거 |
|---|---|---|---|---|
| I1 메뉴 파생 스위치 · 매핑 상수 | backlog. 스위치 ON 은 M7 몫이고 OFF 는 정책의 로컬 편차 | accept-as-is (OFF 유지). 운영 ON 은 defer | OFF 유지. 스위치 ON 과 매핑 완성은 backlog 1683 · 1684 | PRD M7 행의 「메뉴는 권한에서 파생한다」 는 M7 몫이라는 fable 판정이 맞다. OFF 는 정책 §3 · §4 가 정한 채택이라 PRD 와 정책이 어긋난다. 이 어긋남은 사용자 확인 대상으로 둔다. 매핑 상수는 3행뿐이다 (`menu-permission-map.ts:17-21`) |
| I2 역할별 메뉴 대조 | defer (I1 에 흡수) | backlog. 격리 합성 대조는 로컬에서 가능 | backlog 1684. 운영 행 대조만 defer | 정책 §2 M7 행이 격리 합성 대조를 허용한다. 합성은 로컬에서 돌릴 수 있으므로 defer 할 근거가 없다 |
| I3 버튼 필터 | backlog | backlog | backlog 1685 | 두 판정 일치. 효과는 M10 전까지 0 이라는 fable 의 지적은 사용자 확인 사항으로 남긴다 |
| I4 영업 · 기업 상수 메뉴 | backlog. 사용자가 refactor PRD 로 옮길 수 있다 | backlog | backlog 1686. 사용자 답 대기 | 두 판정 일치. 증명 7 행에 대응 검사가 없다. M7 유지 또는 PRD 개정은 사용자가 정한다 |
| I5 옛 번들 403 | defer. 219 전수 분류는 결론을 바꾸지 않는다 | backlog. 전수 분류 필요 | 번들 sha 확인은 defer. 분류는 backlog 1687 | PRD 착수 조건 문구가 「219 catch 중 권한 거부 분기」 분류를 요구한다. 분류는 로컬 작업이라 defer 할 근거가 없다. 다만 219 수치는 재현되지 않으므로 분류 기준부터 다시 정해야 한다 |
| I6 판정 실패 시 일반 관리자 사이드바 차단 | backlog. m7bb 첫 항목 | fix-now | backlog 1677 · 1688. 이 마감에서 고치지 않음 | Task 3(a) 와 게이트 #3 · spec 제목이 이 동작을 계획으로 동결했다. 고치면 계획 본문과 게이트가 함께 깨지고, 구현 뒤 계획 수정은 ship 게이트를 막는다 (메모 `mccp-plan-edit-after-implement-dead-end`). 결함은 아직 배포되지 않았다. M7 을 stg 에 올리기 전 필수 확인으로 둔다 |

- 결론: 마일스톤 7 은 in-progress 를 유지한다. 서브모듈 커밋은 0이다. 이번 마감에서 새 backlog 행은 추가하지 않았다. 판정 항목 모두 1677 · 1683–1688 · 1684 · 1685 · 1686 · 1687 에 이미 있다.
- 멈춤 조건 확인: 응답 불변 13개 검사 rc 0, 베이스 트리 · 서브모듈 HEAD 불변, 사용자 지시 확인 exit 0. 해당 없음.
- 다음 착수 전 사용자 답 필요 (m7bb 입력 확정용):
  1. I4 범위: M7 에 둘지, refactor PRD 로 넘기는 PRD 개정을 할지.
  2. I1 스위치: OFF 유지(정책 §3 · §4) 를 확정할지, PRD M7 행을 정정할지.
  3. I6 · I5: 배포 전 필수 확인 (M7 을 stg 에 올리기 전 I6 수정, 운영 · stg 번들 sha 확인).
- 사용자 몫 (미검증): 기존 「마감 요약」 (미검증) 1–10 과 같다.
