# 관리자 RBAC 마일스톤 6b 노트

## 마감 요약

- 판정: **M6 은 in-progress 로 둔다.** 이 보완 계획 (m6b) 의 구현 · 측정 산출 · 응답 불변 증명은 섰다. 그러나 PRD Outcome 핵심 항목 네 건이 아직 미달이라 complete 로 올리지 않았다. 판정 기준은 메인 세션이 정했다 (목록 · 단건 · 쓰기 · 일괄 · 조인표 · 측정 먼저). 강제 상수 OFF, 운영 동일 환경 1회 재생, R5 업무 확인은 complete 를 막지 않는다.
- 이 절과 백로그 HIGH 네 줄, PRD 6 행 갱신은 같은 부모 커밋 (`docs(prd)`) 에 싣는다. 최종 리뷰 R1 · R2 · R3 는 아래 「최종 리뷰」 절의 라운드다.

### 게이트 결과

- 전체 게이트 본문 (`.claude/PRPs/plans/admin-rbac-m6b.gates.sh`, 격리 MySQL `m6b-iso-` 합성 시드): `GATES_OK m6b`, 검사 #1–#17 통과 (R3 구간 실측, 서버 `36009c72`).
- 검사 #9 (측정 sha 조상 · 산출 커밋 포인터 · 상수 OFF): 부모 `d38dd5a` 위에서 통과 (「커밋 뒤 (R3)」 절). 이 마감 커밋 뒤에도 다시 돌렸다.
- 검사 #10 (응답 불변): 행 6336 · 쓰기 행 6 · 상태 0 행 0. 착수 · 재실행 · 최종 행 파일이 바이트로 같다 (편차 33 · 34 · 35 반영).
- 검사 #5 (측정 셀): 132 셀 ok, would_deny 합 36 (구조적 0 두 셀 포함, 「측정 공시 — 구조적 0」 절).
- 타입 검사: 서버 `tsc` 오류 23줄 = 기준선 23.
- jest (권한 · 범위 · 조직 · 머니 · 분석 · 구독 · 소속): 580 시험 중 579 녹색. 붉은 1건은 착수 기준선의 ENOENT 픽스처 (`jest-baseline.txt`).
- 계획 receipt 없음 (skip-receipt). 사용자 지시 원문은 `verify-user-directive.sh` 로 확인했다 (rc 0, type=user · userType=external). 구현 receipt 는 divergent.

### 코드 리뷰와 최종 리뷰

- code-review: R1 20건 · R2 9건 (흡수 · 부분 흡수 · 기각 · 이월 표는 「코드 리뷰」 절).
- 최종 리뷰 (fable · codex, 상한 3 라운드, 메인 세션 집계):
  - R1: 지적 10 · 흡수 9 · HIGH 흡수 4 · HIGH 미해소 3 · codex ok
  - R2: 지적 4 · 흡수 4 · HIGH 흡수 1 · HIGH 미해소 1 · codex ok
  - R3: 지적 5 · 흡수 4 · HIGH 흡수 0 · HIGH 미해소 0 · codex ok
- CRITICAL 0. 미해소 HIGH 네 건은 아래 미달 목록의 1)–4) 로 묶었다.

### PRD 미달 목록

백로그 HIGH 네 줄 (2026-10-21 「마감 · PRD 미달 1–4」, `→ 다음 계획 반영 대상 · 보완 계획 admin-rbac-m6bb`) 과 1:1 이다. m6 노트의 「PRD 미달」 7건 중 1–4 는 미달, 5–7 은 섰다.

1) 단건 3의미 분리 — **미달.** `judgeSingleScope` 는 spec 밖 호출처가 0 이다 (`scope-monitor.ts:143` 정의만). 단건 행은 전부 `judgeRowScope` 로 판정한다 (편차 14 · 20). I8 `detailList` 는 영업 전용 의미이고 「소유자 또는 관리자 열람」 은 연결되지 않았다. 증명 6 행의 「단건 3의미 spec」 도 함수 수준에만 있다.
2) 쓰기 검사와 변경의 같은 트랜잭션 — **미달 (잔여 4 경로).** 소속 · 조직관리자 지정 · 조직 라우트 쓰기 · 머니 기업 지정 쓰기 다섯 라우트는 잠금 뒤 판정이다. 남은 것: 결제 취소 (`cancelPaymentByAdmin`, PG 호출 앞 무잠금 판정), 회원 지정 적립 · 환불, 조직 권한 업로드 (`orgPermissionUpload`, AO4, 무트랜잭션), 소속 쓰기와 조직 삭제의 잠금 순서 역전 (교착 시험 없음).
3) 일괄 전건 검사 · 부분 성공 정책 — **미달.** 계획은 전건 거부를 골랐지만 실측은 모의 객체 시험 (`not.toHaveBeenCalled`) 뿐이다. 격리 MySQL 실측이 없다. 판정이 없는 일괄 경로가 남았다: 회원 지정 적립 엑셀 업로드 · 미리보기, 조직 권한 업로드 (AO4).
4) 목록 · count · 집계 · 내보내기의 타 기업 행 0 격리 증명 — **미달 (증명 6 행).** 생성기 (`scopeCondition`) 는 I1 · I4–I8 목록 · count · 집계 · 내보내기에 붙었고 「범위 인자 동일」 시험이 있다. 그러나 격리 MySQL 에서 타 기업 행 0 과 목록 · count 일치를 본 질의는 적립 목록 · 개수 (`money-scope.itspec.ts` 3건) 뿐이다. 지갑 · 결제 · 기업 이력 · 환불 목록 · 분석 질의는 행 수 대조가 없다.

섰다 (5–7 과 나머지 항목):

- 5) 「M5 범위 would-deny 측정」 — 섰다. `synthetic-tenants.sql` · `identities-scope.json` · `scope-would-deny.json` · `scope-measure.mjs` (측정 트리 판정, 편차 36).
- 6) 응답 불변 (검사 #10) — 섰다. 최종 빌드 `36009c72` 에서 3 빌드 행 파일이 바이트로 같다.
- 7) 조인표 — 섰다. 검사 #4: I 행 조인 칸의 「(미검증)」 0, 스키마 토큰 대조. 행 수 대조는 아니다.
- 신규 엔드포인트 범위 선언 — 섰다 (단언 항목 「범위 선언 누락」 + 동결 골든 103행, 검사 #17). 런타임 모드 `warn` 은 그대로다. fail 전환은 M9 상수 몫이다 (정책 §4). 「M6 에서 활성화」 를 단언 항목과 동결 목록으로 읽었다. 이 해석은 리뷰 대상이다.
- 조직관리자 GET 외 기본 거부 · `@AllowOrgWrite` 예외 — 섰다. 이행표 AO1–AO15 (검사 #6), `corp-readonly.guard` 규칙 행.
- 열린 경로 9항목 · 담당 · 기업 기본 배치 — 섰다. 이행표 I1–I9 행이 각 판정 함수에 배치되어 있다.
- ORG `orgPath` 누락 거부 — 섰다. 검사 #7 의 `orgPath` 누락 시험 (판정 함수 수준).

공시 (미달 아님, M9 입력 또는 사용자 몫):

- 레거시 예외표: 파일이 없고 행은 0 이다 (정책 §3). 행은 사용자가 책임자 · 기한 · 잔여 위험과 함께 채운다 (R5). 완료 선언 없음.
- 조직관리자 기업 단위 행 판정: I1 의 org-A1 · org-A2 셀 would_deny 1 씩이 구조적 0 이다. 판정 키 설계는 미결이다 (백로그 MEDIUM).
- 분석 형제 경로 (basic · aggregate · prescribe · download): 측정 셀 밖이다 (백로그 LOW).
- 강제 상수 3개와 M7 · M10 전환 스위치는 전부 OFF 다 (M9).

### (미검증) 과 사용자 몫

- 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인, UI8): defer-unverified. complete 를 막지 않는다.
- R5 영업 전사 열람 · 쓰기 업무 확인: defer-unverified. 예외 행은 사용자가 채운다.
- 실트래픽 would-deny: (미검증).
- 편차 33: 창 경로 (`/admin/treat-monitor/overview`) 는 구조 · 상태 · 헤더만 비교했다. 본문 값은 대조하지 않았다.
- 응답 본문의 code E0607 · reason `scope` 격리 실호출: 없음 (R3 미검증).
- 머니 쓰기 네 종 · 환불 · 분석의 격리 두 연결 경합 시험: 없음 (호출 순서 시험과 반사실로만 본다). 소속 쓰기와 조직 삭제 교착 시험도 없다.
- 경합 itspec 의 서버 CI 편입: 이연 (R2 L3 MEDIUM).
- 강제 ON 체크리스트 (정책 §4): DDL 두 축 · 적재 · NULL 0 · would-deny 판단 표 · R2 · R5 · R19 · M7 번들 · R16. 전부 M9 에서 확인한다.

### 다음 마일스톤이 알아야 할 것 (보완 계획 admin-rbac-m6bb 입력)

1) 상태: M6 in-progress. 미달 네 건은 백로그 HIGH 행이다. 기존 HIGH 행 (1611 · 1628 철회 행, 조직 삭제 교착, 회원 지정 적립 · 환불, 결제 취소, AO4) 과 같은 자리는 행끼리 참조한다.
2) 판정 키 설계가 미결이다: 단건 신원 종류별 키 (영업 `sagId`, 기업 최고 `agcId`, 조직관리자 `agcId` + `orgPath`), I8 단건 의미 (잠정 영업 판정), 기업 행의 조직관리자 판정.
3) 잠금 순서가 두 갈래다. 소속 · 조직관리자 지정은 회원 → 담당 기업 → 현재 소속 조직 → 목적지 조직이다. 머니 지급은 기업 → 회원이다. 조직 삭제는 담당 기업 → 하위 회원이다. 교차 교착 시험이 없다.
4) 측정 산출 (`scope-would-deny.json`) 의 `server_sha` 는 `b2c476a8` (착수) 다. 검사 #9 는 이 sha 가 첫 비시험 서버 소스 커밋 `ef82c4bc` 의 조상임을 본다. 판정 함수는 측정 트리의 `scope-monitor.ts` 를 부른다 (편차 36).
5) 서버 HEAD `36009c72`, 부모 `d38dd5a` (R3 커밋). 서브모듈 admin `1301b664` · site `fc8872a1` 은 M6b 에서 바뀌지 않았다.
6) 게이트 사이드카 `admin-rbac-m6b.gates.sh` 는 동결이다. 다음 계획은 새 사이드카를 쓴다. 검사 번호의 정본은 사이드카 블록이다.
7) 격리 MySQL 주의: itspec 전체 실행은 전체 스키마 덤프가 필요하다 (편차 25). 경합 두 개만 `jest-itspec.config.cjs` 로 고른다. 격리 컨테이너 접두 `m6b-iso-` 는 끝나면 내린다.

## 계획 게이트

- 판정: **divergent · 상한 소진 · receipt 없이 skip-receipt 로 진행**. 원문 판정은 `.claude/reviews/plan-review-admin-rbac-m6b.md`.
  - 용어: L2 = 역할별 패널 리뷰 (정족수 3 필요), L3 = Codex 리뷰, 상한 = 계획 리뷰 3라운드, skip-receipt = 계획 receipt 는 쓰지 않고 구현 진입 검사만 환경 변수 `MCCP_SKIP_RECEIPT=1` 로 넘기는 처리.
  - L1 converged · L2 divergent (통과 2 / 응답 4, 정족수 3 필요) · L3 divergent (high 2) · halt `5.2e`.
  - review_source `multi-agent` · review_proof `null`. 정상 receipt 를 쓸 수 없는 형태이고, Codex 판정은 넘기지 않았다 (`forwardCodexVerdict=false`).
- 최종 계획 해시: `sha256:331c3e90fe123362c950cfad6b3a5754a6bebb4cec7c6d41f96f9f8b81b2b9fb`. 계획서 `.claude/PRPs/plans/admin-rbac-m6b.plan.md` 는 고치지 않았다 (동결).
- 사용자 지시 (세션 기록 원문 확인, 2026-10-19, 전역 CLAUDE.md mccp 한시 규칙보다 우선):
  「Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.」
- CRITICAL 유무: **없음**. 마지막 라운드 지적은 HIGH 4 · MEDIUM 5 · LOW 3 (합 12). architect · test 의 `FAIL` 은 판정 문구라 지적 수에서 뺐다.
- 구현 진입 검사: `MCCP_SKIP_RECEIPT=1 node .../receipt/cli.js preflight --command mccp:prp-implement --decision admin-rbac-m6b --plan .claude/PRPs/plans/admin-rbac-m6b.plan.md` → `ok: true`, `bypassed: true`, `reason: MCCP_SKIP_RECEIPT`. env 는 이 명령에만 붙였다. `MCCP_SKIP_INTENT_GATE` 는 쓰지 않았다. 계획 receipt write 는 시도하지 않았다.

### 라운드별 결과

| 라운드 | L2 | L3 | 판정 | 비고 |
|---|---|---|---|---|
| 1 · 2 | 세부 판정을 이 세션에서 확인하지 못했다 (상태 파일에는 마지막 라운드 것만 남아 있다) | 같은 이유로 미확인 | divergent | 흡수 흔적은 backlog 의 2026-10-21 RESOLVED-IN 줄과 PLAN-R2 3줄 |
| 3 (마지막) | divergent · 통과 2 / 응답 4 (정족수 3) · HIGH 2 · MEDIUM 5 · LOW 3 | divergent · HIGH 2 | divergent · 상한 소진 | 아래 처리 대상 |

### 처리

1. 마지막 라운드 미해소 지적 12건을 `.claude/plans/codex-findings-backlog.md` 에 `2026-10-21` 줄로 append 했다. 기존 줄은 고치지 않았다 (추가 12줄, 삭제 0줄). 같은 라운드 항목은 backlog 에 없었다.
2. 리뷰 원문에는 있으나 요약 목록에 빠진 지적 1건 (L2 invariant LOW · 검사 #9 머리 주석의 merge-base 검사 없음)도 함께 넣었다.
3. HIGH 4건에는 `→ 다음 계획 반영 대상` 표지를 달았다 (아래 목록).
4. receipt 는 쓰지 않았다 (`receipt_path = (없음)`). 계획서는 고치지 않았다.

### 다음 계획 반영 대상 (HIGH 4)

1. L2 architect · I8 단건 (detailList) 이 `judgeSingleScope` 를 써서, 목록은 기업·조직 신원에게 보이는데 단건은 막힌다. 여섯 신원에 관리자가 없어 단건 allow 가 구조적으로 0 이다 (`plan:66,173,185`).
2. L2 test · ON 모드에서 저장소가 범위 절을 실제로 적용하는지 검증이 없다. 시험이 전부 저장소를 모킹한다 (`plan Task 3` · `gates.sh:189-198`).
3. L3 · 쓰기 측정은 변경 전 소유 정보가 필요하다. `assignOrg`·`setOrgManager` 는 `{updated}` 만 반환하고, 조직 삭제는 응답 시점에 원천 행이 없다 (`plan:173-175`).
4. L3 · 단건 판정 키는 신원 종류별로 달라야 한다 (sales `sagId` · corp-super `agcId` · org-manager `agcId`·`orgPath`). 계획의 `agcId` 단일 키는 정상 영업 요청에서 예외를 낸다 (`plan:184-187` · `scope-monitor.ts:42`).

### 미검증

- 계획 검사 다수의 음성 케이스는 「구현 뒤 관측 기록」이다. 구현 창에서 처음 실측된다 (L2 test LOW).
- 이 처리는 계획 검사를 통과시킨 것이 아니다. 구현 진입 검사만 환경 변수로 넘겼다. 위 HIGH 4건은 다음 계획 작성에서 닫아야 한다.

## Codex Implementation Review

- 호출: `node <플러그인>/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, 2.5.3). focus 는 구현 전 결정 5건 (측정 · 강제 · 단건 두 갈래 · 쓰기 잠금 순서 · 계약 단언). 결과 원문은 워킹 git 디렉터리 `mccp/tmp/m6b-codex.stdout`.
- 라운드 수: 1 (원장 `mccp-implement-codex__admin-rbac-m6b`, cap 3). 이 호출이 마일스톤 게이트의 마지막 라운드로 처리됐다 (사용자 지시 2026-10-19).
- 판정: `needs-attention` → `divergent`. class=ok, blocking=false. CRITICAL 없음 → 사용자 지시에 따라 skip-receipt 진행, HIGH 는 backlog.
- 합치 결론: 구현 전 설계에 HIGH 3건. 쓰기 측정 자원 id 부재 · I8 member-only 판정의 승인 열람 차단 · 소속과 조직 삭제 사이 잠금 교착.
- YAGNI triage (구현 편차 판단은 아래 「구현 편차」):

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 쓰기 측정이 응답 id 를 쓸 수 없다 | HIGH | DEFER_TO_BACKLOG | 계획 Task 2 의 측정 어댑터 범위 변경이다. 계획서 동결로 이 계획은 원안대로 구현하고, 재현된 자리는 구현 편차에 적는다 |
  | F2 I8 member-only 판정이 승인 열람을 막는다 | HIGH | DEFER_TO_BACKLOG | 계획 정책 4 의 잠정 의미다. 기본 상수 OFF 라 운영 영향 없음. 같은 자리 기존 HIGH |
  | F3 소속 · 조직 삭제 잠금 교착 | HIGH | DEFER_TO_BACKLOG | 신규 지적. 잠금 순서는 Task 6 이 정한 것이다. 구현 중 재현 여부를 구현 편차에 적는다 |

- Deferred to backlog: 3 → `.claude/plans/codex-findings-backlog.md` 끝 2026-10-21 줄 3건 (HIGH, `→ 다음 계획 반영 대상`).
- Open Questions: 없음 (CRITICAL 없음).
- Codex session 참조: threadId `01a15c1a-f037-7960-b3cb-0c9b6aa5a6d2` (wrapper 출력의 threadId).
- 계획서 주입 대신 이 노트에 둔다 (메모 mccp-implement-review-goes-to-notes, plan_hash 보존).

## 구현 실행 기록 (이번 실행 · 커밋 없음 · 미완)

- 결과: **미완 (halted)**. Task 1 · 9 · 10 과 Phase 2.5 gate 는 끝냈다. Task 2 (측정) 가 끝나지 않아 Task 3 이후 코드를 쓰지 않았다 (계획 Task 2 Action 의 순서 규칙). 아래 「검사 결과」 가 현재 상태다.
- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` 워킹트리 clean. 부모 HEAD `c05d421` (계획 커밋 뒤). 부모 push · 커밋 없음.
- Phase 2.5: 정책 seal · 라운드 원장 seal (cap 3, key `mccp-implement-codex__admin-rbac-m6b`). Codex 2.5.3 호출 1회 → `needs-attention` → divergent (class ok, blocking false, CRITICAL 없음). implement receipt 는 env 없이 write 가 통과했다 (`.claude/receipts/mccp-implement-codex/admin-rbac-m6b.json`, `codex_verdict=divergent`, `skipped=false`, validate 통과). 사용자 지시 원문은 `verify-user-directive.sh` exit 0 으로 확인했다.
- Task 10 완료: jest 전체 234 suite · 2673 시험 · 실패 suite 6 · 실패 시험 3 (앞 M6 기준선과 같다). tsc 23 오류. 권한 spec 통과 168줄. 격리 Redis 는 treat spec 이 고정한 6380 포트에 띄웠다 (공유 dev-redis 는 쓰지 않았다).
- Task 1 완료 (열 추가 · 대조): 이행표 I 행 22 (항목 9 · 핸들러 실재), AO 행 15 (코드 사용처 15). 조인 열 중 `tn_agency.sag_id` · `tn_agency_organization.agc_id · org_path` · `tn_member.agc_id` · `mem_is_org_manager` 를 스키마 덤프로 대조했다. I4–I9 의 조인 열 다수는 「(미검증)」 이 남았다 (14 자리).
- Task 9 부분: `jest-itspec.config.cjs` 를 만들었다. `--listTests` 가 itspec 파일만 고른다 (검사 #8 의 설정 확인 부분). 게이트 사이드카 본문은 계획 시점 것을 그대로 두었다.
- Task 2 미완: 합성 테넌트 · 신원 파일 · 측정 하네스 · `scope-would-deny.json` 을 만들지 않았다. 쓰기 행 측정은 응답 자원 id 가 없어서 (`assignOrg` · `setOrgManager` 는 `{ updated }` 만 반환, `fc-agency.service.ts:728,756`) 측정 설계를 먼저 고쳐야 한다 (implement-codex HIGH 1, 백로그 2026-10-21 줄).
- Task 3–8 미완, Task 11 부분: RESOLVED-IN 줄은 아직 쓰지 않았다 (완료된 백로그 줄이 없다). 검사 #13 은 이 상태에서 붉다.

### 검사 결과 (이번 실행, 구현 뒤 아님)

| 검사 | 결과 | 근거 |
|---|---|---|
| #1 | 통과 | 세 착수 sha 가 HEAD 의 조상 (사이드카 실행) |
| #2 | 통과 | 기준선 헤더 full sha · failed-count 6 = suite 6 · tsc 헤더 |
| #3 | 통과 | 사이드카 실행에서 `build-catalog --check` 통과 |
| #4 | 통과 | I 행 9 id · 파일 · 핸들러 실재 · 조인 토큰 3 스키마 대조 (표 전체 토큰은 검사 본문이 센다) |
| #5 | 붉음 (멈춤) | 산출 파일 부재 (`scope-would-deny.json`). 사이드카 줄 154 에서 멈춤, 뒤 검사는 돌지 않았다 |
| #6 | 통과 (수동 실행) | 코드 `@AllowOrgWrite()` 15 = 표 AO 15 |
| #7 | 붉음 (수동 실행) | 표 I 행 강제 경로 식별자가 핸들러 파일에 없다 (0건): `organization-admin.controller.ts getList` (listScopeCondition) · `agency-fc-admin.controller.ts assignOrg · setOrgManager` (judgeRowScope) · `money-admin.controller.ts` 10개 (getCorpHistory · getWalletList · downloadWalletExcel · getMemberWalletHistory · getPaymentList · downloadPaymentExcel · getPaymentDetail · getPaymentUsage · getIssueList · getIssueDetail) · `analyze-admin.controller.ts detailList` (judgeSingleScope). M6 의 편차 C 와 같은 자리 |
| #8–#17 | 미실행 | 위 붉음 · 미작성 산출에 막혀 이번 실행에서 돌리지 않았다 |

## 구현 편차

1. **중단 (계획 순서 규칙)**: Task 2 측정 산출이 없어 Task 3 이후 (목록 · 단건 · 쓰기 · 계약 단언 · 상수 시험) 를 쓰지 않았다. 계획 Task 2 Action 「측정이 끝나기 전에는 Task 3 이후 코드를 쓰지 않는다」.
2. **측정 판정 키의 신원별 분해**: 계획 표기 `judgeRowScope(키 목록)` 은 신원 종류마다 키가 다르다 (sales = sagId · corp-super = agcId · org-manager = agcId + orgPath, `scope-monitor.ts` 의 `REQUIRED_KEYS`). 이행표 「측정 판정」 열에 종류별 키를 적었다. 시그니처는 바꾸지 않는다 (계획 정책 9). implement-codex HIGH 4 (`plan:184-187`) 와 같은 자리.
3. **쓰기 측정 설계 결함 (구현 전 확인)**: `assignOrg` · `setOrgManager` 는 `{ updated }` 만 돌려준다. 응답 자원 id 조인으로는 쓰기 셀을 잴 수 없다. 계획 Task 2 의 측정 방식은 쓰기 행에 대해 맞지 않는다. 계획서가 동결이라 이 노트와 백로그에 적고, 다음 계획에서 고친다.
4. **I8 단건 의미 (잠정)**: implement-codex HIGH 2. 기업 · 영업 · 조직관리자 신원에 `memId` 가 없어 `judgeSingleScope` (소유 회원 일치) 가 승인된 열람을 막는다. 계획은 잠정으로 적었고 강제 상수는 OFF 다. 구현 단계로 가지 않았으므로 재현은 실행하지 않았다.
5. **잠금 교착 (implement-codex HIGH 3, 신규)**: 소속 쓰기 (회원 → 담당 영업) 와 조직 삭제 (담당 영업 → 삭제 subtree 회원) 의 교차 교착 가능성. 구현 전 코드 읽기로만 확인했다 (`organization.repository.ts:488-492`). 재현 시험은 쓰지 않았다.
6. **커밋 없음**: 사용자 지시에 따라 커밋하지 않았다. 계획 검사 #9 (측정 산출 sha 의 조상 · 서브모듈 포인터) 는 커밋이 생긴 뒤에야 성립하므로 이번 실행에서는 (미검증).
7. **(미검증) 항목**: 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인), R5 영업 전사 열람 업무 확인, 실트래픽 would-deny, 운영 스키마 대조. 전부 계획 Acceptance 의 (미검증) 줄 그대로다.

## 구현 2차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` 워킹트리 clean (서브모듈 status 에 `+`·`-` 없음). 부모 `c05d421`. 이 실행 전 `m6b-iso-` 컨테이너 없음.
- 사용자 지시 원문: `verify-user-directive.sh` 재확인 rc 0 (UI6 · UI7 근거 유지).
- Task 1: 이전 실행의 이행표 (I 행 22 · AO 15 · 조인 열 대조) 를 그대로 쓴다. 이번 실행에서 손대지 않았다.
- 격리 DB 기동 확인 (Task 2 의 첫 단계): `m6b-iso-mysql-*` (mysql:8.0.42, `--log-bin-trust-function-creators=1 --sql-mode=""`, 빈 포트) 에 `02-schema.sql` · `01-functions.sql` · `drizzle/0031_admin_audit.sql` · `drizzle/0036_auth_permission.sql` 을 적재했다. 결과: 표 160 · 루틴 6 (에러 없음). 적재 스크립트는 실행 작업 디렉터리 밖 (임시 디렉터리) 에 두었고 커밋하지 않았다. 확인 뒤 컨테이너는 내렸다 (`m6b-iso-` 0개).
- 신원 파일 `.claude/meta/2026-10-21-admin-rbac-m6b/identities-scope.json` 작성 (CREATE, JSON 파싱 통과). 순서는 이행표 「기대 상태」 칸과 같다. 합성 id 는 기업 A·B = `agcId` 9001 · 9002, 영업 A·B = `sagId` 9101 · 9102, 조직 A1 · A2 = `/9001/` · `/9002/` (`ORG_PATH_RE` 형식). 토큰 값은 싣지 않는다 (측정 하네스가 `GK_JWT` 로 메모리에서 서명). 합성 시드 (`synthetic-tenants.sql`) 가 이 id 를 같게 써야 한다 (아직 없다).

### 남은 Task 2 (다음 실행이 이어 할 순서)

1. `synthetic-tenants.sql` 작성: 기업 2 (9001 · 9002) · 영업 2 (9101 · 9102) · 조직 2 (`/9001/` 루트와 접두 관계의 자식 쌍 포함). 블록마다 `-- @tenant agency` · `sales` · `org` 표지. 적재 대상 표는 이행표 I 행의 조인 열이다 (`tn_agency` · `tn_agency_organization` · `tn_member` · `tn_order`/`tn_payment` · `tn_money_*` · `tn_analysis_*` · `tn_subscribe_failure` · `th_fax` 등 · 열 이름은 `02-schema.sql` 의 CREATE 블록에서 읽는다). 적재는 `mysql --default-character-set=utf8mb4` (메모 `server-live-isolated-mysql` 의 시드 함정).
2. 신원 토큰: `GK_JWT` 를 실행마다 `openssl rand` 로 만들고 probe.mjs 와 같은 `_aaT` 페이로드 형상으로 서명한다 (M6 probe.mjs 의 sales · corp-super · org-manager 행 참고).
3. 측정 하네스 `scope-measure.mjs`: 이행표 22행 각각의 호출 방법 (목록 · 단건 · 내보내기 · 쓰기) 을 정하고, 응답의 자원 id 로 원천 테이블을 조인해 소유 키를 얻은 뒤 `judgeRowScope` · `judgeSingleScope` 를 적용한다. 쓰기 행 (I2 create · update · move · delete · I3 assignOrg · setOrgManager) 은 응답에 자원 id 가 없으므로 (구현 편차 3) 요청이 가리키는 대상 id 로 쓰기 전 상태를 원천 조인해 판정한다. 이 판정 경로 선택을 편차에 적는다.
4. 서버 기동: `insuniverse-server` 를 `npx nest build` 로 빌드하고 `env -i` 로 격리 env (`DB_*` 격리 포트 · 빈 `REDIS_URL` · 합성 `JWT_SECRET`) 를 준다. `.env` 파일이 트리에 없어야 한다 (M5 `gk_no_dotenv`).
5. 셀마다 상태 확인: 기대와 다르거나 status 0 · 5xx 면 측정을 멈춘다 (0 으로 채우지 않는다). 셀 집합 132 = 22 행 x 6 신원. 완료 뒤 `scope-would-deny.json` (server_sha · harness_sha256 · tenants 포함) 을 쓰고 검사 `#5` 를 돌린다.
6. 그 뒤 검사 `#9` · Task 3 이후 구현은 Task 2 산출이 선 뒤에만 한다 (계획 Task 2 Action 의 순서 규칙).

### 검사 결과 (이번 실행)

- 검사 `#5`: 산출 미작성 (붉음 상태 그대로, 실행하지 않았다). 나머지 검사는 Task 2 산출 뒤로 미뤘다.
- 격리 DB 기동 · 적재 확인은 검사 밖의 사전 점검이다 (게이트 항목 아님).

### 이번 실행의 결정

- 쓰기 행 판정 경로: 응답 id 가 아니라 요청 대상 id 를 원천 조인해 쓰기 전 소유 키를 읽는다 (구현 편차 3 의 대안). 계획서 Task 2 본문 「응답의 자원 id」 와 다르므로 계획서는 고치지 않고 (동결) 이 노트와 편차에만 적는다. implement-codex F1 (백로그 2026-10-21) 의 재현 자리와 같다.
- 신원 키 값은 합성 id 9001 · 9002 · 9101 · 9102 로 고정했다 (실행 간 재현을 위해).

### 미검증

- 위 전 항목은 검증이 아니라 준비다. 측정 수치 · would-deny 값은 아직 없다.

## 구현 3차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` 워킹트리 clean. 부모 `c05d421` 위 변경은 이 노트 · 백로그 · 메타 디렉터리뿐이다. 착수 시 `m6b-iso-` 컨테이너 없음.
- 사용자 지시 원문: `verify-user-directive.sh` 재확인 rc 0 (2026-10-19 · user/external · 라운드 상한 3 · 마지막 라운드 skip-receipt · HIGH 는 backlog).
- 앞 실행의 「구현 실행 기록」 · 「2차 실행 기록」 은 그대로 둔다.

### Task 2 step 1 (합성 시드) 완료

- `.claude/meta/2026-10-21-admin-rbac-m6b/synthetic-tenants.sql` (DDL 없음, `-- @tenant` 표지 22 블록): 영업 2 (sag 9101 · 9102), 기업 2 (agc 9001 · 9002), 조직 4 행 (`SYNORGA1` 루트 · `SYNORGA1C` 자식 · `SYNORGA10` 접두 충돌 형제 · `SYNORGA2` 기업 B 루트), 회원 6 (무소속 · 개인 포함), 머니 (발급 · 부여 · 지갑 · 원천 · 결제 · 환불 · 충전 요청 · 기업 원장), 분석 사슬 (`tn_subscribe` → `tn_payment` → `tn_order` → `tn_order_detail` → `tn_analysis_basic` · `tn_analysis_detail`), 구독 실패 2, 팩스 2.
- 적재 확인: `seed-load.sh` 한 번 돌려 rc 0. 표 160 · 행 수 tn_member 6 · tn_agency_organization 4 · tn_order_detail 3 · tn_money_issue 2. 격리 컨테이너는 이름을 적어 내렸다 (`m6b-iso-` 0개). 적재는 `sql-mode=""` 로 한다 (기존 M6 과 같다).
- 적재 스크립트: `.claude/meta/2026-10-21-admin-rbac-m6b/seed-load.sh` (환경 변수 `M6B_SCHEMA_DIR` 만 받는다). 비밀번호는 `MYSQL_PWD` 로 넘기며 파일에 쓰지 않는다. 컨테이너 이름을 표준 출력에 낸다. 내리는 것은 호출자가 그 이름으로 `docker rm -f` 한다.
- 편차 7: `identities-scope.json` 의 조직 신원 `orgPath` 를 `/9001/` · `/9002/` 에서 시드의 `org_path` 인 `/SYNORGA1/` · `/SYNORGA2/` 로 바꿨다. 신원 키 값이 바뀐 것이다 (정규식 `ORG_PATH_RE` 는 두 형태 모두 받는다). 「키 값은 합성 id 와 일치」 규칙을 맞추려는 것이고, 조직 id 가 26자 ULID 가 아니라는 사실은 시드 행에 그대로 둔다 (`varchar(26)` 안).
- 편차 8: 시드 값은 스키마의 NOT NULL 칸을 채우는 최소 값이다. 운영 데이터 형상 (예: 실제 결제 상태 전이 · 회원 유형 분포) 과 다르다. 대표성은 (미검증) 이다 (계획 Risks 1).

### 남은 작업 (다음 구간이 이어 한다)

1. Task 2 step 2 · 3: `scope-measure.mjs` (이 구간 미착수). 설계 사항 세 가지를 먼저 정한다. (가) 신원 토큰은 실행마다 `GK_JWT` 로 `_aaT` 쿠키를 서명한다 (M6 `probe.mjs` 의 `sign` 과 같은 형상, 영업 · 기업 · 조직관리자 payload 는 `identities-scope.json` 의 claims). (나) 판정 키는 신원 종류별로 다르다 (영업 `sagId` · 기업 `agcId` · 조직관리자 `agcId` + `orgPath`) — implement-codex HIGH 4, 편차 2 와 같다. (다) 쓰기 행 (I2 create · update · move · delete, I3 assignOrg · setOrgManager) 은 응답에 자원 id 가 없으므로 (편차 3) 요청 대상 id 를 쓰기 전에 원천 조인해 소유 키를 읽는다.
2. Task 2 step 4: 서버 `npx nest build` 와 격리 env 기동 (`gk_serve` 계열, `.env` 부재 확인 `gk_no_dotenv`). 바인딩은 루프백.
3. Task 2 step 5: 셀 132 = 22 행 x 6 신원. 각 셀의 기대 상태 (이행표 「기대 상태」 칸) 와 대조하고, 어긋나면 측정을 멈춘다 (0 으로 채우지 않는다). 완료 뒤 `scope-would-deny.json` (server_sha · harness_sha256 · tenants 포함). 주의: I4 corp 신원은 역할 가드로 403 이 정상이다 (이행표 I4 행 노트 · 측정 기대 상태에 반영 필요).
4. Task 2 step 6: 검사 `#5` (셀 집합 · 기대 칸 · 행마다 양성 · 하네스 sha256 · 테넌트 표지 · 자가 시험 세 변이). 그 뒤 검사 `#9` 는 커밋이 생긴 뒤에만 성립한다.
5. Task 3–11 (목록 · 집계 · 내보내기 · 단건 · 범위 선언 · 쓰기 트랜잭션 · 응답 불변 probe · 경합 itspec 설정 · 기준선 · 기록) 와 사이드카 검사 `#1`–`#17` 전부: 미착수. Task 2 산출 뒤에만 시작한다 (계획 Task 2 Action 의 순서 규칙).
6. 기준선 파일 (`jest-baseline.txt` · `tsc-baseline.txt` · `prior-specs-baseline.txt`) 은 1차 실행에서 만들었고 착수 sha 기준이라 그대로 쓴다.
7. Task 8 (응답 불변) 의 `isolated-probe.sh` · `probe.mjs` 는 M6 사본을 기반으로 한다. 이번 구간에서는 만들지 않았다.

### 검사 결과 (이번 구간)

- 검사 `#5`: 산출 `scope-would-deny.json` 부재 (붉음 상태 그대로). 실행하지 않았다 (측정 산출 전).
- 나머지 검사: 미실행. 사전 점검으로 시드 적재와 행 수만 확인했다 (게이트 항목 아님).

### 이번 구간의 결정

- 측정 산출을 만들기 전에 Task 3 이후 코드를 쓰지 않았다 (계획 Task 2 Action).
- 시드는 한 구간의 범위로 끊었다. 측정 하네스는 서버 빌드와 같은 구간에서 끝내야 하므로 다음 구간으로 넘긴다.

### 미검증

- 측정 수치 · would-deny 값 · 기대 상태와의 대조 결과는 아직 없다. 이 구간의 모든 값은 준비 상태다.
- 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인), R5 영업 전사 열람 업무 확인, 운영 스키마 대조: (미검증) 그대로다.

## 구현 4차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` 워킹트리 clean. 부모 `c05d421` 위 변경은 노트 · 백로그 · 메타 디렉터리 · receipt 뿐이다. 착수 시 `m6b-iso-` 컨테이너 0개. 사용자 지시 원문 `verify-user-directive.sh` 재확인 rc 0 (2026-10-19 · user/external).
- 이번 구간의 결과: 남은 Task 2 의 step 2–5 를 끝냈다 (측정 하네스 · 서버 빌드 · 격리 기동 · 셀 132 · 산출 `scope-would-deny.json`). 검사 #5 본 판정이 붉다 (I8 양성 대조, 아래). 계획 Task 2 Action 의 「검사 #5 통과 뒤에만 Task 3 이후」 규칙에 따라 Task 3–11 은 시작하지 않았다.
- 측정 산출: `.claude/meta/2026-10-21-admin-rbac-m6b/scope-would-deny.json` (server_sha `b2c476a8` · harness_sha256 `f9f0d3b7…9075d` · tenants 2/2/2 · 셀 132 · 상태 200 78 · 201 4 · 403 50 · would_deny 합 41 · 양성 실패 1 행 (I8)).
- 실행: `GK_MEASURE_MODE=measure M6B_SCHEMA_DIR=<스키마 덤프 디렉터리> bash .claude/meta/2026-10-21-admin-rbac-m6b/scope-measure.sh`. 격리 MySQL · Redis 는 `m6b-iso-` 접두로 띄우고 끝에 내렸다. 서버는 `env -i` · 루프백 바인딩 · `.env` 부재 확인 (M5 `_lib.sh` 재사용, 이름 접두만 바꿨다).

### 이번 구간에서 고친 것 (근거 줄 있음, 관찰값에 맞춘 것 아님)

1. 시드 보강 (`synthetic-tenants.sql`, DDL 없음):
   - 조직관리자 행 9311 (A1) · 9312 (A2) 추가 (`mem_type` MEM002 · `mem_is_org_manager` 1 · 미탈퇴). 조직관리자 신원은 로그인 때 DB 행을 다시 읽는다 (`admin-token-version.ts` org-manager). 행이 없을 때 org-A1 · org-A2 의 전 셀이 401 E0601 이었다 (probe-2).
   - `tn_agency.agc_analysis_view = 2` (기업 9001 · 9002). `CorpAnalysisViewGuard` 가 기본 0 이면 기업 신원의 분석 상세를 E0607 403 으로 막는다 (probe-2). 이 칸을 2 로 두는 것은 「기업이 분석관리 노출을 받은 운영 상태」 를 재는 가정이다.
   - 기업상품 `tn_product` 1 (PRD003) 과 구독 9401 · 9402 의 `sub_state` 를 SUB004 (실패) 로 바꿨다. 구독 실패 목록은 기업상품 · 실패 상태만 본다 (`subscribe-failed.repository.ts` whereClause). 행이 없어 목록이 0 행이었다 (probe-4).
2. 신원 (`identities-scope.json`): `tenant` 필드 (셀의 요청 대상 기업, A = 9001 · B = 9002) 와 조직관리자 `memId` (9311 · 9312) 를 더했다. 영업 신원 2 명은 A 를 고정한다 (교차 기업 목록을 잰다). 기업 B · 조직 A2 는 B 자원을 부른다.
3. 요청 규격 (`scope-measure.mjs` `specFor`): 신원의 대상 기업 자원을 부른다. A 기업의 대표 회원은 9302 (SYNORGA1C · A1 하위). 접두 충돌 형제 회원 9303 (SYNORGA10) 은 A1 범위 밖이라 assign-org 가 403 E0401 을 낸다 (`fc-agency.service.ts` assertOrgWritableByAdmin · assertMembersWritableByAdmin). 이 음성은 셀 대상에서 뺐고 Task 5 시험 자리로 남긴다. I8 요청에 필수 쿼리 `ansType=ANS001` 을 더했다 (`analyze.dto.ts` ansDetailType).
4. 이행표 기대 칸 정정 (`scope-transition-table.md`, 5 칸). 코드를 다시 읽어 고쳤다:
   - I2 getList · getDetail: org-A1 · org-A2 200 → 403. `organization-admin.controller.ts` assertCorpAgency 가 조직관리자 (ORG) 를 조직 라우트 전부에서 막는다.
   - I2 create: admin · sales · corp 200 → 201. 쓰기 POST 는 Nest 기본 201 이고 이 핸들러에 `@HttpCode` 가 없다 (관찰 201 과 일치).
   - I7 getIssueList · getIssueDetail: org-A1 · org-A2 200 → 403. `money-admin.controller.ts` 121 · 145 행 「조직관리자는 머니지급을 조회할 수 없습니다」.
   - 정정 전 표가 corp-B 에 200 을 준 I2 · I7 칸은 신원별 대상 기업 설정으로 맞았다 (표를 바꾸지 않았다).

### 편차 (이번 구간 추가)

- 편차 9: 엑셀 행 (I5 downloadWalletExcel · I6 downloadPaymentExcel) 은 같은 질의의 목록 행으로 잰다. 엑셀 열과 목록 행의 대조는 하지 않았다 (미검증).
- 편차 10: 쓰기 행의 판정은 쓰기 전 대상 원천 조인이다 (I2 create = 대상 기업, update · delete = 대상 조직, move = 이동 조직, I3 = 대상 회원). 응답 자원 id 는 쓰지 않는다 (편차 3 의 경로).
- 편차 11: 읽기 행은 스냅샷 상태에서 먼저 돈다. 쓰기 셀만 셀 앞마다 스냅샷을 다시 적재한다 (셀마다 복원하는 첫 시도는 재적재 비용이 커서 중단했다).
- 편차 12: 소유 행 (`owned_rows`) 은 판정 함수와 따로 계산한다 (영업 = 담당 sagId · 기업 = agcId · 조직 = agcId 와 조직 경로 하위). 양성 대조가 판정을 자기 자신과 비교하지 않게 하려는 것이다.
- 편차 13 (I8, 잠정 의미 실측): `judgeSingleScope('소유자 또는 관리자 열람')` 는 관리자 신원만 통과시키고, 기업 · 영업 · 조직관리자 신원은 memId 가 없어 전 셀 deny 다. 소유 행은 있는데 (owned 1) 허용 행이 0 이다. 편차 4 가 예고한 자리의 실측이다. 판정을 바꾸면 I8 의 기대 200 칸과 측정 판정 칸이 같이 바뀌므로 이번 구간에서 바꾸지 않았다 (사용자 확인 대상).

### 검사 결과 (이번 구간)

- 검사 #5: 본 판정 rc 1 — 「양성 대조 실패 I8 detailList sales-A」 (owned 1 · allow 0). 셀 집합 (132) · 상태 · 기대 칸 · 하네스 sha256 · 테넌트 2/2/2 · 범위 밖 합 41 (≥ 1) 은 통과했다. 음성 자가 시험 (zero · drop · dup) 은 게이트 본 판정 앞에서 도므로 이번 구간의 수동 실행에서는 돌리지 않았다. 게이트 전체 실행에서 다시 본다 (미검증).
- 검사 #1–#4 · #6 · #7: 직전 구간 결과 유지 (이번 구간 미재실행).
- 검사 #8–#17: 미실행 (#5 본 판정이 붉어 순서상 멈췄다).
- 검사 #9: 커밋이 없어 (미검증).

### 최종 결과 (이번 구간까지)

- 완료: Task 2 의 step 1 (시드) · step 2 (신원 토큰 · 하네스) · step 3 (산출 쓰기) · step 4 (서버 빌드 · 격리 기동 · 바인딩) · step 5 (셀 132 · 기대 칸 대조 · 산출). step 6 (검사 #5) 은 본 판정 붉음.
- 미완: 검사 #5 (I8) · Task 3–11 · 사이드카 #8–#17 · 검사 #9 (커밋 뒤) · 엑셀 열 대조 · 운영 동일 환경 1 회 재생 (사람 승인).
- 커밋 없음. 원격 조작 없음. 서브모듈 clean. `m6b-iso-` 컨테이너 0개.

### 남은 Task (다음 구간이 이어 한다)

1. 사용자 판단: I8 「소유자 또는 관리자 열람」 의 단건 의미 (편차 13). 둘 중 하나다. (가) 기업 · 영업 · 조직관리자는 소유 범위의 분석 상세를 본다 — judgeRowScope 와 같은 키로 판정한다 (표 기대 200 과 양성 규칙이 이 쪽이다). (나) 관리자 열람만 허용한다 — 기대 칸 (200) 과 I8 측정 판정 칸을 바꾼다. 이 판단 전에는 Task 4 (단건 연결) 를 시작하지 않는다.
2. 검사 #5 본 판정 통과 뒤 Task 3 (목록 · 집계 · 내보내기) 부터 Task 11 (기록) 까지. Task 3 전에 #5 통과가 전제다.
3. 하네스 (`scope-measure.mjs`) 를 고치면 `scope-would-deny.json` 을 다시 만든다 (`harness_sha256` 이 바뀐다).
4. 엑셀 열 대조 (편차 9) 와 음성 자가 시험 (zero · drop · dup) 의 게이트 실행 관측.
5. 사이드카 #1–#17 전부 (검사 #9 는 측정 산출 커밋 뒤).

### 미검증

- 측정 수치 (would-deny 41) 는 합성 테넌트 (기업 2 · 영업 2 · 조직 4 행) 에서 나왔다. 운영 형상과 대표성은 (미검증) 이다 (계획 Risks 1).
- 운영 동일 환경 1 회 재생, R5 영업 전사 열람 업무 확인, 운영 스키마 대조: (미검증) 그대로다 (복제본 생성은 사람 승인).

## 구현 5차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` 워킹트리 clean (서브모듈). 착수 시 `m6b-iso-` 컨테이너 0개 (4차 구간 정리 뒤). 사용자 지시 원문 `verify-user-directive.sh` rc 0 (2026-10-19 · user/external).
- 이번 구간의 결정 (편차 14, 사용자 확인 대상 · 최종 fable·codex 리뷰가 다시 본다): I8 `detailList` 단건 판정을 `judgeSingleScope` 에서 `judgeRowScope` (회원 소유 키 = 회원의 agc · sag · orgPath, 관리자 신원만 allow) 로 바꿨다.
  - 근거 1 (실측, 편차 13): 「소유자 또는 관리자 열람」 을 memId 한 칸으로 재면 기업 · 영업 · 조직관리자 신원은 memId 가 없어 여섯 셀이 전부 deny 다. 기대 칸 (200) 과 현행 가드 (`CorpAnalysisViewGuard` · 기업 소유 검사) 와도 어긋난다. 양성 규칙 (검사 #5) 도 이 판정에서는 어떤 기대 200 셀도 allow 를 얻지 못한다.
  - 근거 2 (이행표 정본): I8 행의 「범위 키」 열이 `agcId · sagId` 다. memId 는 이 행의 범위 키가 아니다. 소유 키 판정은 `judgeRowScope` 가 쓰는 키 목록과 같다.
  - 근거 3 (의미 보존): 기업 신원은 자기 기업 행만, 영업 신원은 담당 sagId 행만 본다. 현행 기업 가드가 허용하는 범위와 같다. 「기업 · 영업 · 조직관리자 전부 deny」 (가 판정 (나)) 는 현행 기업 열람을 끊는 업무 변경이라 고르지 않았다.
  - 바뀐 것: `scope-measure.mjs` I8 분기 (judge 이름 · 판정 함수) 와 이행표 I8 행의 「측정 판정」 · 「강제 경로」 칸. 계획서 (`admin-rbac-m6b.plan.md`) 는 고치지 않는다 (plan_hash · implement receipt 가 묶여 있다). 계획의 정책 4 · 9 · Task 4 「judgeSingleScope 로 부른다」 는 이 편차로 대체되며, Task 4 구현 때 같은 자리를 judgeRowScope 로 쓴다.
- 측정 재생성: 격리 `m6b-iso-` 스택 (MySQL 8.0.42 · Redis 7) 으로 `scope-measure.sh` 를 1회 돌렸다. 결과 `cells=132 positive_fail=0`, EXIT 0. 산출 `scope-would-deny.json` 의 `server_sha` = `b2c476a8`, `harness_sha256` = `870b7ace…2986149`, `tenants` = agency 2 · sales 2 · org 2, would_deny 합 36 (이전 41: I8 기업 · 영업 · 조직 5 셀이 allow 로 바뀜). 컨테이너는 스크립트가 끝에 내렸다.
- 검사 결과 (이번 구간, `bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh` 를 `M6B_SCHEMA_DIR` 로 실행):
  - 검사 #1–#4: 통과 (직전 구간 결과와 같음).
  - 검사 #5: 통과 (본 판정 「검사 #5 ok 셀 132」 · 자가 시험 zero · drop · dup 셋 다 rc 1 · 양성 행마다 통과).
  - 검사 #6: 통과 (AO 행 수와 코드 사용처 수 일치).
  - 검사 #7: 붉음 (기대대로). 강제 경로 식별자 (I1 `listScopeCondition` · I8 `judgeRowScope` 등) 가 핸들러 파일에 아직 없다. Task 3 · 4 가 붙이면 통과한다. 이 붉음은 구현 미완의 결과이지 검사 오류가 아니다.
  - 검사 #8–#17: 미실행 (#7 에서 멈췄다).
- 이번 구간에서 하지 않은 것: Task 3 (목록 · 집계 · 내보내기) · Task 4 (단건 연결) · Task 5 · Task 6 · Task 7 · Task 8 (응답 불변 probe 사본 · 행 파일 · 요약 — 메타 디렉터리에 아직 없다) · Task 11. 코드 변경 없음 (서버 · 관리자 · 사이트 서브모듈 clean).
- 주의: 이번 구간 중 `.claude/meta/` 에 `scope-measure.mjs` 사본이 잘못 생겼다가 바로 지웠다 (git 추적 밖, 남은 것 없음).

### 검사 결과 요약 (이번 구간 끝)

- 통과: #1–#6. 붉음: #7 (구현 미완). 미실행: #8–#17.

### 남은 Task (다음 구간이 이어 한다)

1. Task 3 (목록 · 집계 · 내보내기 강제 경로, I1 · I4–I8): 절 생성기 `listScopeCondition` 을 `agency-admin.controller.ts` (I1), `subscribe-failed-admin.controller.ts` (I4), `money-admin.controller.ts` · `money.service.ts` · `money.repository.ts` (I5–I7), `analyze-admin.controller.ts` · `analyze.service.ts` · `analyze.repository.ts` (I8 목록 · 집계 · 내보내기) 에 붙인다. OFF 는 현행 인자 (응답 불변). 시험 「범위 인자 동일」 「타 기업 누출 0」.
2. Task 4 (단건 연결, 편차 14 반영): I2 `getDetail` · I5 `getMemberWalletHistory` · I6 `getPaymentDetail` · `getPaymentUsage` · I7 `getIssueDetail` · I8 `detailList` 에 `judgeRowScope` (키 목록 · 신원 컨텍스트) 를 붙인다. 이것으로 검사 #7 의 「강제 경로」 식별자 배선 표지가 통과한다.
3. Task 5 (범위 선언 강제 · 계약 단언), Task 6 (쓰기 트랜잭션 · 경합 itspec 2종), Task 7 (상수 OFF · ON 시험 — 상수는 이미 OFF 인지 확인), Task 8 (응답 불변 probe 사본 · 착수 빌드 대 최종 빌드 · 쓰기 행), Task 9 (게이트 정정 — 사이드카 본문 반영 완료로 보이나 #8 itspec 실행 확인), Task 11 (백로그 RESOLVED-IN append · notes 마감 · 텍스트 위생).
4. 사이드카 #7 → #17 을 순서대로 통과시킨다. #9 는 측정 산출 커밋 (`scope-would-deny.json` 이 든 커밋) 이 첫 강제 커밋의 조상이어야 하므로, 커밋 단계 (사용자 지시 전까지 보류) 에서 본다.
5. 사용자 확인 대상: 편차 14 (I8 단건 판정 의미), 편차 9 (엑셀 열 대조 미검증), 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인).

### 미검증

- 측정 수치 (would-deny 36) 는 합성 테넌트 (기업 2 · 영업 2 · 조직 2) 에서 나왔다. 운영 형상과 대표성은 (미검증) 이다.
- 운영 동일 환경 1회 재생, R5 영업 전사 열람 업무 확인, 운영 스키마 대조: (미검증) 그대로다 (복제본 생성은 사람 승인).

## 구현 6차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` 기준 워킹트리였고 `m6b-iso-` 컨테이너 0개 (기동 중인 격리 스택 없음). 측정 산출 `scope-would-deny.json` 은 5차 구간 것을 그대로 쓴다 (cells=132, would_deny 합 36).
- 이번 구간에서 한 것 (Task 3 · 목록 일부, 서버 워킹트리 수정 · 커밋 없음):
  - I5 `getWalletList` · `downloadWalletExcel`: 컨트롤러 `getSalesScope(req)` 가 영업 신원에 담당 영업 절 (`listScopeCondition({kind:'sales', sagId}, ['sagId'])`) 을 만든다. 상수 OFF 면 null (현행). 저장소 `getWalletListWithSearch` · `getWalletCountWithSearch` 가 선택 인자 `scope` 를 받아 `toDrizzleCondition(scope, { sagId: tnAgency.sagId })` 를 같은 조건 목록에 더한다. 목록 · count · 엑셀이 같은 인자를 받는다.
  - I6 `getPaymentList` · `downloadPaymentExcel`: 같은 방식 (`getPaymentListWithSearch` · `getPaymentCountWithSearch`, 조인 `tn_member.agc_id → tn_agency`).
  - 시험 추가 (`scope-handlers.spec.ts`, 「머니 목록 (I5 · I6 목록 · 엑셀)」 블록): ON 절 전달 · 「범위 인자 동일」 (머니 내역 · 결제 목록의 목록과 count 가 같은 객체를 받음) · OFF 응답 불변 (null) · 담당 키 없음 403 (서비스 호출 0회). 12건 녹색.
- 편차 15 (이번 구간): I5 · I6 목록의 영업 범위 키를 표의 「agcId」 칸 대신 `sagId` (회원 agc_id → tn_agency.sag_id 경유) 로 걸었다. 영업 신원은 agcId 를 갖지 않아 `judgeRowScope(agcId)` 로는 던진다. 측정 산출의 영업 소유 정의 (sales-A owned 1) 와 같은 방향이지만 소유 정의를 셀 단위로 대조하지는 않았다 (미검증). 이행표 I5 · I6 「측정 판정」 칸의 키 표기 정정은 아직 안 했다 (다음 구간).
- 검사 결과 (이번 구간):
  - 서버 `tsc -p tsconfig.json`: 오류 줄이 기준선 23줄과 같다 (`diff` 빈 결과). 처음 낸 시험 타입 오류 4건은 mock 시그니처 (`..._args: any[]`) 로 고쳤다.
  - jest (`auth/access-control` · `money` · `agency` · `subscribe`, itspec 제외): 38 suite · 392 test 녹색.
  - 게이트 사이드카 전체 (검사 #1–#17) 는 이번 구간에 돌리지 않았다. #7 은 강제 경로 식별자 배선 (목록 쪽은 생겼지만 I7 · I8 아직 없음) 때문에 여전히 붉을 것이고, #8 은 「타 기업 누출 0」 시험이 아직 없어 붉다.
- 서식: 변경한 파일 (`money-admin.controller.ts` · `money.service.ts` · `money.repository.ts`) 은 HEAD 가 prettier 와 맞지 않는다 (`git show HEAD:<f> | prettier` 와 cmp 불일치). 그래서 `prettier --write` 는 돌리지 않았다 (전체 파일 재정렬을 막기 위해). 긴 줄 몇 개는 정리되지 않은 채 남았다 (미커밋).

### 남은 Task (다음 구간이 이어 한다)

1. Task 3 나머지: I7 `getIssueList` (서비스 `buildIssueWhere` 가 `tn_money_issue.agc_id` 를 건다 — 영업 신원은 `agc_id IN (SELECT agc_id FROM tn_agency WHERE sag_id = ?)` 같은 부분질의 또는 조인이 필요하다. 측정 산출의 issue sales-A 소유 정의와 대조할 것), I5 `getCorpHistory` (목록, `getCorpHistoryList` · `getCorpHistoryCount`), I8 `aggregate` · `download` (`analyze.service.ts`, 목록 · 집계 · 내보내기는 `listScopeCondition`). 목록 · count · 집계 · 내보내기 「범위 인자 동일」 시험을 I7 · I8 까지 넓힌다.
2. 「타 기업 누출 0」 시험 추가 (저장소 조건 생성을 실제 질의 문자열이나 열 객체로 확인). 지금은 이름을 붙이지 않았다.
3. Task 4 단건 (편차 14 를 따른다): I2 `getDetail` · I5 `getMemberWalletHistory` · I6 `getPaymentDetail` · `getPaymentUsage` · I7 `getIssueDetail` 은 `judgeRowScope`, I8 `detailList` 는 `judgeRowScope` (회원 소유 키, 관리자 allow). `getMemberWalletHistory` 는 표에 「agcId」 로 적혀 있지만 `memId` 경로라 키 값을 어디서 얻을지 먼저 정한다.
4. 이행표 I5 · I6 「측정 판정」 · 「강제 경로」 칸의 영업 키 표기를 편차 15 에 맞춘다 (계획서는 고치지 않는다).
5. Task 5 · 6 · 7 · 8 · 9 · 10 · 11 과 사이드카 #7 → #17 (이번 구간에 실행하지 않았다). 특히 #9 는 측정 산출 커밋 (`scope-would-deny.json` 이 든 커밋) 이 첫 강제 커밋의 조상이어야 본다 (커밋 단계는 사용자 지시 전까지 보류).
6. 사용자 확인 대상은 그대로: 편차 14 (I8 단건 의미), 편차 15 (영업 키 sagId), 편차 9 (엑셀 열 대조, 미검증), 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인).

### 미검증

- 이번 구간의 목록 강제는 `jest.mock` 시험과 저장소 인자 시험으로만 본다. 실제 질의 (조인 결과 행 수, 타 기업 행 0) 는 격리 MySQL 에서 아직 돌리지 않았다.
- 측정 산출의 영업 소유 정의와 이번 구간 조건 (`tn_agency.sag_id`) 의 셀 단위 대조는 하지 않았다.

## 구현 7차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` · 관리자 `1301b664` · 사이트 `fc8872a1` (관리자 · 사이트 워킹트리 clean). 서버 변경은 머니 세 파일과 시험 한 파일 (4 파일). 착수 시 `m6b-iso-` 컨테이너 0개, 이번 구간에서도 격리 기동 없음 (게이트 `#5` 는 산출 파일 판정이다). 사용자 지시 원문 `verify-user-directive.sh` rc 0 (2026-10-19 · user/external).
- 이번 구간에서 한 것 (Task 3 목록 · 집계 · 내보내기, 서버 워킹트리 수정 · 커밋 없음):
  - I7 `getIssueList` (목록 · count): 컨트롤러가 `getSalesScope(req)` 를 서비스에 넘기고, 저장소 `getIssueList` · `getIssueCount` 가 선택 인자 `scope` 를 `toDrizzleCondition(scope, { sagId: tnAgency.sagId })` 로 같은 조건 자리에서 더한다. 적립 목록은 이미 `tn_agency` 를 조인하므로 조인을 더하지 않았다. OFF 는 `scope` null, 조건을 더하지 않는다 (현행 쿼리 형상).
  - I5 `getCorpHistory` (목록 · count): 서비스가 먼저 `isAgencyInScope(agcId, scope)` 로 담당 기업인지 본다. 범위 밖이면 403 이고 이력 · 요약 저장소를 부르지 않는다 (요약은 범위 절을 받지 않으므로 먼저 막는다). 담당이면 목록 · count 가 같은 범위 절을 받는다 (`tn_agency` 를 기업 PK 로 1:1 조인). OFF 는 검사 없이 null.
  - 시험 추가 (`scope-handlers.spec.ts`, 「핸들러 범위 절 · 머니 적립 · 기업 이력 (I7 · I5 목록)」 블록 7건): 「범위 인자 동일」 (적립 목록 · 이력 목록 · count), 「타 기업 누출 0」 (범위 밖 기업 403 · 이력 · 요약 미호출, 질의 형상 — 열 객체를 시험이 직접 만든 `mysqlTable('tn_agency')` 로 `sag_id` 와 인자 `[4]` · 타 영업 `5` 없음), ON 컨트롤러 전달 · OFF 응답 불변 (null).
  - I1 · I4 는 M6 에서 이미 `listScopeCondition` 이 연결돼 있어 손대지 않았다.
- 편차 16 (I8 `aggregate` · `download` 는 강제하지 않는다): Task 3 본문은 I8 의 목록 · 집계 · 내보내기를 강제하라고 적었지만, 이행표 I 행에는 I8 이 `detailList` 한 행뿐이고 (측정 셀 132 밖), 계획 「범위 밖」 절은 분석 aggregate · download 를 「M9 판단 표 입력」 으로 넘긴다. 측정하지 않은 경로를 바꾸면 would-deny 산출과 응답 불변 증명이 그 경로를 보지 못한다. 그래서 이 구간은 두 경로에 손대지 않았다. 두 문서가 충돌하므로 최종 리뷰 (fable · codex) 가 다시 본다.
- 편차 17 (I2 `getList` 의 강제 경로 칸 정정 예정): 이행표는 `listScopeCondition` 이라고 적었으나, 실제 강제는 `assertSalesAgency` 가 기업 단위로 `judgeRowScope` (기업의 sagId) 를 부르는 것이다. 목록은 한 기업의 조직 목록이라 같은 판정이다. 이 구간에서 표는 고치지 않았다 (다음 구간에서 「강제 경로」 칸을 `judgeRowScope` 로 정정하고 편차로 적는다).
- 편차 18 (I6 `getPaymentDetail` · `getPaymentUsage` 강제 경로 칸 정정 예정): 단건 행인데 표 「강제 경로」 칸이 `listScopeCondition` 이다. 계획 Task 4 는 단건을 `judgeRowScope` 로 쓰라고 한다. 다음 구간 정정 대상이다.
- 검사 결과 (이번 구간, `M6B_SCHEMA_DIR=<스키마 덤프 디렉터리> bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`):
  - 통과: 검사 `#1`–`#6` (`#5` 는 자가 시험 세 변이와 본 판정 「검사 #5 ok 셀 132」).
  - 붉음: 검사 `#7` 의 배선 표지 (줄 178 의 행별 grep). 남은 행: I2 `getList` (편차 17 표 정정 대기), I3 `assignOrg` · `setOrgManager` (Task 6 쓰기 판정 미구현), I5 `getMemberWalletHistory` · I7 `getIssueDetail` · I8 `detailList` (Task 4 단건 미구현). I6 `getPaymentDetail` · `getPaymentUsage` 는 표 정정 전까지 통과만 한다 (편차 18).
  - 미실행: `#8`–`#17` (`#7` 에서 멈췄다).
- 시험과 빌드:
  - jest `auth/access-control` · `money` · `agency` · `subscribe` (itspec 제외): 38 suite · 399 test 녹색 (앞 구간 392 + 이번 7).
  - 서버 `tsc -p tsconfig.json --noEmit`: 오류 23줄이 기준선과 같다 (위치 `(줄,열)` 을 뺀 정규화 비교, `SAME_TSC`).
  - 격리 MySQL · Redis 는 이번 구간에서 띄우지 않았다 (목록 · 단건의 질의 실측은 아직 없다).
- 서식: 변경한 머니 세 파일 · 시험 파일은 HEAD 가 prettier 와 맞지 않아 `prettier --write` 를 돌리지 않았다 (앞 구간과 같은 이유, 전체 파일 재정렬 방지). 긴 줄 몇 개가 남았다 (미커밋).

### 최종 결과 (이번 구간까지)

- 완료: Task 3 의 목록 부분 (I1 · I4 는 앞 계획부터, I5 `getWalletList` · `downloadWalletExcel` · `getCorpHistory` 목록 · count, I6 `getPaymentList` · `downloadPaymentExcel`, I7 `getIssueList` 목록 · count). 시험 「범위 인자 동일」 · 「타 기업 누출 0」 이 있다.
- 미완: Task 3 의 I8 집계 · 내보내기 (편차 16, 범위 밖으로 기록). Task 4 전부 (단건 3의미 연결: I5 `getMemberWalletHistory`, I6 `getPaymentDetail` · `getPaymentUsage` 칸 정정, I7 `getIssueDetail`, I8 `detailList` (편차 14 의 `judgeRowScope` · 회원 소유 키)). Task 5 · 6 · 7 · 8 · 9 · 10 · 11 과 사이드카 `#7`–`#17` 전부.
- 커밋 없음. 원격 조작 없음. 서브모듈 포인터 변경 없음 (서버 `b2c476a8`). `m6b-iso-` 컨테이너 0개.

### 남은 Task (다음 구간이 이어 한다)

1. Task 4 단건 (편차 14 · 18 을 따른다): I7 `getIssueDetail` 은 corp 판정을 `judgeRowScope({kind:'corp-super', agcId}, {declared:true, agcId: result.issue.agcId}, ['agcId'])` 로 바꾸고 (기존 `!==` 비교와 같은 결과), 영업 판정은 소유 기업의 sagId 를 서비스에서 받아 같은 함수로 한다. I5 `getMemberWalletHistory` 는 `memId` 에서 agcId 를 얻는 경로를 먼저 정한다. I8 `detailList` 는 `judgeRowScope` (회원 소유 키 agcId · sagId · orgPath, 관리자만 allow). 세 자리의 식별자 `judgeRowScope` 가 컨트롤러 파일에 있어야 검사 `#7` 이 통과한다.
2. 이행표 정정 (측정 값 불변, 편차 17 · 18): I2 `getList` 과 I6 `getPaymentDetail` · `getPaymentUsage` 의 「강제 경로」 칸을 `judgeRowScope` 로 고치고 정정 근거를 적는다.
3. Task 6 쓰기 트랜잭션: `assignOrg` · `setOrgManager` (`fc-agency.service.ts`) 의 ON 경로 잠금 순서 (회원 → 담당 영업 → 목적지 조직) 와 조직 쓰기 (`organization.service.ts` · `organization.repository.ts`) 의 트랜잭션, 경합 itspec 2종 (「조직 이동 경합」 「소속 경합 잠금 순서」). 격리 MySQL 두 연결이 필요하다 (`m6b-iso-` 접두).
4. Task 5 계약 단언 (「범위 선언 누락」 · fail 모드 던짐) 과 Task 7 (ON 단건 음성 · 정상 경로 시험, 상수 `false` 확인).
5. Task 8 응답 불변: `isolated-probe.sh` · `probe.mjs` (M6 사본), 행 파일 3종 (`rows-before-m6b.txt` 착수 빌드 `b2c476a8`, 재실행 대조군, `rows-after-m6b.txt`), `probe-summary-m6b.txt`. 쓰기 행 재생 포함. OFF 에서 머니 · 조직 · 분석 응답 행이 착수와 같아야 한다.
6. Task 9 · 10 · 11: 게이트 사이드카 `#8` 의 itspec 설정 실행 확인, 기준선 헤더 (`#2`) · 권한 spec 집합 (`#11`), 백로그 RESOLVED-IN append (`#13`), 텍스트 위생 (`#14`), plan-kit lint (`#15`), 장기 관측 문구 (`#16`).
7. 검사 `#7`–`#17` 을 순서대로 통과시킨다. `#9` 는 측정 산출 커밋이 첫 강제 커밋의 조상이어야 하므로 커밋 단계 (사용자 지시 전까지 보류) 에서 본다.
8. 사용자 · 리뷰 확인 대상: 편차 14 (I8 단건 의미), 편차 15 (영업 키 sagId 경유), 편차 9 (엑셀 열 대조 미검증), 편차 16 (I8 집계 · 내보내기 범위 밖), 편차 17 (I2 목록 강제 경로 표현), 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인).

### 미검증

- 이번 구간의 목록 강제는 jest 모킹과 조건 형상 시험으로만 본다. 실제 질의 (조인 결과 행 수, 타 기업 행 0, count 와 목록 일치) 는 격리 MySQL 에서 아직 돌리지 않았다.
- 응답 불변 (OFF) 은 아직 행 단위로 증명하지 않았다. 머니 목록에 `tn_agency` 조인을 더한 기업 이력 질의는 PK 1:1 이라 행이 같아야 하지만 Task 8 전에는 (미검증) 이다.
- 운영 동일 환경 1회 재생, R5 영업 전사 열람 업무 확인, 운영 스키마 대조: (미검증) 그대로다 (복제본 생성은 사람 승인).

## 구현 8차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` (포인터 그대로) · 서버 워킹트리 변경 6 파일 (머니 컨트롤러 · 머니 저장소 · 머니 서비스 · 분석 컨트롤러 · 분석 저장소 · 분석 서비스 · 접근 계약 단언 · 접근 계약 spec · 신규 접근 계약 골든 · 범위 핸들러 spec 포함). 격리 기동 없음 (`m6b-iso-` 컨테이너 0개 · 착수 시점에도 0개). 사용자 지시 원문 확인은 앞 구간 기록 (rc 0) 을 따르고 이번 구간에서 다시 세지 않았다.
- 이번 구간에서 한 것:
  - Task 4 단건 (편차 14 · 15 · 18 을 따른다). 모든 판정은 `SCOPE_ENFORCE_SALES_CORP` 가 켜지고 신원이 영업일 때만 부른다 (OFF 는 판정 · 추가 조회 없음 — 서비스 조회는 `() => Promise` 로 늦춰 OFF 경로에서 아무 질의도 나가지 않는다).
    - I7 `getIssueDetail`: 소유 기업 `result.issue.agcId` → 담당 영업 `sagId` (`getSagIdOfAgc`) → `judgeRowScope({kind:'sales', sagId}, {declared:true, sagId}, ['sagId'])`. corp 의 기존 `!==` 검사는 그대로 둔다 (ON · OFF 와 무관한 현행 경로, 타입 비교가 바뀌지 않도록).
    - I5 `getMemberWalletHistory`: 회원의 `agcId` (`getMemberAgcId`) → sagId → 같은 판정. 컨트롤러 시그니처에 `@Request() req` 를 앞에 더했다 (호출처는 Nest 라우터뿐, 시험 0건).
    - I6 `getPaymentDetail` · `getPaymentUsage`: 결제 회원의 `agcId` (저장소 `getPaymentOwnerAgcId`, tn_money_payment ⋈ tn_member) → sagId → 같은 판정.
    - I8 `detailList` (분석 상세, `analyze-admin.controller.ts`): 영업만 판정한다. 분석 기본 `asbId` → `oddId` → 주문 상세 ⋈ 결제 ⋈ 구독 ⋈ 회원 ⋈ 기업 → `sag_id` (저장소 `getSagIdOfOdd`, 서비스 `getSagIdOfAsb`). 기업 · 조직관리자 쪽은 현행 `guardCorp` 를 그대로 둔다 (편차 14 의 회원 소유 키 agcId · orgPath 판정으로 바꾸면 OFF 경로가 바뀌므로 이 구간에서 바꾸지 않았다. 사용자 확인 대상 편차 14 와 함께 다음 구간에서 정한다).
  - 저장소 · 서비스 헬퍼: `money.repository.ts` 에 `getSagIdByAgcId` · `getPaymentOwnerAgcId`, `money.service.ts` 에 `getMemberAgcId` · `getPaymentAgcId` · `getSagIdOfAgc`, `analyze.repository.ts` 에 `getSagIdOfOdd`, `analyze.service.ts` 에 `getSagIdOfAsb`.
  - 시험 (`scope-handlers.spec.ts` 두 블록 추가): 「담당 기업의 다른 회원 허용」 · 「타 기업 거부」 (회원 · 적립 · 결제 · 분석 상세) · 「미선언 403」 · 「범위 밖 조직 거부」 (I2 상세) · OFF 응답 불변 (추가 조회 0) — 검사 `#8` 의 필수 시험 이름 네 개가 생겼다. 28 건 녹색 (분석 상세 2건 포함).
  - 이행표 정정 (측정 값 불변): I2 `getList` · I6 `getPaymentDetail` · `getPaymentUsage` · I7 · I5 등 「강제 경로」 칸을 `judgeRowScope(` 형태로 맞췄다 (편차 17 · 18). 칸 텍스트에 `judgeRowScope (` (공백) 이 있으면 검사 `#7` 의 파서가 `judgeRowScope ` 를 찾아 파일에 없다고 판정하는 함정이 있어 공백을 뺐다 (편차 19).
  - Task 5 (범위 선언 누락): `access-contract.scope-golden.ts` (생성물, 레지스트리 자동 칸 가운데 sales · corp 역할 103 행의 목록. 재현은 레지스트리 행 줄에서 `scopeSource: 'auto'` 이고 `roles` 에 sales 또는 corp 가 있는 행을 뽑는 node 한 줄이다). `access-contract.assert.ts` 에 `checkScopeDeclarations` · `RULE_SCOPE_UNDECLARED` 를 더하고 `judgeRuntimeRoutes` 가 합쳐 낸다. 모드 상수 `warn` 그대로. 시험 (spec 블록 「접근 계약 범위 선언 (M6b Task 5)」): 「현재 레지스트리는 범위 선언 누락 위반이 0건」 · 「범위 선언 누락 1건」 · 「fail 모드는 던진다」. 접근 계약 spec 32 건 녹색 (실제 AppModule 대조 포함).
- 검사 결과 (이번 구간):
  - `M6B_SCHEMA_DIR=<스키마 덤프 디렉터리> bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`: `#5` 셀 132 ok. `#6` 통과. `#7` 의 J7 jest (scope-monitor · scope-condition) 통과, 배선 표지 줄 178 만 붉다. 행별 시뮬레이션 결과 남은 행은 `assignOrg` · `setOrgManager` 두 개뿐이다 (`judgeRowScope` 식별자 없음 — Task 6 쓰기 판정). 나머지 I 행은 모두 통과한다 (I2 목록 · I7 · I5 단건 · I6 단건 · I8 포함).
  - `#8`–`#17` 미실행 (`#7` 에서 멈춘다). 다만 `#17` 에 쓸 시험 이름 「범위 선언 누락 1건」 · 「fail 모드는 던진다」 는 이미 녹색이다 (access-contract.spec.ts).
  - jest (`--testPathIgnorePatterns itspec`, auth/access-control · auth/access-contract · money · agency · subscribe · analyze): 49 suite 중 48 녹색 · 514 시험 중 513 녹색. 붉은 1건은 `analyze/analyze-insight-route.spec.ts` 의 ENOENT (`REPO_ROOT/.claude/plans/ai-insight-report-v2-m1.report-fixture.json` 이 워크트리에 없다). 이번 변경과 무관한 워크트리 픽스처 부재다 (메모 server-jest-baseline-red-needs-redis · 워크트리 픽스처 부재 항목과 같은 종류). 기준선 `jest-baseline.txt` 에도 이 suite 가 있다.
  - `tsc -p tsconfig.json --noEmit`: 오류 24 줄 중 기준선 23 줄과 정규화 비교가 같다 (SAME_TSC). 첫 시도에서 `checkScopeDeclarations` 입력 타입이 `Pick<method,path>` 와 맞지 않아 새 오류 1 줄이 생겼고, 입력 타입을 `Partial<roles|scopeSource>` 로 고쳐 제거했다 (범위 필드가 없는 입력 행은 판정하지 않는다는 주석을 달았다).
- 편차 (이번 구간 추가):
  - 편차 19 (파서 함정): 이행표 「강제 경로」 칸의 `judgeRowScope (단건)` 형태는 검사 `#7` 파서가 `judgeRowScope ` (끝 공백) 으로 잘라 파일에서 찾는다. 칸을 `judgeRowScope(` 로 통일했다. 측정 칸 (col 11) 과 측정 함수 이름은 바꾸지 않았다.
  - 편차 20 (I8 영업만 판정): 분석 상세는 영업 신원에만 `judgeRowScope` (키 sagId) 를 건다. 기업 · 조직관리자는 현행 `guardCorp` 가 맡는다. 이행표 I8 행의 「회원 소유 키 agcId · sagId · orgPath」 중 agcId · orgPath 판정은 이 구간에서 구현하지 않았다 (편차 14 의 사용자 확인 대상과 같이 남긴다).
  - 편차 21 (I5 · I6 키 표기, 편차 15 의 측정 칸 문구): 「측정 판정」 칸 (col 11) 은 아직 `judgeRowScope(agcId 키)` 로 남았다. 측정 하네스는 함수 이름만 쓰므로 값은 바뀌지 않지만, 문구 정정은 다음 구간에서 한다.
  - 편차 22 (Task 5 골든 생성): 골든 목록은 M6b 시작 시점 레지스트리의 자동 칸 (sales · corp 역할) 103 행이다. 1489 잔여의 목록 축소는 저자 선언 (이행표 I 행) 이 있을 때만 하므로, 이번 구간에서는 목록을 줄이지 않았다.
- 미검증: 목록 · 단건의 격리 MySQL 질의 실측 (행 수 · 타 기업 행 0), 응답 불변 행 단위 증명 (Task 8), 쓰기 행 재생, 엑셀 열 대조 (편차 9), I8 기업 · 조직관리자 판정 의미 (편차 14 · 20).

### 최종 결과 (이번 구간까지)

- 완료: Task 4 전부 (I2 목록 · 단건 정정, I5 · I6 · I7 · I8 단건 영업 판정, 편차 19 · 20), Task 5 전부 (범위 선언 누락 단언 · 골든 103 행 · 시험 3 건). 시험 이름 「담당 기업의 다른 회원 허용」 · 「타 기업 거부」 · 「미선언 403」 · 「범위 밖 조직 거부」 · 「범위 선언 누락 1건」 · 「fail 모드는 던진다」 가 생겼다.
- 미완: Task 6 (쓰기 트랜잭션 · `assignOrg` · `setOrgManager` 판정 · 경합 itspec 두 종 — 격리 MySQL 두 연결 필요, 검사 `#7` 의 남은 붉은 행), Task 7 (ON 음성 · 정상 시험의 잔여 — 단건 음성은 이번 구간에 들어왔다, 상수 `false` 확인은 검사 `#9` 몫), Task 8 (응답 불변 3층 대조 · probe 행 파일 · 쓰기 행 재생 — 격리 스택 필요), Task 9 · 10 · 11 과 사이드카 `#7` 의 마지막 행 · `#8`–`#17`.
- 커밋 없음. 원격 조작 없음. 서브모듈 포인터 변경 없음 (서버 `b2c476a8`). `m6b-iso-` 컨테이너 0개 (이번 구간에서 기동하지 않았다).

### 남은 Task (다음 구간이 이어 한다)

1. Task 6 쓰기 트랜잭션: `fc-agency.service.ts` 의 `assignOrg` · `setOrgManager` ON 경로에 담당 영업 행 잠금 · 목적지 조직 잠금을 판정 앞에 둔다 (잠금 순서 회원 → 담당 영업 → 목적지 조직). `agency-fc.repository.ts` 에 `lockAgencyForOrgAssign` · `lockOrgForAssign` 을 더하고 판정 조회가 `tx` 를 받게 한다. 그 뒤 `agency-fc-admin.controller.ts` 의 두 핸들러 식별자 `judgeRowScope` 가 보여야 검사 `#7` 이 통과한다. 조직 쓰기 (`organization.service.ts` · `organization.repository.ts`) 의 `tx` 인자와 `FOR UPDATE` 재읽기, 경합 itspec 2 종 (`agency-fc.race.itspec.ts` · `organization.race.itspec.ts`) 은 격리 MySQL 두 연결로 돌린다 (`m6b-iso-` 접두).
2. Task 7: ON 음성 · 정상 경로 시험의 잔여 (쓰기 행 · 조직 행), 상수 `false` 확인은 검사 `#9`.
3. Task 8 응답 불변: `isolated-probe.sh` · `probe.mjs` (M6 사본), 행 파일 3 종 (`rows-before-m6b.txt` 착수 빌드 `b2c476a8`, 재실행 대조군, `rows-after-m6b.txt`), `probe-summary-m6b.txt`. 쓰기 행 재생 포함. OFF 에서 머니 · 조직 · 분석 응답 행이 착수와 같아야 한다.
4. Task 9 · 10 · 11: 사이드카 `#8` 의 itspec 설정 실행 확인 · 기준선 헤더 (`#2`) · 권한 spec 집합 (`#11`) · 백로그 RESOLVED-IN append (`#13`) · 텍스트 위생 (`#14`) · plan-kit lint (`#15`) · 장기 관측 문구 (`#16`).
5. 편차 21 (I5 · I6 측정 칸 키 표기) 정정, 편차 20 의 I8 기업 · 조직관리자 판정 의미는 사용자 확인 대상으로 남긴다.
6. 검사 `#7`–`#17` 을 순서대로 통과시킨다. `#9` 는 산출 커밋이 첫 강제 커밋의 조상인지 커밋 단계에서 본다 (사용자 지시 전까지 보류).
7. 사용자 · 리뷰 확인 대상: 편차 14 · 20 (I8 단건 의미), 편차 15 (영업 키 sagId 경유), 편차 9 (엑셀 열 대조 미검증), 편차 16 (I8 집계 · 내보내기 범위 밖), 편차 17 · 19 (강제 경로 표현), 편차 22 (골든 103 행 · 1489 이연), 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인).

### 미검증

- 목록 · 단건 질의의 격리 MySQL 실측 (조인 결과 행 수 · 타 기업 행 0 · count 일치) 은 아직 없다. 지금까지 판정은 jest 모킹과 조건 형상 시험으로만 본다.
- 응답 불변 (OFF) 은 행 단위로 증명하지 않았다 (Task 8 전). 단건 · 목록 경로의 OFF 분기는 시험으로 「추가 조회 0 · 저장소 인자 null」 만 본다.
- 운영 동일 환경 1회 재생, R5 영업 전사 열람 업무 확인, 운영 스키마 대조: (미검증) 그대로다 (복제본 생성은 사람 승인).

## 구현 9차 실행 기록 (2026-10-21 · 커밋 없음 · 미완, halted)

- 착수 확인: 서버 `b2c476a8` (포인터 그대로) · 서버 워킹트리 변경은 8차 구간 6 파일 + 이번 구간 4 파일. 착수 시 `m6b-iso-` 컨테이너 0개, 백그라운드 실행 없음. 사용자 지시 원문은 `verify-user-directive.sh` 로 다시 확인했다 (rc 0, type=user · userType=external, 2026-10-19T02:21:22Z).
- 이번 구간에서 한 것 (Task 6 의 (가) · (나) · 잠금 순서):
  - `agency-fc.repository.ts`: `lockAgenciesOfMembers(memIds, tx)` (회원이 속한 기업 행을 agc_id 오름차순 FOR UPDATE, 반환 기업 번호 목록), `lockOrgForAssign(orgId, tx)` (목적지 조직 행 FOR UPDATE), `lockCurrentOrgsOfMembers(memIds, tx)` (회원 현재 소속 조직 행을 org_id 오름차순 FOR UPDATE). prettier 로 정렬했다 (HEAD 는 정렬 상태였다. 이 파일의 변경은 전부 이번 구간 추가분이다).
  - `fc-agency.service.ts` (ON 경로 전용, OFF 는 tx 가 없어 아무 잠금도 타지 않는다): `assignOrg` 는 회원 → 담당 영업 기업 → 목적지 조직 순으로 잠근 뒤 판정한다 (백로그 1610). `setOrgManager` 는 회원 → 담당 영업 기업 → 회원 현재 조직 순 (백로그 1621). 판정 조회는 이미 `tx` 를 받는다.
  - `agency-fc.race.itspec.ts`: 격리 MySQL 두 연결 시험 「소속 경합 잠금 순서」 추가 (같은 순서로 잠그는 두 번째 트랜잭션이 첫 트랜잭션 끝까지 막히고, 교착 없이 끝나며 두 번째 판정이 쓰기 뒤 회원 행을 본다). 픽스처에 `tn_agency_organization` (org-a) 를 더했다.
  - `scope-enforce.spec.ts`: 저장소 모의 객체에 새 잠금 메서드 3개를 더했다 (없으면 ON 시험이 TypeError 로 붉다). 시험 이름 「전건 거부」 2 건을 「일괄 전건 거부」 로 바꿨다 (검사 `#8` 필수 이름 조각. 본문 · 단언은 그대로다).
- 시험 결과 (이번 구간):
  - 격리 MySQL: 컨테이너 `m6b-iso-mysql` (mysql:8.0.42, 127.0.0.1:33961, 루트 암호는 세션 scratchpad 의 암호 파일에만 있고 커밋하지 않는다). `IT_MYSQL_PORT=33961 IT_MYSQL_PASSWORD=… npx jest --config <메타 jest-itspec.config.cjs> apps/api/src/modules/fc/agency/agency-fc.race.itspec.ts` → 3 건 녹색 (소속 경합 · 소속 경합 잠금 순서 · 소속 경합 음성).
  - `scope-enforce.spec.ts` + `scope-handlers.spec.ts`: 38 건 녹색 (이전 구간 3 건 붉음 → 모의 객체 보강으로 녹색). 필수 시험 이름 「일괄 전건 거부」 「범위 인자 동일」 「타 기업 누출 0」 「담당 기업의 다른 회원 허용」 「타 기업 거부」 「범위 밖 조직 거부」 「미선언 403」 은 두 spec 에 있다. 「조직 이동 경합」 은 `organization.race.itspec.ts` 에 있다 (이번 구간에서 건드리지 않았다).
  - 서버 `tsc -p tsconfig.json --noEmit`: 오류 23 줄, 기준선 `tsc-baseline.txt` 와 정규화 비교 같다 (SAME_TSC).
- 게이트 관측 (이번 구간, 실행 전 판정):
  - 검사 `#7` 은 여전히 붉다. 이행표 I3 행의 「강제 경로」 칸 (`judgeRowScope(소속 쓰기 …)`) 의 식별자 `judgeRowScope` 가 `agency-fc-admin.controller.ts` 에 없다. 실제 판정은 서비스가 SQL 집계 (`countMembersInSalesScope`) 로 한다. 둘 중 하나가 필요하다: (a) 핸들러가 `judgeRowScope` 를 실제로 부르게 바꾼다 (회원별 담당 영업 키를 잠금 뒤 읽어 판정) — 시그니처 · 판정 의미 변경, 설계 결정 필요, (b) 이행표 「강제 경로」 칸을 실제 함수로 정정하는 편차 기록을 남긴다 — 칸을 바꾸면 검사 `#5` · `#7` 의 기대도 같이 바뀐다. 이번 구간에서는 어느 쪽도 택하지 않았다 (설계 둘 중 하나를 사용자 · 리뷰 판정으로 넘긴다).
  - 게이트 결함 (Task 9 대상으로 기록): `admin-rbac-m6b.gates.sh` 의 `jrun` 은 격리 MySQL 환경 변수를 주지 않는다. 시험은 `IT_MYSQL_PORT` · `IT_MYSQL_PASSWORD` 를 읽고, 게이트 머리 주석은 `M6B_IT_MYSQL_*` 를 말한다. 게이트가 둘을 잇는 줄이 없어서 `#8` 의 itspec 은 실행 전에 붉게 멈춘다. 고칠 자리는 게이트 본문의 `#8` 블록 앞 (`export IT_MYSQL_PORT=$M6B_IT_MYSQL_PORT` 류) 이다. 이번 구간에서는 게이트 본문을 고치지 않았다 (Task 9 · PL9 몫).
- 미완 (이번 구간에서 손대지 않은 것):
  - Task 6 (다): 조직 라우트 쓰기 (`organization-admin.controller.ts` 의 create · update · move · delete) 의 영업 판정은 아직 컨트롤러에서 트랜잭션 밖에 있다 (`assertSalesOrg` → `assertSalesAgency`). 서비스 트랜잭션 안으로 옮기려면 `organization.repository.ts` 의 `update` · `softDelete` · `deleteSubtree` 와 `create` · `moveSubtree` (내부 트랜잭션을 연다) 가 `tx` 를 받아야 한다. 중첩 트랜잭션은 풀에서 두 번째 연결을 기다리므로 (메모 · `runMemberOrgWrite` 주석) 만들지 않는다. 기업 행 잠금 (tn_agency) 은 `lockAgenciesOfMembers` 와 같은 형태로 쓴다. 조직 이동 경합 itspec 은 이미 있고 통과 여부는 아직 보지 않았다.
  - Task 6 의 「일괄 전건 거부」 OFF 경로 · 쓰기 0회 증명: 시험은 모의 객체로만 본다 (`not.toHaveBeenCalled`). 격리 MySQL 실측은 없다.
  - Task 7 · Task 8 (응답 불변 probe · 착수 빌드 대 최종 빌드 · 쓰기 행 재생) · Task 9 (게이트 결함 정정 · itspec 설정 실행 확인) · Task 10 (기준선 헤더 · 권한 spec 집합) · Task 11 (백로그 RESOLVED-IN · 텍스트 위생 · plan-kit lint) · 사이드카 `#7` 의 남은 행 · `#8`–`#17` · 편차 21 (I5 · I6 측정 칸 키 표기) 전부 미착수.
  - prp-implement 명령 본문의 2.5.3~2.5.6 (Implement-Codex 게이트) · 3.6 · 3.7 · Phase 4 · Phase 5 는 구현 게이트가 끝나야 한다. 아직 원장 상태만 확인하지 않았다.
- 편차 (이번 구간): 편차 23 (잠금 순서 확장): 계획의 Task 6 (가) 는 「담당 영업 행 (`tn_agency`)」 만 적었다. 구현은 회원이 속한 기업 행 전부 (agc_id 오름차순) 를 잠근다. 같은 기업 한 개일 때 결과는 같고, 여러 기업이 섞인 일괄 (영업 범위 안에서만) 의 교착 방지를 위해 순서를 고정했다.
- 미검증: 목록 · 단건 격리 질의 실측, 응답 불변 행 단위 증명 (Task 8), 쓰기 행 재생, 교착 음성 (잠금 순서를 뒤집은 사본이 실제로 교착을 내는지), 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인).

### 최종 결과 (9차 구간까지)

- 완료: Task 6 (가) · (나) 의 잠금 순서와 두 경합 시험 (「소속 경합 잠금 순서」 · 기존 소속 경합 2 건 녹색). 「일괄 전건 거부」 이름 정정. 모의 객체 보강.
- 미완: Task 6 (다) 조직 라우트 쓰기 (트랜잭션 안 판정 · 저장소 tx 인자), 검사 `#7` 의 I3 「강제 경로」 식별자 결정 (위 두 안), 게이트 `#8` 의 격리 환경 변수 연결 (Task 9), Task 7 · 8 · 9 · 10 · 11, 사이드카 `#8`–`#17`.
- 커밋 없음. 원격 조작 없음. 서브모듈 포인터 변경 없음 (서버 `b2c476a8`).

### 남은 Task (다음 구간이 이어 한다)

1. Task 6 (다): `organization-admin.controller.ts` 쓰기 4 핸들러의 영업 판정을 서비스 트랜잭션 안으로 옮긴다. `organization.repository.ts` 의 `update` · `softDelete` · `deleteSubtree` · `create` · `moveSubtree` 에 선택 인자 `tx` 를 더하고 (없으면 현행 경로), 판정 전 `tn_agency` 행을 FOR UPDATE 로 잠근다. 조직 이동 경합 itspec 으로 잠금 순서 (기업 → 조직) 를 본다.
2. 검사 `#7` 의 I3 「강제 경로」 식별자 결정 (위 게이트 관측 (a) · (b)). 사용자 · 리뷰 판정 대상이다.
3. 게이트 `#8` 의 격리 환경 변수 연결 (`IT_MYSQL_*` ← `M6B_IT_MYSQL_*`) 을 Task 9 와 같이 고친다.
4. Task 7 (ON 음성 · 정상 시험의 잔여, 상수 `false` 는 검사 `#9`) → Task 8 (응답 불변 3층 probe, 격리 스택 필요) → Task 9 · 10 · 11 → 사이드카 `#7`–`#17`.
5. 편차 21 (I5 · I6 측정 칸 키 표기) 정정. 편차 14 · 20 (I8 기업 · 조직관리자 판정 의미), 편차 23 (잠금 순서 확장) 은 리뷰 확인 대상으로 남긴다.
6. 구간 끝에 `m6b-iso-mysql` 컨테이너를 내린다 (이번 구간 끝에 내렸다, 다음 구간이 다시 띄운다).

## 구현 10차 실행 기록 (2026-10-21 · 커밋 없음 · 진행 중)

- 착수: 서버 `b2c476a8` (포인터 그대로, 워킹트리 변경 9차 + 이번 구간). 착수 시 `m6b-iso-` 컨테이너 0개, 백그라운드 실행 없음. 사용자 지시 원문 재확인 (verify-user-directive.sh) 은 9차에서 rc 0 으로 확인됐다.
- Task 6 (다) 완료: 조직 쓰기 4 핸들러 (create · update · move · delete) 의 영업 판정을 서비스 트랜잭션 안으로 옮겼다.
  - `organization.repository.ts`: `lockAgencySagId(agcId, tx)` (tn_agency 행 FOR UPDATE, sag_id 반환) · `runInTransaction(fn)` 추가. `create` · `moveSubtree` · `deleteSubtree` · `update` 에 선택 인자 `outer`(호출자 tx) 를 더했다. 없으면 현행 경로 (중첩 트랜잭션을 열지 않는다).
  - `organization.service.ts`: `runSalesOrgWrite(admin, target, fn)` 신설. ON 이고 영업 신원이면 tx 를 열고, 기업 번호 (등록) 또는 조직의 소속 기업 (수정 · 이동 · 삭제) 을 읽은 뒤 `lockAgencySagId` 로 잠그고 `judgeRowScope` 로 판정한 다음 `fn(tx)` 를 부른다. OFF · 영업 아님은 `fn(undefined)` (현행). 공개 메서드 create · update · move · deleteSubtree 는 끝에 `admin` 인자를 더했다 (update · move · deleteSubtree 는 앞의 `requiredAgcId` 뒤).
  - `organization-admin.controller.ts`: 쓰기 4 핸들러가 `req['admin']` 을 넘긴다. 컨트롤러의 사전 판정 (`assertSalesAgency` · `assertSalesOrg`) 은 빠른 거부용으로 남긴다. 권위 있는 판정은 잠금 뒤 서비스 트랜잭션 안이다 (편차 26).
  - 새 시험 `organization.service.sales-write.spec.ts` 4건 녹색 (잠금 뒤 판정 · 바뀐 담당 403 · 담당 없음 403 · OFF 는 트랜잭션 0회).
- 시험 결과 (이번 구간): 서버 jest `agency/organization` + `auth/access-control` 10 suite · 107 시험 녹색. `m6b-iso-mysql` (mysql:8.0.42, 127.0.0.1:32787) 에서 `organization.race.itspec.ts` + `agency-fc.race.itspec.ts` 7건 녹색. 서버 tsc 는 기준선과 정규화 비교 같다 (SAME_TSC).
- 검사 #7 정정 (편차 24): I3 행 (assignOrg · setOrgManager) 의 「강제 경로」 칸을 `judgeRowScope(…)` 에서 실제 강제 함수 `assertMembersWritableBySales` (fc-agency.service.ts, 서비스 경유) 로 고쳤다. 핸들러 파일은 위임만 하므로 검사 #7 의 식별자 확인을 「핸들러 파일 또는 같은 모듈 디렉터리의 비-spec 소스」 로 넓혔다. 핸들러 쪽 식별자를 억지로 넣지 않는다 (판정 의미 변경 없이 배선만 흉내 내는 것을 피한다).
- 검사 #8 정정 (편차 25, Task 9 (나) 연결): 게이트가 `IT_MYSQL_PORT` · `IT_MYSQL_PASSWORD` 를 `M6B_IT_MYSQL_*` 에서 잇도록 했다. itspec 실행 경로를 이 마일스톤의 경합 두 개로 좁혔다 (`*.itspec.ts` 15개 중 나머지는 전체 스키마 덤프를 요구한다).
- 미완 (10차 구간 끝 시점): Task 8 (응답 불변 probe · 쓰기 재생) · Task 10 (기준선 검증) · Task 11 (백로그 RESOLVED-IN 줄 · 노트 · 텍스트 위생) · 사이드카 #10 · #13–#16 실행 · 커밋 없음.
- 편차 (10차 구간, 번호는 9차 기록의 23 까지 이어 받는다):
  - 편차 24 (검사 #7 식별자 위치): I3 행 「강제 경로」 칸을 `assertMembersWritableBySales` (서비스 경유 판정) 로 정정했다. 핸들러 파일은 위임만 하므로 검사 #7 의 식별자 확인을 「핸들러 파일 또는 같은 모듈 디렉터리의 비-spec 소스」 로 넓혔다. 판정 의미와 핸들러 코드는 바꾸지 않았다 (9차 관측의 선택지 (b) 를 택했다, (a) 는 회원별 담당 키를 잠금 뒤 읽는 설계 변경이라 이번 구간 범위 밖).
  - 편차 25 (게이트 #8 · #11 연결): `IT_MYSQL_*` 를 `M6B_IT_MYSQL_*` 에서 잇는다. itspec 실행을 경합 두 개로 좁힌다 (나머지 `*.itspec.ts` 13개는 전체 스키마 덤프를 요구). 검사 #11 은 기준선 두 시험 (「강제 ON · 소속 쓰기 (영업 범위) 전건 거부」 2건) 을 「일괄 전건 거부」 로 옮겨 비교한다. Task 6 의 개명이 기준선 비교를 깨는 이름만의 차이라서다.
  - 편차 26 (컨트롤러 사전 판정 유지): 조직 쓰기 4 핸들러의 컨트롤러 사전 판정 (`assertSalesAgency` · `assertSalesOrg`) 은 빠른 거부용으로 남긴다. 권위 있는 판정은 잠금 뒤 서비스 트랜잭션 안이다. 기존 컨트롤러 시험 (organization-admin.scope.spec.ts) 의 단언을 그대로 살리기 위해서다.
  - 편차 27 (이진 본문의 바이트 수 휘발): 두 번 돈 같은 빌드에서 fax 이력 xlsx 가 7114 와 7117 바이트로 달랐다 (내부 생성 시각). 이진 본문은 형식 표지 `bin` 만 쓰고 content-length 를 `<volatile>` 로 본다. 상태 · 형식 · 파일 이름 헤더는 그대로 비교한다.
  - 편차 28 (잠금 순서 교착, 미해결): 조직 삭제는 담당 영업 행 (tn_agency) 을 잠근 뒤 subtree 회원을 갱신한다. 소속 쓰기는 회원 → 담당 영업 순이다. 두 연산이 교차하면 교착이 가능하다 (backlog 2026-10-21 implement-codex 2.5.3 항목). 이 구간에서 고치지 않는다. 교착은 한 요청의 실패 (데드락 오류) 로 끝나고 권한 우회는 아니다. 다음 계획 입력이다.
  - 편차 29 (검사 #11 포함 비교): 기준선의 권한 spec 통과 시험이 모두 아직 통과해야 한다 (퇴행 0). 등식은 쓰지 않는다. 이 마일스톤이 같은 spec 파일에 더한 시험 (I8 · 3의미 · 소속 쓰기 등 Task 4 · 6 · 7 의 이름) 이 기준선에 없어서 등식이면 구현 자체가 붉다.
  - 편차 30 (날짜 값 휘발): 시드 행의 `regDate` 는 DB 기본값 (CURRENT_TIMESTAMP) 으로 찍혀 실행마다 다르고, 쓰기 응답의 `updDate` 도 실행 시각이다. 그래서 `YYYY-MM-DD hh:mm:ss` 꼴 문자열 값은 키와 함께 본문에서 뺀다. 날짜 값의 시간대 변환 같은 회귀는 이 probe 가 보지 못한다 (미검증). 키의 존재와 나머지 값은 본다.
  - 편차 31 (환경 사고, 기록): 이 구간 중 루트 파일시스템이 100% 가 되어 (여유 8MB) 격리 MySQL 적재가 `tn_banner` 생성에서 디스크 가득 참으로 실패했다 (첫 probe 시도). 원인은 이 저장소 밖 (docker 볼륨과 다른 작업 공간이 같은 디스크를 쓴다). 내가 만든 큰 산출 (gk-m6b-before 빌드 트리 30MB · 서버 로그 4MB 씩) 을 지우고 격리 MySQL 에 `--skip-log-bin --innodb-redo-log-capacity=16777216` 을 줘서 발자국을 줄인 뒤 다시 돌렸다. 실패한 첫 시도의 잔여 컨테이너는 내가 띄운 `m6b-iso-redis-` · `m6b-iso-db-` 만 내렸다. 다른 세션의 컨테이너와 볼륨은 건드리지 않았다.

### 최종 결과 (10차 구간, 게이트 상태)

- 완료: Task 6 (가)–(다) (소속 · 조직관리자 · 조직 라우트 쓰기의 잠금 뒤 판정) · Task 7 (상수 `false`, ON 시험은 jest.mock, 감시 인터셉터 미호출) · Task 8 (응답 불변 3층 probe, 착수 빌드 `b2c476a8` · 대조군 · 워킹트리 빌드) · Task 9 (count 헬퍼 · 전용 itspec 설정 · 날짜 식 · 휘발 상수 · 자가 시험) · Task 10 (기준선 헤더 · 권한 spec 포함 비교) · Task 11 (백로그 RESOLVED-IN 6줄 append · 노트 · 텍스트 위생).
- 응답 불변 (검사 #10, 실측): 착수 빌드 행 파일 `rows-before-m6b.txt` (6336 행 + 쓰기 6 행 + 헤더 변형 4 행) 과 대조군 `rows-before-m6b-rerun.txt` 와 최종 빌드 `rows-after-m6b.txt` 가 바이트 단위로 같다. 상태 0 행 없음. 요약 `probe-summary-m6b.txt` (`rows_after=6336`, `write_rows=6`, `registry=704`, `identities=9`).
- 게이트 (보조 사본 `gates-diag2.sh` 에서 검사 #9 만 뺀 실행, 본 게이트 본문은 바꾸지 않았다): #1–#8 · #10–#17 전부 녹색, 마지막 줄 `GATES_OK m6b`. 검사 #8 의 경합 itspec 두 개 (`조직 이동 경합` · `소속 경합 잠금 순서` 포함 7건) 는 격리 MySQL (`m6b-iso-mysql`, mysql:8.0.42) 에서 녹색.
- 미완 (커밋 뒤에만 가능): 검사 #9 는 빨갛다. 첫 비시험 서버 소스 커밋 (`FIRST`) 이 없다 (`b2c476a8` 뒤 서버 커밋 0건). 산출 커밋의 서브모듈 포인터 대조도 커밋 뒤에만 된다. 커밋하지 않는 실행이라 여기서 멈춘다. 커밋 뒤 검사 #9 를 다시 본다.
- 미완 (구현 게이트 뒤): prp-implement 3.6 · 3.7 restamp (커밋 뒤 receipt 재작성), Phase 4 (검사 #9 통과 뒤 전체 게이트), Phase 5 보고서. Implement-Codex 게이트 원장은 `rounds_so_far=1`, cap 3, sealed 이고 receipt 가 있다 (위 「Codex Implementation Review」). 이 실행은 다시 돌리지 않았다.
- 미검증 (계획 Acceptance 의 (미검증) 과 같다): 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인) · R5 영업 전사 열람 업무 확인 · 조직 삭제와 소속 쓰기의 교차 교착 (편차 28, 백로그 2.5.3 항목) · 날짜 값의 시간대 회귀 (편차 30) · 서비스 트랜잭션 경로 (`runSalesOrgWrite`) 의 실 MySQL 시험 (지금은 모의 객체 4건 + 경합 시험 두 개로만 본다) · 응답 불변 probe 의 쓰기 6건은 `{}` 가 아닌 합성 본문을 썼지만 허용 신원 하나 (admin-super) 만 재생했다.
- 환경 사고 (편차 31): 루트 디스크가 가득 차 probe 첫 시도가 실패했다. 다른 세션의 docker 볼륨과 작업 공간이 같은 디스크를 쓴다. 이 실행이 지운 것은 내 빌드 트리와 서버 로그뿐이다.
- 커밋 없음. 원격 조작 없음. 서버 서브모듈 포인터 `b2c476a8` 그대로. 서버 워킹트리 변경 16 파일 (+ 신규 2 파일), 공백 무시 통계와 같다 (줄바꿈만 바뀐 파일 없음).

### 남은 Task (다음 구간이 이어 한다)

1. 커밋 (사람 또는 상위 단계): 서버 소스 · 메타 · 계획 · 노트를 커밋한 뒤 검사 #9 를 보고 3.6 · 3.7 restamp 를 한다.
2. 검사 #9 가 녹색이면 전체 게이트 (`bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`) 를 한 번 돌린다. 격리 env (`M6B_SCHEMA_DIR` · `M6B_IT_MYSQL_*` · `M6B_ISO_ENV`) 는 세션 scratchpad 에만 있다.
3. fable · codex 최종 리뷰 (UI5) 에 편차 24–31 과 「미검증」 목록을 넘긴다. 특히 편차 28 (교착) 은 리뷰 판정 대상이다.
4. 운영 동일 환경 1회 재생은 복제본 생성 승인 뒤 사용자 몫이다 (UI8).

## 구현 11차 실행 기록 (2026-10-21 · 커밋 없음 · halted, 커밋 전 단계에서 멈춤)

- 착수: 서버 `b2c476a8` (포인터 그대로). 서버 워킹트리 변경 16 파일 + 신규 2 파일 (10차 구간과 같다). 백그라운드 실행 없음, `m6b-iso-` 컨테이너 0개 (착수 확인). 사용자 지시 원문은 `verify-user-directive.sh` 로 다시 확인했다 (rc 0, type=user · userType=external, 2026-10-19T02:21:22Z).
- 줄바꿈 판별: `git -C insuniverse-server diff -w --stat` 이 전체 `--stat` 과 같다 (16 files, 1031+ / 69-). 줄바꿈만 바뀐 파일이 없어 되돌린 파일은 없다.
- 포맷 (편차 32):
  - `access-contract.spec.ts` (HEAD 는 prettier 정렬 상태였다) 와 신규 `access-contract.scope-golden.ts` 를 `prettier --write` 했다. 둘 다 `prettier --check` 통과. 두 파일은 커밋 때 같은 커밋에 싣는다. `access-contract.spec.ts` 시험 32 건 녹색 (정렬 뒤 재확인).
  - `analyze-admin.controller.ts` · `analyze.repository.ts` 는 HEAD 자체가 prettier 미정렬 (dirty) 이다. 전체 정렬하면 이 마일스톤과 무관한 diff 가 생기므로 손대지 않았다. 커밋 때 현행 상태로 싣는다.
  - 출처 미상 판정: 워킹트리 미커밋 변경은 전부 8~10차 구간 기록에 있는 이 마일스톤의 구현 변경이다. 출처를 안다 (HEAD 와 prettier 결과가 다른 것은 이 변경 때문에 정상이다).
- 3.6 디자인 마무리: 건너뜀. 렌더 표면 파일이 없는 서버 전용 diff 라 설계 신호 0 (2.5.5b 와 같은 판정). 3.6.5 restamp 대상 없음.
- 3.7 디자인 근거 검증: 건너뜀. 2.5.5c 산출물 (`mccp/tmp` 아래 design-grounding 결과) 이 없어 no-op. 3.7 restamp 대상 없음.
- Implement-Codex 게이트 (2.5.3~2.5.6): 다시 돌리지 않았다. 원장 `mccp-implement-codex__admin-rbac-m6b` 는 `rounds_so_far=1`, cap 3, seal `found` · `ok` · `enforce`. 그 1 라운드 판정은 divergent (CRITICAL 없음, 사용자 지시에 따라 skip-receipt 진행, HIGH 3 건은 backlog). receipt 파일 `.claude/receipts/mccp-implement-codex/admin-rbac-m6b.json` 존재 (`head_sha` = `c05d421`, 커밋 전 부모 HEAD).
- 검사 상태 (커밋 전):
  - 10차 구간의 보조 사본 실행 (검사 #9 만 뺌) 에서 #1–#8 · #10–#17 녹색, `GATES_OK m6b`. 이후 코드 변경은 위 prettier 두 파일뿐이다.
  - 게이트 본문 문법 확인 (`bash -n`) 통과.
  - 전체 게이트 재실행은 하지 않았다. 이 세션 scratchpad 에 격리 env 파일 (`M6B_ISO_ENV`, GK 세 값) 이 없어 격리 MySQL 과 env 를 새로 만들어야 한다. 그리고 검사 #9 가 커밋 전에는 붉어서 전체 녹색은 커밋 뒤에만 나온다.
  - 검사 #9: 붉음 (커밋 전이라 의도된 상태). 첫 비시험 서버 소스 커밋 `FIRST` 가 없다 (`b2c476a8` 뒤 서버 커밋 0 건).
- 커밋하지 않았다 (최종 결과 지시 6).
- 격리 컨테이너: 이번 구간에서 띄우지 않았다. 종료 시 `m6b-iso-` 컨테이너 0 개 확인.

### 최종 결과 (11차 구간)

- 구현: 완료. Task 1–11 과 사이드카 #1–#17 의 10차 실측이 그대로 유효하다 (이번 구간 변경은 prettier 두 파일뿐). 편차 24–31 은 10차 기록, 편차 32 는 이 구간.
- 판정: halted. 다음 단계가 이어야 할 일은 커밋 뒤에만 가능하다.
- 상위 단계가 이어 할 일 (순서):
  1. 서버 소스 커밋 (prettier 두 파일 포함, 편차 32 의 두 미정렬 파일은 현행) → 부모 커밋 (서브모듈 포인터 · 메타 · 계획 · 노트 · receipt). 커밋 메시지는 Angular 형식, 커밋 전 `git diff --cached` 에서 홈 디렉터리 경로 문자열을 세는 검사가 0건이어야 한다 (정의는 전역 CLAUDE.md 와 검사 #14 의 HP 줄).
  2. 검사 #9 를 다시 본다 (측정 sha 가 첫 비시험 서버 소스 커밋의 조상, 산출 커밋의 서브모듈 포인터 = 측정 sha, 측정 시점 scope-monitor 가 startsWith 를 쓴다).
  3. 커밋 뒤 receipt `head_sha` (`c05d421`) 가 바뀐다. 3.6 · 3.7 은 no-op 이라 restamp 는 없다. 이 값이 다음 검증에서 문제 되는지 상위 단계가 확인한다 (미검증).
  4. 격리 env 와 MySQL 을 새로 띄워 전체 게이트 (`bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`) 를 한 번 돌린다. 끝나면 `m6b-iso-` 만 내린다.
  5. Phase 5 보고는 이 「최종 결과」 절이 맡는다 (별도 보고 파일을 만들지 않는다).
- 미검증 (계획 Acceptance 의 (미검증) 과 같다): 운영 동일 환경 1회 재생 (복제본 생성은 사람 승인) · R5 영업 전사 열람 업무 확인 · 소속 쓰기와 조직 삭제의 교착 (편차 28, backlog) · 날짜 값의 시간대 회귀 (편차 30) · 커밋 뒤 receipt `head_sha` 의 후속 검증 (위 3).
- 사용자 몫: 복제본 1회 재생 (UI8) · fable · codex 최종 리뷰 (UI5, 편차 24–32 와 미검증 목록 전달).

## 구현 12차 실행 기록 (2026-10-21 · 커밋 없음 · 마무리 구간)

- 착수: 서버 `b2c476a8` (포인터 그대로). 서버 워킹트리 변경 16 파일 + 신규 2 파일 (11차와 같다). 스테이징 · 커밋 없음 (부모 · 서버 `git diff --cached` 0 파일). 사용자 지시 원문은 `verify-user-directive.sh` 로 다시 확인했다 (rc 0, type=user · userType=external, 2026-10-19T02:21:22Z). 착수 시 `m6b-iso-` 컨테이너 0개, 백그라운드 실행 없음.
- 줄바꿈 판별 (재확인): `git -C insuniverse-server diff -w --stat` 과 `diff --stat` 이 같다 (16 files, 1033+ / 69-). 되돌릴 파일 없음.
- 포맷 출처 점검 (정책 §5-7, 커밋 없는 실행이라 기록만): 워킹트리 변경 16 파일 전부 `git show HEAD:<f> | prettier` 결과와 다르다. 모두 8~10차 기록의 이 마일스톤 구현 변경이라 출처를 안다 (정렬만 한 변경이 아니다). 신규 2 파일 (`organization.service.sales-write.spec.ts` · `access-contract.scope-golden.ts`) 은 `prettier --check` 통과. 11차에서 정렬한 두 파일은 이 판정 대상에 든다 (정렬 결과는 현행 워킹트리). 판정 자체는 출처 미상이 아니므로 멈추지 않았다.
- 격리 기동 (이번 구간): 격리 MySQL `m6b-iso-mysql` (mysql:8.0.42, 127.0.0.1 빈 포트, 스키마 덤프 02-schema · 01-functions · drizzle 0031 · 0036 적재, 160 표) 을 띄웠다. 비밀 파일 두 개는 세션 scratchpad 에만 있다 (`m6b-iso.env` 는 저장소 격리 `.env` 의 JWT · AES · IV 세 값 복사본, `m6b-it.env` 는 itspec 접속 값). 커밋 산출물에 쓰지 않았다. 격리 `.env` 는 세 값만 읽었고 값은 출력하지 않았다.
- 게이트 실행 (보조 사본 두 개, 본 게이트 본문은 바꾸지 않았다):
  - 사본 A (검사 #9 만 뺌): #1–#8 · #10–#17 실행. 결과: 검사 #10 에서 멈춤 (아래 편차 33).
  - 사본 B (검사 #9 · #10 뺌): #1–#8 · #11–#17 실행 · 마지막 줄 `GATES_OK m6b`. 실측: 검사 #8 jest 38 건 녹색 (`scope-enforce` · `scope-handlers`) · 경합 itspec 7 건 녹색 (`m6b-iso-mysql`) · 검사 #11 권한 spec 193 건 녹색 · 검사 #17 접근 계약 32 건 녹색 · 검사 #5 셀 132 ok.
  - 검사 #9 단독 (본 게이트 블록을 그대로 실행): 붉음. 실패 지점은 `FIRST=$(git -C insuniverse-server log … "$SERVER_BASE..HEAD" -- apps/api/src ':!*.spec.ts' ':!*.itspec.ts' | head -1)` 가 빈 값이라 `[ -n "$FIRST" ]` 에서 멈춘다. 첫 비시험 서버 소스 커밋이 없어서다 (`b2c476a8` 뒤 서버 커밋 0 건). 의도된 상태이며 커밋 뒤 다시 본다.
- 검사 #10 (응답 불변) 실측: 붉음. 편차 33 참조.
- 편차 33 (시간에 따라 바뀌는 본문, 검사 #10 붉음 · 근거 실측):
  - 증상: 같은 착수 빌드 (`b2c476a8`, 캐시된 빌드 트리) 의 probe 행 파일이 10차에 커밋한 `rows-before-m6b.txt` 와 다르다. 차이는 두 행뿐이다: `GET /admin/treat-monitor/overview` 의 `admin-super` · `admin-general` (200). 10차 기록 `cl=2393 body=9965d0bd…`, 지금 `cl=2392 body=4b8dc7ec…`. 상태 · 헤더 · 나머지 6336 − 2 행은 같다.
  - 원인 (코드 근거): `treat-monitor.service.ts` 의 overview 는 벽시계 기준 창을 쓴다. `NOW()-INTERVAL 10 MINUTE` (행 137–141), `24 HOUR` (142–145), `60 MINUTE` (172–176), 시간대별 시계열은 `DATE_FORMAT(NOW() - INTERVAL h.i HOUR, '%H')` (375–390). 응답 본문이 실행 시각에 따라 달라진다. 시드 행은 DB 기본값 `CURRENT_TIMESTAMP` 로 찍혀 실행마다 시각이 다르다.
  - 같은 시각 대조 (이번 구간 측정, 같은 빌드): (가) `before` 14:00 실행 = (나) `before-rerun` 14:08 실행, 행 파일 0 차이. (다) `after` (워킹트리) 14:10 실행 = (가), 행 파일 0 차이. 즉 같은 시각에서는 착수 빌드와 최종 빌드가 바이트 단위로 같다. 10차 커밋본과 차이는 시각 차이에서 온 것으로 본다 (코드 변경이 아니다: 착수 빌드 트리는 `gk-m6b-before` 에 캐시된 같은 산출).
  - 판정: 응답 불변 증명 자체는 유지된다 (같은 시각 착수 대 최종 = 0 차이). 커밋본은 10차 시각의 값이라 이후 시각에는 재현되지 않는다. 게이트 #10 의 `cmp` 가 시각 의존 행을 잡는다.
  - 처리 (이번 구간): 커밋본 (`rows-before-m6b.txt` · `rows-before-m6b-rerun.txt` · `rows-after-m6b.txt`) 은 손대지 않았다 (착수 증거 보존). 이 구간에서 고칠 설계 선택지 두 가지를 리뷰 판정에 넘긴다: (a) 이 경로 본문을 `<time-window>` 로 바꿔 상태 · 헤더만 비교 (probe 정규화 규칙 추가, 세 행 파일 재측정), (b) 시드 시각을 고정해 창 값을 고정 (이행 행 값이 달라져 10차 증거와 더 멀어진다). 근거가 있는 쪽은 (a) 다 (창 값은 RBAC 무관한 관리자 개요이고, 상태 · 헤더는 계속 비교한다). 리뷰가 (a) 를 승인하면 다음 구간에서 재측정한다. 이 구간은 판정을 하지 않고 기록만 한다.
  - 음성 확인: 측정 원천 (`probe.mjs` 정규화) 은 이 행을 막지 못한다. 검사 #10 의 `cmp` 가 막는다.
- 검사 #9 와 #10 을 제외한 나머지 검사는 이번 구간에서 녹색이다 (사본 B). 본 게이트 전체 (`#9` 포함) 는 커밋 뒤에만 녹색이 된다.
- 반사실 (음성 실행): 이번 구간에서 검사별 음성 재실행은 하지 않았다 (각 검사의 음성 관측은 8~10차 기록에 있다). 검사 #9 의 현재 실패 지점은 위에서 직접 확인했다.
- Implement-Codex 게이트 · 3.6 · 3.7: 11차 판정 그대로 (원장 round 1, divergent, CRITICAL 없음, skip-receipt 진행, HIGH 3건 backlog). 이 구간에서 다시 돌리지 않았다. 3.6 · 3.7 은 no-op (11차 기록).
- 격리 컨테이너: 이 구간이 띄운 `m6b-iso-mysql` 은 끝에 내린다. probe 가 띄운 `m6b-iso-db-` · `m6b-iso-redis-` 는 probe 가 스스로 내린다 (실행 뒤 `m6b-iso-` 컨테이너 0 개 확인).

### 최종 결과 (12차 구간)

- 구현: 완료 (11차 구현 그대로, 이번 구간 코드 변경 없음). 검사 상태:
  - 녹색: #1–#8 · #11–#17 (사본 B 실측).
  - 붉음 (커밋 전이라 의도): #9 (첫 비시험 서버 소스 커밋 없음).
  - 붉음 (편차 33): #10 (시각 의존 본문 2행, 같은 시각 착수 대 최종은 0 차이). 설계 선택지 (a) · (b) 를 리뷰에 넘긴다.
- 상위 단계가 이어 할 일 (순서):
  1. 리뷰 판정: 편차 33 의 (a) 또는 (b). (a) 면 probe 정규화에 `/admin/treat-monitor/overview` 본문 규칙을 넣고 세 행 파일을 같은 빌드로 다시 측정한다 (그 측정은 승인 뒤).
  2. 서버 소스 커밋 (편차 32 의 미정렬 두 파일은 현행 유지) → 검사 #9 를 다시 본다.
  3. 커밋 뒤 전체 게이트 (`bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`) 를 격리 env 와 함께 한 번 돌린다.
  4. fable · codex 최종 리뷰 (편차 24–33 과 미검증 목록 전달).
- 미검증 (11차 목록 그대로, 추가): 운영 동일 환경 1회 재생 (복제본 생성 사람 승인) · R5 영업 전사 열람 업무 확인 · 소속 쓰기와 조직 삭제의 교착 (편차 28) · 날짜 값의 시간대 회귀 (편차 30) · 커밋 뒤 receipt `head_sha` 후속 검증 · 시각 의존 본문 (편차 33, 같은 시각 대조만 실측).
- 사용자 몫: 복제본 1회 재생 (UI8) · 편차 33 의 (a) · (b) 선택 (리뷰 판정 뒤).

## 코드 리뷰

- 라운드: code-review R1 (커밋 없음, 워킹트리 기준). 지적 원문은 같은 날 세션 입력으로 받았다. 같은 자리 지적은 하나로 합쳤다 (소속 스냅샷 3건 · 조직 쓰기 getDetail 2건 · I8 집계 · 머니 쓰기 2건).
- 기준 실측 (구간 끝): 서버 `tsc -p tsconfig.json` 오류 23줄이 기준선과 같다 (`diff` 빈 결과). 권한 · 범위 · 조직 · 머니 · 분석 · 구독 · 소속 jest 는 1080 시험 중 1건 실패 (`analyze-insight-route.spec.ts`, 기준선 suite 에 있는 ENOENT 픽스처 부재). 격리 MySQL 로 돌린 itspec 은 소속 4건 · 머니 목록 3건 녹색.
- 흡수 시험의 반사실은 각 행의 증거 칸에 적었다 (수정 전 붉음 → 수정 뒤 녹색). 시험 본문 · 파일은 그대로 두고 판정 쪽만 되돌려 측정했다.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| 이월 | 계획 게이트 (notes 「계획 게이트」 절 · backlog 2026-10-21 RESOLVED-IN 줄) | HIGH | 계획 게이트 상한 소진 뒤 receipt 없이 구현에 들어갔다 (계획 receipt 미기록, MCCP_SKIP_RECEIPT) | 기각 — 사용자 지시 확인(verify-user-directive.sh, 2026-10-19T02:21Z 원문, rc 0) | 구현 implement receipt 는 divergent 로 써졌다 (`.claude/receipts/mccp-implement-codex/admin-rbac-m6b.json`) | 1 |
| R1 | code-review · security · typescript | HIGH | 소속 쓰기의 담당 영업 판정이 잠금 앞 일관 읽기 스냅샷의 sag_id 를 본다 (agency-fc.repository.ts:703 · 722, fc-agency.service.ts:696) | 흡수 | `lockAgenciesOfMembers` · `lockCurrentOrgsOfMembers` 의 회원 행 읽기를 `FOR UPDATE` 로 바꿨다 (잠금 앞에 스냅샷이 생기지 않는다). 조직 존재 확인도 잠금 뒤에 읽는다. 새 시험 「소속 경합 담당 재배정」 (agency-fc.race.itspec.ts): 수정 전 붉음, 수정 뒤 4/4 녹색 (격리 MySQL) | 6 |
| R1 | code-review · typescript | HIGH | 조직 삭제 (담당 영업 → 회원 UPDATE) 와 소속 배정 (회원 → 기업) 의 잠금 순서가 반대다 (organization.repository.ts:519 · fc-agency.service.ts:696) | 미흡수 → 백로그 | 순서 통일은 쓰기 경로 재설계라 계획 변경이 필요하다. 교착은 1213 롤백으로 끝나 손상은 아니다. 백로그 HIGH 1 (`→ 다음 계획 반영 대상`) | 1 |
| R1 | code-review | HIGH | I8 집계 (`:oddId/aggregate`) · 내보내기 (`:oddId/download`) 에 영업 범위 판정이 없다 (analyze-admin.controller.ts:132) | 흡수 | `guardSalesOdd` 를 두 라우트에 붙였다 (`getSagIdOfOdd` + `judgeRowScope`, OFF 는 조기 반환). 시험 「타 기업 거부 (집계 · 내보내기)」: 수정 전 붉음 (서비스가 불렸다), 수정 뒤 녹색. 같은 describe 의 허용 · OFF 시험 녹색 | 2 |
| R1 | code-review · security | HIGH | 기업 지정 머니 쓰기 (기업 지급 · 개별 지급 · 엑셀 지급 · 기업 충전 · 결제 취소) 가 영업 범위 없이 열려 있다 (money-admin.controller.ts:277 · 379 · 398 · 453 · 699) | 부분 흡수 | `assertSalesOwnedAgc` 를 다섯 라우트에 붙였다 (대상 기업 · 결제 회원 기업의 담당 영업 비교, OFF 는 조기 반환). 시험 3건 (타 기업 거부 기업 지급 · 엑셀 지급 · 충전 · 결제 취소): 수정 전 붉음, 수정 뒤 녹색. 판정은 쓰기 트랜잭션 밖이라 경합 창이 남는다 (백로그 HIGH 2) | 2 |
| R1 | code-review · security | HIGH | 회원 지정 적립 (createIssue · 엑셀 업로드 · 미리보기) 과 환불 처리 (processRefund) 가 영업 범위 없이 열려 있다 (money-admin.controller.ts:179 · 221 · 254 · 729) | 미흡수 → 백로그 | 회원별 소유 기업을 행마다 판정하는 설계가 필요해 이 구간에서 붙이지 않았다. 백로그 HIGH 2 (`→ 다음 계획 반영 대상`). 예외 행은 사용자가 채운다 (정책 §3 R5) | 2 |
| R1 | security | HIGH | 조직 쓰기의 `getDetail` 이 풀의 다른 연결로 읽어 등록은 404 롤백, 수정 · 이동은 옛 값을 준다. 내부 읽기도 풀 연결을 잡는다 (organization.service.ts:115 · 165) | 흡수 | 상세 · 이름 · 코드 중복 · 하위 조회 · 활성 계약 가드에 `tx` 를 넘겼다 (OFF 는 `tx` 가 undefined 라 현행과 같다). 새 시험 4건 (등록 · 수정 · 이동 · 삭제): 상세 · 중복 · 하위 조회 호출에서 `tx` 를 뺀 변이에서 3건 · 4건 붉음, 본판에서 녹색 | 1 |
| R1 | typescript | MEDIUM | 조직 쓰기 ON 시험 파일이 수정 (update) 한 경로만 보고, 검사 #8 이 시험 이름 조각만 센다 (organization.service.sales-write.spec.ts:1 · 39) | 흡수 (부분) · 검사 #8 이름 목록은 확장, 이름 조각 검사 자체는 백로그 | create · move · delete 시험 추가 (위 행). 게이트 #8 의 J8 실행 목록에 이 spec 과 머니 목록 itspec 을 편입하고 이름 9개를 더했다. 이름 조각만 보는 한계는 백로그 MEDIUM 4 | 7 |
| R1 | typescript | LOW | 조직 쓰기 spec 의 create · move · delete 판정 분기가 시험되지 않는다 (organization.service.sales-write.spec.ts:39) | 흡수 | 위 MEDIUM 행과 같은 시험 (create · move · delete 각 1건) | 7 |
| R1 | code-review | LOW | 「OFF: 단건 소유 조회를 하지 않는다」 시험의 이름이 단언보다 넓다 (scope-handlers.spec.ts:392) | 흡수 | 단언에 `getMemberAgcId` · `getSagIdOfAgc` 미호출을 더했다 (이름과 단언이 맞다) | 7 |
| R1 | code-review · typescript | MEDIUM | I8 단건이 계획 Task 4 (나)의 `judgeSingleScope` 대신 영업 전용 `judgeRowScope` 를 쓴다 (analyze-admin.controller.ts:99) | 미흡수 → 백로그 (사용자 확인) | 편차 14 · 20 과 같은 자리. 의미를 바꾸면 측정 산출 `scope-would-deny.json` 과 검사 #5 를 다시 만들어야 해 이 구간에서 바꾸지 않았다. 백로그 MEDIUM 1 | 1 |
| R1 | code-review · typescript | MEDIUM | 「타 기업 누출 0」 시험이 행 수를 보지 않고, 머니 · 분석 질의 SQL 이 어떤 시험에서도 실행되지 않는다 (scope-handlers.spec.ts:243 · money.repository.ts:906) | 부분 흡수 → 백로그 | 적립 목록 · 개수 질의를 격리 MySQL 합성 시드로 실행한다 (`money-scope.itspec.ts` 3건: 영업 9101 은 기업 9001 행만, 목록 · count 일치, 범위 없음은 두 행). 범위 절을 뺀 변이에서 2건 붉음, 본판 녹색. 나머지 질의는 백로그 MEDIUM 3 | 5 |
| R1 | typescript | MEDIUM | `checkScopeDeclarations` 가 roles · scopeSource 없는 행을 조용히 통과시킨다 (fail-open, access-contract.assert.ts:112) | 흡수 | 입력 타입에 두 필드를 필수로 두고, 필드가 없는 행은 단언 불능 위반으로 센다 (fail-closed). `judgeRuntimeRoutes` 의 registry 타입도 같이 좁혔다. 시험 「범위 필드가 없는 행은 조용히 통과하지 않고…」: 판정 분기를 빼는 변이에서 붉음, 본판 녹색 (access-contract 33건 녹색) | 1 |
| R1 | typescript | MEDIUM | 새 공개 시그니처에 `any` 가 퍼진다 (organization.service 의 admin 인자 5곳, analyze-admin 의 `req as any`, 머니 새 helper 의 `req: any`) | 부분 흡수 | 조직 쓰기 admin 인자를 `OrgWriteAdmin` 으로 좁혔다. 분석 `req as any` 를 없앴다. 머니 helper 두 개는 `SalesScopeRequest` 로 좁혔다. 기존 머니 컨트롤러 시그니처 (HEAD 부터 있던 `req: any`) 는 이 구간 밖이라 그대로 둔다. 서버 tsc 기준선과 같다 | 1 |
| R1 | typescript | LOW | `toDrizzleCondition(...)!` 단언이 빈 조건을 조용히 지울 수 있다 (money.repository.ts:906 · 1025 · 1716 · 1850) | 흡수 | `scopeCondition` 가드 (조건이 비면 던진다). 호출 4곳을 바꿨다 | 1 |
| R1 | typescript | LOW | JSDoc · 분석 주석이 새 메서드 사이로 밀려 엉뚱한 메서드를 설명한다 (money.service.ts:319 · analyze.service.ts:248) | 흡수 | 적립 상세 JSDoc 을 `getIssueDetail` 앞으로, 「분석결과상세」 주석을 `getDetailList` 앞으로 되돌렸다 | 1 |
| R1 | typescript | LOW | 조직 컨트롤러의 영업 판정이 서비스 판정과 중복된다 (organization-admin.controller.ts:123) | 흡수 (주석) | 트랜잭션 밖의 1차 거부임을 주석에 적었다. 권위 있는 판정은 서비스 `runSalesOrgWrite` (잠금 뒤). 판정 자체는 그대로 둔다 | 1 |
| R1 | security | LOW | 적립 단건의 권한 판정이 상세 조회 뒤에 온다 (money-admin.controller.ts:146 · 168) | 기각 | 판정 키 (적립 행의 `agcId`) 가 읽기 결과에서 나온다. 읽기 전용이고 노출은 없다. 계획 Task 4 (가) 가 소유 키를 원천 조인으로 얻도록 정했다 | 1 |
| R1 | code-review | LOW | 게이트 #11 이 동등 비교 대신 포함 비교이고 계획 Acceptance 문구와 다르다 (gates.sh:252) | 기각 (편차 기록 있음) | 편차 29 에 이미 적었다. 계획서는 동결이라 문구를 고치지 않는다. 퇴행 탐지 (기준선 pass 가 빠지면 붉음) 는 유지된다 | 2 |
| R1 | code-review | LOW | 머니 컨트롤러 권한 변경 diff 에 무관한 재정렬 hunk 가 섞였다 (money-admin.controller.ts 여러 곳) | 기각 (이연) | HEAD 가 prettier 비정렬인 파일이다 (편차 32 · 서식 기록). 되돌리려면 파일 재작성이 필요해 이 구간에서 하지 않았다. 다음 구간 정리 대상 | 1 |
| R1 | typescript | LOW | OFF 에서 `getCorpHistory` 쿼리에 `tn_agency` 조인이 항상 붙는다 (money.repository.ts:364) | 기각 | 조인 키 `tn_agency.agc_id` 는 1:1 이라 행 수가 같다. 쿼리 형상만 다르고 응답 불변은 검사 #10 (행 대조) 이 보는 대상이다. 조건부 조인은 동적 빌더 정리와 함께 이연 | 1 |

| R2 | code-review · typescript | MEDIUM | 분석 기본 (`basicList`) · 처방 (`prescribeList`) 단건에 영업 판정이 없다. 집계 · 내보내기만 `guardSalesOdd` 로 막혀 같은 oddId 키가 라우트마다 다르다 (analyze-admin.controller.ts:94 · :164 · :96 · :166) | 흡수 | `guardSalesOdd` 를 두 라우트에 붙였다 (`guardCorp` 뒤). 시험 「타 기업 거부 (기본 · 처방)」 (scope-handlers.spec.ts): 수정 전 붉음 (거부 없이 서비스 호출), 수정 뒤 녹색. 같은 describe 의 「담당 기업의 기본 · 처방은 허용」 녹색 | 2 |
| R2 | code-review | MEDIUM | 환불 목록 (`getRefundList`) · 단건 (`getRefundDetail`) 에 범위 판정이 없고 기록에도 없다. 클래스 `@Roles` 가 영업을 허용한다 (money-admin.controller.ts:712 · :728) | 흡수 (목록 · 단건 · 질의 대조는 이연) | 목록 · count 가 같은 `scopeCondition` 절을 받는다 (`getSalesScope`, OFF 는 null). 단건은 `assertSalesOwnedAgc` + `getRefundOwnerAgcId` (환불 회원의 agc_id). 시험 4건 (「ON: 영업 환불 목록」 · 「OFF: 환불 목록」 · 「범위 인자 동일: 환불」 · 「타 기업 거부 (환불 단건)」) 과 허용 1건: 수정 전 4건 붉음, 수정 뒤 녹색. 행 수 대조 (격리 MySQL) 는 아직 없다 — 백로그 MEDIUM 행 (R2) | 6 |
| R2 | code-review | MEDIUM | 노트에 호스트 절대경로가 있다 (notes:340) · 커밋 검사 문자열 리터럴이 같은 검사에 걸린다 (notes:475) | 흡수 | 340행은 `M6B_SCHEMA_DIR=<스키마 덤프 디렉터리>` 자리 표지로, 475행은 검사 문자열을 풀어 썼다. 노트의 홈 경로 문자열 수 0 (`grep -c`). 검사 #14 는 노트를 대상에 넣지 않아 커밋 전 검사가 이 행을 잡는 자리다 | 6 |
| R2 | code-review | LOW | 이행표 「판정하지 않는 것」 이 분석 집계 · 내보내기를 밖으로 적었고 basic · prescribe 는 판정 없음이다. 백로그 MEDIUM 행은 분석 질의에 범위 절이 있는 것처럼 적었다 (scope-transition-table.md 「판정하지 않는 것」 · codex-findings-backlog.md 2026-10-21 MEDIUM 행) | 흡수 (문서 정정) | 이행표 절을 실제 판정 상태로 다시 적었다 (판정이 붙은 형제 경로 · 분석 질의에 범위 절이 없고 단건 판정으로 덮는다 · 판정 없는 형제 경로 · 회원 지정 적립 · processRefund 는 백로그 HIGH). 백로그는 append 로 정정 행을 더했다 (기존 행은 그대로). 검사 #4 · #5 · #7 은 I 행만 세므로 영향 없음 (검사 녹색) | 2 |
| R2 | code-review · typescript | LOW | 조직 서비스 클래스 JSDoc 이 `OrgWriteAdmin` 타입 별칭에 붙었다 (organization.service.ts:32-37). R1 이 같은 종류를 머니 · 분석에서만 흡수했다 | 흡수 | 타입 별칭을 JSDoc 앞으로 옮기고 설명 한 줄을 달았다. 타입 검사 기준선 대조 (오류 줄 23 = 기준선 23, `diff` 빈 결과) | 1 |
| R2 | security · typescript | MEDIUM | 소속 쓰기 (`assignOrg` · `setOrgManager`) 는 회원 → 기업 순서로, 머니 지급 (`createGrantSingle` · `grantMoney` 등) 은 기업 → 회원 순서로 잠근다. 강제 ON 에서 1213 교착이 나면 머니 지급이 500 을 받는다 (fc-agency.service.ts:696-697 · :751-752, money.service.ts:1592 · :1731) | 미흡수 → 백로그 (MEDIUM 1행) | 상수 OFF 에서는 잠금 블록이 돌지 않는다 (`if (SCOPE_ENFORCE_SALES_CORP && tx)`, fc-agency.service.ts:690 부근). 교착은 롤백으로 끝나 손상이 없다. 순서 통일은 기업 번호를 잠금 앞에서 읽고 회원 소속을 잠금 뒤에 재확인하는 재설계라 계획 변경이다. R1 HIGH 행 (조직 삭제 대 소속 쓰기) 과 같은 축이다 | 1 |
| R2 | security · typescript | LOW · MEDIUM | 조직 쓰기 트랜잭션이 잠금 앞에서 일관 읽기로 스냅샷을 고정한다 (organization.service.ts:92 · 101). 잠금 뒤 하위 조회가 낡은 값을 본다 | 흡수 | 소속 기업 번호는 불변이라 (`agcId` 를 바꾸는 쓰기 0건, 검사: `.set({` 블록의 `agcId` 0건) 트랜잭션 밖에서 읽는다. 잠금 `lockAgencySagId` 가 트랜잭션 안의 첫 작업이다. 시험 「삭제: 기업 행 잠금이 트랜잭션 안의 첫 작업이다」 (organization.service.sales-write.spec.ts): 수정 전 붉음 (`read-tx` 가 `lock` 보다 앞섬, 부정 사본으로 확인), 수정 뒤 녹색. 격리 MySQL 두 연결 경합 시험은 아직 없다 (미검증, 호출 순서 시험으로 대신) | 6 |
| R2 | typescript | LOW | 시험 가능한 식별자 타입이 세 곳에 따로 있다 (money-admin `SalesScopeRequest`, organization `OrgWriteAdmin`, analyze 인라인 판정). analyze 상세의 인라인 판정은 집계와 13줄 중복이다 (money-admin.controller.ts:77 · organization.service.ts:37 · analyze-admin.controller.ts:119-131) | 부분 흡수 | 머니 컨트롤러는 `AdminRequest` 를 쓴다 (로컬 타입 삭제). 조직은 `OrgWriteAdmin = AdminIdentity \| undefined`. 분석은 `guardSalesRow(req, sagIdOf)` 한 헬퍼로 집계 · 내보내기 · 기본 · 처방 · 상세가 같이 쓴다. 남김: 분석 컨트롤러는 `Request` 타입 유지 (`guardCorp` 의 `agcId: number` 가 신원 타입의 선택 칸과 안 맞는다). sagId 판정 (조직 `Number()` · 머니 · 분석 원값 `String()`) 의 차이는 그대로 둔다 — 판정 함수 입력을 바꾸는 일이라 이 구간 밖. 타입 검사 기준선과 같다 | 1 |
| R2 | typescript | LOW | 경합 시험 「소속 경합 담당 재배정」 이 중간 단언 실패 때 관리자 트랜잭션을 닫지 않는다. 잠금이 남아 뒤 시험과 afterAll 이 줄줄이 붉어진다 (agency-fc.race.itspec.ts:156-183) | 흡수 | 관리자 트랜잭션을 `try/finally` 로 감싸 실패 때 롤백 · 종료하고, 대기 중인 쓰기 트랜잭션을 기다린다. 잠금 신호 (`lockedSignal`) 는 트랜잭션과 `Promise.race` 로 겨룬다. 반사실 (격리 MySQL, 주입 실패 사본): 수정 전 사본은 afterAll 이 20초 훅 타임아웃으로 붉고, 수정 뒤 사본은 주입한 시험 한 건만 붉고 afterAll 은 녹색 | 8 |

### 검사 결과 (R2 구간 끝, 커밋 없음)

- 게이트 본문 전체 (`bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`) 는 검사 #9 에서 멈춘다. 커밋 전이라 의도한 붉음이다 (첫 비시험 서버 소스 커밋이 아직 없다). 같은 본문에서 검사 #9 · #10 만 뺀 사본은 `GATES_OK m6b` 다 (격리 MySQL 과 시험 이름 목록 #8 포함, 검사 #8 의 이름 목록에 R2 시험 3개를 더했다).
- 격리 MySQL (`m6b-iso-` 접두, 합성 시드 적재) 에서 itspec 세 개 (조직 이동 경합 · 소속 경합 · 머니 목록 범위) 11 건 녹색.
- 응답 불변 probe (검사 #10 과 같은 명령, GK_BUILD=after, 워킹트리): 행 6336 개. 커밋본 `rows-after-m6b.txt` 와 다른 행은 `GET /admin/treat-monitor/overview` (admin-super · admin-general) 두 행의 본문 길이뿐이다 (편차 33, 앞 구간과 같은 자리). 상태 0 행 0 개, 쓰기 행 6 개.
- 타입 검사: 서버 `tsc -p tsconfig.json` 오류 줄 23 = 기준선 23 (`diff` 의 오류 줄 빈 결과).
- jest (`--testPathIgnorePatterns itspec`, auth/access-control · auth/access-contract · money · agency · analyze · fc/agency · subscribe): 67 suite 중 66 녹색 · 729 시험 중 728 녹색. 붉은 1건은 기준선에 있는 `analyze-insight-route.spec.ts` 의 ENOENT (워크트리 픽스처 부재, R1 과 같다).
- 반사실은 각 행의 증거 칸에 적었다. 시험 본문과 원본 파일은 그대로 두고 수정 전 로직을 복사본에 넣어 측정했다 (복사본은 측정 뒤 지웠다).
- 미검증: 환불 목록 · 기업 조직 쓰기의 격리 MySQL 두 연결 경합 (호출 순서 시험으로 대신) · 환불 · 분석 질의의 타 기업 행 0 대조 (백로그 MEDIUM 2행) · 소속 쓰기와 머니 지급의 교착 (백로그 MEDIUM 1행) · 운영 동일 환경 1회 재생 (복제본 생성 사람 승인) · 편차 33 (시각 의존 본문).
- 백로그: 위 세 행 (교착 · 환불 질의 · 분석 정정) 을 append 했다. HIGH 흡수 0 · HIGH 미해소 0 · CRITICAL 0 이라 라운드 루프는 여기서 끝날 수 있다 (상한 3 중 2 소진).

### 검사 결과 (R1 구간 끝)

- 게이트 사이드카 전체 (`bash .claude/PRPs/plans/admin-rbac-m6b.gates.sh`, 격리 env 와 함께): 검사 #1–#8 · #11 까지 통과 뒤 검사 #9 에서 멈춘다. 실패 지점은 `FIRST=$(git log … -- apps/api/src ':!*.spec.ts' ':!*.itspec.ts' | head -1)` 가 빈 값이라 `[ -n "$FIRST" ]` 에서 멈추는 것이다 (커밋 전 상태, 편차 32 와 같은 이유). 검사 #10 · #9 는 커밋 뒤에만 본다.
- 같은 게이트에서 검사 #9 · #10 만 뺀 사본: 검사 #1–#8 · #11–#17 녹색 (`GATES_OK m6b`). 검사 #13 (백로그 append 전용) 은 백로그 추가 뒤 다시 돌려 녹색이다 (`GATES_OK m6b`).
- 응답 불변 probe (검사 #10 과 같은 명령, 최종 워킹트리 빌드, 격리 스택): 행 6336 개 중 6334 개가 커밋본 `rows-after-m6b.txt` 와 같다. 다른 두 행은 `GET /admin/treat-monitor/overview` (admin-super · admin-general) 의 본문 길이 · 해시로, 편차 33 (시각에 따라 바뀌는 본문) 과 같은 자리다. 상태 · 헤더 · 쓰기 행 6개는 같다.
- 미검증: 운영 동일 환경 1회 재생 (복제본 생성 사람 승인) · R5 영업 전사 열람 업무 확인 · 소속 쓰기와 조직 삭제 교착 (백로그 HIGH 1) · 머니 쓰기 판정의 경합 창 (백로그 HIGH 2) · 시각 의존 본문 (편차 33).

## 커밋 단계 (2026-10-21 · 커밋 완료)

- 서버 커밋 (브랜치 feat/admin-rbac, 기준 b2c476a8 위, 논리 단위 3개): `ef82c4bc` feat(agency) · `4ec66c4a` feat(money, 분석 포함) · `ee6a6b74` feat(auth).
- 서식 출처 점검 (정책 §5-7): 워킹트리 수정 16 파일은 모두 `git show HEAD:<f> | prettier` 결과와 달라 서식만 바뀐 파일이 없다. 전부 8~12차 구간에 기록된 이 마일스톤의 구현 변경이다. 신규 3 파일은 `prettier --check` 통과. 커밋 전 `git diff --cached` 의 홈 경로 문자열 수 0.
- 부모 커밋은 셋으로 나눴다.
  1. `b781a1c` test(admin-rbac): 측정 산출 · probe 행 파일 · 기준선 · 게이트 본문. 서브모듈 포인터는 올리지 않아 b2c476a8 을 그대로 가리킨다.
  2. 다음 부모 커밋: 서브모듈 포인터를 `ee6a6b74` 로 올리고 노트 · 백로그 · implement receipt 를 싣는다. 산출을 처음 넣은 커밋의 포인터가 측정 sha 와 같아야 하므로 (검사 #9) 한 커밋에 묶으면 검사 #9 가 붉어진다.
- 검사 #9 재확인 (커밋 뒤): 통과. 측정 sha `b2c476a8` 은 첫 비시험 서버 소스 커밋 `ef82c4bc` 의 조상이고 그 커밋이 아니다. 산출 커밋 `b781a1c` 의 서브모듈 포인터가 측정 sha 와 같다. `SCOPE_ENFORCE_SALES_CORP` 는 한 곳에서 `false` 한 줄이다.
- 검사 #10 은 커밋 뒤 재실행하지 않았다. 착수 행 파일 (10차 시각) 과 지금 시각의 `GET /admin/treat-monitor/overview` 본문이 다를 수 있다 (편차 33). 행 파일 재측정 여부는 리뷰 판정이 정한다.
- 구현 보고서 (Phase 5, `.claude/PRPs/reports/` 아래) 는 쓰지 않았다. 서브에이전트 작업에서 보고서 파일을 만들지 않는다. 필요하면 부모가 작성한다.
- 미검증 (커밋 뒤에도 남는다): 운영 동일 환경 1회 재생 (복제본 생성 사람 승인) · R5 영업 전사 열람 업무 확인 · 백로그 HIGH 세 건 (소속 쓰기 대 조직 삭제 잠금 순서, 회원 지정 적립 · 환불의 행별 소유 판정, 머니 쓰기 판정의 경합 창) 은 M9 계획 반영 대상.

## 최종 리뷰

최종 리뷰 라운드 1/3 (fable · codex). 흡수 · 기각 판정과 증거는 아래 표에 한 줄씩 적는다. 판정 근거의 정본은 이 표다.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable | HIGH | 응답 불변 검사 #10 이 커밋 상태에서 증명되지 않는다. 「최종 빌드」 행 파일이 서버 HEAD 가 아닌 워킹트리 산출이고, 벽시계 창 본문 (`/admin/treat-monitor/overview`) 이 실행마다 바뀌어 cmp 가 첫 행 파일에서 멈춘다 | 흡수 | probe 에 시각 창 규칙 (`TIME_WINDOW_PATHS`, 키 구조만 해시 · 편차 33) 을 넣고 행 파일 셋 (착수 · 재실행 · 최종) 을 서버 HEAD `24e46c38` 에서 다시 쟀다. 세 파일 바이트 같음. 요약 `after_sha` 는 HEAD 전체다. 검사 #10 이 `before_sha` · `after_sha` 결속과 `apps` 워킹트리 청결을 본다 | 2 |
| R1 | fable | HIGH | 백로그 RESOLVED-IN 줄이 구현과 어긋난다 (1611 · 1628 · 1633 · 1632). 1611 · 1628 은 `judgeSingleScope` 호출처가 0 이다 | 흡수 (1611 · 1628 철회 · 다음 계획 HIGH 로 이월 · 1633 · 1632 는 이번 구간 실측으로 재확인 줄 추가) | 백로그 2026-10-21 append 4행 (기존 줄은 그대로). `scope-monitor.ts:143` 의 호출처 0 (spec 제외). 단건은 편차 14 에 따라 `judgeRowScope` | 2 |
| R1 | fable | MEDIUM | 검사 #4 의 조인 열 검사가 토큰 없는 행에서 공허하다. I4–I8 조인 칸 열두 개가 「(미검증)」 산문이다 | 흡수 | 열두 칸을 코드 사슬로 채우고 스키마 덤프 (`02-schema.sql`) 로 열 이름을 대조했다 (토큰 16종 모두 ok). 검사 #4 에 I 행마다 `tn_*.열` 1개 이상 · I9 는 `-` 만 · 「(미검증)」 조인 칸 0 건을 더했다. 음성: I1 조인 칸을 `-` 로 바꾼 사본에서 rc 1 (관측) | 6 |
| R1 | fable | MEDIUM | 범위 선언 경로가 없다. 생성기가 전 행을 `scopeSource: 'auto'` 로 박고 골든 목록은 손으로 늘린다. 골든 자체는 검사되지 않는다 | 부분 흡수 | 검사 #17 에 골든 = 레지스트리 자동 칸 sales · corp 행 (103 = 103, 초과 0 · 누락 0) 을 더했다. 음성: 한 줄 뺀 사본 (누락 1) · 한 줄 더한 사본 (초과 1) 에서 rc 1 (관측). 생성기가 이행표를 읽는 설계는 백로그 MEDIUM 행으로 이연 | 2 |
| R1 | fable | LOW | 계획 Files to Change 표와 커밋 범위가 어긋난다 (조직 라우트 경합 시험 파일이 UPDATE 로 안 바뀜 · 신규 spec · golden 세 파일이 표에 없다) | 흡수 (노트 편차 기록) | 편차 37. 두 연결 경합 시험은 백로그 HIGH 「소속 쓰기 대 조직 삭제」 행과 함께 다음 계획에 둔다 (미검증) | 1 |
| R1 | codex | HIGH | 머니 쓰기의 범위 검사와 변경 사이 권한 우회. 강제 ON 에서 판정 뒤 담당이 바뀌면 기업 충전 · 지급 · 개별 지급 · 엑셀 지급이 타 기업 머니를 커밋한다 | 부분 흡수 (4종 흡수 · 결제 취소는 백로그 HIGH 이연) | 서버 커밋 `24e46c38`. 잠금 뒤 같은 트랜잭션에서 담당을 재판정하는 `assertSalesOwnerAfterLock` 을 4곳에 호출한다 (검사 #8 이 호출 행 4 를 센다). 시험 4건 (「잠금 뒤 판정」). 반사실: 가드를 무력화한 사본에서 두 시험이 붉다. 결제 취소는 PG 호출이 트랜잭션 밖이라 흐름 설계가 필요해 백로그 HIGH (`money.service.ts:2316`) | 1 |
| R1 | codex | HIGH | 응답 불변 검사가 유효한 범위 신원으로 실행되지 않는다. 영업 · 기업 · 조직관리자 머니 목록이 전부 401 이라 인증 뒤 회귀를 가린다 | 흡수 | probe 범위 신원을 `identities-scope.json` 의 시드 신원 (sales-A · corp-A · org-A1) 으로 바꿨다 (편차 34). 최종 분포: 영업 200 ×51 · 기업 200 ×40 · 조직관리자 200 ×36 (앞 행 파일은 전부 401). 행 수 9 신원 × 등록 경로 704 는 그대로 | 6 |
| R1 | codex | MEDIUM | 새 잠금 순서가 조직 삭제 · 머니 지급과 교착한다 | 기각 (중복) | 백로그 기존 행 (2026-10-21 HIGH 「소속 쓰기 대 조직 삭제 잠금 순서」 · MEDIUM 「소속 쓰기 대 머니 지급 교착」). 같은 자리를 다시 적지 않았다 | 1 |
| R1 | codex | MEDIUM | 내보내기 본문 변경을 불변 검사가 전부 지운다 (`bin` 치환 · content-length 제거) | 흡수 | 엑셀은 시트 이름과 셀 값으로 해시한다 (날짜시각 칸은 값째 뺀다 · 편차 35). 그 밖의 이진은 바이트 해시이고 content-length 는 실제 값이다. 착수 빌드 재실행에서 엑셀 분 단위 칸이 흔들려 날짜 칸 규칙을 넓혔다 (편차 35) | 6 |
| R1 | codex | MEDIUM | would-deny 측정이 실제 서버 판정 함수를 검증하지 않는다 (별도 복사본, 입력 `SYNORGA1` 과 `/SYNORGA1/` 가 어긋남) | 흡수 | 측정 하네스가 측정 트리 (착수 sha `b2c476a8` 의 git archive 빌드) 의 `scope-monitor.ts` 를 ts-node 로 부른다 (편차 36). 재측정 132 셀 전부 기존 산출과 같다 (차이 0). 바뀐 것은 harness sha 뿐이다 | 6 |

### 편차 (최종 R1 구간)

34. **응답 불변 probe 의 범위 신원**: 앞 probe 의 `sagId 3 · agcId 1 · memId 43` 은 합성 시드에 없어 세 신원이 전부 401 이었다. 지금은 `identities-scope.json` 의 sales-A · corp-A · org-A1 claims 를 쓴다 (admId 는 측정 하네스와 같은 10 + 순번). 신원 이름 9종 · 행 수 6336 은 같다. 행 파일 셋을 다시 만들었다.
35. **엑셀 본문 해시와 날짜 칸**: 엑셀 (xlsx) 은 시트 이름과 셀 값으로 해시한다. zip 안의 생성 시각 때문에 바이트 해시는 흔들린다. 날짜시각 문자열 칸 (초 단위 · 분 단위 포함, 지갑 엑셀 `substring(0,16)`) 은 값째 뺀다 (편차 30 의 규칙을 엑셀 칸까지 넓혔다). 파싱이 안 되면 바이트 해시로 떨어진다. 그 밖의 이진은 바이트 해시이고 content-length 는 실제 값이다 (엑셀에서만 `<volatile>`).
36. **측정 판정 함수**: 측정 하네스는 판정 함수를 복사하지 않고 측정 트리의 `scope-monitor.ts` 를 ts-node 로 부른다. 측정 트리는 착수 sha (`b2c476a8`) 의 git archive 빌드 (`gk-m6b-before`, 측정 sha 와 같은 코드) 다. `scope-measure.sh` 는 현재 HEAD 가 아니라 이 트리로 서버를 띄운다. 재측정 결과는 기존 산출과 같다 (132 셀 차이 0, `harness_sha256` 만 바뀜).
37. **계획 Files 표와 커밋 범위**: 계획 Files 표의 `organization.race.itspec.ts` (UPDATE · 조직 이동 · 조직 라우트 쓰기 경합 시험 추가) 는 이 구간에 바뀌지 않았다. 호출 순서 시험 (`organization.service.sales-write.spec.ts`) 과 금지 칸 시험으로 대신한다 (노트 R2 와 같다). 표에 없는 `money-scope.itspec.ts` · `organization.service.sales-write.spec.ts` · `access-contract.scope-golden.ts` 는 CREATE 로 표에 없다. 공시된 (미검증) 항목이고 범위 밖 변경은 아니다. 두 연결 경합 시험은 백로그 HIGH 행과 함께 다음 계획에 둔다.

### R1 구간 끝 검사 결과

- 게이트 본문 실행 1 (격리 env · 격리 MySQL `m6b-iso-`): 검사 #1–#9 통과, #10 에서 멈춘다. 행 파일 cmp 가 `GET /admin/treat-monitor/overview` 의 content-length (2391 ↔ 2390) 에서 다르다. 창 수치가 길이를 바꾸는 것이라 창 경로의 content-length 를 `<volatile>` 로 바꿨다 (편차 33 확장).
- probe 재생 (수정 뒤, 같은 격리 스택): 착수 빌드 · 같은 빌드 대조군 · 최종 빌드 (서버 HEAD `24e46c38`) 의 행 6336 개가 세 번 모두 바이트로 같다. 쓰기 행 6, status=0 행 0.
- 게이트 본문 실행 2: 검사 #1–#14 통과 (#10 은 새 행 파일과 요약 sha 결속으로 통과). 검사 #15 (plan-kit lint) 가 계획서가 아닌 게이트 주석의 `# 기대 (R1 추가):` 표기 두 줄 때문에 PL2 로 멈춘다. 표기를 `# 기대:` 로 고쳤다 (검사 행 의미 불변).
- 고친 뒤 직접 재실행: 검사 #15 (lint) rc 0 · 검사 #16 (장기 관측 문구) 0 건 · 검사 #17 (골든 = 레지스트리 자동 칸 sales · corp 103 = 103, 범위 선언 시험 33 건 녹색). 검사 #8 · #9 · #10 · #14 는 실행 2 에서 통과했고, 그 뒤 주석 줄만 고쳤다. 커밋 뒤 전체 본문을 다시 돌린다 (검사 #9 커밋 뒤 재확인).
- 음성 관측 (사본, 워킹트리 밖): I1 조인 칸을 `-` 로 바꾼 사본은 조인 검사 rc 1. 골든 한 줄 뺀 사본은 누락 1 · rc 1, 한 줄 더한 사본은 초과 1 · rc 1. 머니 호출 행을 하나 뺀 사본은 호출 행 3 개가 되어 검사 #8 의 기대 4 와 다르다 (count 는 실패 쪽).
- 측정 재실행 (측정 트리 `b2c476a8` · 판정 함수는 그 트리의 `scope-monitor.ts`): 132 셀 전부 기존 산출과 같다 (차이 0). 검사 #5 ok 셀 132.
- 커밋 뒤 (부모 `ae9ebf5f`, 서버 `24e46c38`) 게이트 본문 전체를 한 번 더 돌렸다: `GATES_OK m6b`, EXIT 0. 검사 #9 (측정 sha 조상 · 산출 커밋 포인터 · 상수 OFF) 를 이 실행에서 다시 확인했다.
- 500 응답 관찰 (기존 상태, 착수 빌드와 최종 빌드에 같다): 관리자 · 영업 신원 일부 엑셀 · PDF 경로의 500 은 합성 데이터의 빈 칸에서 난다 (행 파일 그대로).

### R1 구간 미검증 (커밋 뒤에도 남는다)

- 운영 동일 환경 1회 재생 (복제본 생성 사람 승인, UI8) · R5 영업 전사 열람 업무 확인.
- 결제 취소의 담당 판정 경합 (백로그 HIGH) · 소속 쓰기와 조직 삭제 교착 (백로그 HIGH) · 환불 · 분석 질의 타 기업 행 0 대조 (백로그 MEDIUM).
- 머니 쓰기 4종의 격리 MySQL 두 연결 경합 시험은 없다. 잠금 뒤 판정은 호출 순서 시험과 반사실로만 본다.
- 편차 33 (시각 창 본문) 은 구조 대조만 한다. 값 대조는 없다. 창 경로의 content-length 도 값 대신 `<volatile>` 로 본다 (게이트 실행 중 한 줄이 흔들려 확인, 편차 33).
- 행 파일의 500 응답 (관리자 · 영업 · 기업 신원 일부 경로) 은 착수 빌드와 최종 빌드에 같이 있다 (기존 상태, 이번 구간 변경 아님).

## 최종 리뷰 라운드 2 (fable · codex)

최종 리뷰 라운드 2/3. 지적 4건 (fable 2 · codex 1 · 리뷰어 밖 자체 발견 1). 표가 판정 근거의 정본이다.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R2 | fable | MEDIUM | 범위 선언 강제 (검사 #17) 가 동어반복이다. 골든이 레지스트리 자동 칸의 거울이라 신규 영업 · 기업 행을 골든 한 줄로 덮을 수 있다. 백로그 1630 RESOLVED-IN 이 과장이다 | 흡수 (골든을 동결 목록으로 바꿨다) | 서버 `145bd21d` (골든 머리 주석) · 메타 `scope-golden-frozen-m6b.txt` (103 행). 검사 #17 은 골든 키 집합 = 동결 파일 (차이 0) · 누락 0 · 초과 0. 관측 (사본): 골든에 한 줄 더함 → 동결 차이 1 · rc 1 · 한 줄 뺌 → 누락 1 · 동결 차이 1 · rc 1 · 레지스트리 사본에 sales 자동 행 더함 → 누락 1 · rc 1 · 그 행을 골든에도 더함 → 동결 차이 1 · rc 1. 런타임 부팅 단언 (warn) 은 골든 밖 행을 「범위 선언 누락」 으로 센다 (spec 「범위 선언 누락 1건」 두 시험). 백로그에 1630 재정정 줄 append (런타임은 warn 유지, fail 전환은 M9) | 2 |
| R2 | fable | LOW | 검사 #10 의 최종 빌드 결속이 워킹트리 `apps` 만 본다. 빌드 입력인 `drizzle` · `libs` 가 빠진다 | 흡수 | 게이트 청결 검사를 `status --porcelain -- apps drizzle libs` 로 넓혔다 (검사 #10 주석도 고쳤다). 음성 관측은 아래 「검사 결과 (R2 구간)」 참조 | 6 |
| R2 | codex | HIGH | 소속 배정 (`assignOrg`) 이 회원의 현재 소속 조직을 잠그지 않는다. 판정 뒤 조직 이동이 끼면 범위 밖 조직에서 옮겨 온 회원이 배정된다 | 흡수 | 서버 `ce700329`: 잠금 순서를 회원 → 담당 기업 → 현재 소속 조직 → 목적지 조직으로 고치고 판정을 잠금 뒤에 둔다 (`setOrgManager` 와 같은 순서). 단위 시험 「잠금 순서」 신설 (`scope-enforce.spec.ts`, 11 건 녹색). 반사실: `lockCurrentOrgsOfMembers` 호출 한 줄을 뺀 사본에서 「잠금 순서」 시험이 붉다 (관측). 경합 itspec 세 시나리오 (`agency-fc.race.itspec.ts`) 에 같은 줄을 더했고, 격리 MySQL 실행은 게이트 검사 #8 이 본다. 조직 이동은 `organization.repository.ts:434 · 471` 의 FOR UPDATE 가 잠근다 | 1 |
| R2 | fable | LOW | 분석 형제 경로 4개 (basic · aggregate · prescribe · download) 에 `guardSalesOdd` 가 붙었으나 측정 셀 밖이다. ON 영향 (영업 would-deny) 에 측정값이 없다 | 부분 흡수 (공시 · 측정은 M9 입력) | 측정 셀은 이번 구간에 넓히지 않았다. OFF 불변은 등록 경로 704 × 신원 9 로 이미 증명됐다 (행 파일 cmp). M9 판단 입력은 백로그 LOW 행 (분석 형제 4 경로: 미측정 · ON 시 영업은 소유 기업 담당만, 또는 I8 계열 셀 추가). 근거 `analyze-admin.controller.ts:117 · 159 · 178 · 197` | 2 |
| R2 | 자체 (리뷰어 미지적) | HIGH | 조직 권한 업로드 (`orgPermissionUpload`, 이행표 AO4) 가 판정 뒤 무잠금 · 무트랜잭션으로 소속 · 조직관리자 쓰기를 한다. 기업 조직관리자 신원에서 판정 뒤 조직 이동이 끼면 범위 밖 회원이 배정되고, 중간 실패 시 부분 반영이 남는다 | 이연 (백로그 HIGH, 다음 계획 반영 대상) | `fc-agency.service.ts:1401` (판정 1440 · 쓰기 1448 · 1451 · 1460~1463 에 `tx` 가 없다). 이행표 AO4 는 「미구현 범위」 다. 같은 계열 R2 HIGH 의 처방을 이 경로에 적용해야 하지만 이번 계획 범위 밖이다 | 1 |

### 라운드 2 처리 요약

- 흡수 3건 (MEDIUM 1 · LOW 1 · HIGH 1), 부분 흡수 1건 (LOW, 공시). 기각 0건.
- 리뷰어 HIGH 는 흡수 1건이다. 자체 발견 HIGH 1건은 이번 계획 범위 밖이라 백로그에 `→ 다음 계획 반영 대상` 으로 넘겼다. 계획서 본문은 고치지 않았다.
- 백로그 append (기존 줄은 그대로): 1630 재정정 RESOLVED-IN 1 · 자체 발견 HIGH 1 · 분석 형제 LOW 1.
- 서버 커밋: `ce700329` (fix(fc) 잠금 순서) · `145bd21d` (docs(access-contract) 골든 동결 표지). 서버 HEAD 는 `145bd21d` 다.
- 응답 불변 probe (최종 빌드 `145bd21d`, 격리 DB · 새 비밀 파일): 행 6336 · 쓰기 6 · status=0 행 0. 행 파일이 커밋본 `rows-after-m6b.txt` 와 바이트로 같다. 요약 `probe-summary-m6b.txt` 의 `after_sha` 를 `145bd21d` 로 갱신했다.

### 검사 결과 (R2 구간)

- 타입 검사 (`tsc --noEmit`): 오류 23줄, 기준선 (`tsc-baseline.txt`) 과 정렬 후 같다. 바뀐 파일의 오류 0.
- 단위 jest (access-contract · access-control · fc/agency, 25 suite): 340 건 녹색.
- 「잠금 순서」 시험 반사실: 호출 한 줄을 뺀 사본에서 붉다 (관측). `scope-enforce.spec.ts` 11 건 녹색.
- 골든 동결 음성 (사본, 워킹트리 밖): 위 MEDIUM 행의 네 관측.
- 전체 게이트 본문 (`admin-rbac-m6b.gates.sh`, 격리 MySQL `m6b-iso-` · 합성 시드 적재 · 격리 비밀 파일은 세션 scratchpad 에만 두었다): `GATES_OK m6b`, EXIT 0. 검사 #1–#17 통과 (#10 은 probe 세 번 재생 뒤 통과, #8 의 격리 MySQL 경합 시험 세 파일 녹색).
- 검사 #10 청결 검사 음성 관측: 서버 `drizzle/schema.ts` 에 한 줄을 더한 사본에서 `git status --porcelain -- apps drizzle libs` 가 한 줄을 내 `[ -z ]` 가 거짓이다 (게이트 붉음). 원상 복구 뒤 서버 트리 청결 (`git status` 0 줄). 이전 pathspec `-- apps` 로는 이 편집이 보이지 않는다.
- 검사 #9 커밋 뒤 재확인 (부모 `9871d55` 위, 검사 #9 블록을 떼어 실행): 통과. 측정 sha · 산출 커밋 · 상수 OFF 는 이번 커밋이 바꾸지 않았다 (서버 커밋 두 개는 측정 뒤 비시험 소스 커밋이고, 산출 커밋의 서브모듈 포인터는 그대로다).

## 최종 리뷰 라운드 3 (fable · codex)

최종 리뷰 라운드 3/3 (상한). 지적 5건 (fable 4 · codex 1). 표가 판정 근거의 정본이다. 흡수 0 HIGH 라 루프는 여기서 끝난다. 흡수하지 못한 MEDIUM 1건은 백로그에 다음 계획 입력으로 넘겼다.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R3 | fable | MEDIUM | 영업 범위 거부 403 이 M7 계약 (E0607 · reason 'scope') 을 타지 않는다. 머니 · 분석 · 기업 조직 · 소속 쓰기의 범위 사유 9 곳이 plain ForbiddenException 이라 응답에 code 가 없다. 관리자 프런트는 code 로만 분류한다 | 흡수 | 서버 `36009c72` (fix(access-control)). 범위 사유 9 곳을 `permissionDenied('scope', 현행 문구)` 로 바꿨다. 예외 타입 · 문구는 같다. 소속 쓰기의 영업 거부는 기업용 문구 (CORP_FORBIDDEN.message) 를 유지하고 code 만 E0607 이 된다. 강제 상수 OFF 라 응답 불변. 관측: 바뀌지 않은 plain 403 중 범위 사유는 기업 쪽 현행 판정 2곳 (기업 지급 내역 조회 · 기업 지급 범위, money-admin 164 · money.service 1597) 이다. 영업 신원과 무관하고 응답이 현행이라 이번 구간에서 바꾸지 않았다 (M9 에서 함께 정한다). 검증: 타입 검사 기준선 정렬 같음 · prettier 검사 통과 · 단위 jest 579 녹색 (1 건은 기준선 실패, 아래 검사 결과). 응답 본문 code 의 격리 서버 실호출은 하지 않았다 (미검증, 아래) | 2 |
| R3 | fable | MEDIUM | I1 getAllAgencyList 의 조직관리자 셀 (org-A1 · org-A2) would_deny 1 씩이 구조적이다. 기업 단위 행에 orgPath 가 없어 조직관리자 판정이 항상 거부하고, 검사 #5 는 이것을 누출과 구별하지 못한다 | 부분 흡수 (공시 · 게이트 차단선 흡수, 판정 키 설계는 백로그 MEDIUM 으로 이연) | 이행표 I1 「측정 판정」 칸에 구조적 0 을 적고, 이행표 끝에 「측정 공시 — 구조적 0」 절을 더했다. 검사 #5 는 조직관리자 셀 중 기대 200 · 행 있음 · 소유 0 · 허용 0 인 셀 집합이 선언 목록 (org-A1 · org-A2) 과 정확히 같아야 한다. 반사실: I3 assignOrg 의 org-A2 셀을 소유 0 · 허용 0 으로 바꾼 사본에서 검사 #5 가 rc 1 (「구조적 0 셀이 선언과 다르다」) 이고, 원본은 rc 0 이다. 기업 행을 조직관리자에게 어떻게 판정할지 (판정 키 설계) 는 미결이며 M9 판단 표 입력으로 넘긴다 (백로그 MEDIUM 행) | 6 |
| R3 | fable | LOW | 이행표 I8 「강제 경로」 칸이 없는 목록 · 집계 경로 (listScopeCondition) 를 적었고, 검사 #7 은 칸의 첫 식별자만 본다 | 흡수 | 칸을 `judgeRowScope(단건 · 집계 · 내보내기 모두 행 소유 판정 · 목록 절 없음)` 으로 고쳤다. 측정 값 불변. 검사 #7 의 배선 표지 (첫 식별자 judgeRowScope 가 분석 컨트롤러에 있다) 가 통과한다. 관측: 분석 컨트롤러에 listScopeCondition 호출은 없다 (grep 0 건) | 2 |
| R3 | fable | LOW | 착수 빌드 트리 캐시 (`gk-m6b-before`) 를 펼친 sha 확인 없이 재사용한다. 검사 #5 · #10 의 착수 빌드 출처가 검증되지 않는다 | 흡수 | 응답 불변 probe 가 펼친 트리에 `.gk-sha` 표지 (착수 sha 전체) 를 쓰고, 표지가 다르거나 없으면 펼치고 다시 빌드한다. 이 실행에서 표지 없던 기존 트리는 다시 펼쳐 빌드되었고 (표지 생성 확인), 검사 #10 의 세 행 파일 cmp 가 통과했다 | 8 |
| R3 | codex | MEDIUM | Jest 스위트 단위 실패 (afterAll throw · 런타임 오류) 가 게이트를 통과한다. 검사 #7 · #8 · #11 · #17 이 numFailedTests 만 보고 numFailedTestSuites · numRuntimeErrorTestSuites 를 보지 않는다 | 흡수 | 게이트 본문의 Jest 판정 네 곳 (#7 · #8 두 번 · #17) 에 numFailedTestSuites · numRuntimeErrorTestSuites 를 더했다. 검사 #11 (권한 spec 기준선 포함 검사) 은 스위트 단위 두 값을 따로 본다. 반사실 (합성 결과 JSON: 시험 전부 통과 · 스위트 런타임 오류 1): 이전 술어 rc 0, 새 술어 rc 1 (관측). 게이트 본문 실행에서 Jest 블록 전부 녹색 | 4 |

### 편차 (R3 구간)

38. **구조적 0 의 공시 위치**: 판정 열 (I1 「측정 판정」) 과 이행표 끝 절에 같은 사실을 적었다. 검사 #5 는 칸 문구를 읽지 않고 셀 집합만 본다. 문구와 셀 집합의 일치는 이 노트와 측정 공시 절이 적는다.
39. **범위 밖 합**: 현재 산출 (측정 기준) 의 would_deny 합은 36 이다. 이 합은 구조적 0 두 셀 (합 2) 을 포함한다. 이번 구간에서 산출 파일의 셀을 다시 세어 36 을 확인했다. 지적 본문의 41 은 지적 작성 시점의 수치다.

### 검사 결과 (R3 구간)

- 게이트 본문 실행 (격리 MySQL `m6b-iso-mysql-r3-` · 격리 비밀 파일 · 실행 뒤 r3 컨테이너 내림): `GATES_OK m6b`, EXIT 0. 검사 #1–#17 통과. 검사 #10 은 착수 빌드 트리를 표지 없이 다시 펼쳐 빌드한 뒤 통과했다. 행 파일 6336 · 쓰기 6 · 상태 0 행 0. 요약 `after_sha` 를 서버 `36009c72` 로 갱신했다 (행 수 불변).
- 검사 #5 의 자가 시험 (zero · drop · dup · undecl) 은 모두 rc 1 이다. undecl 은 R3 구조적 0 규칙이 잡는다 (관측).
- 타입 검사 (`tsc --noEmit`): 오류 23 줄. 기준선과 위치 열을 뺀 정렬 비교가 같다.
- 단위 jest (access-control · access-contract · money · organization · analyze · fc/agency, 50 suite, 580 시험): 579 녹색. 1 건 (인사이트 정회원 라우트 getInsight, 합성 보고 fixture 파일 부재 ENOENT) 은 착수 기준선 `jest-baseline.txt` 의 실패 목록에 있다 (이번 구간 신규 아님).
- prettier 검사: 바뀐 서버 파일 5개 모두 통과 (착수 시점에 HEAD 와 워킹트리가 같은 정렬 상태였다).
- 서버 커밋 `36009c72` 의 검사 #9 (측정 sha 조상 · 산출 커밋 포인터 · 상수 OFF) 는 게이트 본문에 있고, 부모 커밋 뒤 재확인은 아래 「커밋 뒤」 절에 적는다.

### 미검증 (커밋 뒤에도 남는다)

- 응답 본문의 code E0607 · reason 'scope' 를 격리 서버에서 실호출로 본 적은 없다. 헬퍼 형상은 `permission-denied.spec` 이, 예외 타입 유지는 기존 spec 이 본다 (사이트 단위 응답 형상 시험은 없다).
- 기업 행을 조직관리자에게 판정하는 설계 (백로그 MEDIUM 행, M9 입력).
- 이전 구간의 격리 컨테이너 `m6b-iso-mysql-r2-` 가 떠 있다. 이번 실행이 띄운 것이 아니라 내리지 않았다. 사람이 확인해 내린다.
- 운영 동일 환경 1회 재생 (복제본 생성 사람 승인) · R5 영업 전사 열람 업무 확인 (defer-unverified, 이전 구간 기록과 같다).

### 라운드 3 처리 요약

- 흡수 4건 (fable MEDIUM 1 · LOW 2 · codex MEDIUM 1 중 해당 건), 부분 흡수 1건 (fable MEDIUM, 판정 설계는 이연). 기각 0건. HIGH 0건. CRITICAL 0건 (critical_open 0).
- 흡수 뒤 HIGH 가 0 이라 최종 리뷰 루프는 여기서 끝난다 (상한 3/3). 흡수하지 못한 MEDIUM 은 백로그에 `→ 다음 계획 반영 대상` 으로 append 했다 (기존 줄은 그대로).
- 커밋: 서버 `36009c72` (fix(access-control)) · 부모 `d38dd5a` (fix(rbac): 포인터 · 게이트 · 메타 · 백로그 · notes). 부모 커밋 뒤 검사 #9 결과는 아래 「커밋 뒤」 절.

## 커밋 뒤 (R3)

- 부모 `d38dd5a` 위에서 검사 #9 블록을 떼어 실행했다 (측정 sha 조상 · 산출 커밋의 서브모듈 포인터 · 상수 OFF · startsWith): 통과 (CHECK9_OK, rc 0).
