# 관리자 RBAC 마일스톤 7 노트

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

- 상태: PRD 마일스톤 7 은 in-progress 다. complete 로 올리지 않았다. PRD Outcome 과 증명 행에 미달이 남아 있다 (선례 M6 163c75f).
- 「PRD 미달」 (백로그 2026-10-20 HIGH 행 7건, 보완 계획 admin-rbac-m7b 입력):
  1. 메뉴 파생 미배선. `/admin/me` 의 메뉴는 현행 `getMyMenuTree` 결과다 (`admin-me.service.ts:49`). `MENU_SOURCE_DERIVED = false` 라 스위치가 꺼져 있다 (`menu-permission-map.ts:15`, 편차 3).
  2. 증명 행 「역할별 메뉴 집합 행 대조」 미실시. 메뉴 ↔ 권한 매핑은 근거가 확인된 3건뿐이다 (편차 4). 운영 tn_menu · tn_menu_auth_map 대조는 (미검증).
  3. 버튼 필터 없음. Outcome 「메뉴 · 라우트 · 버튼 필터가 그 응답을 쓴다」 중 버튼 부분이 미충족이다.
  4. 영업 · 기업 상수 메뉴 미이관. `AdminLayout.tsx:387-422` 가 `salesMenus` · `corpMenus` 상수를 그대로 쓴다 (편차 1).
  5. 403 분기 미소비. 관리자 프런트는 `PERMISSION_DENIED_CODE` (`shared/api/index.ts:39`) 를 정의만 하고 읽지 않는다. 사이트 열람 화면은 권한 거부 표지를 콘솔 경고로만 남기고 화면 상태가 없다 (편차 5).
  6. 옛 번들 403 처리 확인 미완 (착수 조건 · 증명 행). 메뉴 경로 주입은 403 분기를 타지 않았다 (편차 20 정정). 219 재계수 불일치 (`.catch(` 381 · try 블록 75 · 403 을 다루는 파일 20). 실 배포 번들은 (미검증).
  7. 권한 거부 전용 code 계약 밖 지점. 조직관리자 쓰기 차단이 plain ForbiddenException 이다 (`corp-readonly.guard.ts:33`, code-review 미해소 MEDIUM).
  - 같은 항목의 기존 MEDIUM 행 (4건) 은 백로그 추가 전용 규칙 (검사 #14) 에 따라 남긴다.
- 게이트 결과:
  - 계획 게이트: R1 · R2 · R3 모두 divergent, 상한 3/3 도달. 구현 진입은 `MCCP_SKIP_RECEIPT=1` (skip-receipt) 로 열었다. 사용자 지시 원문은 `verify-user-directive.sh` exit 0 으로 확인했다 (2026-10-19T02:21Z). 계획 receipt 는 없다.
  - 구현 receipt: `mccp-implement-codex`, divergent (Codex medium 1, 편차 9).
  - 구현 검사 #1–#19: 코드 리뷰 수정 뒤 워킹트리에서 녹색. 검사 #2 · #9 의 rc 는 실행마다 달라진다 (편차 14). 음성 실행은 하지 않았다 (미관측).
  - 검사 #12 (응답 불변 probe, 최종 리뷰 뒤 재실행): `registry before=703 after=704`, `rows before=703 after=704 common=703 allowed403=1562 diff=0`, `check 12 ok`.
  - 서버 전체 jest 는 붉은 suite 9개다 (환경 사유, 편차 12). 변경과 맞닿은 45 suite 470 test 는 녹색이다.
- code-review (라운드 1): 지적 23건. 흡수 16 · 기각 4 · 미해소 3 (HIGH 1 · MEDIUM 2). CRITICAL 0. 미해소 3건은 `/admin/me` 판정 실패의 isSuper:false (HIGH, 다음 계획 입력) · 열람 화면 표시 상태 · corp-readonly 403 code 다.
- 최종 리뷰 라운드 요약 (호출자 전달값): `r1 found 9 · absorbed 6 · high_absorbed 0 · high_open 1 · codex ok`. 노트에 최종 리뷰 절이 없어 개수를 대조하지 못했다 (미검증). 최종 리뷰에서 흡수한 것은 게이트 #12 의 형상 해시 (배열 길이 · code 값) 와 신원 집합 대조, 표 밖 변경 두 파일 공시 (`menu.service.ts` · `Navbar.tsx`), 백로그 MEDIUM 1행이다. 미흡수 HIGH 는 `/admin/me` 판정 실패 건이다. 코드 주석 `admin-me.service.ts:61` 과 `probe-compare.mjs` 가 「R2」를 인용하는데, 이것이 계획 게이트 차수인지 최종 리뷰 차수인지 기록에서 확인되지 않는다 (확인 필요).
- 편차 중 사용자 확인 대상 (확인 기록 없음): 편차 1 · 3 (스위치 OFF · 상수 메뉴, 정책 §3), 편차 8 (isSuper 조회 실패를 fail-closed 로 둘지 비-200 으로 낼지).
- (미검증) · 사용자 몫으로 남은 것:
  - 실 배포 번들 · 열린 탭의 403 처리. 다시 볼 조건은 페이지 데이터 API 하나 (예: `GET /admin/user`, 권한 user-admin.read) 에 E0607 을 주입하는 것이다. 메뉴 경로 주입은 쓰지 않는다.
  - 운영 tn_menu · tn_menu_auth_map 행과 역할별 사이드바 대조.
  - 감시 풀 포화 중 `/admin/me` 응답. 관리자 프런트 런타임 메뉴 값 · isSuper 대조 (시험 러너 없음). 프런트 폴백 두 곳의 실제 혼재 동작 (편차 18).
  - 백로그 1593–1633 (M6 측정 · 강제 · 트랜잭션).
  - stg 병합 sha 대조 (정책 §1-1, 사용자 몫).
- 다음 마일스톤이 알아야 할 것:
  - 착수 조건 연쇄: M8 은 「M7 닫힘」을, M9 는 M7 완료를, M10 은 M9 완료를 요구한다. admin-rbac-m7b 가 「PRD 미달」 7건을 채우기 전에는 M8 이 착수 조건을 채우지 못한다 (정책 판정 필요).
  - 착수 sha 는 서버 `b9f0823a` · 관리자 `4cb92d8e` · 사이트 `6de0a9e9` 다 (`feat/admin-rbac`, 워킹트리 깨끗).
  - 기준선: 관리자 tsc 서명 기준선 764줄 (`tsc-baseline-admin.sig`, 146ed4d6 클린 빌드, 서명 = 파일 + 식별자). 서버 tsc 오류 23줄 (전부 spec).
  - M9 ON 전환 체크리스트 (정책 §4) 의 「M7 번들 배포 확인」 항목은 위 실 배포 번들 미검증과 같은 자리다. 스위치 OFF · 상수 메뉴 (미달 3 · 4) 도 M9 입력이다.

## 계획 게이트

계획서 `.claude/PRPs/plans/admin-rbac-m7.plan.md` 의 mccp 계획 게이트가 상한 3라운드(`ROUNDS=3/3`)를 다 쓰고 divergent 로 끝났다. 수렴하지 못했다. 계획서는 고치지 않았다 (R3 판 해시 `sha256:240101cf…`). 판정 원문 `decision.json` · `l2.json` · `l3.json` · `l3-findings.json` 은 `.claude/state/plan-review/` 에 있고 gitignore 대상이라, 정본은 리뷰 기록 `.claude/reviews/plan-review-admin-rbac-m7.md` 와 이 노트다.

약어 풀이: R1 ~ R3 은 계획 판의 검토 차수, L1 은 계획서 형식 검사, L2 는 4역할 리뷰 패널 (architect · security · test · invariant), L3 는 Codex 교차 리뷰, cap 은 라운드 상한 3, skip-receipt 는 계획 receipt 를 쓰지 않고 구현 진입 검증만 환경 변수로 넘기는 처리다.

| 라운드 | 계획 해시 | L2 (4역할) | L3 (Codex) | 처리 |
|---|---|---|---|---|
| R1 | `sha256:2e28a618…` | divergent · 정족수 실패 · blocking 7건 (architect HIGH·FAIL, test HIGH·FAIL 외) | divergent | 계획 반영 (`e141a5e` · `377707d`) |
| R2 | `sha256:62eaa462…` | divergent · 정족수 실패 · blocking 4건 (architect HIGH·FAIL, test HIGH·FAIL) | divergent | 계획 반영 (`e0012aa`) |
| R3 (상한 3/3) | `sha256:240101cf…` | divergent · 정족수 실패 · blocking 2건 (test HIGH · FAIL) · 지적 10건 (HIGH 1 · MEDIUM 3 · LOW 6) | divergent (high 1 · medium 1) | 흡수하지 않음 · 아래 상한 처리 |
| 구현 진입 (상한 뒤) | `sha256:240101cf…` | — | — | skip-receipt (`MCCP_SKIP_RECEIPT=1`, preflight ok · bypassed) · plan receipt 미기록 |
| 구현 진입 receipt 부재 (「receipt 없이 구현 — 사용자 판단 필요」 항목) | `sha256:240101cf…` | — | — | 기각 — 사용자 지시 확인(verify-user-directive.sh, 2026-10-19T02:21Z 원문) |

R3 판정: `review_verdict=divergent` · `review_source=multi-agent` · `forwardCodexVerdict=false` · 사유 `L2 quorum not satisfied: 2 blocking finding(s): test/HIGH, test/FAIL`. L1 converged, L2 divergent (정족수 4/3 응답이지만 test HIGH 가 막음), L3 divergent (raw `needs-attention`, high 1 · medium 1). R1 · R2 의 L3 지적 건수는 리뷰 기록에 남지 않았다.

### 상한 처리

- 사용자 지시 (세션 입력 원문 · `verify-user-directive.sh` exit 0 으로 확인, 2026-10-19T02:21Z): 「Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.」 이 지시가 전역 CLAUDE.md mccp 한시 규칙의 「receipt 없음 → 멈춤」을 대체한다.
- CRITICAL 유무: 없음. R3 의 최고 등급은 HIGH 2건 (L2 test 1 · L3 codex 1).
- receipt: 쓰지 않았다. plan 게이트 receipt write 는 시도하지 않았다 (intent gate 가 fail-closed 로 막는다). multi-agent 판정은 `review_proof` 가 null 이라 정상 receipt 를 쓸 수 없고, 상한 봉인 `seal-divergent-hybrid.sh` 도 hybrid 형태에만 해당해 쓰지 않았다.
- 구현 진입 검증: `MCCP_SKIP_RECEIPT=1 node <mccp 플러그인 캐시 1.33.6>/scripts/receipt/cli.js preflight --command mccp:prp-implement --decision admin-rbac-m7 --plan .claude/PRPs/plans/admin-rbac-m7.plan.md` 가 `"ok": true` · `"bypassed": true` · `"reason": "MCCP_SKIP_RECEIPT"` 로 exit 0 이다. env 는 그 명령에만 주었다. `MCCP_SKIP_INTENT_GATE` 는 쓰지 않았다.
- 계획서 · `l2.json` · `decision.json` 은 고치지 않았다. `.claude/state/fix-task.md` 는 생기지 않았다.

### backlog 로 넘긴 항목

`.claude/plans/codex-findings-backlog.md` 끝의 2026-10-20 항목 12줄이 R3 미해소 지적 전부다 (CRITICAL 0). 지정 목록 밖에서 1건 더했다: L2 invariant LOW 「permissions [] + permissionsAvailable false 의 「알 수 없음」 의미를 M7 게이트가 고정하지 않음」 (security LOW 와 같은 계획 :93 을 가리키지만 게이트 공백이라는 다른 층위다).

HIGH 2건 (사유 끝 `→ 다음 계획 반영 대상`):

1. L2 test: permission-adapter 스펙이 `codes()` 만 검사하고 binding·isSuper 읽기는 무검사다 (`permission-adapter.spec.ts:44-57` · 계획 Task 3). 리팩터가 바인딩 질의를 깨도 검사 #4 가 녹색으로 남는다.
2. L3 codex: 조회 실패를 권한 없음으로 바꾼다 (계획 :182-184). `isSuperAdmin` 실패→false 로 바뀌면 기존 `isSuperAdminStrict` 의 비-200 전달이 사라져 장애가 denied 로 보이고 재시도 버튼이 안 뜬다 (`super-admin-permission.controller.ts:42`).

MEDIUM 4건 (표지 없음):

1. L2 test: 게이트 #10 이 `isPermissionDenied` 플래그를 못 잡는다 (`gates.sh:184-191` · site `shared/api/index.ts:150`).
2. L2 test: Task 8 격리 사본 행 존재를 게이트가 강제하지 않는다 (`gates.sh:208-221`).
3. L2 architect: `/admin/me` 가 menus 실패 시 isSuper 까지 잃는다 (계획 :93, :196).
4. L3 codex: 신원별 probe 기대 200 이 인증 가드와 충돌한다 (anon · member · view-token · basic-bad 는 401, 계획 :214).

LOW 6건 (표지 없음): L2 test 가드 스펙이 예외 타입만 검사 (`analysis-target.guard.spec.ts:48` 외) · L2 architect `permissionDepsWith` 의 isSuper 분기가 SuperAdminService 우회 여지 (`permission.adapter.ts:42-51`) · L2 security `permissions []` 의 「알 수 없음」 소비자 주의 (계획 :93) · L2 invariant 위생 검사가 `2>/dev/null` + `wc -l` 이라 대상 부재 시 0건 통과 (`gates.sh:274,291`) · L2 invariant 「알 수 없음」 의미를 게이트가 고정하지 않음 (계획 :93, 위 추가 1건) · L2 invariant 상태코드 롤백 절차 자동 검사 없음 (계획 Risks 10, 미검증).

### 구현 진입 시 주의

- 계획 본문은 동결이라 HIGH 2건과 나머지 지적이 계획에 그대로 남아 있다. 구현 뒤 검사로 확인하기 전에는 해소된 것으로 적지 않는다.
- 이 구현 진입은 수렴 증거가 없다. `MCCP_SKIP_RECEIPT` 로 열렸고 plan receipt 는 없다.
- 다음 계획 작성은 backlog 의 `→ 다음 계획 반영 대상` 항목을 입력으로 쓴다. 핵심은 (1) 조회 실패 경로를 `isSuperAdminStrict` 의 비-200 전달로 보존하는 것, (2) permission-adapter 스펙에 binding·isSuper 값 검증을 넣는 것이다.

## 구현 (implement 단계)

### 착수 상태

| 저장소 | 착수 sha | 브랜치 | 상태 |
|---|---|---|---|
| 부모 | 9abc8d4 (기준 커밋 4df9dd5 위) | feat/admin-rbac | 커밋하지 않음 (UI3 · 지시) |
| insuniverse-server | 3ad1ab14 | feat/admin-rbac | 변경 있음, 커밋하지 않음 |
| insuniverse-admin | 146ed4d6 | feat/admin-rbac | 변경 있음, 커밋하지 않음 |
| insuniverse-site | 09181654 | feat/admin-rbac | 변경 있음, 커밋하지 않음 |

조상 검사 (검사 #1) 통과. 프런트 설치는 `npm ci --ignore-scripts` 로 했다 (INV1, postinstall 미실행).

### 검사 결과 (계획 Validation 블록 순서)

| 검사 | 결과 | 근거 |
|---|---|---|
| #1 착수 sha 조상 | 통과 | 조상 7건 · 비교 객체 1건 |
| #2 서버 tsc | 통과 | 오류 23 줄 (전부 spec 파일), rc 2, 설정 계층 0. 기준 23 과 같다 |
| #3 권한 거부 계약 | 통과 | 정의 1 · 호출 파일 7 · 사유 · 메시지 보존 · corp-readonly 0 |
| #4 가드 spec 묶음 | 통과 | 8 suite · 85 test 녹색 |
| #5 /admin/me 형상 spec | 통과 | admin-me.service.spec 10 test 녹색 |
| #6 메뉴 파생 spec | 통과 | 4 test 녹색 · 스위치 OFF |
| #7 레지스트리 drift 0 | 통과 | 생성기 출력 = 레지스트리 바이트 동일 · /admin/me 1행 |
| #8 예외표 swagger-yaml | 통과 | 1행 |
| #9 관리자 tsc 기준선 | 통과 | 764 줄 · 기준선 밖 파일 0 (서명 방식 변경, 아래 편차 1) |
| #10 사이트 vitest | 통과 | permission-denied.test 3 test · 전체 50 파일 484 test |
| #11 로그아웃 불변 | 통과 | 관리자 E0601 분기 1 · failCodes 불변 |
| #12 응답 불변 probe | 아래 「응답 불변 probe」 절 | |
| #13 옛 번들 403 관찰 | 통과 | 결과 파일 형식 · 빈 화면 false · 로그아웃 false (보류 기록 불필요) |
| #14 백로그 추가 전용 | 통과 | 삭제 행 0 · 1574 RESOLVED-IN 1행 |
| #15 동그라미 숫자 · kbd | 통과 | 0 · 0 |
| #16 호스트 경로 | 통과 | 0 (이 절 작성 전 한 건을 치웠다) |
| #17 M6 과 Files 교집합 | 통과 | 빈 값 |
| #18 계획서 lint | 통과 | rc 0 |
| #19 관리자 호출 경로 · 빌드 | 통과 | vite build rc 0 · getMyMenuTree 는 AdminLayout 에 없다 |

음성 검사 (깨진 입력에서 붉은지 보는 실행) 는 이번 구현에서 돌리지 않았다. 계획이 「구현 뒤 확인 예정」으로 적은 항목이고, 미관측으로 남긴다. 예: #3 정의 파일 제거 사본, #12 행 본문 변경 사본.

### 응답 불변 probe (검사 #12)

격리 스택 (mysql:8.0.42 · redis:7 일회용, 접두 이름, 스키마는 STG_SCHEMA_DUMP 한 파일) 에서 시작 빌드 (3ad1ab14 archive · 시작 레지스트리 703 행) 와 최종 빌드 (작업 트리 · 최종 레지스트리 704 행) 를 같은 스냅샷에서 돌렸다. 대조기 `probe-compare.mjs` 결과 한 줄:

`rows before=703 after=704 common=703 allowed403=1562 diff=0`

- 공통 행의 상태코드 차이 0. 본문 차이는 전부 403 이고 시작 키 `error,message,statusCode` → 최종 키 `code,message,reason`, 최종 code 는 E0607 이다 (1562 행 = 경로 × 신원).
- 최종 쪽에만 있는 경로는 `GET /admin/me` 하나이고 신원마다 행이 있다. 관리자 쿠키 신원 5 종은 200 · 키 `isSuper,menus,permissions,permissionsAvailable`. 인증 없음 · 회원 · 열람 토큰 · 잘못된 Basic 은 401 이다 (편차 13).
- 헤더 변형 (X-Forwarded-For 두 값, /live · /health) 은 양쪽 같다.
- 격리 사본 (tn_auth_permission 을 이름만 바꿈): admin-general 은 200 · permissionsAvailable false, isSuper · menus 는 기준과 같다. admin-super 는 최고관리자 경로가 권한 표를 읽지 않아 permissionsAvailable true 다 (의도된 동작). corp-super · anon 도 기준과 같다.
- 이 실행의 probe 원본 · 대조 결과는 구현 노트 밖 (.git 아래) 에 있다. 게이트 #12 의 본 실행 결과는 아래 「게이트 #12 본 실행」 절이 정본이다.

### 옛 번들 403 관찰 (Task 7, substitute)

- 고정 빌드: 146ed4d6 를 git archive 로 펼쳐 `vite build` (node_modules 는 워크트리 설치본 심볼릭 링크).
- 주입: `GET /admin/menu/my` 를 403 · `{code:"E0607", message, reason:"permission"}` 으로 바꿈. 다른 요청은 200 `[]`.
- 관찰: 로그아웃 이벤트 0 · 세션 유지 · 알림 없음 · 빈 화면 아님 (`/403` 페이지로 이동, 「403 Forbidden 이 페이지에 접근할 수 있는 권한이 없습니다」).
- 대조 (주입 없음, 메뉴 200 `[]`): 본문이 비어 있다. 403 주입과 무관한 별도 관찰이고 M9 판단 입력으로 남긴다.
- 결론: 메뉴 경로 주입은 403 분기를 타지 않았다 (편차 20 의 결론을 정정한다, 최종 리뷰 R1). 관찰된 「`/403` 이동 · 로그아웃 없음」은 옛 번들의 `getMyMenuTree` 가 오류를 `.catch(() => [])` 로 빈 메뉴로 접어 (146ed4d6 `src/api/menuApi.ts:25-28`) `AdminLayout` 의 자체 게이트가 `/403` 으로 보낸 경로다. control (200 `[]`) 과 같은 코드 경로이고, 응답 본문 code · reason 은 어디에서도 읽히지 않았다. 옛 번들이 페이지 데이터 요청의 403 을 각 페이지의 `.catch` 에서 어떻게 다루는지는 (미검증).
- 근거 한계: `GET /admin/menu/my` 는 `AdminJwtAuthGuard` 만 걸리고 `@Roles` 가 없으며 (`menu.controller.ts:30-31`), 권한 맵 (`route-permission-map.ts:181`) 에도 `GET /admin/menu/all` 행만 있다. 그래서 M9 가 권한 맵으로 강제해도 이 경로는 인증된 관리자에게 403 을 내지 않는다. 이 주입은 M9 에서 실제로 날 403 을 대표하지 않는다.
- 한계: 주입은 메뉴 경로 하나다. 다른 가드 403 의 화면 반응은 관찰하지 않았다.

### 219 재계수 (plan 의 수치 재현 실패)

계획 시점 「219 catch」는 옛 번들 (146ed4d6) 의 src 에서 재현되지 않았다. 재계수 결과: `.catch(` 호출 381 곳, `try … catch (` 블록 75 곳, 「403」 문자열을 포함한 파일 20 개. 권한 거부 분기만 고르는 기준은 이번에 세우지 않았다 (옛 번들 차단 사례는 Task 7 관찰로만 본다).

### admin-view 싱크 (Task 6)

| 파일 | catch 블록 | 처리 |
|---|---|---|
| AdminViewB2cAnalysisPage.tsx | 3 (403 을 삼키고 console.error 만 남김) | 권한 거부 표지 분기 추가 (console.warn 한 줄, 편차 5) |
| AdminViewClientAnalysisPage.tsx | 3 | 같은 처리 |
| AdminViewMemberAnalysisPage.tsx | 3 | 같은 처리 |
| AdminViewB2cSubDetailPage.tsx | 0 | 변경 없음 |
| AdminViewClientSubDetailPage.tsx | 0 | 변경 없음 |
| AdminViewSubDetailPage.tsx | 0 | 변경 없음 |

### 구현 편차

1. **검사 #9 서명 방식 변경 (후속: 편차 15).** 계획은 「오류 줄의 정규화 문자열」로 서명을 비교한다. 실측 결과 같은 764 개 오류라도 소스를 편집하면 타입 문자열 (합집합 멤버 순서 · 객체 속성 순서) 과 오류 코드 (TS2322 ↔ TS2719) 가 검사 순서에 따라 바뀌었다 (편집 뒤 거짓 붉음 8 건 실측). 그래서 서명을 「파일 경로 한 줄 = 오류 하나」로 바꿨다. 파일별 오류 개수는 편집 전후 113 파일 전부 같았다. 기준선은 구현 전 원출력으로 다시 썼다 (`tsc-baseline-admin.sig`, 헤더 `# admin_sha=146ed4d6` 유지).
2. **레지스트리 생성기 확장 (M2 산출물 수정).** 계획 게이트 `#7` 은 M1 판정표 (`route-contract-table.md`) 를 입력으로 고정한다. 새 행 `GET /admin/me` 는 M1 표에 없으므로, 생성기가 동결본 옆의 `route-contract-addendum.md` (같은 13 칸 형식) 를 이어 읽게 고쳤다. 같은 자리에서 `tree: ea3c0359` 줄을 읽어 `guardsAdd` 규칙을 적용한다 (동결본 이름에 트리가 없어 treat PATCH 행의 SuperAdminGuard 가 빠지는 것을 막았다). M1 동결본으로 돌리면 전과 바이트 같다 (실측). 파일 위치는 `.claude/meta/2026-10-19-admin-rbac-m2/build-registry.mjs`.
3. **/admin/me 의 스위치 ON 배선 없음.** `composeMenus` 는 순수 함수로 두었고 ON 분기는 spec 에서 `jest.mock` 으로만 본다. `/admin/me` 응답은 OFF 값 (현행 `getMyMenuTree`) 만 준다. ON 배선은 M9 몫.
4. **매핑 3건만 근거 확인.** 메뉴 ↔ 권한 매핑은 `/user/user` (UserPage → `/admin/user` → user-admin.read), `/money/general` (MoneyGeneralPage → `/admin/money/issue` → money-admin.read), `/subscription/treat-monitor` (treatMonitorApi → `/admin/treat-monitor/overview` → treat-monitor.read) 뿐이다. 나머지는 근거가 없어 「미매핑」이다. 운영 `tn_menu` 행은 저장소에 없어 현행 집합은 합성 fixture 다 (골든은 `menu-parity.expected.json`).
5. **Task 6 표시 분기를 콘솔 표지로 축소.** 계획은 「표시 분기」라고 적었다. 세 catch 는 빈 영역 (상태를 세우지 않는다) 이라 화면 문구를 새로 설계하는 일이 된다. 이번에는 `isPermissionDenied` 표지를 보고 `console.warn` 한 줄을 남기고, 화면 문구는 M9 · M10 몫으로 둔다.
6. **permissionDepsWith 의 핸들 타입을 좁혔다.** 공용 DrizzleService 가 `MySql2Database<schema>` 와 구조적으로 맞지 않아 (`tsc` 가 nest build 를 막았다) 필요한 면 `select` · `execute` 만 받는다. 동작은 같다.
7. **permission-adapter 에 바인딩 시험 2 건 추가.** 백로그 HIGH 「codes 만 검사」 항목을 구현에서 같이 닫았다 (`binding` 행 있음 · 없음).
8. **implement Codex 리뷰 (1 라운드) 의 지적이 구현 중 재현됐다.** medium 1 건 (= 백로그 HIGH 「조회 실패를 권한 없음으로」): `/admin/me` 가 designation 조회 실패 시 `isSuper:false` 를 내고 `superAdminApi` 가 denied 로 읽는다 (기존 `isSuperAdminStrict` 경로는 unavailable). 계획은 fail-closed 를 동결해 두었으므로 구현은 계획대로 두고, 백로그에 「→ 다음 계획 반영 대상」으로 적었다.
9. **implement 게이트 receipt 는 divergent.** 구현 Codex 리뷰가 needs-attention 이라 `codex_verdict=divergent` 로 썼다. 쓰기는 env 없이 통과했다 (plan receipt 부재 가드가 쓰기에서는 막지 않았다). `prp-implement` 의 `validate` 는 plan receipt 부재를 보고 실패하므로 `preflight` 를 `MCCP_SKIP_RECEIPT=1` (이 명령에만) 로 돌렸다 (bypass 로그 남음, ok true). write 가 `.claude/state/fix-task.md` 를 만들었다 (divergent_unresolved 로 간주).
10. **고정된 계획 판정 자료의 ROUNDS 를 다시 쓰지 않았다.** plan receipt 는 없다 (skip, 사용자 지시 2026-10-19).
11. **Phase 2.5 를 구현 뒤에 돌렸다.** 명령 본문은 구현 착수 전에 2.5 를 돌리라고 적는다. 이번 실행은 이미 구현이 끝난 뒤 Codex 리뷰 (focus 는 서브모듈 경로) 를 돌렸다. 게이트 round seal (`mccp-implement-codex`, cap 3) 은 2.5.0 과 같이 봉인했다.
12. **서버 전체 jest 에 붉은 suite 9 개.** 변경과 무관한 환경 사유다: ENOENT (워크트리에 부모 저장소 `.claude/plans/` 파일이 없음 — ai-insight-report-v2-m1.report-fixture.json · ai-insight-cr2-projection.openapi.json), Redis 의존 dispatch 3 suite 의 시간 초과, drizzle mysqlErrorWrapper 의 문자열 검사 1. 이번 변경이 건드린 auth · menu · admin · admin-view-token · alarm 은 45 suite 470 test 녹색이다.
13. **계획의 「모든 신원 200」 기대는 맞지 않는다.** `/admin/me` 는 인증 가드를 달므로 인증 없음 · 회원 토큰 · 열람 토큰 · 잘못된 Basic 은 401 이다 (계획 :214 의 가드 충돌, 백로그 MEDIUM 과 같다). 검사 #12 의 대조 규칙은 이 사실에 맞춰 적었다 (아래 probe 절).

### 이연 체크리스트 (M9 ON 조건 · 계획 Acceptance 의 미검증 항목)

- 감시 슬롯 · 판단 표 · 분류 행 정정 (1574 · 1484 · 1489 잔여 · 1560 · 1563 · 1564 · 1577)
- 영업 · 기업 상수 메뉴의 서버 파생 (편차 1 · M9(2) 입력)
- 로그인 훅 (`useLoginForm.ts`) 의 getMyMenuTree 와 레이아웃 getMe().menus 의 메뉴 원천 이중화를 스위치 ON 전에 한 원천으로 합친다
- 운영 tn_menu · tn_menu_auth_map 행과 역할별 현행 사이드바 대조 (미검증)
- 감시 풀 포화 중 /admin/me 응답 (미검증 · 공용 핸들 분리의 실측)
- 관리자 프런트 런타임 메뉴 값 · isSuper 대조 (미검증 · 시험 러너 없음)
- 실 배포 번들 · 열린 탭의 403 처리 (미검증 · 사용자 몫). 다시 관찰할 조건: 페이지 데이터 API 하나 (예: `GET /admin/user`, 권한 user-admin.read) 에 E0607 을 주입해 옛 번들의 로그아웃 · 빈 화면 · 알림을 본다. 메뉴 경로 주입은 다시 쓰지 않는다 (최종 리뷰 R1 · 편차 20 정정).
- 백로그 1593–1633 (M6 측정 · 강제 · 트랜잭션) · admin-rbac-m6b 보완 계획 입력
- 「나머지 ForbiddenException 사용처」 (도메인 업무 거부) 의 범위 계약 (M6 · M9)
- implement 백로그 HIGH (isSuper 조회 실패 = denied 로 보임) · 다음 계획 입력

### 변경 파일 (서브모듈별, 커밋 전 워킹트리)

- insuniverse-server: auth/admin/permission-denied.ts (+ spec) · 가드 7 파일 · auth/permission/permission.adapter.ts · permission-adapter.spec.ts · menu/admin-me.controller.ts · admin-me.service.ts (+ spec) · menu/menu.module.ts · menu/menu-permission-map.ts · menu/menu-derivation.ts (+ spec) · auth/access-contract/access-contract.registry.ts
- insuniverse-admin: src/api/superAdminApi.ts · src/api/menuApi.ts · src/layouts/AdminLayout.tsx · src/shared/api/index.ts
- 표 밖 변경 (최종 리뷰 R1 에서 공시): insuniverse-server `apps/api/src/modules/menu/menu.service.ts` (b9f0823a · `getMyMenuTree(admin, knownSuper?)`, 인자가 없으면 기존 판정 경로라 동작 보존) · insuniverse-admin `src/shared/layouts/Navbar/Navbar.tsx` (4cb92d8e · 주석 정정만)
- 부모 (최종 리뷰 R1 흡수): `.claude/meta/2026-10-20-admin-rbac-m7/probe-m7.mjs` (형상 해시) · `probe-compare.mjs` (신원 집합 대조) · `.claude/prds/admin-rbac.prd.md` M7 행 (in-progress) · `.claude/PRPs/reports/admin-rbac-m7-report.md` 편차 1 · 백로그 줄 수
- insuniverse-site/front: src/shared/api/index.ts · src/shared/api/permission-denied.test.ts (신규) · src/api/_error.msg.ts · admin-view 3 파일 (catch 분기)
- 부모 (커밋 안 함): .claude/meta/2026-10-20-admin-rbac-m7/ 신규 파일 · .claude/meta/2026-10-19-admin-rbac-m2/build-registry.mjs (편차 2) · .claude/PRPs/plans/admin-rbac-m7.gates.sh (검사 #9 키 · 편차 1) · .claude/plans/codex-findings-backlog.md (추가 1줄) · .claude/notes/admin-rbac-m7.md · .claude/receipts/mccp-implement-codex/admin-rbac-m7.json

## Codex Implementation Review

- 호출: `codex-invoke.js adversarial-review` (fail-closed 래퍼), 집행 키 `mccp-implement-codex__admin-rbac-m7`, cap 3.
- 라운드 수: 1 (상한 3 의 첫 라운드. CRITICAL 이 없어 마지막 라운드 규칙에 따라 divergent receipt 로 진행).
- 판정: needs-attention (medium 1). 요약 원문: 「Fix the super-admin failure contract before shipping. Server and site TypeScript checks pass; admin has existing diagnostics, with none in the targeted changed files.」
- 지적 1 (medium): `insuniverse-server/apps/api/src/modules/menu/admin-me.service.ts:40` — 최고관리자 지정 조회가 실패하면 `isSuperAdmin` 이 거짓을 내어 `/admin/me` 가 200 · `isSuper:false` 를 준다. 새 `superAdminApi` 는 이 응답을 「아니오」(denied)로 읽고, 옛 경로의 「판정 불가」(unavailable, 재시도 버튼)가 사라진다. 백로그 HIGH 「조회 실패를 권한 없음으로 바꿈」과 같은 항목이다.
- 처리: 계획 (Task 3) 이 fail-closed 를 동결해 두었고, 이 판정은 계획의 설계 결정을 바꾸므로 구현에서 바꾸지 않았다. 편차 8 로 기록했고 백로그에 「→ 다음 계획 반영 대상」으로 옮겼다. 사용자 확인 대상: 조회 실패를 200 + 거짓 대신 비-200 으로 낼지 (옛 경로 동작 보존).
- 나머지: 판정 결과 TypeScript (서버 · 사이트) 통과. 관리자 쪽 진단은 기존 것이며 대상 파일에는 없다.

### 게이트 #12 본 실행

`STG_SCHEMA_DUMP` 를 주고 게이트 본문 `#12` 를 처음부터 돌렸다 (격리 빌드 2 회 · probe 2 회 · 격리 사본 1 회). 결과: 헤더 `registry before=703 after=704` 가 게이트가 레지스트리에서 다시 센 값과 같고, 대조기 `rows before=703 after=704 common=703 allowed403=1562 diff=0`, `check 12 ok`. 나머지 검사 (1–11 · 13–19) 는 최종 코드 상태에서 다시 돌려 모두 rc 0 이다 (검사 #2 의 tsc rc 는 1 로 나온다. 계획 시점 관측은 2 였다. 게이트는 0 이 아니기만 본다).

### 구현 편차 추가 (본 실행 뒤)

14. 게이트 #2 · #9 의 rc 관측이 계획 시점과 다르다 (tsc rc 1 과 2 가 실행마다 나온다). 게이트는 rc 값이 아니라 「0 이 아님」만 보므로 판정에는 영향이 없다.

### 구현 편차 추가 (코드 리뷰 R1 뒤)

15. **검사 #9 서명을 파일 + 식별자로 바꾸고 기준선을 다시 썼다 (편차 1 의 후속).** 파일 경로 한 줄만 보던 서명은 같은 파일 안의 오류 치환을 못 잡았다 (code-review MEDIUM). 새 서명은 파일과 식별자 (`Property` · `Cannot find name/module` · `Module` 메시지의 첫 인용, 나머지는 `-`) 다. 오류 코드는 뺐다. TS2322 ↔ TS2719 가 146ed4d6 클린 빌드 대비 여덟 줄 옮겨 다녀 (실측) 거짓 붉음을 냈기 때문이다. 기준선은 `git archive 146ed4d6` 를 펼친 클린 빌드에서 다시 썼다 (764 줄 · 식별자 132 종). 워킹트리 서명과 기준선의 차집합은 0 이다. 반사실 (정규화 단위, 가짜 tsc 출력): 같은 파일의 `Property` 식별자 치환은 옛 서명 0 줄 · 새 서명 2 줄, 타입 문자열만 바뀐 경우는 둘 다 0 줄. 알려진 한계: 식별자 없는 메시지 (TS2322 류) 의 같은 파일 내 치환은 여전히 못 잡는다.
16. **서버 골든 사본.** `insuniverse-server/apps/api/src/modules/menu/fixtures/menu-parity.expected.json` 을 spec 이 읽는다 (서버 저장소 단독 실행에서 ENOENT 를 막는다). 정본은 이 메타 디렉터리 그대로다. 게이트 #6 이 두 파일을 `cmp` 로 맞춘다. 계획 Files 표에 없는 파일이다 (계획 동결 · 다음 계획 반영).
17. **`/admin/me` 응답 값 두 가지를 고쳤다.** (가) 비관리자 역할의 권한 집합은 evaluate 와 같은 허용 규칙 (카탈로그 안 · 최고관리자 전용 아님) 으로 거른다. 옛 구현은 역할의 원시 코드 목록을 그대로 실었다. 키 집합 (`isSuper,menus,permissions,permissionsAvailable`) 과 상태코드는 그대로다. (나) 판정은 요청마다 한 번만 읽어 `MenuService.getMyMenuTree(admin, isSuper)` 로 넘긴다. 질의 수는 일반 관리자 5 → 4, 최고관리자 3 → 2 다. 바인딩 · 코드 질의는 여전히 직렬이다 (바인딩 결과에 의존).
18. **관리자 프런트 폴백 두 곳.** `menuApi.getMe` 가 실패하면 `GET /admin/menu/my` 로 `menus` 를 채운다. `superAdminApi` 는 `/admin/me` 가 404 면 옛 판정 엔드포인트 `GET /admin/super-admin/permission` 을 한 번 더 묻는다. 관리자 프런트에 시험 러너가 없어 (INV1) 이 둘은 빌드 · 정적 검사로만 확인했다. 실제 옛 서버 혼재 동작은 (미검증).
19. **사이트 권한 거부 표지 판정 `isPermissionDeniedError` 추가.** 열람 화면 아홉 곳의 캐스트를 이 가드로 바꿨다 (표지 이름을 한 곳에 둔다). 시험 한 건을 더했다. 표시 상태는 여전히 없다 (편차 5 · 백로그 MEDIUM).
20. **옛 번들 403 결과의 근거 범위를 낮춘다.** 주입 구동 스크립트는 저장소 밖 (시험 전용 정적 서버 · playwright) 이라 저장소에 없다. 그래서 결과의 근거는 `old-bundle-403.result.json` 의 method 칸 자기 보고뿐이다. 빈 화면 대조도 기준이 맞지 않는다 (주입 경로는 `/403` 으로 이동하고, 대조 control 은 메뉴 빈 목록이라 본문이 빈다). 따라서 이 절의 결론은 「로그아웃 없음 · `/403` 으로 이동」까지이고, 빈 화면 판정은 (미검증)이다. 계획 Files 표에 없는 `build-registry.mjs` 수정 (편차 2) 도 같은 자리에서 추적한다. 표 줄 추가는 계획 동결이라 다음 계획에 넣는다.

## 코드 리뷰

- 라운드 1/3 (code-review 지적 묶음 · 출처 code-review · security · typescript 세 레인). 커밋 전 워킹트리를 기준으로 판정했다.
- 집계: 흡수 16 · 기각 4 · 미해소 3 (HIGH 1 · MEDIUM 2). 흡수한 HIGH 0. CRITICAL 0.
- 반사실 검증: 각 흡수 건은 수정 전 검사가 붉고 수정 뒤 초록임을 실행으로 확인했다 (정적 확인뿐인 건은 증거 칸에 적었다).
- 층위: 1 사실 · 설계, 2 주장 ≠ 검사 (주석 포함), 3 명령 ≠ 게이트, 4 셸 실패 규약, 5 게이트 실행 불가 (검사가 못 도는 자리), 6 게이트가 보는 대상, 7 검사 자신의 주장, 8 사각지대.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | code-review · security · typescript | HIGH | `/admin/me` 의 isSuper 가 판정 실패를 200 `isSuper:false` 로 접어 최고관리자가 「담당자 지정 요청」 쪽으로 간다 (unavailable 재시도 경로 소실) | 미해소. 계획 동결 (Task 3 부분 실패 계약 「isSuper 는 평소대로」 와 충돌). 백로그 2026-10-20 HIGH 행이 이미 있어 추가 행은 쓰지 않았다. 다음 계획 반영 대상 | `insuniverse-server/apps/api/src/modules/menu/admin-me.service.ts:48` · `insuniverse-admin/src/api/superAdminApi.ts` · 편차 8 | 1 |
| R1 | code-review · typescript | MEDIUM | 골든 픽스처를 서버 저장소 밖 (`.claude/meta`) 에서 모듈 로드 시 읽어 서버 단독 실행에서 ENOENT | 흡수. 서버 `menu/fixtures/` 사본을 spec 이 읽고 게이트 #6 이 정본과 cmp 로 맞춘다. 원 경로는 서버 저장소 바깥을 가리킴 (정적 확인). spec 5건 녹색 | `menu-derivation.spec.ts` · gates #6 · 편차 16 | 5 |
| R1 | code-review | MEDIUM | 검사 #9 서명이 파일 경로만이라 같은 파일 안의 오류 치환을 못 잡는다 | 흡수. 서명 = 파일 + 식별자. 기준선 146ed4d6 클린 빌드 재작성. 반사실 (정규화 단위): 옛 0줄 · 새 2줄. 검사 #9 녹색 | gates.sh `admin_sig` · `tsc-baseline-admin.sig` · 편차 15 | 6 |
| R1 | code-review · typescript | MEDIUM | 열람 6화면의 권한 거부 처리가 console.warn 뿐이라 화면에 신호가 없다 (계획 Task 6 「표시 분기」 미충족) | 미해소. 표시 상태 설계는 UI 결정 (편차 5 · M9 · M10). 백로그 2026-10-20 MEDIUM 행 추가. 캐스트 중복 부분은 아래 행으로 흡수 | `insuniverse-site/front/src/pages/admin-view/AdminViewB2cAnalysisPage.tsx:237` · 편차 5 | 1 |
| R1 | typescript | MEDIUM | 열람 화면 아홉 곳이 같은 캐스트와 console.warn 을 복제한다. 표지 이름이 바뀌면 사이트 tsc (0 파일) 가 못 잡고 조용히 죽는다 | 흡수. `shared/api` 에 `isPermissionDeniedError` 가드 · 아홉 곳 교체 · 시험 1건. 반사실: 가드 이름을 바꾼 사본에서 시험 1건 붉음 → 원복 후 4/4 녹색 | `insuniverse-site/front/src/shared/api/index.ts` · `permission-denied.test.ts` · 편차 19 | 5 |
| R1 | code-review · security | MEDIUM | 조직관리자 쓰기 차단 (역할 거부) 이 plain ForbiddenException 이라 E0607 계약 밖이다. 계획의 reason 「permission」 정의 (역할 · 권한 거부) 와 어긋나고, 검사 #3 이 corp-readonly 호출 0 을 고정한다 (보안 LOW 와 같은 건) | 미해소. 계획 범위 (가드층 일곱 곳) 와 검사 #3 고정을 바꾸는 계획 결정이다. 백로그 2026-10-20 MEDIUM 행 추가 | `insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:33` · gates #3 | 1 |
| R1 | security | MEDIUM | `/admin/me` 의 permissions 가 역할의 원시 코드 목록이라 최고관리자 전용 코드 · 카탈로그 밖 코드가 비관리자에게 실린다 (계획은 evaluator 결과라 적었다). 서버 거부는 유지되므로 우회는 아니다 | 흡수. evaluate 와 같은 허용 규칙으로 거른다. 반사실: 전용 코드 · 카탈로그 밖 코드를 넣은 spec 이 붉음 → 필터 후 녹색 | `admin-me.service.ts` (`allowedForNonSuper`) · `admin-me.service.spec.ts` | 2 |
| R1 | security | MEDIUM | 새 관리자 번들이 `/admin/me` 에만 기대고 실패를 빈 메뉴로 접는다. 옛 서버 혼재나 서버 롤백에서 사이드바가 비고 모두 `/403` 으로 간다 | 흡수. getMe 실패 시 `/admin/menu/my` 로 menus 를 채우고, 최고관리자 판정은 404 면 옛 엔드포인트로 묻는다. 관리자 시험 러너가 없어 (INV1) 빌드 · 정적 확인만 했다. 실제 혼재 동작은 (미검증) | `insuniverse-admin/src/api/menuApi.ts` · `superAdminApi.ts` · 편차 18 | 1 |
| R1 | typescript | MEDIUM | 요청 하나가 판정을 두 번 읽고 질의를 직렬 다섯 번 낸다. 판정과 메뉴가 서로 다른 시점의 값을 한 응답에 싣는다 | 흡수 (부분). 판정을 한 번 읽어 `getMyMenuTree(admin, isSuper)` 로 넘긴다. 반사실: 인자를 기대하는 spec 이 붉음 → 녹색. 바인딩 · 코드 병렬화는 미적용 (의존) | `admin-me.service.ts:48` · `menu.service.ts:32` · spec · 편차 17 | 1 |
| R1 | typescript | MEDIUM | 관리자 eslint 가 비활성 규칙 이름 (`no-var-requires`) 을 가리켜 lint 가 붉다 | 흡수. 규칙 이름을 `no-require-imports` 로. 반사실: 옛 이름에서 두 건 error · rc 1 → 새 이름 rc 0 | `menu-derivation.spec.ts` | 3 |
| R1 | security | LOW | 스위치 ON 이 매핑 없는 메뉴를 전부 숨긴다 (파생 ∩ 현행). 전환 한 번에 사이드바 lockout | 흡수. 매핑 없는 메뉴는 현행을 유지한다 (`unmapped` 인자). 반사실: 미매핑 보존 spec 이 붉음 → 녹색. 응답 배선은 M9 | `menu-derivation.ts` `composeMenus` · spec | 1 |
| R1 | code-review | LOW | 스위치 `MENU_SOURCE_DERIVED` 가 응답 경로에 연결되지 않아 켜도 아무것도 안 바뀐다 | 기각. 배선은 계획 NOT Building (M9). 상수 주석이 OFF 목적을 적었고, 게이트는 OFF 값 · 응답 불변 (검사 #6 · #12) 으로 본다 | `menu-permission-map.ts` · 편차 3 | 1 |
| R1 | code-review | LOW | 감시 핸들 미주입 검사가 생성자 인자 개수 3 만 본다 (어떤 3인자 생성자도 통과) | 흡수. 주입 타입 목록 (DrizzleService · SuperAdminService · MenuService) 을 고정. 반사실: 셋째 인자 `unknown` 을 더한 임시 사본에서 붉음 → 원복 후 녹색 | `admin-me.service.spec.ts` | 6 |
| R1 | code-review · security | LOW | 회원 소유 분석 가드 (own-analysis) 의 plain ForbiddenException | 기각. 회원 측 라우트로 관리자 RBAC 범위 밖이다. 계획 「정책과 다르게 옮긴 자리 3」 이 도메인 · 소유 거부를 E0607 로 바꾸지 않는다고 적었다 | `own-analysis.guard.ts:39` · `:50` · `:53` · 계획 정책 3 | 1 |
| R1 | code-review | LOW | `permission-denied.ts` 주석이 관리자 프런트가 E0607 로 권한 거부를 가른다고 적지만 아무도 읽지 않는다 | 흡수. 주석을 현재 사실 (소비는 M9) 로 고쳤다 | `permission-denied.ts:1-8` | 2 |
| R1 | typescript | LOW | 관리자 `PERMISSION_DENIED_CODE` 가 어디서도 쓰이지 않는다 | 흡수 (부분). 계획 Task 5 가 상수를 요구하므로 유지하고 주석을 고쳤다. 소비는 M9 | `insuniverse-admin/src/shared/api/index.ts` | 2 |
| R1 | code-review | LOW | 검사 #2 가 tsc 오류 개수 상한 (23) 만 본다 | 기각. 계획 Task 1 · 검사 #2 가 개수 기준을 정의한다. 서버 서명 기준선은 계획 밖 (다음 계획 후보) | gates #2 | 6 |
| R1 | code-review | LOW | 골든 파생 검사가 저자가 손으로 쓴 합성 픽스처와 같은 매핑으로 비교해 독립 검증이 아니다 | 기각 (계획 Acceptance 동결). 한계는 이 표와 편차 16 에 (미검증) 로 적는다. 운영 행 대조는 M9 ON 체크리스트 | `menu-derivation.spec.ts` · 골든 note | 6 |
| R1 | code-review | LOW | 옛 번들 403 결과의 구동 스크립트가 저장소 밖이고 빈 화면 대조의 기준이 control 과 injected 가 다르다 | 흡수 (결론 하향). 근거는 method 칸 자기 보고뿐이라고 적었다. 빈 화면 판정은 (미검증). 게이트 #13 은 형식 검사로 둔다 | `old-bundle-403.result.json` · 편차 20 | 8 |
| R1 | code-review | LOW | `build-registry.mjs` (M2 산출) 수정이 계획 Files 표에 없다 | 흡수 (notes 추적 보강). 행 동작 보존은 게이트 #7 (재생성 결과가 레지스트리와 바이트 같음) 으로 확인했다. M1 동결본 대조는 리뷰어 실측이며 이 라운드에서 다시 돌리지 않았다. 표 줄 추가는 계획 동결이라 다음 계획에 넣는다 | 편차 2 · 편차 20 | 1 |
| R1 | typescript | LOW | 「부분 실패라도 최고관리자 판정은 유지」 테스트가 실패 경로를 타지 않는다 | 흡수. 이름을 정상 경로로 고쳤다. 실패 주입은 권한 조회 실패 spec 이 맡는다 | `admin-me.service.spec.ts` | 7 |
| R1 | typescript | LOW | superAdminApi 주석이 /admin/me 의 판정 실패를 비-200 으로 적어 stale 이다. Navbar 주석은 옛 endpoint 만 적고, permission.adapter 헤더 주석은 다른 함수 위에 있다 | 흡수 (주석 정정 3곳). superAdminApi 는 404 폴백과 fail-closed 잔존을 적었다 | `superAdminApi.ts` · `Navbar.tsx:53` · `permission.adapter.ts` | 2 |
| R1 | typescript | LOW | 컨트롤러가 요청을 이중 캐스트하고 non-null 단언을 쓰며, 응답 menus 가 unknown[] 이라 관리자 ApiMenuItem 과 컴파일러가 못 맞춘다 | 흡수 (부분). 요청 타입 `AdminRequest` 를 직접 쓰고 menus 를 MenuService 반환형으로 고쳤다. non-null 은 가드 전제로 남겼다 | `admin-me.controller.ts` · `admin-me.service.ts` | 5 |

- 게이트 결과 (수정 뒤 워킹트리): 검사 #1 · #2 (서버 tsc 오류 23 · 설정 계층 0) · #3 · #4 · #5 · #6 · #7 · #8 · #9 (기준선 밖 0) · #10 (vitest) · #11 · #13 · #17 · #18 · #19 (vite build) 녹색. 검사 #12 는 격리 probe 를 다시 돌렸다 (m7-iso 접두 컨테이너, 끝나고 내림): 헤더 `registry before=703 after=704` 가 재계수와 같고, 대조 `rows before=703 after=704 common=703 allowed403=1562 diff=0`, `check 12 ok`. 검사 #14 · #15 · #16 도 녹색이다.
- 알려진 한계 (미검증): 관리자 프런트의 폴백 두 곳 (편차 18), 열람 화면 표시 신호 (백로그), 실 배포 번들 (사용자 몫).

## 보고서

산출 보고서는 `.claude/PRPs/reports/admin-rbac-m7-report.md` (추적 대상, finish 단계 커밋에 싣는다).
