# 관리자 RBAC 마일스톤 9d · 보완 3차 기록 (admin-rbac-m9d)

- 계획: `.claude/PRPs/plans/admin-rbac-m9d.plan.md` · 검사: `.claude/PRPs/plans/admin-rbac-m9d.gates.sh`
- 산출 디렉터리: `.claude/meta/2026-10-23-admin-rbac-m9d/` (구현 단계에서 만든다)
- 원장 구획: `.claude/state/santa-ledger.md` 의 「마일스톤 9d」 (gitignore 대상)

## 마감 요약

- 판정: 행 9 (영역별 강제) 는 **in-progress 를 유지한다**. 상태 열과 문구는 바꾸지 않았다 (검사 #37 이 행 9 줄의 불변을 본다). 보완 계획의 측정 · 검사 · 리뷰는 끝났지만, 격리로 할 수 있는데 하지 않은 증명 항목이 남아 complete 로 올리지 않았다.
- 게이트: 검증 스크립트 (사이드카) 38/38 PASS, rc 0. 마감 직전 워킹트리에서 재확인했고, 백로그 · PRD 를 고친 뒤 커밋 전에 한 번 더 돌린다.
- 측정 (격리 MySQL · 합성 데이터, 재생 셀 394): 의도되지 않은 소실 0. 소실 28 셀은 전부 「의도된 범위 거부」 (영업 신원의 교차 셀 ON 403). 0행 축소 0 (I9 팩스 목록은 길이를 재지 못해 제외, 미검증). 허용 방향 (기대 403 이 ON 에서 2xx) 0. OFF 재실행은 OFF 와 전 셀이 같다. 2xx 에서 항목 수가 준 부분 축소 14 건은 전부 범위 축소이고, 관리자 신원의 축소는 0 이다.
- 교차 경계: ON 에서 닫힌 교차 셀은 영업 신원 28 셀뿐이다. 기업 · 조직 관리자의 교차 셀 56 과 형제 조직 셀 14 는 OFF 가 이미 403 이라 ON 의 거부를 본 적이 없다.
- 소비자 대응표: 9 흐름 × {OFF, ON} 의 code 는 전부 같다. 그러나 2xx 로 정상 경로에 닿은 흐름은 3 개 (토큰 발급 · 결제 승인 · 결제 취소, 결제 두 건은 가드 통과만 증명) 이고, 나머지 6 은 미증명이다.
- 격리 itspec: 12 파일 · 123 시험 통과 (실패 0, 보류 0). 형제 픽스처를 뺀 스냅샷에서 돌렸다 (목록 필터 장면 미실행, 아래 미달 5).
- 회귀 jest (기준 커밋 3418d3a0 의 트리에서 같은 명령으로 재측정): 2860 시험 · 통과 2802 · 실패 35 · 보류 23. 실패 · 보류 이름 집합이 기준선과 차이 0. 실패 원인: Redis 미연결 32 (dispatch 3 suite) · 기존 결함 1 (drizzle 오류 래퍼가 500 본문에 원본 mysql 코드를 싣는다) · 픽스처 부재 2 시험 + suite 3.
- code-review: 2 라운드. R1 15 행 (HIGH 5 행: 흡수 3, 부분 흡수 2 는 백로그 HIGH 행으로 넘김). R2 지적 19 건, HIGH 0 (흡수 · 기각 · 기존 백로그 행과 중복으로 판정).
- 최종 리뷰 라운드 요약: `[{"r":1,"found":6,"absorbed":4,"high_absorbed":0,"high_open":0,"codex":"ok"}]` (fable 3 · codex 3 지적, 흡수 4 · 기각 2, HIGH 미해소 0). 흡수 대상은 검증 스크립트 · 측정 스크립트 · 측정 원자료 재생성 절차다. 계획서 · receipt 는 고치지 않았다.
- PRD 미달 (격리로 할 수 있는데 하지 않은 것 → 백로그 HIGH 5 행, `→ 다음 계획 반영 대상 · 보완 계획 admin-rbac-m9db`):
  1. 소비자 미증명 6 흐름 (site admin-view 공개 · 앱 refresh · 쿠콘 콜백 · MOK 콜백 · 스케줄러 push · 스케줄러 알림톡). 필요한 입력: 발급 토큰 전달 · 저장 refresh 토큰 · 스케줄러 만기 행 시드 · 콜백 입력.
  2. 기업 · 조직 관리자의 교차 셀 56 · 형제 조직 셀 14 의 ON 경계. OFF 가 막고 있어 전이로는 증명되지 않는다.
  3. I9 팩스 목록의 목록 길이 (0행 축소 조항). 응답 배열이 list · data 별칭으로 나뉘어 원자료에 길이가 없다.
  4. 목록형 행 8 과 AO 행 15 의 교차 기업 셀과 목록 항목 소유자 검사 (자기 셀만 쟀다).
  5. 형제 픽스처를 넣은 스냅샷의 itspec 재실행 (9303 을 쓰는 머니 목록 · 지갑 이력 · 결제 · 조직 경로 장면).
- 미검증 · 사용자 몫 (defer-unverified 라 complete 를 막지 않는다): 운영 동일 환경 복제본 1회 재생 · 실패 0건 (복제본 생성은 사람 승인) · 칸 · 표 부재 판정의 운영본 확인 · would-deny 판단 표 실내용 · R2 · R7 · R19 · 스위치 3개와 범위 상수 1개의 ON 커밋 · 동시 판정 상한 부하 실측 · OFF 상태 영업 교차 노출 28 셀의 수용 여부 (백로그 HIGH 행, 소유자 사용자) · 외부 연동 송신은 미측정 (치환은 설정 기준, 「외부 실서버 호출 0」 은 미검증) · 측정 산출의 고객사명 원문 (실명 탐색은 자동 검사가 없다).
- 다음 마일스톤이 알아야 할 것:
  1. 다음 보완 계획 이름은 admin-rbac-m9db (마감 지시). PRD 마감 판정과 2026-10-26 백로그 PARTIAL 행의 「admin-rbac-m9e」 는 제안명이며 같은 계획을 가리킨다.
  2. 계획 게이트 receipt 는 없다. 상한 3/3 에서 divergent (CRITICAL 없음, HIGH 는 백로그) 였고, 구현 진입은 `MCCP_SKIP_RECEIPT` 를 preflight 명령 하나에만 걸어 열었다. 사용자 지시 원문은 검증 스크립트로 확인했다 (exit 0, 2026-10-19T02:21Z). 구현 receipt 는 R3 divergent (needs-attention) 로 쓰였다.
  3. 회귀 jest 검사는 실패 · 보류 이름과 전체 시험 수만 본다. suite 집합과 통과 시험 이름은 보지 않는다 (백로그 MEDIUM).
  4. 재생 (픽스처 포함) 과 itspec (픽스처 없음) 은 데이터 스냅샷이 다르다. 두 결과를 한 표에 섞어 읽지 않는다.
  5. 서버 코드 변경 0 (기준 커밋 3418d3a0 그대로). 이 마일스톤이 바꾼 것은 측정 스크립트 · 원자료 · 문서뿐이다. 측정 산출 색인은 `.claude/meta/2026-10-23-admin-rbac-m9d/README.md`. 원본 JSON 과 음성 사본은 git 디렉터리 아래에 있고 커밋하지 않았다.
  6. 격리 실행 함정: 실행기에서 `docker exec -i` 가 `while read` 루프의 stdin 을 먹었다 (파일 목록이 루프 밖으로 새는 자리). 목록을 fd 3 으로 읽게 고쳤다. 재실행 결과는 앞 실행과 같았다.
  7. 회귀 jest 선택 패턴 `(permission|admin|menu|guard)` 은 절대 경로에 적용되어 서버 suite 245 개 전부를 고른다. 기준선과 같은 집합이라 비교는 성립하지만 「묶음」 이라는 표현은 틀렸다 (백로그 LOW).
  8. 스위치 OFF 상태에서 영업 신원 2명은 다른 기업 · 조직 대상에 쓰기 셀을 포함한 28 셀에서 현재 2xx 를 받는다. 스위치를 켜기 전 사용자 결정이 필요하다.

## 착수 기록

구현 시작 때 채운다 (Task 1).

- 부모 기준 커밋: `83ac506` (계획 커밋 직전 HEAD, feat/admin-rbac)
- 서버 `insuniverse-server`: `3418d3a0` (feat/admin-rbac)
- 어드민 `insuniverse-admin`: `1301b664` (feat/admin-rbac)
- 사이트 `insuniverse-site`: `fc8872a1` (feat/admin-rbac)
- 착수 sha 조상 검사: 검사 #2 (계획 시점 녹색)

## 설계 신호

서버 전용 계획이다. 화면 파일을 바꾸지 않으므로 impeccable 설계 신호는 0 으로 본다 (5.0 검출 대상 아님, 계획서에 `## Design` 절을 두지 않는다).

## 계획 시점의 판정 기록

계획 커밋 직전 워킹트리에서 사이드카를 한 번 돌렸다 (`bash .claude/PRPs/plans/admin-rbac-m9d.gates.sh`). 측정 산출이 없으므로 측정 검사는 붉다. 그 붉음은 구현 전 관측이다.

- 계획 시점 녹색 (16개): #1–#6 · #15–#24. 계획 · 노트 · 원장 구획 · 짝 행을 계획 커밋 직전 워킹트리에서 확인했다. R1 시점의 「21개」 는 목록 셈 오류였고 R2 에서 16 으로 고쳤다.
- 구현 전 붉음 (21개): #7–#14 · #25–#37 (측정 산출 · 측정 스크립트 · 원자료 · 결과 표 · 대응표 · itspec 산출 · PRD 9d 절). #36 은 R1 흡수로 실행 결과 검사가 되어 붉다.
- 계획 시점 음성 실행 (실제로 돌렸다, `M9D_ONLY` · `M9D_CONST` · `M9D_PLAN` · `M9D_BACKLOG` · `M9D_CELLS` 재지정): #4 · #15 · #16 · #19 · #25 는 사본에서 FAIL, #25 정상 합성 셀과 #30 자기 시험은 PASS. 사본은 git 디렉터리 아래에 있고 커밋하지 않는다.
- 계획 시점 실행 중 고친 것: 사이드카 첫 실행에서 백로그 `git show` 가 재지정 경로를 받아 음성이 엉뚱한 이유로 붉었다 (기준 백로그는 고정 경로로 읽도록 바꿨다). 검사 #24 는 마커가 없던 초안에서 공허하게 녹색이었다 (마커를 붙였다).

## 판정 기록 (계획 단계의 설계 분기)

1. **교차 셀 정의.** 범위 신원 6 마다 신원 tenant 가 아닌 대상을 부른다. 관리자 신원은 교차 셀이 없다. 근거: PRD 행 9 증명 「다른 기업 접근 거부」. 설계 분기이므로 메인 · fable · codex 리뷰가 다시 본다.
2. **0행 축소의 소실 집계.** 2xx → 2xx, OFF 목록 길이 1 이상, ON 목록 길이 0 을 소실 집계에 넣는다. 비소유 범위 신원은 같은 행의 소유자 ON 길이가 1 이상이면 「의도된 범위 거부」 로 옮길 수 있다. 근거: 백로그 1931 · 1946 (count 10 · 목록 10→0 이 녹색이던 자리).
3. **계획 커밋의 범위.** 계획서 · 사이드카 · 노트 · 백로그 짝 행 · 원장 구획을 함께 싣는다. 지시 (d) 와 어긋나는 부분이며 계획서 「정책과 다르게 옮긴 자리」 1 에 적었다.
4. **백로그 짝 행 8개의 상태 표기.** 계획 시점에 `RESOLVED-IN` 으로 적었다 (m9c 와 같은 방식). 구현 검사 전에는 닫힌 것이 아니며, 구현 뒤 해소 판정은 새 행으로 덧붙인다.

## 이월 처리 (계획 단계)

- 백로그 118 표지 줄 (기준 83ac506) 을 계획서 「이월 지적 반영」 표가 전부 덮는다 (검사 #15).
- 짝 행 append (계획 커밋): 1929 · 1931 · 1932 · 1946 · 1947 · 1948 · 1949 · 1950.
- 이연 (사용자 몫 또는 범위 밖): 1484 · 1560 · 1563 · 1564 · 1577 · 1647 · 1652 · 1653 · 1656 · 1657 · 1658 · 1684 · 1687 · 1805 · 1894.

## 영역 3 ON 체크리스트에 올릴 항목 (노트 전용)

- PG 취소 호출 뒤 종료 시 조정 로그 (백로그 1805, 짝 1778 · 1806 계열). 영역 3 ON 전에 해소 판정이 필요하다.

## 사용자 몫 (이 마일스톤에서 하지 않는다)

- 복제 환경 1회 재생 (UI7). 복제본 생성은 사람 승인.
- 영역별 would-deny 판단 표 실내용과 부분 축소 (200 축소) 의 정당성 판정.
- R2 (계정 · 권한 쓰기 최고관리자 한정) · R7 (NULL 계정) · R19 (소비처 책임자 · IP 5라우트).
- 동시 판정 상한 N · 대기열 정책 (백로그 1894 · 1902), 부하 실측.
- 영역 상수 · 범위 상수 · 메뉴 스위치의 ON 커밋.

## R1 흡수 반사실 (R2 계획)

R1 의 L2 블로킹 7 · L3 high 3 과 자가 점검 항목을 같은 합성 입력에 옛 사이드카 (R1 사본) 와 새 사이드카로 나란히 돌렸다. 입력은 git 디렉터리 아래 `mccp-gatekit/m9d-r2/cf/` 에 만들었고 커밋하지 않는다. 옛 사본은 측정 산출 경로만 합성 입력을 가리키도록 최소 치환했다 (검사 본문은 그대로).

| 입력 (합성) | 검사 | 옛 사이드카 | 새 사이드카 | 뜻 |
|---|---|---|---|---|
| 양성 셀 (self 전부 · 대상 행 교차 · 형제 · OFF 2xx · ON 403) | #7 · #28 · #29 | PASS | PASS | 양성은 둘 다 통과 |
| 대상 행 하나의 교차 셀 6줄 삭제 | #7 | PASS | FAIL | TARGETED 집합 누락을 잡는다 (R1 test HIGH) |
| 첫 교차 셀의 ON 을 404 로 | #28 | PASS | FAIL | 무효 요청의 허용 방향 통과를 잡는다 (R1 test HIGH) |
| 첫 교차 셀의 OFF 를 404 로 | #29 | PASS | FAIL | 교차 셀 요청 유효성을 잡는다 (R1 test HIGH) |
| 끝 트리 줄 (SRV_TREE_END) 을 지운 원자료 | #12 | PASS | FAIL | 끝 트리 미검사를 잡는다 (R1 invariant MEDIUM) |
| 시험 하나 pending · 파일 셋 · success true | #34 | PASS | FAIL | 전 시험 skip 녹색을 잡는다 (R1 invariant HIGH) |
| 파일 한 개만 든 산출 | #34 | PASS | FAIL | 필수 파일 실행 증명을 잡는다 (R1 L3 high 3) |
| 시험 하나 pending | #35 | PASS | FAIL | 회귀 jest 의 같은 결함 (자가 점검) |
| 「교차 기업 거부」 시험 pending (소스는 그대로) | #36 | PASS | FAIL | 소스 문자열만 보던 결함 (R1 L3 high 3) |

양성 입력의 셀 수는 합성 값이다 (이행표 행 × 신원 8 · 대상 행 3 · 교차 18 · 형제 3). 실제 측정 수는 구현 뒤에 센다.

### 데이터 대조 (R1 L3 high 1 의 근거)

- `identities-m9c.json`: sales-B 는 `claims.sagId` 9102 이고 `tenant` 는 A 다.
- `synthetic-tenants.sql`: 기업 9002 (합성기업B) 는 `sag_id` 9102 다. 기업 9001 은 9101 이다.
- 따라서 R1 계획의 「sales-B → 기업 B」 교차 셀은 정상 소유자 요청을 거부로 기대하는 잘못된 정의였다.
- m9c 는 `specFor(mapKey, id.tenant)` 로 대상을 라벨로 골랐다 (`replay-m9c.mjs` 의 행 루프). sales-B 의 「자기」 셀은 기업 A 를 불렀다. m9c 결과 표는 이 점을 반영해 읽어야 한다 (계획 Risks 9).

### 형제 조직 확인 (R1 architect HIGH)

- `synthetic-tenants.sql`: `SYNORGA10` 은 agc 9001 소속이고 경로는 `/SYNORGA10/` 다.
- 셸로 확인: `/SYNORGA10/` 는 `/SYNORGA1/` 로 시작하지 않는다 (접두 충돌이 아니라 경계 판정 시험이다).
- 같은 기업 안의 형제 조직이므로 교차 기업 셀로는 잴 수 없다. 형제 셀 (`sibling`) 로 따로 잰다.

### 검사 대상 실재 확인

- 사이드카의 `find` 가 찾는 itspec 파일은 access-control 4 · permission 1 · admin 1 · organization.race 1 · money-scope 2 · money-bulk-scope 1 · fc 2 다. 개수는 사이드카가 세며 계획 본문에 리터럴로 쓰지 않았다.
- `enforce-cross-tenant.itspec.ts` 의 「교차 기업 거부」 「교차 기업 대조」 「역할 없는 신원 거부」 시험명은 소스에서 확인했다 (`:127` · `:149` · `:161`).

### 구현 뒤로 미룬 음성 (정직한 공백)

- #9 (owner-check-m9d) · #10 · #26 · #33 의 음성은 해당 스크립트나 셀이 구현 뒤에만 있어 계획 시점에 돌릴 수 없다. 구현 Task 4 의 의무로 적었다.
- #27 의 「기대 열 재계산」 은 사이드카에 없다. 계획 본문의 주장을 (미검증) 으로 고쳤다.

## 계획 게이트

계획 게이트는 상한 3라운드를 모두 쓰고 끝났다. 세 라운드 모두 divergent 였고, 마지막 라운드(R3) 에 CRITICAL 이 없어 plan 게이트 receipt 는 쓰지 않았다. 구현 진입은 `MCCP_SKIP_RECEIPT` 로 넘긴다. 계획서는 고치지 않았다.

라운드 표 (A = L2 패널 findings 수 (괄호는 블로킹 수) · B = L3 high 수 · 층위 = WT CLAUDE.md §2 번호):

| 라운드 | A | B | 층위 | 흡수 | 기각 |
|---|---|---|---|---|---|
| R1 | 10 (블로킹 7) | 3 | 1·2·5 | 블로킹 7 · L3 high 3 (커밋 c20be23 본문) | 명시 기각 없음 |
| R2 | 8 (블로킹 2: test HIGH · FAIL) | 2 | 2·5·6 | test HIGH · L3 high 2 (커밋 894b74e 본문). 비블로킹 MEDIUM · LOW 는 흡수 기록 없음 (미확인) | 명시 기각 없음 |
| R3 (상한) | 7 (블로킹 2: test HIGH · FAIL) | 2 | 1·2·5·6 | 0 (계획서 미수정) | 0. 전부 backlog 로 넘김 |

### R3 상한 처리

- 판정: divergent (L2 블로킹 2 · L3 high 2). 원문은 `.claude/reviews/plan-review-admin-rbac-m9d.md`, 상태 파일은 `.claude/state/plan-review/`.
- CRITICAL: 없음. 최고 심각도는 HIGH.
- 근거 지시: 세션 기록(user/external, 2026-10-19 02:21 UTC)의 원문을 검증 스크립트로 확인했다 (exit 0). 원문: 「Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.」
- receipt: `receipt_path = (없음)`. write 는 시도하지 않았다 (intent gate 가 fail-closed 로 막는다). intent 게이트 우회 env 는 쓰지 않았다.
- 구현 진입 확인: `MCCP_SKIP_RECEIPT=1` 을 preflight 명령 하나에만 걸었다 (`--command mccp:prp-implement --decision admin-rbac-m9d --plan .claude/PRPs/plans/admin-rbac-m9d.plan.md`). 결과는 `{"ok": true, "bypassed": true, "reason": "MCCP_SKIP_RECEIPT"}`, exit 0.
- backlog 에 넘긴 HIGH (다음 계획 반영 대상, 3줄):
  1. `#33` 은 OFF·ON 대응 code 만 대조해, 두 모드가 같은 실패(404·500·서명 거부·401)를 내도 통과한다 (test HIGH, gates.sh:607).
  2. 같은 검사의 L3 판정. 500 합성 결과 9흐름×2모드를 넣어도 bad=0 으로 통과한다. 흐름별 기대 상태 계약이 필요하다 (L3, gates.sh:603-608). 1) 과 같은 결함이다.
  3. 목록형 8행이 교차 검증에서 빠져 타 기업 행 치환·범위 필터 누락을 못 잡는다 (L3, plan:81). 누출 단정이 아니라 보안 회귀를 승인할 수 있다는 지적이다.
- 넘긴 비 HIGH: FAIL 1 (1) 과 같은 결함의 판정 표현) · MEDIUM 2 (Task 5 Validate 가 상수 검사뿐 · #17 빈 입력 PASS) · LOW 3 (원장 내용 해시 없음 · 사이드카 범위 표기 #1–#37 대 38개 · #19 비밀 패턴 16자 이상 · 호스트 경로 접두 2종).

## 구현 뒤 실행 결과

산출 경로: `.claude/meta/2026-10-23-admin-rbac-m9d/` (사이드카 · 스크립트 · 셀 · 요약 · 결과 표 · 소비자 · itspec · 회귀 jest). 재생 원자료 3개 (`raw-*.tsv`) 와 실행 원장 (`run-ledger-m9d.txt`) 도 이 디렉터리에 있다. 원자료는 R2 에서 git 디렉터리 사본을 옮긴 것이고 커밋 대상이다 (상태 코드 줄뿐이다). 색인은 `README.md`.

### 측정 결과 (재측정 뒤, 셀 394)

- 재생 보고 (`replay-report-m9d.mjs`): `CELLS=394 TARGETED=98 LOSS=28 ZERO_ROW=0 SHRINK200=14 SHRINK_ADMIN=0 OVER_PERMIT=0 CROSS_OPEN=0 OFF_RERUN_DIFF=0 UNCLASSIFIED=0`.
- 소실 28 건은 전부 「의도된 범위 거부」 (교차 · 형제 셀의 ON 403). 「의도되지 않은 소실」 은 검사 #9 가 0 으로 센다.
- 소비자 대응표 (`consumer-regress-m9d.txt`, 18줄): 9 흐름 × {OFF, ON} 의 code 가 모두 같다. 스케줄러 두 잡 run=1.
- 격리 itspec (`itspec-m9d.json`): 12 suite · 123 시험 전부 통과 (pending 0). 형제 픽스처 제외 (아래 이탈 2) 와 표지 행 (이탈 3) 을 넣은 실행이다.
- 회귀 jest (`jest-m9d.json`): 2860 시험 · 통과 2802 · 실패 35 · 보류 23. 실패는 `jest-baseline-m9d.tsv` (F 행 38 = 시험 35 + suite 실행 실패 3, 원인 열 3종) 와 정확히 같다. 원인: Redis 미연결 32 (dispatch 3 suite, 메모 `server-jest-baseline-red-needs-redis` 와 같은 수), 기존 결함 1 (`libs/drizzle` mysqlErrorWrapper 가 500 본문에 `ER_LOCK_WAIT_TIMEOUT` 원본을 싣는다), 픽스처 부재 5 (`.claude/plans/ai-insight-report-v2-m1.report-fixture.json` 등 3 파일이 워크트리 · 메인 체크아웃 · 홈 아래 어디에도 없다 (`find` 0건)). 서버 트리는 기준 커밋 3418d3a0 그대로라 실패는 이 마일스톤과 무관하다.

### 검사 결과 (사이드카 `admin-rbac-m9d.gates.sh`)

- 38 검사 전부 PASS, rc 0 (구현 뒤 실행 · 검사 #29 · #27 · #14 · #13 · #35 는 정의를 고친 뒤).
- 음성 (합성 사본에서 붉음을 관측): #13 패치 헤더 한 글자 · 패치 줄 추가 · #14 `mysql -h prod.example.com` · #27 길이 「-」 행 (예외 행 밖) · #29 교차 셀 OFF 404 · #29 짝 셀 OFF 404 · #33 ON code 변경 · #33 한 줄 삭제 · #33 scheduler run=0 · #35 기준선 한 줄 빠짐 · #35 기준선 한 줄 추가. 전부 FAIL. 대조 (원본) 는 PASS.
- 미실행 음성: #19 (비밀 형상), #37 (PRD 9d 절 없음), #13 원복 sha 한 글자 (헤더 줄 사본), #15–#18 · #20–#26 · #28 · #30–#31 · #38 의 구현 뒤 음성. 이것들은 구현 전 기록 그대로다.

### 이탈 (계획 본문과 다르게 한 자리)

1. 검사 #29 정의 변경 (사용자 지시에 따른 메인 판정, 사이드카만). 교차 · 형제 셀의 OFF 는 2xx 또는 403 이고, ON 은 반드시 403, 짝 셀 (같은 행 · 같은 신원의 자기 범위 셀) OFF 는 2xx 또는 403 이다. 교차 셀 OFF 403 은 무효로 세지 않는다 (원인 미확인 403 으로 둔다. 「기존 가드가 닫았다」 는 원자료로 특정되지 않아 쓰지 않는다, R2 정정). 원자료의 교차 · 형제 OFF 403 70 셀의 코드는 「-」 40 · `E0607/permission` 20 · `E0401` 10 이다. 짝 OFF 가 403 인 셀은 41 (정보 없음 그룹), 2xx 인 셀은 29 (기본 차단 그룹) 다. 두 그룹 수는 요약의 `INFO_BLOCKED` · `INFO_NOINFO` 와 같다. 옛 정의는 교차 403 과 자기 403 이 어긋나면 붉었다.
2. 형제 픽스처를 뺀 스냅샷으로 격리 itspec 을 돌렸다 (`M9D_NO_SIBLING=1`, 계획 Task 7 은 픽스처를 먼저 적재하라고 했다). 첫 실행에서 `money-scope.itspec.ts` 의 「타 기업 누출 0 (머니 목록)」 과 「범위 없음 (null)」 이 실패했다. 누출은 없었고 (9002 행 0, 9001 행이 3 대 1) 형제 회원 9303 의 합성 행이 기대 건수를 바꿨다. 재생 측정은 픽스처 포함 스택에서 끝났고 그 셀은 바뀌지 않았다.
3. 격리 itspec 에 칸 · 표 부재 시험의 안전 표지 행 (`tn_admin` adm_id 3, adm_login_id `m9b-super`, 합성) 을 넣었다. 시험은 이 행이 없으면 파괴적 DDL 을 돌리지 않는다 (계획 밖 항목). 이 행은 격리 DB 에만 들어간다.
4. 검사 #27 의 예외 `LEN_GAP_ROWS` = `I9/fax-admin.controller.ts/getFaxHistoryList`. 응답 본문에 배열이 한 개가 아니어서 목록 길이가 「-」 로 남는다 (count 열은 2 등 값이 있다). 이 행의 목록 길이 · 0행 축소는 측정되지 않는다 (사각지대, 백로그 1931 · 1946 PARTIAL).
5. 검사 #35 의 정의를 바꿨다. 계획 본문은 「회귀 jest 시험 전부 통과」 였지만 기준선 편차 35 건이 실측 (위) 이므로, 실패 · 보류 집합이 `jest-baseline-m9d.tsv` 와 정확히 같을 때 통과하도록 했다. 이 바꿈은 최종 리뷰가 다시 본다.
6. 검사 #13 의 패치 파일 (`on-patch-m9d.patch`) 은 difft 출력 (side-by-side) 이라 git 형식이 아니었다. 같은 4줄 편집을 HEAD 블롭에 적용한 `on-patch-m9d.unified.patch` (diff -u, 빼기 4 · 더하기 4) 를 검토 패치로 두었다. 측정 원본의 sha 는 on-build · 원자료 헤더가 가리키는 그대로이고, `on-build-m9d.txt` 끝에 후기록 줄 (`ON_UNIFIED_SHA256`) 을 덧붙였다.
7. 검사 #14 의 호스트 패턴 `-h ?[A-Za-z0-9]` 가 `openssl rand -hex` 를 DB 호스트로 잘못 읽었다 (오탐). `-h` 뒤 공백을 요구하도록 고쳤다 (음성 `mysql -h prod...` 는 여전히 붉다).
8. 검사 #19 는 jest JSON 의 절대 경로 (사용자 홈 디렉터리 접두) 를 잡았다. 측정 산출의 경로를 `<wt>/` · `<home>/` 로 치환하고 (runner 와 기존 JSON 모두), runner 문자열에 홈 경로 리터럴을 두지 않도록 `$HOME` 을 썼다.
9. 회귀 jest 실행에 `REDIS_URL` 을 격리 Redis 로 넘겼다. 실패 수는 그대로다 (Redis 32 건은 이 env 로도 붉다. 원인은 이 노트에서 확인하지 않았다).
10. PRD 결정 로그 「2026-10-23 · 관리자 RBAC 마일스톤 9d 보완 (admin-rbac-m9d)」 절과 백로그 상태 행 8 개 (1929 · 1931 · 1932 · 1946–1950) 를 덧붙였다. 행 9 는 바꾸지 않았다.

### 사각지대 · 미검증

- I9 목록 길이 미측정 (위 4).
- 칸 · 표 부재 판정은 격리 DB 의 시험이다. 운영 복제본 1회 실행은 사용자 몫 (복제본 생성은 사람 승인).
- 소비자 흐름은 가짜 수신기로 외부 연동을 받았다 (실서버 0).
- `libs/drizzle` 500 본문 결함은 이 마일스톤에서 고치지 않았다 (백로그 행 필요 시 다음 계획).
- 회귀 jest 의 Redis 32 건 · 픽스처 부재 5 건은 환경 원인을 확정하지 않았다 (관측 · 메모 근거).

## Codex Implementation Review

- 리뷰 대상: 구현 뒤 커밋 전 워킹트리 (사이드카 · 측정 스크립트 · 메타 산출 · PRD · 백로그 · 노트). 서버 · 어드민 · 사이트 코드는 바뀌지 않았다.
- 라운드 1 (`codex-invoke.js adversarial-review`, 원본 `mccp/tmp/codex-r1.json`, 89 초): verdict `needs-attention`, 지적 4건. 라운드 상한 3 · 원장 `mccp-implement-codex__admin-rbac-m9d` (cap 3, seal 완료).
- YAGNI triage (R1):

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 소비자 서버에 가짜 외부 주소가 안 넘어감 | HIGH | ACCEPT_NOW (흡수) | `m9d_server_start` 의 `env -i` 가 `M9D_EXTRA_ENV` 를 버렸다. 실측으로 확인: 첫 실행의 가짜 수신기 기록은 `FAKE_READY` 만 있고 요청 0건, 서버 로그의 Smartro 호출 url 은 빈 문자열 (`ERR_INVALID_URL`). 고쳐서 두 빌드를 다시 쟀다. 수정 뒤 approve 흐름은 가짜 수신기에 도달해 200 을 받는다 (`fake_hits=1`, 두 모드 모두). 나머지 콜백 (cancel · coocon · mok) 은 외부 호출 전 단계에서 닫힌다 (서버 로그에 외부 url 없음) |
  | F2 기준선 비교가 todo 시험을 무시함 | HIGH | ACCEPT_NOW (흡수) | 검사 #35 에 `numTodoTests === 0` 과 assertion 상태 허용 목록 (passed · failed · pending) 을 넣었다. 반례 (passed 50 개를 todo 로 바꾼 사본) 에서 FAIL 을 실측 |
  | F3 I9 예외가 팩스 목록 소실을 숨김 | MEDIUM | DEFER_TO_BACKLOG | 팩스 저장소가 같은 배열을 list · data 두 별칭으로 돌려줘 측정 함수가 길이를 「-」 로 남긴다. 고치려면 재생 원자료를 다시 재야 한다. 메인 지시로 세 재생은 다시 돌리지 않았다. 백로그 1931 · 1946 PARTIAL 에 추가하고 메인 판정 요청 |
  | F4 DB 호스트 검사가 붙임형 옵션을 놓침 | MEDIUM | ACCEPT_NOW (흡수) | `(mysql|mysqldump)` 명령 문맥의 `-h<호스트>` · `--host=` 를 잡도록 고쳤다. `openssl rand -hex` 는 오탐하지 않는다 (음성 `mysqldump -hprod…`, `mysql --host=prod…` 는 FAIL, 양성 `openssl -hex` 와 `mysql -h127.0.0.1` 는 PASS) |
- 리뷰 뒤 흡수 실측: 검사 #35 · #14 음성 · 소비자 재측정 뒤 38 검사 전부 PASS, rc 0.
- Deferred to backlog: 1 (F3) → `.claude/plans/codex-findings-backlog.md`.
- 에스컬레이션 (2.5.4): 흡수로 HIGH 2건을 닫았다 (자기 확인). 남은 ACCEPT_NOW HIGH 가 없어 R2 는 열지 않았다.
- Open Questions: F3 (MEDIUM) — I9 목록 길이는 재생 원자료 재측정 없이는 닫히지 않는다. 메인 판정 요청. 칸 · 표 부재 시험의 표지 행과 형제 픽스처 제외 (이탈 2 · 3) 도 메인 판정 대상.
- Codex session 참조: `mccp/tmp/codex-r1.json` (threadId 는 원본 참조).

### 라운드 2 · 3 (흡수 검증, 상한 3 까지)

- 에스컬레이션 기준은 열지 않았지만 (R1 의 HIGH 2건은 자기 확인으로 닫혔다), 흡수가 실제로 맞는지 보려고 R2 · R3 를 돌렸다. 둘 다 같은 원장 (`mccp-implement-codex__admin-rbac-m9d`, cap 3 · `rounds_so_far=3`). 원본은 `mccp/tmp/codex-r2.json` · `codex-r3.json`.
- R2 (59 초): verdict `needs-attention`, MEDIUM 2.
  - F5 `M9D_EXTRA_ENV` 를 `set -u` 아래에서 확장해 재생 (`replay-m9d.sh`) 이 기동 중 죽는다. 소비자 실행만 값을 넣어 두 번의 성공 재측정이 이 결함을 가렸다 → `${M9D_EXTRA_ENV:-}` 로 고쳤다 (기본 빈 값에서 `EXTRA=[]` 실측).
  - F6 검사 #14 가 변수가 한 줄에 있으면 명령 전체를 빼서 `mysql -hprod.example.com` 이 통과했다 → 호스트 리터럴만 떼어 보는 방식으로 바꿨다.
- R3 (46 초): verdict `needs-attention`, MEDIUM 1.
  - F7 따옴표로 감싼 호스트 (`-h"prod.example"`, `--host="prod.example"`) 가 빠지고, 같은 줄의 다른 곳에 있는 `localhost` 가 외부 호스트를 가렸다 → 호스트 토큰 추출 뒤 루프백만 제외하도록 다시 고쳤다. 음성 5건 FAIL · 양성 4건 PASS (`neg4` 실측, 노트 밖 임시 스크립트).
- R2 · R3 의 MEDIUM 은 모두 고쳤고 고친 뒤에는 Codex 를 다시 돌리지 않았다 (상한 3 도달). 따라서 최종 verdict 는 R3 의 `divergent` (needs-attention) 로 남고, 이 판정은 메인 · 최종 리뷰가 다시 본다.
- 최종 게이트: 38 검사 전부 PASS, rc 0 (R3 흡수 뒤 실행).

## 구현

### 최종 결과

- 상태: 구현 · 측정 · 검사 · 리뷰 흡수 완료. 커밋하지 않았다 (워킹트리 변경: 사이드카 · 노트 · 백로그 · PRD · 메타 산출 · implement receipt).
- 검사 38/38 PASS, rc 0 (최종 실행). **이 주장은 틀렸다**: 코드 리뷰 R2 재확인에서 #34 (상태 줄 파일 없음) 와 #35 (요약 필드 누락) 가 붉었다. 정정은 「코드 리뷰」 R2 절. 재생 · 소비자 · itspec · 회귀 jest 산출은 측정 절 참조.
- implement-codex receipt: `.claude/receipts/mccp-implement-codex/admin-rbac-m9d.json` 을 R3 verdict `divergent` (needs-attention) 로 썼다. 설명: R1 · R2 · R3 의 HIGH 와 MEDIUM 은 흡수 (I9 MEDIUM 1건만 백로그). R3 뒤의 흡수는 Codex 재검토 없이 자기 음성 실측으로 닫았다 (상한 3 도달). 따라서 receipt 는 approve 가 아니다.
- receipt 검증: `receipt validate` 는 blocking 0 · missing 1 (`mccp-plan-codex` 부재, 계획 게이트 receipt 는 없다 — 사용자 지시 skip-receipt). 구현 receipt 는 env 없이 쓰였다 (앞 게이트 부재 거부가 없었다).
- 3.6 · 3.7 (설계 · 디자인 grounding): 건너뜀. 화면 · 프런트 파일 변경 0, 설계 트리거 없음.
- 메인 판정 요청 (다음 단계 전에 필요):
  1. I9 팩스 목록 길이 (백로그 1931 · 1946 · 2026-10-24 MEDIUM 행). 재생 원자료 재측정 (별칭 인지 길이) 이 필요하다. 세 재생은 다시 돌리지 않았다 (메인 지시).
  2. 검사 #35 정의 (기준선 목록 허용) 와 검사 #29 정의 (교차 403 허용, 짝 셀 검사) 를 받아들일지.
  3. 격리 itspec 의 형제 픽스처 제외 (이탈 2) 와 칸 · 표 표지 행 (이탈 3).
  4. 회귀 jest 기준선 35 건 중 drizzle 500 본문 결함 (`ER_LOCK_WAIT_TIMEOUT`) 은 서버 결함이다. 다음 계획에서 다룰지.
  5. 소비자 실행 재측정 결과 (`consumer-regress-m9d.txt`) 는 외부 주소를 실제로 넘긴 뒤의 값이다. 첫 실행 값은 버렸다 (외부 주소 미전달, 가짜 수신기 요청 0건).
- 커밋 · push · PR 은 하지 않았다. 운영 복제본 1회 실행은 사용자 몫이다.

## 코드 리뷰

### 라운드 1 판정 (2026-10-24, 커밋 전 워킹트리)

검사 38/38 PASS, rc 0 (정정 뒤 실행 · R2 재확인에서 틀렸다, 아래 R2 절). 서버 코드 변경 0. 격리 스택은 재측정 뒤 내려졌다 (`m9d-iso-` 컨테이너 0). 지적은 세 레인 (code-review · security · typescript) 의 중복을 합쳐 15 행으로 묶었다. 층위는 자가 1패스 체크리스트 (CLAUDE.md §2) 의 번호다.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | code-review | HIGH | 검사 #28 이 짝 셀 OFF 2xx 를 유효 조건으로 보지 않고, 정보 없는 셀을 세지 않는다 | 흡수 | `c28` 을 다시 썼다 (유효 = 자기 OFF 2xx 이고 짝 OFF 2xx). 실측 `INFO_28 valid cells=28`, 정보 없음 41 (11행), 기본 차단 29 (10행). 반사실: I5 getCorpHistory 영업 A · B 자기 셀 OFF 를 403 으로 바꾼 사본에서 옛 식은 0 (PASS), 새 식은 FAIL. 원본은 PASS | 2 · 6 |
| R1 | code-review | HIGH | 백로그 1948 RESOLVED-IN 이 기업 · 조직 관리자의 교차 셀과 형제 경계의 전이를 증명하지 않는다 | 흡수 (정정) | 교차 셀의 OFF 2xx → ON 403 전이는 영업 sales-A · sales-B 의 28 셀뿐이다 (`replay-cells-m9d.tsv`). 기업 · 조직 관리자 교차 56 셀과 형제 조직 14 셀은 전부 OFF 403 이라 ON 의 기여가 없다. 백로그에 `[원 1948] 정정` 행 (PARTIAL) 을 추가했다 (기존 행은 고치지 않음). PRD 9d 절에 같은 내용을 적었다. 형제 변별 0 도 같은 판정에 든다 | 2 |
| R1 | code-review | HIGH | 소비자 정상 회귀: 9 흐름 중 거부로 끝난 4 흐름과 빈 실행 스케줄러 2 흐름이 OFF·ON 일치만으로 통과한다 (검사 #33) | 부분 흡수 | 2xx 로 실제 닿은 흐름 3 개 (토큰 발급 · 결제 승인 · 결제 취소) 를 `CONS_PROVEN` 으로 고정하고 두 모드 모두 2xx 를 요구했다. 자기 시험: 결제 승인의 두 모드 code 를 403 으로 바꾼 사본에서 옛 식은 0 (PASS), 새 식은 FAIL. 실측 `INFO_33 proven=3 unproven=6`. 1949 는 `[원 1949] 정정` 행으로 PARTIAL. 공개 흐름의 토큰 전달 · 저장된 refresh 토큰 · 스케줄러 만기 행 시드는 재측정이 필요해 백로그 HIGH 로 남겼다 | 6 · 2 |
| R1 | typescript | HIGH | 스케줄러 두 잡이 내부 오류를 삼키고 격리 DB 에 예약 행이 없어 빈 실행이다 (`push.scheduler.ts:24`, `alimtalk.service.ts:257-272`) | 부분 흡수 | 「처리 0 건」을 증명 집합에서 뺐다 (`CONS_PROVEN` 에 없음). PRD · 백로그에 적었다. 만기 행 1 건 시드 후 부작용을 단정하는 시험은 재측정이 필요해 백로그 HIGH (1949 행) | 6 |
| R1 | typescript | HIGH | 형제 조직 셀 14 개가 전부 OFF 403 이라 형제 경계 변별이 0 이다. 검사 #28 은 교차 셀만으로 행을 채운다 | 흡수 (1948 행과 합침) | 형제 변별 0 을 1948 정정 행과 PRD 9d 절에 적었다. 형제 전용 유효 셀을 요구하는 조건은 넣지 않았다 (그 셀이 OFF 2xx 가 되는 대상을 새로 재야 해서 재측정 범위다) | 2 |
| R1 | code-review | MEDIUM | 격리 itspec (형제 픽스처 없는 스냅샷) 과 재생 (픽스처 포함) 이 다른 데이터 상태에서 돈다 | 부분 흡수 (한계 기록) | PRD · 노트에 두 스냅샷의 차이를 한계로 적고 `[원 1950] 정정` 행을 PARTIAL 로 뒀다. 같은 데이터에서 다시 돌리려면 기대 건수 보정 (시험 수정) 이 필요해 이번 판정 범위 밖이다. 백로그 MEDIUM | 8 |
| R1 | code-review | MEDIUM | 회귀 jest 기준선이 같은 실행에서 파생됐고 출처 줄이 없다. 검사 #35 가 「기준선과 같다」 로 약해졌다 | 흡수 | 기준 커밋 3418d3a0 의 트리 (`5e740146`) 에서 같은 명령 (`jest-rerun-m9d.sh`) 으로 다시 쟀다: 2860 시험, 실패 35, 보류 23. 실패 · 보류의 이름 집합이 기준선과 차이 0 (`DIFF_F=0 DIFF_P=0`). 요약 `jest-rerun-m9d.summary.txt` 에 트리 · 명령 · 시작 · 끝 시각을 적었다. 검사 #35 가 이 요약을 본다. 음성: `DIFF_F=1` 사본에서 #35 FAIL, 원본 PASS. 원인 열 확인: Redis 미연결 32 는 treat dispatch 시험 3 개 (`127.0.0.1:6380` 을 읽음) 이고, 실행 시점 6380 리스너 0, `MaxRetriesPerRequestError` 64 줄. 픽스처 부재 5 건과 기존 결함 1 건은 원원인 확인을 하지 않았다 (원 열 그대로) | 7 |
| R1 | code-review | MEDIUM | PRD 374 행의 「0행 축소 0 건」 이 I9 목록 길이 측정 예외를 빼지 않는다 | 흡수 | PRD 9d 절에 「I9 제외 · 원자료에 길이 없어 미검증」 을 붙였다. 행 9 본문은 바꾸지 않았다 (검사 #37 PASS) | 2 |
| R1 | security | MEDIUM | AO 행 15 와 목록형 8 행에 교차 기업 셀이 없고, 목록 항목의 소유자 검사도 없는데 PRD 가 「no over-permit」 로 읽힌다 | 부분 흡수 | PRD 9d 절에 미측정을 명시했다. 1974 는 열린 채로 둔다. 측정 추가는 재생 재실행이 필요해 이번 판정 범위 밖이다 | 2 |
| R1 | security | MEDIUM | 스위치 OFF 상태에서 영업 신원 sales-A · sales-B 의 교차 셀 28 개가 2xx 다 (I2 쓰기 포함). 기록은 「의도된 범위 거부」 만 있다 | 흡수 (기록) | PRD 9d 절에 노출 항목을 적고 사용자 몫에 「수용 여부 결정」 을 넣었다. 백로그 HIGH 행 (소유자: 사용자). 코드 · 스위치는 바꾸지 않았다 (범위 밖) | 2 |
| R1 | code-review | MEDIUM | on-area-build 가 `git diff` 를 외부 diff 도구 (difft) 로 돌려 패치를 저장하고, 다시 돌리면 #13 이 깨진다 | 부분 흡수 | `on-area-build-m9d.sh` 가 `git diff --no-ext-diff` 로 저장하고 `ON_UNIFIED_SHA256` 줄을 쓰도록 고쳤다. 반사실: 이 환경의 기본 `git diff` 는 difft 출력이었고 (임시 저장소), `--no-ext-diff` 는 git 형식이었다. 측정 원본 `on-patch-m9d.patch` 는 difft 출력 그대로 남는다 (재측정 금지). 백로그 LOW | 5 |
| R1 | code-review | LOW | 소비자 결과 집계 파일 (`consumer-regress-m9d.txt`) 을 만드는 스크립트가 없다 | 흡수 | `consumer-regress-m9d.sh` 가 두 모드 원자료가 모두 있을 때 ON · OFF 순서로 집계를 만든다. 집계 결과와 기존 파일은 `cmp` 로 바이트가 같다 | 5 |
| R1 | security | LOW | `mysqlErrorWrapper` 가 원본 MySQL 오류 코드를 500 본문에 싣는다. 코드 (`libs/drizzle/src/drizzle.utill.ts:42-48`) 와 시험 (`drizzle.utill.spec.ts:30`) 이 충돌하고, 검사 #35 가 실패로 고정한다 | 부분 흡수 | 서버 코드 · 시험은 범위 밖이라 바꾸지 않았다. 백로그 LOW 행을 추가해 결정을 넘겼다 (의도된 동작인지, 시험을 고칠지) | 1 |
| R1 | security | LOW | implement receipt 가 `converged: true` 와 빈 findings 로 적혀 있다 | 기각 | `.claude/receipts/` 는 mccp receipt-write 가 쓰는 산출물이다. 수기로 고치면 체인 (CLAUDE.md §0 B2 범위 밖) 이 깨진다. 이월 항목은 이미 백로그 (2026-10-24 MEDIUM, I9 · F3) 와 노트 구현 절에 남아 있다 | 2 |
| R1 | code-review | LOW | I9 목록 길이: 원자료에서 잴 수 있는지 | 흡수 (사실 기록) | `raw-off.tsv` · `raw-off-rerun.tsv` · `raw-on.tsv` 의 I9 셀 길이 열이 전부 「-」이고 응답 본문이 원자료에 없다. 재측정 없이는 판정 불가. 영향: I9 목록 0행 축소 미검증 (PRD · 백로그 LOW 행) | 8 |

### 판정 근거 · 확인한 것

- 「PRD 9d 행」 과 노출 항목 (`prds/admin-rbac.prd.md`, 9d 절): 행 9 본문은 바꾸지 않았다 (검사 #37 PASS).
- 측정 산출 (`jest-rerun-m9d.summary.txt`, 새 스크립트 · 요약): 홈 경로 · 비밀 값 형상 없음 (검사 #19 PASS). 원본 JSON 은 git 디렉터리 아래에만 있다.
- 검사 #29 는 정의를 그대로 둔다. 짝 OFF 403 인 교차 셀을 「무효」 로 세지 않고, #28 이 「정보 없음」 으로 따로 센다 (메인 판정과 같은 분리).
- 검증한 명령: 게이트 `M9D_ONLY=28` · `33` · `35` 각각의 원본 PASS 와 음성 FAIL (검사 #29 는 정의를 바꾸지 않았다). 전체 게이트 38/38 PASS 라고 적었으나 R2 재확인에서 틀렸다 (#34 · #35 붉음).
- 이번 판정에서 서버 테스트 · 타입 검사는 다시 돌리지 않았다. 서버 파일을 바꾸지 않았고, 건드린 것은 사이드카 · 측정 스크립트 · 문서뿐이다.

### 사각지대 · 미검증 (이 라운드 뒤에도 남는 것)

- 소비자 공개 흐름 (토큰 전달 · 저장 refresh 토큰) 과 스케줄러 만기 행 처리: 재측정하지 않았다 (백로그 1949 HIGH).
- AO 행 교차 셀과 목록 항목 소유자 검사: 미측정 (백로그 1974).
- I9 목록 길이: 원자료에 없다 (위 표).
- 격리 itspec 의 픽스처 포함 스냅샷: 실행하지 않았다 (백로그 1950 PARTIAL).
- 측정 원본 `on-patch-m9d.patch`: difft 출력 그대로다. 검토용 git 형식은 `on-patch-m9d.unified.patch`.
- 스위치 OFF 상태의 영업 교차 노출 28 셀: 코드 변경 없이 기록만 했다. 수용 여부는 사용자 판단이다.

### 라운드 2 판정 (2026-10-26, 워킹트리 · 커밋 전)

지적 19 건을 판정했다 (세 레인 중복 합침). 지적은 커밋본 (HEAD) 기준으로 적힌 것이 많다. HEAD 의 사이드카는 워킹트리보다 옛 판이라 (`RAW_DIR` 가 git 디렉터리를 가리키고 `files` 에 `.cjs` 가 없다), 아래 「증거」 는 워킹트리 파일과 커밋본을 같이 확인했다. 계획서 (`admin-rbac-m9d.plan.md`) 는 고치지 않았다 (지시 4). 흡수한 건은 반사실 (수정 전 붉음 · 수정 뒤 초록) 을 돌렸다.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R2 | code-review | MEDIUM | 계획서 326 행 · 82 행의 교차 · 형제 「기본 차단」 정의가 게이트 c28 과 반대다 | 중복 · 미흡수 | 계획서 동결 (지시 4). 백로그 2026-10-24 MEDIUM 행 (계획 본문) 이 이미 있어 다시 적지 않았다. 게이트 c28 본문 (짝 OFF 2xx = 기본 차단 29, 짝 403 = 정보 없음 41) 은 맞다 | 2 |
| R2 | code-review | MEDIUM | #28 이 행 단위로만 PASS 라 교차 · 형제 유효 0 을 가린다. INFO_28 은 stderr 뿐이다 | 기각 (사실 아님) | 산출 파일 `replay-summary-m9d.txt` 에 `VALID_<부류>=0` 5 줄 · `UNPROVEN_CLASSES` · `INFO_BLOCKED=29` · `INFO_NOINFO=41` (행 목록 포함) 이 있다. c28 두 번째 조건이 미증명 부류 집합을 정확히 대조한다 (corp-A · corp-B · org-A1 cross · org-A1 sibling · org-A2 cross). 행 단위 PASS 는 설계 (R1 판정) 이고 기업 · 조직 교차 음성 미증명은 PRD 9d · 백로그 1948 정정 행에 적혀 있다 | 6 |
| R2 | code-review | LOW | 노트 141 행과 백로그 1980 행이 교차 · 형제 OFF 403 을 기존 가드가 닫았다고 귀속한다 | 기각 (중복 · 정정 있음) | 노트 이탈 1 본문은 「기존 가드가 닫았다」 를 쓰지 않고 R2 정정을 적었다. 백로그 1994 정정 행이 있다. 1980 은 RESOLVED-IN 이력 행이라 고치지 않았다 | 2 |
| R2 | code-review | MEDIUM | 격리 itspec 산출에 픽스처 상태가 없고 9303 목록 필터 장면이 실행되지 않았다 | 흡수 (문구 · 기존 검사 확인) | 상태는 `itspec-state-m9d.txt` 에 있고 검사 #34 가 `M9D_NO_SIBLING=1` · `SIBLING_FIXTURE=absent` · 시작 끝 트리를 본다 (c34). PRD 9d 절과 이 노트에 「목록 필터 미실행」 을 적었다. 형제 경계 근거는 `scope-single-join.itspec.ts` 의 키 조회 판정 (픽스처와 무관) 이다. 재생 쪽 형제 유효 셀은 0 이다 (VALID org-A1/sibling=0) | 8 |
| R2 | code-review | LOW | README.md (CREATE) 가 없다 | 기각 (사실 아님) | `.claude/meta/2026-10-23-admin-rbac-m9d/README.md` 가 있다 (색인 · 재현 명령 · 환경 변수 · 커밋 범위) | 2 |
| R2 | code-review | LOW | 회귀 jest 증거 사슬: 이름 파일 미커밋 · 요약에 끝 트리 · 환경 줄 없음 · 기준선 스탬프 없음 · 실패 분류 불일치 | 흡수 (재실행) | 재실행 (`jest-rerun-m9d.sh` 갱신본, env -i 허용 목록 · 끝 트리 · 끝 청결 · 이름 파일 생성) 으로 요약과 `jest-rerun-m9d.names.tsv` 를 다시 만들었다. #35 는 이름 파일을 기준선 F · P 행과 `cmp` 한다. 재실행 전 #35 는 FAIL 이었다 (이름 파일 · 끝 줄 부재). 분류는 PRD 를 기준선 원인 열에 맞췄다 (픽스처 부재 5 = 시험 2 + suite 3). 기준선 TSV 자체의 스탬프는 넣지 않았다 (요약의 `SRV_TREE` 가 기준 트리 `5e740146` 을 싣고 #35 가 대조한다) | 7 |
| R2 | code-review | LOW | on-build 산출이 측정 뒤 손으로 보강됐고 편집 스크립트가 산출을 재현하지 못한다 | 흡수 (이력 기록) | 이력: (1) `on-patch-m9d.patch` 는 측정 때 difft 출력으로 저장된 원본이다 (고치지 않는다). (2) `on-area-build-m9d.sh` 는 측정 뒤 `--no-ext-diff` 저장으로 고쳤다. 지금 다시 돌리면 패치 sha 가 달라진다. (3) `on-build-m9d.txt` 12–13 행은 측정 뒤 후기록이다. #13 은 원본 패치의 `false as const;` 와 `true as const;` 가 같은 행인 줄이 4 개인지 본다 (원본 패치 4 줄 확인) | 5 |
| R2 | code-review | LOW | 측정 산출 JSON 에 「삼성화재」 가 있다 | 기각 (중복 · 백로그 2026-10-24 LOW 기록) | 서버 저장소 시험 제목 원문이다 (`samsung-intake-*.spec.ts` 제목에 같은 문자열). 고객사 상호이고 개인 실명이 아니다. 원문 보존을 위해 치환하지 않았다. 개인 실명 탐색은 자동 검사가 없어 (미검증) | 6 |
| R2 | code-review | LOW | 「외부 실서버 호출 0」 은 설정 진술이다 (FCM · AWS 치환 없음) | 흡수 (문구) | PRD 9d 절을 「설정상 치환, 송신 미측정, 외부 실서버 호출 0 은 (미검증)」 으로 낮췄다. 백로그 1996 정정 3 과 같은 내용 | 2 |
| R2 | code-review | LOW | 두 OFF 실행이 병렬이었는데 기록이 없다 | 흡수 (기록) | 실행 원장: `raw-off` 와 `raw-off-rerun` 의 RUN_ID 가 같은 초 (`20261024T002249`) 다. 같은 초에 시작했으니 병렬이었을 가능성이 높다 (서버 로그 대조는 하지 않았다). 결정성 검사 #11 은 이 조건에서 OFF 와 재실행이 같은 상태 · 길이를 냈다는 증거다 | 8 |
| R2 | security | MEDIUM | 재생 원자료가 git 디렉터리에만 있고 셀 파일의 도출 관계를 아무도 보지 않는다 | 기각 (워킹트리에서 이미 고침 · 반사실) | 워킹트리 `RAW_DIR` 는 측정 디렉터리 (`${M9D_RAW:-$META}`) 다. 커밋본 (HEAD) 만 git 디렉터리를 가리킨다. 검사 #12 가 원자료에서 보고서 생성기를 다시 돌려 셀 · 요약 · 표를 `cmp` 한다. 반사실: 셀 원인 한 칸 변경 사본 · ON 열 한 칸 변경 사본 모두 `CHECK_12=FAIL`, 원본 PASS | 5 |
| R2 | security | MEDIUM | 소비자 결과 파일에 출처 머리글이 없어 라벨 · 빌드 · 트리를 검사가 확인하지 못한다 | 중복 · 미흡수 | 백로그 2026-10-24 MEDIUM 행 (소비자 회귀 출처 머리글) 이 이미 있다. ON 재측정은 편집 · 빌드 · 재생 · 원복 전 경로 (`on-area-build-m9d.sh`) 를 다시 타야 하고, OFF 만 다시 재면 ON 과 기준이 달라진다. 이번 판정 범위 밖 | 8 |
| R2 | typescript | MEDIUM | 검사 #14 가 원소가 떨어진 배열 인자 `'-h', '호스트'` 를 못 잡는다 | 흡수 (정규식 쉼표 허용) | 사본 시험 (수정 전 · 같은 입력): 현행 정규식 0 건 · 새 정규식 1 건. 게이트 사본 시험 (수정 뒤): 원소 분리 호스트 주입 FAIL · 원소 분리 루프백 PASS · 원본 PASS · `-hHOST` 주입 FAIL · `docker -p 0.0.0.0` 주입 FAIL. 공백 형식 `mysql -h 호스트` 는 현행도 잡는다 (사본 FAIL). 음성 기록은 `m9d-neg-r2` (git 디렉터리) | 5 |
| R2 | typescript | MEDIUM | 소비자 결과 (consumer-raw) 에 출처 머리글이 없다 | 중복 · 미흡수 | 위 security MEDIUM 과 같은 항목이다 (백로그 2026-10-24 MEDIUM 행) | 8 |
| R2 | typescript | LOW | owner-check 의 교차 소실 분기가 죽은 코드이고 분모를 출력하지 않는다 | 기각 (현행 판에서 사실 아님) | 현행 출력: `LOSS=28 ... INTENDED_SCOPE=28 OWNER_NO_2XX=0 NONOWNER_2XX=0 CELLS=394` 와 `EVAL nonowner_target=100 owner_target_skipped=230` 이 나온다. 28 셀은 소실 분기에서 「대상 행 · 403 · 소유 아님」 으로 셈하고 소유 대조를 거친다 (독립 판정). 지적은 옛 판의 조건문을 가리킨다 | 7 |
| R2 | typescript | LOW | 검사 #14 의 `.cjs` 파일이 구문 · 호스트 검사에서 빠진다 | 기각 (워킹트리에서 이미 포함) | 워킹트리 c14 의 파일 목록은 `*.sh *.mjs *.cjs` 다. 커밋본만 `*.sh *.mjs` 다. 사본에 `.cjs` 로 주입한 호스트를 검사가 본다 | 4 |
| R2 | security | LOW | 격리 주장 · 복원 함수 접두 확인 · 게시 포트 바인딩 · `.cjs` 포함 · 「외부 호출 0」 문구 | 부분 흡수 | 복원 접두 확인은 이미 있다 (`replay-m9d.mjs:151` · `m9d-iso-` 접두 둘 다 검사, 기각). 게시 포트: 검사 #14 가 `-p "0.0.0.0:…"` 사본을 FAIL 로 잡는다 (기존 검사, 확인). DB · Redis 기동 뒤 ss 확인은 `_m9d-lib.sh` 에 추가했고 재실행에서 실행됐다 (사본 시험: 루프백 일치 · 와일드카드 불일치). 백로그 2026-10-24 LOW 행을 RESOLVED-IN 으로 닫았다. 「외부 호출 0」 은 PRD 에서 (미검증) 로 낮췄다 | 5 · 8 |
| R2 | security | LOW | 「정상 경로」 증명 집합 (결제 승인 · 취소) 의 문구가 실제보다 크다 | 흡수 (문구) | PRD 9d 절을 「가드 통과만 증명」 으로 고쳤다 (백로그 1949 정정 2 와 같다). 증명 집합 이름은 검사 #33 에 그대로 둔다 (주석에 이미 「가드 통과」 가 있다) | 2 |
| R2 | security | LOW | 회귀 jest 선택 패턴이 절대 경로에 맞고 실행 환경이 비어 있지 않다 | 부분 흡수 | 환경: 재실행 스크립트가 이미 `env -i` 허용 목록으로 돈다 (재실행에서 확인). 패턴: 기준선과 같은 245 suite 집합이라 비교는 성립한다. 「묶음」 이라는 표현은 노트 · PRD 에 없었다 (grep 0 건). 패턴을 파일 이름 기준으로 바꾸는 것은 기준선 재측정이 필요해 백로그 2026-10-24 LOW 행 (다음 계획) 에 남긴다. PRD 9d 절에 선택 범위를 적었다 | 8 |
| 지시 | 사용자 | — | 계획 게이트 receipt 없이 구현에 진입했다 (R3 상한 · CRITICAL 없음 · `MCCP_SKIP_RECEIPT`) | 기각 — 사용자 지시 확인(verify-user-directive.sh, 2026-10-19T02:21Z 원문) | 검증 스크립트 exit 0. 원문은 「계획 게이트 · R3 상한 처리」 절 | — |

#### 라운드 2 · 추가 확인 (지시 항목)

- 지시 2 (I9 목록 길이): 원자료에 응답 배열 길이가 없다. I9 의 2xx 셀은 관리자 6 셀뿐이고 (OFF 2 · 재실행 2 · ON 2, 전부 `200/2`), `2` 는 응답의 `count` 필드 값이지 배열 길이가 아니다. OFF 와 ON 의 `count` 가 같으므로 `count` 로도 축소는 없다. 예외 `LEN_GAP_ROWS` 는 유지한다. 영향: I9 관리자 신원의 0행 축소는 미검증이다 (백로그 2026-10-24 LOW · I9 행과 같다). 나머지 I9 셀은 OFF 와 ON 모두 403 이라 목록이 없다.
- 지시 1 (#28 짝 셀 정보 없음): 짝 OFF 403 인 교차 셀 41 개 (11 행) 는 「정보 없음」 으로 따로 세고 요약 · 출력에 개수와 행이 있다. 그 셀은 유효 셀이 아니라 증명에 들지 않는다. 기업 · 조직 교차 부류는 미증명 표지가 붙어 있고, 검사 #28 이 그 집합을 고정한다.
- 지시 4 (itspec 을 형제 픽스처 없이 돌림): 형제 셀 증명은 재생 쪽에 원래 없었다 (형제 셀 14 개 OFF 403, 유효 0). 형제 경계 근거는 `scope-single-join.itspec.ts` 의 「구분자 경계」 시험 (`SYNORGA1` 이 `SYNORGA10` 의 회원 9303 을 거부) 으로, 키 조회 판정이라 픽스처와 무관하다. `M9D_NO_SIBLING` 이 형제 증명을 추가로 약하게 하지는 않았다. 약해진 것은 목록 필터 장면 (9303 을 쓰는 머니 목록 · 지갑 · 결제 · 조직) 이다.
- 지시 5 (측정 산출 민감 정보): 홈 디렉터리 경로 접두 0 건, 비밀 형상 (32 자 이상 16 진, `eyJ`) 은 없다. 32 자 이상 16 진은 git 트리 sha 뿐이다. `Bearer` 는 시험 제목 문자열뿐이다. 「삼성화재」 는 서버 시험 제목 원문이다 (위 표). 실명 탐색은 자동 검사가 없어 (미검증) 이다.
- 검사 #14 의 음성 기록 (git 디렉터리 `mccp-gatekit/m9d-neg-r2`): 원소 분리 호스트 · 공백 형식 · 배열 결합 형식 · `docker -p 0.0.0.0` 은 FAIL, 루프백은 PASS (위 표).
- 흡수 뒤 검사 전체: 아래 「라운드 2 검사 결과」.
- 이월 (백로그 기존 행, 이번 라운드 미흡수): 소비자 출처 머리글 (MEDIUM) · 계획 본문 정의 (MEDIUM) · 회귀 jest 패턴 (LOW) · 삼성화재 기록 (LOW).

#### 라운드 2 검사 결과

- 사이드카 38/38 PASS, rc 0 (워킹트리 · 커밋 전).
- 흡수 전후 붉음 · 초록: 검사 #35 는 흡수 전 FAIL (이름 파일 · 끝 트리 · 환경 줄 부재), 회귀 jest 재실행 뒤 PASS. 재실행 결과 2860 시험 · 실패 35 · 보류 23, F 행 38 · P 행 23, `DIFF_F=0 DIFF_P=0`, 시작 · 끝 트리 `5e740146` (기준 3418d3a0), 끝 워킹트리 청결.
- 검사 #14: 흡수 전 현행 정규식은 원소 분리 호스트 `'-h', '호스트'` 사본을 PASS 로 넘겼다 (같은 입력의 신 정규식은 1 건). 흡수 뒤 그 사본 FAIL, 루프백 사본 PASS, 원본 PASS.
- 검사 #12: 셀 파일 한 칸 변경 사본 두 개 (원인 열 · ON 열) 에서 FAIL, 원본 PASS.
- DB · Redis ss 확인: 사본 시험 (루프백 리스너 일치 · 와일드카드 리스너 불일치) 으로 식을 검증했고 재실행에서 기동 확인이 실행됐다.
- 서버 코드 변경 0 이라 서버 테스트 · 타입 검사는 다시 돌리지 않았다. 측정 재실행 (회귀 jest) 과 격리 itspec 산출은 이번 라운드에서 다시 쟀다 (itspec 은 재실행하지 않았다, 상태 파일 2026-10-26 09:57 기준).
- 음성 기록 위치: git 디렉터리 `mccp-gatekit/m9d-neg-r2` (사본 · 커밋하지 않음).

## 메인 실행 (2026-10-26) — itspec 실행기 stdin 결함 수정과 재실행

- 리뷰 R2 흡수 에이전트가 찾은 결함: `itspec-run-m9d.sh` 의 `while read` 루프 안 `restore_db` → `m9d_gq -e` 의 `docker exec -i` 가 루프 stdin(파일 목록)을 먹는다. 목록을 fd 3 으로 읽게 고쳤다(`read -r f <&3` · `done 3< "$FILES_LIST"`).
- 메인 세션이 고친 실행기로 다시 돌렸다(`M9D_NO_SIBLING=1`, 약 4분, 포그라운드로 충분): `ITSPEC_DONE suites=12 tests=123 failed=0 pending=0` · `JEST_DONE tests=2860 failed=35 pending=23` · `FILES=12`. 앞 실행과 같은 수다 — 앞 실행도 12 파일을 다 돌았다(결함이 결과를 줄이지는 않았다).

## 마감 판정 (2026-10-26)

- 판정: **행 9 는 in-progress 를 유지한다.** 보완 계획 admin-rbac-m9d 의 측정 · 검사 · 리뷰는 끝났지만, 증명 9 행의 일부가 격리로 할 수 있는데 하지 않은 채 남았다. complete 로 올리지 않는다.
- 대조 (PRD 행 9 의 Outcome · 증명):
  - 증명 「소비자 대응표 행마다 정상 회귀」: 9 흐름 중 2xx 로 증명된 것은 3 개뿐이다 (`INFO_33 proven=3 unproven=6`). 미증명 6 흐름 (`site-admin-view-public` · `app-user-refresh` · `callback-coocon` · `callback-mok-auth` · `scheduler-push` · `scheduler-alimtalk`) 은 발급 토큰 전달 · 저장 refresh 토큰 · 스케줄러 만기 행 시드로 격리에서 닿을 수 있다. 격리 가능 항목이다.
  - 증명 「강제 후 음성 spec (다른 기업)」: ON 에서 닫힌 교차 셀은 영업 28 셀뿐이다. 기업 · 조직 관리자 교차 56 셀과 형제 14 셀은 OFF 403 이라 ON 경계를 본 적이 없다 (백로그 1948 PARTIAL). 재생 재측정으로 닿을 수 있다. 격리 가능 항목이다.
  - 증명 「이행표 권한 소실 0」: 의도되지 않은 소실 0 (검사 #9). 목록 0행 축소 0 이지만 I9 목록 길이는 원자료에 없어 미검증이다 (백로그 MEDIUM). AO 교차 셀 (백로그 1974) 도 같은 계열이다. 격리 가능 항목이다.
  - 증명 「정상 경로 spec 녹색」: 격리 itspec 123 시험은 통과했으나 픽스처 포함 스냅샷에서 다시 돌리지 않았다 (백로그 1950 PARTIAL). 격리 가능 항목이다.
  - 증명 「would-deny 판단 표」: 표의 실내용은 사용자 몫이다. 격리로 채울 항목이 아니다.
- 잔여 분류
  - 격리 가능 (in-progress 의 이유): 미증명 6 흐름 · 기업 · 조직 관리자 교차와 형제 경계의 ON 증명 · I9 목록 길이 · AO 교차 셀 · 픽스처 포함 itspec 재실행. 다음 보완 계획 범위로 백로그 2026-10-26 표지 행에 넘겼다. 계획 이름 `admin-rbac-m9e` 는 제안이다.
  - 사람 확인 몫 (미검증): 복제본 1회 재생 (복제본 생성은 승인 필요) · 스위치 ON 커밋 · would-deny 판단 표 운영본 · R2 · R7 · R19 · 동시 판정 상한 부하 실측 · OFF 상태 영업 교차 노출 28 셀의 운영 판단.
- 계획 게이트 receipt: 없음. 상한 3/3 뒤 `MCCP_SKIP_RECEIPT` 로 진입했다. 사용자 지시 원문은 `verify-user-directive.sh` 로 확인했다 (exit 0). 라운드 2 판정 표의 「지시」 행으로 기각 판정을 남기고, 백로그에 RESOLVED-IN 행 (2026-10-26) 을 달았다.
- 구현 보고서 (`.claude/PRPs/reports/admin-rbac-m9d-report.md`): 만들지 않았다. 검사 #6 의 허용 목록과 경계 B2 (`.claude/PRPs/plans/` 까지) 에 `.claude/PRPs/reports/` 가 없어 그 파일을 넣으면 검사가 붉어진다. 이 노트의 「구현」 · 「코드 리뷰」 · 「메인 실행」 절이 같은 내용을 싣는다. 보고서 경로를 허용할지는 메인 판정이다. 검사 허용 목록은 고치지 않았다.
- 포맷: 노트 · 백로그 · PRD 세 md 는 HEAD 자체가 prettier 정렬이 아니어서 (`prettier(HEAD)` ≠ HEAD) 「prettier 만 바뀐 변경인가」 를 가릴 수 없었다. 워킹트리 변경은 이 마일스톤의 측정 · 리뷰 기록이다 (위 절들). prettier --write 는 돌리지 않았다. `.sh` 는 prettier 대상이 아니다.

## 최종 리뷰

### 라운드 1 판정 (2026-10-26, 커밋 전 워킹트리)

지적 6 건 (fable 3 · codex 3). 흡수 4 · 기각 2. HIGH · CRITICAL 0. 흡수 대상은 측정 원자료나 서버 코드가 아니라 검사 사이드카 (`admin-rbac-m9d.gates.sh` 의 #13 · #34 · #35) 와 측정 스크립트 세 개다. 계획서 · 원자료 · receipt 는 고치지 않았다. 흡수 뒤 검사 38/38 PASS, rc 0 (커밋 전 재확인). 반사실은 임시 사본 (`/tmp` 아래) 에서 돌렸다. 새 측정은 돌리지 않았다 (격리 스택 필요, 이 라운드 범위 밖).

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable | LOW | 계획 Acceptance 331 「회귀 jest 시험 전부 passed」 와 게이트 #35 정의가 다르다 (#35 는 실패 35 · 보류 23 이 기준선과 같으면 통과). 계획 326 · 82 의 교차 · 형제 「기본 차단」 정의도 게이트 #28 (짝 2xx 면 기본 차단) 과 다르다 | 기각 (계획서 동결) | 사실은 맞다. 계획서는 plan_hash 로 동결돼 고치지 않는다. 이탈 1 · 5 와 백로그 2026-10-24 MEDIUM 행 (c28, 계획 326 · 82) 에 이미 적혀 있다. 331 행은 백로그 2026-10-26 LOW 행으로 보탠다 | 2 |
| R1 | fable | LOW | 검사 #34 가 형제 픽스처 없음 (`SIBLING_FIXTURE=absent`) 을 정답으로 박아, 남은 항목 (픽스처 포함 itspec 재실행) 을 돌리면 붉어진다 | 흡수 | #34 를 「짝 맞음」 검사로 바꿨다 (`M9D_NO_SIBLING=1` ↔ `absent`, `0` ↔ `present`). 이탈은 노트 이탈 2 가 적는다. 반사실: 현재 상태 PASS · 짝 맞는 present 사본 PASS (옛 #34 FAIL) · 짝 어긋난 사본 (1 · present) FAIL | 1 |
| R1 | fable | LOW | implement receipt 의 `findings: []` · `deferred_findings_count: 0` 이 노트의 R1 ~ R3 지적과 맞지 않아, PR 단계 dedupe 가 이월 항목을 못 읽는다 | 기각 | receipt 는 mccp 가 쓰는 산출이라 수기로 고치면 `receipt_hash` 가 깨진다 (R1 코드 리뷰의 같은 기각과 같은 근거). 이월 F3 (I9 목록 길이) 은 백로그 2026-10-24 MEDIUM 행에 있다. PR 단계가 receipt 만 보고 이월 0 을 읽는 위험은 (미검증) 으로 남긴다 | 2 |
| R1 | codex | MEDIUM | `itspec-run-m9d.sh` 와 회귀 jest 원본 (`jest-rerun-m9d.sh` 포함) 이 part · raw JSON 을 지우지 않고 `|| true` 로 종료 상태를 버린다. jest 가 JSON 을 못 쓰고 죽으면 지난 실행의 통과 결과가 합쳐진다 | 흡수 | 실행 전에 part · raw 를 지우고, JSON 이 비면 중단한다 (`ITSPEC_PART_MISSING` · `JEST_RAW_MISSING` · `JEST_RERUN_RAW_MISSING`). 반사실: JSON 을 안 쓰는 가짜 jest 에서 옛 경로는 지난 결과를 소비해 rc 0, 새 경로는 rc 1. 현재 증거의 신선도: itspec 12 파일의 시작 시각은 모두 STARTED 뒤 (최소 7.4 초), 회귀 245 suite 는 itspec 뒤 (100 초 이후), 재측정 raw 245 suite 는 요약 STARTED 뒤 (1.6 초) | 4 |
| R1 | codex | MEDIUM | 검사 #35 가 실패 · 보류 이름과 파일 토큰만 대조해, 시험과 suite 가 대량으로 빠져도 붉지 않는다 (245 suite · 2,860 시험을 16 suite · 135 시험으로 줄이고 counts 를 맞춘 사본에서 통과) | 부분 흡수 → 백로그 | #35 에 전체 시험 수 대조를 더했다 (`numTotalTests` 가 재측정 요약의 값과 같아야 한다). 반사실: 실패 · 보류 suite 와 토큰 suite 를 남기고 21 suite · 395 시험으로 줄인 사본에서 옛 #35 PASS · 새 #35 FAIL. 남은 한계: 전체 suite 집합과 통과 시험 이름은 이 검사가 보지 않는다 (요약에 suite 수가 없다). 백로그 2026-10-26 MEDIUM 행 | 6 |
| R1 | codex | MEDIUM | 검사 #13 이 side-by-side 행 4 개를 요구해, 현재 빌드 스크립트 (`git diff --no-ext-diff`) 의 패치로는 새 측정이 반드시 붉다 | 흡수 | #13 은 side-by-side 행이 없을 때 `ON_PATCH` 가 `ON_UNI` 와 같은 파일이면 통과한다 (unified 본문 검사 `+` 4 · `-` 4 가 그 역할). 원자료 (difft) 는 그대로 통과한다. 반사실: 원자료 PASS (새 · 옛 모두) · unified 사본 PASS (새) / FAIL (옛) · 사본 패치에 빈 줄을 더한 것 (`ON_UNI` 와 어긋남) FAIL (새) | 5 |

### 판정 메모

- 흡수 4 건 중 #35 는 부분 흡수다. 전체 suite 집합 대조는 재측정 요약 (`jest-rerun-m9d.summary.txt`) 에 suite 수와 이름 집합이 없어 검사만으로는 할 수 없다. 다음 계획의 입력으로 백로그에 넘겼다.
- 검사 #34 와 #13 은 현재 측정의 판정을 바꾸지 않는다 (PASS 가 PASS 로 남는다). 바뀐 것은 다음 측정이 붉어지는 조건이다.
- 검사 #35 의 기준선 출처 (재측정 raw 245 suite) 는 요약 STARTED 뒤에 돌았다 (위 표). 그 신선도 확인은 이번 라운드의 증거로 남긴다.
- 모듈 테스트 · 타입 검사는 대상이 없다 (서버 · 어드민 · 사이트 코드 변경 0, 변경은 `.sh` 와 `.gates.sh`). prettier 대상도 아니다.
- HIGH 흡수 0, HIGH 미해소 0, CRITICAL 미해소 0. 루프 종료 조건 (HIGH 흡수 0) 에 해당한다.
