# 관리자 RBAC 마일스톤 10 · 권한 관리 화면 · 가드레일 (admin-rbac-m10)

- 계획: `.claude/PRPs/plans/admin-rbac-m10.plan.md` · 검사: `.claude/PRPs/plans/admin-rbac-m10.gates.sh`
- 상태: 마감 (complete, 2026-10-27 메인 판정). 구현 커밋 완료 (서버 b1c4a851 · e5fc7fe3 · 관리자 981d5b67 · 3fb1f840 · 부모 2b70efad · 671a77b0 · fb6f64ea · e75c3eb4). 측정 완료 (RUN_ID 20261026T144801Z-b22de405). 최종 리뷰 R1 판정은 「## 최종 리뷰」 절. 마감 근거와 남은 항목은 아래 「## 마감 요약」 절.

## 마감 요약

- 판정 (2026-10-27, 메인): **complete**. 동결 완료 조건 F1–F10 (계획 「동결 완료 조건」 표) 이 녹색이다. PRD 행 10 의 상태를 complete 로 바꾸고 Plan 열에 이 계획을 더했다. 문구 열은 기준 커밋 그대로다.
- 게이트 결과
  1. `M10_PHASE=impl bash .claude/PRPs/plans/admin-rbac-m10.gates.sh` 를 2026-10-27 에 다시 돌렸다: FAILS=0 · RAN=14 · rc 0. 검사 #4 · #32 의 상태 기준은 마감 판정 값 (complete) 으로 옮긴 뒤 녹색이다. 검사 #4 는 양성 1건과 음성 2건 (상태를 pending 으로 되돌린 사본 · 문구 열을 고친 사본) 을 실행해 붉음을 확인했다.
  2. `M10_PHASE=plan`: #1 (착수 sha) 만 붉다. 구현 커밋이 서버 HEAD 를 옮겼으므로 계획 시점 기록이 낡았다 (정상, 고치지 않았다). 나머지 #2–#19 는 녹색이다.
  3. 격리 측정 RUN_ID `20261026T144801Z-b22de405`: 서버 트리 `8ed2cc6e` 이 서버 HEAD `e5fc7fe3` 의 트리와 같고 워킹트리가 깨끗했다. 시험 30건 (`permission-guardrail.itspec.ts` 17 · `admin-edit-guardrail.itspec.ts` 13) 전부 통과. 판정 헬퍼를 무력화한 사본에서 판정 태그 19건이 붉고 11건이 통과했다.
  4. 서버 단위 회귀 2900건: 2842 통과 · 35 실패 · 23 건너뜀. 실패 suite 9개 (`treat/dispatch` 3 · `ddx` 3 · `analyze-insight-route` · `drizzle.utill`) 는 이 마일스톤이 바꾼 파일을 import 하지 않는다 (마감 때 재대조). 착수 커밋 기준 실패 이름 집합 대조는 실측하지 않았다 (미검증).
- 계획 게이트 (hybrid, 상한 3): R1 · R2 는 L2 패널 divergent 와 L3 Codex divergent 였고, R3 에서 상한에 닿았다 (`ROUNDS=3/3`). 계획 receipt 는 없다. 세션 기록의 사용자 지시 (2026-10-19T02:21:22Z) 를 `verify-user-directive.sh` 로 원문 확인 (exit 0) 한 뒤 skip-receipt 로 진행했다. 구현 receipt 의 Codex 판정은 divergent 다 (「Implement-Codex receipt」).
- 코드 리뷰 R1: 지적 28건 (중복 병합 전) → 흡수 18 · 기각 또는 백로그 9 · 미해소 1 (삭제 경로, 아래 「PRD 미달 검토」 1). HIGH 는 흡수 1 (상세 · 역할 조회의 버전 읽기 순서).
- 최종 리뷰 R1 (fable 5 · codex 3): 지적 8건 → 흡수 3 · 백로그 또는 기각 5. HIGH 1 (삭제 경로 가드, codex) 은 이연 유지. CRITICAL 미해소 0. 흡수 3 은 노트 C4 배포 순서 추가 · 검사 #21 의 리터럴 비교 · 노트 4곳의 「붉지 않음」 정정이다.
- PRD 미달 검토: **없음**. 근거는 아래와 같다.
  1. 삭제 경로 가드 (HIGH, 백로그 2131 · 2133). `POST admin/admin/delete` 는 `@Roles('admin')` 과 `assertMayTouch` (지정 최고관리자 삭제 거부) 만 있다. rbac.manage 를 가진 계정은 비최고관리자가 지울 수 있다. 이 결함은 M10 이전부터 있다 (`ea3c0359` 의 `admin.repository.ts` 에도 `delete` 가 있다). M10 은 삭제 트랜잭션에 sentinel 잠금과 권한 버전 상승을 더했고, 가드는 더하지 않았다. PRD 는 계정 생성 · 수정 · 삭제와 권한 그룹 쓰기의 최고관리자 한정을 R2 로 분리했다 (2026-10-15 사용자 확정, #96 몫, 「승계 전 즉시 차단」 절). 이 마일스톤의 동결 조건 (F1–F10) 과 PRD 증명 10 행에도 삭제 시나리오가 없다. 그래서 M10 의 미달로 세지 않고 활성 전환 전 필수 항목으로 둔다. 다만 동결 규칙 (i) 의 「가드 누락」 에 해당하는 항목이라, 이 분류가 틀렸다고 보면 최종 리뷰 레인이 행 10 의 complete 를 되돌려야 한다. 설계가 둘로 갈린 자리이므로 이 근거를 기록한다.
  2. 스위치 ON 분기의 결함 (백로그 2166 · 2167 · 2148, 모두 MEDIUM). 역할 생성은 빈 권한 배열로 메뉴 매핑을 넣다 실패한다. 계정 상세의 「- 회수 -」 는 값 0 과 처음 값 `""` 의 비교 때문에 항상 binding 변경으로 읽힌다. 역할의 메뉴 표 편집 화면이 없다. 스위치가 OFF 라 운영에 닿지 않는다. 노트 C4 의 전환 체크리스트에 넣는다.
- (미검증) · 사용자 몫 (complete 를 막지 않는다)
  1. 활성 전환: 스위치 `PERMISSION_SCREEN_ENABLED` ON, 새 쓰기 경로 `admin/permission/*` 의 모듈 등록. R2 · R16 활성 조건이 먼저다.
  2. 배포 순서 (C4 「배포 순서」, 백로그 2164): 0036 DDL 을 먼저 적용하고, 서버 e5fc7fe3 과 관리자 981d5b67 을 같은 창에 올린다. 어긋나면 최고관리자의 자격 변경도 400 · 409 · 503 으로 막힌다 (fail-closed, 데이터 손상 없음).
  3. 지정 행 확인: 최고관리자 지정 행 (`adm_is_super`) 이 있는지 본다. 없으면 활성 편집 판정이 전원을 비최고로 보아 자격 변경이 막힌다 (계획 위험 1 · 8).
  4. R10 (첫 비최고관리자 권한 관리자 지정, 사람이 정한다), R7 (ath_id NULL 내부 관리자 수), R19 (소비처 확인 책임자), R16 (관리 쓰기 전체의 CSRF 방어 소유자).
  5. 복제 환경 1회 재생과 실패 0건 (2026-10-21 지시. 복제본 생성은 사람 승인). 운영 would-deny 판단표.
  6. 삭제 경로 (위 PRD 미달 검토 1) 와 레거시 권한 그룹 삭제 경로 (백로그 2147) 는 R2 (#96) 에서 닫는다.
  7. 동급 계정 비밀번호 변경 허용 (백로그 2162): PRD 결정 B 3 의 문구대로 구현했다. 허용 여부는 사용자 확인이다.
  8. 계산된 border 렌더 대조와 화면 행위: 프런트 시험 러너가 없어 정적 확인만 했다 (관리자 tsc 764 · eslint 오류 0). 권한 코드의 한글 이름 표도 사용자 확인이다.
  9. 메뉴 배분 병행 편집 제거 (스위치 ON 조건, 백로그 2148) 와 영업 교차 노출 28셀의 수용 (백로그 1986).
  10. 이연: 발송 경로 오류 처리 (백로그 2097 · 2112), 목록 장면 조직 범위 (2074), AO 교차 쓰기 범위 (2075), 판정 동시 상한 (1894).
  11. 서버 단위 회귀의 착수 기준 이름 집합 대조 (위 게이트 결과 4).
- 다음 마일스톤이 알아야 할 것
  1. 마감 시점의 착수 sha: 서버 `e5fc7fe3` (트리 `8ed2cc6e`), 관리자 `3fb1f840` (기능 커밋 `981d5b67`), 사이트 `fc8872a1` (이 마일스톤에서 고치지 않았다).
  2. 행 11 (정리) 은 pending 이다. 착수 조건은 CAR_VIEW 이관 행 대조 (R20) 이고 이 마일스톤은 R20 을 닫지 않았다.
  3. 검사 #4 · #32 는 마감 판정 값을 본다. 앞 마일스톤 사이드카 (m9f 의 #1 · #5 · #18 · #24) 는 서버 HEAD 나 트리가 움직이면 붉다. 이 마일스톤 게이트는 그것을 요구하지 않는다. 처리 방향은 사람이 정한다.
  4. 측정 산출 (`.claude/meta/2026-10-26-admin-rbac-m10/`) 은 서버 트리 `8ed2cc6e` 에 묶여 있다. 서버 트리가 바뀌면 재측정하고 검사 #33 을 다시 본다.
  5. 활성 전 해소 목록은 위 (미검증) · 사용자 몫 항목과 백로그 2131 · 2133 · 2147 · 2148 · 2162 · 2164 · 2166 · 2167 이다.

## 착수 기록

## 착수 기록

| 저장소 | 브랜치 | 착수 sha | 비고 |
|---|---|---|---|
| 부모 (`insuniverse-temp` 워크트리) | feat/admin-rbac | a289538d | 계획 커밋 직전 HEAD |
| insuniverse-server | feat/admin-rbac | bf0c1db5 | 선행 병합 ea3c0359 의 후손 (정책 §1-1) |
| insuniverse-admin | feat/admin-rbac | 1301b664 | 화면 구현 대상 |
| insuniverse-site | feat/admin-rbac | fc8872a1 | 이 마일스톤에서 고치지 않는다 |

- 기준 백로그 커밋: `a289538d` (표지 줄 144개, 계획서 「이월 지적 반영」 표가 전부 덮는다).
- 지시 확인: 사용자 세션 기록의 상한 · skip-receipt 원문은 `verify-user-directive.sh` 가 확인했다 (rc 0, 2026-10-19T02:21:22Z 기록).

## 계획 시점 판정 기록

- 검사 #1–#19 (계획 커밋 `42cb5a66` 뒤, `M10_PHASE=plan`): 전부 PASS (FAILS=0, RAN=19).
- 음성 실행 (임시 사본, 원 작업 트리 불변, `M10_*` 재지정): 열여섯 검사에서 FAIL 을 확인했다. #1 노트 사본 (1301b664 행 제거) · #3 정책 사본 (처리 칸 변경) · #4 PRD 사본 (행 10 complete) · #6 계획 사본 (1805 행 제거) · #7 백로그 사본 (SELF_STATE_REGRESS 두 줄 변경) · #8 백로그 사본 (2089 짝 행 제거) · #9 사이드카 사본 (검사 #30 제거) · #11 노트 사본 (홈 경로 덧붙임) · #12 계획 사본 (F1 의 검사 #99) · #13 계획 사본 (음성 행 문구 제거) · #14 계획 사본 (게이트 표기 제거) · #15 계획 사본 (Gate 행 하나 제거) · #16 계획 사본 (금지 어휘 덧붙임) · #17 계획 사본 (태그 반례 b 제거) · #18 사이드카 사본 (검사 #7 제거) · #19 계획 사본 (Validation 절 제거).
- 미실행 (사유): #2 (착수 sha 를 바꿔야 한다), #5 (원 작업 트리 상수를 건드리지 않는다), #10 (세션 기록 경로를 바꾸지 않는다). 구현 뒤 검사 #20–#33 의 음성은 「구현 뒤 실행 결과」 절에 적는다.
- 음성 사본은 임시 디렉터리에 두었고 커밋하지 않았다.

## 계획 게이트

mccp `/mccp:plan` hybrid 게이트 (L1 형식 검사 · L2 리뷰어 패널 4명 · L3 Codex 교차 모델 리뷰, 라운드 상한 3) 기록이다. 검사 파일은 `.claude/PRPs/plans/admin-rbac-m10.gates.sh`, 리뷰 원문은 `.claude/reviews/plan-review-admin-rbac-m10.md`, 판정 원자료는 `.claude/state/plan-review/` (gitignore) 다. R1 · R2 행은 계획서 「리뷰 라운드 기록」 표에서 옮겼다 (계획서는 동결이라 고치지 않았다).

| 라운드 | A (L2 패널) | B (L3 Codex) | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R1 | divergent · 차단 9 | divergent · HIGH 1 · MEDIUM 1 | 2 · 3 · 4 · 6 · 7 · 8 | 계획서 R1 행 (판정 자리 · 잠금 순서 · 활성 경로 · expectedVersion 409 등) | security LOW 지정 부재 (위험 8) |
| R2 | divergent · 차단 4 (HIGH 2 · 리뷰어 FAIL 2) | divergent · HIGH 1 · MEDIUM 2 | 2 · 3 · 4 · 6 · 7 · 8 | 계획서 R2 행 (create 세 번째 쓰기 · 완전성 검사 · 잠금 읽기 · `HttpException` 통과 등) | 롤백 스위치 (정책 §5-8 · PRD 결정 B 로 기각) · 지정 부재 통과 (위험 8) |
| R3 | divergent · 차단 7 (HIGH 4 · 리뷰어 FAIL 3) · MEDIUM 3 · LOW 5 | divergent · HIGH 1 (신뢰도 0.92) | 1 · 2 · 5 · 6 · 7 | 없음 (상한 도달, 계획서 동결) | 없음 (전부 백로그로 넘김) |

### R3 처리 (상한)

1) 라운드 상한 3/3 에 도달했고 (`status.sh` 는 `ROUNDS=3/3`) R3 판정은 divergent 다 (post-b decide exit 12 · `HALT_STAGE=5.2e` · L2 정족수 미충족 · L3 divergent, `forwardCodexVerdict=false`). 계획서는 고치지 않았다 (동결, 검토 해시 `sha256:529ec7637b45e70175880373a8e459b876343b8a390aa0c96a930110ab26c95b`).

2) CRITICAL 유무: 없다. L2 는 HIGH 4 · MEDIUM 3 · LOW 5 이고, 리뷰어 FAIL 3 건은 판정 표현이라 심각도가 아니다. L3 는 high 1 건 (신뢰도 0.92) 이다. 그래서 receipt 를 쓰지 않는 조건 (CRITICAL 미해소) 에 걸리지 않는다.

3) 넘김 근거 — 사용자 지시 원문 (세션 기록 2026-10-19T02:21:22Z, `verify-user-directive.sh` exit 0 으로 원문 확인):

   > Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.

   이 지시가 전역 CLAUDE.md mccp 한시 규칙의 「receipt 없음 → 멈춤」 을 대체한다.

4) 계획 receipt 는 쓰지 않았다. write 는 시도하지 않았다 (intent gate 가 fail-closed 로 막는다 — m9f 선례). 구현 진입 검증은 `MCCP_SKIP_RECEIPT=1` 을 그 명령 하나에만 붙여 `preflight --command mccp:prp-implement --decision admin-rbac-m10 --plan .claude/PRPs/plans/admin-rbac-m10.plan.md` 를 실행했고, 결과는 `ok: true` · `bypassed: true` · reason `MCCP_SKIP_RECEIPT` 였다. `MCCP_SKIP_INTENT_GATE` 는 쓰지 않았고 env 는 세션 · 설정 파일에 두지 않았다. 계획 receipt 가 없으므로 구현 receipt 체인은 skip 으로 이어진다.

5) backlog (`.claude/plans/codex-findings-backlog.md`, 2026-10-26) 에 넘긴 HIGH 5 건, 「→ 다음 계획 반영 대상」 표지:
   1) architect · 삭제 경로 가드 없음. rbac.manage 보유 계정을 비최고관리자가 삭제할 수 있다 (`admin.repository.ts:193` · `plan:76,85`)
   2) test · #21 전후 대조가 같은 생산자 산출끼리 비교해 순환한다 (`plan:189` · `gates.sh:429`)
   3) test · 활성 경로 가드에 DB 수준 검사가 없다 (`plan:142,189` · `gates.sh:417-418`)
   4) invariant · 생성 경로가 createAdmin 의 지정 상태 최고관리자 전용 차단을 잃는다 (`admin.service.ts:111-114` · `plan:139`)
   5) L3 · 상세 조회와 permissionVersion 을 같은 스냅샷으로 읽도록 계약하지 않는다. 회수 전 athId 와 회수 후 버전 조합으로 이전 binding 복원 가능 (`plan:84`)

   MEDIUM 3 건 · LOW 5 건도 같은 백로그에 비차단으로 적었다. 같은 라운드 항목은 백로그에 없었다 (기존 admin-rbac-m10 행 3 건은 R1 · R2 가 낸 RESOLVED-IN 행). `gates.sh` 는 `.claude/PRPs/plans/admin-rbac-m10.gates.sh`, `plan` 은 `.claude/PRPs/plans/admin-rbac-m10.plan.md` 를 뜻한다.

6) 남은 HIGH 5 건은 구현 산출의 증명 강도를 낮춘다. 구현 뒤 판정에서 #21 · #20 · 활성 경로 · 생성 판정 · 상세 스냅샷을 증명으로 인용할 때 이 한계를 함께 적는다.

## 메인 실행 요청

구현 에이전트는 10분을 넘는 측정을 돌리지 않는다 (에이전트가 끝나면 백그라운드 작업이 죽는다). 아래 항목은 메인 세션이 돌리고, 산출을 같은 디렉터리에 둔 뒤 검사 #20 · #21 · #22 · #23 · #33 을 다시 돌린다.

1. 서버 jest 회귀와 격리 시험: 격리 MySQL (`m10-iso-` 접두 컨테이너, 포트는 루프백) 을 띄우고 `insuniverse-server` 에서 `permission` · `admin` · `auth` 패턴 전체를 `--json --outputFile=jest-m10.json` 으로 돈다. 이 한 실행에 `permission-guardrail.itspec.ts` 와 `admin-edit-guardrail.itspec.ts` 가 든다 (정정: 계획 Files 표의 `.spec.ts` 이름은 동결된 계획서에 남아 있다). 기준선은 착수 커밋의 실패 · 보류 이름 집합이다.
2. 변이 사본: 판정 호출 (`assertGuardrailInTx`) 과 `expectedVersion` 비교를 지운 사본에서 같은 패턴을 `--json --outputFile=jest-neg-m10.json` 으로 돈다. 변이는 임시 worktree 에서만 하고 원 작업 트리는 건드리지 않는다 (메모 ops-gate-negative-runs-temp-worktree). 원 트리 복원을 확인한다.
3. 전후 대조: 격리 DB 에서 binding · 역할 편집 전후 `tn_auth_permission` · `tn_admin.ath_id` 행을 탭 구분 정렬 파일 `tn-before.tsv` · `tn-after.tsv` 로 덤프한다. 기대 차분 `tn-expected-delta.tsv` 는 `comm -3 <(sort tn-before.tsv) <(sort tn-after.tsv)` 와 같은 형식으로 쓴다. 차분은 검사 #21 이 다시 계산한다. 감사 행은 `audit-m10.tsv` 로 낸다.
4. 머리와 매니페스트: `run-header-m10.txt` 에 `# SRV_TREE=` (서버 `HEAD^{tree}`) · `# SRV_WORKTREE_CLEAN=yes` · `# RUN_ID=` 를 싣는다. 측정 전에 서버 추적 파일이 깨끗해야 한다 (`git status --untracked-files=no`). `manifest-m10.sha256` 은 마지막에 쓴다 (커밋 지점). 첫 두 줄은 `# RUN_ID=` · `# SRV_TREE=` 이고, 나머지는 위 산출 (머리 파일 포함) 의 `sha256sum` 줄이다.
5. 격리 컨테이너와 Redis 는 `m10-iso-` 접두로만 띄우고 끝나면 그것만 내린다.

측정 결과가 나오면 이 절 밑에 「메인 실행 결과」 를 적고 검사 #20–#23 · #33 을 다시 돌린다.

## 메인 실행 결과

측정은 `measure-m10.sh` rc 0 (RUN_ID=20261026T144801Z-b22de405, 서버 HEAD e5fc7fe3, SRV_TREE=8ed2cc6e1047a393e9130b33aca1b9e47d773ed6). 서버 · 관리자 코드는 이 실행에서 바꾸지 않았으므로 측정은 다시 돌리지 않았다.

- 격리 시험 `jest-m10.json`: 30 건 전부 passed (`permission-guardrail.itspec.ts` 17 · `admin-edit-guardrail.itspec.ts` 13).
- 변이 사본 `jest-neg-m10.json`: 30 건 중 19 failed · 11 passed. 붉은 19 건은 판정 태그 B1 3 · B2 2 · B3 2 · B4 4 · 반례 a 1 · 반례 b 2 · 동시 4 · 최고(수정 경로) 1 이다. 통과한 11 건은 대조군 3 · 이력 · 전후 · 내부 · 최고(기존 보호 경로) 2 · B1(역할 편집, 기존 `requireSuper` 가 먼저 막음) · 지정 생존 생성 보호 · 이름 저장 이다 (로컬 실행 기록과 같다, 「최종 리뷰」 R1 F4 로 바로잡음).
- 단위 회귀 `jest-unit-m10.json`: 2900 건 중 2842 passed · 35 failed · 23 skipped. 실패 suite 는 treat · dispatch (Redis 연결, `MaxRetriesPerRequestError`) 와 ddx insight-view · analyze-insight-route · drizzle.utill 등이다. 이 마일스톤의 권한 · 관리자 시험은 전부 passed (`permission-admin.controller.spec` 12 · `origin-check.guard.spec` 4). 실패 집합이 착수 기준선과 같은지는 재지 않았다 (미검증).
- 게이트 `M10_PHASE=impl`: 수정 전 FAILS=3 (#22 · #23 · #30), 수정 뒤 FAILS=0 · RAN=14.
- 계획 시점 검사 `M10_PHASE=plan`: RAN=19 · FAILS=1. #1 (착수 sha) 만 붉다. 서버 HEAD 가 구현 커밋으로 옮겨져 시점 기록이 낡았다. 계획 시점 검사이므로 정상이고 고치지 않았다.

### 검사 #22: 분류 (가), 게이트 정의가 리뷰 뒤 코드와 어긋났다

- 원인 1: 기대가 `admin.service.ts` 가 `permission-guardrail` 을 쓴다고 했다. 계획 Files 표대로 판정은 `admin.repository.ts` 가 맡고, service 는 `assertMayTouch` 사전 거부만 남긴다.
- 원인 2: 헬퍼 이름 `assertGuardrailInTx` 는 리뷰 중 `assertAccountGuardrailInTx` (생성 · 갱신 · binding) 와 `assertRoleGuardrailInTx` (역할 편집) 로 나뉘었다.
- 운영 코드는 계획 의도와 맞다. repository 판정 호출 2 · 트랜잭션 첫 문장 sentinel 3 · `tn_admin` 쓰기 3 (두 파일) · 활성 경로 시험 태그 passed.
- 수정: 사이드카 `c22` 가 repository 의 import 와 두 함수 이름 정규식을 본다. 계획 본문은 고치지 않았다.
- 반사실: 수정 전 「admin.service.ts 가 판정 함수를 쓰지 않는다」 로 FAIL, 수정 뒤 PASS. 격리 사본 (임시 디렉터리, 서버 워킹트리는 건드리지 않음): repository 판정 호출 제거 → 「판정 호출이 생성 · 갱신 두 자리가 아니다」 FAIL. 생성 트랜잭션 sentinel 제거 → 「sentinel 잠금이 없는 것이 있다」 FAIL. 쓰기 서비스 판정 제거 → 「쓰기 경로가 판정 헬퍼를 부르지 않는다」 FAIL.

### 검사 #23: 분류 (나), 측정 범위 밖을 읽었다

- 원인: CSRF 시험 두 파일 (`permission-admin.controller.spec.ts` · `origin-check.guard.spec.ts`) 은 `.spec.ts` 라 단위 산출 `jest-unit-m10.json` 에 있다. 게이트는 격리 산출 `jest-m10.json` 을 읽었다. 시험 자체는 전부 passed (단위 산출 16 건).
- 수정: 사이드카 `c23` 이 `jest-unit-m10.json` 을 읽는다.
- 반사실: 수정 전 「CSRF 태그가 컨트롤러 · 가드 시험에서 passed 가 아니다」 로 FAIL, 수정 뒤 PASS. 격리 사본: 모듈에 `PermissionAdminController` 를 등록 → 「모듈 등록 검사가 등록을 찾았거나 …」 FAIL. POST 데코레이터 (줄 93) 의 `OriginCheckGuard` 제거 → 「POST 경로 (줄 93) 에 OriginCheckGuard 가 없다」 FAIL. 단위 산출에서 CSRF 시험 4 건을 failed 로 바꾼 사본 → 「CSRF 태그가 … passed 가 아니다」 FAIL.

### 검사 #30: 분류 (가), 앞 사이드카 검사 #1 은 시점 기록이다

- 원인: 검사 #30 이 `M9F_PHASE=plan` 전체를 돌렸다. m9f 계획 시점 검사 #1 은 m9f 착수 서버 sha (bf0c1db5) 가 HEAD 와 같아야 한다고 본다. M10 구현 커밋이 서버 HEAD 를 옮겼으므로 #1 이 붉은 것은 정상이다. m9f 사이드카 머리도 계획 시점 검사는 「계획 커밋에서 녹색이어야 한다」 고 적는다. 즉 이 마일스톤 게이트가 과거 기록을 현재 HEAD 에 요구한 것이 결함이었다. 나머지 (변이 이름 · `bash -n` · m9f 검사 2–14) 는 현재 HEAD 와 무관하게 녹색이다.
- 답 (앞 마일스톤 결속 질문): 앞 사이드카의 착수 sha 결속은 그 마일스톤 시점 기준이므로, 이후 서버 변경으로 붉는 것이 정상이다. 이 마일스톤 게이트는 #1 을 요구하지 않고, 그 대신 붉은 이유가 「서버 HEAD 이동」 한 줄뿐이고 m9f 착수 서버 sha 가 현재 HEAD 의 조상인지 (앞으로만 움직였는지) 를 본다.
- 수정: 사이드카 `c30` 이 m9f #1 을 제외하고 검사 2–14 를 `M9F_ONLY` 로 하나씩 돌린다. 계획 본문은 고치지 않았다.
- 반사실: 수정 전 「m9f 계획 시점 검사가 깨졌다」 로 FAIL, 수정 뒤 PASS (`M10_ONLY=30` 실측). 격리 사본: m9f 사이드카에서 `MUT_TWO_FIELD` 이름을 바꾼 사본 → 「m9f 사이드카에 MUT_TWO_FIELD 이 없다」 FAIL.

### 이 실행에서 본 것 (고치지 않음)

- 앞 마일스톤 m9f 의 구현 뒤 검사 #18 · #24 는 서버 트리 (`SRV_TREE`) 에 묶여 있어 M10 구현 커밋 뒤 붉다 (`M9F_PHASE=impl` FAILS=2). M10 게이트는 이 두 검사를 돌리지 않는다. 같은 결함 (시점 기록이 현재 HEAD 를 요구) 이므로 백로그에 올렸다. 처리 방향은 사람이 정한다.
- 측정 산출 (`.claude/meta/2026-10-26-admin-rbac-m10/`) 은 커밋하지 않았다. jest JSON 세 개에 호스트 홈 경로가 들어 있어 호스트 홈 경로 0 건 규칙 (커밋 산출 검사 #11) 에 걸린다. 게이트는 워킹트리 파일을 그대로 읽으므로 미커밋 상태로도 통과한다.

## C4 사람 몫 (미검증)

판정이 사용자 몫인 항목이다. 이 마일스톤의 코드 · 격리 측정으로 닫히지 않는다.

- 활성 전환: 화면 스위치 `PERMISSION_SCREEN_ENABLED` ON, 새 쓰기 경로의 모듈 등록 (R2 · R16 활성 전환 조건과 함께).
- 메뉴 배분 병행 편집 제거 (스위치 ON 전환 조건). 스위치 OFF 상태의 기존 `권한메뉴` 입력은 현행 경로다.
- 복제 환경 1회 재생과 실패 0건 (UI7). 복제본 생성은 사람 승인.
- 운영 would-deny 판단 표 실내용. R2 · R7 (ath_id NULL 내부 관리자 수) · R19 (소비자 확인 책임자).
- R10: 최초 비최고관리자 권한 관리자 지정 (자동 부여 금지).
- R16 활성: 관리 쓰기 전체의 CSRF 방어 소유자 지정과 검증.
- 영업 교차 노출 28 셀 (백로그 1986) 의 수용 여부.
- 발송 경로 오류 처리 (백로그 2097 · 2112) 와 목록 장면 조직 범위 (2074) · AO 교차 쓰기 범위 (2075): 이연 행의 담당 판정.
- 활성 전환 때 권한 쓰기 컨트롤러의 `SuperAdminGuard` 를 풀면 binding · 역할 쓰기 서비스 (`setBinding` · `setRolePermissions`) 에 행위자 `rbac.manage` 검사가 없다. 지금은 가드가 최고관리자만 통과시켜 판정이 닿지 않는다. 활성 전에 둘 중 하나를 고른다: 서비스 판정에 `rbac.manage` 검사를 넣거나, 가드를 유지한다 (code-review R2 · 흡수 = 체크리스트 명시).
- 지정 행 확인: 배포 전에 최고관리자 지정 행이 있는지 본다. 지정 부재면 활성 편집 판정이 전원을 비최고로 보아 자격증명 · 부여 변경이 막힌다 (계획 최고관리자 행 · 위험 1, fail-closed). 활성 전환 조건 R2 와 함께 닫는다 (미검증).
- 배포 순서 (최종 리뷰 R1 F1, 미검증): 0036 DDL 을 먼저 적용하고, 서버 e5fc7fe3 과 관리자 981d5b67 을 **같은 배포 창**에 올린다. 서버만 올라가고 관리자 번들이 1301b664 인 창에서는 그 번들이 `expectedVersion` 을 보내지 않아, 최고관리자도 자격증명 (비밀번호 · 권한 묶음) 변경이 `assertVersionMatch` 의 400 「expectedVersion 이 필요하다」 로 막힌다. 버전 검사는 최고관리자 면제 앞에 있다 (`admin.repository.ts` update · `permission-guardrail.ts` `assertVersionMatch`). DDL 이 없으면 버전을 못 읽어 409, 생성은 503 이다. 데이터 손상은 없고 fail-closed 다. 계획 Risk 1 · Acceptance 의 해당 줄은 동결 계획서라 고치지 않았다 (백로그 이월).

## 롤백

- 활성 경로 판정 (`admin/admin/edit` 의 자격증명 변경, 생성 · 바인딩 판정) 에는 끄는 스위치가 없다. 판정은 기존 쓰기를 조이는 방향이라 스위치로 끄면 구멍이 다시 열린다 (계획 「잠정 채택 업무 결정」 롤백 행 · 정책 §5-8).
- 되돌림은 구현 커밋의 `git revert` 다. 서버 서브모듈 포인터와 부모 gitlink bump 를 같이 되돌린다.
- 되돌린 뒤 사이드카 #20–#22 와 #23 은 구현 전 상태에서 붉다 (정상). 되돌림 자체의 검증은 메인 실행 (미검증).

## 구현 뒤 실행 결과

구현 상태 (커밋 전, 워킹트리) 에서 `M10_PHASE=impl bash .claude/PRPs/plans/admin-rbac-m10.gates.sh` 를 돌렸다 (2026-10-26).

| 검사 | 결과 | 비고 |
|---|---|---|
| #20 가드레일 음성 (격리 산출) | FAIL | 측정 머리 없음 (메인 실행 대기). 구현 상태의 격리 시험은 아래 「로컬 실행」 에서 녹색 |
| #21 이력 · 전후 (격리 산출) | FAIL | 측정 머리 없음 (메인 실행 대기). 전후 차분은 로컬 실행에서 기대와 같았다 |
| #22 활성 경로 · assertMayTouch · 쓰기 자리 3 | FAIL | 측정 머리 없음 (메인 실행 대기) |
| #23 쓰기 경로 미등록 · Origin 가드 · CSRF | FAIL | 측정 머리 없음 (메인 실행 대기). 모듈 미등록 · Origin 가드 · 음성 단위 시험은 구현 상태에서 녹색 |
| #24 서버 tsc 오류 수 ≤ 23 | PASS | 서버 tsc 23 건 (기준과 같다) |
| #25 화면 스위치 OFF · 파일 · 다섯 라벨 | PASS | 「일반 역할 편집」 제목을 상세 화면에 두었다 |
| #26 권한메뉴 입력이 스위치 분기 안 | PASS | 두 화면 모두 |
| #27 화면 금지 문구 · border 선언 | PASS | |
| #28 관리자 tsc (node) ≤ 764 | PASS | 측정 뒤 생기는 tsbuildinfo 는 지웠다 (서브모듈 청결) |
| #29 PRODUCT.md:53 | PASS | |
| #30 앞 사이드카 보강 (m9f) | FAIL | 아래 「검사 #30 의 붉음」 |
| #31 처리 행 3건의 구현 뒤 행 | PASS | 짝 행 3건 추가 |
| #32 PRD 행 9 · 10 불변 · 결정 로그 | PASS | |
| #33 메인 실행 요청 · 산출 머리 | FAIL | 측정 머리 없음 (메인 실행 대기) |

계획 시점 검사 (#1–#19) 는 이 실행에서 다시 돌리지 않았다. 구현 변경 때문에 서버 · 어드민 워킹트리가 더러워져 #1 · #5 의 청결 조건과 어긋난다. 구현 커밋 뒤에 다시 돌린다 (미검증).

### 검사 #30 의 붉음

- m9f 사이드카 계획 시점 검사 #1 · #5 가 붉다. 서버 워킹트리가 비어 있지 않아서다 (구현이 커밋되지 않았다). 커밋 뒤 녹색이 된다 (미검증).
- 이 실행에서 m9f 사이드카 #8 을 고쳤다 (구현 「정책과 다르게 옮긴 자리」 3). 이유: M10 의 짝 행 (`[원 2088]` 등) 이 m9f 기준 뒤에 추가된 줄을 가리켜 m9f #8 이 붉었다. M10 짝 행은 M10 계획이 소유하므로 m9f #8 의 범위에서 뺐다.
- m9f #17 · #18 · #19 는 구현 상태에서 녹색이다 (`M9F_PHASE=impl M9F_ONLY=18|19`). 변이 세 건 (MUT_SELF_STATE · MUT_LIST_OWNER · MUT_TWO_FIELD) 은 0 이 아닌 회귀 줄을 낸다.

### 로컬 실행 (메인 실행 전 확인, 격리 DB)

- 격리 컨테이너 `m10-iso-mysql` (mysql:8.0.42, 루프백 임의 포트) 에 insuniverse-isolated 의 `02-schema.sql` · `01-functions.sql` 을 올리고, `drizzle/0031_admin_audit.sql` · `0033_admin_audit_index.sql` · `0036_auth_permission.sql` 을 적용했다. `adm_is_super` 칸은 `super-admin.seed.ts` 의 ALTER 와 같은 형태로 더했다. 03 · 04 시드는 쓰지 않았다 (시험 씨앗은 시험 안에서 만든다).
- 시험 파일: `permission-guardrail.itspec.ts` 17 건 · `admin-edit-guardrail.itspec.ts` 13 건, 합계 30 건 모두 passed (`maxWorkers: 1`, 두 파일이 같은 DB 를 되돌리므로 병렬 금지).
- 변이 사본 (`mutant-m10.mjs` 로 만든 사본, `M10_MUT=guard`: 판정 헬퍼 세 개와 버전 비교를 즉시 반환): 19 건 failed · 11 건 passed. 실패한 19 건은 판정 태그 `[B1]` · `[B2]` · `[B3]` · `[B4]` · `[반례 b]` · `[동시]` · `[최고]` (B 계열 자리) 이고, 통과한 11 건은 대조군 · 이력 · 전후 · 내부 (내부는 서비스 쪽 판정이라 가드 헬퍼를 지워도 남는다) 다. `[반례 a]` 는 가드 헬퍼를 지운 사본에서 붉다 (19 건 중 하나). 바로잡음: 앞선 로컬 기록은 이 자리를 「붉지 않음」 으로 적었으나 측정 산출 (`jest-neg-m10.json`) 이 붉음을 보인다. 아래 「구현 편차」 4 는 B1 역할 편집 건만 중복 방어로 본다.
- 단위 회귀 (`permission|admin|auth`, 기본 설정): 2835 passed · 35 failed · 9 failed suites. 실패 suite 는 `treat/dispatch` 셋 (Redis 필요), `ddx` · `analyze-insight-route` · `libs/drizzle/src/drizzle.utill.spec.ts` 다. 이 마일스톤의 파일을 import 하지 않는다 (의존 대조). 기준선은 착수 커밋에서 다시 재지 않았다 (미검증).
- 서버 tsc: 23 건 (기준 23 과 같다). 같은 명령으로 확인했다.

## 구현 편차

1. **구현 순서.** 명령 본문은 Phase 2.5 (Codex 구현 리뷰와 receipt) 를 Phase 3 (구현) 앞에 둔다. 이 실행은 구현을 먼저 시작했다. Codex 리뷰 (2.5.3) 는 구현 중간에 돌았고, 그 검토 대상은 부모 브랜치 diff 였다 (서브모듈 포인터뿐, 메모 `mccp-implement-codex-has-no-diff-to-review`). receipt 는 구현 뒤에 썼다. 결정 목록은 계획서와 노트 「잠정 채택 업무 결정」 에 있다.
2. **Phase 2 의 `git pull --rebase` 는 돌리지 않았다** (사용자 지시). rebase 는 시작되지 않았다.
3. **m9f 사이드카 #8 의 범위 조정.** 위 「검사 #30 의 붉음」. 짝 행을 가리키는 M9F 검사의 기준이 M10 짝 행과 어긋나 그 행만 뺐다. 이 조정은 M9F 검사의 의미를 바꾸지 않는다 (M9F 자기 짝 행은 그대로 센다).
4. **역할 편집 B1 의 중복 방어.** 역할 편집의 B1 (rbac.manage 편집) 은 기존 `requireSuper` 가 먼저 막는다. 가드 판정은 같은 규칙을 한 번 더 건다. 음성 시험에서 `[B1]` 역할 편집 건이 가드를 지워도 붉지 않은 이유다 (`[반례 a]` 는 가드 헬퍼를 지우면 붉다, 위 「메인 실행 결과」 정정). 삭제 경로 (`admin.repository.ts` `delete`) 에는 판정을 걸지 않았다 (계획 Task 4 의 「잠금 순서만 바꾼다」). 백로그 1 (삭제 경로 가드 없음) 이 그대로 남는다.
5. **역할 편집의 전체 집합 저장.** 서버 계약은 전체 집합 교체다. 화면은 바뀐 뒤의 전체 집합을 보낸다. 「바뀐 값만 보낸다」 (계획 Task 8 (f)) 는 서버 계약을 바꾸지 않고는 맞지 않아 저장 활성 조건 (바뀜 있음) 으로만 구현했다.
6. **신규 역할 화면의 권한 묶음 저장.** 역할 생성 응답은 성공 여부만 준다. 저장은 이름으로 역할을 다시 찾아 (정확히 한 건일 때만) 한다. 이름 중복이면 권한 묶음을 저장하지 않는다 (미검증, 사용자 확인).
7. **계정 상세의 권한 묶음 칸.** 스위치 ON 에서 권한 묶음을 바꾸면 binding 으로 저장하고, 나머지 정보는 다시 저장하게 한다 (한 번에 두 쓰기를 묶지 않는다, 버전 충돌 방지). 최고관리자 행은 칸을 비활성으로 두고 한 줄 사유를 붙인다.
8. **코드별 한글 이름.** 권한 코드는 코드 문자열 그대로 보인다 (이름 표는 카탈로그에 없다, 계획 Task 8 (b) 의 이월 항목, (미검증)).
9. **이력 화면 상세.** 옆 패널에 허용목록 요약 JSON 을 보인다. 역할 이름 · 계정 이름은 싣지 않는다 (감사 칸에 없다).
10. **서버 추가 읽기.** `GET admin/permission/catalog` 를 더했다 (신규 역할 화면이 역할 없이 카탈로그를 받으려고). 계획 Files 표에 없던 경로지만 같은 컨트롤러 파일 안이다. 등록하지 않는 비활성 경로다.

## 리뷰 수용 규약에 따른 분리 (out_of_scope 후보)

- 계획 Task 6 의 「판정 헬퍼를 지운 사본」 은 `M10_MUT` 환경 변수로 헬퍼를 즉시 반환시킨 사본이다 (호출은 그대로 두었다). 원 소스에는 이 분기가 없다. 메인 측정 스크립트 (`measure-m10.sh`) 가 이 사본을 임시 디렉터리에 만든다.
- 이 실행에서 측정 산출 (`run-header-m10.txt` · `jest-m10.json` · `jest-neg-m10.json` · `tn-*.tsv` · `audit-m10.tsv` · `manifest-m10.sha256`) 은 만들지 않았다. 서버 워킹트리가 커밋되기 전에는 머리의 `SRV_WORKTREE_CLEAN=yes` 와 맞지 않는다.

## Codex Implementation Review

- 호출: mccp 1.33.6 의 `scripts/lib/codex-invoke.js adversarial-review` (2.5.3, 자동)
- 라운드 수: 1 (R2 없음)
- 합치 결론: 판정 `needs-attention` · blocking 아님 (classification ok). 지적 1 건 (HIGH).
- YAGNI Triage:
  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 활성 계정 편집이 B1–B4 · expectedVersion 을 거치지 않는다 | HIGH | ACCEPT_NOW | 구현으로 흡수했다 (Task 4 (d): `admin.repository.ts` 의 갱신 · 생성 판정, `admin/admin/edit` 의 자격증명 판정). 검토 시점의 서브모듈 상태 (착수 sha) 를 보고 쓴 지적이라 구현 뒤 상태에는 맞지 않는다. 격리 음성 시험이 흡수를 증명한다 |
- Deferred to backlog: 0 건 (이번 라운드). 백로그에는 계획 시점 행 (2088 · 2089 · 2092 의 구현 뒤 행) 만 더했다.
- Open Questions: 없음 (auto-CRITICAL 없음).
- 판정 (receipt 기록): `codex_verdict = divergent` (converged 위장 금지). 리뷰 원문 (`codex-impl-review.json`) 은 `.git` 아래 `mccp/tmp/` 에 있다.
- 보안 리뷰어: 서브에이전트를 부를 수 없어 `security_skipped` 로 기록했다 (receipt 의 사유 문구 참조). 권한 · 인증 경로라 보안 리뷰는 `/mccp:code-review` 와 최종 fable · codex 리뷰가 맡는다.

## Implement-Codex receipt

- 경로: `.claude/receipts/mccp-implement-codex/admin-rbac-m10.json` (커밋 대상 아님, 구현 커밋에 함께 싣는다)
- 플래그: `codex_verdict=divergent` · `security_skipped` (사유: Task 도구 없음) · `impeccable_silent_skip` (사유: no-signal, detector 가 서브모듈 UI 를 보지 못했다) · 설계 grounding 캡처 없음
- 계획 receipt 부재: `mccp-plan-codex` 가 없다. 사용자 지시 (2026-10-19 · 세션 기록 원문 `verify-user-directive.sh` exit 0) 에 따라 `MCCP_SKIP_RECEIPT=1` 로 preflight 를 통과시켰다 (`bypassed: true`, reason `MCCP_SKIP_RECEIPT`). `validate` 는 계획 receipt 를 여전히 missing 으로 보고 exit 2 다. 이 체인은 skip 이다.
- impeccable 설계 게이트 (2.5.5b): 신호 없음 (silent skip). 계획 시점의 `## Design Critique` (CONVERGED) 가 설계 검토를 맡았다.

## 격리 DB 준비 (메인 실행 전)

1. `docker run -d --name m10-iso-mysql -e MYSQL_ROOT_PASSWORD=<임의> -e MYSQL_DATABASE=insuniverse -p 127.0.0.1::3306 mysql:8.0.42 --log-bin-trust-function-creators=1 --sql-mode=""` (포트는 `docker port` 로 읽는다)
2. 순서대로 적재: `02-schema.sql` → `01-functions.sql` → `drizzle/0031_admin_audit.sql` → `drizzle/0033_admin_audit_index.sql` → `drizzle/0036_auth_permission.sql` → `ALTER TABLE tn_admin ADD COLUMN adm_is_super TINYINT NULL DEFAULT NULL, ADD UNIQUE KEY uq_tn_admin_adm_is_super (adm_is_super), ADD CONSTRAINT ck_tn_admin_adm_is_super CHECK (adm_is_super = 1)`
3. 환경 변수 `IT_MYSQL_PORT` · `IT_MYSQL_PASSWORD` · `IT_MYSQL_DATABASE=insuniverse` 를 넣고 `.claude/meta/2026-10-26-admin-rbac-m10/measure-m10.sh` 를 돌린다 (서버 워킹트리가 커밋으로 깨끗해진 뒤).
4. 끝나면 `docker rm -f m10-iso-mysql` (이름이 `m10-iso-` 인 것만).

이 구현 실행에서 만든 `m10-iso-mysql` 컨테이너는 시험 뒤 내렸다 (측정 산출은 없다). 위 절차로 다시 올린다.

## 메인 실행 요청 (구현 뒤 갱신)

- 실행: `bash .claude/meta/2026-10-26-admin-rbac-m10/measure-m10.sh` (위 준비 뒤). 산출은 같은 디렉터리에 쓰이고 매니페스트가 마지막이다.
- 그 뒤 `M10_PHASE=impl bash .claude/PRPs/plans/admin-rbac-m10.gates.sh` 를 다시 돌려 #20–#23 · #33 을 확인하고, 이 절 밑에 결과를 적는다.
- 서버 jest 회귀의 기준선 대조 (실패 · 보류 이름 집합) 는 메인이 착수 커밋 기준으로 한다 (미검증).

## 구현 보고 (계획 Phase 5 내용)

이 실행은 `.claude/PRPs/reports/` 아래 별도 보고 파일을 쓰지 않았다 (보고 파일 금지 규칙). 필요한 내용은 이 노트에 있다.

- 바뀐 파일 (서브모듈별):
  - 부모: `.claude/PRPs/plans/admin-rbac-m9f.gates.sh` (#8 범위 · #18 · #19 변이), `.claude/plans/codex-findings-backlog.md` (구현 뒤 짝 행 3건), `.claude/prds/admin-rbac.prd.md` (결정 로그 절), `.claude/meta/2026-10-26-admin-rbac-m10/` (측정 스크립트 · 격리 설정), `.claude/notes/admin-rbac-m10.md`.
  - insuniverse-server: 서비스 · 컨트롤러 · 판정 파일 (`permission-guardrail.ts` 신규, `permission-admin.service.ts` · `permission-admin.controller.ts` 수정), 활성 경로 (`admin.repository.ts` · `admin.service.ts` · `admin.dto.ts`), 출처 가드 (`origin-check.guard.ts` 신규), 단위 spec (`permission-guardrail.spec.ts` · `origin-check.guard.spec.ts` 신규, 기존 spec 여러 개의 호출 · 가짜 갱신), 격리 시험 (`permission-guardrail.itspec.ts` · `admin-edit-guardrail.itspec.ts` 신규, `test/m10-guardrail-fixture.ts` 신규).
  - insuniverse-admin: 화면 (`PermissionChecklist.tsx` · `PermissionImpactPanel.tsx` · `RolePermissionView.tsx` · `PermissionHistoryPage.tsx` 신규, `permissionApi.ts` · `permissionScreen.ts` 신규), 기존 화면 4 파일 (`DetailAuthorityPage.tsx` · `AddAuthorityPage.tsx` · `AuthorityPage.tsx` · `DetailAdminPage.tsx`), 라우트 (`security.routes.tsx`), `PRODUCT.md`.
- 격리 시험 30 건 passed · 변이 사본에서 판정 태그 붉음 · 단위 회귀 (위 로컬 실행).
- 남은 것 (사람 몫, (미검증)): 활성 전환 (스위치 ON · 모듈 등록), 복제 환경 1회 재생과 실패 0 건, 운영 would-deny 표, R2 · R10 · R16 활성 조건, 지정 행 확인, 메뉴 배분 병행 편집 제거, 발송 경로 · 조직 범위 · 영업 교차 노출 (백로그 2097 · 2112 · 2074 · 2075 · 1986).

## 코드 리뷰

- 라운드: R1 (code-review 1/3). 지적 28 건 (중복 병합 전) 을 판정했다. 흡수 18 · 기각 또는 백로그 9 · 미해소 1 (병합 전 28 건).
- HIGH/CRITICAL: 흡수 1 (상세 · 역할 조회의 버전 읽기 순서) · 미해소 1 (삭제 경로 가드, 메인 재확인 요청) · CRITICAL 미해소 0.
- 측정 영향: 격리 시험 두 파일의 B2 행위자를 바꿨고 단위 시험 여섯 건이 늘었다. 변이 실측의 붉음 건수는 이전 값과 다르다. 메인이 측정 전에 다시 돌려야 한다 (미검증).
- 프런트 행위 반사실은 시험 러너가 없어 돌리지 못했다. tsc (node 설정) 764 로 기준과 같고 eslint 는 오류 0 이다. 화면 행위는 (미검증).

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | code-review | HIGH | 관리자 삭제 경로에 가드가 없다 (rbac.manage 보유 계정을 비최고관리자가 지울 수 있다) | 미해소 (범위 밖 · 메인 재확인) | PRD 결정 로그 「삭제 경로의 가드는 이 마일스톤 범위 밖이다 (백로그)」 (`.claude/prds/admin-rbac.prd.md:427`) · 계획 Task 4 「잠금 순서만 바꾼다」 · 백로그 기존 HIGH 행 (architect 삭제 경로) | 1 |
| R1 | code-review · typescript · security | HIGH (typescript) · MEDIUM (나머지) | 상세 · 역할 조회가 데이터를 먼저, 권한 버전을 나중에 읽어 낡은 athId 에 새 버전이 붙는다 (L3 HIGH 와 같은 항목) | 흡수 (병합 3 건) | `admin.service.ts` `getDetail` 과 `permission-admin.service.ts` `getRole` 에서 버전을 먼저 읽는다. 반사실: `admin-detail-order.spec.ts` · `permission-admin.read-order.spec.ts` 수정 전 붉음 (`['detail','version']`, `['data',…]`) → 수정 뒤 녹색 | 2 |
| R1 | code-review | MEDIUM | 역할 내용 편집이 현재 집합을 행위자와 대조하지 않아, 고권한 계정의 역할을 강등한 뒤 그 계정의 자격증명을 바꾸는 우회가 열린다 | 흡수 | `permission-guardrail.ts` `decideRoleGuardrail` 에 B3 줄 추가 (현재 집합이 행위자 집합 밖이면 최고관리자만). 반사실: 「[B3] 행위자 집합 밖의 코드를 가진 역할은 줄이는 편집도…」 수정 전 붉음 → 수정 뒤 녹색 | 2 |
| R1 | code-review | MEDIUM | 레거시 권한 그룹 삭제(`authority-adimin.controller.ts`)가 rbac.manage 역할을 비최고관리자에게 열어 둔다 | 백로그 (R2 · #96 이연) | 계획 R2 행 「권한 그룹 쓰기의 최고관리자 한정은 #96 에서 즉시」 · PRD 승계 전 즉시 차단 (R2) · 백로그 MEDIUM 신규 행 | 1 |
| R1 | code-review · typescript | MEDIUM | 스위치 ON 역할 편집 화면이 이름 · 메인노출 입력을 조용히 버리고, 저장 중 중복 요청이 두 번째에 409 가 된다 | 흡수 (ON 에서 두 칸 숨김 · 저장 중 가드, 병합 2 건) | `DetailAuthorityPage.tsx`: 두 칸을 `!PERMISSION_SCREEN_ENABLED` 아래로 · ON 저장에 `loading` 가드와 `setLoading`. 기능 공백 (ON 에서 이름 · 메인노출 편집 경로 없음) 은 편차 15 와 사람 몫에 적었다. 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 1 |
| R1 | code-review · typescript | MEDIUM | 신규 역할 화면이 권한 저장을 건너뛰거나 실패해도 성공 알림을 띄운다 | 흡수 (병합 2 건) | `AddAuthorityPage.tsx`: `failMessage` 분기로 실패 시 성공 알림 없이 안내 후 목록으로. 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 2 |
| R1 | code-review | MEDIUM | 스위치 ON 에 역할 메뉴 표 편집 화면이 없어 메뉴 노출을 ON 에서 바꿀 수 없다 | 백로그 (활성 전 결정) | 노트 C4 「메뉴 배분 병행 편집 제거 (스위치 ON 전환 조건)」 · 백로그 MEDIUM 신규 행 | 1 |
| R1 | code-review | LOW | binding · 계정 판정이 최고관리자 전용 코드를 보지 않아 역할 편집과 비대칭이다 | 백로그 (정책 결정) | `permission-admin.service.ts:147-149` (역할 편집만 `requireSuper`) · `permission-guardrail.ts` 계정 판정 (rbac.manage 만) · 상승 폭 없음 (B4 상한 안) | 1 |
| R1 | code-review | LOW | 권한 관리 컨트롤러의 SuperAdminGuard 가 비최고관리자 규칙을 HTTP 로 막지 못하게 한다 | 기각 (설계 기록) | 계획 M4 계약 · 활성 전환 설계 기록 (백로그 LOW 신규 행) | 1 |
| R1 | code-review | LOW | 계정 상세의 권한 묶음 값이 NaN 이면 JSON 에서 null 이 되어 binding 회수로 읽힌다 | 흡수 | `DetailAdminPage.tsx` ON 분기: `Number.isInteger` · 양수 가드 후에만 `setBinding` 호출. 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 1 |
| R1 | code-review | LOW | 권한 묶음 저장 뒤 같은 화면의 이름 · 비밀번호 입력이 사라진다 | 백로그 | 구현 편차 7 (두 쓰기 분리, 알림 「나머지 정보는 다시 저장」 이미 뜸) · 백로그 LOW 신규 행 | 2 |
| R1 | code-review | LOW | 이력 조회 실패가 「기록이 없다」로 보인다 (50건 쪽 이동 없음은 별도) | 흡수 (실패 상태) · 쪽 이동은 백로그 | `PermissionHistoryPage.tsx`: `null` = 불러오지 못함 분기. 쪽 이동 · 상한은 백로그 LOW 신규 행 | 2 |
| R1 | code-review | LOW | 최고관리자 행의 사유가 칸 아래 텍스트로 나와 메모 규칙 3 에 어긋난다 | 흡수 | `DetailAdminPage.tsx` ON: `infoMsg` 를 `params.title` (툴팁) 으로 옮김 · `AddDetail.tsx` select 에 `title` 전달 (다른 select 는 title 을 주지 않아 바뀌지 않는다). 툴팁 육안 확인은 (미검증) | 1 |
| R1 | code-review | LOW | 변경 영향 패널 · 이력 상세 패널의 card 클래스가 새 테두리를 만든다 | 흡수 | `PermissionImpactPanel.tsx` · `PermissionHistoryPage.tsx`: `card card-body` → `p-3`. 검사 #27 은 border 선언만 보므로 이 클래스는 잡지 못한다 (층위 6, 사각지대로 기록) | 6 |
| R1 | code-review | LOW | 바뀜 점이 aria-hidden 이라 보조 기술이 변경을 알 수 없다 | 흡수 | `PermissionChecklist.tsx`: 바뀐 칸에 `sr-only` 「바뀜」 추가 (점은 aria-hidden 유지) | 1 |
| R1 | code-review | LOW | 영향 미리보기 `codes` 가 같은 키 반복 (배열) 이면 TypeError 로 500 | 흡수 | `permission-admin.controller.ts` `impact()`: 문자열이 아니면 400. 반사실: 「영향 미리보기의 codes 가 같은 키 반복 (배열) 이면 500 이 아니라 400 이다」 수정 전 붉음 → 수정 뒤 녹색 | 1 |
| R1 | code-review | LOW | binding 의 athId 가 역할 목록에 있는지 검사하지 않아 FK 오류가 500 으로 나간다 | 백로그 (비활성 경로) | `permission-admin.service.ts` `setBinding` · 활성 전환 체크 · 백로그 LOW 신규 행 | 1 |
| R1 | code-review | LOW | 계획 Files 표와 Validation 의 시험 파일명 `admin-edit-guardrail.spec.ts` 가 실제 `.itspec.ts` 와 다르다 | 흡수 (노트 정정) · 계획서 행은 동결이라 백로그 | 노트 「메인 실행 요청」 1) 을 `.itspec.ts` 로 정정. 계획 행 (plan:142) 은 다음 계획 반영 백로그 LOW 행 | 6 |
| R1 | code-review | LOW | 목록 화면에서 역할별 권한 조회로 가는 진입점이 없다 (계획 Task 8 (b) 편차) | 백로그 | 기능 누락 아님 · 백로그 LOW 신규 행 | 2 |
| R1 | security | MEDIUM | `mutant-m10.mjs` 세 번째 앵커가 현재 소스 서식과 맞지 않아 변이 사본을 못 만든다 (노트 19 건 붉음 값은 옛 서식) | 흡수 | 정규식 앵커 + 앵커별 일치 수 인쇄. 반사실: 원 앵커 3 의 일치 수 0 을 재현했고 (node 확인), 새 스크립트는 세 앵커 모두 1 이며 임시 사본 생성 성공 (변이 시험 실행은 하지 않았다) | 5 |
| R1 | security | LOW | 이력 화면이 활성 경로 변경을 싣지 않고 binding 감사 행에 요청 맥락이 없다 | 백로그 (활성 전 조건) | `permission-admin.service.ts` `history` · `setBinding` 감사 행 · 백로그 LOW 신규 행 | 1 |
| R1 | security | LOW (실질은 라이브 경로 회귀) | B2 가 보유자가 아닌 모든 행위자에게 걸려, 일반 관리자가 본인 비밀번호를 바꾸면 403 이 된다 | 흡수 (PRD 결정 B 2 의 보유자 범위로 좁힘) | `permission-guardrail.ts` `decideAccountGuardrail` B2 조건에 `actor.perms.has(RBAC_MANAGE)`. 반사실: 「[B2 범위] rbac.manage 를 갖지 않은 행위자는 자기 비밀번호를 바꿀 수 있다」 수정 전 붉음 → 수정 뒤 녹색. 계획 잠정 결정 행 (plan:80) 과 다르므로 편차 11 | 1 |
| R1 | security | LOW | Origin 가드가 CORS 허용 목록 전체를 신뢰하고 목록이 비면 전부 403 이 된다 | 백로그 (비활성 경로) | `origin-check.guard.ts:27` · 활성 체크리스트 (R16 소유자 지정, 노트 C4) · 백로그 LOW 신규 행 | 1 |
| R1 | typescript | MEDIUM | 권한 표가 없는 이행기에 계정 생성이 500 (`ER_NO_SUCH_TABLE`), 자격 변경은 409 가 된다 | 흡수 (명시적 503) | `permission-guardrail.ts` `readPermSet`: 표 부재면 `ServiceUnavailableException(PERMISSION_VERSION_UNAVAILABLE)`. 빈 집합으로 읽으면 판정이 조용히 통과하므로 그렇게 하지 않았다. 반사실: 「권한 표가 없으면 빈 집합으로 읽지 않고 503 이다」 수정 전 붉음 → 수정 뒤 녹색. 배포 순서 전제 (0036 DDL 선행) 는 노트 C4 에 추가 | 1 |
| R2 | code-review | MEDIUM | 동급 계정 비밀번호 변경이 활성 편집 경로에서 허용된다 (같은 권한 집합의 동료 계정으로 로그인 가능) | 백로그 (사용자 확인) | PRD 결정 B 3 문구 (고권한 = rbac.manage 보유 또는 요청자보다 넓은 역할) 를 그대로 구현했다 (`permission-guardrail.ts:108-112`). 권한 상승이 아니다 (동급 집합). 백로그 행 2026-10-26 MEDIUM | 1 |
| R2 | code-review | MEDIUM | 정책이 말한 두 겹 중 rbac.manage 겹이 코드에 없다 (`setBinding` · `setRolePermissions` 에 행위자 검사 없음) | 흡수 (활성 전환 체크리스트에 명시, 위 「C4 사람 몫」) | 컨트롤러 가드는 `SuperAdminGuard` 하나이고 모듈 미등록으로 도달하지 않는다 (`permission-admin.controller.ts:58`). 판정 로직은 바꾸지 않았다. 활성 전 결정 항목으로 옮겼다 (문서 항목, 반사실 해당 없음) | 2 |
| R2 | code-review | MEDIUM | 스위치 ON 에서 권한 묶음을 회수할 수 없다 (칸에 회수 선택지가 없고, 현재 묶음이 없는 계정은 ON 에서 다른 경로로 저장된다) | 흡수 | `DetailAdminPage.tsx`: 스위치 ON 에만 「- 회수 -」(값 `0`) 옵션을 더했고, 칸 값 `0` 이면 `setBinding(null)` 로 보낸다. 분기 조건을 「칸 값이 처음 값과 다르다」 로 바꿔 묶음이 없던 계정도 binding 경로를 탄다. 반사실: 프런트 시험 러너 없음 → admin tsc 764 동일 · 관련 파일 오류 0 · eslint 오류 0 (행위는 미검증) | 2 |
| R2 | typescript | MEDIUM | 역할 상세의 권한 묶음 읽기 실패가 조용하고, 낡은 버전이 남아 저장이 409 로 오해를 부른다 | 흡수 | `DetailAuthorityPage.tsx`: 읽기 실패 시 「권한 묶음을 불러오지 못했다.」 를 보이고 저장을 막는다 (`permFailed`). 저장 뒤 재조회가 실패해도 같다. 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 2 |
| R2 | security | LOW | tsbuildinfo 산출물이 어드민 서브모듈에 남아 있다 (`tsconfig.node.tsbuildinfo`, 643KB, 노트 기록과 어긋남) | 흡수 | 파일을 지웠다. 게이트 #28 의 tsc 명령에 `--tsBuildInfoFile` 을 넣어 산출을 트리 밖으로 돌렸다 (검사 번호 · 기준 764 는 그대로). 재실행 결과 764 로 같고 트리에 파일이 생기지 않았다 | 5 |
| R2 | typescript | LOW | 같은 항목의 중복 지적 (tsbuildinfo, 위와 같다) | 흡수 (위 행과 병합) | 위 행 | 5 |
| R2 | code-review | LOW | PRD 결정 로그가 측정 전 결과를 완료 사실로 적었다 (판정 태그가 붉다) | 흡수 | `admin-rbac.prd.md` M10 절: 30건 통과는 로컬 실행으로, 판정 태그가 붉은 건수는 R1 수정 뒤 메인 재측정 전까지 (미검증) 으로 고쳤다. 반사실: 문서 항목 (검사 없음) | 2 |
| R2 | typescript | LOW | 권한 버전을 못 읽으면 자격증명 변경이 「expectedVersion 이 필요하다」 400 으로 나가 원인을 가린다 | 흡수 | `permission-guardrail.ts` `assertVersionMatch`: 버전 null 검사를 누락 검사보다 먼저 한다 (→ 409 `PERMISSION_VERSION_CONFLICT`, 「권한 버전을 읽지 못했다」). 반사실: 「[동시] 권한 버전을 못 읽으면 expectedVersion 이 없어도 409 다」 를 수정 전 실행 → BadRequest 로 붉음, 수정 뒤 녹색. 단위 묶음 `permission` · `admin` 19 suites 155 passed (격리 시험은 미실행, 메인 측정 몫) | 1 |
| R2 | typescript | LOW | 신규 역할 화면이 권한 저장 전에 로딩을 풀어 두 번째 제출이 같은 이름의 역할을 하나 더 만든다 · 실패 문구가 버전 null 과 역할 중복을 섞는다 | 흡수 | `AddAuthorityPage.tsx`: 권한 쓰기가 끝난 뒤에 로딩을 풀고, 실패 문구를 「역할 수 ≠ 1」 · 「역할 읽기 실패」 · 「버전 null」 · 「저장 실패」 로 나눴다. 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 1 |
| R2 | typescript | LOW | 서버 계약 필드가 캐스트와 `any` 로만 프런트에 연결된다 (`expectedVersion`, `permissionVersion`, `writeFail(err: any)`) | 흡수 | `adminApi.ts`: `EditAdminParam.expectedVersion` · `AdminDetailReturn.permissionVersion` 을 더했다. `permissionApi.ts`: `writeFail(unknown)` + `isAxiosError`. 반사실: admin tsc 764 동일 (타입 연결이 깨지면 증가) | 2 |
| R2 | code-review | LOW | 충돌 (409) 뒤 화면이 낡은 버전으로 다시 저장한다 | 흡수 | 계정 상세 (권한 묶음 · 정보 저장) 와 역할 상세가 `PERMISSION_VERSION_CONFLICT` 일 때 다시 불러온다 (`PermissionWriteResult.conflict` 신설). 반사실: 프런트 시험 러너 없음 → 정적 확인 (미검증) | 1 |
| R2 | code-review | LOW | 역할 상세의 불러오기 실패 상태가 없고, 계정 상세의 저장 실패가 서버 원문을 그대로 보인다 | 흡수 (앞 절) · 서버 원문 부분은 기각 | 불러오기 실패 상태는 위 MEDIUM 행과 함께 흡수했다. 서버 원문 부분은 기각한다: 계정 상세는 최고관리자 보호 사유 (E0827) 를 서버 문구로 보이는 것이 설계이다 (`DetailAdminPage.tsx` 주석 「서버가 사유를 주면 그것을 보인다」). 영향 미리보기 빈 패널은 저장 비활성과 함께 낡은 정보를 쓰지 않으므로 이연한다 | 2 |
| R2 | code-review | LOW | 최고관리자 사유와 고위험 표지가 hover (`title`) 에서만 보인다 (비활성 칸은 포커스를 받지 않는다) | 이연 (LOW, 백로그 미기록) | 메모 `admin-ui-design-rules` 8 번은 정보를 포커스 · 호버 · 팁에 두라고 한다. 비활성 칸의 키보드 · 보조기술 접근은 칸 밖 대체 표시 설계가 필요하다. 활성 전 화면 접근성 점검에서 다룬다 | 6 |
| R2 | typescript | LOW | `PermissionChecklist` 의 영역 표 · 고위험 집합을 서버 상수에서 복사했고 대조 검사가 없다 | 기각 | 프런트 시험 러너가 없다. 서버 응답에 `area` · `highRisk` 를 싣는 것은 계획 Task 8 (b) 범위 밖이다. 현재 접두 30개 · 고위험 집합은 서버와 같다 (R1 · R2 대조) | 6 |
| R2 | typescript | LOW | 새 서비스 메서드 · 컨트롤러 GET 에 명시 반환 타입이 없다 | 기각 | 계약 테스트 없이 반환 타입만 더해도 드리프트를 잡지 못한다. 응답 계약 검사는 활성 전 과제로 둔다 | 6 |

### R1 흡수에 따른 편차 (계획서는 동결이라 고치지 않았다)

11. **B2 의 범위.** 계획 잠정 결정 행 (`plan:80`) 은 모든 행위자에게 자기 계정 변경을 막았다. 그 행은 PRD 결정 B 2 를 인용하지만 PRD 문구는 「보유자」다 (`prds/admin-rbac.prd.md:256`). 모든 행위자에게 걸면 일반 관리자가 본인 비밀번호를 바꾸지 못하는 라이브 경로 회귀가 된다. 그래서 PRD 문구에 맞췄다. 격리 시험 두 파일 (`permission-guardrail.itspec.ts` · `admin-edit-guardrail.itspec.ts`) 의 B2 행위자를 보유자 adm 4 로 바꿨다. 이 두 파일은 실행하지 않았다 (미검증, 측정 몫).
12. **역할 편집의 B3 줄.** 현재 집합이 행위자 집합 밖이면 줄이는 편집도 최고관리자만 한다. 반례 a · B1 · B4 와 함께 판정 순서는 반례 a → B1 → B3 → B4 다.
13. **권한 표 부재.** 빈 집합으로 읽지 않고 503 (`PERMISSION_VERSION_UNAVAILABLE`) 으로 쓰기를 롤백한다. 자격 변경의 409 (`assertVersionMatch(null)`) 는 기존 설계대로 둔다.
14. **상세 · 역할 조회의 읽기 순서.** 권한 버전 → 데이터. 중간 변경은 옛 버전이라 저장이 409 로 막힌다.
15. **ON 전환 전 사람 몫 추가.** (a) 0036 DDL 을 코드보다 먼저 적용 (이행기 503 회피) · (b) 스위치 ON 에서 역할 이름 · 메인노출 편집 경로 결정 · (c) 역할 메뉴 표 편집 경로 결정 (R1 MEDIUM 항목과 같은 결정).

### R2 흡수에 따른 편차 (계획서는 동결이라 고치지 않았다)

16. **게이트 #28 의 tsc 명령.** `--tsBuildInfoFile` 을 트리 밖 임시 경로로 돌린다 (`gates.sh` 의 c28). 검사 번호 · 기준 764 · 판정은 그대로다. 재실행 764, 트리에 tsbuildinfo 가 생기지 않았다.
17. **버전 null 검사의 순서.** `assertVersionMatch` 는 버전을 못 읽으면 누락 400 보다 먼저 409 를 던진다 (편차 13 의 「409」 가 화면 경로에서도 맞도록). 단위 시험 한 건을 추가했다 (수정 전 붉음).
18. **회수 칸 값.** 스위치 ON 의 「- 회수 -」 칸은 값 `0` 이다. 서버 계약 (`athId` null = 회수) 은 바꾸지 않았다.

### 메인 측정 요청 (R2 뒤)

- R2 흡수로 서버 `permission-guardrail.ts` (버전 순서) 와 격리 시험 파일 밖의 단위 시험이 바뀌었다. 격리 DB 로 `permission-guardrail.itspec.ts` · `admin-edit-guardrail.itspec.ts` 를 다시 돌려 30건을 확인한다 (미검증, 이 라운드는 측정하지 않았다).
- 변이 사본의 판정 태그 붉음 건수를 다시 잰다 (R1 · R2 뒤 값이 바뀐다).
- 측정 전에 관리자 워킹트리를 깨끗하게 만든다 (tsbuildinfo 는 지웠다).

### 메인 판정 요청

- 삭제 경로 가드 (HIGH, 첫 행): PRD 결정 로그가 범위 밖으로 적었고 백로그 기존 행 (architect 삭제 경로) 과 같은 항목이다. 이연이 맞는지 재확인을 요청한다. 맞다면 흡수하지 않고 다음 계획에 반영한다.
- 측정 (검사 #20–#23 · #33) 은 메인 몫이다. 이번 리뷰에서 돌리지 않았다.
- 게이트 점검 (impl 단계, 리뷰 뒤): #24 · #25 · #26 · #27 · #28 · #29 · #31 · #32 통과. #20–#23 · #33 은 측정 머리가 없어 실패 (예정). #30 은 계획 시점 m9f 검사 #1 · #5 가 워킹트리 더러움으로 붉은 상태 그대로다 (커밋 뒤 녹색).

## 최종 리뷰

- 라운드: R1 (최종 리뷰 1/3). 지적 8 건 (fable 5 · codex 3) 을 판정했다. 흡수 3 · 기각 또는 백로그 5.
- HIGH/CRITICAL: HIGH 1 (계정 삭제 경로의 가드, codex) 은 이연 유지로 판정했다. CRITICAL 미해소 0.
- 측정 영향: 이 라운드의 코드 변경은 게이트 사이드카 (#21) 뿐이다. 서버 · 관리자 트리는 바꾸지 않았으므로 측정 (RUN_ID 20261026T144801Z-b22de405) 은 다시 돌리지 않았다.
- 검증 (이 라운드): `M10_PHASE=impl` FAILS=0 RAN=14 (rc 0) · `M10_ONLY=21` PASS. `M10_PHASE=plan` FAILS=1 (#1 착수 sha · 워킹트리 청결 — 착수 sha 이동은 기존 기록과 같고, 워킹트리 부분은 이 커밋 뒤 다시 본다 (미검증)).
- 서버 jest · 관리자 tsc 는 돌리지 않았다 (두 서브모듈 트리 변경 없음).

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable 1) | MEDIUM | 권한 버전 검사가 최고관리자 면제보다 앞이라, 서버만 올라가고 관리자 번들이 옛 것인 창에서 최고관리자의 자격증명 변경도 400 · 409 · 503 으로 막힌다. 계획 Risk 1 · Acceptance 가 배포 순서를 적지 않는다 | 흡수 (부분). 노트 C4 「배포 순서」 에 같은 배포 창 · 0036 DDL 선행을 적었다. 계획 Risk 1 · Acceptance 문장은 동결이라 백로그 이월 | `admin.repository.ts:172-173` (버전 검사가 판정보다 앞) · `permission-guardrail.ts:87` (최고 면제는 뒤) · `assertVersionMatch` 324-332. 코드 변경 없음 (fail-closed 유지). 반사실: 문서 항목 | 2 |
| R1 | fable 2) | MEDIUM | 검사 #21 이 시험이 쓴 `tn-expected-delta.tsv` 를 같은 `comm -3` 로 다시 계산해 비교한다 (동어반복). F3 의 실질 증명은 시험 expect 한 자리뿐 | 흡수. 게이트가 고정 리터럴 (계정 3 athId 3→5 · 역할 0 버전 0→2 · 역할 5 에 user-admin.read) 과 비교하도록 바꿨다. 리터럴은 시험의 expect 와 같다. 계획 F3 문구는 동결이라 그대로 두었다 (리터럴 비교가 증명이 된다) | `.claude/PRPs/plans/admin-rbac-m10.gates.sh` c21. 반사실: after 사본의 계정 3 athId 를 2 로 바꾸면 리터럴 비교가 불일치 (실행). `M10_ONLY=21` PASS | 6 |
| R1 | fable 3) | LOW | 노트 · 로컬 실행 기록이 반례 a 를 「붉지 않음」 으로, 편차 4 가 같은 것을 적었다. 상태 줄은 「커밋 전」 이다. PRD 결정 로그 (prd.md:426) 는 붉은 건수를 「미검증」 으로 적는다 | 흡수 (노트 4 곳 정정: 상태 줄 · 메인 실행 결과 · 로컬 실행 · 편차 4). PRD 결정 로그는 PRD 가 B2 의 4 곳 밖이고 결정 로그라 이 커밋에서 고치지 않았다 (메인 판정 때 정정) | `jq` 로 `jest-neg-m10.json` 을 항목별로 다시 셌다: 붉음 19 (B1 3 · B2 2 · B3 2 · B4 4 · 반례 a 1 · 반례 b 2 · 동시 4 · 최고 1), 통과 11 | 2 |
| R1 | fable 4) | LOW | 검사 #22 의 「새 쓰기 자리가 생기면 #22 가 붉다」 는 drizzle 빌더 호출만 센다. 원시 SQL `UPDATE tn_admin` 과 `*.itspec.ts` 는 범위 밖이다. 현재 원시 쓰기는 seed 한 곳 (adm_is_super) 이고 빌더 3 은 맞다 | 백로그 (LOW). 현 산출에는 영향이 없다. 계획 완전성 주장 (plan:85) 의 범위를 다음 계획에서 좁히거나 검사를 넓힌다 | `.claude/PRPs/plans/admin-rbac-m10.gates.sh` c22 · `insuniverse-server/apps/api/src/modules/admin/admin.repository.ts` 등 빌더 호출 3 | 6 |
| R1 | fable 5) | LOW | 측정 매니페스트를 경로 가림 뒤 사람이 다시 썼다. `header_ok` 의 해시 대조는 같은 주체가 다시 서명하면 통과한다 (자기 증명) | 기록만. 수치는 시험별 목록과 대조해 맞다 (fable 3 의 재집계). 조치 없음, 다음 측정 절차에서 스크립트가 끝까지 쓴다 | `.claude/meta/2026-10-26-admin-rbac-m10/manifest-m10.sha256` · gates.sh `header_ok` | 7 |
| R1 | codex | HIGH | 관리자 삭제 경로 (`POST admin/admin/delete`) 가 가드를 거치지 않는다. 비최고관리자가 rbac.manage 보유 계정을 지울 수 있다 | 이연 유지 (재확인 완료). 백로그 기존 행 (architect HIGH, 2026-10-26) 과 같은 항목이라 다시 적지 않았다. 근거: 삭제 경로는 M10 이전 (`ea3c0359` 의 `admin.repository.ts` 에도 `async delete`) 부터 있고, PRD 결정 로그 「삭제 경로의 가드는 이 마일스톤 범위 밖이다 (백로그)」 (prd.md:427) 가 이연했다. 삭제 규칙 (B1 · B3 유추인지) 은 PRD 가 정하지 않았다. 서버 변경은 측정 머리 SRV_TREE 를 바꿔 재측정을 요구한다 | 1 |
| R1 | codex | MEDIUM | 스위치 ON 에서 비노출 역할 생성이 항상 실패한다. `AddAuthorityPage` 가 빈 권한 코드 배열을 보내고, 서버 `AuthorityRepository.create` 가 빈 배열로 메뉴 매핑을 insert 해 drizzle 이 던져 역할 생성이 롤백된다 | 백로그 (MEDIUM, 스위치 ON 전 해소). 서버 변경은 재측정을 요구한다. 역할 생성은 M10 Outcome 다섯 화면 밖이라 동결 완료 조건의 증명이 거짓은 아니다. 근거 확인: drizzle `mysql-core/query-builders/insert.js:21` 이 빈 values 를 던진다 | `insuniverse-admin/src/pages/Admin/Security/Authority/AddAuthorityPage.tsx:65` · `insuniverse-server/apps/api/src/modules/authority/authority.repository.ts:64-74` | 1 |
| R1 | codex | MEDIUM | 스위치 ON 계정 상세에서 처음 값이 없는 계정의 「- 회수 -」 는 값 `0` 이 처음 값 `""` 과 달라 항상 binding 변경으로 읽힌다. 그 뒤 이름 · 연락처 · 비밀번호 저장은 가지 않는다. 빈 칸을 두면 잘못된 athId 가 legacy update 로 나간다 | 백로그 (MEDIUM, 스위치 ON 전 해소). 프런트 행위 결함이고 권한 상승 · 가드 우회가 아니다. 행위 시험 러너가 없어 정적 확인만 가능하다 | `insuniverse-admin/src/pages/Admin/Security/Admin/DetailAdminPage.tsx:55-58` | 5 |

### 메인 판정 요청

- 삭제 경로 가드 (HIGH): 이연 유지로 판정했다. 뒤집으려면 삭제 규칙을 정하고 서버를 바꾼 뒤 측정을 다시 돌려야 한다. 이 판단은 사용자 몫이다.
- 배포 순서 (노트 C4 「배포 순서」) 는 사람 몫이다 (미검증).
- PRD 결정 로그 426 줄 「붉은 건수 … 다시 재야 한다 (미검증)」 는 낡았다 (측정 19 붉음이 기록돼 있다). PRD 는 이 커밋에서 고치지 않았다.
