# 관리자 RBAC 마일스톤 9c 노트 (보완 계획 admin-rbac-m9c)

계획: `.claude/PRPs/plans/admin-rbac-m9c.plan.md` · 검사 사이드카: `.claude/PRPs/plans/admin-rbac-m9c.gates.sh` · 측정 산출: `.claude/meta/2026-10-23-admin-rbac-m9c/`.

## 마감 요약

- 판정: **보완 계획 admin-rbac-m9c 는 마감한다. 마일스톤 9 (PRD 행 9) 는 in-progress 를 유지한다.** 증명 행 9 를 대조하니 코드 · 격리로 할 수 있는데 하지 않은 항목이 남았다 (아래 「PRD 미달」). complete 로 올리지 않았다.
- 행 9 의 Plan 열에 m9c 계획을 넣지 않았다. 검사 #25 가 행 9 줄의 변경을 삭제로 세기 때문이다. 계획 가리킴은 PRD 결정 로그 9c 절이 맡는다 (마감 지시의 「Plan → m9c」 와 다른 자리).
- 게이트 결과 (마감 시점): `bash .claude/PRPs/plans/admin-rbac-m9c.gates.sh` rc 0 · `M9C_GATES_DONE=1 FAILS=0` (33 검사). 그 전 상태는 검사 #12 하나가 붉었다. 최종 리뷰 R1 (`04a4013`) 이 검사 #12 와 `absent-boot.sh` 를 바꿨는데 산출을 다시 찍지 않아서다. 아래 재측정으로 고쳤다.
- 재측정 (2026-10-23 23:13–23:16, 측정 전용, 커밋 밖): 칸 부재 · 표 부재 앱 부팅 (영역 1 임시 ON) 과 스위치 OFF 칸 부재 부팅. 산출 `absent-boot-m9c.txt` · `absent-boot-off-m9c.txt` (MODE · SRV_TREE 는 같은 트리 `5e740146…`). 실측:
  - 비전용 요청 `/admin/alarm` (부재 분기를 타는 유일한 요청): 칸 부재 내부 관리자 403 E0607/permission, 표 부재 내부 관리자 403 E0607/permission. 기준선 NORM 내부 관리자 200. OFF 칸 부재 내부 관리자 200 (가드 없음).
  - `/admin/authority/1` 칸 부재 최고관리자 200 (가드 통과 · 본문이 칸을 읽지 않음). 표 부재 최고관리자 200.
  - `/admin/admin` 칸 부재 최고관리자 500 IE9999 — OFF 도 500 IE9999 (레거시 조인, 기존 성질, 회귀 아님). 표 부재 최고관리자 200.
  - `/admin/admin` · `/admin/authority/1` 의 내부 관리자 403 은 super-only 사유라 부재 분기 증거가 아니다. 부재 분기 증거는 `/admin/alarm` 뿐이다.
  - 측정 뒤 상태: 서브모듈 세 저장소 워킹트리 깨끗함 (HEAD 는 착수 sha), 상수 원복 sha = HEAD, `m9c-iso-` 컨테이너 없음. 끝에 `absent-boot.sh off` 가 dist 를 OFF 로 다시 빌드했다 (`DIST_CONST_OK true=[없음]`).
- 재생 산출 (296 셀): 이번 마감에서 다시 재지 않았다. 원자료 상태는 「PRD 미달」 2번 참조. 노트 「구현 뒤 실행 결과」 의 값은 22:25 산출 (원자료 손상 전) 의 기록이다.
- 코드 리뷰 (구현 뒤, 커밋 전 작업 트리, 한 라운드): 출처 세 lane (code-review · security · typescript). 노트 집계는 흡수 15 · 기각 6 · 백로그 이월 2 · 기존 백로그 HIGH 1 (미해소, 백로그 1934 · 목록 0행 축소). 세부는 「## 코드 리뷰」.
- 최종 리뷰 라운드 요약 (메인 입력): `[{"r":1,"found":5,"absorbed":5,"high_absorbed":2,"high_open":0,"codex":"ok"},{"r":2,"found":2,"absorbed":2,"high_absorbed":0,"high_open":1,"codex":"ok"}]`.
  - 노트와 대조한 결과: 최종 리뷰 R2 표는 fable MEDIUM 1 · LOW 1 두 건이고 둘 다 흡수다. HIGH 는 0 이다. R1 지적 목록은 노트에 없고 커밋 `04a4013` 에만 있다 (메인 입력 수치는 노트로 확인하지 못했다). 열린 HIGH 1 은 노트상 코드 리뷰 라운드의 백로그 1934 (목록 0행 축소) 다. 메인이 준 「R2 HIGH 미해소 0」 과 r2 `high_open=1` 은 서로 맞지 않는다. 메인 확인이 필요하다.
  - 최종 리뷰 라운드 3 은 상한이라 돌리지 않았다.
- 판정 자료 (메인 판정으로 확정 · 재론하지 않음): 이행표 I 22 · AO 15 × 신원 8 = 296 셀. 허용 방향 (기대 403 이 ON 에서 2xx) 0. 의도되지 않은 소실 0 (검사 #9 독립 대조). 소실 19 는 전부 영업 `sales-B` 의 타 기업 범위 거부로 「의도된 범위 거부」 로 분류했다 (소유 영업 `sales-A` 는 같은 19 행을 ON 2xx 로 통과). 200 축소 14 는 부분 축소이며 판단 표 입력이다. 관리자 축소 0 · 0행 축소 (측정된 범위에서) 0.
- PRD 미달 (행 9 in-progress 사유, 보완 계획 `admin-rbac-m9cb` 로 넘김. 백로그 HIGH 1946–1950 · 짝 행 1951–1957):
  1. 목록 0행 축소 미측정 (원 1931 · 1934). 재생 셀 파일이 7열이라 목록 길이 열이 없다. 게이트 #30 의 0행 축소 분기는 입력을 받지 못하고 count 만 본다 (count 10 · 목록 10→0 이 녹색).
  2. 재생 원자료 손상. 2026-10-23 22:50 재측정 체인이 `raw-off.tsv` 를 덮어쓰고 137바이트에서 멈췄다. 원 19:24 OFF 원자료는 남아 있지 않다. OFF · OFF 재실행 · ON 을 현행 절차로 다시 재야 한다 (한 번 약 30분).
  3. 교차 기업 · 형제 조직 · 기업 · 조직 관리자 대상 셀이 재생에 없다 (원 1945 MEDIUM 격상). 측정 신원은 영업 하나이고 대상은 자기 테넌트다.
  4. 소비자 대응표 (site admin-view · 앱 · 외부 콜백 · 스케줄러) 정상 회귀의 격리 실측이 없다. `consumers-same-value.spec.ts` 는 소비처 세 곳의 관리자 칸 정적 대조뿐이다. 판정에는 「없음」 으로 넣었다.
  5. itspec 격리 시험 미실행 (계획 Task 7, 최종 리뷰 R2 미검증 1). `jest-m9c.json` 은 `.spec.ts` 41 suite · 535 시험 · 실패 0 이다. 게이트 #24 는 이 누락을 못 잡는다.
- 사용자 몫 (미검증, 행 9 in-progress 사유 아님): 영역별 would-deny 판단 표 실내용 (복제본 1회 재생, 복제본 생성은 사람 승인) · R2 최고관리자 한정 (block, 활성 금지) · R7 `ath_id` NULL 내부 관리자 수 · R19 소비처 확인 책임자 · 영역 상수 · 범위 상수 · 메뉴 스위치 ON 커밋과 이미지 SHA 재배포 · 동시 판정 상한 N · 대기열 정책 부하 실측 (백로그 1894 · 1902) · 운영 칸 보존 후 레거시 조인 500 해소 (백로그 1941 LOW).
- plan 영수증 체인: `validate --command mccp:prp-implement` 는 `missing: mccp-plan-codex` 로 rc 2 다. plan 게이트는 skip-receipt 지시로 진행했다. 메인 판정 대상.
- 커밋: 부모 2건 (검사 본문 · 측정 산출 · 노트 · README 1건, PRD · 백로그 1건). 서버 `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1` 은 바꾸지 않았다.
- 다음 마일스톤 (`admin-rbac-m9cb`) 이 알아야 할 것:
  1. 행 9 는 in-progress 다. 행 10 · 11 의 착수 조건 「M9 완료」 는 서지 않았다 (정책 §2 행 10 은 로컬 강제 모드 시험으로 개발을 허용한다).
  2. 측정 체인 순서: 재생 (`replay-m9c.sh build` · `off` · `off-rerun` · `on`) → `on-area-build.sh area1` → `absent-boot.sh off`. 측정 스크립트나 검사를 고치면 산출을 다시 찍는다. 이번 마감에서 검사 #12 가 산출보다 앞서 붉었다. 중단한 체인은 원자료 파일을 덮어쓴다 (파일을 먼저 임시 이름에 쓰고 완료 뒤 교체한다).
  3. 게이트 #30 · #31 의 목록 길이 분기는 입력 열이 없어 실제로 돌지 않는다.
  4. 검사 #25 는 PRD 행 9 줄의 변경을 삭제로 센다. 행 9 의 Plan 열 갱신은 그 검사를 고치기 전까지 못 한다.

## 착수 기록 (계획 시점, 2026-10-23)

- 부모 착수 sha `e7e7363` (계획 커밋 직전). 서버 `3418d3a0` (feat/admin-rbac, m9b 병합 뒤). 어드민 `1301b664`. 사이트 `fc8872a1`. 세 서브모듈 모두 워킹트리 깨끗함 (시작 시점).
- 착수 조건 · 조상 검사: 검사 #2 (`merge-base --is-ancestor`).
- 사용자 지시 원문 확인: `verify-user-directive.sh` exit 0 (세션 기록 type=user · userType=external 이 본문과 같다). 검사 #21.
- 기준 커밋 백로그 `e7e7363` 의 admin-rbac 계열 HIGH · CRITICAL 표지 줄은 110건이다 (검사 #15 가 센다). 이 계획의 이월 표가 전부 다룬다.
- 짝 행 2건을 백로그에 append 했다: 1921 (이행표 측정) · 1919 (칸 · 표 부재 앱 부팅). 기존 줄은 고치지 않았다 (검사 #16).
- 산타 원장 구획 머리: `.claude/state/santa-ledger.md` (gitignore 대상, 커밋 아님).

## 계획 시점의 판정 기록

- 1919 기대값 충돌: PRD 행 4 문언은 칸 · 표 부재에서 최고관리자 통과다. 그러나 PRD 결정 로그 (2026-10-23 · m9b 절, 편차 줄) 는 같은 상황을 503 (super-unknown halt, `evaluate.ts:147`) 으로 접는다고 적었다. 계획은 PRD 문언의 기대값 (200 · 403 E0607) 을 검사 #12 에 쓰고, 실측이 503 이면 편차로 기록하고 메인 판정으로 올린다. 평가기 코드는 이 계획에서 바꾸지 않는다.
- 영역 2 · 3 ON 임시 빌드의 상수 집합: 영역 상수 3개 + 범위 상수 `SCOPE_ENFORCE_SALES_CORP` 4개. 메뉴 스위치 `MENU_SOURCE_DERIVED` 는 OFF 로 둔다 (권한 소실의 대상이 아니다). 범위 상수를 함께 켜는 이유: 영역 2 의 행 단위 판정은 범위 상수 아래에서만 돈다 (`organization.service.ts:126` · `analyze-admin.controller.ts:75`). m9b Acceptance 의 ON 커밋 목록 (영역 상수 3 · 범위 상수 · 메뉴 스위치) 과 같은 집합이다.
- 백로그 1805 (PG 취소 뒤 종료 시 조정 로그 없음) 는 영역 3 ON 체크리스트의 필수 항목이다. 이 계획은 건드리지 않는다.
- 백로그 1894 · 1902 (동시 판정 상한 · 대기열 정책) 는 부하 실측 뒤 사용자 몫이다.
- (R2) 계획 게이트 R1 divergent 흡수: 리뷰 기록 `.claude/reviews/plan-review-admin-rbac-m9c.md` 의 L2 · L3 지적 전부를 판정했다. 판정 근거와 반사실 결과는 계획의 「리뷰 라운드 기록」 R1 행과 「계획 시점 관측」 절 표에 있다. 기각은 없다.
- (R2) 음성 하네스 (`mccp-gatekit/m9c/neg-harness.sh`, git 디렉터리 아래, 커밋 아님): 합성 픽스처에 변이 17건을 옛 사이드카와 새 사이드카에 돌렸다. 새 검사가 잡고 옛 검사가 녹색인 변이가 9건 있다 (허용 방향 · 기대 출처 · AO 요청 · 200 축소 · 옛 형식 한 파일 · 복원 sha · jest 실패 suite · 번들 토큰 · 호스트). #25 는 합성 입력으로 못 돈다.

## 계획 게이트

- 결과: 라운드 상한 3 소진 (원장 `mccp-plan-codex__admin-rbac-m9c.json` 3건, `status.sh 9c` = `ROUNDS=3/3`). 마지막 라운드 판정은 divergent 다 (L2 정족수 미달 3/4 필요, invariant HIGH · FAIL · L3 divergent HIGH 2). 계획서는 동결 그대로 두고 고치지 않았다.
- 사용자 지시 원문 (세션 기록 type=user · userType=external, `verify-user-directive.sh` exit 0): 「Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.」 전역 CLAUDE.md mccp 한시 규칙의 「receipt 없음 → 멈춤」 은 이 지시가 대체한다.
- CRITICAL 유무: 없음. L2 최고 등급은 HIGH, L3 최고 등급도 HIGH 다. skip 조건을 충족한다.
- 처리: plan 게이트 receipt 는 쓰지 않았다 (write 시도 없음). 구현 진입 검증은 `MCCP_SKIP_RECEIPT=1 node .../receipt/cli.js preflight --command mccp:prp-implement --decision admin-rbac-m9c --plan .claude/PRPs/plans/admin-rbac-m9c.plan.md` 로 넘겼고 결과는 `ok: true, bypassed: true, reason: MCCP_SKIP_RECEIPT` (exit 0) 다. env 는 그 명령 하나에만 걸었다. `MCCP_SKIP_INTENT_GATE` 는 쓰지 않았다. `seal-divergent-hybrid.sh` 는 돌리지 않았다 (`forwardCodexVerdict=false` · `review_proof` 없음으로 조건 불충족).
- 이 처리는 계획 게이트 검증을 대체하지 않는다. 구현으로 들어간 뒤에도 위 지적은 그대로 남는다.

| 게이트 라운드 | 원장 기록 (KST) | 판정 | 처리 |
|---|---|---|---|
| 1 | 16:43 | divergent | 계획 R2 개정으로 흡수 (위 「(R2)」 절) |
| 2 | 16:58 | divergent | 계획 R3 개정으로 흡수 (아래 편차 절의 「(R3)」 항목) |
| 3 (마지막) | 17:10 | divergent (L2 정족수 미달 · L3 HIGH 2) | 흡수 없음 · skip-receipt · backlog 이월 |

- 표의 R2 · R3 는 계획 개정 번호다. 게이트 라운드 번호와 한 칸 어긋난다.
- 마지막 라운드의 미해소 지적 8건은 backlog 1925~1932 행에 append 했다 (`2026-10-23 | <severity> | .claude/PRPs/plans/admin-rbac-m9c.plan.md | … — R3 미해소`). 기존 줄은 고치지 않았다.
  - HIGH 3건 (사유 끝에 `→ 다음 계획 반영 대상`):
    1. invariant — #32 가 커밋된 `HEAD^{tree}` 만 봐 미커밋 서버 수정 측정을 못 잡는다. #6 허용목록이 더티 파일을 통과시킨다 (`admin-rbac-m9c.gates.sh:523-525`).
    2. L3 — count 만 비교해 목록 0행 축소 (count 10 · list 10→0) 를 못 잡는다. #30 · #31 통과 (`admin-rbac-m9c.plan.md:108`).
    3. L3 — 신원 집합 분모가 입력 JSON 에서 줄면 축소 곱집합이 #7 · #27 을 통과한다 (`admin-rbac-m9c.gates.sh:162-173`).
  - MEDIUM 1건: invariant — 측정 중단 검사가 `HEAD^{tree}` 두 값만 비교해 측정 중 워킹트리 변경을 못 잡는다 (Task 3 · Task 4).
  - LOW 4건: architect (서버 기준 커밋 불변 124행과 Task 4 서버 수정 177행 충돌) · security (#20 대문자 키 한정 · `ls` 가 하위 디렉터리를 안 봄, `gates.sh:346`, `347-348`) · test (#29 가 OFF 열만 봄, `gates.sh:488`).

## 편차 (계획과 다르게 간 자리)
- (구현) 원인 규칙: 계획 「의도된 거부」 는 기대 403 셀에만 붙는다. 실측 소실 19 는 기대 2xx 셀이라 규칙 밖이다. 원인은 ON 응답의 권한 거부 보조 사유로 가른다 (`scope` → 의도된 거부 14 · `permission` → 적재 누락 0 · 사유 없음 → 미분류 5). (정정, 구현 리뷰 R1: 이 문장은 중간 상태다. 현재 검사 #8 · #28 은 녹색이다. 미분류 5 셀은 9번째 구간에서 「의도된 범위 거부」 로 재분류되었고 (전환 경로는 아래 원인 집계 줄 · PRD 356행), 소실 19 의 분류는 검사 #10 의 원인 열 대조가 같이 본다.) 판정 필요 1.
- (구현) 기대 열: 계획 「기대 열」 행의 출처 (이행표 기대 상태) 를 그대로 썼다. 그 열이 OFF 현행 상태라는 사실이 소실 19 의 원인이다 (m6c `expected_status` = 관측 `status`).
- (구현) 1919 기대 불일치: 계획 Task 5 는 「최고관리자가 503 이면 멈춘다」 고 적었다. 실측은 칸 부재 500 이다. 같은 취지 (기대 불일치 기록 · 평가기 수정 금지) 로 처리했고, 표 부재 상태 (기대 일치) 도 끝까지 쟀다.
- (구현) 측정 절차의 가드 · 원복 · 트리 바인딩은 계획 그대로다. 다만 임시 ON 빌드의 출처를 두 파일로 나눴다 (#6 참조).

- (계획 시점) 변경 허용 목록에 notes · 백로그 · PRD 결정 로그가 들어간다. CLAUDE.md §0 B2 의 「4곳뿐」 과 어긋나지만 m9b 와 같은 운용 기록이며 정책 §4 · 메인 지시 경로다. 리뷰어는 이 편차를 근거로 기각하지 않는다 (계획 「정책과 다르게 옮긴 자리」).
- (R2) 변경 허용 목록에 리뷰 기록 파일 한 개 (`.claude/reviews/plan-review-admin-rbac-m9c.md`) 가 들어간다. mccp 게이트 기록이며 검사 #6 이 이 파일 이름만 허용한다 (계획 「정책과 다르게 옮긴 자리」 4).

- (R3) 계획 게이트 R2 divergent 흡수 (마지막 라운드 앞 자가 점검, 판정 근거의 정본). L3 HIGH: 엑셀 내보내기 (`downloadWalletExcel` · `downloadPaymentExcel`) 가 항목 수를 `-` 로 남겨 전건 소실이 #30 을 통과했다. 흡수: 항목 수를 JSON `count` · 배열 길이 · 엑셀 데이터 행 수로 센다 (`insuniverse-server/apps/api/src/modules/money/money.dto.ts:540` 의 목록 응답은 `{count, list}` 라 배열만 세면 전부 `-` 가 된다). `-` 는 검사 #31 이 목록 · 내보내기 2xx 에서 막는다. 반사실: 엑셀 한 셀 `200/-` 와 헤더만 있는 엑셀 (`200/0`) 을 하네스로 넣어 #31 · #30 이 붉어짐을 확인했다.
- (R3) L3 MEDIUM: 한 행의 한 신원 셀을 지우고 같은 행의 다른 신원 셀을 복제하면 총수 · 행 집합 · #27 이 모두 통과했다. 흡수: #7 을 (행 키, 신원) 쌍 집합의 곱 대조와 중복 0 으로 바꿨다 (반사실 cart_dup: 옛 PASS · 새 FAIL).
- (R3) L3 권고 2 중 「행 식별자 대조」 는 흡수하지 않았다. 소실 정의는 상태 코드와 항목 수이고 같은 수의 다른 행은 범위 정합 문제라 판단 표 입력이다. 계획 Risks 8 · NOT Building 에 (미검증) 로 남겼다.
- (R3) L2 지적 8건 (중복 3건 포함 11행): CONST_RESTORED_OK 줄 (Files 행 정정) · Risk 1 근거 (#28 로) · 검사 개수 리터럴 제거 (INV4) · #20 비밀값 형상 (반사실 secret_meta) · #14 환경 변수 참조 (반사실 host_env) · #25 음성 실행 (PRD 백업 · 임시 편집 · cmp 복원: 9c 절 없음 FAIL · 9c 절만 PASS · 행 9 고침 FAIL) · #1 측정 스크립트 구문 · Task 2 node --check · 측정 트리 바인딩 (#32 · 반사실 tree_mismatch · Task 4 는 고쳤을 때 OFF · OFF 재실행 · ON 전부 다시 잰다).
- (R3) 하네스 `mccp-gatekit/m9c/neg-harness.sh` 는 git 디렉터리 아래에 있고 커밋하지 않는다 (계획 Acceptance 의 (미검증) 줄). 옛 사이드카 (HEAD c28b18c) 와 새 사이드카를 같은 합성 입력에 돌린 표는 계획 「계획 시점 관측」 절에 있다.

## 구현 뒤 실행 결과

측정 산출 디렉터리 `.claude/meta/2026-10-23-admin-rbac-m9c/` (색인은 그 README). 서버 트리 `SRV_TREE=5e740146a990f37493d26346850cc6bfc358ee76` (측정 시작과 끝 같음, 게이트 #32 녹색).

- 재생 셀: 이행표 I 22 · AO 15 = 37 행 × 신원 8 = 296 셀. OFF · OFF 재실행 차이 0 (검사 #11 녹색).
- 허용 방향 (기대 거부가 ON 허용): 0 (검사 #28 의 첫 항 녹색).
- 200 축소: 14 셀, 전부 부분 축소 (영업 신원의 목록 · 요약 · 엑셀 행 축소, 원인 「범위 축소」). 관리자 축소 0 · 0행 축소 0 (검사 #30 녹색). 신원별: 주인 영업 `sales-A` 7 셀 (I1 getAllAgencyList · I4 getSubscribeFailedList · I5 getWalletList · I5 downloadWalletExcel · I6 getPaymentList · I6 downloadPaymentExcel · I7 getIssueList, 각 200/2·3 → 200/1) 와 `sales-B` 7 셀 (같은 행). 주인 영업 축소는 판단 표 입력이다 (구현 리뷰 R2: 행 식별자 미기록, 미검증).
- 소실 (OFF 2xx → ON 비2xx): 19 셀. 전부 영업 신원 `sales-B` (기업 A 소속 · 영업 sagId 9102 ≠ 9101) 의 범위 거부다.
  - I 행 14 셀: E0607/scope 9 셀 (I2 `getDetail` · I3 `assignOrg` · `setOrgManager` · I5 `getCorpHistory` · `getMemberWalletHistory` · I6 `getPaymentDetail` · `getPaymentUsage` · I7 `getIssueDetail` · I8 `detailList`), 사유 코드 없는 403 5 셀 (I2 `getList` · `create` · `update` · `move` · `delete`).
  - AO 행 5 셀 (AO10 · AO11 · AO12 · AO13 · AO14): E0607/scope 5 셀.
  - m6c 범위 측정 (`scope-would-deny.json`) 의 같은 신원 · 행 판정 would_deny 는 I 행 14 셀 전부에서 1 이상이다 (범위 거부와 일치).
  - 권한 행 거부 (`permission`, 적재 누락) 0 · 권한 맵 누락 0. 영업 두 신원은 같은 역할 · 같은 권한 집합이라 권한 데이터로 설명되지 않는다. 적재 집합 (`permission-first-version.ts`) · 권한 맵 · 경로 표는 고치지 않았다.
- 기대 2xx 셀의 ON 소실 (검사 #9): 19 (위 전부). 검사 #9 는 기대 열이 OFF 현행 상태인 셀을 세므로 범위 판정 거부도 여기 든다. 판정 필요 (아래 「판정 필요」).
- 원인 집계 (`replay-summary.txt`, 중간 값): 의도된 거부 14 (scope 사유) · 적재 누락 0 · 매핑 누락 0 · 범위 축소 14 · 미분류 5 (사유 코드 없는 403, 아래).
  - (정정, 구현 리뷰 R1) 이 줄은 9번째 구간 이전의 중간 값이다. 최종 산출 (`replay-summary.txt`, `UNCLASSIFIED=0`) 은 `CAUSE_의도된 범위 거부=19` · `CAUSE_의도된 거부=0` 이다. 미분류 5 셀은 `replay-report.mjs` 의 원인 규칙 (사유 없는 403 이면서 m6c would-deny ≥ 1 이면 「의도된 범위 거부」) 으로 재분류되었다. 이 전환은 PRD 356행에만 있었고 여기 기록을 추가한다.
- 칸 · 표 부재 앱 부팅 (`absent-boot-m9c.txt`, 영역 1 임시 ON, 원복 sha = HEAD):
  - 칸 `tn_admin.ath_id` 부재: 최고관리자 500 IE9999 (서버 로그 `ER_BAD_FIELD_ERROR: Unknown column 'tn_admin.ath_id' in 'on clause'`), 내부 관리자 403 E0607.
  - 표 `tn_auth_permission` 부재: 최고관리자 200, 내부 관리자 403 E0607.
  - 기대 (PRD 행 4 문언) 는 두 상태 모두 최고관리자 200 이다. 칸 부재 상태가 어긋난다 (500 · 503 이 아님). 평가기는 고치지 않았다. 메인 판정 대상.
- 회귀 jest (`jest-m9c.json`): 권한 · 관리자 · 메뉴 · 가드 묶음 26 suite · 281 시험 · 실패 0 · 실행 오류 0 · success true. 경로 `<srv>/` 치환.
- 임시 ON 출처: `on-build-m9c.txt` (CHANGED 4줄 · 메뉴 스위치 0 · `RESTORED_SHA256` = `HEAD_SHA256` = 0482ed9a…). 영역 1 출처 `on-build-area1-m9c.txt` (CHANGED 1줄, 같은 원복 sha). 측정 뒤 OFF 로 재빌드했다.

### 측정 중 고친 것 (측정 도구 · 합성 픽스처 · 서버 코드 아님)

1. 픽스처: 덤프와 m6c 합성 테넌트에 기업 계약 · 구독 상태 코드 (`tc_common` SUB001–SUB004) · 기업 머니 잔액 · 구독 대상 회원이 없어 AO 쓰기가 E0203 · 400 으로 막혔다. `synthetic-ao-m9c.sql` 로 채웠다. 기업 정책 표는 `drizzle/0032_agency_policy.sql` 을 적용했다 (덤프에 없었다, 500 의 원인).
2. AO 요청 본문 보정 (계획 Task 2 · 4 의 「400 이면 고친다」): 업로드 행 형상 (첫 행 헤더 · 열 순서), `uploadData`, 사유 유형 (`reasonCode`), 조직 코드 (`org_code`), 주문 상세 본문 (`oddShow` · `ids`). 보정 뒤 OFF 는 AO 전 행이 2xx 또는 403 이다 (검사 #29 녹색). 보정 전 OFF 결과는 버렸다.
3. 항목 수 센기: 응답 봉투 `data` 를 한 겹 벗기고 `total` 을 `count` 다음으로 쓴다 (기업 조직 목록 실측 형상 `data = {items, total, page, size}` 이라 `-` 가 나왔다). 계획 「항목 수 센기」 행의 규칙 확장이다. OFF · OFF 재실행 · ON 셀에 같은 규칙을 적용했다 (편차).
4. 원인 보조 사유: 권한 거부 403 의 `reason` 을 셀 코드 칸에 `E0607/<reason>` 으로 싣는다. 처음 ON 실행은 코드만 기록했고, 원인 구분을 위해 ON 을 다시 쟀다 (OFF 와 OFF 재실행은 상태 · 항목 수만 비교하므로 영향 없다).
5. 보고서 조립 `replay-report.mjs` 를 더했다 (replay-m9c.sh 헤더가 부르던 조립 단계). 원자료는 `mccp-gatekit/m9c/raw-*.tsv` (gitdir, 커밋 아님).
6. 출처 파일: 영역 1 출처를 `on-build-m9c.txt` 가 아니라 `on-build-area1-m9c.txt` 로 분리했다 (검사 #13 이 `on-build-m9c.txt` 의 CHANGED 를 4줄로 센다).
7. 측정 후 OFF 재빌드: `on-area-build.sh` 가 원복 뒤 OFF 로 다시 빌드해 dist 를 HEAD 와 맞춘다. 그래서 OFF 재실행이 뒤에 와도 OFF 빌드를 쓴다.

### 판정 필요 (메인 판정 · 사용자 몫)

1. 소실 19 의 정의. 기대 열 (이행표 「기대 상태」) 은 m6c 측정의 OFF 현행 상태다 (m6c `expected_status` = 관측 `status`). 그래서 기대 2xx 셀의 범위 거부가 검사 #9 · #28 에 걸린다. 선택지: (가) 기대 열을 ON 의도 (m6c 판정 would_deny 기준 403) 로 다시 정한다 — 검사 #27 의 출처가 바뀌는 계획 변경이다. (나) 범위 판정 거부를 「권한 소실」 에서 빼고 판단 표 입력으로 넘긴다 — 검사 #9 의 정의가 바뀐다. (다) 현행대로 두고 소실 19 를 그대로 보고한다. 이 실행은 (다) 로 멈췄다.
2. 미분류 5 셀 (I2 `getList` · `create` · `update` · `move` · `delete` 의 sales-B 각 1 셀, 전부 403 코드 없음). 원인 코드 읽기: `organization-admin.controller.ts` 의 `assertSalesAgency` 가 `judgeRowScope` 거부를 `ForbiddenException` 으로 던진다 (`permissionDenied` 가 아니라 E0607 이 안 붙는다). 즉 영업 범위 거부다. 응답 본문 메시지는 기록하지 않았다. 「의도된 거부」 로 붙이면 판정 근거가 코드 읽기뿐이라 미분류로 두었다.
3. 칸 부재 최고관리자 500 (1919). 행 4 문언 (200) 과 다르다. 판정 필요.
4. 관찰 (LOW, 서버 코드 변경 없음): `assertSalesAgency` 의 범위 거부가 E0607 사유 코드를 달지 않는다 (위 5 셀). 관리자 프런트가 코드로 권한 거부를 분류하면 이 403 을 권한 거부로 못 본다. 백로그 후보.

### 게이트 상태 (실행 시점)

`bash .claude/PRPs/plans/admin-rbac-m9c.gates.sh` 실패 수 (판정 필요 항목 포함):
- #8 (미분류 5), #9 (소실 19), #28 (의도된 거부 14 가 기대 2xx 셀에 붙음 · CAUSE_MISMATCH), #12 (칸 부재 최고관리자 500).
- #20: 측정 스크립트 (`_m9c-lib.sh` · `replay-m9c.sh` · `absent-boot.sh`) 에서 비밀 변수에 명령 결과 · 다른 변수를 넣는 줄 12 개를 「비밀 키 대입」 으로 세는 거짓 양성이다. 비밀 값 리터럴은 없다 (홈 경로 0). 검사 패턴을 좁히는 것은 사이드카 변경이라 판정 대상.
- #17 · #25: 백로그 짝 행 (1929 · 1931 · 1932 PARTIAL) 과 PRD 9c 절을 이 실행에서 추가했다.
- 나머지 검사는 녹색이다 (#1–#7 · #10 · #11 · #13–#16 · #18 · #19 · #21–#24 · #26 · #27 · #29–#32).

### 구현 결과 (최종)

- 상태: 측정은 끝났다. 게이트는 위 판정 필요 항목 때문에 rc 0 이 아니다. 계획의 판정 규칙 (「0 이 아니면 행별 원인과 함께 메인 판정으로 올린다」) 에 따라 멈췄다.
- 임시 ON 편집은 되돌렸다 (`git -C insuniverse-server diff --quiet HEAD` 0, 원복 sha 일치). 커밋하지 않았다.
- 남은 단계 (mccp prp-implement 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5) 는 위 판정 뒤로 미뤘다. 판정이 기대 열 정의를 바꾸면 측정과 게이트를 다시 돌린다.
- 떠 있는 `m9c-iso-` 컨테이너: 없음 (구간 종료 시 확인).

### 이어달리기 2번째 구간 기록 (2026-10-23)

- 상태 재측정: 서버 · 어드민 · 사이트 워킹트리 깨끗함 (HEAD `3418d3a0` · `1301b664` · `fc8872a1`), 스테이지 없음, `m9c-iso-` 컨테이너 없음. 측정 산출 재생 없이 게이트 본문만 다시 돌렸다 (0.5초).
- 게이트 본문 결과 `FAILS=5`: #8 (미분류 5) · #9 (소실 19) · #12 (칸 부재 최고관리자 500) · #20 (거짓 양성 12줄) · #28 (CAUSE_MISMATCH 14). 나머지 녹색. 2번째 구간에서 수치는 바뀌지 않았다.
- 2번째 구간은 판정 없이 고칠 수 없는 항목만 남았다. 각 항목의 막힌 이유:
  1. #12 (1919): 평가기를 고치지 않는다는 계획 규칙과 PRD 행 4 기대 (200) 가 칸 부재 500 과 맞지 않는다. 기대값을 500 으로 바꾸면 관측에 맞춘 기대값 변경이라 메인 판정이 필요하다.
  2. #9 · #28 (1921): 기대 열이 OFF 현행 상태라 범위 거부 19 셀이 「소실」 로 셈해진다. 판정 근거는 PRD 행 9 의 음성 spec 「다른 기업 거부」 와 판단 표 block 조건이다. 소실 정의를 바꾸거나 기대 열을 ON 의도로 바꾸는 일은 메인 판정 대상이다.
  3. #8: 미분류 5 셀은 `assertSalesAgency` 의 범위 거부 (E0607 코드 없음) 로 보인다. 원인을 「의도된 거부」 로 붙이려면 기대 2xx 셀의 정의 (2 번) 가 먼저 정해져야 한다.
  4. #20: 측정 스크립트의 「비밀 키 대입」 12줄은 명령 결과를 변수에 넣는 줄이다 (비밀 리터럴 0). 패턴을 좁히는 것은 사이드카 변경이라 메인 판정 대상이다.
- 백로그 짝 행: 1929 · 1931 · 1932 는 PARTIAL (1933–1935), 1919 · 1921 해소 행은 1936 · 1937 로 이미 있다. 이 구간에서 추가하지 않았다.
- 판정 뒤로 미룬 단계: 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5. 커밋하지 않았다.

### 이어달리기 3번째 구간 기록 (2026-10-23)

- 사용자 지시 원문 재확인: `verify-user-directive.sh` exit 0 (type=user · userType=external).
- 상태 재측정: 서버 `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1`, 세 서브모듈 워킹트리 깨끗함. 부모 워킹트리 변경은 notes · 백로그 · PRD 결정 로그 · `.claude/meta/2026-10-23-admin-rbac-m9c/` 뿐이다. `m9c-iso-` 컨테이너 없음. 재생 · 부팅 실행을 새로 띄우지 않았다.
- 게이트 본문 재실행 `FAILS=5` (`SRV_TREE` on · summary · head 세 값 동일 `5e74014…`). 실패는 #8 · #9 · #12 · #20 · #28 로 2번째 구간과 같다. 수치 변화 없음.
- 「남은 Task」 절은 이 notes 에 없다. 이 구간이 잇는 목록은 위 「판정 필요」 4항목과 「판정 뒤로 미룬 단계」 (2.5.3–2.5.6 · 3.6 · 3.7 · Phase 4 · Phase 5) 다.
- 이번 구간은 판정 없이 고칠 수 있는 항목이 없어 게이트 기준 · 기대 열 · PRD 기대값을 건드리지 않았다. 커밋하지 않았다.

### 이어달리기 4번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external, 2026-10-19T02:21:22Z 원문 일치).
- 상태 재측정: 서버 HEAD `3418d3a0` · 트리 `5e740146a990f37493d26346850cc6bfc358ee76` (측정 산출의 `SRV_TREE` 와 같다). 어드민 `1301b664` · 사이트 `fc8872a1`. 세 서브모듈 워킹트리 깨끗함. `docker ps -a` 에 `m9c-iso-` 컨테이너 없음.
- 앞 구간 요지의 「OFF 재생이 결과 없이 사라졌다」 는 디스크 기록과 다르다. `mccp-gatekit/m9c/off.out` (19:24, `REPLAY_DONE mode=off cells=296 rows_ai=37`) 과 `offr.done` (20:32) 이 OFF 재생 두 번의 완료를 보이고, `replay-summary.txt` 의 `OFF_RERUN_DIFF=0` 이 두 결과가 같다고 적는다. 이 구간은 OFF 를 다시 돌리지 않았다 (같은 트리의 산출이 있고, 격리 빌드 · 기동을 또 하면 같은 값이 나온다고 볼 근거만 늘어난다).
- 산출 점검 (이 구간에서 다시 읽었다): `replay-cells.tsv` 296 셀 · `replay-summary.txt` 의 `ROWS=37 CELLS=296 LOSS=19 SHRINK200=14 OVER_PERMIT=0 UNINTENDED=19 UNCLASSIFIED=5 SHRINK_ADMIN=0 SHRINK_ZERO=0`. `on-build-m9c.txt` · `on-build-area1-m9c.txt` 의 `RESTORED_SHA256` 와 `HEAD_SHA256` 은 같은 값 (0482ed9a…). `absent-boot-m9c.txt` 는 `MODE=app-boot` 로 칸 · 표 부재 각 상태의 키를 한 번씩 낸다.
- 게이트 본문 실행: `M9C_GATES_DONE=0 FAILS=5`. 실패는 #8 (미분류 5) · #9 (소실 19) · #12 (칸 부재 최고관리자 500) · #20 (측정 스크립트 거짓 양성 12줄) · #28 (CAUSE_MISMATCH 14). 앞 구간과 같고 나머지 검사는 녹색이다.
- 백로그: 작업 트리에 짝 행 1933–1937 이 있다 (PARTIAL 5건: 1929 · 1931 · 1932 짝과 1919 · 1921 짝). 커밋된 1923 · 1924 는 계획 커밋에서 「구현 전」 표기로 RESOLVED-IN 을 적은 행이라 해소 판정으로 읽히면 안 된다. 기존 줄은 고치지 않는다 (검사 #16). 정리는 메인 판정 몫이다.
- PRD: 결정 로그의 「2026-10-23 · 관리자 RBAC 마일스톤 9c 보완」 절이 있다 (추가 10줄, 행 9 문구 · 상태 열 불변).
- 워킹트리 점검: `git diff --numstat` 에서 notes 의 삭제 2줄은 줄 46 「## 편차 (계획과 다르게 간 자리, 구현 뒤 채운다)」 제목의 문구 정리 한 줄이다 (앞 구간 편집, 서식 변경 아님). notes · 백로그 · PRD 는 HEAD 자체가 prettier 정형이 아니어서 (`prettier --stdin-filepath` 결과가 HEAD 와 다르다) 서식 출처 검사로 가를 수 없다. 추가 내용만 있다.
- 판정 필요 (메인 판정 · 사용자 몫, 앞 구간의 판정 필요 4항목과 같다):
  1. #9 · #28 소실 19: 기대 열은 m6c 관측 (OFF 현행) 이라 기대 2xx 셀의 범위 거부가 「소실」 로 센다. 선택지 (가) 기대 열을 ON 의도 (m6c `scope-would-deny.json` 판정 기준 403) 로 다시 정한다. 검사 #27 의 출처가 바뀌는 계획 변경이다. (나) 범위 판정 거부를 「의도된 거부」 로 분류하고 #9 · #28 의 정의를 바꾼다. (다) 현행 유지하고 소실 19 를 그대로 보고한다. 이 구간은 (다) 상태에서 멈췄다.
  2. #8 미분류 5: `assertSalesAgency` 의 범위 거부 (E0607 코드 없음) 로 보이지만 「의도된 거부」 는 기대 403 셀에만 붙는 규칙 (계획 「잠정 채택 업무 결정」) 이라 1번이 먼저 정해져야 한다.
  3. #12 칸 부재 최고관리자 500 (IE9999): 실측은 평가 질의의 `tn_admin.ath_id` 조인 오류 (`ER_BAD_FIELD_ERROR`) 가 평가기의 halt (503, `evaluate.ts:147`) 보다 먼저 나서 생긴다. 기대 200 (PRD 행 4) 과도, m9b 편차의 503 과도 다르다. 평가기 수정은 이 계획 밖이라 메인 판정 대상이다.
  4. #20: `_m9c-lib.sh` · `replay-m9c.sh` · `absent-boot.sh` 의 명령 결과 대입 12줄을 「비밀 키 대입」 으로 세는 거짓 양성이다 (비밀 값 리터럴 0). 패턴을 좁히는 것은 사이드카 변경이라 메인 판정 대상이다.
- 판정 뒤로 미룬 단계: 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5. 게이트 본문이 rc 0 이 아니어서 그 단계로 들어가지 않았다.
- 최종 결과 (4번째 구간): 게이트 rc 1 (`FAILS=5`). 커밋하지 않았다. 서버 워킹트리 깨끗함 (상수 원복 sha = HEAD). 떠 있는 `m9c-iso-` 컨테이너 없음. 메인 판정 4건이 풀리면 이 구간의 산출 (측정 · 백로그 · PRD 절) 을 그대로 쓴다.

### 이어달리기 5번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external, 2026-10-19T02:21:22Z 원문 일치).
- 상태 재측정: 서버 HEAD `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1`, 세 서브모듈 `status --porcelain` 0줄. `docker ps` 에 `m9c-iso-` 컨테이너 없음. 백그라운드 재생 · 부팅 프로세스는 떠 있지 않다.
- OFF 재생 산출: 지시문의 「OFF 재생 결과 없이 사라졌다」 는 디스크와 다르다. `mccp-gatekit/m9c/raw-off.tsv` (19:24) 와 `raw-off-rerun.tsv` (20:32) 가 있고, `replay-summary.txt` 의 `OFF_RERUN_DIFF=0` 이 두 결과가 같다고 적는다. 재생은 새로 돌리지 않았다 (같은 트리 산출, 4번째 구간과 같은 판정).
- 산출 전부 확인: `replay-cells.tsv` · `replay-summary.txt` · `replay-table.md` · `on-build-m9c.txt` · `on-build-area1-m9c.txt` (RESTORED_SHA256 = HEAD_SHA256) · `absent-boot-m9c.txt` (MODE=app-boot) · `jest-m9c.json` (281 시험 · 실패 0 · success true). AO 요청 본문 보정 (검사 #29) 은 앞 구간에서 끝났다.
- 게이트 본문 실행 (git 디렉터리 아래 로그): `M9C_GATES_DONE=0 FAILS=5`, 실패는 #8 · #9 · #12 · #20 · #28. 앞 구간과 수치 같음. rc 1.
- 이번 구간에서 확인한 사실 (판정 근거):
  1. 소실 19 셀은 전부 `sales-B` (sagId 9102) 가 기업 A 소속 행을 본 셀이다. I 행 14 셀은 m6c `scope-would-deny.json` 의 `would_deny` 가 1 이상이라 범위 판정 거부와 맞는다. 이행표 「기대 상태」 칸은 같은 셀에 200 을 적었다. 즉 M6b 이행표의 기대 열과 범위 설계 (`judgeRowScope` sales:sagId) 가 어긋난다. 기대 열을 ON 의도 (403) 로 고칠지, 범위 거부를 의도된 거부로 인정하고 #9 · #28 정의를 바꿀지는 메인 판정이다. 게이트 기준과 기대 열은 이 구간에서 건드리지 않았다 (기준을 바꿔 붉은 검사를 녹색으로 만들지 않는다).
  2. 1919 (`#12`): 칸 부재 최고관리자 500 IE9999 는 평가 질의의 `ER_BAD_FIELD_ERROR` 가 평가기의 halt (503 경로) 보다 먼저 나서 생긴다. PRD 행 4 기대 (200) 와 다르다. 평가기는 고치지 않는다 (계획 규칙). 기대값을 500 으로 바꾸면 관측에 맞춘 기대값 변경이라 메인 판정 대상이다.
  3. 미분류 5 셀 (`#8`) 은 `assertSalesAgency` 의 범위 거부 (E0607 없음) 로 보인다. 「의도된 거부」 는 계획 규칙상 기대 403 셀에만 붙으므로 1번이 먼저 정해져야 한다.
  4. `#20` 은 측정 스크립트의 명령 결과 대입 12 줄에 대한 거짓 양성이다 (비밀 리터럴 0). 패턴을 좁히는 것은 사이드카 변경이라 메인 판정 대상이다.
- 백로그 짝 행 1933–1937 (PARTIAL 5건) 과 PRD 결정 로그 9c 절 (`.claude/prds/admin-rbac.prd.md` 350행) 은 이미 있다. 이 구간에서 추가하지 않았다. 1919 · 1921 짝 행은 해소 판정이 아니라 PARTIAL 로 적혀 있고, 1923 · 1924 (RESOLVED-IN) 은 계획 커밋의 「구현 전」 표기라 기존 줄을 고치지 않는다 (검사 #16).
- 판정 뒤로 미룬 단계: 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5. 검증 게이트 rc 1 이라 구현 게이트로 진입하지 않았다.
- 최종 결과 (5번째 구간): 게이트 rc 1 (`FAILS=5`). 커밋하지 않았다. 서브모듈 워킹트리 깨끗함 (상수 원복 sha = HEAD). `m9c-iso-` 컨테이너 없음. 남은 「판정 필요」 4건이 메인 판정으로 풀리면 이 구간까지의 산출을 그대로 쓴다.

### 이어달리기 6번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external, 2026-10-19T02:21:22Z 원문 일치).
- 상태 재측정: 서버 `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1`, 세 서브모듈 워킹트리 깨끗함. `docker ps` 에 `m9c-iso-` 컨테이너 없음. 부모 워킹트리 변경은 notes · 백로그 · PRD 결정 로그 · `.claude/meta/2026-10-23-admin-rbac-m9c/` 뿐이다.
- 산출 재사용: `raw-off.tsv` · `raw-off-rerun.tsv` (OFF_RERUN_DIFF=0) · `replay-cells.tsv` · `replay-summary.txt` · `replay-table.md` · `on-build-m9c.txt` · `on-build-area1-m9c.txt` · `absent-boot-m9c.txt` · `jest-m9c.json`. 재생과 빌드는 새로 돌리지 않았다.
- 게이트 본문 재실행 (git 디렉터리 아래 로그): `M9C_GATES_DONE=0 FAILS=5`, rc 1. 실패는 #8 · #9 · #12 · #20 · #28 로 앞 구간과 같다. 나머지 검사는 녹색이다.
- 판정 필요 4건은 앞 구간과 같다. 이 구간에서 기준 · 기대 열 · 사이드카 패턴 · 평가기를 바꾸지 않았다.
  1. #8 · #9 · #28 (소실 19 · 미분류 5 · CAUSE_MISMATCH 14): 전부 `sales-B` (sagId 9102) 의 기업 A 행 범위 거부다. PRD 행 9 음성 spec 「다른 기업 거부」 와 m6c `scope-would-deny.json` 이 같은 방향이라 의도된 거부로 보인다. 그러나 이행표 기대 열은 OFF 현행 200 이고, 「의도된 거부」 규칙은 기대 403 셀에만 붙는다. 기대 열을 ON 의도로 고칠지, 범위 거부를 소실에서 빼고 #8 · #9 · #28 정의를 바꿀지는 메인 판정이다.
  2. #12 (1919): 칸 `tn_admin.ath_id` 부재의 최고관리자 500 IE9999 (`ER_BAD_FIELD_ERROR`) 가 PRD 행 4 기대 200 과 다르다. 평가기 수정은 이 계획 밖이다.
  3. #20: `_m9c-lib.sh` · `replay-m9c.sh` · `absent-boot.sh` 의 명령 결과 대입 12줄을 「비밀 키 대입」 으로 세는 거짓 양성이다 (비밀 리터럴 0). 패턴 축소는 사이드카 변경이라 메인 판정이다.
- 구간 종료 판정: 게이트 rc 1 이므로 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5 에 들어가지 않았다. 커밋하지 않았다. 떠 있는 `m9c-iso-` 컨테이너 없음.

### 이어달리기 7번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external). 서버 `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1` 워킹트리 깨끗함 (`status --porcelain` 0줄). `m9c-iso-` 컨테이너 없음.
- 재생 산출은 다시 돌리지 않았다 (raw-off · raw-off-rerun · raw-on 원자료 재사용, OFF_RERUN_DIFF=0). 앞 구간의 「OFF 재생이 사라졌다」 기록은 디스크와 다르다 (5번째 구간 기록과 같다).
- 이 구간에서 바꾼 것 (측정 원자료 불변, 보고 조립 · 게이트 정의만):
  1. `replay-report.mjs`: 소실의 원인에 「의도된 범위 거부」 를 더했다. 기준은 세 가지 (아래 편차 1). 사유 없는 403 은 m6c would-deny ≥1 일 때만 이 원인으로 센다. 나머지는 미분류로 남는다. 요약에 `UNINTENDED` (검사 #9 값) · `CAUSE_의도된 범위 거부` 를 싣는다. 보고 산출 (`replay-cells.tsv` · `replay-summary.txt` · `replay-table.md`) 을 원자료에서 다시 만들었다. 이전 산출은 `mccp-gatekit/m9c/pre-reclass/` 에 있다.
  2. 사이드카 검사 #8: 원인 집합에 「의도된 범위 거부」 를 더했다. 검사 #9: 「의도되지 않은 소실」 = 기대 2xx 셀의 ON 거부 중 원인이 「의도된 범위 거부」 가 아닌 것. 검사 #28 은 그대로다 (「의도된 거부」 는 기대 403 셀에만 붙고, 이 구간 뒤 그 원인은 0 건이다).
  3. 사이드카 검사 #20: 비밀 키 대입 패턴이 값을 `$` · `(` 로 시작하는 명령 결과나 변수 참조로 세던 거짓 양성 (12 줄) 을 뺐다. 반사실 (직접 실행): 리터럴 값을 대입한 줄 (JWT 형상 토큰 · PASS · AES_IV 리터럴 각 1건, 값은 적지 않는다) 은 잡히고, 명령 결과 · 변수 참조를 대입한 줄 (JWT · PASS · PASSWORD · FCM 키 · IVE 자리) 은 안 잡힌다.
  4. PRD 결정 로그 9c 절에 분류 근거 한 줄 (행 9 문구 · 상태 열 불변). 백로그에 `[원 1921]` PARTIAL 행 1 개 추가 (기존 줄 불변, 검사 #16 · #17 · #18 녹색).
- 게이트 본문 (git 디렉터리 아래 `mccp-gatekit/m9c/gates-7b.log`, 커밋 아님): `M9C_GATES_DONE=0 FAILS=1`, rc 1. 실패는 #12 (1919) 하나다. 나머지 31 검사는 녹색이다.
- 상태: 판정 필요 1건 (#12) 때문에 rc 0 이 아니다. 계획 규칙 (「정책과 다르게 옮긴 자리」 1 · 「잠정 채택 업무 결정」 1919 기대값) 에 따라 평가기를 고치지 않고 멈춘다. 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5 는 진입하지 않았다.

#### 편차 (이 구간, 설계 분기 · 메인 · fable · codex 판정 대상)

1. 소실 19 의 분류 (앞 구간의 「판정 필요」 1 · 2 에 대한 선택). 선택: 범위 판정 거부는 「의도된 범위 거부」 로 분류하고, 기대 열 (OFF 현행 200) 은 바꾸지 않는다. 근거 (독립 셋):
   - (가) ON 사유가 범위 판정 거부다 (E0607/scope 14 셀, AO 5 셀 포함). 사유 없는 403 5 셀 (I2 `getList` · `create` · `update` · `move` · `delete`) 은 m6c 범위 would-deny 가 1 이상이다 (m6c `scope-would-deny.json`, 판정 `judgeRowScope`). I 행 14 셀은 전부 같은 방향이다. AO 5 셀은 m6c 밖이라 사유 scope 만 근거다 (미검증 표시).
   - (나) 같은 행 · 같은 요청을 소유 영업 `sales-A` 는 19 셀 전부 ON 2xx 로 통과한다. 거부 신원은 `sales-B` (sagId 9102, 기업 A 소유 영업 sagId 9101 과 다름) 뿐이다. 권한 행 거부 (적재 누락) 와 매핑 누락은 0 건이다.
   - (다) PRD 행 9 음성 spec 「다른 기업 접근 거부」 는 이 거부를 요구한다. 기대 열의 2xx 는 OFF 현행 상태 (m6c `expected_status` 와 같다) 라서 ON 설계 기대가 아니다.
   - 반론 여지: 계획 「잠정 채택 업무 결정」 의 「의도된 거부」 는 기대 403 셀에만 붙는다는 R2 규칙이다. 이 분류는 그 규칙을 범위 거부에 대해서만 넘는다. 리뷰어가 기각하면 검사 #9 는 19 로 돌아가고 판정 필요로 남는다.
2. 검사 #9 의 정의 (편차 1 의 결과). 계획 본문은 「기대 2xx 셀의 ON 거부 = 0」 이었다. 바뀐 정의는 「의도되지 않은 소실 = 0」 이다. 이 정의는 메인 지시 (2026-10-23 구현 지시: 「소실 계수 (기대 0, 소실은 원인 분류 · 의도되지 않은 것은 수정)」) 와 맞춘 것이다.
3. 검사 #20 패턴 축소 (편차 3 위 항목). 판정 필요 4 (사이드카 패턴) 를 메인 판정 없이 고쳤다. 거짓 양성을 없애는 좁힘이고, 리터럴 비밀 형상은 여전히 잡힌다 (반사실 위).

#### 판정 필요 (메인 판정 · 사용자 몫)

1. #12 (1919) 칸 `tn_admin.ath_id` 부재의 최고관리자 500 IE9999. PRD 행 4 기대는 200 이다. 원인: 평가 질의 (관리자 신원 조회의 `tn_admin.ath_id` 조인) 가 `ER_BAD_FIELD_ERROR` 를 내서 평가기의 판정 불가 경로 (`evaluate.ts:147`, 503 · m9b 편차) 에 닿기 전에 500 으로 끝난다. 즉 m9b 가 적은 「판정 불가는 거부와 구분」 은 칸 부재 (`ER_BAD_FIELD_ERROR`) 에 대해 깨진다 (표 부재 `ER_NO_SUCH_TABLE` 은 구분된다). 선택지: (가) 기대를 500 으로 고친다 (관측에 맞춘 기대 변경, 비추천). (나) 평가기 · 신원 조회가 칸 부재를 판정 불가로 접게 고친다 (서버 코드 변경, 이 계획 범위 밖이다. 고쳐도 결과는 503 이라 PRD 행 4 의 200 과 여전히 다르다). (다) 현행 유지 · 편차 기록 · 사용자 판정. 칸 부재는 PRD 착수 조건 (DDL 적용 확인 · `ath_id` NULL 0) 이 막는 상태이고, 500 은 fail-closed 라 권한 상승은 없다. 이 구간은 (다) 를 전제로 기록만 하고 멈췄다.
2. 편차 1 의 분류를 리뷰어가 받아들이는지 (fable · codex). 기각하면 소실 19 는 판정 필요로 돌아간다.

### 최종 결과 (7번째 구간)

- 게이트 본문 rc 1 (`FAILS=1`, #12 만 붉음). 커밋하지 않았다. 서브모듈 워킹트리 깨끗함 (상수 원복 sha = HEAD `0482ed9a…`). `m9c-iso-` 컨테이너 없음.
- 변경 (작업 트리 · 커밋 밖): `.claude/meta/2026-10-23-admin-rbac-m9c/replay-report.mjs` · `replay-cells.tsv` · `replay-summary.txt` · `replay-table.md` · `.claude/PRPs/plans/admin-rbac-m9c.gates.sh` (#8 · #9 · #20) · `.claude/prds/admin-rbac.prd.md` (9c 절 1 줄) · `.claude/plans/codex-findings-backlog.md` (1 행) · 이 노트.
- 다음 단계: 메인 판정 (1919 1 건, 편차 1 · 2) 이 끝나면 이 구간의 산출을 그대로 쓴다. 그 뒤 게이트 rc 0 을 확인하고 2.5.3–2.5.6 · 3.6 · 3.7 · Phase 4 · Phase 5 로 간다.

### 이어달리기 8번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external, 2026-10-19T02:21:22Z 원문 일치).
- 상태 재측정: 서버 `3418d3a0` · 어드민 `1301b664` · 사이트 `fc8872a1`, 세 서브모듈 `status --porcelain` 0줄. `docker ps -a` 에 `m9c-iso-` 컨테이너 없음. 떠 있는 재생 · 부팅 프로세스 없음. 재생 · 빌드 · 부팅은 다시 돌리지 않았다 (산출 재사용, 7번째 구간과 같은 판정).
- 게이트 본문 첫 실행: `FAILS=2` (#12 · #20). #20 은 앞 구간이 notes 에 적은 문장이 원인이다. 7번째 구간 기록의 반사실 문장에 비밀 형상 리터럴 예시 3건과 홈 경로 한 줄이 들어 있어 검사가 셌다 (`HOME_PATH_LINES=1 SECRET_LINES=1`). 측정 산출 · 사이드카 패턴은 건드리지 않고 notes 두 줄을 고쳤다 (값 없이 형상만 적음, 홈 경로는 git 디렉터리 상대 표기). 고친 뒤 #20 은 녹색이다.
- 게이트 본문 재실행 (`mccp-gatekit/m9c/gates-8.log`, git 디렉터리 아래, 커밋 아님): `M9C_GATES_DONE=0 FAILS=1`, rc 1. 실패는 #12 (1919) 하나다. 나머지 32 검사는 녹색이다 (#9 · #28 은 7번째 구간 정의 그대로 녹색).
- #12 판정 (이 구간에서 바꾸지 않았다): 실측 `COL_SUPER_STATUS=500` (`absent-boot-m9c.txt`) 이고 검사의 기대는 200 (PRD 행 4). m9b 편차 (503) 도 아니므로 계획의 편차 규칙 (「실측이 503 이면 편차로 기록하고 메인 판정」) 을 벗어난다. 이 구간은 셋 중 어느 것도 하지 않았다.
  - 기대값을 500 으로 바꾸기: 관측에 맞춘 기대 변경이다 (run-policy §4 · 계획 규칙 위반). 하지 않았다.
  - 평가기 · 신원 조회 고치기: 계획 밖 서버 변경이다 (계획 「평가기 코드는 이 계획에서 바꾸지 않는다」). 고쳐도 결과는 503 이라 PRD 행 4 의 200 과 여전히 다르다. 하지 않았다.
  - 현행 유지 · 편차 기록 · 메인 판정: 이 구간의 선택. 칸 부재는 PRD 착수 조건 (DDL 적용 확인 · `ath_id` NULL 0) 이 막는 상태이고, 500 은 fail-closed 라 권한 상승은 없다.
- 백로그: 1919 · 1921 짝 행은 PARTIAL (1936 · 1937 · 1938, 1922 원 행 불변). RESOLVED-IN 은 1923 · 1924 (계획 커밋의 「구현 전」 표기) 뿐이다. 이 구간에서 해소 행을 새로 쓰지 않았다. 해소 판정은 #12 와 편차 판정 뒤다.
- 판정 뒤로 미룬 단계: 2.5.3–2.5.6 Implement-Codex 게이트 · 3.6 · 3.7 restamp · Phase 4 · Phase 5. 게이트 rc 1 이라 진입하지 않았다. 커밋하지 않았다.

#### 남은 Task (8번째 구간 끝 기준)

1. 메인 판정 (사용자 몫 · fable · codex): #12 (1919, 칸 부재 최고관리자 500 IE9999) 의 처리 (가 · 나 · 다 는 7번째 구간 「판정 필요」 1 과 같다).
2. 메인 판정: 편차 1 (소실 19 를 「의도된 범위 거부」 로 분류, 검사 #9 를 「의도되지 않은 소실 0」 으로 정의) 의 수용 여부. 기각이면 소실 19 는 판정 필요로 돌아가고 검사 #9 는 19 로 붉어진다.
3. 1 · 2 가 풀려 게이트 본문 rc 0 이 되면: 2.5.3–2.5.6 Implement-Codex 게이트 (원장 `status --gate mccp-implement-codex --decision admin-rbac-m9c --json` 먼저 확인) · 3.6 · 3.7 restamp · Phase 4 · Phase 5. 그 전에 커밋하지 않는다.
4. 백로그 정리 (메인 판정 몫): 1919 · 1921 짝 행을 해소로 바꿀지, PARTIAL 로 둘지. 1923 · 1924 (RESOLVED-IN, 「구현 전」 표기) 의 의미 정리.
5. 관찰 (LOW, 서버 코드 변경 없음, 백로그 후보): `assertSalesAgency` 의 범위 거부가 E0607 사유 코드를 달지 않는다 (미분류 5 셀의 원인).
6. 떠 있는 `m9c-iso-` 컨테이너: 없음.

#### 최종 결과 (8번째 구간)

- 게이트 본문 rc 1 (`FAILS=1`, #12 만 붉음). 사이드카 · 평가기 · 기대 열 · 상수 · 서브모듈은 건드리지 않았다 (서브모듈 워킹트리 깨끗함, 상수 원복 sha = HEAD `0482ed9a…`). 커밋하지 않았다.
- 변경 (이 구간): notes 두 줄 (#20 거짓 양성 해소, 값 없는 형상 표기) 과 이 절 추가. 나머지 변경은 앞 구간과 같다.
- 판정 필요 (메인 판정 · 사용자 몫) 는 위 남은 Task 1 · 2 다. 두 건이 풀리기 전에는 게이트 rc 0 에 도달하지 않는다.

### 이어달리기 9번째 구간 기록 (2026-10-23)

- 지시 확인: `verify-user-directive.sh` exit 0 (type=user · userType=external, 2026-10-19T02:21:22Z 원문 일치). 게이트 상한 3 · 마지막 라운드 skip-receipt 지시가 이 구간의 기준이다.
- 이 구간의 범위: 메인 세션의 fable · codex 일치 판정 (wf_6f254a6e-7d0) 을 그대로 반영한다. 재생 산출 (296 셀) 은 다시 돌리지 않았다. 칸 부재 앱 부팅 (1919) 만 새 경로로 다시 쟀다.

#### 판정 결정 (9번째 구간 · 메인 세션 판정 반영, 「판정 필요」 절을 닫는다)

이 절은 「판정 필요」 (구간 7 · 8) 의 항목 1 (#12 · 1919) 과 항목 2 (편차 1 · 소실 19 분류 수용) 를 닫는다. 나머지 항목 (#20 패턴) 은 구간 7 에서 닫혔다.

1) 칸 `tn_admin.ath_id` 부재 (1919, 검사 #12). 정정: 500 IE9999 의 원인은 평가 질의가 아니라 가드 통과 뒤 핸들러의 레거시 조인이다. 관리자 목록 · 건수 · 상세 질의 (`admin.repository.ts`, `leftJoin(tnAuth, eq(tnAuth.athId, tnAdmin.athId))`) 가 칸을 읽고, 평가기 (`evaluate.ts`) 는 칸 부재에서도 가드를 통과시킨다.
   - 가드 통과 실측: 칸 부재 영역 1 임시 ON 부팅에서 `GET /admin/authority/1` (권한 rbac.manage, 본문은 `tn_auth` · `tn_menu_auth_map` 만 읽고 `tn_admin` 을 읽지 않는다) 최고관리자 200, 내부 관리자 403 E0607. 가드 수준 (내부 관리자의 403 E0607) 이 같은 경로에서 돈다.
   - 스위치 OFF 대조 (같은 칸 부재 DB, 상수 HEAD 빌드, `absent-boot.sh off` 가 dist 를 HEAD 로 다시 빌드한 뒤 잰다): `GET /admin/admin` 최고관리자 500 IE9999 · 내부 관리자 500 IE9999 · `GET /admin/authority/1` 최고관리자 200 · 내부 관리자 200 (가드 없음). 그러므로 ON 의 `/admin/admin` 최고관리자 500 IE9999 는 기존 성질이고 회귀가 아니다. ON 의 내부 관리자 403 E0607 은 가드가 한 것이다 (OFF 에서는 같은 요청이 핸들러까지 가서 500).
   - 행 4 기대 처리: 칸 부재 최고관리자 200 기대는 `/admin/admin` 에서 뺐고 (실측 500), 가드 경로 `GET /admin/authority/1` 200 으로 옮겼다. 칸 부재 내부 관리자 403 E0607 과 표 부재 셋 (최고관리자 `/admin/admin` 200 · 내부 관리자 403 E0607) 은 유지했다. PRD 행 4 의 문언은 바꾸지 않았다.
   - 검사 #12 의 새 기대 (`absent-boot-m9c.txt` · `absent-boot-off-m9c.txt`): 칸 부재 `COL_SUPER_AUTH_STATUS=200`, `COL_INTERNAL_STATUS=403` · `COL_INTERNAL_CODE=E0607`, `COL_INTERNAL_AUTH_STATUS=403` · `COL_INTERNAL_AUTH_CODE=E0607`, 표 부재 셋, 그리고 칸 부재 `/admin/admin` 최고관리자 상태 · 코드가 ON 과 OFF 에서 같다 (500 IE9999). OFF 파일은 `OFF_CONST_SHA256 = HEAD_SHA256` 이어야 한다.
   - 작업 중 정정 (기록): OFF 대조 키를 처음에는 「OFF 내부 관리자 403 E0607」 로 써 두었는데, 측정 전에 쓴 가정이었고 실측은 500 IE9999 다. 검사 본문에서 그 키를 뺐다. 기대를 실측에 맞춘 것이 아니라, 실측이 보여 준 OFF 의 성질 (가드가 없으면 핸들러까지 간다) 을 기대로 쓴 것이다. 같은 이유로 `COL_SUPER_CODE` 도 ON·OFF 같음으로 묶었다.
   - 측정 산출: `absent-boot-m9c.txt` (영역 1 ON, `MODE=app-boot`, 상태 2 × 요청 네 개), `absent-boot-off-m9c.txt` (OFF, `MODE=app-boot-off`, 칸 부재 1 상태), `on-build-area1-m9c.txt` (CHANGED 1줄 · RESTORED_SHA256 = HEAD_SHA256 = 0482ed9a…). 측정 뒤 OFF 로 재빌드했다 (cleanup).
   - 백로그: 1919 는 RESOLVED-IN 행 (표 부재 실측 · 칸 부재의 가드 경로 실측), 1936 은 본문을 고치지 않고 「평가 질의」 → 「관리자 목록 질의 admin.repository.ts」 정정 행을 append 했다, 레거시 조인 500 은 LOW 행 (운영 칸 보존이 선행 조건). PRD 결정 로그 9c 절에 같은 내용과 정정 줄을 append 했다. 기존 줄은 고치지 않았다.

2) 소실 19 의 분류 (편차 1) 와 검사 #9 의 정의. 판정: 수용하되 근거를 서버 응답 사유에서 떼어 독립 대조로 다시 세었다.
   - 새 검사 #9 는 `owner-check-m9c.mjs` 다 (이 구간에서 추가, 이행표 재생 산출과 같은 디렉터리). 입력은 셀 파일의 기대 열 · ON 열, 신원 파일의 kind · tenant · claims, 재생 하네스의 대상 기업 표 (A = 기업 9001 · 조직 SYNORGA1, B = 기업 9002 · 조직 SYNORGA2), 합성 시드의 `tn_agency` (agc_id → sag_id) 와 `tn_agency_organization` (org_id → agc_id, org_path) 이다. 셀의 원인 열과 서버 사유 (E0607 · scope) 는 읽지 않는다.
   - 소유 판정: 영업은 `claims.sagId` = 대상 기업의 시드 sag_id, 기업은 `claims.agcId` = 대상 기업, 조직 관리자는 기업 번호 일치 + `claims.orgPath` 가 대상 조직 경로의 접두사, 관리자는 기업 범위가 없으므로 소유자로 본다 (보수적).
   - 소실 셀 (기대 2xx 이고 ON 이 2xx 가 아님) 을 두 갈래로 센다. 소유자 신원의 소실은 `UNINTENDED` (의도되지 않은 소실), 소유자가 아닌 신원의 소실은 `INTENDED_SCOPE` (의도된 범위 거부) 이고, 이 갈래는 같은 (행, 대상 기업) 에서 범위 소유자 신원의 ON 이 2xx 인지 확인한다 (`OWNER_NO_2XX` = 0 이어야 한다).
   - 실측 (게이트 시점): `LOSS=19 UNINTENDED=0 INTENDED_SCOPE=19 OWNER_NO_2XX=0 CELLS=296`, 신원별 `sales-B=19`. 소실 19 전부 영업 `sales-B` (sagId 9102, 소유 영업 9101 과 다름) 이고, 같은 행의 `sales-A` 는 전부 ON 2xx 다. 기대 열은 OFF 현행 상태 그대로다 (바꾸지 않았다).
   - 음성 (실측): `sales-B` 의 tenant 를 B 로 바꾼 신원 사본 (그 영업이 기업 B 의 소유자가 된다) 으로 `owner-check-m9c.mjs` 를 돌리면 `LOSS=19 UNINTENDED=19 INTENDED_SCOPE=0`, 종료코드 1 이다. 게이트 함수 `c09` 를 사본 디렉터리 (git 디렉터리 아래 `mccp-gatekit/m9c/neg-9/meta`) 로 돌려도 `NEG9_RC=1` 로 붉다.
   - 경계 (기록): 이 대조는 신원 파일 · 시드 · 대상 기업 표의 세 입력이 맞다는 전제에서 돈다 (그 셋이 틀리면 판정도 틀린다). 소유 판정의 조직 수준은 조직 관리자 신원에만 있고, 이 재생의 소실 셀에는 조직 관리자 소실이 없다 (미검증 면: 조직 경로 접두사 판정은 이 셀에서 발동하지 않았다).
   - 리뷰 판정 대상: 「소유자가 아닌 범위 거부를 소실에서 뺀다」 는 분류의 수용 여부 (fable · codex 최종 리뷰). 기각되면 소실 19 는 판정 필요로 돌아간다.

3) 측정 중 사소 정리 (측정 도구만): `absent-boot.sh` 는 인자 `on` (기본) · `off` 를 받게 고쳤고, 상태마다 네 요청을 낸다 (`/admin/admin` 두 신원 + `/admin/authority/1` 두 신원). `replay-*` 는 건드리지 않았다. 서버 소스 · 상수 · 기대 열 · 신원은 바꾸지 않았다.

#### 게이트 결과 (이 구간 끝)

- `bash .claude/PRPs/plans/admin-rbac-m9c.gates.sh` → 32 PASS, `M9C_GATES_DONE=1 FAILS=0`, rc 0 (로그: git 디렉터리 아래 `mccp-gatekit/m9c/gates-9a.log`).
- 음성 (실측, 사본): 검사 #9 는 `UNINTENDED=19` rc 1 (위 2)). 검사 #12 는 칸 부재 가드 경로를 503 으로 적은 사본에서 `ABSENT_KEY_MISS=COL_SUPER_AUTH_STATUS=200` rc 1 (git 디렉터리 아래 `mccp-gatekit/m9c/neg-12/meta`).
- 서브모듈 워킹트리: 세 저장소 모두 `status --porcelain` 0줄 (상수 원복 sha = HEAD `0482ed9a…`, 서버 HEAD `3418d3a0`). 떠 있는 `m9c-iso-` 컨테이너 없음 (측정 스택은 매번 자기 것만 내렸다).
- 포맷 출처 검사 (정책 §5-7): `git show HEAD:<f> | prettier | cmp` 는 노트 · 백로그 · PRD 세 파일 모두 「다르다」. 이 cmp 는 내용이 바뀐 파일에서는 원리상 같아질 수 없다 (내용 변경이 곧 차이다). 그래서 공백 · 서식 전용 변경 여부를 `git diff -w --stat` 으로 봤다: 전체 238 추가 · 20 삭제, `-w` 와 같다 (공백만 바뀐 줄 0). 모든 변경은 이 구간 · 앞 구간의 편집 기록과 맞는다 (사이드카 검사 #9 · #12 · 노트 · 백로그 행 · PRD 결정 로그 줄). 출처 미상 변경은 없다고 판단했다. `.gates.sh` 는 prettier 지원 대상이 아니다.
- 포맷 명령 (`prettier --write`) 은 이 구간에서 돌리지 않았다.


#### 구현 리뷰 (Implement-Codex) 라운드와 흡수 (9번째 구간)

- 리뷰 원장: `mccp-implement-codex` · 결정 `admin-rbac-m9c` (seal cap 3, enforce). 리뷰 입력은 커밋하지 않은 작업 트리 (`adversarial-review`, working-tree 모드). 실행 래퍼는 git 디렉터리 아래 `mccp/tmp/run-codex-r{1,2}.sh`.
- 2.5.5b impeccable 설계 게이트: 검출 `no-signal` (silent skip). 작업 트리에 화면 파일이 없다 (서버 · 관리자 · 사이트 워킹트리 깨끗함). 설계 검토 루프는 돌지 않았다.
- R1 (`needs-attention`, HIGH 1): 전역 목록 행 (I1 `/admin/agencies/all`) 은 대상 기업이 없는데 `owner-check` 가 소유자 면제를 주어, sales-B 의 I1 ON 을 403 으로 바꾼 사본이 `UNINTENDED=0` 으로 통과했다 (리뷰어 실측 `LOSS=20 UNINTENDED=0`). 흡수 (ACCEPT_NOW): 의도된 범위 거부는 다음 셋을 모두 만족할 때만 센다. (a) 그 행의 하네스 요청 규격 (`replay-m9c.mjs` 의 `ID 핸들러` 줄) 이 대상 기업을 고른다 (`t.` 또는 `tenant` 참조), (b) ON 상태가 정확히 403, (c) 신원이 시드상 소유자가 아니다. 나머지 소실은 전부 의도되지 않은 소실이다. 같은 리뷰 지적의 부수 정리: 소실 셀이 5xx 나 기타 상태면 범위 거부로 세지 않는다.
- R2 (`needs-attention`, HIGH 1 · 부분 해소): 상태 조건 `^4` 가 400 · 401 · 404 · 429 를 범위 거부로 세었다. 흡수: 상태가 정확히 403 일 때만 (`status !== '403'` → 의도되지 않은 소실). 같은 R2 의 두 번째 지적 (AO15 처럼 파일 경로가 든 행 키의 핸들러 매핑) 은 마지막 `/` 뒤를 핸들러로 읽도록 고쳤다 (AO15 행은 positive 실행이 실패 없이 통과한다).
- 반사실 (실측, 사본은 git 디렉터리 아래 `mccp-gatekit/m9c/neg-9`, `neg-9b`):
  - 원본: `LOSS=19 UNINTENDED=0 INTENDED_SCOPE=19 OWNER_NO_2XX=0`, rc 0.
  - I1 행 sales-B 의 ON 을 403/- 로 바꾼 사본: `LOSS=20 UNINTENDED=1`, rc 1 (R1 의 재현 조건).
  - I2 getList sales-B 의 ON 을 400 · 401 · 404 · 429 · 500 으로 바꾼 사본 각각: `UNINTENDED=1`, rc 1 (R2 의 재현 조건, 다섯 개 모두).
  - 신원 사본 (sales-B 의 tenant 를 B 로): `UNINTENDED=19`, rc 1 (앞 기록과 같다).
- 게이트 본문 재실행 (흡수 뒤): `M9C_GATES_DONE=1 FAILS=0`, 32 PASS, rc 0 (로그 `mccp-gatekit/m9c/gates-9c.log`).
- 판정 기록 정정 (앞 절 2): 소실 19 의 분류 근거 중 「같은 행 소유자의 ON 2xx」 는 여전히 참이다. 다만 분류 자체는 위 세 조건을 모두 만족하는 셀에만 적용된다. 앞 절의 「소유자가 아니면 의도된 범위 거부」 문장은 이 셋의 조건을 함께 읽는다.

## Codex Implementation Review

<!-- 2.5.4 판 (구현 리뷰 절은 계획서가 아니라 노트에 둔다: 계획서에 붙이면 plan_hash 가 어긋난다). -->

- 호출: `node <plugin>/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, 작업 트리 모드, 커밋하지 않은 상태에서 돌렸다)
- 라운드 수: 3 (원장 `rounds_so_far` = 3, cap 3). R1 `needs-attention` · HIGH 1, R2 `needs-attention` · HIGH 1 (부분 해소), R3 `approve` (최종 라운드).
- 합치 결론: 마지막 라운드가 approve 다. 최종 판정 (`CODEX_VERDICT`) = `converged` (구조화 판정 `source=structured`).
- YAGNI Triage:
  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | R1 F1: 전역 목록 행 (I1) 의 소유자 면제가 소실을 가린다 | HIGH | ACCEPT_NOW (흡수) | 대상 기업이 없는 행의 소실은 범위 거부로 설명되지 않는다. 행마다 「대상 행 · 403 · 비소유자」 세 조건을 모두 요구하도록 고쳤다. 반사실: I1 sales-B 403 사본 UNINTENDED=1 |
  | R2 F1: 상태 `^4` 가 400 · 401 · 404 · 429 를 범위 거부로 센다 | HIGH | ACCEPT_NOW (흡수) | 범위 거부는 정확히 403 일 때만. 반사실: I2 getList sales-B 400 · 401 · 404 · 429 · 500 사본 각각 UNINTENDED=1 |
  | R2 F2: 파일 경로가 든 행 키 (AO15) 의 핸들러 매핑 | MEDIUM | ACCEPT_NOW (흡수) | 마지막 `/` 뒤를 핸들러로 읽는다 |
  | R3: 최종 확인 | — | approve | 「정확한 403 조건 · targeted() · 변이 228 · 전역 목록 7 · 소유자 403 변이 198 (AO15 포함) 모두 거부」 |
  | 관찰: `assertSalesAgency` 의 범위 거부가 E0607 사유 코드를 달지 않는다 (미분류 5 셀의 원인) | LOW | DEFER_TO_BACKLOG | 서버 코드 변경 없음. 백로그 LOW 행 1개 (`.claude/plans/codex-findings-backlog.md`) |
- Deferred to backlog: 1 → `.claude/plans/codex-findings-backlog.md` (LOW 행, 9번째 구간).
- Open Questions: 「편차 1 (소실 19 를 의도된 범위 거부로 세는 분류) 의 수용」 은 메인 · fable · codex 최종 판정 대상이다. CRITICAL 아님, 게이트 차단 아님 (차단 항목 없음). 신원 · 시드 · 대상 기업 표 세 입력의 정확성은 전제다.
- 보안 리뷰어 (`security-reviewer`): 호출하지 않았다. 이 구간의 변경은 제품 코드 (인증 · 암호 · 입력 검증) 가 아니라 측정 도구 · 검사 사이드카 · 문서다. 측정 하네스가 메모리에서 JWT 를 서명하지만 비밀을 파일에 쓰지 않는다 (검사 #20 · 녹색). 자동 대체 사유는 없다 (`SECURITY_SKIPPED_REASON` 미설정).
- Codex 세션 참조: 래퍼 실행 기록 `mccp/tmp/codex-r{1,2,3}.stdout` (git 디렉터리 아래, 커밋 아님).
- 임풀시브 설계 게이트: `no-signal` silent skip (화면 파일 없음). 3.6 · 3.7 는 트리거 · 캡처 아티팩트가 없어 skip.

### 최종 결과 (9번째 구간, 마무리 단계 · Phase 4 · Phase 5)

- 검증 게이트 (사이드카): `bash .claude/PRPs/plans/admin-rbac-m9c.gates.sh` → 32 PASS, `M9C_GATES_DONE=1 FAILS=0`, rc 0 (로그 `mccp-gatekit/m9c/gates-9e.log`). 이 구간에서 판정 필요 항목 (#12 · 편차 1) 은 위 「판정 결정」 절로 닫았다. 게이트 본문은 이 구간에서 다음만 고쳤다: 검사 #9 (독립 대조, 리뷰 R1 · R2 흡수), 검사 #12 (새 기대 · OFF 대조), 검사 #6 허용 목록에 이 게이트의 영수증 한 파일 (`.claude/receipts/mccp-implement-codex/admin-rbac-m9c.json`, 추적되는 다른 영수증과 같은 종류) 추가, 검사 #12 헤더의 `# 기대:` 표지 복원 (계획 lint PL2).
- 계획 lint: `node .claude/templates/plan-kit/lint-plan.mjs .claude/PRPs/plans/admin-rbac-m9c.plan.md` → rc 0 (이 구간에서 사이드카 헤더 · 선언을 고친 뒤 통과; 전 구간 실패는 사이드카 헤더 표지 때문이었다).
- 구현 리뷰 (Implement-Codex): 3 라운드, 마지막 라운드 approve, 원장 `rounds_so_far` = 3 (cap 3). 최종 `CODEX_VERDICT` = `converged`. 두 HIGH 는 흡수 · 반사실 재현 (위 「구현 리뷰 라운드와 흡수」).
- 영수증: `write --gate mccp-implement-codex --decision admin-rbac-m9c --plan .claude/PRPs/plans/admin-rbac-m9c.plan.md --codex-verdict converged --impeccable-silent-skip --impeccable-silent-skip-reason no-signal --quiet` 는 환경 변수 없이 rc 0 으로 통과했다 (거부가 없어 사유 기록 쓰기는 하지 않았다). 파일: `.claude/receipts/mccp-implement-codex/admin-rbac-m9c.json` (untracked).
- 검증 (chain) 판정 미해결: `validate --command mccp:prp-implement --decision admin-rbac-m9c --plan …` → rc 2, `missing: mccp-plan-codex (no receipt written)`. 사용자 지시에 따라 plan 게이트는 skip-receipt 이고 plan 영수증은 없다. `MCCP_SKIP_RECEIPT=1` 을 주면 `preflight` 는 `bypassed: true` (로깅) 로 통과하지만 `validate` 는 같은 env 에서도 rc 2 다 (v1.33.6 의 validate 는 이 env 를 보지 않는다). 가짜 plan 영수증을 쓰지 않았다. 이 chain 판정은 메인 판정 대상이다.
- 3.6 (design finish) · 3.7 (design grounding): 트리거 없음 (`SIGNAL=0`) · 캡처 아티팩트 없음 → 둘 다 skip. restamp 없음.
- 보안 리뷰어: 호출하지 않음 (위 「구현 리뷰」 절 이유). 
- 서버 · 관리자 · 사이트 워킹트리: 세 저장소 모두 `status --porcelain` 0줄, HEAD 는 착수 sha (`3418d3a0` · `1301b664` · `fc8872a1`). 상수 파일 원복 sha = HEAD (`0482ed9a…`).
- 떠 있는 `m9c-iso-` 컨테이너: 없음 (`docker ps -a` 확인).
- 커밋하지 않음. 커밋 시 스테이징 대상: `.claude/PRPs/plans/admin-rbac-m9c.gates.sh` · `.claude/notes/admin-rbac-m9c.md` · `.claude/plans/codex-findings-backlog.md` · `.claude/prds/admin-rbac.prd.md` · `.claude/meta/2026-10-23-admin-rbac-m9c/` · `.claude/receipts/mccp-implement-codex/admin-rbac-m9c.json`. 커밋 전 `git diff --cached` 에서 홈 디렉터리 접두 문자열을 센 값이 0 이어야 한다 (검사 #20 과 같은 패턴으로 센다) (노트 · 측정 산출에 홈 경로 없음 · 검사 #20 녹색).
- 재생 (296 셀) · 칸 부재 측정은 산출 재사용 (재생 자체는 다시 돌리지 않았다). 칸 부재 측정은 이 구간에서 새 경로로 다시 잰 것이다 (ON 1회, OFF 1회).
- 미검증 (사용자 몫): 복제 환경 1회 재생 (운영 쪽 완료 조건), 판단 표 실내용, 소실 19 분류의 최종 수용 (메인 · fable · codex 판정), plan 영수증 chain 판정 (위).
- 이 구간의 편차 (기록): (a) 세션 시작 시 implement 원장 시드가 빈 인수 때문에 `admin-rbac` 로 잡혀 한 번 잘못 시드했다 → `admin-rbac-m9c` 로 다시 시드했고 원장 키는 `admin-rbac-m9c` 다. (b) 격리 측정 OFF 대조의 내부 관리자 기대를 측정 전에 403 으로 썼다가 실측 (500 IE9999) 으로 고쳤다 (위 판정 절 1). (c) 검사 #9 는 계획 본문의 「기대 2xx 셀 ON 거부 = 0」 이 아니라 「의도되지 않은 소실 = 0 (독립 대조)」 이다 (편차 1 · 2 와 같다). (d) 백그라운드 `cat` 이 stdin 에서 멈춘 작업 하나를 `TaskStop` 으로 종료했다 (파일 쓰기 없음).

## 코드 리뷰

코드 리뷰 라운드 1 (보완 계획 admin-rbac-m9c, 커밋 전 작업 트리). 출처 세 lane (code-review · security · typescript) 의 지적을 합쳤다 (중복 3건은 한 행으로 묶었다). 판정: 흡수 15 · 기각 6 · 백로그 이월 2 · 기존 백로그 HIGH 1 (미해소). 흡수한 건은 수정 뒤 검사를 돌려 확인했고, 반사실 실행 결과는 증거 열에 적었다.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| 1 | code-review · security · typescript | MEDIUM | 중단 경로가 이전 실행의 백업으로 상수 파일을 덮는다 (3건 중복) | 흡수 | `on-area-build.sh` 에서 실행 시작 때 백업을 지우고 이번 실행이 백업을 만든 뒤에만 복원한다. 미커밋 편집 probe 실측: 수정 전 줄 소실 (count 0, rc 1) · 수정 뒤 보존 (count 1, rc 1) | 4 |
| 1 | security · typescript | MEDIUM | OFF 재빌드 실패가 rc 0 으로 끝나고, 재생이 dist 가 라벨대로 빌드됐는지 보지 않는다 (2건 중복) | 흡수 | 재빌드 · 원복 실패는 rc 1 로 올린다. `_m9c-lib.sh` 의 `m9c_check_dist` 를 `replay-m9c.sh` (off · off-rerun · on) 와 `absent-boot.sh` 에 넣었다. 실측 4 사례: 실제 OFF dist 통과 · ON 집합 확인이 OFF dist 에서 실패 · 한 상수만 true 인 사본이 OFF 확인에서 실패 · 네 상수 true 사본이 ON 확인 통과 · OFF 확인에서 실패 | 4 |
| 1 | code-review | MEDIUM | OFF 원자료에 서버 트리 출처가 없고 검사 #32 는 요약 · ON 출처 값만 본다 | 백로그 이월 | 백로그 2026-10-23 MEDIUM 행 (첫째). 원자료 헤더에 트리를 찍으려면 OFF · OFF 재실행 · ON 세 측정 모두 재실행이 필요하다 | 6 |
| 1 | code-review | MEDIUM | 검사 #6 허용 목록이 서버 워킹트리만 보고 관리자 · 사이트 변경을 못 본다 | 흡수 | `c06` 에 관리자 · 사이트 루프 추가 (기준 커밋 대비 커밋 변경 + 미추적 파일, 허용 0). 반사실 P2: 관리자 워킹트리에 빈 파일을 두면 수정 뒤 `OUTSIDE` 에 나와 FAIL (수정 전에는 관리자 저장소를 보지 않아 PASS) | 6 |
| 1 | security | LOW | 검사 #6 의 `.claude/meta/` · `.claude/PRPs/plans/` 전체 접두가 증거 입력 (m6c 이행표 · would-deny JSON) 을 덮는다 | 흡수 | 허용 접두를 이 계획의 측정 디렉터리와 계획 · 사이드카 파일로 좁혔다. 반사실 P1: m6c 이행표에 공백 한 칸을 넣으면 수정 뒤 `OUTSIDE` 에 나와 FAIL (수정 전에는 접두가 덮어 PASS) | 6 |
| 1 | code-review | MEDIUM | 원인 열을 어떤 검사도 소실 판정에 쓰지 않는다 (sales-B I2 getList 의 원인을 「적재 누락」 으로 바꾼 사본이 #8 · #9 통과) | 흡수 | 검사 #10 에 원인 열 대조 추가: 기대 2xx 소실 중 원인이 「의도된 범위 거부」 가 아닌 셀 = 0, 요약 `UNINTENDED` 와 같음. 반사실 P3: 같은 사본에서 #8 · #9 는 PASS, #10 은 FAIL (`UNINTENDED_CELLS=1/0`) | 2 |
| 1 | typescript | HIGH | 검사 #9 재작성 뒤 「적재 누락 · 매핑 누락 = 0」 을 강제하는 검사가 없다 | 흡수 | 위 MEDIUM 행과 같은 변경 (검사 #10). 원인 열 기준 미분류 소실을 세고 요약 값과 맞춘다. 반사실 P3 와 같은 결과 | 2 |
| 1 | code-review | HIGH | 목록 축소 계수가 `count` · `total` 을 배열 길이보다 먼저 써 목록 0행 축소를 못 본다 (백로그 1931 기록됨) | 미흡수 (기존 백로그 행 유지) | 원자료에 응답 본문 · 목록 길이가 없어 재측정이 필요하다 (측정 하네스 변경 + 재생 약 30분). 백로그 1931 PARTIAL 행이 이미 「잔여 → 다음 계획 반영 대상」 으로 있어 다시 쓰지 않았다 | 6 |
| 1 | code-review | LOW | 신원 분모가 입력 JSON 에 기댄다 (백로그 1932 기록됨) | 기각 | 기존 백로그 1932 행. 입력 파일 변조를 가정하는 지적이다 | 6 |
| 1 | code-review | LOW | 테넌트 B 의 쓰기 대상이 대상 기업 자신 (A 와 B 의 쓰기 셀 요청 형태가 다르다) | 기각 | 측정 설계 변경은 재생이 필요하다. 미검증 면으로 아래 목록에 적었다 | 6 |
| 1 | code-review | LOW | 교차 기업 탐침이 영업 신원 하나뿐이라 기업 · 조직 관리자의 교차 거부는 측정되지 않았다 | 부분 흡수 | 측정 확장은 기각. PRD 2026-10-23 9c 절에 「미검증」 을 명시하는 정정 줄을 추가했다 | 2 |
| 1 | code-review | LOW | 계획 Acceptance 272행 (최고관리자 200) · 261행 (원인 「세 가지」) 이 구현과 어긋난다 | 기각 | 계획서는 동결이라 고치지 않는다 (할 일 4). 편차는 위 「편차」 절의 칸 부재 500 · 원인 규칙 항목이 가리킨다 | 2 |
| 1 | code-review | LOW | 구현 리뷰 영수증의 findings · resolution 이 빈 배열이고 노트의 흡수 · 이월 기록과 맞지 않는다 | 기각 | 영수증은 mccp 가 쓴 감사 기록이라 손으로 고치지 않는다. 차이는 아래 미검증 목록에 적었다 | 2 |
| 1 | code-review | LOW | 검사 #20 이 백로그 · PRD 를 빼고 돈다 | 흡수 | #20 이 백로그 · PRD 의 추가 줄 (`git diff HEAD` 의 `+` 줄) 을 본다. 기준 커밋의 기존 호스트 경로 줄 (백로그 · PRD 7건) 은 이 검사 밖이다. 반사실 P4: 백로그에 호스트 경로 추가 줄을 넣으면 수정 뒤 `HOME_PATH_LINES=1` 로 FAIL (수정 전에는 백로그를 보지 않아 PASS) | 6 |
| 1 | code-review | LOW | 검사 #13 은 메뉴 스위치 0 을 부재 검사로만 본다 | 기각 | 긍정 기록을 손으로 쓰면 출처 없는 줄이 된다. `on-area-build.sh` 의 변경 이름 목록 (NAMES) 에 메뉴 스위치가 없어 측정 빌드가 메뉴를 건드리지 않는다 | 3 |
| 1 | code-review | LOW | 노트 「편차」 첫 항목이 현재 녹색인 검사 #8 · #28 을 붉다고 쓴다 | 흡수 | 중간 상태 문장에 정정을 덧붙였다 (미분류 5 셀의 재분류 경로 포함) | 2 |
| 1 | code-review | LOW | `absent-boot.sh` 가 측정 전에 dist 빌드 상태를 확인하지 않는다 | 흡수 | `m9c_check_dist` 를 on (영역 1 상수 true) 과 off (전부 false) 에 넣었다 (위 MEDIUM 행의 실측 4 사례가 함수 검증) | 3 |
| 1 | security | MEDIUM | 수평 상승 (타 기업 · 형제 조직 대상) 이 측정되지 않았는데 「권한 상승 0」 이 그 뜻으로 읽힌다 | 부분 흡수 + 백로그 이월 | PRD 정정 줄로 미측정을 명시했다. 교차 기업 · 형제 조직 셀 추가와 재생은 백로그 2026-10-23 MEDIUM 둘째 행 | 7 |
| 1 | security | LOW | PRD 2026-10-23 9c 절의 「허용 방향 (권한 상승) 은 0 건」 문언이 뒤집혔다 (소실 19 과 같은 줄처럼 읽힌다) | 흡수 | 기존 줄은 두고 정정 줄을 추가했다 (권한 상승 = 기대 403 이 ON 에서 2xx) | 2 |
| 1 | security | LOW | 격리 DB 비밀번호가 `$RANDOM` 이라 실행마다 무작위가 아니고 예측 가능성이 크다. 비밀이 argv 로 전달된다 | 부분 흡수 | 비밀번호는 `openssl rand -hex 16` 으로 바꿨다. argv 전달은 기각 (루프백 전용 · 일회성 · 합성 데이터이고 커밋 산출에 값이 없다 — 검사 #20 실측 0) | 1 |
| 1 | typescript | LOW | 재생 보고서가 AO 행의 종류 칸을 `undefined` 로 쓴다 (15행) | 흡수 | `replay-report.mjs` 에서 `r[6] ?? '-'`. 원자료에서 다시 조립: 셀 파일 · 요약은 바이트 동일, 결과 표는 AO 15행의 종류 칸만 바뀌었다 (`undefined` 15 → 0) | 1 |
| 1 | typescript | LOW | 재생 하네스가 셀 결과를 끝까지 모았다가 쓰고, 복원 · 실행에 timeout 이 없다 | 기각 | 측정 중단은 재생 재실행을 뜻한다. 복원 실패로 멈추는 것 자체는 옳다. 출력 방식 변경은 재생 없이는 검증할 수 없다. 다음 측정 설계에서 다룬다 | 4 |
| 1 | typescript | LOW | 엑셀 항목 수가 첫 시트만 센다 (계획 Task 2 는 시트별 합산) | 흡수 | `replay-m9c.mjs` 의 엑셀 계수를 시트 합산으로 바꿨다. 반사실 (함수 본문을 옮긴 사본, 두 시트 엑셀): 옛 2 → 새 5 · 한 시트 2 · 2 동일 · 헤더 전용 0 · 0 동일 | 6 |
| 1 | typescript | LOW | 노트 「구현 뒤 실행 결과」 의 원인 집계 줄이 중간 값 (미분류 5) 을 최종처럼 싣는다 | 흡수 | 원인 집계 줄에 정정 항목을 추가했다 (최종 `CAUSE_의도된 범위 거부=19` · `UNCLASSIFIED=0`) | 2 |

### 라운드 1 검증

- 게이트 사이드카: `bash .claude/PRPs/plans/admin-rbac-m9c.gates.sh` → 32 PASS, `M9C_GATES_DONE=1 FAILS=0`, rc 0 (작업 트리 기준).
- 반사실 P1 · P2 · P3 · P4 는 측정 파일이나 저장소 파일을 한 줄 바꾼 뒤 해당 검사가 FAIL 하는지 보고, 원래 내용으로 되돌리고 `cmp` 로 확인했다. 원복 뒤 게이트는 32 PASS 다.
- 측정 스크립트 중 `on-area-build.sh` 의 복원 경로는 실제 실행으로 확인했다 (위 MEDIUM 행). `absent-boot.sh` 와 `replay-m9c.sh` 의 격리 실행은 이번 라운드에 다시 돌리지 않았다. 측정 산출은 재사용한다. 가드 `m9c_check_dist` 는 실제 OFF dist 와 변형 사본으로 확인했다.
- 서버 코드 · 서브모듈은 바꾸지 않았다 (세 저장소 워킹트리 깨끗함, HEAD 는 착수 sha). 그래서 jest · tsc 는 다시 돌리지 않았다. 바꾼 것은 사이드카 · 측정 스크립트 · 측정 산출 · 문서이고 `bash -n` · `node --check` 로 구문을 확인했다.
- 다음 재측정 때 새 가드가 실제 격리 실행에서 처음으로 돈다. 그 실행에서 `replay-m9c.sh off` 가 OFF dist 를 통과하는지가 (미검증) 이다.

### 미검증 (이번 라운드에서 드러내고 기록만 한 것)

- 교차 기업 · 형제 조직 대상 요청 (수평 상승) 과 쓰기 셀 — 측정 셀에 없다 (백로그 MEDIUM 둘째 행).
- 기업 · 조직 관리자의 교차 기업 거부 — 측정 신원이 영업 하나뿐이다.
- 테넌트 B 의 쓰기 대상이 대상 기업 자신이라 A 와 B 의 쓰기 측정이 대칭이 아니다.
- OFF 원자료의 서버 트리 출처 (백로그 MEDIUM 첫째 행).
- 구현 리뷰 영수증 (`.claude/receipts/mccp-implement-codex/admin-rbac-m9c.json`) 의 findings 와 resolution 이 빈 배열이다. 이 노트의 구현 리뷰 흡수 · 이월 기록은 영수증에 실리지 않았다. 영수증은 mccp 가 쓴 감사 기록이라 손대지 않았다.
- 계획서 Acceptance 의 문언 (272행 · 261행) 은 동결 상태로 두었다. 편차는 「편차」 절에 있다.
- 목록 항목 수의 응답 본문 기반 검사 (백로그 1931) 는 이번에 흡수하지 않았다.

## 최종 리뷰

보완 계획 admin-rbac-m9c 의 최종 리뷰 (fable · codex). 라운드 1 의 흡수분은 커밋 `04a4013` (`최종 리뷰 R1 을 반영한다`) 에 실려 있다. R1 의 지적 목록과 판정은 이 절에 옮겨 적지 않았다 (아래 미검증 1번).

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| 2 | fable | MEDIUM | 교차 기업 거부 (영업 B → 기업 A) 를 어떤 검사도 요구하지 않는다. AO10 assignOrg 의 영업 B ON 을 200 으로 바꿔도 전 게이트가 녹색이다 (리뷰어 실측 rc 0) | 흡수 | `owner-check-m9c.mjs` 에 `NONOWNER_2XX` (대상 행 × 관리자 아닌 비소유 신원 × ON 2xx) 를 더해 0 을 요구한다 (검사 #9 의 rc 에 포함). 현행 셀: `LOSS=19 UNINTENDED=0 INTENDED_SCOPE=19 OWNER_NO_2XX=0 NONOWNER_2XX=0` rc 0. 반사실 (사본 셀 파일): AO10 · AO11 · AO12 · AO13 · AO14 의 영업 B 를 ON 200/- 로 바꾸면 각각 `NONOWNER_2XX=1` rc 1. 전역 목록 I1 사본은 대상 행이 아니라서 rc 0 으로 남는다 (의도) | 2 |
| 2 | fable | LOW | Task 7 의 itspec 격리 실행이 이행되지 않았고 「미검증」 에도 없다 (`jest-m9c.json` 의 41 파일이 전부 `.spec.ts`, `.itspec.ts` 0) | 흡수 (미검증 기록 · 재사용 근거) | 아래 「### 미검증 (최종 리뷰 R2)」 1번. m9b 구현 커밋 `8fe118e9` · `3418d3a0` 위에서 격리 itspec 3종 녹색 (m9b 노트 172행). 서버 HEAD 는 `3418d3a0` 이고 reflog 상 그 뒤 HEAD 가 움직이지 않았다 | 2 |

- 판정 메모: MEDIUM 의 흡수는 리뷰가 요구한 최소 수정 (계수 추가 · 0 요구) 이다. 「수평 상승 (타 기업 · 형제 조직 대상 셀) 이 측정 셀에 없다」 는 백로그 2026-10-23 MEDIUM 둘째 행으로 남아 있다. 이 계수는 측정 셀에 있는 대상 행만 본다.
- 검사 #9 의 기대 문구 (`gates.sh` 의 c09 주석) 와 README 행을 `NONOWNER_2XX` 에 맞췄다. 검사 본문은 rc 만 보므로 바뀌지 않았다.

### 미검증 (최종 리뷰 R2)

1. itspec 격리 실행 (계획 Task 7, 195행 「itspec 격리 시험은 Task 3 의 m9c-iso- 격리 MySQL 을 쓴다」): 이 구간에서 돌리지 않았다. 게이트 #24 는 spec 11 개만 요구하므로 이 누락을 잡지 못한다. 재사용 근거: m9b 가 같은 서버 HEAD (`3418d3a0`) 의 구현 커밋 위에서 `judge-cancel` · `enforce-cross-tenant` · `evaluate-absent` 을 격리 컨테이너에서 녹색으로 기록했다 (m9b 노트 172행). m9c 구간에서 서버 워킹트리는 바뀌지 않았다 (`status --porcelain` 0줄).
2. R1 지적 목록과 판정 행: 이 절에 옮겨 적지 않았다. R1 흡수의 증거는 게이트 · README 주석의 「최종 리뷰 R1」 표지와 커밋 `04a4013` 에 있다.
3. 「## 최종 리뷰」 표는 라운드 2 까지다. 라운드 3 은 상한이며 이 라운드 뒤 루프 종료 여부는 메인 판정이다.
