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

## 마감 요약

- 판정: **M6 은 in-progress 로 둔다.** 보완 계획 m6c 의 구현 · 게이트 · 코드 리뷰 3라운드 · 최종 리뷰 2라운드는 섰다. 그러나 PRD Outcome 미달 네 건이 남았다 (아래 「PRD 미달 목록」). 네 건은 백로그 HIGH 네 줄 (`2026-10-22` · 「[마감 6c · PRD 미달 1–4]」, `→ 다음 계획 반영 대상 · 보완 계획 admin-rbac-m6cb`) 과 1:1 이다. 강제 상수 OFF 유지, 운영 동일 환경 1회 재생 · R5 업무 확인은 complete 를 막지 않는다 (사용자 몫).
- 이 절의 PRD 6 행 갱신 (Plan 열에 m6c 추가, Status 는 in-progress 유지), 백로그 네 줄, 이 절은 같은 부모 커밋 (`docs(prd)`) 에 싣는다.

### 6c 네 범위의 결과

1. 단건 3의미 분리: 배선 완료. 단건 호출처 9곳이 래퍼 4종을 거치고, 신원별 키 (영업 sagId · 기업 최고 agcId · 조직관리자 agcId + orgPath, 구분자 경계 접두) 로 판정한다. 증명은 미달 (미달 1).
2. 쓰기 검사와 변경의 같은 트랜잭션 잔여 4경로 (결제 취소 · 지정 적립 · 환불 · 조직 권한 업로드): 완료. 조직 삭제의 잠금 순서는 회원 (후보 기본키) → 기업 → 조직으로 소속 쓰기와 같은 방향이다. 그러나 재확인의 current read 계약이 없고 (미달 2), 회원 정보 수정 경로 (B19) 가 판정 · 변경을 한 트랜잭션에 묶지 않는다 (미달 3).
3. 일괄 전건 검사: 변경 라우트 인벤토리 34행 (`bulk-inventory.md`, B1–B34) 과 전건 거부 시험 이름이 격리 MySQL 에서 녹색이다. 경합 증명은 복제본이다 (미달 4).
4. 증명 6 행 「타 기업 행 0」 넓힌 격리 측정: 질의 11종 (지갑 · 결제 · 기업 이력 · 적립 · 환불 · 분석 · 단건) 이 격리 MySQL 에서 타 기업 행 0 · 행 수 일치를 통과한다 (게이트 #9). 섰다.

### 게이트 결과 (서버 HEAD 47fcc439 = 부모 gitlink, 격리 `m6c-iso-` 컨테이너 · 합성 시드)

- 전체 게이트 `.claude/PRPs/plans/admin-rbac-m6c.gates.sh` 검사 #1–#19: `GATES_OK m6c`, rc 0 (R2 구간 재실행 기록).
- 검사 #10 응답 불변: 착수 행 파일 (`rows-before-m6c.txt`) 과 최종 행 파일 (`rows-after-m6c.txt`) 이 6346행 `cmp` 일치. 쓰기 행 6. `probe-summary-m6c.txt` 의 `after_sha` = 47fcc439.
- tsc 오류 23 = 착수 기준선 (전부 spec 파일, 비-spec 0). jest: 착수 기준선의 ENOENT 1건 (`analyze-insight-route.spec.ts`, 워크트리 밖 fixture) 외 녹색.
- 계획 게이트: divergent · 상한 3 소진 · CRITICAL 없음 · receipt 없이 skip-receipt. 사용자 지시 원문은 `verify-user-directive.sh` exit 0 으로 다시 확인했다 (2026-10-22 마감 실행).
- 구현 Codex 게이트: 라운드 3 approve (CRITICAL · HIGH 없음). 1차 HIGH (조직 권한 업로드의 잠금 뒤 재판정) 는 수정. implement receipt 는 divergent (`.claude/receipts/mccp-implement-codex/admin-rbac-m6c.json`).

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

- code-review (커밋 전 워킹트리, 3라운드): R1 지적 25 (흡수 20 · 기각 4 · 부분 흡수 1 = HIGH 1, 백로그 1817). R2 지적 25 (같은 자리를 묶어 12행, 흡수 8 · 기각 4, HIGH 흡수 1, HIGH 미해소 0). R3 지적 12 + 파생 1 (흡수 11 · 기각 1 · 부분 흡수 1 → 백로그, HIGH 흡수 1, HIGH 미해소 0, CRITICAL 0).
- 최종 리뷰 (fable · codex, 2라운드, 상한 3 안): R1 지적 9 (fable 7 · codex 2), 흡수 7, 미흡수 2 (백로그 MEDIUM), HIGH 흡수 1, HIGH 미해소 0. R2 지적 4 (fable 3 · codex 1), 흡수 4, HIGH 0. 두 라운드 codex ok.

### PRD 미달 목록 (백로그 m6cb 입력)

1. 단건 3의미 · 행 키 증명 (1801 의 증명 부분 철회). 단건 호출처 9곳의 행 키 조인을 실 SQL 로 검사하는 검사가 없다 (백로그 1808). 배선 경로에서 SingleMeaning 인자는 판정을 바꾸지 않는다 (R2 LOW 사실 기록, 정책 2 · 10). 증명 6 행의 「단건 3의미 spec」 은 순수 함수 수준이다.
2. 조직 삭제의 재확인 (1802 의 잠금 순서 부분 철회). `organization.service.ts` 의 `deleteSubtreeIn` 에서 기업 잠금 뒤 회원 재확인이 잠금 없는 일관 읽기다. 커밋 전 소속 이동을 못 본다 (백로그 1806). 잠금 읽기로 바꾸면 회원 → 기업 순서가 역전되므로 설계 결정이 먼저다.
3. 회원 정보 수정 (B19, `POST admin/agency-fc/edit/:memId`) 의 소속 쓰기 (1802 의 쓰기 트랜잭션 부분 철회). `fc-agency.service.ts` 의 update 가 판정 (`assertMembersWritableByAdmin` · `assertOrgWritableByAdmin`) 을 트랜잭션 밖에서 하고, `fc.service.update` → `fc.repository.update` 가 기업 잠금 없이 orgId 를 쓴다 (백로그 1819 · 편입 창).
4. 경합 증명 복제본 (1803 의 경합 증명 부분 철회). 조직 권한 업로드 · 지정 적립 · 결제 취소 · 환불의 경합 시험 네 쌍은 저장소 복제본이다. 영업 신원이 서비스 경로에서 커밋 대기 뒤 거부되는 형태가 없다 (백로그 1817).

### 사용자 몫 · 미검증 (complete 를 막지 않는 것)

- 운영 동일 환경 (복제본) 1회 재생: 복제본 생성은 사람 승인 (2026-10-21 운영 검증 규칙). 이 실행은 격리 합성 데이터로만 돌렸다.
- R5 영업 전사 열람 · 쓰기의 업무 확인: 레거시 예외 행 0 (책임자 · 기한 · 승인은 사용자).
- 결제 취소의 PG 프로세스 종료 · PG 쪽 멱등 (백로그 1805, 정책 5). 구조적 해소는 스키마 변경이 필요해 이연했다. 탐지 로그는 catch 가 돌아야만 남는다.
- 머니 지급 (기업 → 회원) ↔ 지정 적립 (회원 → 기업) 의 역순 교착 (백로그 1770): 이연, M9 체크리스트.
- 격리 스키마 덤프 (저장소 밖) 에 `tn_member.mem_org_id` 인덱스와 `tn_agency_organization.idx_org_path` 가 없다 (백로그 1822). 제품 선언과 어긋난다. 운영 실재는 (미검증, 운영 접속 금지). 교착 재현용 인덱스는 조직 격리 시험 fixture 에만 더했다.
- 운영 sql_mode (격리는 빈 값으로 띄웠다), 실트래픽 would-deny, 경합 itspec 의 서버 CI 편입 (이연).
- 응답 불변 probe 의 쓰기 행 6개는 전부 admin-super 신원이다. 머니 쓰기 4경로는 probe 에 없다 (백로그 1820).
- 신규 엔드포인트 범위 선언 강제의 「M6 에서 활성화」 해석 (백로그 1821). 런타임 모드는 warn 그대로이고 fail 전환은 M9 몫이다.
- 강제 상수 3개와 M7 · M10 전환 스위치는 OFF 유지 (M9).

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

1. 상태: M6 in-progress. 미달 네 건 = 위 「PRD 미달 목록」. 같은 자리의 기존 백로그 행 (1801 · 1802 · 1803 · 1806 · 1808 · 1817 · 1819) 을 참조한다.
2. 잠금 순서 현행 (서버 코드 실측):
   - 소속 쓰기 (assignOrg · setOrgManager): 회원 → 기업 → 현재 조직 → 목적지 조직.
   - 조직 삭제 (deleteSubtree): 회원 후보 기본키 잠금 → 기업 → 조직 잠금 읽기 → 회원 재확인 (잠금 없는 일관 읽기).
   - 머니: 지정 적립 (createIssue) 회원 → 기업, 지정 지급 (createGrant) 기업 → 회원 (역순, 1770 이연). 결제 취소는 결제 행 → 회원 → 기업, 판정 뒤 PG 호출, 같은 트랜잭션.
   - 조직 권한 업로드: 기업 (corp) 신원만 도달한다. 조직 잠금 뒤 재판정.
   - 회원 정보 수정 (B19): 판정 · 변경이 트랜잭션 밖. 미달 3.
3. 단건 판정 키 (정책 2, 신원별): 영업 sagId · 기업 최고 agcId · 조직관리자 agcId + orgPath (구분자 경계 접두) · 관리자 래퍼 통과 (정책 10) · 회원 memId 경로 보존.
4. 측정 산출: `.claude/meta/2026-10-21-admin-rbac-m6c/` (`scope-would-deny.json` 132 셀, positive_fail 0 · 행 파일 · probe 사본 · 시드 · 이행 표). 디렉터리 이름의 날짜는 최초 작성일이다.
5. 서브모듈: 서버 `47fcc439` (부모 gitlink 와 일치, 착수 기준 `700b7d26`), 관리자 `1301b664`, 사이트 `fc8872a1`. 6c 는 서버만 바꿨다.
6. 게이트 사이드카 `admin-rbac-m6c.gates.sh` 는 동결이다. m6cb 는 새 사이드카를 쓴다. 검사 #10 의 `after_sha` 는 커밋 뒤 HEAD 와 같아야 하므로, 서버에 새 커밋이 생기면 응답 불변 probe 를 다시 돌린다.

### 6c 커밋 목록

- 서버 (`feat/admin-rbac`): `23353d3f` 단건 판정 신원별 키 · `7ef0c778` 소속 쓰기 · 조직 삭제 잠금 순서 · `0e80578f` 머니 쓰기 4경로 · 분석 질의 범위 · `ceeae2f5` 일괄 엑셀 시험 · `7dcbd7e5` 영업 판정 PG 응답 유실 시험 · `47fcc439` 조직 삭제의 회원 잠금 기본키화.
- 부모 (`feat/admin-rbac`): `22eeb04` 구현 결과 · 포인터 · `da77bf3` 최종 리뷰 R1 · `b829073` 최종 리뷰 R2 · 이 절의 `docs(prd)` 커밋.

## 계획 게이트

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

### 라운드별 결과

| 라운드 | 계획 해시 (앞부분) | L1 | L2 (패널) | L3 (Codex) | 판정 | 비고 |
|---|---|---|---|---|---|---|
| 1 | `sha256:e014e0d3…` | converged | divergent · 응답 4 · 정족수 3 필요 · 막힘 7 (사유 원문: architect/HIGH, architect/FAIL, test/HIGH, test/FAIL) | divergent (세부 건수는 이 세션에서 확인하지 못했다) | divergent | 리뷰 기록 커밋 `029f8c0` |
| 2 | `sha256:82e6d597…` | converged | divergent · 응답 4 · 정족수 3 필요 · 막힘 4 (사유 원문: test/HIGH, test/FAIL, invariant/HIGH, invariant/FAIL) | divergent (세부 건수는 이 세션에서 확인하지 못했다) | divergent | 리뷰 기록 커밋 `6cf338d` |
| 3 (마지막) | `sha256:fc0d03fc…` | converged | divergent · 응답 4 · 막힘 2 (사유 원문: test/HIGH, test/FAIL) · 지적 9 (HIGH 1 · MEDIUM 5 · LOW 3) | divergent · HIGH 2 · MEDIUM 1 | divergent · 상한 소진 | 이 처리 |

- 라운드 번호는 리뷰 기록 커밋 이력과 기록 머리의 계획 해시로 맞췄다. 판정 원장 (`dispatch-log` 의 `round_index`) 은 세 줄 모두 0 이라 쓸 수 없었다.
- 막힘은 7 → 4 → 2 로 줄었지만 상한 3 에서 멈췄다.

### 처리

1. 마지막 라운드 미해소 지적 12건을 `.claude/plans/codex-findings-backlog.md` 에 `2026-10-22` 줄로 append 했다 (추가 12줄, 삭제 0줄). 기존 줄은 고치지 않았다. 같은 라운드 항목은 backlog 에 없었다 (m6c 줄은 이전 라운드의 RESOLVED-IN 17건뿐이었다).
2. 지적 구성: L3 3건 (HIGH 2 · MEDIUM 1) + L2 9건 (HIGH 1 · MEDIUM 5 · LOW 3). 판정 문구 `FAIL` 은 지적 수에서 뺐다.
   - L2 9건에는 요약 목록에 빠진 security LOW 1건 (단건 래퍼의 admin 역할 판정 없는 통과, 계획이 M9 판단 표 입력으로 명시적으로 넘긴 잔여 위험) 이 포함된다. 리뷰 원문에 있어 함께 적었다.
   - L2 의 「PG 호출 전 실패 로그 부재」 와 「검사 #6 호출처 미결속 · Task 9 (자) 함수 수」 는 원문에서 별개의 지적이라 두 줄로 나눴다.
3. HIGH 3건 줄의 사유 끝에 `→ 다음 계획 반영 대상` 표지를 달았다 (아래 목록).
4. CRITICAL 이 없으므로 receipt 를 쓰지 않았다 (`receipt_path = (없음)`). 계획서는 고치지 않았다.
5. 나머지 MEDIUM 6건 · LOW 3건은 backlog 에 `(이연)` 으로 기록만 했다.

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

1. L3 · PG 취소 호출 뒤 프로세스가 종료되면 조정 로그가 남지 않고 DB 만 롤백된다 (계획서 :68, 정책 5). 같은 요청이 재호출되면 부분취소가 이중으로 나갈 수 있다. 다음 계획은 PG 호출 전에 취소 요청 ID · 금액 · 처리 상태를 영속 커밋하고, 미확정 요청은 PG 결과 대조 전 재호출을 막는 계약을 적어야 한다.
2. L3 · 부분트리 재조회의 최신 읽기 계약이 없다 (계획서 :245, `insuniverse-server/apps/api/src/modules/agency/organization/organization.service.ts:92`). 기업 잠금 뒤 잠금 읽기 (current read) 로 다시 읽는다는 계약과, 최초 조회와 기업 잠금 사이에 조직 이동을 커밋하는 두 연결 시험이 필요하다.
3. L2 test · 단건 조회 호출처 9곳 (계획서 S1-S9) 의 행 키 조인을 실제 SQL 로 검사하는 검사가 없다 (계획서 Task 1 (나), `.claude/PRPs/plans/admin-rbac-m6c.gates.sh:275`). asb · odd 행이 잘못된 agcId · orgPath 로 풀리면 과허용이 나도 게이트가 붉어지지 않는다.

### 미검증

- 이 처리는 계획 검사를 통과시킨 것이 아니다. 구현 진입 검사만 환경 변수로 넘겼다. 위 HIGH 3건과 나머지 지적은 다음 계획 작성에서 닫아야 한다.
- 라운드 1 · 2 의 L3 세부 건수는 확인하지 못했다 (리뷰 기록에 L3 목록이 없고, 상태 파일은 마지막 라운드 것만 남았다).
- 구현 진입은 아직 시작하지 않았다. 이 노트는 계획 게이트 처리만 적는다.

---


상태: **구현 진행 중 (미완)**. 커밋하지 않았다 (사용자 지시: 구현 뒤 별도 세션에서 code-review, 커밋은 그 뒤).
게이트 receipt 는 아직 쓰지 않았다 (구현 완료 뒤 Implement-Codex 게이트 단계에서 쓴다. plan receipt 는 skip-receipt, 사용자 지시 2026-10-19 · verify-user-directive.sh exit 0).

## 구현 진행 기록 (implement 단계)

상태: **구현 진행 중 (미완)**. 커밋하지 않았다 (사용자 지시). implement receipt 는 아직 없다.

### 착수 기준
- 부모 `817962cf` (계획 기준) · 서버 `700b7d26` · 관리자 `1301b664` · 사이트 `fc8872a1` (feat/admin-rbac, 작업 트리 착수 시 clean)
- 서버 착수 sha 가 HEAD 의 조상이다 (검사 #1 의 조상 부분 통과, 구현 시작 전 관측)

## 완료한 Task
### Task 1 · 단건 판정 3의미 분리 (부분 검증 완료)
- `scope-monitor.ts`: `judgeSingleScope` 를 신원별 키로 재작성 (sales=sagId · corp-super=agcId · org-manager=agcId+orgPath 구분자 경계 접두 · admin 은 「소유자 또는 관리자 열람」 만 allow · member 는 memId 경로 불변). 키 부재는 던지지 않고 deny. 추가: `singleScopeOf` (신원 → 판정 종류, 미지 corpScope · 미지 역할은 null=deny), `SingleRowKeys` 타입.
- 단건 읽기 호출처 9곳 (S1–S9) 을 새 래퍼로 옮겼다. 래퍼는 상수 OFF 면 즉시 반환, admin 역할은 판정 없이 통과 (정책 10), 나머지는 `judgeSingleScope`.
  - 머니 `assertSingleOwnedAgc`: 이슈(S1) · 회원(S2) · 결제 상세/이용(S3·S4) · 환불(S5)
  - 조직 `assertSingleOrg`: 조직 상세(S6). 조직 쓰기 · 목록은 현행 `assertSalesOrg` · `assertSalesAgency` 유지
  - 분석 `guardSingleOdd`: 기본(S7) · 처방(S9) · `guardSingleRow`: 상세(S8). 집계 · 내보내기는 현행 `guardSalesOdd` 유지
- 키 원천 조인: `money.repository` (`getMemberScopeRow` · `getPaymentOwnerMemId` · `getRefundOwnerMemId` · `getIssueScopeRow`), `analyze.repository` (`getOddScopeRow`), `organization.service` (`singleRowKeysOfOrg`).
- 지정 적립(S1)의 조직 경로: 대상 회원들의 소속 경로가 한 값이면 그 경로, 여럿이거나 없으면 null → 조직관리자 deny (fail-closed). 정책 2 의 「회원 소속 조직」 을 여러 대상에 확장하지 않았다.
- 단위 시험: `scope-monitor.spec.ts` 「M6c 단건 신원별 키」 블록 11건, `scope-handlers.spec.ts` 「M6c 단건 래퍼」 블록 7건 + 기존 ON 시험 13건의 mock 을 새 키 제공자로 바꿈.
- 실측: access-control · money · agency/organization · analyze 스위트 374건 중 373 통과.
  - 유일한 실패 `analyze-insight-route.spec.ts` 는 착수 전부터 ENOENT (`.claude/plans/ai-insight-report…` 픽스처가 워크트리에 없음, 메모 ai-insight 계열). 이 구현의 회귀가 아니다.
- 구조 검사 (검사 #6 의 본문 조건을 수동 재현): 래퍼 4종 모두 `judgeSingleScope(` 1 · `judgeRowScope(` 0 · `assertSales|guardSales` 0 · 상수 가드 1. `await this.` 단건 호출 9. spec 밖 `judgeSingleScope(` 호출처 4.
- 타입: `npx tsc --noEmit -p tsconfig.json` 오류 23건. 전부 기존 spec 파일 (`terms.repository.spec.ts` · `loss-analysis.repository.spec.ts` · `medication-standard.repository.spec.ts` · `libs/*`). 이번 변경 파일의 비-spec 오류 0.

## 미완료 Task (다음 실행이 이어 간다)
- Task 2 측정 하네스 사본 · 132 셀 재측정 (`scope-measure.mjs` · `scope-would-deny.json` · `identities-scope.json`)
- Task 3 잠금 순서 한 방향 (`deleteSubtree` 회원 선행 잠금 · `lockSubtreeMembers` · `setOrgManager` 조직 행 잠금 뒤 경로 읽기 · 경합 itspec 다섯 이름)
- Task 4 쓰기 판정과 변경의 같은 트랜잭션 (결제 취소 · 지정 적립 · 환불 · 조직 권한 업로드 · PG 조정 로그 `payment.cancel.pg_called_db_rolled_back` · 경합 4종 + 반사실 4종)
- Task 5 일괄 · 변경 라우트 인벤토리 34 행 (`bulk-inventory.md`) 과 전건 거부 격리 시험
- Task 6 증명 6 행 격리 질의 11종 (`money-scope-query.itspec.ts` 신규, 합성 시드)
- Task 7 강제 ON 음성 · 정상 경로 (`scope-enforce.spec.ts` 의 jest.mock 시험 보강)
- Task 8 응답 불변 probe (`isolated-probe.sh` · `probe.mjs` 사본, 착수 빌드 · 최종 빌드 행 파일 cmp) · 상수 OFF 확인
- Task 10 기준선 (`jest-baseline.txt` · `tsc-baseline.txt` · `prior-specs-baseline.txt`, 착수 `700b7d26` 에서)
- Task 11 백로그 RESOLVED-IN 17행 append · PRD 마일스톤 6 행 판정 · 서버 서브모듈 포인터 (커밋 뒤)

## 정책과 다르게 옮긴 자리 (구현 중 추가)
1. 래퍼의 admin 통과를 판정 호출 앞에 둔다 (정책 10 의 「admin 은 판정 없이 통과」 를 문자 그대로 옮김). `singleScopeOf` 가 admin 에 kind `admin` 을 주므로 judge 는 admin 에 대해서만 「소유자 또는 관리자 열람」 allow 를 낸다. 래퍼는 admin 을 judge 에 넘기지 않는다.
2. 「지정 적립」 의 조직 경로를 대상 회원 경로의 단일 값으로 정했다 (위 Task 1 참고). 사용자 확인 대상 (정책 2 와 연결).

## (미검증)
- 실 DB 에서의 단건 키 조인 결과 (원천 조인은 코드로만 확인, 격리 MySQL 실행 없음)
- 격리 시험 (Task 3–6) 과 응답 불변 probe (Task 8) 전부

## 구현 재개 (2026-10-22, implement 후속 실행)

- Task 1 은 앞 실행의 미커밋 변경을 그대로 이어받았다. 접근 권한 시험 134 건 녹색 (`auth/access-control` 스위트).
- Task 2 완료: 측정 사본 `.claude/meta/2026-10-21-admin-rbac-m6c/` 에 m6b 도구 사본을 두고 단건 6 행을 `judgeSingleScope` 로 바꿨다.
  - 측정 실행: 격리 MySQL · Redis (`m6c-iso-` 컨테이너, 실행 뒤 정리) · 착수 빌드 `700b7d26` · 셀 132 · positive_fail 0.
  - 착수 빌드 행 파일 `rows-before-m6c.txt` 는 m6b 행 파일과 여덟 줄 다르다. 원인은 첨부 파일 이름의 날짜 (`2026-10-21` → 실행일) 뿐이라 probe 에 날짜 정규화 (`<date>`) 를 더했다.
- 서버 HEAD 는 아직 `700b7d26` 이다. 검사 #10 의 `after_sha ≠ 착수 sha` 는 커밋 뒤에만 참이 된다 (커밋하지 않는다는 지시에 따라 대기).
- Task 3 코드 완료: `organization.service.ts` 의 `deleteSubtree` 를 회원 → 기업 → 조직 순서로 바꿨다 (`runSalesOrgWrite` 의 `beforeAgencyLock` 인자, 기업 잠금 뒤 재확인이 다르면 한 번 더, 두 번째도 다르면 409). `organization.repository.ts` 에 `lockSubtreeMembers` · `lockOrgRow` · `lockSubtreeOrgIds` 를 더했다. `hasActiveContractsInOrgs` 는 잠금 공유 읽기 옵션을 받는다.
  - 시험 `organization.race.itspec.ts` 9 건 녹색 (새 5 건: 소속 쓰기 후 삭제 · 삭제 후 소속 쓰기 · 구 순서 반사실 (1213 재현) · 삭제 경로 회원 선행 잠금 · 조직 라우트 쓰기 잠금).
  - `setOrgManager` (다) 는 현행 코드가 이미 회원 → 기업 → 현재 조직 순으로 잠그고 그 뒤 경로를 읽는다. 바꾸지 않았다.
- Task 4 코드 완료 (시험은 대기): `money.service.ts` 의 `cancelPaymentByAdmin` (결제 행 → 회원 → 기업, 판정 뒤 PG, 한 트랜잭션, 로그 `payment.cancel.pg_called_db_rolled_back`) · `createIssue` (회원 행 → 기업 행 오름차순, 판정) · `processRefund` (회원 → 기업, 판정, 트랜잭션). `fc-agency.service.ts` 의 `orgPermissionUpload` 를 한 트랜잭션으로 묶었다. 저장소 메서드 여럿에 `db` 인자를 더했다.
  - 제한: PG 「커밋 실패」 경로는 같은 로그 분기이며, 실제 커밋 실패는 주입하지 못하고 DB 문장 실패로 대신 본다 (시험 이름은 게이트 고정 이름을 쓴다).
  - PG 호출이 던진 거절도 `pg_outcome=unknown` 으로 적는다 (거절과 유실을 PG 응답만으로 가르지 못한다).
- 격리 DB: 컨테이너 `m6c-iso-it` (이름 접두 준수) 에 전체 스키마 (`insuniverse` 데이터베이스 · 158 표) + 합성 시드를 적재했다. 실행 뒤 이 컨테이너만 내린다.

## 구현 중간 결과 (2026-10-22, 이번 실행 종료 시점 · 미완으로 멈춤)

상태: **미완 (halted)**. 커밋하지 않았다 (실행 지시). 아래 목록의 남은 Task 가 끝나야 검사 전체가 선다.

### 끝난 것
- Task 1 (앞 실행의 미커밋 코드 이어받음): `auth/access-control` 스위트 134 건 녹색.
- Task 2: 측정 사본 `.claude/meta/2026-10-21-admin-rbac-m6c/` (도구 사본 · 단건 6 행 `judgeSingleScope`). 격리 측정 셀 132 · positive_fail 0 · `scope-would-deny.json` 산출. 착수 빌드 행 파일 `rows-before-m6c.txt` (probe 날짜 정규화 `<date>` 추가, m6b 행 파일과 날짜 줄 8 개만 다르다).
- Task 3: `organization.service.ts` 삭제 경로 (회원 → 기업 → 조직, 재확인 뒤 한 번 재시도). `organization.race.itspec.ts` 9 건 녹색 (게이트 #12 이름 5 개 포함, 1213 재현 반사실 포함).
- Task 4: 코드 4 경로 (결제 취소 · 지정 적립 · 환불 · 조직 권한 업로드). 시험: `money/money-race.itspec.ts` 11 건 녹색 (결제 취소 경합 2 · 판정 거부 PG 미호출 · PG 뒤 실패 로그 · PG 유실 로그 · 지정 적립 경합 2 · 환불 경합 2 · 환불 거부 행 변화 0 · 지정 적립 전건 거부). `fc/agency/agency-fc.race.itspec.ts` 6 건 녹색 (조직 권한 업로드 경합 + 반사실 신규 2).

### 남은 것 (다음 실행이 이어 간다)
1. Task 4 · 5 의 엑셀 전건 거부 시험 6 개: 「지정 지급 엑셀 전건 거부」 「지정 적립 엑셀 전건 거부」 「소속 업로드 전건 거부」 「조직 권한 업로드 전건 거부」 「계정 수정 업로드 전건 거부」 「구독 업로드 전건 거부」. 게이트 #8 이 이름을 요구한다. 엑셀 버퍼 (exceljs 로 생성 가능) 와 서비스 DI 가 필요하다.
2. Task 5: `bulk-inventory.md` (변경 라우트 B 행 34 개, 열 `| B번호 | 라우트 | 파일 | 일괄 | 판정 | 근거 | 시험 |`) 작성. 아직 없다.
3. Task 6: `money-scope-query.itspec.ts` (질의 11 종 × 3 이름 + 「분석 집계 합 일치」). 아직 없다. 분석 질의는 서비스의 범위 절 조립 (`listScopeCondition`) 을 따라야 한다.
4. Task 7: `scope-enforce.spec.ts` 의 jest.mock ON 시험 정리 (음성 · 정상 경로만). 미확인.
5. Task 8: 응답 불변 probe 의 최종 빌드 (`GK_BUILD=after`) 와 `probe-summary-m6c.txt`. 검사 #10 의 `after_sha ≠ 착수 sha` 는 서버 커밋 뒤에만 참이다 (커밋하지 않는다는 지시에 따라 대기). 상수 OFF 와 인터셉터 계수 (#11 · #17) 는 현재 워킹트리로 확인 가능.
6. Task 9: 게이트 스크립트 (`.claude/PRPs/plans/admin-rbac-m6c.gates.sh`) 는 아직 한 번도 끝까지 돌지 않았다. 검사 #2 는 기준선 파일이 없어 붉다.
7. Task 10: 기준선 (`jest-baseline.txt` · `tsc-baseline.txt` · `prior-specs-baseline.txt`) 을 착수 `700b7d26` 에서 다시 만든다. 아직 없다.
8. Task 11: 백로그 RESOLVED-IN 17 행, PRD 마일스톤 6 행, 서버 서브모듈 포인터 (커밋 뒤).
9. 전체 게이트 실행, 구현 Codex 게이트 (2.5.3–2.5.6), 3.6 · 3.7 restamp, Phase 4 · 5 보고.

### 실행 환경 (정리 대상)
- 격리 MySQL: 컨테이너 `m6c-iso-it` (mysql:8.0.42, 포트와 비밀번호는 `$(git rev-parse --git-dir)/gatekit-m6c-it.env`, 권한 600). 전체 스키마 · 합성 시드가 `insuniverse` 데이터베이스에 있다. 다음 실행이 이어 쓰고, 끝나면 이 컨테이너만 내린다.
- 임시 측정 환경 파일은 지웠다 (다음 실행에서 `openssl rand` 로 새로 만든다).

### 편차 (이번 실행)
- 커밋하지 않았다 (실행 지시). 그래서 검사 #10 의 `after_sha` 와 Task 11 의 서브모듈 포인터 갱신은 커밋 뒤로 미룬다.
- PG 「커밋 실패」 시험은 커밋 자체의 실패를 주입하지 못해, 커밋 전 DB 쓰기 실패 (`updateMemberMoney` 주입) 로 같은 로그 분기를 본다.
- 결제 취소의 PG 로그 `pg_outcome` 은 PG 호출이 던진 거절도 `unknown` 으로 적는다 (거절과 유실을 PG 응답만으로 가르지 못한다).
- 조직 권한 업로드 경합은 업로드 본체 대신 같은 잠금 순서를 재현하는 저장소 시퀀스로 시험했다 (엑셀 · DI 비용). 엑셀 전건 거부 시험과 함께 다음 실행에서 본체를 친다.

## 구현 재개 2 (2026-10-22, 후속 실행 2 · 정지 · 커밋 안 함)

상태: **미완 (halted)**. 이 실행은 Task 4 잔여 · Task 5 인벤토리 작성에 들어가기 전에 라우트 분류 공백을 확정하고 멈췄다. 커밋하지 않았다.

### 이번 실행에서 확인한 것 (실측)
- 워킹트리: 서버 15 파일 변경 (`git diff -w --stat` 15 files · 1293+ · 114-, 줄바꿈 전용 변경 없음). 부모는 `M .claude/notes/admin-rbac-m6c.md` 와 메타 디렉터리 `??`. 관리자 · 사이트 서브모듈 clean.
- 격리 컨테이너 `m6c-iso-it` 가 살아 있다 (Up). 다음 실행이 이어 쓴다. 끝나면 이 컨테이너만 내린다.
- 앞 구간 결과와 대조: 고정 이름 8 개 중 「지정 적립 전건 거부」「환불 거부 시 행 변화 0」 은 `money/money-race.itspec.ts` 에 있다 (이번 실행에서 다시 돌리지 않았다). 나머지 6 개 (아래) 는 아직 없다.
- 게이트 `#8` 이 요구하는 엑셀 전건 거부 6 개 이름: 「지정 지급 엑셀 전건 거부」 「지정 적립 엑셀 전건 거부」 「소속 업로드 전건 거부」 「조직 권한 업로드 전건 거부」 「계정 수정 업로드 전건 거부」 「구독 업로드 전건 거부」. 미구현.
- 변경 라우트 수 (`#8` 의 grep 식으로 다시 셈): 머니 · 소속 · 조직 디렉터리 `*-admin.controller.ts` 의 `@Post/Put/Patch/Delete` 31 + 게시판 · 팝업 · 검색 업로드 3 = 34. 계획과 같다.
- 라우트 → 핸들러 → 기준 커밋(700b7d26) 의 범위 판정 상태:
  - 머니: `issue` createIssue (판정 없음, Task 4 에서 추가) · `issue/excel/upload` uploadIssue (판정 없음, 추가 필요) · `issue/excel/preview` previewIssue (uploadIssue 를 dryRun 으로 부름, 판정 없음) · `corp` createCorpMoney (있음) · `charge-requests` · `charge-requests/cancel` (corp 최고관리자 본인 agcId, 세션에서 얻음) · `charge-requests/process` (admin 전용, 정책 10 통과) · `grant` createGrant (있음) · `grant/single` createGrantSingle (있음) · `grant/excel/upload` uploadGrant (있음) · `payment/cancel` (Task 4 에서 추가) · `refund/process` processRefund (Task 4 에서 추가).
  - 조직: `create` · `update` · `move` · `delete` 는 서비스가 `runSalesOrgWrite` 류 잠금 뒤 판정 (Task 3 · 기준 커밋에 이미 있음 · 삭제 경로만 Task 3 에서 잠금 순서 변경).
  - 고객사 FC: `assign-org` assignOrg · `org-manager` setOrgManager 만 `assertMembersWritableBySales` (영업 범위) 를 부른다. `upload` · `create` · `account-update-upload` · `org-upload` · `subscribe/upload` · `subscribe` · `edit/:memId` · `resign` · `delete` · `unsubscribe` 는 `assertMembersWritableByAdmin` (corp 쪽) 만 본다 — 영업 범위 판정이 있는지 이번에 확인하지 못했다.
  - 삼성 인테이크 `parse` · `preview` · `confirm`, 게시판 · 팝업 · 검색 업로드 3 건: 범위 키 없음으로 보인다 (확인 필요).

### 멈춘 이유 (결정이 필요한 공백)
1. fc 고객 쓰기 열 10 개의 영업 범위 판정 유무. 기준 커밋에 영업 판정이 `assignOrg` · `setOrgManager` 두 곳뿐이면, 나머지 열 개는 이 마일스톤이 판정을 더하는 대상이거나 (Task 4 와 Task 5 가 「판정추가」로 분류해야 함) 기존 공백이다. 어느 쪽인지 코드로 확정해야 `bulk-inventory.md` 의 「판정」 칸을 쓸 수 있다.
2. `uploadIssue` 와 `previewIssue`: 쓰기는 없지만 미리보기도 회원 대조를 한다. 미리보기 응답이 담당 밖 회원을 드러내는지 (읽기 노출) 를 정해야 「이연」 과 「판정추가」 중 고를 수 있다. 근거 있는 쪽을 택하고 기록한다.
3. 검사 `#8` 은 「일괄」 · 「판정있음」 · 「판정추가」 행마다 통과하는 시험 이름 하나를 요구한다 (`NAMES8B`). 분류가 확정되면 필요한 이름 수가 정해지고, 지금은 그 수를 셀 수 없다. 현재 추정으로 약 25 개 이상이다.
4. 이연 기준: 「이연」 은 범위 키가 없는 행이며 근거 칸이 필요하다. `charge-requests` 두 행은 기업 본인 신원 (세션의 agcId) 이라 범위 키가 없어 「이연」 후보다. 근거를 코드로 확인해야 한다.

### 다음 실행이 할 일 (순서)
1. 위 1–2 를 코드로 확정한다 (`fc-agency.service.ts` 의 `assertMembersWritableByAdmin` 본문 · `uploadIssue`/`previewIssue` 의 판정 위치). 확정 결과를 이 절에 적는다.
2. `bulk-inventory.md` 를 쓴다 (`| B번호 | 라우트 | 파일 | 일괄 | 판정 | 근거 | 시험 |`, 34 행). 「시험」 칸 이름은 실제 시험과 맞춘다.
3. 시험 작성 순서: 엑셀 전건 거부 6 개 → `money-bulk-scope.itspec.ts` · `fc-bulk-scope.itspec.ts` (일괄 행 이름) → 검사 `#9` 용 `money-scope-query.itspec.ts` (34 이름). 각 시험은 실행해서 통과를 본다.
4. Task 7 · 8 · 9 · 10 · 11 과 전체 게이트는 그 뒤에 한다. 검사 `#10` 의 `after_sha ≠ 착수 sha` 는 커밋 뒤에만 참이므로, 커밋 전에는 이 한 검사가 붉은 것이 정상이다.

### 편차 (이번 실행)
- 앞 실행 노트 「편차」 와 같다. 추가로: `prettier` 확인 (`git show HEAD:<f> | prettier --stdin-filepath <f> | cmp -s - <f>`) 을 서버 15 파일에 돌렸더니 15 개 모두 다르다. 그 파일들의 차이는 이 마일스톤의 기능 변경이고 (위 「구현 진행 기록」 · 「완료한 Task」 · 「구현 재개」 에 적혀 있다), 포맷만의 변경이 아니므로 `style` 커밋 대상이 아니다. 「출처 미상」 으로 멈추지 않았다 (정책 §5-7 은 이 마일스톤 밖의 변경을 막는다). 이 판단은 다음 실행에서 확인한다.
- 게이트는 아직 한 번도 끝까지 돌지 않았다. 「검사 #8 · #9 가 붉다」 는 이 상태에서 기대되는 결과다 (시험 파일이 없다).

## 구현 재개 3 (2026-10-22, 이어달리기 3번째 구간 · 정지 · 커밋 안 함)

상태: **미완 (halted)**. 커밋하지 않았다 (실행 지시). 멈춘 자리는 Task 6 의 분석 질의 범위 절이다.

### 이번 구간에서 한 것
1. 멈춤 사유 세 가지를 코드로 확정했다.
   - fc 고객 쓰기 열 10 개: 역할 가드가 `admin` · `corp` 전용이라 영업 신원은 도달하지 못한다. 기업 쪽 판정 (`assertMembersWritableByAdmin`) 은 기준 커밋부터 있다. 잠금 뒤 판정은 아니다 (기업 키는 세션 고정값). 「판정있음」 으로 적었다 (`bulk-inventory.md` B13–B19 · B22 · B24).
   - `uploadIssue` · `previewIssue`: 역할은 클래스 가드 `admin · sales` 라 영업이 도달한다. 판정이 없었다. 미리보기도 회원 식별 · 잔액을 싣는다. 「판정추가」 로 두고 구현했다 (B2 · B3).
   - 검사 #8 이 요구하는 시험 이름: 행마다 이름 하나 (시험 칸). 공유 이름 허용 (게이트 `jnames` 는 부분 문자열 일치).
2. 코드 변경 (이 구간):
   - `money.service.ts` `uploadIssue`: 여섯째 인자 `salesOwner` 를 더했다. 미리보기 (dryRun) 는 담당 밖 회원이 한 명이라도 있으면 거부한다. 적용은 회원 행 잠금 → 기업 행 잠금 (agcId 오름차순) → `assertSalesOwnerAfterLock` 을 트랜잭션 안에서 한다. 거부 시 전건 되돌린다.
   - `money-admin.controller.ts`: `issue/excel/upload` · `issue/excel/preview` 가 `salesLockedOwner(req)` 를 넘긴다.
   - `money.repository.ts` `getAgencyByIdForUpdate`: 기준 커밋 (`700b7d26`) 의 본문 (`SELECT … FOR UPDATE` 뒤 `getAgencyById`) 으로 되돌렸다. 앞 구간이 바꾼 `.for('update')` 체인이 `money-audit-charge-approve.spec.ts` 의 mock 형상과 어긋나 붉혔다. 잠금 의미는 같다.
   - `scope-handlers.spec.ts` (M6b 「잠금 뒤 판정」 두 시험): 기대 메서드를 `getSagIdByAgcId` 에서 `getSagIdByAgcIdForUpdate` 로 바꿨다. 앞 구간이 판정 함수를 잠금 읽기로 바꿨기 때문이다. 판정 함수가 잠금 읽기를 쓰는 쪽이 경합에 강하다 (Task 4 의 「잠금 뒤 판정」 취지).
   - `organization.service.sales-write.spec.ts` (기준 커밋부터 있던 삭제 순서 시험 두 개): Task 3 이 바꾼 삭제 잠금 순서 (회원 선행 → 기업 → 조직, 잠금 없는 한 번 읽기 뒤) 에 맞췄다. 기대를 「기업 잠금이 첫 잠금」 에서 「회원 잠금이 기업 잠금보다 먼저」 로, `hasActiveContractsInOrgs` 기대에 잠금 옵션 `true` 를 더했다. 이 파일은 계획서 Files to Change 표에 없다 (편차 참고).
   - 신규 시험 파일 두 개 (격리 MySQL, `m6c-iso-it` 의 합성 시드 사용):
     - `money/money-bulk-scope.itspec.ts` 7 건: 지정 적립 엑셀 전건 거부 (미리보기 + 적용) · 같은 파일의 담당 범위 안 미리보기 대조 · 지정 지급 엑셀 전건 거부 · 기업머니 충전 담당 밖 거부 · 지정 지급 담당 밖 거부 · 지정 지급 단건 담당 밖 거부 · 충전 취소 타 기업 거부.
     - `fc/agency/fc-bulk-scope.itspec.ts` 13 건: 계정 수정 업로드 · 조직 권한 업로드 · 구독 업로드 (엑셀 본체, 활성 계약 합성 행 한 개를 시험 중에만 넣고 지운다) · 구독 해지 · 재직 전환 · 구독 신청 · 회원 정보 수정 · 조직관리자 지정 · 기업FC 등록 · 조직 쓰기 4 경로 · 소속 업로드 (판정 헬퍼 직접) · 관리자 전용 라우트 역할 고정 (메타데이터) · 최고관리자 전용 라우트 가드 고정 (메타데이터).
   - `.claude/meta/2026-10-21-admin-rbac-m6c/jest-itspec.config.cjs`: `maxWorkers: 1` 을 더했다. 병렬 실행이면 두 시험 파일이 같은 합성 기업 (9001) 의 담당 (`sag_id`) 을 바꿔 판정 결과가 흔들린다 (관측: 병렬 실행에서 담당 밖 거부 시험이 통과했다, 시험이 만든 조직 행 1건이 남았다).
   - `.claude/meta/2026-10-21-admin-rbac-m6c/bulk-inventory.md` 신규 (34 행). 게이트 `#8` 의 행 수 · 판정 · 근거 · 시험 칸 검사를 로컬에서 같은 awk 식으로 돌려 통과했다 (rows=34, badJ=0, badK=0, emptyTest=0, emptyReason=0).
3. 검증 (실측):
   - 격리 실행 (`jest-itspec.config.cjs`, 직렬): `money-race` + `agency-fc.race` + `money-bulk-scope` + `fc-bulk-scope` = 37 건 전부 통과 (실패 0, 건너뜀 0). 게이트 `#8` 의 필수 이름 (고정 19 + 행 시험 23) 을 전부 통과 집합에서 확인했다 (missing: []).
   - 단위 시험 (product jest): `money` · `fc/agency` · `auth/access-control` · `agency/organization` 39 suites, 490 건 전부 통과. `analyze-insight-route.spec.ts` 만 붉다 (ENOENT, 착수 전부터, 이 노트의 앞 구간 기록).
   - `tsc --noEmit -p tsconfig.json`: 오류 23 건, 전부 기준선과 같은 spec 파일 (`libs/octover` 13 · `libs/hyphen` 4 · `loss-analysis.repository.spec` 4 · `medication-standard.repository.spec` 1 · `terms.repository.spec` 1). 이번 변경 파일의 비-spec 오류 0.
   - prettier: 이 구간의 신규 · 수정 파일 (시험 두 개, 두 spec, `money.service.ts`, `money-admin.controller.ts`) 은 `--check` 통과. 두 spec 은 기준 커밋에서 이미 정렬된 파일이라 정렬 변경이 이 구간의 변경과 같은 커밋에 실린다 (커밋 때 같이 싣는다).

### 남은 것 (다음 실행이 이어 간다, 순서)
1. Task 6 (증명 6 행 질의 확장) — 가장 큰 남은 항목.
   - 기존 질의 중 범위 절을 받는 것: 지갑 목록 (`getWalletListWithSearch` · `getWalletCountWithSearch`), 결제 목록 (`getPaymentListWithSearch` · `getPaymentCountWithSearch`), 기업 이력 (`getCorpHistoryList` · `getCorpHistoryCount`), 적립 목록 · 개수 (`getIssueList` · `getIssueCount`), 환불 목록 · count (`getRefundListWithSearch` · `getRefundCountWithSearch`). 단건 상세는 판정 함수 (`judgeSingleScope`) 로 본다.
   - 미구현: 분석 목록 · 집계 · 내보내기. `analyze.repository.ts` 의 `getBasicList` · `basicCount` · `getAggregateList` · `getDetailList` 는 범위 절 인자가 없다. 분석 쪽 범위는 지금 1차 거부 (`guardSalesOdd`) 와 단건 판정뿐이다. 계획 Task 6 · `analyze.repository.ts` 「분석 목록 · 집계 · 내보내기 질의의 범위 절」 은 이 구간 전에 구현되지 않았다 — 목록 질의 자체에 절을 넣는 작업이다 (query builder 변경). 이것 없이 「분석 목록 타 기업 행 0」 을 실측할 수 없다.
   - 시험 파일 `money/money-scope-query.itspec.ts` (34 이름) 는 아직 없다. 게이트 `#9` 는 이 파일 두 개 (`money-scope.itspec.ts`, `money-scope-query.itspec.ts`) 를 본다.
2. Task 7 (`scope-enforce.spec.ts` 의 jest.mock ON 음성 · 정상 경로) — 미확인.
3. Task 8 (응답 불변 probe · 상수 OFF) — `after_sha` 가 서버 HEAD 와 같고 착수 sha 와 달라야 하므로 커밋 뒤에만 참이다 (검사 #10 은 커밋 전 붉은 것이 정상).
4. Task 9 (사이드카 정정) — 사이드카 `admin-rbac-m6c.gates.sh` 는 기존 커밋에 있다. 이 구간에서 수정하지 않았다. 검사 #8 · #9 가 원 이름을 쓰는 방식은 위 1 · 2 에서 맞췄다.
5. Task 10 (기준선) — 착수 `700b7d26` 에서 jest 실패 suite 집합 · tsc 정렬본 · 권한 spec 통과 집합을 만든다. 착수 sha 를 가리키는 임시 워크트리가 필요하다 (현행 작업 트리에 대고 만들면 기준이 아니다). 아직 없다.
6. Task 11 (기록 · 백로그 RESOLVED-IN · PRD · 서브모듈 포인터) — 커밋 뒤.
7. 전체 게이트 실행 (`admin-rbac-m6c.gates.sh`) · 구현 Codex 게이트 (2.5.3–2.5.6) · 3.6 · 3.7 restamp · Phase 4 · 5 보고.

### 편차 (이번 구간 추가)
- 「소속 업로드 전건 거부」 는 기업FC 엑셀 업로드 (`upload`) 본체가 아니라 판정 헬퍼 `assertUploadOrganizationsWritableByAdmin` 을 직접 부른다. 본체 엑셀 (열 많음 · 검증 많음) 재생은 다음 실행. 인벤토리 B13 의 「근거」 에 적었다.
- 「관리자 전용 라우트 역할 고정」 · 「최고관리자 전용 라우트 가드 고정」 은 요청 재생이 아니라 라우트 메타데이터 시험이다 (일괄 이연 행 B21 · B25–B27 · B34 가 게이트 `#8` 의 시험 칸을 요구하므로).
- 구독 업로드 시험 (`fc-bulk-scope`) 은 상품 준비 검사 (`subscribeService.assertAgencyInitialPlanProductReady`) 와 다음 변경일 계산 (`getNextChangeDay`) 을 스텁으로 둔다. 범위 판정은 스텁 뒤에 실제로 돈다.
- 「조직 쓰기 담당 밖 거부」 는 조직 서비스를 실 저장소 (격리 `insuniverse`) 로 부르고, 한 시험이 생성 · 수정 · 이동 · 삭제 네 메서드를 모두 부른다 (이름 하나를 네 행이 공유).
- `organization.service.sales-write.spec.ts` 는 계획서 Files to Change 표에 없다. Task 3 이 바꾼 삭제 순서를 이 spec 이 옛 순서로 고정하고 있었다. 기대를 새 순서로 고쳤다 (위 2).
- `scope-handlers.spec.ts` 의 M6b 「잠금 뒤 판정」 두 시험 기대를 `getSagIdByAgcIdForUpdate` 로 고쳤다 (위 2). M6b 산출 자체는 다른 파일이라 건드리지 않았다.
- `money.repository.ts` `getAgencyByIdForUpdate` 를 기준 커밋 본문으로 되돌렸다 (위 2).
- 커밋하지 않았다. 검사 #10 의 `after_sha` 와 Task 11 의 서브모듈 포인터는 커밋 뒤로 미룬다.
- `m6c-iso-it` 컨테이너는 살아 있다 (다음 실행이 이어 쓴다). 합성 시드에 시험이 만든 행은 `BULK-` 접두로 남지 않았다 (확인: 조직 행 3, 담당 9101 · 9102 복원).

### (미검증)
- Task 6 · 7 · 8 · 10 · 11 전부. 게이트 전체 (`#1`–`#19`) 를 돌리지 않았다 (커밋 전 `#10` 은 붉은 것이 정상이고, 기준선 파일이 없어 `#2` 가 멈춘다).
- 「지정 지급 담당 밖 거부」 · 「기업머니 충전 담당 밖 거부」 등 새 시험의 반사실 (잠금 제거 시 위반 재현) 은 실행하지 않았다. 이름 있는 반사실은 기존 경합 파일 (`money-race`, `agency-fc.race`) 에만 있다. 새 엑셀 · 단건 시험은 「담당 밖이면 거부」 의 음성 (판정을 빼면 붉은지) 을 실측하지 않았다.
- 정책 §5-7 (출처 미상 변경) 확인: 서버 워킹트리 변경 파일 중 이 마일스톤과 무관한 것은 없다고 판단했다 (`git diff -w` 로 줄바꿈 전용 변경은 확인하지 않았다 — 다음 실행에서 확인).

### 최종 결과 (구현 재개 3 시점)

- 결과: **halted**. 커밋 없음. implement receipt 없음 (skip-receipt 는 plan 게이트에만 적용, 구현 Codex 게이트는 아직 안 돌렸다).
- 완료: Task 1 · 3 · 4 (코드와 시험) · 5 (인벤토리 34 행과 전건 거부 시험). 격리 37 건 통과, 단위 39 suite 통과, tsc 기준선 23 유지.
- 미완: Task 6 (분석 질의 범위 절 + 34 이름 시험) · 7 · 8 · 9 · 10 · 11, 전체 게이트, 구현 Codex 게이트 · restamp · Phase 4–5.
- 다음 실행은 위 「남은 것」 1번 (분석 질의 범위 절) 부터 잇는다.

## 구현 재개 4 (2026-10-22, 이어달리기 4번째 구간 · 정지 · 커밋 안 함)

상태: **미완 (halted)**. 커밋하지 않았다 (실행 지시). 이번 구간은 상태 측정과 Task 6 의 사실 확인만 했고 코드는 바꾸지 않았다.

### 이번 구간에서 확인한 것 (실측)
1. 사용자 지시 원문: `verify-user-directive.sh` exit 0, 원문 일치 (2026-10-19T02:21:22Z user/external). 상한 3 · 마지막 라운드 skip-receipt · HIGH 는 backlog 는 유효하다.
2. 컨테이너: `m6c-iso-it` 만 이 접두다 (Up 31 분 · 합성 시드 적재 상태 유지). 다른 컨테이너는 건드리지 않았다. 백그라운드 실행 잔존 없음.
3. 워킹트리: 서버 16 파일 변경 (기존 15 + 신규 시험 3 개 중 일부, 아래 목록은 `git status` 기준) · 부모 `M` notes · `??` 메타 디렉터리 · 서브모듈 `m insuniverse-server` (내용 변경). 관리자 · 사이트 clean.
4. 계획서 Task 6 의 분석 질의 범위 절 사실 확인 (`analyze.repository.ts` · `analyze.service.ts` · `analyze-admin.controller.ts`):
   - 분석 목록 · 집계 · 내보내기 질의는 모두 `oddId` (분석 원장 1건) 로 조립된 `where` 를 받는다 (`getBasicList(where, page)` · `basicCount(where)` · `getDetailList(where)` · `getAggregateList(id, dto)`).
   - 호출처는 서비스 안에 30 곳 이상이고 (`analyze.service.ts` 의 ANS 유형별 경로 · B2C 경로 · 내보내기), 공용 `analyze.controller.ts` 와 `analyze-admin-view.controller.ts` 도 같은 함수를 부른다.
   - 관리자 분석 라우트의 범위 판정은 현재 1차 거부 (`guardSalesOdd` · `guardSingleOdd`) 로만 이뤄진다. 목록 질의 자체에는 범위 절 인자가 없다.
   - 따라서 Task 6 의 「분석 목록 · 집계 · 내보내기 타 기업 행 0」 을 실측하려면 (가) 위 질의 네 개와 호출처에 범위 절 인자를 더하고 (나) 분석 행이 있는 격리 시드를 쓰고 (다) 34 이름 시험을 만들어야 한다. 이 셋은 한 구간 분량을 넘는다. 이번 구간에서 시작하지 않았다.
5. `scope-enforce.spec.ts` (Task 7): 파일은 이미 `jest.mock` 으로 상수를 ON 으로 켠다. 13 개 시험 중 음성 · 정상 경로 목록을 대조하지 않았다. 게이트 #7 과 대조하는 것은 다음 구간이 한다.
6. 게이트 스크립트 (`admin-rbac-m6c.gates.sh`, 371 행) 는 set -e 로 검사 #1 부터 한 번에 돈다. 입력 (`M6C_SCHEMA_DIR` · 격리 MySQL 변수) 과 기준선 파일 (`jest-baseline.txt` · `tsc-baseline.txt`) 이 없어서 이번 구간에서는 돌리지 않았다 (Task 10 이 기준선을 만든 뒤 돈다).

### 다음 구간이 이어 갈 순서 (그대로)
1. Task 6: 분석 질의 네 개 (`getBasicList` · `basicCount` · `getDetailList` · `getAggregateList`) 에 범위 절 인자를 더하는 설계를 먼저 정한다. 호출처 30 곳 이상을 한 번에 바꾸지 말고, 관리자 라우트가 쓰는 경로부터 바꾼다. 그 결정 (범위 절을 where 에 합칠지, 별도 인자로 받을지) 을 이 절에 적는다. 그다음 `money-scope-query.itspec.ts` (11 종 × 3 이름 + 집계 합 1) 를 쓴다. 정책 6 · 9 와 충돌하는 결정이 나오면 기록한다.
2. Task 7: `scope-enforce.spec.ts` 13 시험을 음성 · 정상 경로 기준으로 대조 (게이트 #7 과 같은 통과 집합).
3. Task 10 (기준선) → Task 8 (probe 두 빌드, 커밋 뒤) → Task 9 (사이드카 정정 확인) → Task 11 (백로그 17 행 · PRD · 서브모듈 포인터, 커밋 뒤).
4. 커밋 뒤에만 검사 #10 · Task 11 서브모듈 포인터 · 구현 Codex 게이트 (2.5.3–2.5.6) 가 의미를 가진다. 커밋은 사용자 지시에 따라 이 구간에서 하지 않는다.

### 편차 (이번 구간)
- 없음. 설계 결정 (범위 절 위치) 은 아직 내리지 않았다.

### 최종 결과 (구현 재개 4 시점)
- 결과: **halted**. 커밋 없음. implement receipt 없음 (구현 Codex 게이트는 아직 안 돌렸다).
- 완료 (누계): Task 1 · 3 · 4 (코드와 시험) · 5 (인벤토리 34 행과 전건 거부 시험) · Task 2 (측정 사본 · 셀 132).
- 미완: Task 6 (분석 질의 범위 절과 34 이름 시험) · 7 · 8 · 9 (게이트 실행 검증) · 10 · 11, 전체 게이트, 구현 Codex 게이트 · restamp · Phase 4–5.

## 구현 재개 5 (2026-10-22, 이어달리기 5번째 구간 · 정지 · 커밋 안 함)

상태: **미완 (halted)**. 이번 구간은 상태 측정과 Task 6 설계 사실 확인만 했다. 코드는 바꾸지 않았다. 커밋하지 않았다.

### 이번 구간에서 확인한 것 (실측)
1. 컨테이너: `m6c-iso-it` 만 이 접두다 (Up 33 분). 다른 컨테이너 (`pentagi*`, `rs-pms-pg-rsin-local`) 는 건드리지 않았다. 백그라운드 실행 잔존 없음.
2. 워킹트리: 서버 16 파일 변경 · `git diff -w --stat` 16 files · 1385+ · 118- (줄바꿈 전용 변경 판별은 아직 안 했다). 부모 `M` notes · `??` 메타 디렉터리. 관리자 · 사이트 clean.
3. Task 6 분석 질의 구조 (코드 사실):
   - `analyze.repository.ts` 의 `getBasicList` · `basicCount` · `getDetailList` · `getAggregateList` 는 `tn_analysis_basic` · `tn_analysis_detail` 에 `oddId` 조건만 건다. 두 표 모두 소유자 열 (`mem_id` · `agc_id` · `sag_id` · `org_path`) 이 없다. 소유자는 `tn_order_detail` → `tn_order` → `tn_payment` → `tn_subscribe` → `tn_member` → `tn_agency` · `tn_agency_organization` 사슬로만 정해진다 (`getOddScopeRow` 가 이 사슬을 쓴다, M6c Task 1).
   - `analyze.service.ts` 안에서 `basicWhere(oddId, dto, …)` 로 `where` 를 만들어 repository 를 부르는 자리가 30 곳 이상이다 (ANS 유형별 경로 · B2C 경로 · 내보내기 · `getDetailList` 안의 `basicWhere` 재사용).
   - 따라서 행 수준 범위 절은 「소유자 열 조건」 이 아니라 「`odd_id IN (사슬을 조인한 부분질의 중 범위 절에 든 것)`」 형태의 부분질의로 붙여야 한다. `toDrizzleCondition(clauses, cols)` 는 열 객체 맵을 받으므로 부분질의의 `tnAgency.sagId` · `tnMember.agcId` · `tnAgencyOrganization.orgPath` 를 그대로 넘기면 된다.
4. 1차 가드: 관리자 분석 라우트는 `guardSalesOdd` · `guardSingleOdd` 로 odd 의 소유자를 먼저 본다. 이 가드가 통과한 odd 의 행은 모두 같은 `oddId` 로 묶여 있다. 그래서 「분석 목록 타 기업 행 0」 은 행 수준 절이 없어도 odd 수준 가드로 이미 성립한다. Task 6 이 행 수준 절을 요구하는 이유는 정책 9 (목록 · 집계 · 내보내기도 절로 막는다) 다. 이 차이를 다음 구간이 노트에 명시해야 한다.

### 설계 결정 (다음 구간이 따를 것 · 근거 있는 쪽)
- 택한 방식: 분석 공개 메서드 (관리자 라우트가 부르는 `getBasicList` · `getDetailList` · `getAggregateList` · 집계 · 내보내기) 에 선택 인자 `scope?: SQL` 를 더하고, 기본 `where` 와 `and(...)` 로 합친다. 값은 `toDrizzleCondition` 으로 만든 「odd_id IN (범위 부분질의)」 이다. 공개 라우트 (`analyze.controller.ts` · `client/` · `b2c/`) 는 인자를 안 넘겨 응답이 바뀌지 않게 한다.
- 근거: 열 추가 (마이그레이션) 는 금지 범위 밖이고, 반복 호출 30 곳을 모두 바꾸는 것보다 공개 메서드 시그니처 한 층만 바꾸는 쪽이 작다. 공유 가능한 부분은 서비스 `basicWhere` 한 곳에서 합친다.
- 반대 방향 (행 수준 절 없이 odd 가드만으로 Task 6 을 닫는다) 은 계획서 Task 6 의 「(가) 타 기업 행 0」 을 odd 가드의 간접 증명으로 바꾸므로 계획 변경이다. 계획서는 동결 상태이므로 택하지 않았다.
- 이 설계가 정책 6 · 9 와 충돌하지 않는지는 다음 구간이 정책 파일 (`admin-rbac-run-policy.md`) 과 대조해 적는다.

### 남은 것 (다음 구간이 이어 갈 순서)
1. Task 6 구현: 위 설계로 `analyze.repository.ts` 에 범위 부분질의 헬퍼 1개 (`odd_id` 목록) 와 `analyze.service.ts` 의 공개 메서드 `scope` 인자를 더한다. 호출처 30 곳은 기본값 (`undefined`) 으로 두고, 관리자 라우트 경로만 인자를 넘긴다. 격리 시험 `money/money-scope-query.itspec.ts` (질의 11 종 × 3 이름 + 「분석 집계 합 일치」) 를 쓰고 실행한다. 격리 시드에 분석 행이 있는지 먼저 센다 (없으면 합성 행을 시험 안에서만 넣는다).
2. Task 7 (`scope-enforce.spec.ts` 13 시험을 음성 · 정상 경로 기준으로 대조).
3. Task 10 (기준선: 착수 `700b7d26` 임시 워크트리에서 jest 실패 suite · tsc 정렬본 · 권한 spec `pass:` 줄) → Task 8 (probe 두 빌드 · 최종 빌드는 커밋 뒤) → Task 9 (사이드카 정정 관측 · 자가 시험) → Task 11 (백로그 RESOLVED-IN 17 행 · PRD · 서브모듈 포인터, 커밋 뒤).
4. 전체 게이트 `.claude/PRPs/plans/admin-rbac-m6c.gates.sh` (`#1`–`#19`) 실행, 붉은 항목은 원인 고치기 또는 기준선 편차 기록.
5. 구현 Codex 게이트 (2.5.3–2.5.6) → 3.6 · 3.7 restamp → Phase 4 · 5. 커밋은 사용자 지시에 따라 검증 뒤.

### 편차 (이번 구간)
- 없음. 코드 변경 0.

### 최종 결과 (구현 재개 5 시점)
- 결과: **halted**. 커밋 없음. implement receipt 없음.
- 완료 (누계): Task 1 · 3 · 4 (코드와 시험) · 5 (인벤토리 34 행과 전건 거부 시험) · Task 2 (측정 사본 · 셀 132).
- 미완: Task 6 (분석 행 수준 절 · 33 + 1 이름 시험) · 7 · 8 · 9 (게이트 실행 검증) · 10 · 11, 전체 게이트, 구현 Codex 게이트 · restamp · Phase 4–5.

## 구현 재개 6 (2026-10-22, 이어달리기 6번째 구간 · 커밋 안 함)

상태: **미완 (halted at 커밋 의존 단계)**. 커밋하지 않았다 (실행 지시). 검사 #10 과 구현 Codex 게이트는 커밋 뒤에만 의미가 있다.

### 이번 구간에서 끝낸 것
1. Task 6 (증명 6 행 격리 질의) 코드와 시험 완료.
   - `analyze.repository.ts`: `oddScopeOf(scope, oddCol)` 를 더했다. 분석 기본 · 상세 · 내보내기 표에는 소유자 열이 없으므로 주문 상세 → 주문 → 결제 → 구독 → 회원 → 기업 · 조직 사슬로 odd 집합을 부분질의로 만들고 `inArray` 로 붙인다. `getBasicList` · `basicCount` · `getDetailList` · `getAggregateList` · `getOriginalTreatList` 에 선택 인자 `scope` (기본 null = 현행 질의) 를 더했다.
   - `analyze.service.ts`: `getBasicList(oddId, dto, isB2c, scope)` · `getDetailList(asbId, dto, scope)` · `getAggregateList(oddId, dto, isB2c, scope)` · `downloadOriginalList(oddId, res, includeCar, scope)` 가 scope 를 그대로 넘긴다.
   - `analyze-admin.controller.ts`: `analysisScope(req)` (영업 신원만, 상수 OFF 또는 영업이 아니면 null) 를 목록 · 상세 · 집계 · 내보내기 4 라우트에 넘긴다. 머니 컨트롤러 `getSalesScope` 와 같은 모양이다.
   - 시험 `money/money-scope-query.itspec.ts`: 질의 11 종 × 세 이름 + 「분석 집계 합 일치」 = 34 건, 격리 MySQL 에서 34/34 녹색.
   - 기대값은 저장소 질의가 아니라 시드를 직접 세는 SQL 에서 얻는다 (지갑 · 결제 · 적립 · 환불 · 분석 소유 집합 등).
   - 음성 관측: 지갑 목록 질의에서 scope 를 null 로 바꾼 사본의 「지갑 목록 타 기업 행 0」 이 붉다 (Failed 1, 실행 기록 확인). 다른 10 종의 음성은 이번 구간에 관측하지 않았다 (미실행).
   - 시험 중 임시 변경 두 가지: 시드 상세 9811 의 `asd_in_etc` 를 `합성기타` 로 바꿔 ANS009 집계 행을 만들고 afterAll 에 원래 값 (NULL) 으로 되돌린다. `tt_treat_basic` 합성 행 두 건을 넣고 지운다. 두 값 모두 되돌림을 확인했다.
2. Task 7 (강제 ON 시험 정리): 검토만 했다. `scope-enforce.spec.ts` 13 시험은 음성 (일괄 전건 거부 2 · 범위 인자 뺀 음성 · 사전 판정 2 · 신원 없음 · 숫자가 아닌 sagId) 과 정상 경로 · tx 배선 (정상 경로 · 잠금 순서 · 연결 1개 2) 이다. 저장소 절 적용 증명은 Task 6 격리 질의가 맡는다. 파일 변경 없음. 세 파일 jest 96/96 녹색.
3. 게이트 스크립트 정정 두 건 (Task 9 일부):
   - `iso_reset` 를 더했다. 시험군 J8 · J9 · J12 앞에서 격리 DB 를 스키마 · 함수 · 마이그레이션 두 개 · 합성 시드로 다시 적재한다. 관측: 경합 시험 (J8) 이 남긴 적립 행 때문에 기존 M6b 머니 목록 시험 (`money-scope.itspec.ts`) 이 35/37 로 붉었다. 재적재 뒤 J9 37/37 녹색. 새 환경 변수 `M6C_IT_MYSQL_CONTAINER` (격리 컨테이너 이름) 가 필요하다.
   - 검사 #14 의 토큰 패턴이 자기 파일의 `ghp_` 문자열과 맞았다 (자기 참조). 패턴을 `gh[p]_` 로 바꿨다. 같은 검사가 가리키는 대상 (계획서 · 사이드카 · 메타) 에는 토큰이 0 건이다.
4. Task 10 기준선: 착수 서버 트리 (`gk-m6c-before`, `700b7d26` 과 소스 cmp 일치) 를 `/tmp/m6c-base-srv` 로 펼쳐 jest 전체 · tsc 를 돌렸다 (`.git` 아래 경로는 jest 가 파일을 찾지 못해 쓰지 않았다).
   - Redis: 격리 컨테이너 `m6c-iso-redis` (127.0.0.1:6380, redis:7). 실 redis 시험 (dispatch 3 건) 은 이 포트가 있어야 녹색이다.
   - 결과: 스위트 235 · 실패 스위트 8 · 실패 시험 7 · 건너뜀 스위트 4 (live · mysql 조건부). 실패 8 스위트는 전부 `.claude/plans/...` 픽스처 부재 (ENOENT 6) 와 `drizzle.utill` 1 · `insight-view` 의 픽스처 1 이다. 픽스처는 워크트리에 없다 (착수에서도 같다).
   - 산출: `jest-baseline.txt` (헤더 `# base-sha: 700b7d26…`, `# failed-count: 8`, suite 줄 8), `tsc-baseline.txt` (`# errors: 23`), `prior-specs-baseline.txt` (`auth/` 아래 통과 시험 483 줄, `pass:` 형식).
5. 타입 검사: 현재 트리 `tsc --noEmit` 오류 23 건, 기준선과 줄 단위로 같다 (차이 0).
6. 포맷: 이번 구현에서 바꾼 HEAD 정렬 파일 여덟 개에 `prettier --write` 를 돌렸다 (`scope-monitor.ts` · `scope-monitor.spec.ts` · `analyze.service.ts` · `analyze-admin.controller.ts` · `organization.service.ts` · `organization.race.itspec.ts` · `organization-admin.controller.ts` · `money-scope-query.itspec.ts`). 커밋할 때 같이 싣는다. `analyze.repository.ts` 는 HEAD 자체가 prettier 비정렬이라 파일 전체에 돌리지 않았다 (이번에 넣은 줄 일부가 80 자를 넘는다. 커밋 전 손으로 줄바꿈).
7. 게이트 실행:
   - 전체 스크립트 (실제 `.claude/PRPs/plans/admin-rbac-m6c.gates.sh`) 는 검사 #1–#9 를 통과하고 #10 에서 멈춘다 (`rows-after` 와 `probe-summary` 가 아직 없다 · 커밋 뒤에만 참).
   - 진단 사본 (#10 블록만 뺀 사본, 저장소 밖 gitdir 아래) 은 #1–#9 · #11–#19 를 모두 통과해 `GATES_OK m6c` 를 냈다. 사본 실행 결과 (J7 96/96 · J8 37/37 · J9 37/37 · J12 9 건 · 검사 #5 셀 132) 는 gitdir 아래 로그에 있다.

### 미완 (커밋 뒤 또는 다음 구간)
- 검사 #10: 착수 빌드 (`GK_BUILD=before`) 재실행 · 최종 빌드 (`GK_BUILD=after`, HEAD) · `probe-summary-m6c.txt`. 격리 비밀 파일 (`GK_JWT` · `GK_AES` · `GK_IV`) 은 이번 구간에 새로 만들지 않았다 (이전 측정 파일을 지웠다). 두 빌드는 같은 비밀로 돌려야 행 파일을 비교할 수 있다.
- 구현 Codex 게이트 (2.5.3–2.5.6) · 3.6–3.7 restamp · Phase 4–5 보고. 커밋 뒤에 한다.
- Task 11: PRD 마일스톤 6 행 판정, 서버 서브모듈 포인터 갱신 (커밋 뒤).
- Task 8 의 상수 OFF · 인터셉터 계수 (#11 · #17) 는 진단 실행에서 녹색이다.

### 편차 (이번 구간)
- 커밋하지 않았다 (실행 지시). 검사 #10 과 서브모듈 포인터는 커밋 뒤로 미룬다.
- 분석 관리자 뷰 컨트롤러 (`analyze-admin-view.controller.ts`, 경로 `admin-view/analyze/:oddId/{basic,detail,aggregate}`) 는 scope 를 넘기지 않는다. 이 컨트롤러는 인증 가드가 없고 (`@ApiOperation` 설명: 「인증 없음」), 기존 상태 그대로다. 상수를 켜는 M9 전에 이 경로를 어떻게 할지 판단이 필요하다. 이번 구간에서 바꾸지 않았다.
- 기업 (corp) 신원의 목록 절은 만들지 않았다. `guardCorp` 가 odd 단위로 막는다 (정책 9 는 영업 · 기업 목록 절을 요구하지만, 기업 목록은 기존 머니 · 분석 목록 경로에서 이미 서비스가 scopeAgcId 로 거른다. 이 구간에 확인만 했다).
- 「단건 상세」 kind 는 분석 상세 (asb) 목록 질의로 정의했다. 머니 단건 읽기는 Task 1 의 판정이 맡는다.

### (미검증)
- 시드 상세 9811 의 `asd_in_etc` 를 바꾼 상태에서 「분석 집계 행 수 하한 1」 이 녹색인 것은 합성 값에 기댄다. 실제 분석 데이터의 ANS009 분포는 검증하지 않았다.
- 실 DB 의 주문 · 결제 흐름 (구독 · 주문 연결) 은 시드의 값이다. 운영과 같은 조인 분포는 증명하지 않는다.
- 분석 목록 · 집계 · 내보내기 · 단건의 음성 (scope null 사본) 은 지갑 목록 한 건만 관측했다.

### 최종 결과 (구현 재개 6 시점)
- 결과: **halted**. 커밋 없음. implement receipt 없음 (구현 Codex 게이트는 커밋 뒤).
- 완료 (누계): Task 1 · 2 · 3 · 4 · 5 · 6 · 7 (검토) · 10 (기준선 산출) · 9 의 게이트 정정 · 타입 검사 · 진단 게이트 녹색.
- 남음: 검사 #10 (커밋 뒤 두 빌드 · 프로브), Task 8 의 최종 빌드, Task 11 (PRD · 서브모듈 포인터), 구현 Codex 게이트 · restamp · Phase 4–5.

## 구현 재개 7 (2026-10-22, 이어달리기 7번째 구간 · 마무리 · 커밋 안 함)

상태: **미완 (halted at 커밋 의존 단계)**. 커밋하지 않았다 (실행 지시). 검사 #10 · 구현 Codex 게이트 · restamp · Phase 4–5 는 커밋 뒤에 한다.

### 이번 구간에서 한 것
1. 상태 측정: 격리 컨테이너 `m6c-iso-it` · `m6c-iso-redis` 만 살아 있다 (이 구간 시작 시점). 백그라운드 게이트 실행 잔존 없음 (완료 신호 파일 `gatekit-m6c-gates-diag.done` 확인). 다른 접두 컨테이너는 건드리지 않았다.
2. 지시 원문 확인: `verify-user-directive.sh` exit 0 (2026-10-19 사용자 입력, 라운드 상한 3 · 마지막 라운드 skip-receipt · HIGH 는 backlog).
3. 서버 서브모듈 줄바꿈 판별 (지시 3번): 변경 16 파일 전부 `git diff -w` 에서도 변경이 남는다 (의미 변경). CRLF 줄 0 (HEAD · 작업 트리 모두). 되돌린 파일 없음.
4. 진단 게이트 재실행 (검사 #1–#9 · #11–#19, #10 제외 사본): `GATES_OK m6c`, `GATES_RC=0`. 사본 생성 시각(12:13)이 마지막 코드 · 게이트 스크립트 수정(12:11–12:12) 뒤라서 현재 트리를 덮는다.
5. 구현 Codex 게이트 원장 확인: `mccp-implement-codex` 의 `rounds_so_far=0` (아직 시작 안 함). 계획 게이트 봉인(seal)은 `found=true`, `cap=3`.
6. 백로그: 「admin-rbac-m6c」 RESOLVED-IN 17 행이 이미 append 돼 있다 (검사 #13 통과). 이 구간에서 추가하지 않았다.

### 남은 것 (커밋 뒤 순서)
1. 커밋 (사용자 지시 뒤, 전역 규칙: 한국어 subject · body · 100 자 · 스테이징 diff 의 호스트 절대경로 개수가 0 (세는 명령은 이 노트에 적지 않는다)). 포맷 정렬 여덟 파일 (`prettier --write` 결과) 과 `analyze.repository.ts` 의 80 자 초과 줄 손줄바꿈은 같은 커밋에 싣는다.
2. 검사 #10 (`GK_BUILD=before` · `GK_BUILD=after` 행 파일 cmp · `probe-summary-m6c.txt`). `after_sha ≠ 착수 sha` 는 커밋 뒤에만 참이다.
3. 전체 게이트 `.claude/PRPs/plans/admin-rbac-m6c.gates.sh` #1–#19 재실행.
4. Task 11: PRD 마일스톤 6 행 판정 (outcome · 선행조건 · 증명 6 행) 과 서버 서브모듈 포인터 갱신 (`insuniverse-server` HEAD 가 `700b7d26` 에서 바뀌어야 한다).
5. 구현 Codex 게이트 (2.5.3–2.5.6) → 3.6 · 3.7 restamp → Phase 4–5 보고.

### 편차 (이번 구간)
- 커밋하지 않았다 (실행 지시). 그래서 #10 · 포인터 · Codex 게이트를 이번 구간에서 돌리지 못했다.
- 진단 사본은 #10 을 뺀 상태로 돌렸다 (DIAG 줄에 기록).

### (미검증)
- 검사 #10 (응답 불변 · 두 빌드 행 파일 바이트 비교) 은 아직 한 번도 돌지 않았다.
- 격리 DB 시험은 합성 시드 기준이다. 운영 복제본 1회 실행은 사용자 몫이다 (운영 검증 규칙 2026-10-21).

### 최종 결과 (구현 재개 7 시점)
- 결과: **halted**. 커밋 없음. implement receipt 없음 (구현 Codex 게이트 미실행 · rounds 0).
- 완료 (누계): Task 1 · 2 · 3 · 4 · 5 · 6 · 7 (검토) · 10 (기준선 산출) · 9 의 게이트 정정 · 타입 검사 (tsc 23 건 기준선과 줄 단위 동일) · 진단 게이트 녹색 (두 번).
- 남음: 위 「남은 것」 1–5.

## 구현 재개 8 (2026-10-22, 이어달리기 8번째 구간 · 마무리 · 커밋 안 함)

상태: **구현 마감 (커밋 전)**. implement 는 커밋하지 않는다 (운용 정책: prp-implement 커밋 금지 → 별도 세션 code-review → prp-commit). 검사 #10 은 커밋 뒤에만 참이 된다 (`after_sha ≠ 착수 sha`). 나머지 커밋 뒤 항목은 아래 「남은 것」.

### 이번 구간에서 한 것
1. 상태 측정: 격리 컨테이너 `m6c-iso-it` · `m6c-iso-redis` 만 살아 있었다. 백그라운드 게이트 잔존 없음.
2. 지시 원문: `verify-user-directive.sh` exit 0 (2026-10-19 사용자 입력 · 라운드 상한 3 · 마지막 라운드 skip-receipt · HIGH 는 backlog).
3. 서버 서브모듈 줄바꿈 판별: 변경 파일 전부 `git diff -w` 에서도 의미 변경이 남는다. 되돌린 파일 없음.
4. 검사 #10 의 전제 확인: 이전 측정에 쓴 격리 비밀 파일이 지워져 있었다. 새 비밀 파일 (권한 600, gitdir 아래) 을 만들어 착수 빌드 (`GK_BUILD=before`, 착수 트리 재사용) 와 최종 빌드 (`GK_BUILD=after`, 현재 워킹트리 nest build) 를 같은 비밀로 다시 돌렸다.
   - 결과: 행 6346 줄 (행 6336 · 쓰기 6), 착수 빌드 행 파일과 최종 빌드 행 파일이 바이트로 같다 (`cmp` rc 0). 응답 불변 확인.
   - 착수 빌드 행 파일은 이전 측정 `rows-before-m6c.txt` 와 바이트로 같다 (비밀과 무관함을 확인).
   - 산출: `rows-after-m6c.txt` · `probe-summary-m6c.txt` (after_sha 는 아직 착수 sha 와 같다 · 검사 #10 은 커밋 뒤에 통과한다).
5. 진단 게이트 (검사 #1–#9 · #11–#19, #10 제외 사본): 수정 뒤 두 번 돌려 `GATES_OK m6c` · `GATES_RC=0`.
6. 타입 검사: `npx tsc --noEmit -p tsconfig.json` 오류 23 건 (기준선 23 건 · 비-spec 오류 0).
7. 구현 Codex 게이트 (2.5.0 seal · 2.5.3 wrapper · 원장 `mccp-implement-codex__admin-rbac-m6c`, cap 3, rounds 3 소진).

### 구현 Codex 게이트 판정 (2.5.4 · 3 라운드, 마지막 라운드 skip-receipt 규칙 적용)

## Codex Implementation Review

- 호출: `codex-invoke.js adversarial-review` (fail-closed wrapper) · 대상: 서버 워킹트리 diff (base `700b7d26`)
- 라운드 수: 3 (cap 3 소진 · 원장 `rounds_so_far=3`)
- 합치 결론: 라운드 3 `approve` (CRITICAL · HIGH 없음). 1차 HIGH 는 수정했다. 1차 MEDIUM · 2차 MEDIUM 은 이연 (M9 체크리스트 · 강제 상수 OFF 라 현재 응답 불변에 영향 없음). 라운드 3 은 이연 항목을 제외하고 본다고 focus 에 명시했다.
- YAGNI Triage:
  - 라운드 1 HIGH: `orgPermissionUpload` 가 대상 조직 판정을 트랜잭션 밖에서만 하고 잠금 뒤 경로를 다시 보지 않는다 (조직이 부분트리 밖으로 옮겨진 사이에 소속 · 조직관리자 지정이 들어갈 수 있다). **수정**: `assertUploadOrganizationsWritableByAdmin` 에 `tx` 를 더하고, 강제 ON 이고 tx 가 있을 때 조직 잠금 뒤에 한 번 더 판정한다 (`fc-agency.service.ts`). 잠금 전 판정은 그대로 둔다 (상수 OFF 동작 불변). 라운드 3 에서 해소 확인.
  - 라운드 1 MEDIUM: `createIssue` 가 회원 → 기업 순서로 잠그고 `createGrant` 는 기업 → 회원 순서다 (역순 교착 → InnoDB 1213 · 요청 실패, 조용한 손상 아님). **이연**: 백로그 1770 (머니 지급 교착) 과 같은 계열이다. 계획의 이연 표 (1770 행) 와 M9 체크리스트 항목으로 넘긴다. 근거: 계획 결정 4 는 소속 쓰기와 조정 경로의 회원 → 기업 한 방향을 정했고, 지급 경로 순서 변경은 범위 밖이다. 이 항목은 `createIssue` 가 새로 같은 쌍에 들어온 것이므로 M9 체크리스트 문구에 함께 적는다.
  - 라운드 2 MEDIUM: `deleteSubtree` 가 기업 잠금 뒤 새로 늘어난 부분트리 회원을 잠근다. 동시 소속 쓰기와 교착 → 요청 실패 (추론, 재현 안 함). **이연**: 상수 OFF 에서 잠금 사슬이 없다. 방향은 기록한다 (기업 잠금 뒤 조직 집합 비교를 회원 잠금 앞으로 옮긴다. 회원만 바뀌는 경로는 남는다). M9 체크리스트 항목이다.
  - 라운드 3: `approve`. 신규 CRITICAL · HIGH 없음.
- 기각: 없음.

### 남은 것 (커밋 뒤 순서 · 이 구간에서 하지 않았다)
1. 커밋 (prp-commit 세션 · 한국어 subject · body · 100자 · 스테이징 diff 의 호스트 절대경로 개수가 0 (세는 명령은 이 노트에 적지 않는다)). 포맷 정렬 여덟 파일과 `analyze.repository.ts` 의 80자 초과 줄은 같은 커밋에 싣는다 (`style(...)` 커밋으로 분리할지는 커밋 세션이 정한다).
2. 검사 #10 · 전체 게이트 #1–#19 재실행 (`.claude/PRPs/plans/admin-rbac-m6c.gates.sh`). #10 은 `after_sha` 가 커밋 뒤 HEAD 와 같고 착수 sha 와 달라야 한다.
3. Task 11: PRD 마일스톤 6 행 판정 (outcome · 선행조건 · 증명 6 행) 과 서버 서브모듈 포인터 갱신 (커밋 뒤).
4. 3.6 · 3.7 restamp (구현 시점 디자인 신호 없음 · 서버 전용 diff 라 무신호 예상) → Phase 4 검증 → Phase 5 보고.
5. implement receipt 는 restamp 가 끝난 뒤 보고서 커밋에 싣는다 (메모 `mccp-implement-receipt-commit-after-restamp`).

### 편차 (이번 구간)
- 격리 비밀 파일을 새로 만들었다 (이전 파일 삭제됨). 착수 빌드와 최종 빌드가 같은 비밀을 쓴다. 비밀 값은 기록하지 않는다.
- 2차 Codex 결과 이연 (MEDIUM) 과 1차 Codex 결과 이연 (MEDIUM) 은 HIGH 가 아니므로 backlog 에 넣지 않고 이 notes 와 M9 체크리스트로 남긴다 (사용자 지시: HIGH 만 backlog).

### (미검증)
- 격리 DB 시험은 합성 시드 기준이다. 운영 동일 환경 (복제본) 1회 실행은 사용자 몫이다 (운영 검증 규칙 2026-10-21).
- 2차 MEDIUM 의 교착은 추론이다 (재현 안 함).
- `orgPermissionUpload` 의 잠금 뒤 판정 (1차 HIGH 수정) 은 강제 ON 에서만 돈다. 강제 ON 음성 시험 (Task 7 검토) 은 기존 시험을 그대로 둔다. 이 수정을 증명하는 새 음성 시험은 없다 (미검증).

Open Questions: DIVERGENT_UNRESOLVED (R1 MEDIUM · R2 MEDIUM 이연). 마지막 라운드 판정은 approve 지만, 이연한 두 MEDIUM 이 미해소다. converged 로 봉인하면 교차 모델 중복 검토가 닫혀 PR-Codex 가 건너뛰어진다. 그래서 divergent 로 기록한다 (2.5.3 triage 규칙).

### 2.5.6 · 3.x · Phase 4–5 (이번 구간)
- 2.5.5b (impeccable): 서버 워킹트리 diff 는 `apps/api/src` 의 `.ts` 20 개뿐이다 (프런트 surface 0). 디자인 신호 없음 → silent skip 으로 본다. 3.6 (finish routing) · 3.7 (design grounding) 은 신호가 없어 restamp 대상이 없다 (미검증: 검출기가 서브모듈 diff 를 못 보는 메모 `impeccable-detect-blind-to-submodules` 와 별개로, 파일 목록으로 직접 확인).
- 2.5.6 receipt write: `.claude/receipts/mccp-implement-codex/admin-rbac-m6c.json` (`receipt_hash` `sha256:1b2bce8a…cb752`, `subject_hash` `sha256:42d1fe8c…e9a6`, `--codex-verdict divergent`). 환경 변수 없이 먼저 쓴 것이 받아들여졌다. 그래서 `MCCP_SKIP_RECEIPT` · `--skip-reason` 은 쓰지 않았다. 이 receipt 는 커밋하지 않는다 (restamp 가 없어도 커밋 세션이 보고서 커밋에 싣는다).
- 주의: 이 receipt 은 plan-codex receipt 이 없는 상태에서 써졌다 (계획 게이트 skip-receipt). `validate` 는 앞 게이트 부재로 실패할 수 있다 (미검증 · 커밋 세션에서 확인).
- Phase 4 (검증 요약): 진단 게이트 #1–#9 · #11–#19 녹색 (수정 뒤 재실행) · tsc 오류 23 (기준선 동일) · 응답 불변 행 파일 `cmp` 일치 (`#10` 의 나머지 조건 충족 · `after_sha ≠ 착수 sha` 만 커밋 뒤).
- Phase 5 (보고): 구현은 커밋 전 상태로 마감한다. 커밋 · 검사 #10 · 전체 게이트 · Task 11 (PRD 6 행 판정 · 포인터) 은 커밋 세션 몫이다.

### 최종 결과 (구현 재개 8 시점)
- 결과: **구현 마감 (커밋 전)**. 커밋 없음. implement receipt 있음 (divergent · 위 경로).
- 완료 (누계): Task 1 · 2 · 3 · 4 · 5 · 6 · 7 (검토) · 8 (응답 불변 행 파일 일치, 이 구간) · 10 (기준선) · 구현 Codex 3 라운드 (1차 HIGH 수정).
- 남음 (커밋 세션): 커밋 · 검사 #10 · 전체 게이트 #1–#19 · Task 11 (PRD 6 행 · 서브모듈 포인터) · 최종 fable·codex 리뷰 (마일스톤 끝).
- 이연 (M9 체크리스트 입력): 1770 계열 (createIssue 회원 → 기업 순서 vs createGrant 기업 → 회원, 1차 MEDIUM) · deleteSubtree 확장 회원 잠금 (2차 MEDIUM).

### 포맷 정렬과 재검증 (구현 재개 8 마지막)
- `fc-agency.service.ts`: 1차 수정 한 줄이 80자를 넘어 `prettier --write` 를 돌렸다. 이 파일은 HEAD 가 prettier 정렬 상태였으므로 바뀐 줄은 이번 수정의 줄뿐이다 (`prettier --check` 통과).
- `analyze.repository.ts`: HEAD 자체가 prettier 정렬 상태가 아니다 (이 파일의 기존 줄 42 곳). 전체 `--write` 는 무관한 줄을 바꾸므로 돌리지 않았다. 이번 구현 줄 세 곳 (`.where(scope ? and(…oddScopeOf…) : …)` 3 행, 858 · 901 · 966 줄 부근) 만 prettier 모양으로 손줄바꿈했다. 의미 변화 없음 (식 그대로).
- 커밋 세션 몫: 이 두 파일의 포맷 결과를 같은 커밋에 싣는다. `analyze.repository.ts` 는 기존 정렬 위반 42 곳을 그대로 둔다 (별도 `style` 커밋 여부는 커밋 세션이 정한다).
- 재검증: 포맷 정리 뒤 진단 게이트 #1–#9 · #11–#19 다시 녹색 (`GATES_OK m6c` · `GATES_RC=0`), tsc 오류 23 (기준선과 줄 단위 동일). 격리 컨테이너는 재검증 뒤 내렸다 (`m6c-iso-` 접두 두 개만). 격리 비밀 파일은 지웠다.
- 서버 셰이프: 최종 Codex 라운드 3 (approve) 은 포맷 정리 전 워킹트리를 봤다. 포맷 정리는 식을 바꾸지 않으므로 그 판정을 유지한다 (미검증: 재리뷰 없음).
- 체인 확인 (`validate --command mccp:prp-implement --plan <계획서>`, 우회 env 사용): `stale []` · `blocking []` · `missing` 은 `mccp-plan-codex` 1 건뿐 (계획 게이트 skip-receipt 로 의도된 부재). 판정은 커밋 세션이 다시 본다.

## 코드 리뷰

라운드 1 (code-review, 판정 · 흡수 · 2026-10-22, 커밋 전 워킹트리). 지적 25건 (같은 자리를 가리키는 중복은 한 행에 묶었다). 흡수 20 · 기각 4 · 부분 흡수 1 (HIGH 1, 백로그 기록). CRITICAL 0.
반사실은 수정 전 결함 줄을 되돌린 사본에서 시험이 붉은 것을 실제로 돌려 확인했다 (잠금 순서 · 재판정 · 사전 판정 · 전체 대상 판정 · 엑셀 사전 판정).

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | code-review | HIGH | 삭제가 기업 잠금 뒤 회원 잠금을 다시 건다 (역순, organization.service.ts) | 흡수 | 부분트리 후보 조직을 트랜잭션 밖에서 읽고, 회원 선행 잠금 뒤 기업 잠금, 회원 재확인은 잠금 없는 일관 읽기. 반사실: 재확인을 잠금 읽기로 되돌리면 sales-write 순서 시험 2건 붉음 | 1 |
| R1 | code-review | HIGH | 1차 HIGH 수정 (잠금 뒤 재판정) 을 증명하는 음성 시험이 없다 | 흡수 | fc-bulk-scope.itspec.ts 「조직 권한 업로드 잠금 뒤 재판정」 (잠금 직전 조직을 부분트리 밖으로 옮김 · Forbidden · 소속 쓰기 0). 반사실: 재판정 줄 제거 시 붉음 | 2 |
| R1 | code-review | HIGH | 경합 시험 복제본이 서비스를 부르지 않는다 (agency-fc.race.itspec.ts:230 · 지정 적립 엑셀 적용 블록 미실행) | 부분 흡수 (open) | 서비스 호출 시험 추가: 조직 권한 업로드 잠금 뒤 재판정 · 지정 적립 엑셀 범위 안 적용 (money-bulk-scope). 영업 신원의 커밋 대기 뒤 거부 형태와 복제본 시험은 남음 → 백로그 HIGH | 5 |
| R1 | code-review | MEDIUM | 잠금 앞 일관 읽기 금지 불변식 시험을 지우고 반대 동작을 허용 (organization.service.ts:392) | 흡수 | 잠금 앞 읽기를 트랜잭션 밖으로 옮김. 시험 「잠금 없는 읽기가 없고, 회원 재확인만 잠금 뒤 잠금 없는 읽기다」 복원 (ops 순서) | 2 |
| R1 | code-review | MEDIUM | 재시도 · 409 주석이 실제와 다르고 재시도 · 409 시험이 없다 (organization.race.itspec.ts:332) | 흡수 | 주석을 실제 대기 지점 (회원 선행 잠금) 으로 고침. 재시도 1회 성공 · 두 번째 변경 409 는 단위 시험 (sales-write, 재확인 spy). 실 DB 에서 재시도 경로를 결정적으로 세우지 못함 (미검증: 회원 선행 잠금의 인덱스 범위가 삽입을 막는다) | 7 |
| R1 | code-review | MEDIUM | 구 순서 반사실이 원시 SQL 두 개다 (organization.race.itspec.ts:402) | 흡수 (이름 낮춤) | 시험 이름을 「MySQL 일반 성질」 로 낮추고 주석에 제품 경로가 아님을 적음. 제품 경로 반사실은 미흡수 (미검증) | 7 |
| R1 | code-review | MEDIUM | analyze.repository.ts 에 범위 무관 포맷 변경 혼입 (358행) | 흡수 | 범위 hunk 10개만 남기고 나머지 되돌림 (diff 358 → 122행). HEAD 자체가 정렬 상태가 아니다 (기존 42곳) — 그 자리는 그대로 둠 | 1 |
| R1 | code-review | MEDIUM | 담당 밖 메시지가 잔액 · 존재 여부를 싣는다 (money.service.ts 지정 적립 · 엑셀) | 흡수 | assertSalesScopeOfMemberPre: 메시지 생성 전에 판정, 범위 밖 · 없는 회원은 같은 403. 시험 「사전 거부는 존재 · 잔액을 새지 않는다」. 반사실: 판정 줄 제거 시 붉음 | 2 |
| R1 | code-review | MEDIUM | 잠금 뒤 판정이 잠금 전에 읽은 기업 번호를 쓴다 (money.service.ts:519) | 흡수 | lockMembersByIds 가 잠금 읽기 값 (memId, agcId) 을 돌려주고 그 값으로 기업 잠금 · 판정 | 1 |
| R1 | code-review | MEDIUM | 지정 적립 · 엑셀 적립이 범위 판정 전에 전 회원 행을 잠근다 (ALL 대상) | 흡수 | 잠금 전 사전 판정 (기업별 캐시, 잠금 없음). 시험 「전체 대상 담당 밖: 트랜잭션을 열지 않고 거부한다」. 반사실: 사전 판정 줄 제거 시 트랜잭션이 열려 붉음 | 2 |
| R1 | code-review | MEDIUM | 결제 취소의 PG 호출 · 잠금이 상수 OFF 에서도 항상 켜진다 (money.service.ts:2388) | 흡수 | scoped = 상수 ON 과 영업 신원. OFF · 비영업은 현행 흐름 (트랜잭션 · 잠금 없음, PG 실패 로그만 더함). money.service.cancel-scope.spec.ts 2건. 영업 판정 경로의 PG 잠금 보유는 정책 5 (계획 승인 항목) | 1 |
| R1 | code-review | MEDIUM | 단건 가드 assertSingleOrg 가 code 없는 plain ForbiddenException (organization-admin.controller.ts:74) | 흡수 | permissionDenied('scope', …) 로 교체 (36009c72 와 같은 규약). 전용 시험 없음 (미검증). 기존 organization-admin.scope.spec 8건 통과 | 2 |
| R1 | code-review | MEDIUM | 지정 적립 엑셀 선검사의 행마다 순차 질의 (N+1) · !== 비교 (money.service.ts:4080) | 흡수 | 기업별 sagId 캐시 + judgeRowScope (사후 대조 루프 삭제) | 1 |
| R1 | code-review | LOW | PG 취소 실패 로그에 거래번호(TID) 가 없다 (money.service.ts:2492) | 흡수 | 로그에 mpyTid 추가 (pg.tid). 로그 시험은 없음 (미검증). PG 취소 식별자 · 취소 시각은 미포함 (이연: 응답 본문 변경은 PG 클라이언트 범위) | 2 |
| R1 | code-review | LOW | 「커밋 실패」 시험이 커밋 실패를 주입하지 않는다 (money-race.itspec.ts:211) | 기각 | 시험 이름은 계획 고정 이름 (gate #8 필수 이름). 주입은 DB 쓰기 실패 (updateMemberMoney), 커밋 단계 주입은 드라이버 훅이 필요해 이연. 이름 변경은 계획 수정 대상 | 7 |
| R1 | code-review | LOW | createIssue (회원 → 기업) 와 createGrant (기업 → 회원) 의 역순 쌍 | 기각 (이연) | 범위 2 는 소속 쓰기 ↔ 조직 삭제 쌍만 명시한다. 정책 4 가 머니 지급 경로 순서를 범위 밖으로 뺐다. M9 체크리스트에 교착 시험 항목 추가 (아래) | 1 |
| R1 | code-review | LOW | 잠금 뒤 판정이 잠금 전 기업 번호 (money.service.ts:516, typescript 쪽) | 흡수 | (위 MEDIUM 「잠금 뒤 판정이 잠금 전에 읽은 기업 번호」 와 같은 수정) | 1 |
| R1 | code-review | LOW | 엑셀 사전 판정이 judgeRowScope 대신 !== (money.service.ts:4088) | 흡수 | judgeRowScope 로 대체 (사전 판정 한 곳) | 1 |
| R1 | code-review | LOW | iso_reset 이 컨테이너 접두를 검사하지 않고 DROP DATABASE 를 실행 (gates.sh) | 흡수 | case 가드 (m6c-iso-* 가 아니면 exit 1). gates 를 다시 돌려 통과 | 4 |
| R1 | code-review | LOW | eslint prefer-const (money-race.itspec.ts:37) | 흡수 | const 로 바꿈 (eslint 는 이 파일 오류 0) | 3 |
| R1 | code-review | LOW | 새 tx 인자가 tx?: any (money.service.ts:2593) | 기각 | 저장소 MoneyDb 가 any 라 타입을 좁혀도 컴파일이 바뀌지 않는다. 새 취소 본문은 runInTransaction 의 tx 타입 (MoneyTxType) 으로 좁혔다. 호출 누락은 영업 판정 경로 시험이 본다 | 1 |
| R1 | code-review | LOW | PG 가 명시적으로 거절한 경우에도 ERROR 로그 (money.service.ts:2492, typescript 쪽) | 기각 | 응답만으로 확정 거절과 유실을 가르는 것은 smartro 클라이언트 변경 범위다 (정책 5 잔여 위험). 로그 키 의미는 유지 · 미검증 | 1 |
| R2 | code-review | HIGH | 지정 적립이 호출자 기업 번호 (dto.agcId) 를 회원 기업과 맞춰 보지 않는다 (money.service.ts:565). 같은 자리 typescript MEDIUM 과 묶음 | 흡수 | 영업 판정 경로: 잠금 뒤 dto.agcId 가 회원 기업과 다르면 403, 배치 기업은 회원 기업이 하나면 그 값, 여럿이면 null. 시험: money.service.write-scope.spec.ts 「기업 번호가 회원 기업과 다르면 거부」 (수정 전 resolved 로 붉음, 수정 뒤 녹색). money-bulk-scope.itspec.ts 「기업 번호 불일치」 (반사실: 가드와 헤더 재계산을 빼면 붉음, 행 증가 실측) | 1 |
| R2 | security | MEDIUM | 단건 기업 키가 헤더 기업 하나만 보고 대상 회원의 기업과 묶이지 않는다 (money.service.ts:384, money.repository.ts:1520) | 흡수 (읽기 쪽) | getIssueScopeRow 가 대상 회원 기업 집합을 돌려주고, 전부 헤더와 같을 때만 기업 키를 넘긴다 (아니면 null, deny). 시험: money.service.write-scope.spec.ts 「섞이면 기업 키 null」 (수정 전 붉음, 수정 뒤 녹색). money-bulk-scope.itspec.ts 「단건 기업 키」 (실 질의. 반사실: 조건을 빼면 붉음) | 1 |
| R2 | code-review | MEDIUM | lock-order.md · README.md 가 없다 (meta 디렉터리) | 흡수 | 두 문서를 만들었다. lock-order.md 는 쓰기 경로 12행의 잠금 순서와 현행 상태 · 이연 3건, README.md 는 기준 · 파일 · 환경 변수 이름 · 재현 명령. 게이트는 두 파일의 존재를 보지 않는다 (존재 검사 추가는 다음 계획, 미검증) | 2 |
| R2 | code-review | LOW | 환불 처리가 상수 OFF · 비영업에서도 트랜잭션과 행 잠금을 연다 (money.service.ts processRefund) | 흡수 | 결제 취소와 같은 조건 (SCOPE_ENFORCE_SALES_CORP && !!salesOwner) 으로 나눴다. OFF · 비영업은 트랜잭션 없이 HEAD 와 같은 순서. 시험: money.service.write-scope.spec.ts 「상수 OFF: 트랜잭션과 행 잠금 없이」 (수정 전 runInTransaction 호출 1 로 붉음, 수정 뒤 녹색) | 1 |
| R2 | typescript | MEDIUM | 환불 처리에 영업 신원의 잠금 전 사전 판정이 없다 (money-admin.controller.ts refund/process) | 흡수 | 컨트롤러에서 assertSalesOwnedAgc (환불 행의 소유 기업 → 담당 영업) 를 트랜잭션 앞에 둔다. 결제 취소 (money-admin.controller.ts:754) 와 같은 규약. 시험: money-admin.refund-scope.spec.ts 「담당 밖이면 잠금 전 403」 (수정 전 서비스 호출로 붉음, 수정 뒤 녹색). 담당 · OFF 양성 대조 2건 | 1 |
| R2 | code-review | LOW | cancelPaymentByAdmin 의 tx 단언 (tx!) 4곳이 호출 규약에만 기댄다 | 흡수 | 영업 판정 블록 첫 줄에 if (!tx) throw 로 명시 오류, 단언 4곳 제거. 시험은 없다 (타입 수준, 미검증). 기존 money.service.cancel-scope.spec.ts 2건 녹색 | 1 |
| R2 | typescript | LOW | analyze.repository.ts 의 asdInEtc 삼항 포맷 변경 (getAggregateList) 이 범위 hunk 와 같이 남았다 | 흡수 | HEAD 모양 4줄로 되돌렸다. diff 122 → 115 행, 의미 변화 없음. 실측: git diff --no-ext-diff 에 asdInEtc 줄 변경 없음 | 1 |
| R2 | code-review | LOW | notes 의 새 줄 두 건이 커밋 가드가 세는 호스트 절대경로 접두를 담는다 | 흡수 | 두 줄을 「스테이징 diff 의 호스트 절대경로 개수가 0」 으로 바꿨다. 노트 diff 추가 줄의 호스트 절대경로 0 (실측). HEAD 에 있던 1행 (plugin 경로) 은 바꾸지 않았다 (스테이징 diff 에 나오지 않는다). 검사 #14 는 notes 를 보지 않는다 — 커밋 가드가 잡는다 | 6 |
| R2 | security | MEDIUM | 결제 취소의 잠금 아래 PG 호출 (최대 30초) 이 대기 요청마다 풀 연결을 점유해 전역 정지를 만들 수 있다 (drizzle.service.ts:36-39) | 기각 → 백로그 | 정책 5 (계획 결정 5) 가 잠금 아래 PG 호출과 「잠금 보유 시간 = PG 지연」 잔여 위험을 승인했다. 풀 점유 증폭은 계획 Risks 에 없다. NOWAIT 이나 짧은 선점 (CANCELLING) 은 결정 5 를 바꾸는 설계 변경이라 이 라운드에서 고르지 않는다. 백로그 MEDIUM 행 (M9 전 동시 취소 부하 시험 · 잠금 대기 상한 결정). 시험 없음 (미검증) | 1 |
| R2 | code-review | LOW | 잠금 메서드 (lockPaymentRow · lockMemberAgcId · lockRefundRow) 의 db 인자가 필수라 컴파일러가 누락을 못 잡는다 | 기각 (사실 오류) | 필수 매개변수는 누락을 컴파일 오류로 잡는다 (money.repository.ts:1052 · 2545 · 2558 은 db: MoneyDb 기본값 없음). 기본값 (this.drizzle) 을 주면 트랜잭션 밖 autocommit 으로 잠금이 조용히 풀린다 | 1 |
| R2 | 구현 Codex 이연 | MEDIUM | createIssue (회원 → 기업) ↔ createGrant (기업 → 회원) 역순 쌍 | 기각 (범위 밖) | 범위 2 (잠금 순서 한 방향) 는 소속 쓰기 ↔ 조직 삭제 쌍만 정한다 (계획 정책 4). 머니 지급 쌍은 백로그 1770 에 이미 있다. 교착은 InnoDB 1213 으로 한쪽이 롤백되어 조용한 손상이 아니다. 이 라운드에서 고치지 않는다. 근거 lock-order.md 이연 1 | 1 |
| R2 | 구현 Codex 이연 | MEDIUM | 조직 삭제가 기업 잠금 뒤 새로 늘어난 부분트리 회원을 잠근다 | 기각 (논증) → 백로그 | 범위 2 안이다. 재판정: 부분트리 소속 변경은 같은 기업 잠금 뒤에만 커밋된다 (assignOrg · setOrgManager · orgPermissionUpload). 기업 잠금 뒤 재확인 (organization.service.ts:442-453) 이 다르면 재시도한다. 교착 사슬은 찾지 못했다. 다만 회원 정보 수정 (fc.service.ts:168 → fc.repository.ts:345-395) 이 기업 잠금 없이 orgId 를 써서 재확인 뒤 편입 창이 남는다 (데이터 창, 재현 안 함, 미검증). 백로그 MEDIUM 행. 근거 lock-order.md 이연 2. (R3 정정: 위 근거의 「교착 사슬은 찾지 못했다」 는 틀렸다. 무잠금 orgId 쓰기가 재확인 뒤 편입하면 삭제의 회원 UPDATE 가 그 행을 기다려 1213 이 날 수 있다. 데이터 손상은 없다. 본인 경로는 R3 HIGH 에서 닫았고, 관리자 수정 경로는 R3 MEDIUM 행을 본다.) | 1 |
| R3 | code-review | HIGH | 기업FC 본인 수정으로 조직관리자가 자기 소속을 바꿔 다른 부분트리 권한을 얻는다 (fc.service update 가 orgId 를 범위 판정 없이 쓴다) | 흡수 | 본인 수정 (`insuniverse-server/apps/api/src/modules/fc/fc.controller.ts:91`) 은 orgId 를 서비스로 넘기지 않고 버린다 (memAgency 와 같은 처리). 관리자 수정 경로는 범위 판정이 있으므로 그대로 둔다. 시험 fc-ownership 「소속 본인 수정: orgId 는 서비스로 넘기지 않는다」 (수정 전 passed 에 orgId 가 남아 붉음, 수정 뒤 녹색). 런타임 PATCH 실측은 하지 않았다 (미검증). 편차: 본인 소속 변경을 FE 가 쓰는지는 확인하지 못했다 (미검증) | 1 |
| R3 | code-review | MEDIUM | 조직 삭제의 비영업 분기가 잠금 없이 실행된다 (기업 · 조직관리자 삭제에 회원 → 기업 사슬이 없다) | 흡수 | 강제 ON 이면 모든 신원이 `runOrgTreeLocked` (회원 선행 잠금 → 기업 행 잠금 → 잠금 뒤 재확인 · 재시도) 를 탄다. 영업 경로는 `runSalesOrgWrite` 그대로다 (`insuniverse-server/apps/api/src/modules/agency/organization/organization.service.ts:388`). 시험 sales-write 「삭제: 기업 신원도 회원 선행 잠금 → 기업 행 잠금 → 재확인 순서」 (수정 전 runInTransaction 0회로 붉음, 수정 뒤 녹색), 「강제 OFF: 기업 신원 삭제는 트랜잭션을 타지 않는다」. 재시도 소진 409 는 R3 LOW 행 (E0315) | 1 |
| R3 | code-review | LOW | 이전 R2 행의 기각 근거 「교착 사슬은 찾지 못했다」 가 틀렸다 | 흡수 (정정) | R2 행 근거에 정정 문장을 덧붙였다. 교착 사슬은 있다 (무잠금 orgId 쓰기 → 삭제의 회원 UPDATE 대기 → 1213, 데이터 손상 없음). lock-order.md 이연 2 를 현 상태로 고쳤다 | 1 |
| R3 | code-review | MEDIUM | 관리자 수정 경로 (`fc-agency.service.ts:829` → `fc.service.ts:168` → `fc.repository.ts:381`) 가 기업 잠금 없이 orgId 를 쓴다 (본인 경로와 같은 무잠금 쓰기) | 부분 흡수 → 백로그 | 본인 경로는 위 R3 HIGH 행에서 닫았다. 관리자 경로는 판정 (`assertOrgWritableByAdmin` · `assertMembersWritableByAdmin`) 은 있으나 기업 잠금 사슬이 없다. 시나리오 (리뷰어 추론, 재현 안 함): 삭제 재확인 뒤 커밋 전에 회원이 부분트리로 편입되면 삭제의 조직 DELETE 가 외래키 1451 로 실패한다. 데이터 손상은 없고 한쪽 요청이 실패한다. 닫으려면 관리자 수정 경로에서 orgId 가 있을 때 회원 → 기업 사슬 트랜잭션을 열고 repository 가 tx 를 받게 해야 한다. 이번 라운드에서 닫지 않았다 (재배선 공수가 크고 격리 시험을 새로 써야 한다). 백로그에는 같은 항목의 기존 행 (2026-10-22 MEDIUM, 조직 삭제 재확인 뒤 편입 창) 이 있어 새로 쓰지 않았다. 그 행은 본인 경로가 닫혀 범위가 관리자 경로로 좁아졌다 | 1 |
| R3 | code-review | LOW | 격리 경합 시험 「삭제 후 소속 쓰기」 이름이 제품 거부를 주장한다 (소속 쓰기 쪽은 원시 SQL 헬퍼) | 흡수 (이름 낮춤) | 이름을 「삭제 후 소속 쓰기 (원시 SQL 헬퍼)」 로 바꾸고 주석에 제품 거부 (404 · E0301) 를 증명하지 않는다고 적었다. 게이트 `#12` 는 접두 이름으로 맞는다. 격리 organization.race 9/9 녹색 | 7 |
| R3 | code-review | LOW | M9 체크리스트에 운영 sql_mode 확인이 없다 (격리는 ONLY_FULL_GROUP_BY 를 끈 빈 값으로 띄웠다) | 흡수 (체크리스트) | 아래 「이연과 미검증」 에 운영 sql_mode 확인을 M9 전 항목으로 넣었다. 기동 시 검사는 다음 계획으로 넘긴다. 운영 값은 확인하지 않았다 (미검증) | 5 |
| R3 | security | MEDIUM | 지정 적립 (createIssue) 이 담당 밖 기업 번호에 대해 회원 0명 · 한도 초과 응답으로 기업 회원 존재 · 인원 수를 새게 한다 | 흡수 | 기업 번호 (`dto.agcId`) 를 영업 판정 (sagCache) 으로 인원 검사보다 먼저 본다. 인원 0 과 한도 검사는 판정 루프 뒤로 옮겼다 (검증 실패 메시지 앞 순서는 그대로). 시험 write-scope 「담당 밖 기업 번호 · 회원 0명 → 403」 · 「담당 밖 기업 번호 · 한도 초과 금액 → 403」 (수정 전 400 으로 붉음 2건, 수정 뒤 녹색). 격리 money-bulk-scope 12/12 · money-race 11/11 녹색. 엑셀 경로 (uploadIssue) 는 사전 판정이 먼저라 해당 없다 | 1 |
| R3 | typescript | MEDIUM | 결제 취소 실패 로그가 트랜잭션 없는 현행 흐름에서도 `db_rolled_back` 이라고 말한다 | 흡수 | 현행 흐름 (상수 OFF · 비영업) 은 `payment.cancel.pg_called_db_state_unknown` 로 남긴다. 영업 판정 경로 (트랜잭션) 는 기존 `..._db_rolled_back` 를 유지한다. 계획서 정책 5 의 이름은 영업 경로에서만 참이다. 계획서는 고치지 않고 이 편차를 여기 적는다. 시험 cancel-scope 「상수 OFF … 상태 불명 키」 (수정 전 붉음), 「상수 ON … 롤백 키」. money-race itspec 「PG 응답 유실」 의 기대 키를 새 키로 고쳤다 (격리 11/11 녹색) | 1 |
| R3 | typescript | LOW | 지정 적립 상세 (getIssueDetail) 단건 판정이 대상 목록 조회 뒤에 온다 | 흡수 | 단건 판정을 조회 앞으로 옮겼다. 기업 신원의 결과 행 비교 (`result.issue.agcId`) 만 조회 뒤에 남는다. 시험 money-admin.issue-scope.spec 3건 (수정 전 「담당 밖」 · 「없는 번호」 2건 붉음). 편차: 없는 번호와 범위 밖 영업 신원은 404 대신 403 이다 (다른 단건 읽기와 같은 규약). 강제 ON 에서만 달라진다 | 1 |
| R3 | typescript | LOW | 부분트리 삭제 409 가 raw 문자열이라 응답에 code 가 없다 | 흡수 | `admin.constant.ts` 에 `ORG_SUBTREE_CHANGED` {E0315} 를 더했다 (E0314 는 api.constant 의 NO_COLLECTED_DATA 가 쓰므로 피했다). 시험 sales-write 「삭제: 재확인이 두 번 다르면 409」 에 code 단언을 더했다. 반사실: 문자열로 되돌리면 붉음 (실측), 복원 녹색 | 2 |
| R3 | typescript | LOW | createIssue · uploadIssue 의 잠금 블록이 복붙이고 `as number` 캐스트에 기댄다 | 기각 | 동작을 바꾸지 않는 정리다. 잠금 순서는 money-race · sales-write 시험이 본다. 헬퍼 추출은 M9 전 정리 후보로 둔다 (동작 불변, 이번 라운드 범위 밖) | 1 |
| R3 | typescript | LOW | 호출처가 없어진 옛 판정 입력 메서드 두 개 (getMemberAgcId · getSagIdOfAsb) | 흡수 | 두 메서드를 삭제했다. apps/api/src 에서 정의 외 호출 0 (grep, scope-handlers.spec 의 제목 문구만 남음). tsc 비-spec 오류 0 | 1 |
| R3 | typescript | LOW | 경합 시험의 release 가 try/finally 밖이고 'done' 판정이 500ms 벽시계에 걸려 있다 | 흡수 | 판정을 try 로 감싸고 finally 에서 release · 풀 정리 · 삭제 프라미스 대기를 한다. 'done' 상한은 3초로 올렸다 (실패 때만 상한까지 기다린다). 구조 변경이라 반사실 실측은 하지 않았다 (미검증). 격리 organization.race 9/9 녹색 | 8 |

### 이연과 미검증 (M9 체크리스트 입력)

- 지정 적립 ↔ 지정 지급 (createIssue 회원 → 기업, createGrant 기업 → 회원) 역순 쌍: 교착 시험이 없다. 강제 상수 ON 전에 시험을 추가한다.
- deleteSubtree 재확인 뒤 커밋 전 이동: 회원 재확인이 잠금 없는 읽기이므로 커밋 전 이동은 못 본다. 삭제의 UPDATE 가 현재 읽기로 그 행을 잠그면 교착이 날 수 있다 (추론, 재현 안 함).
- 삭제 재시도 경로는 실 DB 에서 결정적으로 세우지 못했다 (회원 선행 잠금의 인덱스 범위가 삽입을 막는다). 단위 시험만 있다.
- 결제 취소 로그: PG 취소 금액 · 취소 시각은 로그에 없다. PG 거절과 유실을 가르지 않는다 (정책 5 잔여).
- 조직관리자 단건 가드 (assertSingleOrg) 의 scope code 는 전용 시험이 없다 (검사 없음).
- 격리 DB 는 sql_mode="" 로 띄운다 (ONLY_FULL_GROUP_BY 를 끈 stg 와 같은 모드). 기본 모드에서는 scope 질의가 깨진다 (관측).
- 운영 sql_mode 확인 (M9 전, R3 LOW): 격리 DB 는 ONLY_FULL_GROUP_BY 를 끈 빈 값으로 띄웠다. 강제 상수를 켜기 전에 운영 sql_mode 를 확인한다. 꺼져 있지 않으면 범위 질의 (분석 목록 · 집계) 가 깨질 수 있다. 확인 전에는 강제 ON 금지 (미검증, 운영 값은 확인하지 않았다).
- 서버 baseline 에 이미 있던 analyze-insight-route.spec.ts 의 ENOENT (워크트리 밖 fixture) 는 이번 변경과 무관하다 (baseline 에도 실패).

## 코드 리뷰 라운드 2 판정 요약 (2026-10-22, 커밋 전 워킹트리)

- 입력: 지적 25건 (같은 자리 중복을 묶어 12행). 흡수 8 · 기각 4 (기각 중 1건은 백로그로 넘김). HIGH 흡수 1 · HIGH 미해소 0 · CRITICAL 미해소 0.
- 흡수 수정: 지정 적립 기업 번호 판정 (쓰기 · 단건 읽기), 환불 처리 상수 OFF 현행 흐름과 영업 사전 판정, 결제 취소 tx 단언 제거, 노트 가드 문구, analyze 범위 밖 포맷 되돌림, lock-order.md · README.md 신설.
- 반사실 (수정 전 붉음 확인, 실제로 돌림): money.service.write-scope.spec.ts 5건 (5 실패 · 5 통과 → 수정 뒤 12/12 녹색), money-admin.refund-scope.spec.ts 는 수정 전 1건 붉음. money-bulk-scope.itspec.ts 신규 2건은 수정 전 조건 제거 사본에서 붉음 (사본은 원상복구 · sha 일치).
- 격리 DB (m6c-iso-it, sql_mode 빈 값, 합성 적재): money-race · money-bulk-scope 23/23 녹색 (신규 2건 포함) · money-scope · money-scope-query 37/37 녹색. 조직 · fc 경합 시험은 이번 라운드에서 해당 파일을 건드리지 않아 다시 돌리지 않았다 (미실행). 단위: money · auth/access-control · organization · fc · analyze 53 suite 중 1건 실패 (analyze-insight-route.spec.ts, 워크트리 밖 fixture ENOENT, 착수 기준선과 같다).
- tsc: 오류 23 건, 착수 기준선과 같은 파일 · 같은 메시지 (줄 표기만 다르다). 비-spec 오류 0.
- 기각 · 백로그 행 (MEDIUM): 결제 취소의 잠금 아래 PG 호출 풀 증폭, 조직 삭제의 회원 편입 창 (회원 정보 수정 경로가 기업 잠금 없이 orgId 를 씀). 두 건 모두 backlog 에 append 했다.

### 라운드 2 이후 항목 (M9 체크리스트 입력 추가)

- 회원 정보 수정 경로 (fc.service.ts:168 → fc.repository.ts:345-395) 가 소속 (orgId) 을 기업 잠금과 범위 판정 없이 쓴다. 강제 상수 ON 전에 이 경로의 판정 · 잠금 여부를 정한다 (영역 1 · 2 강제 대상 판정). (미검증: 실제 호출 권한 범위는 확인하지 않았다)
- 지정 적립 기업 번호 판정은 영업 배치의 헤더를 다음처럼 바꾼다. 회원이 여럿의 기업에 걸치면 null (이전에는 첫 회원 기업). 전체 대상 (ALL) 이 한 기업에 머무르면 그 기업 (이전에는 null). 둘 다 상수 ON · 영업 신원 경로에서만 일어난다. 단건 읽기는 이 배치를 deny 한다. 관리자 (admin) 와 기업 신원은 이 변경의 영향을 받지 않는다 (OFF · 비영업 흐름 불변). 편차로 기록한다.
- 결제 취소 · 환불의 잠금 아래 PG 호출 (정책 5) 의 부하 시험은 아직 없다 (미검증). 백로그 MEDIUM.
- 격리 컨테이너 m6c-iso-it 는 이번 라운드 시험 뒤 내리고, 비밀 파일 (gitdir 아래) 은 지웠다. 다음 실행은 openssl 로 새로 만든다.

## 코드 리뷰 라운드 3 판정 요약 (2026-10-22, 커밋 전 워킹트리)

- 입력: 리뷰어 지적 12건 + 판정 중 파생 1건 (관리자 수정 경로의 같은 무잠금 쓰기, 위 표 R3 MEDIUM 행). 흡수 11 · 기각 1 · 부분 흡수 → 백로그 1 (기존 행 재사용).
- HIGH 흡수 1 (본인 수정의 소속 이동 차단) · HIGH 미해소 0 · CRITICAL 0 · CRITICAL 미해소 0.
- 흡수 수정: 본인 수정의 orgId 차단, 조직 삭제 비영업 분기의 잠금 사슬, 지정 적립의 담당 밖 기업 번호 사전 판정 (인원 수 비노출), 결제 취소 실패 로그 키 분리, 지정 적립 상세 단건 판정 순서, 409 코드 E0315, 죽은 메서드 2개 삭제, 경합 시험 이름 · 정리 구조.
- 반사실 (수정 전 붉음 확인, 실제로 돌림): fc-ownership 1건 (passed 에 orgId 잔존), sales-write 삭제 순서 1건 (runInTransaction 0회), write-scope 담당 밖 2건 (400), cancel-scope 상태 불명 키 1건, money-admin.issue-scope 2건 (조회 호출됨), sales-write 409 코드 단언 (raw 문자열로 되돌리면 붉음 → 복원 녹색).
- 구조 변경 (반사실 미실측): 경합 시험의 finally 정리와 3초 상한, 시험 이름 낮춤.
- 격리 DB (m6c-iso-r3, mysql 8.0.42, sql_mode 빈 값, 합성 적재): organization.race 9/9 · money-race 11/11 · money-bulk-scope 12/12 · money-scope · money-scope-query 37/37 (+34 질의 시험) · fc-bulk-scope 14/14 · agency-fc.race 6/6 녹색.
- 단위: money · agency · fc · analyze · auth · constants 94 suite 중 93 통과, 1 실패는 analyze-insight-route.spec.ts (워크트리 밖 fixture ENOENT, 착수 기준선과 같다).
- tsc: 오류 23 건 (착수 기준선 23), 비-spec 오류 0.
- 게이트 사이드카: 검사 #1~#19 녹색. 단 검사 #10 의 한 줄 (`after_sha` 가 서버 기준 sha 와 달라야 한다) 은 서버에 커밋이 생긴 뒤에만 통과한다. 이번 실행은 그 한 줄을 사본 게이트에서만 건너뛰었다 (계획 파일은 고치지 않았다). 커밋 뒤 원 게이트를 다시 돌린다.
- 편차 (R3): 본인 수정의 orgId 는 조용히 버린다 (memAgency 처리와 같은 방식). 로그 키 `payment.cancel.pg_called_db_state_unknown` 은 계획서 정책 5 의 이름과 다르다 (현행 흐름에만 쓴다). 없는 번호 · 범위 밖 영업 신원의 적립 상세는 404 대신 403 이다. 계획서는 고치지 않았다.
- 미검증: 본인 소속 변경의 FE 사용 여부, 런타임 PATCH 실측, 운영 sql_mode, 관리자 수정 경로의 편입 창 재현, 경합 시험 구조 변경의 반사실.

## 커밋 (2026-10-22, 커밋 세션)

- 서버 커밋 (`feat/admin-rbac`, 위에서 아래 순서로 쌓았다):
  1. `23353d3f` feat(access-control): 단건 범위 판정을 신원별 자원 키로 나눈다
  2. `7ef0c778` fix(agency): 조직 삭제와 소속 쓰기의 잠금 순서를 한 방향으로 맞춘다
  3. `0e80578f` feat(money): 머니 쓰기 4경로와 분석 질의에 영업 범위 판정을 잇는다
- 커밋 전 정리: `admin.constant.ts` 는 CRLF 파일인데 구현 중 LF 로 저장되어 전 줄 diff (224행) 였다. 줄바꿈을 CRLF 로 되돌려 실제 추가 2행만 남겼다 (`git diff --numstat` 는 2 0).
- 출처 확인: 서버 워킹트리 변경 19 파일과 신규 8 파일은 전부 이 마일스톤의 구현 · 리뷰 기록에 든 파일이다. HEAD 를 prettier 로 정렬한 결과와 같은 파일은 없다 (전부 내용 변경이다). `prettier --check` 는 `analyze.repository.ts` 만 실패한다. 이 파일은 HEAD 부터 정렬 위반 42곳이 있어 손대지 않았다. 나머지 18 파일은 통과한다.
- 구현 receipt (`.claude/receipts/mccp-implement-codex/admin-rbac-m6c.json`) 는 `validate --decision admin-rbac-m6c` 로 확인했다: stale 0 · blocking 0 · missing 은 `mccp-plan-codex` 1건뿐 (계획 게이트 skip-receipt 로 의도된 부재).
- 백로그: 17행 RESOLVED-IN 은 계획 커밋에 이미 있었다. 코드 리뷰가 낸 신규 3행 (HIGH 1 · MEDIUM 2, 모두 `2026-10-22` 날짜 줄) 은 이 커밋에 싣는다.
- 아직 하지 않은 것 (다음 세션 몫):
  1. 검사 #10: `probe-summary-m6c.txt` 의 `after_sha` 가 아직 착수 sha 다. 커밋 HEAD 에서 `GK_BUILD=after` probe 를 다시 돌려 행 파일과 요약을 갱신해야 검사 #10 이 선다. 행 파일이 바이트로 같은지는 그때 본다.
  2. 전체 게이트 `#1`–`#19` 재실행 (커밋 뒤).
  3. Task 11 의 PRD 마일스톤 6 행 마감 판정 (상태 설명). 최종 fable · codex 리뷰 뒤에 한다.
  4. 구현 보고서 `.claude/PRPs/reports/admin-rbac-m6c-report.md` 는 작성하지 않았다 (이 커밋 시점에 파일이 없다).

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

입력은 리뷰어 지적 9건 (fable 7 · codex 2). 판정: 흡수 7 · 미흡수 2 (계획 수정이 필요해 backlog 로 넘김). HIGH 흡수 1 · HIGH 미해소 0 · CRITICAL 0.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable | HIGH | 검사 #10 이 HEAD 에서 붉다 (after_sha 가 착수 sha 와 같음). 행 파일이 R1–R3 이전 트리 산출이다 | 흡수 | 서버 `ceeae2f5` 에서 `GK_BUILD=after` probe 를 다시 돌렸다. 행 파일 6346 행이 착수 빌드 행 파일과 cmp 일치. 요약의 after_sha 를 `ceeae2f5` 로 갱신. 착수 빌드도 같은 비밀로 재실행해 cmp 일치 | 2 |
| R1 | fable | MEDIUM | 응답 불변 probe 의 쓰기 행 6개가 전부 조직 · 소속 쓰기 (admin-super) 라 머니 쓰기 4경로의 OFF 응답을 검사 #10 이 보지 않는다 | 미흡수 · backlog | probe 확장은 새 시드 행과 신원 조합이 필요해 이번 보완 범위 밖. OFF 경로는 단위 spec (cancel-scope · write-scope · refund-scope · issue-scope) 만 덮는다 (미검증) | 6 |
| R1 | fable | MEDIUM | PRD 마일스톤 6 의 「M6 에서 활성화」 절이 m6c 범위 · Acceptance 어디에도 없다. 계획서 :173 의 PRD UPDATE 표기도 마감 전 상태와 맞지 않는다 | 미흡수 · backlog | 계획서 수정이 필요하다 (계획서는 이 라운드에서 고치지 않는다). backlog 2026-10-22 MEDIUM. PRD 6 행 마감 판정 (Task 11) 전에 M9 몫으로 명시 | 2 |
| R1 | fable | LOW | 회원 본인 수정의 `delete body.orgId` 는 상수와 무관하게 켜지는 동작 변경이고, 계획 파일 목록과 probe 밖이다 | 흡수 (근거 기록) | 사이트 프런트 `insuniverse-site/front/src/api/fcApi.ts:100-108` 의 `patchFc` 파라미터 타입에 orgId 가 없고, 호출처 전송 0 (grep 실측). 알려진 소비자 영향 없음. `UpdateFcDto` 의 orgId 선언 잔존 (값이 조용히 버려짐) 은 DTO 를 바꾸면 400 으로 바뀌어 이번 라운드 밖. M9 체크리스트 입력으로 남긴다 | 2 |
| R1 | fable | LOW | 검사 #8 의 이름 대조가 부분 문자열이라 경합 양성 이름 4개가 반사실 이름만으로 통과한다 | 흡수 | `jnames_colon` (이름 뒤 구분자 「: 」 까지 맞춤) 을 경합 4 이름 · 그 밖 목록에 적용. 양성 이름 부분을 빼 본 변이와 반사실 이름만 남긴 변이 둘 다 rc 1. 실 결과 json 에서는 전부 통과 | 6 |
| R1 | fable | LOW | 검사 #5 가 측정 판정 모듈 (`scope-monitor.ts`) 을 묶지 않는다 (산출은 하네스 sha 만 묶음) | 흡수 | 하네스가 `judge_sha256` 을 기록하고 검사 #5 가 HEAD 의 판정 모듈 sha 와 대조한다. 자가 시험에 `judge` 변이 추가 (rc 1). 측정을 다시 돌려 산출 갱신: 132 셀 전부 이전 산출과 같다 (diff 0). 하네스 sha 는 스크립트 수정으로 바뀌었다 | 8 |
| R1 | fable | LOW | 인벤토리 B12 (`refund/process`) 가 「일괄」 로 적혔으나 DTO 는 단건 mrfId 하나다 | 흡수 | `bulk-inventory.md` B12 를 「단건」 으로 고쳤다 (`ProcessMoneyRefundDto` · `money-admin.controller.ts` 사전 판정이 단건). 일괄 19 · 단건 15 | 1 |
| R1 | codex | MEDIUM | 지정 적립 엑셀 전건 거부 시험이 첫 행부터 밖이라, 첫 행만 판정하는 구현도 통과한다 | 흡수 | `money-bulk-scope.itspec.ts` 「지정 적립 엑셀 뒤 행 밖 전건 거부」 신규. 첫 행 담당 안 · 둘째 행 밖 → 미리보기 · 적용 모두 Forbidden, 행 불변. 반사실: `assertSalesScopeOfMemberPre` 호출을 행 2 까지만 남긴 사본에서 신규 시험 붉음 (rc 1), 원본 복원 clean. 지급 엑셀은 기업 단위 판정이라 행 단위 시험 대상이 아니다 (시험 시도 후 기업 밖 회원이 「찾을 수 없음」 400 으로 나와 제외) | 6 |
| R1 | codex | MEDIUM | 내보내기 격리 시험 4 표 (detail · prescribe · car basic · car detail) 가 빈 표라 범위 절을 빼도 녹색이다 | 흡수 | `money-scope-query.itspec.ts`: 내보내기 5 표 각각에 합성 행 2건 (TX_IN · TX_OUT) 을 시험 안에서 넣고 afterAll 에 지운다. 「행 수 일치」 「행 수 하한 1」 을 5 표 전부에 건다. 반사실: detail 표 범위 절을 뺀 사본에서 「분석 내보내기 타 기업 행 0」 붉음 (rc 1), 원본 복원 clean. 수정 뒤 34/34 녹색 | 6 |

### 라운드 1 판정 요약

- 서버 커밋 `ceeae2f5` (`test(money)`): 위 codex 2건과 fable 판정의 시험 추가. 테스트 파일 2개 · prettier 정렬 (query 시험, HEAD 가 정렬된 상태였다).
- 격리 DB (`m6c-iso-t1` · `m6c-iso-gates`, mysql 8.0.42, sql_mode 빈 값, 합성 적재 뒤 실행): bulk-scope 13/13 · query 34/34 녹색. 전체 게이트 `#1`–`#19` 를 커밋 뒤 한 번에 돌려 `GATES_OK m6c` (rc 0). 검사 #10 은 커밋된 서버 HEAD `ceeae2f5` 기준으로 통과한다.
- tsc: 오류 23 건 (착수 기준선 23).
- 계획 린트 (`lint-plan.mjs`): rc 0.
- 미흡수 2건 (fable MEDIUM 쓰기 행 표본 · 계획 PRD 절) 은 `.claude/plans/codex-findings-backlog.md` 에 `2026-10-22 | MEDIUM` 으로 append 했다. HIGH 미해소 0 이라 루프 판정은 다음 라운드 몫이다.
- 측정 산출 (`scope-would-deny.json`) 은 하네스 수정 뒤 재측정했다. 셀 132 개 값은 이전과 같다. 판정 모듈 sha 와 하네스 sha 가 새로 기록된다.
- 격리 비밀 파일 (`gk-m6c-iso.env`, gitdir 아래, 권한 600) 은 이번 라운드에서 만들었다. 다음 실행에서 새로 만들고 지운다.

### 남은 일 (이 라운드 밖)

1. 계획 수정이 필요한 지적 (backlog 2건) 은 다음 계획에 넘긴다. 계획서는 이 라운드에서 고치지 않았다.
2. Task 11 PRD 마일스톤 6 행 마감 판정은 최종 fable · codex 리뷰 (라운드 2 이후) 뒤에 한다. PRD 는 아직 바뀌지 않았다.
3. 운영 동일 환경 (복제본) 1회 재생과 R5 업무 확인은 사용자 몫 (미검증).

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

입력은 리뷰어 지적 4건 (fable 3 · codex 1). 판정: 흡수 4 · 기각 0 · 미흡수 0. HIGH 입력 0 · HIGH 흡수 0 · HIGH 미해소 0 · CRITICAL 0.
HIGH 가 들어오지 않아 흡수 0 의 종료 조건에 해당한다. 루프 종료 여부는 메인 세션이 판정한다.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R2 | fable | MEDIUM | 「PG 응답 유실 뒤 조정 로그」 시험이 영업 판정 경로가 아니라 현행 경로를 돈다 (게이트 #8 이름만 채워짐) | 흡수 | 신규 시험 「PG 응답 유실 뒤 조정 로그: 영업 판정 경로에서 PG 호출이 응답 없이 실패하면 롤백 키 (pg_outcome=unknown) …」 는 SALES 를 넘기고 ETIMEDOUT 을 던진다. 로그 키 `payment.cancel.pg_called_db_rolled_back` · `pg_outcome=unknown` 과 결제 COMPLETED 를 본다. 기존 시험은 「현행 흐름 PG 응답 유실 (트랜잭션 없음)」 으로 개명했다 (부분 일치 게이트가 현행 시험을 세지 않도록). 반사실: 영업 catch 의 키를 상태 불명 키로 바꾸면 신규 시험과 「PG 호출 뒤 커밋 실패 조정 로그」 가 붉다 (실패 2건, 원본 복원). 격리 12/12 녹색. 서버 커밋 `7dcbd7e5` | 6 |
| R2 | codex | MEDIUM | 삭제의 회원 범위 잠금 (소속 인덱스 FOR UPDATE) 이 소속 쓰기와 교착한다 (1213) | 흡수 (재현 · 수정 · 반사실) | 재현: 소속 인덱스 `mem_org_id` 를 더한 조직 격리 fixture 에서 옛 코드는 소속 쓰기의 UPDATE 가 「Deadlock found when trying to get lock」 (1213) 을 받았다 (신규 시험 실패). 수정: 회원 잠금을 후보 회원의 기본키로 바꿨다 (`lockMembersByIds`, 범위 잠금 제거). 후보는 잠금 앞에 잠금 없이 읽고, 기업 잠금 뒤 재확인이 다르면 기존 재시도 (1회) 를 탄다. 수정 뒤 신규 시험 「삭제 · 소속 쓰기 교착 없음」 통과. 「소속 쓰기 후 삭제」 는 대기 지점이 회원 잠금에서 기업 잠금으로 바뀌어 기대를 고쳤다 (재시도 1회 · 1001 · 1002 분리 2건). 조직 격리 10/10 · 단위 organization 4 suite 23/23 · tsc 신규 0. 서버 커밋 `47fcc439`. 계획서 Task 3 (가) 의 「회원 범위」 서술은 개명 전 이름 `lockSubtreeMembers` 를 쓰므로 계획서는 고치지 않고 이 판정으로 대신한다 | 1 · 6 |
| R2 | fable | LOW | 조직 권한 업로드의 영업 경합 시나리오가 제품 경로에서 도달 불가다 (라우트 `@Roles('admin','corp')`) | 흡수 (문서) | `agency-fc-admin.controller.ts:386`. `lock-order.md` 행을 「ON · 기업(corp) 신원만」 으로 고치고 영업 신원은 사슬에 도달하지 않는다고 적었다. `bulk-inventory.md` B16 근거를 corp 판정의 잠금 뒤 재확인으로 고쳤다. 영업 경합 시험 (`agency-fc.race.itspec.ts`) 은 저장소 복제본이라 제품 증명이 아니며 백로그 HIGH 행 (복제본 지적) 으로 남아 있다 | 2 |
| R2 | fable | LOW | `SingleMeaning` 은 배선된 경로에서 판정을 바꾸지 않는다 (증명 6 행 「단건 3의미 spec」 은 순수 함수 시험으로만 선다) | 흡수 (사실 기록) | `scope-monitor.ts` 의 `meaning` 분기는 admin (173-176) 과 member (201-203) 뿐이다. `singleScopeOf` (216-236) 는 member 종류를 내지 않는다. 래퍼는 admin 을 판정 전에 통과시킨다 (`money-admin.controller.ts:578` · `organization-admin.controller.ts:73` · `analyze-admin.controller.ts:77` · `:96`). 따라서 sales · corp-super · org-manager 세 종류에서 의미 인자는 제품 결과에 영향이 없다. 측정 하네스 132 셀도 같은 신원 6 종이라 의미 차이가 나타나지 않는다. 설계 변경은 없다 (정책 2 · 10, R2 기각 1 과 같은 결정). Task 11 에는 「분리는 순수 함수 · spec 에만 있고, 배선된 단건 경로에서 의미 인자는 불활성이다 (사실)」 로 적는다 | 2 |

### 흡수의 반사실과 실측 (이번 라운드)

1. 교착 재현 (옛 코드 + 소속 인덱스 fixture): 「삭제 · 소속 쓰기 교착 없음」 실패 (1213). 수정 뒤 녹색. 이 재현은 정적 지적을 격리 MySQL 8.0.42 에서 확인한 결과다.
2. 머니 시험 반사실: 영업 경로 키를 상태 불명 키로 바꾸면 2건 붉음 (원본 복원, `money.service.ts` 는 커밋에 들어가지 않았다).
3. 응답 불변 probe 재실행: 착수 빌드 (`GK_BUILD=before`) 와 최종 빌드 (`GK_BUILD=after`, 서버 HEAD `47fcc439`) 를 새 비밀 파일로 다시 돌려 행 파일 6346 행이 cmp 로 같다. 기존 행 파일과도 같다. 요약의 `after_sha` 는 `47fcc439` 로 갱신. 비밀 파일은 실행 뒤 지웠다.
4. 전체 게이트 `#1`–`#19`: `GATES_OK m6c` (rc 0). 격리 컨테이너 `m6c-iso-t2` (mysql 8.0.42) 에 스키마 · 저장 함수 · 마이그레이션 두 개 · 합성 시드를 적재한 뒤 돌렸다. 검사 #10 은 서버 HEAD 기준으로 통과한다.
5. tsc: 오류 23 건, 전부 기준선과 같은 spec 파일, 비-spec 0.
6. 포맷: `organization.service.sales-write.spec.ts` 는 HEAD 가 prettier 정렬 상태였으므로 같은 커밋 (`47fcc439`) 에서 정렬했다. 나머지 수정 파일은 정렬 통과.

### 발견 (흡수 중, 미해소)

- 격리 스키마 덤프 (`02-schema.sql`, 저장소 밖) 에 `tn_member` 소속 인덱스 `mem_org_id` 와 `tn_agency_organization` 의 `idx_org_path` 가 없다. 제품 선언 (`drizzle/schema.ts:1612` · 마이그레이션 0014 의 `idx_org_path`) 과 어긋난다. `mem_org_id` 는 마이그레이션 SQL 에 생성문이 없어 운영 DB 실재는 (미검증, 운영 접속 금지). 조직 격리 fixture 에만 인덱스를 더했다. 백로그 MEDIUM 행으로 남긴다.

### 이번 라운드 뒤 남은 일

1. Task 11 (PRD 마일스톤 6 행 마감 판정) 은 메인 세션 몫이다. 이때 「단건 3의미」 행에 위 LOW 의 사실 (배선 경로에서 의미 인자 불활성) 을 함께 적는다.
2. 운영 동일 환경 (복제본) 1회 재생, R5 업무 확인은 사용자 몫 (미검증, 변경 없음).
3. 기존 백로그 항목 (구 포함 편입 창 · 회원 정보 수정 경로) 은 이번 라운드에서 바꾸지 않았다.
