# 관리자 RBAC 마일스톤 6d 노트 (보완 3차 · 구현)

계획서: `.claude/PRPs/plans/admin-rbac-m6d.plan.md` · 측정 산출: `.claude/meta/2026-10-22-admin-rbac-m6d/README.md`.

## 마감 요약

- 상태 (2026-10-23 보완 3차 마감): **PRD 마일스톤 6 Status complete.** 판정 근거와 갈린 자리는 아래 「## 마감 판정」 절. 구현 · 코드 리뷰 R1 · R2 · 최종 리뷰 R1 · 마감 재검증 뒤, 마감 단계에서 서버에 `ed95501f` (OFF 경로 시험 한 건) 를 더했고 응답 행 파일 · 게이트를 다시 돌렸다 (「## 마감 재검증」 의 「재검증 뒤 변경」).
- 마감 판정의 전제: 최종 fable 판정은 complete, 최종 codex 판정은 in-progress 였다. 갈린 자리는 「## 마감 판정」 표에 적었고 최종 fable · codex 리뷰가 다시 본다. 짝 없는 HIGH 행 0 (검사 #2), 행별 실행 검사 · 변이 붉음 기록 (검사 #3 · #5) 은 게이트 실행으로 확인했다.
- PRD 마일스톤 6 행: Status complete (Plan 열 m6d 유지, 결정 로그 「2026-10-22 보완 3차 마감」). 강제 상수 OFF 유지 (`SCOPE_ENFORCE_SALES_CORP` · `MENU_SOURCE_DERIVED` = false).
- 범위 4 건 (1823–1826) 은 계획 Task 1–4 로 구현했다. 세부 결과는 아래 표.
- 구현 라운드에서는 커밋하지 않았다 (사용자 지시). 최종 리뷰 R1 흡수에서 서버 `54b3c629` (시험 보강) 와 부모 커밋을 만들었다. 응답 probe 의 after 행 파일은 그 커밋된 HEAD 로 다시 쟀고 `after_sha` 는 `54b3c629` 다 (「최종 리뷰」 절).

### 게이트 결과 (`.claude/PRPs/plans/admin-rbac-m6d.gates.sh`, 격리 MySQL `m6d-iso-` 접두)

1) 코드 리뷰 전 (실제 본문): 검사 #2 에서 멈춤 (`unpaired_carry` = 3, 백로그 1839 · 1840 · 1841 짝 없음).
2) 코드 리뷰 흡수 뒤 (실제 본문, 환경 변수 주입, 우회 사본 없음): `GATES_OK m6d` (rc 0, 약 57 초). 검사 #1 – #18 이 순서대로 섰다 (`set -e` 로 멈추지 않고 끝).
3) 코드 리뷰 R2 흡수 뒤 (실제 본문, 환경 변수 주입, 우회 사본 없음): `GATES_OK m6d` (rc 0, 약 67 초). 검사 #1 – #20 이 순서대로 섰다. 검사 #19 · #20 은 R2 에서 더했다. 반사실 (검사 #20 · 검사 #8 · 검사 #12) 은 임시 사본으로 따로 돌렸다 (「코드 리뷰」 R2 행).
3) 게이트 본문에 격리 DB 재적재 (`iso_reset`) 와 환경 변수 (`M6D_IT_MYSQL_CONTAINER` · `M6D_SCHEMA_DIR`) 를 더했다 (「구현 편차」 6).

### 최종 게이트 실행

- 흡수 뒤 실제 게이트 본문: `GATES_OK m6d` (rc 0). 검사 #2 는 짝 행 증거 규칙 (편차 19) 으로 통과했다. 우회 사본은 쓰지 않았다.
- 서식 (prettier --write) 은 이번 라운드에 돌리지 않았다. 검사 본문이 읽는 문자열은 바뀌지 않았다.

## 계획 게이트

- 판정: **divergent · 상한 소진 · CRITICAL 없음 · receipt 없이 skip-receipt 로 진행**. 원문 판정은 `.claude/reviews/plan-review-admin-rbac-m6d.md`.
  - 용어: L1 = 계획서 형식 검사, L2 = 역할별 패널 리뷰 (정족수 3 필요), L3 = Codex 리뷰, 상한 = 계획 리뷰 3라운드, skip-receipt = 계획 receipt 는 쓰지 않고 구현 진입 검사만 환경 변수 `MCCP_SKIP_RECEIPT=1` 로 넘기는 처리.
  - 마지막 라운드 (3): L1 converged · L2 divergent · L3 converged (지적 0). halt `5.2e`. review_source `multi-agent` · review_proof `null` · `forwardCodexVerdict=false`. L2 통과 2 (architect · security) 대 필요 3 이라 정족수 미달이다. 정상 receipt 를 쓸 수 없는 형태다.
- 최종 계획 해시 (라운드 3): `sha256:3c123bba3d359079dae5263e1c1c42360bff4b1d39fa668be781d7b52d47c5d5`. 계획서 `.claude/PRPs/plans/admin-rbac-m6d.plan.md` 는 고치지 않았다 (동결).
- 사용자 지시 (세션 기록 원문 확인: `verify-user-directive.sh` exit 0, 2026-10-19 02:21Z · user/external):
  「Round 는 최대 3까지만 진행하고 마지막 라운드는 CRITICAL 이상 건이 아닌이상 skip-receipt로 진행해줘. (환경 변수도 적용). HIGH건은 backlog에 기록해서 다음 plan 작성에 반영하는 방법으로 진행.」
- CRITICAL 유무: **없음**. 마지막 라운드 지적의 최고 등급은 HIGH 다.
- 구현 진입 검사: `MCCP_SKIP_RECEIPT=1 node <mccp 캐시>/mccp/1.33.6/scripts/receipt/cli.js preflight --command mccp:prp-implement --decision admin-rbac-m6d --plan .claude/PRPs/plans/admin-rbac-m6d.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:a1b2628e…` | converged | divergent · 막힘 사유 원문: test/HIGH, test/FAIL, invariant/HIGH, invariant/HIGH | divergent | divergent | 리뷰 기록 커밋 `df8ccf3` |
| 2 | `sha256:dc82a5c3…` | converged | divergent · 막힘 사유 원문: test/HIGH, test/FAIL, invariant/HIGH, invariant/FAIL | divergent | divergent | 리뷰 기록 커밋 `a73d3e3` · 정정 `eaf7acd` |
| 3 (마지막) | `sha256:3c123bba…` | converged | divergent · 응답 4 · 통과 2 · 막힘 5 (test/HIGH ×2 · test/FAIL · invariant/HIGH · invariant/FAIL) | converged · 지적 0 | divergent · 상한 소진 | 이 처리 |

- 라운드 3 의 사유 원문 (`reason`) 은 invariant/FAIL 을 빼고 4건만 적는다. 차단 목록 (`decision.json`) 은 5건이다.
- 라운드 1 · 2 의 L3 세부 건수는 이 세션에서 확인하지 못했다. 상태 파일은 마지막 라운드 것만 남았다.

### 처리

1. 마지막 라운드 미해소 지적 12건을 `.claude/plans/codex-findings-backlog.md` 에 `2026-10-22` 줄로 append 했다 (추가 12줄, 기존 줄 고침 0). 같은 라운드 항목은 backlog 에 없었다 (`gates.sh:103` · `gates.sh:354` · tsc 기준선 문구를 grep 해 확인).
2. 지적 구성: L2 12건 (HIGH 3 · MEDIUM 3 · LOW 6). L3 는 지적 0. 판정 문구 `test/FAIL`·`invariant/FAIL` 두 건은 리뷰어의 판정이라 지적 수에서 뺐다. 지적 근거는 각 HIGH·MEDIUM 줄에 있다.
3. HIGH 3건 줄의 사유 끝에 `→ 다음 계획 반영 대상` 을 달았다 (아래 목록).
4. CRITICAL 이 없으므로 receipt 를 쓰지 않았다 (`receipt_path = (없음)`). 계획서는 고치지 않았다.
5. 나머지 MEDIUM 3건 · LOW 6건은 backlog 에 `(이연)` 으로 기록만 했다.

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

1. L2 test · Task 3 이 공유 함수 시그니처 (`FcService.update` · `getFcByMemId` · `FcRepository.update`) 를 바꾸는데, 구현 뒤 기존 단위 스펙과 tsc 를 다시 도는 게이트가 없다 (계획서 :141-143 · :169). 다음 계획은 해당 스펙 파일과 tsc 재실행을 검사로 넣어야 한다.
2. L2 test · Acceptance 「tsc 기준선 오류 수가 23」 은 파일의 리터럴을 grep 할 뿐이라 구현 뒤 tsc 실측을 보지 않는다 (검사 `#14`, 계획서 :252). 다음 계획은 구현 뒤 tsc 결과를 기준선과 비교하는 검사를 넣어야 한다.
3. L2 invariant · 검사 `#2` (이월 짝 검사) 가 계획 시점에 append 된 RESOLVED-IN 짝 행만으로 구현 전부터 통과한다 (게이트 스크립트 :103). 다음 계획은 짝 행이 녹색 검사 실행이나 receipt 에 묶이도록 해야 한다.

### 미검증

- 이 처리는 계획 검사를 통과시킨 것이 아니다. 구현 진입 검사만 환경 변수로 넘겼다. HIGH 3건과 나머지 지적은 다음 계획 작성에서 닫아야 한다.
- 구현은 아직 시작하지 않았다. 이 노트는 계획 게이트 처리만 적는다.

## 착수 기준

- 서버 `47fcc439` (부모 gitlink · 착수 기준, `700b7d26` 의 후손). 관리자 `1301b664`. 사이트 `fc8872a1`. 부모 `fd8ab4c4` (계획 커밋 뒤). 세 서브모듈 워킹트리는 구현 중 바꾼 파일 외에 깨끗했다.
- 서버 조상 검사: `47fcc439` 와 `ea3c0359` 가 HEAD 의 조상이다 (검사 #1).

## 실행 검사명 · 결과 (1823–1826)

| 번호 | 실행 검사명 (시험 파일 · 이름) | 결과 |
|---|---|---|
| 1823 | 단건 조인 S1 · S2 · S3 · S4 · S5 · S6 · S7 · S8 · S9 (`scope-single-join.itspec.ts`, 이름 아홉 개) | 9 건 전부 통과. 게이트 #4 의 `jgreen` 9 · `jcount` 9 · `jnames` 9 가 섰다. 각 시험은 판정 행렬 일곱 칸 (영업 맞음 · 다름 · 기업 최고 맞음 · 다름 · 조직관리자 맞음 · 접두 형제 · 관리자 래퍼 통과) 을 본다 |
| 1824 | 재확인 불일치 한 번 재시도 · 재확인 두 번째 불일치 실패 · 편입 커밋 두 번째 시도에서 떼어 냄 (`organization.race.itspec.ts`, 파일 전체 13 건) | 13 건 전부 통과 (파일 전체 녹색). 게이트 #6 의 `jgreen` 3 과 `jnames` 3 이 섰다. 게이트 #7 (정적 · 무잠금 읽기 0 건 · 변이 사본 네 종 검출 · 「첫 일관 읽기」 주석) 도 섰다 |
| 1825 | 회원 정보 수정 잠금 뒤 재판정 · 회원 정보 수정 기업 잠금 대기 · 회원 정보 수정 범위 밖 잠금 0회 · 회원 정보 수정 OFF 경로 불변 · 회원 정보 수정 ORG 빈 소속 해제 거부 (`fc-agency-edit-scope.itspec.ts`, 다섯 건) | 5 건 전부 통과. 게이트 #9 의 `jgreen` 5 와 `jnames` 5 가 섰다. 게이트 #8 (잠금 순서 실물 · 음성 fixture 두 건 · `getFcByMemId` 의 tx 인자) 도 섰다 |
| 1826 | 조직 권한 업로드 서비스 대기 후 재판정 (`agency-fc-race-service.itspec.ts`) · 지정 적립 서비스 대기 후 재판정 · 결제 취소 서비스 대기 후 재판정 · 환불 서비스 대기 후 재판정 (`money-race-service.itspec.ts`) | 네 건 전부 통과 (업로드 1 · 머니 3). 게이트 #10 의 `jgreen` 1 · `jgreen` 3 과 `jnames` 가 섰다. 판정 대상 행의 잠금 문장 발행 신호가 실제로 났는지 (`didIssue`) 를 단언한다 |

- 코드 리뷰 R1 흡수 뒤 (`fc-agency-edit-scope.itspec.ts`): 같은 파일에 파이프 형상 시험 3 건 (ORG 이름만 · 전사 이름만 · ORG 빈 소속) 과 신원 없음 시험 1 건이 더해졌다. 합 9 건 전부 통과. 게이트 검사 #9 는 기존 다섯 이름을 본다. 수정 전 실행에서 신규 3 건이 붉었다 (반사실).

## 변이 (저자 실행 기록 · 게이트는 #7 의 정적 네 종만 돈다)

변이는 임시 사본 (원본을 백업한 뒤 한 곳을 바꾸고, 시험을 돌린 뒤 원본 바이트로 되돌리고 `cmp` 로 확인) 에서 저자가 돌렸다. 되돌림 뒤 워킹트리에는 변이가 남지 않았다. 기계 재현 (게이트 포함) 은 (미검증) 이다.

| 변이 | 바꾼 자리 | 시험 결과 | 판정 |
|---|---|---|---|
| 변이 1823-가 | 분석 원장 키의 기업 조인을 회원 번호로 잇기 (오배선) | 단건 조인 S7 · S8 · S9 실패 3 건 (나머지 여섯 통과) | 붉음 |
| 변이 1823-나 | 분석 원장 키의 기업 조인 조건을 상수 0 으로 (조인 제거) | 단건 조인 S7 · S8 · S9 실패 3 건 | 붉음 |
| 변이 1824-가 | 재확인 창 네 곳 (runOrgTreeLocked · runSalesOrgWrite · deleteSubtreeIn 두 창) 에 무잠금 읽기 한 줄 (정적 네 종, 게이트 #7 이 사본으로 직접 돌린다) | 위반 1 건씩 검출 (실물 0 건) | 붉음 |
| 변이 1824-나 | 영업 쓰기 트랜잭션 안, 기업 잠금 앞에 조직 일반 읽기 (스냅샷 고정) | 「재확인 불일치 한 번 재시도」 실패 1 건 | 붉음 |
| 변이 1825-가 | B19 잠금 뒤 재판정 줄 (`assertMembersWritableByAdmin(…, tx)`) 제거 | 「회원 정보 수정 잠금 뒤 재판정」 실패 1 건 | 붉음 |
| 변이 1825-나 | ORG 해제 가드를 `dto.orgId === null` 로 되돌림 (블랙리스트) | 「회원 정보 수정 ORG 빈 소속 해제 거부」 실패 1 건 (빈 문자열 경우) | 붉음 |
| 변이 1826-가 | 조직 권한 업로드의 조직 행 잠금 두 곳 (lockOrgForAssign · lockCurrentOrgsOfMembers 의 조직 읽기) 에서 FOR UPDATE 제거 | 「조직 권한 업로드 서비스 대기 후 재판정」 실패 1 건 | 붉음 |
| 변이 1826-나 | 담당 영업 기업 행 잠금 두 곳 (getAgencyByIdForUpdate · getSagIdByAgcIdForUpdate) 에서 FOR UPDATE 제거 (사본 하나) | 「지정 적립 서비스 대기 후 재판정」 실패 (같은 사본에서 세 머니 시험 모두 실패) | 붉음 |
| 변이 1826-다 | 1826-나 와 같은 사본 (결제 취소 경로) | 「결제 취소 서비스 대기 후 재판정」 실패 | 붉음 |
| 변이 1826-라 | 1826-나 와 같은 사본 (환불 경로) | 「환불 서비스 대기 후 재판정」 실패 | 붉음 |

- 1826-나 · 다 · 라 는 사본을 경로별로 따로 만들지 않았다. 판정이 두 함수 (담당 기업 잠금 읽기) 를 함께 쓰므로 한 곳만 빼면 다른 잠금이 같은 자리를 막는다 (정책과 다르게 옮긴 자리 3). 그래서 같은 사본이 세 시험을 모두 붉힌다는 것은 세 경로가 같은 함수에 기댄다는 뜻이다.
- 1824-나 는 처음에 조직 쓰기 (`runOrgTreeLocked`) 의 트랜잭션에 넣었다. 시험은 영업 신원으로 돌므로 그 사본은 시험 경로를 타지 않아 붉지 않았다 (무효 관측). 영업 경로 (`runSalesOrgWrite`) 에 넣은 사본으로 다시 재어 붉음을 얻었다. 이 사실을 기록한다.
- 1826-가 는 처음에 `lockOrgForAssign` 한 곳만 뺐다. 같은 행을 `lockCurrentOrgsOfMembers` 의 조직 읽기가 먼저 잠가 시험이 붉지 않았다 (무효 관측). 두 곳을 모두 뺀 사본으로 다시 재어 붉음을 얻었다. 계획서의 「경로의 대상 테이블 FOR UPDATE 전부」 규칙과 같다.

## 구현 편차 (계획과 다르게 간 자리 · 검토 대상)

1) 구현 라운드에서는 커밋하지 않았다 (사용자 지시). 최종 리뷰 R1 에서 서버 `54b3c629` 를 커밋했고, 그 HEAD 로 응답 probe 를 다시 돌려 행 파일 · 요약을 갱신했다 (검사 #12 · 「최종 리뷰」 절). 지금의 행 파일은 커밋된 빌드다.
2) 응답 probe 의 쓰기 행 이름: 계획은 여섯 행 (`money-issue` · `money-cancel` · `money-refund` · `org-upload` · `b19-edit` · `org-delete`) 을 적었다. 실제 행은 `org-delete-leaf` (SYNORGA10 삭제) 를 넣어 여섯을 더했다 (기존 여섯 행 + 새 여섯 = 쓰기 행 12, 검사 #12 의 11 이상 충족). `org-upload` 는 파일 없이 보내 500 이 나온다 (두 빌드에서 같다). 그 행은 업로드 판정을 보지 않는다 (판정은 경합 시험이 본다). `money-cancel` · `money-refund` 는 없는 번호 (999999) 로 보내 404 가 나고 PG 를 부르지 않는다.
3) 조직 업로드의 기업 불일치 거부는 E0302 (400 BadRequest) 다. 범위 밖 403 이 아니다. 시험은 `BadRequestException` 을 단언한다. 판정 순서상 기업 소속 검사가 조직 경로 검사보다 먼저 난다.
4) 경합 시험의 발행 신호는 「FOR UPDATE 또는 UPDATE」 문장으로 잡는다 (계획 문구와 같다). drizzle 은 질의를 `{ sql, values }` 객체로 넘기고, 머니 저장소는 따옴표 없는 원문 질의 (`FROM tn_agency … FOR UPDATE`) 를 쓰므로 두 형태를 모두 본다. 신호가 나지 않으면 `didIssue` 단언이 붉다 (계획 Validation 의 「발행 신호」 확인).
5) 조직 삭제 「두 번째 불일치」 시험은 회원 편입이 아니라 부분트리 조직 편입으로 만들었다. 회원 편입 훅을 두 번째 시도에 넣으면 잠금 대기 시간 초과로 끝났다 (관측). 교착의 사슬은 최종 리뷰 R1 에서 확인했다 (정정): 두 번째 시도의 회원 잠금 읽기 (IN · ORDER BY) 가 1004 를 next-key X 로 쥐어, 훅의 1004 갱신이 시험 연결 자신의 잠금을 기다렸다 (`performance_schema.data_locks` 실측). 조직 행은 훅 시점에 잠기지 않으므로 훅이 막히지 않는다. 회원 경로의 두 번째 불일치 시험은 최종 리뷰 R1 에서 더했다 (「재확인 두 번째 불일치 실패 (회원 편입)」 · 훅은 1005 를 편입). 「편입 커밋 두 번째 시도에서 떼어 냄」 시험은 계획대로 회원 편입을 쓴다.
6) 게이트 본문에 `iso_reset` (격리 DB 재적재) 과 환경 변수 `M6D_IT_MYSQL_CONTAINER` · `M6D_SCHEMA_DIR` 를 더했다. m6c 게이트의 같은 방식이다. 시험군마다 DB 를 새로 적재하지 않으면 앞 시험의 담당 변경이 다음 시험의 기대 키를 바꾼다 (관측 · 머니 경합 시험이 담당 9102 를 남겨 단건 키 시험이 붉어졌다).
7) 격리 스키마 보정 (`schema-patch.sql`) 을 다섯 시험 파일이 각자 적용한다 (`applySchemaPatch`). 같은 함수를 다섯 번 쓴다. 공용 헬퍼를 만들지 않은 이유는 계획의 Files 표 (시험 파일 목록) 와 게이트 #11 이 파일 단위로 참조를 세기 때문이다.
8) 단건 시험의 기대 키는 독립 SQL 로 읽는다 (계획 그대로). 다만 조직관리자 S1 (지정 적립 상세) 은 라우트가 「조직관리자는 머니지급을 조회할 수 없습니다」 로 먼저 막고, S6 (조직 상세) 는 `assertCorpOrg` 가 ORG 를 먼저 막는다. 두 경우 모두 기대를 거부로 적었다 (「관리자 래퍼 우회」 1) · 2)).
9) 「세 측정 산출」 (would-deny) 은 다시 재지 않았다 (계획 그대로). 착수 sha 와의 차이 26 건은 `measure-sha-gap.txt` 에 있다 (검사 #17 이 줄 수를 맞춘다). 차이 커밋의 의미 판정은 리뷰 몫 (`(미검증)`).
10) tsc 기준선 파일: 원문 행 (줄 · 열 포함) 을 그대로 썼다. 오류 23 건은 전부 기존 spec 파일이다. 리뷰 R1 흡수로 기준선을 착수 sha `47fcc439` 의 클린 체크아웃 (`git archive`, 워크트리 변경 제외) 에서 다시 쟀다 (`# files: 3505`). 이전 기준선은 작업 트리에서 쟀기 때문에 이 구현이 낸 새 오류를 흡수할 수 있었다 (백로그 1840). 검사 #14 는 tsc 를 실제로 돌려 서명 차집합을 본다 (편차 20).
11) 게이트 검사 #2 (이월 짝 검사) 의 붉음 (위 「게이트 결과」 1)) 은 고치지 않았다. 게이트 의미를 바꿔 통과시키는 것은 이 구현의 범위가 아니다 (계획 리뷰 R3 가 같은 자리를 지적했다, 백로그 1841).
12) 게이트 검사 #3 · #5 는 노트의 표 형식만 본다 (계획 Risks 9). 이 노트의 변이 표는 저자 기록이다. 게이트가 변이를 돌리지 않는다.
13) 구현 중 전 파일 `prettier --write` 는 돌리지 않았다 (HEAD 부터 있던 import 줄 차이는 그대로 둔다). 시험 파일 서식은 편차 15 가 다룬다.
14) 계획 시점 관측 표의 「#4 · #6 · #9 · #10 환경 변수 없이 멈춤」 은 구현 뒤 환경 변수를 주입해 돌렸다. 건너뛴 검사는 없다.
15) 서식: 시험 파일 다섯 개 (네 신규 · 변경 한 개 `organization.race.itspec.ts`) 와 `fc.repository.ts` 의 `update` 시그니처 한 줄을 prettier 규칙에 맞췄다 (`fc.repository.ts` 는 HEAD 에서도 import 줄이 prettier 와 달랐다 — 그 줄은 건드리지 않았다). Codex 리뷰 (구현 뒤) 는 서식 맞춤 전 워킹트리를 봤다. 서식 변경은 동작을 바꾸지 않는다 (사본 게이트 재실행으로 확인).

16) 전역 파이프의 own 키 (코드 리뷰 R1 흡수 · HEAD 부터 있던 결함): `ClassValidatorPipe` (`transform: true`) 와 `@Expose()` 때문에 본문에 없는 `orgId` 도 DTO 에 own 키 `undefined` 로 생긴다. 키 존재를 `hasOwnProperty` 로 보던 세 자리 (`fc.repository.ts` `update` · `fc.service.ts` 기업FC 분기 · `fc-agency.service.ts` `isOrgManagerClear`) 를 `dto.orgId !== undefined` 로 바꿨다. 결과: 관리자 회원 수정이 `orgId` 를 보내지 않으면 소속을 지우지 않고 전역 권한 버전도 올리지 않는다. HEAD 는 파이프를 거친 요청에서 소속을 지웠다. 이 변화는 강제 상수와 무관하게 DB 쓰기를 바꾼다. R2 에서 행 파일을 이 빌드로 다시 쟀다. 응답 상태와 대부분의 본문 해시는 같고, 차이는 `b19-edit` 의 readback 칸 하나뿐이다 (착수 빌드는 소속을 지웠고 이 빌드는 소속을 남긴다). 검사 #12 가 이 두 줄만 허용한다 (편차 23).
17) ORG 소속 해제 가드의 OFF 영향 (코드 리뷰 R1 흡수 · 기재, R2 정정): `isOrgManagerClear` 는 강제 상수와 무관하다. 조직관리자가 `orgId` 를 빈 문자열 · `null` 로 보내면 HEAD 에서 200 (소속 해제) 이던 것이 403 이 된다 (정책 7). 「OFF 응답 불변」 의 예외다. 시험 「ORG 빈 소속 해제 거부」 (문자열 · null) 와 파이프 형상 「''」 시험이 이 경로를 본다. R2 정정: `0` · `false` 는 이 가드에 닿지 않는다. `UpdateFcDto` 의 `orgId` 는 `Member` 에서 가져온 `@ValidateIf(값이 null · undefined 가 아니면) @IsString()` 이라 전역 파이프가 HEAD 와 지금 모두 400 으로 막는다 (`fc.entity.ts` 의 orgId 데코레이터 · `fc.dto.ts` 의 `UpdateFcDto` 가 orgId 를 Pick 한다). 0 · false 는 시험할 대상이 아니다.
18) B19 `update` 의 신원 fail-closed (코드 리뷰 R1 흡수): `assignOrg` · `setOrgManager` 와 같이 `admin?.role` 이 없으면 판정 없이 403 이다. HTTP 경로는 라우트가 `req.admin` 을 세우므로 응답은 같다. 시험 「회원 정보 수정 신원 없음 거부」.
19) 검사 #2 짝 행 증거 규칙 (코드 리뷰 R1 흡수 · R2 강화): 이 계획서의 RESOLVED-IN 짝 행은 「증거: 검사 #K」 와 「시험 「이름」」 · 「커밋 <hex>」 · 「· 게이트」 중 하나가 함께 있어야 짝으로 센다. 다른 계획의 행과 REJECTED · (미검증) 행은 번호 인용만으로 짝이다 (과거 판정 기록). 계획 시점 짝 행 8 건 (1806 · 1808 · 1817 · 1819 · 1823–1826) 의 처리 칸 끝에 증거를 더했다. 1820 짝 행은 주장이 부분이라 (미검증) 으로 낮췄다. R2 정정: 1840 · 1841 짝 행의 시험 이름 「tsc 서명 차집합 0 건」 · 「증거 없는 RESOLVED-IN 짝은 계수에서 뺀다」 는 실재하는 시험이 아니어서 「· 게이트」 증거로 바꿨다. 형식만으로는 위조가 통과하므로 실재는 검사 #20 이 본다. 반사실 (R2 재측정, 백로그 사본 · 같은 계수 함수): 전 계획 짝 행의 증거를 지우면 계수 0 → 10. 1841 행의 증거 한 건만 지우면 계수 0 → 1. 시험 이름만 가짜로 바꾸면 계수는 0 그대로다 (이 경우는 검사 #20 이 붉다). 이전 주장 「3 → 2」 는 재현되지 않아 고쳤다.
20) tsc 검사 #14 (코드 리뷰 R1 흡수): 기준선은 착수 sha 클린 체크아웃에서 쟀다 (편차 10). 게이트는 tsc 를 실제로 돌리고 오류 줄의 서명 (경로 · 코드 · 문구, 줄 · 열 제외) 이 기준선 서명의 차집합으로 0 건인지 본다. 파일 수는 기준선 이상이어야 한다. 반사실 (실측): 서버 안 임시 파일 한 개에 타입 오류 한 줄을 넣자 새 서명 1 건이 나왔다 (옛 리터럴 grep 은 같은 상황에서 통과한다). 임시 파일은 즉시 지웠다.
21) 검사 #18 (코드 리뷰 R1 흡수 · 부분): 공유 시그니처를 쓰는 기존 단위 스위트 (`fc` 모듈 전체와 바깥 스펙 셋) 를 jest 로 돌려 전부 통과를 본다. 반사실 (실측): `FcService.update` 본문 첫 줄에 throw 를 넣은 사본에서도 전부 통과했다. 단위 스위트는 목으로 돌아 본문을 실행하지 않는다. 본문 변이는 이 검사가 못 잡는다 (백로그 MEDIUM). 인자 개수 변화는 검사 #14 의 tsc 가 본다 (ts-jest `isolatedModules` 는 인자 개수를 안 본다).
22) bcrypt 위치 (코드 리뷰 R1 · 기각 · 기록): 이번 구현으로 비밀번호 해시가 바깥 트랜잭션 안으로 들어왔다 (`FcRepository.update` 의 `hash`). 상수 ON 이면 기업 행 잠금 시간이 해시 비용만큼 늘 수 있다. 측정 없이 구조를 바꾸지 않는다. 상수 ON 전 (M9) 측정 항목으로 넘긴다.
23) 코드 리뷰 R2 흡수 (게이트 · 시험 · 측정): (a) 검사 #2 · #20: 짝 행 인용의 실재를 검사 #20 이 본다 (「검사 #K」 머리 · 「시험 「이름」」 의 jest 통과 · 「커밋」 의 `git cat-file`). 반사실: 1824 행 시험 이름을 「가짜 시험 이름」 으로 바꾼 본문 사본에서 검사 #2 는 0 (통과) · 검사 #20 은 실패 1 (실측). (b) 검사 #8 · #9: 잠금 사슬에 「잠금 뒤 목적지 재판정」 줄 (`assertOrgWritableByAdmin(dto.orgId, admin, tx)`) 을 넣고 음성 fixture (그 줄 삭제) 를 더했다. 시험 「회원 정보 수정 잠금 뒤 목적지 재판정」 (잠금 전 범위 안인 SYNORGA1C 를 보유자가 범위 밖 경로로 옮기고 커밋 → 잠금 뒤 재판정이 403 · 소속 불변) 을 더했다 (10 건 전부 통과). 반사실: 그 줄을 지운 서비스 사본에서 이 시험이 붉다 (9 통과 · 1 실패 · 원본은 cmp 로 복구 확인). (c) 검사 #12: 행 파일을 작업 트리 빌드로 다시 쟀다. 본문 차이는 `b19-edit` 의 readback 칸 한 개 (소속 해시) 뿐이고 편차 16 의 의도된 변화다. 검사 #12 는 차이 두 줄 · 그 이름이 `b19-edit` 인 것 · readback 을 뺀 본문 일치를 요구한다. 반사실: readback 만 바꾼 사본은 통과 · 다른 행의 상태를 바꾼 사본은 실패 (실측). 쓰기 행 이름은 `awk` 로 정확 일치 (org-delete 와 org-delete-leaf 를 구별). (d) 검사 #19: m6c 시험 두 파일 (`fc-bulk-scope` 14 건 · `agency-fc.race` 6 건) 을 편입했다. m6c 게이트의 나머지 검사는 이 게이트에 없다 (계획 처리표 61행 문구는 백로그 MEDIUM 행으로 넘겼다). (e) 검사 #12 의 음성은 「본문 한 줄 변경 → 실패」 가 아니라 위 (c) 의 두 반사실로 확인했다 (이전 음성 문구는 readback 차이를 못 다루었다).
24) 격리 스키마 확인 (계획 Task 5 · R2 흡수, LOW): 격리 DB (`m6d-iso-` 접두 컨테이너) 에 `schema-patch.sql` 의 네 문장을 적용한 뒤 `SHOW INDEX` 와 FK 를 확인했다. `tn_member` 의 `mem_org_id` (열 `org_id`) · `tn_agency_organization` 의 `idx_org_path` (열 `org_path`) · `idx_org_parent` (열 `org_parent_id`) 가 보였고, `information_schema.KEY_COLUMN_USAGE` 에 `fk_agency_org_parent` (`tn_agency_organization` → `tn_agency_organization`) 가 보였다. 적용 경로가 시험 셋업의 `applySchemaPatch` 와 같은 문장이고, 다섯 시험 파일이 이 적용 뒤 녹색이다. 시험 셋업 안의 SHOW INDEX 로그는 남기지 않았다 (`(미검증)`, 확인은 위 수동 적용 기준).

25) 최종 리뷰 R1 기재 (fable · MEDIUM · 사용자 몫 이월): 회원 정보 수정 (B19) 은 `orgId` 키가 없으면 소속을 지우지 않는다. HEAD 는 전역 파이프의 own 키 `undefined` 때문에 이름만 고쳐도 소속을 지우고 권한 버전을 올렸다 (편차 16). 같은 원인으로 조직관리자가 소속을 빈 문자열 · `null` 로 풀던 HEAD 의 200 은 이제 403 이다 (편차 17). 강제 상수와 무관한 운영 경로 동작 변화이므로 「OFF 응답 불변」 의 예외다. 게이트 #12 는 이 차이를 `b19-edit` readback 칸 두 줄로만 허용한다. 동결된 계획서 Acceptance 의 「행 파일 본문이 cmp 로 같고」 는 완전 일치를 요구하므로 이 허용은 계획과 어긋난다. HEAD 결함 수정이라 코드는 유지한다. 운영 배포 전 stg 에서 「상세 수정 뒤 소속 유지」 와 「조직관리자 빈 소속 403」 을 사람이 확인하고 PRD 결정 로그에 예외를 적는다 (백로그 2026-10-22 MEDIUM 행 · 사용자 몫). 계획 개정은 다음 계획에서 한다.

26) 마감 보완 (2026-10-23 · dc0b63d2 의 OFF 변화 기재 · 두 최종 판정 모두 fix-now): `getCorpOrgPath` 가 orgPath 없는 ORG 신원을 거부하는 것은 강제 상수와 무관하다. `assertMembersWritableByAdmin` 은 기업 회원 쓰기 판정 경로마다 상수를 보지 않고 부른다 (`fc-agency.service.ts` 의 `update` 첫 판정). HEAD 는 이 신원의 범위를 기업 전체로 넓혀 통과시켰다 (fail-open). 그래서 OFF 응답이 바뀐다: 조직관리자가 orgPath 없이 보내는 회원 정보 수정 · 회원 소속 이동 · 조직 쓰기 판정 9경로가 HEAD 의 통과에서 403 이 된다. 「OFF 응답 불변」 의 예외다. 운영 토큰에 orgPath 없는 ORG 신원이 실재하는지는 확인하지 않았다 (PRD Evidence 12 의 `auth.service.ts:277` 발급 경로). 반영: 서버 `ed95501f` 가 OFF 를 명시한 시험 「회원 정보 수정 ORG 신원 orgPath 없음 거부 (강제 상수 OFF)」 를 더했다. 반사실 (실측): 가드 한 줄을 지우면 이 시험과 ON 시험 「회원 정보 수정 ORG 신원 orgPath 없음 거부」 가 둘 다 붉다. 원본은 `cmp` 로 복구했다. 편차 25 와 함께 stg 확인 대상이다 (「사용자 몫」 절, 미검증).
27) 마감 재측정 (2026-10-23 · 기록 정정): 착수 빌드 `47fcc439` 의 응답 행 파일 (`rows-before-m6d.txt`) 을 2026-10-23 에 다시 쟀다. 10-22 기록과 `GET /admin/statistics/analysis/day` · `GET /admin/treat-monitor/daily` 두 경로의 본문 해시가 달랐다. 같은 빌드를 같은 날 두 번 잰 결과 (`before-rerun`) 는 `ed95501f` 와 readback 두 줄을 빼고 같았다. 그래서 날짜에 의존하는 본문으로 판단하고 착수 쪽 행 파일을 당일 측정으로 바꿨다 (10-22 파일은 git 이력에 남는다). 행 파일 재측정 뒤에도 검사 #12 의 규칙 (readback 두 줄 · 쓰기 행 12 · 일곱 쓰기 행) 은 그대로다.

## 정책과 다르게 옮긴 자리 (구현 중 추가)

없음. 계획서의 「정책과 다르게 옮긴 자리」 1–10 을 그대로 따랐다.

## 관리자 래퍼 우회 (업무 판단 · M9 판단 표 입력)

1) 지정 적립 상세 (`GET admin/money/issue/:mniId`) 는 조직관리자를 라우트가 먼저 막는다 (「조직관리자는 머니지급을 조회할 수 없습니다」). 단건 판정 전 거부다. 판단 표에 「조직관리자 지정 적립 상세 거부 (라우트)」 로 넣는다.
2) 조직 상세 (`GET admin/agency-organizations/:orgId`) 는 `assertCorpOrg` 가 조직관리자 (ORG) 를 기업 조직 화면에서 막는다. 단건 판정 전 거부다.
3) 관리자 (`admin`) 역할은 단건 판정 없이 통과한다 (현행 가드와 같다, 정책 10). 시험 「관리자 래퍼 통과」 (판정 행렬의 마지막 칸) 로 고정했다. 정책 2 의 admin 의미 (S1–S5 에서 거부) 를 래퍼에 적용할지는 M9 판단 표 입력이며 사용자 확인 대상이다.

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

- 운영 동일 환경 (복제본) 1회 재생: 복제본 생성은 사람 승인 (2026-10-21 운영 검증 규칙). 이 실행은 격리 합성 데이터로만 돌렸다.
- R5 영업 전사 열람 · 쓰기 의도의 업무 확인: 레거시 예외 0 행 (책임자 · 기한 · 승인은 사용자).
- 결제 취소의 PG 프로세스 종료 · PG 쪽 멱등 (백로그 1805): 별도 결제 정합성 과제. 이 구현은 닫지 않았다.
- 강제 상수 ON · 부팅 fail 전환 · 신규 엔드포인트 강제 활성화 (백로그 1821, M9).
- 회원 정보 수정의 중복 검사 · 형식 검사와 쓰기 사이의 경합 (정책과 다르게 옮긴 자리 2): 닫지 않았다 (`(미검증)`).
- 실트래픽 would-deny: 배포 뒤 측정이 아니라 운영 동일 환경 1회 재생 (사용자 몫).
- 변이의 기계 재현 (위 변이 표 11 행): 저자 실행 기록이다 (`(미검증)`). 게이트 #5 는 행의 존재와 수만 본다.
- 발행 신호 트리거의 결정성: 시험 실행에서는 신호가 나고 판정이 거부되었다. 재판정 뒤 쓰기 구간의 차단은 이 시험이 보지 않는다 (`(미검증)`).
- 업로드 · 머니 대상 테이블 확정: 업로드 = `tn_agency_organization` 행 (`lockOrgForAssign`), 머니 3 경로 = `tn_agency` 행 (`getAgencyByIdForUpdate` · `getSagIdByAgcIdForUpdate`). 소스로 확인했다.
- 운영 실재 인덱스 (mem_org_id · idx_org_path · FK): 격리 덤프에 없었다 (백로그 1822). 운영 실재는 `(미검증, 운영 접속 금지)`. 시험 셋업의 보정으로만 닫았다.
- 응답 probe 의 주석 기록 (쓰기 행 12 개 중 admin-super 한 신원): 영업 · 기업 신원의 OFF 응답은 단위 시험이 덮는다 (`(미검증)`).
- 사용자 몫 (stg 확인 · 결정 로그 예외): 편차 25 의 「상세 수정 뒤 소속 유지」 · 「조직관리자 빈 소속 403」 과 편차 26 의 「orgPath 없는 조직관리자 쓰기 403」 을 stg 에서 사람이 확인한다. 운영 토큰에 orgPath 없는 ORG 신원이 있는지도 함께 본다 (`(미검증)`, 운영 접속 금지 · 확인 방법은 사용자가 정한다). 결과를 PRD 결정 로그에 예외로 적는다. 백로그 2026-10-22 MEDIUM 행 (편차 25 · 1865) 과 같은 자리다.
- 사용자 몫 (fail 전환 · 백로그 1821): 신규 엔드포인트 범위 선언의 단언은 부팅 경로에 배선되어 `ACCESS_CONTRACT_MODE = 'warn'` 으로 돈다 (`access-contract.assert.ts:16`). 실패 모드 전환은 M2 행과 결정 로그 2026-10-21 에 따라 복제 환경 1회 부팅 · 미선언 0 확인 뒤 사용자가 한 줄 커밋한다 (`(미검증)`). 백로그 1833 의 「M9 몫」 은 이 마감에서 정정했다 (정정 행 2026-10-22 · (미검증)).
- 사용자 몫 (1805 · PG 취소 조정): M6 범위 밖이다. 해소되지 않았다. 백로그 HIGH 행 (`→ M9 계획 반영 대상`) 으로 넘겼다.

## 검사별 결과

| 검사 | 결과 | 근거 |
|---|---|---|
| #1 | 섰다 | 서버 `47fcc439` · 정책 병합 `ea3c0359` 가 HEAD 의 조상 · 관리자 · 사이트 착수 sha 같음 · 부모 `8844bb2` 조상 |
| #2 | 섰다 | 짝 없는 HIGH 행 3 건 (백로그 1839 · 1840 · 1841) 에 짝 행을 증거와 함께 append 했다 (편차 19). 이 계획 짝 행 8 건에는 증거를 더했다. 계수 0 |
| #3 | 섰다 | 노트 표 1823 · 1824 · 1825 · 1826 각 한 행 (합 4) |
| #4 | 섰다 | 단건 조인 S1–S9 아홉 이름 통과 · `jgreen` 9 · 실패 0 |
| #5 | 섰다 (저자 기록) | 변이 행 수: 1823 2 · 1824 2 (정적 네 종 + 동적 1 행) · 1825 2 · 1826 4 (나·다·라 는 같은 사본) |
| #6 | 섰다 | 조직 경합 파일 13 건 통과 · 실패 0 · 재시도 세 이름 통과 |
| #7 | 섰다 | 재확인 창 무잠금 읽기 실물 0 건 · 변이 사본 네 종 모두 검출 · 「첫 일관 읽기」 주석 1 건 이상 |
| #8 | 섰다 | B19 잠금 순서 실물 통과 · 잠금 뒤 목적지 재판정 줄 (R2 추가) 포함 · 음성 fixture 세 건 (회원 잠금 삭제 · 회원·기업 역전 · 목적지 재판정 줄 삭제) 실패 · `getFcByMemId` 의 tx 인자 · `outer ? ` 패턴 |
| #9 | 섰다 | 회원 정보 수정 시험 10 건 전부 통과 · 실패 0 · 여섯 이름 (R2: 「잠금 뒤 목적지 재판정」 추가) 이 통과 집합에 있다 |
| #10 | 섰다 | 업로드 1 건 · 머니 3 건 통과 · 실패 0 |
| #11 | 섰다 | 선언 2 · 0014 마이그레이션 3 이름 · 보정 파일 네 이름 · 다섯 시험 파일이 보정 파일을 참조 |
| #12 | 섰다 (R2 재측정) | `# sha=` 첫 줄 두 개 (착수 `47fcc439` · 빌드 HEAD `47fcc439`) · 행 파일을 작업 트리 빌드로 다시 쟀다 (6336 행, 쓰기 행 12). 본문 차이는 `b19-edit` 의 readback 칸 두 줄뿐이다 (편차 16 · 의도된 변화). readback 을 뺀 본문 비교 0 줄 · 쓰기 행 이름 일곱 종 정확 일치 · write_rows 12. 한계: after_sha 는 커밋 전에 HEAD 와 자명하게 같다 (백로그 MEDIUM) |
| #13 | 섰다 | `SCOPE_ENFORCE_SALES_CORP` · `MENU_SOURCE_DERIVED` 각 false 1 건 · `= true as const` 0 건 |
| #14 | 섰다 | `api.yml` 착수 sha 대비 diff 0 · tsc 기준선 `# base-sha: 47fcc439…` · `# errors: 23` (클린 체크아웃 기준) · 이 워크트리에서 tsc 를 실제로 돌려 새 서명 0 건 · 파일 수 ≥ 기준선 |
| #15 | 섰다 | 계획 · 노트 · 측정 디렉터리에 호스트 절대 경로 0 건 |
| #16 | 섰다 | 열두 번호 (1805 · 1806 · 1808 · 1817 · 1819–1826) 각 짝 행 1 건 (1805 · 1821 은 `(미검증)`) |
| #17 | 섰다 | 측정 목록 26 줄 = `git diff --name-only 700b7d26 47fcc439 -- apps/api/src/modules` 26 줄 |
| #18 | 섰다 | 공유 시그니처의 기존 단위 스위트 (`fc` 모듈 전체와 바깥 스펙 셋) 전부 통과 · 실패 · 런타임 오류 · 보류 0 · 「fc-ownership」 · 「scope-enforce」 포함. 본문 변이는 못 잡는다 (편차 21, 백로그 MEDIUM) |
| #19 | 섰다 | m6c 회귀: `fc-bulk-scope` 14 건 · `agency-fc.race` 6 건 = 20 건 전부 통과 (R2 편입) · 나머지 m6c 검사는 이 게이트에 없다 (백로그 MEDIUM) |
| #20 | 섰다 | 짝 행 인용의 실재: 「검사 #K」 머리 · 「시험 「이름」」 의 jest 통과 · 「커밋 <hex>」 의 `git cat-file` (R2 추가) · 인용 행 11 건 · 실패 0 |

- 검사 #2 는 이번 라운드에서 통과했다. 짝 없는 HIGH 세 건 (백로그 1839–1841, 계획 리뷰 R3 산출) 에 증거 있는 짝 행을 달았다. 게이트 의미는 「짝 행이 있어야 한다」 에서 「짝 행이 구현 증거를 인용해야 한다 (이 계획서 행)」 으로 강화했다 (편차 19). 계획 시점 짝 행만으로는 더 이상 통과하지 않는다.

## Codex Implementation Review

- 호출: `node <플러그인>/scripts/lib/codex-invoke.js adversarial-review --impeccable-available --json` (fail-closed Bash 래퍼). 대상은 서버 서브모듈의 워킹트리 미커밋 변경 (파일 3 수정 · 9 신규 · 부모 워킹트리 diff 는 게이트 본문 · PRD · 노트 · 측정 디렉터리).
- 라운드 수: 1 (상한 3 · 사용자 지시 2026-10-19). 대기 73.9 초 · classification `ok` · blocking `false`.
- 판정: `approve` (`converged`), 지적 0 건. 요약: 「B19 의 잠금 순서 · 재판정 · tx 전달과 조직 삭제의 첫 일관 읽기 경로를 확인했다. OFF 의 falsy orgId 거부 확대는 계획에 명시된 변경이다.」 Codex 는 시험을 코드로만 검토했고 DB 시험과 변이를 다시 돌리지 않았다 (읽기 전용 환경). 이 기록은 `(미검증)` 으로 둔다: 시험의 참은 위 「실행 검사명 · 결과」 와 변이 표 (저자 실행) 가 근거다.
- 지적 흡수 · 기각: 해당 없음 (0 건). YAGNI triage 표 없음. 백로그 「DEFER_TO_BACKLOG」 추가 없음.
- 세션 참조: Codex thread `01a16295-1e52-7ff0-a4f8-f1b3c385b104`.
- implement receipt: `.claude/receipts/mccp-implement-codex/admin-rbac-m6d.json` · `codex_verdict=divergent` (봉인). 봉인 근거는 메모 mccp-implement-codex-has-no-diff-to-review 의 dedupe 정책이다 (divergent 로 닫아야 `/mccp:pr` 의 PR-Codex 를 건너뛸 수 없다). R2 정정: 이 봉인은 Codex 원 판정 (approve · 지적 0) 과 다르다. 이 라운드의 지적 0 은 `resolution.converged=true` 가 기록하고, 교차 모델 봉인은 `codex_verdict` 가 기록한다. 두 칸이 다른 값인 것은 봉인 정책의 결과이며, 재봉인 (converged 또는 divergent 유지) 은 최종 fable·codex 리뷰가 정한다. 봉인 파일은 mccp 변조 표지 규칙 (직접 편집 · 재생성 금지) 때문에 손대지 않았다. 「사용자 지시 converged 위장 금지」 는 이 봉인의 근거가 아니다 (운용 정책 §5-4 는 cap 도달 · receipt 부재 상황의 규칙이다).
- 보안 리뷰어: 이 실행에는 서브에이전트 (Task) 호출이 없어 `security-reviewer` 를 부르지 못했다 → 자동 대체 (auto-fallback) 로 기록 (`security_skipped=true`). B19 · ORG 가드 · 시험의 보안 경계는 Codex 리뷰와 위 시험이 본다. 이 빈칸은 `(미검증)`.
- impeccable 설계 게이트: 디자인 신호 없음 (`no-signal`, 백엔드 · 시험만 바뀜). 침묵 건너뜀 (`--impeccable-silent-skip --impeccable-silent-skip-reason no-signal`).
- validate (`mccp:prp-implement`): 종료 2. 누락 `mccp-plan-codex` (계획 receipt 없음 · 사용자 지시 skip-receipt 로 기대된 결과). 그 밖의 항목은 비었다 (`stale` · `blocking` · `open_critical` 0).

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

- 서버 변경 파일: `organization.service.ts` · `organization.repository.ts` (주석만, 동작 불변), `organization.race.itspec.ts` (시험 세 건 추가 · 셋업 보정 · 자식 먼저 삭제), `fc-agency.service.ts` (B19 update · ORG 가드 · `isOrgManagerClear`), `fc.service.ts` · `fc.repository.ts` (tx 인자 · outer 패턴), 신규 시험 네 파일 (`scope-single-join.itspec.ts` · `fc-agency-edit-scope.itspec.ts` · `agency-fc-race-service.itspec.ts` · `money-race-service.itspec.ts`).
- 부모 변경: 게이트 본문 (iso_reset · 환경 변수), PRD (마일스톤 6 Plan 열 · 결정 로그), 노트 (이 파일), 측정 산출 디렉터리 (`.claude/meta/2026-10-22-admin-rbac-m6d/`). 백로그는 계획 시점에 이미 1827–1838 짝 행이 있어 이번에 더하지 않았다.
- 서브모듈 변경 파일 목록 (구현 라운드 기록 · 커밋은 최종 리뷰 R1 에서 `54b3c629`): 위 서버 파일 목록과 같다.

## 코드 리뷰

- 라운드 1 (1/3 · 사용자 상한 3 · 판정 기준: 흡수 = 수정 · 시험 · 게이트 · 기재 중 하나 이상 반영, 이연 = 백로그 행으로 넘김, 기각 = 근거 사실로 닫음). 원 지적 21 건 중 중복 쌍 2 건을 합쳐 19 건으로 본다.
- 층위 열: 리뷰 전 자가 1패스 체크리스트 (CLAUDE.md §2) 의 층 번호. 1 사실 · 설계, 2 주장 ≠ 검사, 3 명령 ≠ 게이트, 4 셸 실패 규약, 5 게이트 실행 불가, 6 게이트가 보는 대상, 7 검사 자신에 대한 주장, 8 사각지대.

| 라운드 | 출처 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| 진입 | 계획 게이트 receipt (구현 진입) | — | 계획 receipt 없이 구현에 들어갔다 (validate 누락 mccp-plan-codex) | 기각 — 사용자 지시 확인(verify-user-directive.sh, 2026-10-19T02:21Z 원문) | 「## 계획 게이트」 절 · 백로그 2026-10-22 RESOLVED-IN 행 | — |
| R1 | 코드 리뷰 (게이트 실행) | HIGH | 게이트 본문이 검사 #2 에서 멈춘다 (rc 1, 줄 217, 계수 3) | 흡수 | 실제 본문 `GATES_OK m6d` (편차 19) | 6 |
| R1 | 코드 리뷰 | HIGH | 검사 #2 가 계획 시점 짝 행만으로 통과한다 (백로그 1841) | 흡수 | 짝 규칙에 증거 인용 필수 · 반사실 3 → 2 (편차 19) | 6 |
| R1 | 코드 리뷰 (security) | HIGH | `isOrgManagerClear` 가 own 키 `undefined` 를 해제로 읽어, 이름만 고치는 ORG 요청이 403 이다 (상수 OFF 에서도) | 흡수 | `fc-agency.service.ts` 술어를 `!== undefined` 로 · 시험 「회원 정보 수정 파이프 형상 ORG 이름만 수정」 수정 전 붉음 · 수정 후 통과 (편차 16) | 6 |
| R1 | 코드 리뷰 (typescript) | HIGH | 위와 같은 원인 (중복 지적) | 흡수 | 위와 같은 수정과 시험 | 6 |
| R1 | 코드 리뷰 (security) | MEDIUM | `fc.repository.ts` · `fc.service.ts` 의 own 키 판정 때문에 이름만 고쳐도 소속이 NULL 이 되고 권한 버전이 오른다 | 흡수 | 두 자리 `!== undefined` · 시험 「회원 정보 수정 파이프 형상 전사 이름만 수정」 수정 전 붉음 · 수정 후 통과 · 권한 버전 불변 (편차 16) | 1 |
| R1 | 코드 리뷰 (typescript) | MEDIUM | 위와 같은 원인 (중복 지적) | 흡수 | 위와 같은 수정과 시험 | 1 |
| R1 | 코드 리뷰 | MEDIUM | tsc 기준선이 작업 트리에서 쟀고, 검사 #14 는 리터럴 grep 이다 (백로그 1840) | 흡수 | 기준선을 착수 sha 클린 체크아웃에서 재측정 (`# files: 3505`) · 검사 #14 가 tsc 를 실행해 서명 차집합을 본다 · 반사실: 임시 타입 오류에서 새 서명 1 건 (옛 grep 은 통과) (편차 20) | 3 |
| R1 | 코드 리뷰 | MEDIUM | 바뀐 공유 시그니처의 기존 단위 스펙을 도는 검사가 없다 (백로그 1839) | 흡수 (부분) | 검사 #18 추가 · 전부 통과 · 반사실: `FcService.update` 본문을 깬 사본도 통과 → 본문 변이 미검출을 백로그 MEDIUM 으로 넘김 (편차 21) | 6 |
| R1 | 코드 리뷰 | MEDIUM | 검사 #12 의 after 행 파일은 작업 트리 빌드이고 after_sha 는 커밋 전에 자명하게 통과한다 | 이연 | 백로그 MEDIUM 행 · 행 파일 재측정 (격리 서버 기동) 과 빌드 식별 형식 변경이 필요해 이번 라운드 밖 | 7 |
| R1 | 코드 리뷰 | MEDIUM | 응답 행 3 개 (취소 · 환불 404, 업로드 500) 가 판정 코드에 닿지 않는다 (1820 증명 부분) | 부분 흡수 | 1820 짝 행을 (미검증) 으로 낮췄다 · 성공 경로 행은 백로그 MEDIUM 행 | 2 |
| R1 | 코드 리뷰 | MEDIUM | 「ORG 빈 소속 해제」 가드의 OFF 응답 변화 (HEAD 200 → 403) 가 노트에 빠졌다 | 흡수 (기재) | 편차 17 | 2 |
| R1 | 코드 리뷰 | MEDIUM | B19 `update` 에 신원 없음 fail-closed 가드가 없다 | 흡수 | `admin?.role` 가드 추가 · 시험 「회원 정보 수정 신원 없음 거부」 수정 전 붉음 · 수정 후 통과 (편차 18) | 1 |
| R1 | 코드 리뷰 | MEDIUM | 회원 경로의 두 번째 불일치 시험이 없고, 노트의 교착 원인은 확인되지 않았다 | 부분 흡수 | 노트 원인을 가설로 낮췄다 (편차 5) · 시험은 백로그 MEDIUM 행 | 2 |
| R1 | 코드 리뷰 | MEDIUM | 단건 9 시험의 픽스처가 잘못 잇는 조인을 구별하지 못하고 기대 키가 리터럴이다 (1823 · 1808 부분) | 이연 | 백로그 MEDIUM 행 · 시드 재설계가 필요해 이번 라운드 밖 · 편차 5 와 별개 | 6 |
| R1 | 코드 리뷰 | MEDIUM | 서버 시험 5 개가 저장소 밖 부모 경로의 schema-patch 를 읽고 보정 함수를 다섯 벌 복사한다 | 이연 | 백로그 MEDIUM 행 · 계획 Files 표와 검사 #11 변경이 따른다 (계획 수정 필요) | 1 |
| R1 | 코드 리뷰 | LOW | 500ms 시간 예산이 부하에서 흔들릴 수 있다 | 기각 | 「blocked」 단언은 안전하다 · 부하에 따른 흔들림은 측정 전 판정 불가 · 예산 제거는 설계 변경 | 8 |
| R1 | 코드 리뷰 | LOW | ORG 빈 소속 시험이 사전 판정만 본다 (0 · false · 잠금 뒤 재검사 미포함) | 부분 흡수 | 파이프 형상 「''」 시험 추가 · 0 · false 는 LOW 로 기록 (편차 17) | 6 |
| R1 | 코드 리뷰 | LOW | 기업FC 정보수정 JSDoc 이 잘못된 메서드에 붙어 있다 | 흡수 (이미 반영) | 작업 트리에는 `fc-agency.service.ts` 의 JSDoc 이 한 곳 (`:783`) 뿐이다 | 1 |
| R1 | 코드 리뷰 | LOW | 게이트 주석의 「세 종」 과 검사 #7 · 계획의 「네 종」 이 어긋난다 | 흡수 | 게이트 주석을 「네 종」 으로 고쳤다 | 1 |
| R1 | 코드 리뷰 | LOW | 업로드 경합 시험이 403 이 아니라 400 을 단언한다 (계획 문구 불일치) | 흡수 (기재) | 편차 3 · 시험 주석 (`agency-fc-race-service.itspec.ts:246`) 이 노트 편차를 가리킨다 | 2 |
| R1 | 코드 리뷰 | LOW | bcrypt hash · compare 가 기업 행 잠금 아래 돈다 | 기각 | 상수 ON 전이고 측정 없이 구조를 바꾸지 않는다 · M9 전 측정 항목 (편차 22) | 1 |
| R2 | 코드 리뷰 (게이트 실행) | MEDIUM | 검사 #2 의 증거 규칙이 「」 안 문자열의 존재만 봐서 위조 인용이 통과한다 (1840 · 1841 의 「tsc 서명 차집합 0 건」 · 「증거 없는 RESOLVED-IN 짝은 계수에서 뺀다」 는 실재하는 시험이 아니다) | 흡수 | 검사 #20 추가 (인용 시험 이름 · 커밋 · 검사 머리를 실재와 대조). 두 짝 행은 「· 게이트」 증거로 바꿨다 (편차 19 · 23 (a)). 반사실: 가짜 이름 사본에서 #2 통과 · #20 실패 1 (실측). 「3 → 2」 는 재현되지 않아 「0 → 1」 로 고쳤다 | 6 |
| R2 | 코드 리뷰 (게이트 실행) | MEDIUM | m6c 회귀 검사가 m6d 게이트 본문에 없다 (계획 처리표 61행의 「그대로 돈다」 가 게이트로 확인되지 않는다) | 부분 흡수 | 검사 #19 가 m6c 시험 두 파일 (20 건) 을 돈다 (편차 23 (d)). 나머지 m6c 검사 편입과 처리표 문구 축소는 계획 수정이라 백로그 MEDIUM 행 | 2 |
| R2 | 코드 리뷰 (서비스) | MEDIUM | B19 잠금 뒤 목적지 재판정 줄 (`fc-agency.service.ts` 의 `assertOrgWritableByAdmin(dto.orgId, admin, tx)`) 을 지워도 게이트 #1–#18 과 시험 9 건이 전부 통과한다 | 흡수 | 시험 「회원 정보 수정 잠금 뒤 목적지 재판정」 추가 (반사실: 그 줄을 지운 사본에서 이 시험 붉음 · 9 통과 · 1 실패 · 복구 cmp). 검사 #8 의 잠금 사슬에 줄을 넣고 음성 fixture 를 더했다. 검사 #9 는 10 건 (편차 23 (b)) | 6 |
| R2 | 코드 리뷰 (receipt) | MEDIUM | implement receipt 가 `resolution.converged=true` 와 `codex_verdict=divergent` 를 함께 싣고, 원 Codex 판정 (approve) 과 어긋난다. 노트의 근거 「사용자 지시 converged 위장 금지」 도 잘못 인용됐다 | 기재 정정 (흡수) · 봉인 유지 | 봉인 근거를 메모 mccp-implement-codex-has-no-diff-to-review 의 dedupe 정책으로 바꾸고 원 판정과 봉인의 차이를 「Codex Implementation Review」 에 적었다. 봉인 파일은 mccp 변조 표지 규칙 (직접 편집 · 재생성 금지) 때문에 건드리지 않았다. 재봉인 여부는 최종 리뷰 판정 (백로그 MEDIUM 행) | 1 |
| R2 | 코드 리뷰 (측정) | MEDIUM | 응답 불변 행 파일이 수정 전 빌드라 현 코드를 대표하지 않는다. 재측정하면 `b19-edit` 의 readback 이 바뀌어 cmp 가 깨진다 | 흡수 | 작업 트리 빌드로 재측정 (6336 행 · 쓰기 행 12). 차이는 `b19-edit` readback 칸 한 개 (편차 16 · 의도된 변화). 검사 #12 가 이 차이만 허용한다 (편차 23 (c)). 반사실: readback 만 바꾼 사본 통과 · 다른 행 상태를 바꾼 사본 실패 (실측). 빌드 식별 (트리 해시) 은 넣지 않음 → 백로그 MEDIUM 행 | 6 |
| R2 | 코드 리뷰 (노트) | LOW | 계획 Task 5 의 SHOW INDEX 확인 결과가 노트와 메타 README 에 없다 | 흡수 | 격리 DB 에 `schema-patch.sql` 네 문장을 적용하고 SHOW INDEX · FK 를 확인해 편차 24 와 메타 README 에 적었다 | 2 |
| R2 | 코드 리뷰 (계획) | LOW | 계획 Files 표에 `isolated-probe.sh` · `identities-scope.json` 이 없고, probe 쓰기 행 이름이 계획의 `org-delete` 가 아니라 `org-delete-leaf` 를 더한다 | 부분 흡수 | 검사 #12 가 일곱 쓰기 행 이름을 정확 일치로 본다. Files 표 보정은 계획 수정이라 백로그 LOW 행 | 6 |
| R2 | 코드 리뷰 (노트) | LOW | 편차 17 이 `0` · `false` 도 HEAD 에서 200 이던 것이 403 이 된다고 적었다 (사실 아님) | 흡수 (정정) | `UpdateFcDto` 의 `orgId` 는 `@ValidateIf` + `@IsString()` 이라 `0` · `false` 는 전역 파이프에서 400 이다 (HEAD 와 지금 같다). 편차 17 을 고쳤다 | 1 |
| R2 | 코드 리뷰 (주석) | LOW | probe.mjs 주석이 `org-upload` 행을 400 이라 적었고 기록은 500 이다 | 흡수 (정정) | probe.mjs 주석 (198행) 을 500 으로 고쳤다. 기록 (`status=500`) 은 그대로이고 편차 2 와 맞는다 | 1 |

- 집계 (원 지적 21 건 기준): 흡수 16 건 (HIGH 4 건 · 고유 3 건), 기각 2 건, 이연 3 건 (after 행 파일 · 픽스처 · schema-patch). 흡수 행 가운데 부분 흡수는 1839 · 쓰기 행 · 회원 경로 두 번째 불일치 · ORG 빈 소속 (LOW) 이다. 나머지 부분은 백로그 MEDIUM 행으로 남겼다.
- 리뷰 흡수로 바뀐 것: 게이트 본문 · 백로그 · 노트 · 측정 디렉터리의 tsc 기준선 (`tsc-baseline.txt`) · 서버 서브모듈 (fc · agency 의 서비스 · 저장소 · 시험). 계획서와 PRD 는 바꾸지 않았다.
- 백로그 기존 짝 행 9 건 (1806 · 1808 · 1817 · 1819 · 1820 · 1823–1826) 의 처리 칸을 고쳤다. 기존 줄의 요지 칸은 그대로다. 추가된 행: 1839–1841 짝 행 3 건 · MEDIUM 5 건.
- 이 라운드 (R1 · R2) 는 커밋하지 않았다. 이후 최종 리뷰 R1 흡수에서 커밋했다 (「최종 리뷰」 절).

- 라운드 2 (2/3 · 원 지적 8 건 · 중복 없음): 흡수 8 건 (부분 흡수 2 건: m6c 회귀 편입 범위 · 계획 Files 표). 기각 0 건. 이연 (백로그 행으로 넘김): m6c 나머지 검사 편입 · 계획 처리표 문구 · 행 파일 빌드 식별 · receipt 재봉인 판정 · 계획 Files 표 보정. HIGH · CRITICAL 지적은 이 라운드에 없다 (high_absorbed 0 · high_open 0 · critical_open 0).
- R2 에서 바뀐 것: 게이트 본문 (검사 #2 · #8 · #9 · #12 · #19 · #20 · 증거 규칙), 시험 `fc-agency-edit-scope.itspec.ts` (목적지 재판정 시험 · 셋업 복구 한 줄), 서버 서비스 (없음 · 줄 삭제는 반사실 사본에서만), 백로그 (1840 · 1841 짝 행의 증거 칸 정정 · MEDIUM 3 행 · LOW 1 행 추가), 노트 (본 절 · 편차 16 · 17 · 19 · 23 · 24 · 게이트 결과 3), 측정 (`rows-after-m6d.txt` 재측정 · `probe-summary-m6d.txt` 의 before_eq_after=no · expected_diff · `probe.mjs` 주석), 봉인 파일은 손대지 않았다.
- 이 라운드 (R1 · R2) 는 커밋하지 않았다. 이후 최종 리뷰 R1 흡수에서 커밋했다 (「최종 리뷰」 절).

## 최종 리뷰

- 라운드 R1 (리뷰어 fable 과 codex · 지적 7 건 · 중복 1 건 포함). 심각도: HIGH 1 · MEDIUM 6 · CRITICAL 0. 사용자 상한 3 라운드 중 1 라운드. 흡수 = 수정 · 시험 · 게이트 · 기재 중 하나 이상 반영, 이연 = 백로그 행, 기각 = 근거 사실로 닫음.
- 층위 열: 리뷰 전 자가 1패스 체크리스트 (CLAUDE.md §2) 의 층 번호. 1 사실 · 설계, 2 주장 ≠ 검사, 3 명령 ≠ 게이트, 4 셸 실패 규약, 5 게이트 실행 불가, 6 게이트가 보는 대상, 7 검사 자신에 대한 주장, 8 사각지대.

| 라운드 | 리뷰어 | 심각도 | 지적 | 판정 | 증거 | 층위 |
|---|---|---|---|---|---|---|
| R1 | fable | HIGH | 검사 #12 가 커밋된 서버 HEAD (`97b3e815`) 에서 붉다. 행 파일 · 요약이 `47fcc439` 를 가리키고 게이트가 #12 에서 멈춘다 | 흡수 | 서버 커밋 `54b3c629` (시험 보강) 뒤 응답 probe 를 그 HEAD 로 다시 돌렸다 (행 6336 · 쓰기 행 12). 행 파일 본문은 이전 측정과 같고 차이는 `b19-edit` readback 두 줄 (검사 #12 허용 범위). 게이트 본문 전체 `GATES_OK m6d` (「최종 게이트」) | 5 |
| R1 | codex | MEDIUM | 같은 원인 (행 파일 · 요약이 최종 빌드와 다르다) | 흡수 (HIGH 행과 중복) | 위 HIGH 행과 같은 증거 | 5 |
| R1 | fable | MEDIUM | 1824 회원 재확인의 두 번째 불일치 → 409 가 시험되지 않았다. 편차 5 의 교착 원인 (다음 키 잠금 범위 가설) 도 틀렸다 | 흡수 | 시험 「재확인 두 번째 불일치 실패 (회원 편입)」 추가 (두 번째 훅은 1005 를 편입). 원인 실측: 두 번째 시도의 회원 잠금 읽기가 1004 를 next-key X 로 쥐어 훅의 1004 갱신이 대기 → 50 초 시간 초과. 편차 5 정정. 반사실: 재확인 비교 `|| !same(memIds, before.memIds)` 를 지운 사본에서 이 시험과 「재확인 불일치 한 번 재시도」 가 붉다 (실측 · 원본 cmp 복구) | 2 |
| R1 | fable | MEDIUM | 관리자 상세 수정이 소속을 지우지 않게 된 운영 동작 변화가 Outcome 밖이고 계획 Acceptance (행 파일 cmp 완전 일치) 와 어긋난다 | 기재 (편차 25) · 사용자 몫 이월 (백로그 MEDIUM 행) | 편차 25 · 백로그 2026-10-22 MEDIUM 행 (근거 fc.repository.ts:388 · fc.service.ts:152 · fc-agency.service.ts:783). 코드는 HEAD 결함 수정이라 유지. 배포 전 stg 확인은 사람 몫 | 1 |
| R1 | codex | MEDIUM | 단건 조인 S3 · S4 가 요청 ID 를 무시하는 구현 오류 (결제 9241 고정) 를 못 잡는다 | 흡수 | S3 · S4 를 결제 9241 (기업 9001 · 영업 9101) 과 9242 (기업 9002 · 영업 9102) 로 번갈아 부른다. 반사실: 두 컨트롤러의 판정 키를 9241 로 고정한 사본에서 S3 · S4 가 9242 쪽에서 붉다 (실측 · 원본 cmp 복구) | 6 |
| R1 | codex | MEDIUM | 회원 정보 수정 성공 시험이 이름 변경 · 소속 변경 성공 · 권한 버전 증가를 보지 않는다 | 흡수 | 이름은 AES 복호 readback (`memName`) 으로 본다. 「파이프 형상 소속 변경 성공」 추가 (이름 · 소속 · 버전 +1). 반사실: 이름 저장을 끈 사본에서 세 시험이 붉고, 버전 증가를 뺀 사본에서 「소속 변경 성공」 이 `Expected 1 Received 0` 로 붉다 (실측) | 2 |
| R1 | codex | MEDIUM | 머니 경합 세 시험이 거부 시 금액 불변을 보지 않는다 | 흡수 | 세 시험 앞뒤로 `moneyState` (회원 9301 의 소스 잔액 · 적립 · 적립 대상 · 환불 행 수) 를 대조한다. 반사실: 취소 서비스 입구에 소스 잔액 1 차감을 넣은 사본에서 「결제 취소 서비스 대기 후 재판정」 이 붉다 (실측 · 원본 cmp 복구) | 2 |
| R2 | fable | MEDIUM | `getCorpOrgPath` 가 orgPath 없는 ORG 신원을 빈 값으로 넘겨, 쓰기 판정 (`countMembersInCorpScope` · `assertOrgWritableByAdmin`) 이 부분트리 조건을 빼고 기업 전체로 넓어진다 (`judgeRowScope` 와 비대칭 · PRD 「orgPath 없으면 거부」 미반영). 회원 소속 이동 · 정보 수정 · 조직 쓰기 경로 | 흡수 | 서버 `dc0b63d2` (`fix(agency)`): 헬퍼가 ORG 이고 orgPath 가 비면 CORP_FORBIDDEN 을 던진다 (호출처는 이 헬퍼 둘뿐). 시험 「회원 정보 수정 ORG 신원 orgPath 없음 거부」 (edit-scope 12 건 전부 통과). 반사실: 가드 줄을 지운 사본에서 이 시험이 붉다 (실측 · 원본 cmp 복구) | 1 |
| R2 | fable | LOW | 단건 조인 시험의 「조직관리자 접두 형제」 사례는 신원 경로가 행보다 긴 쪽이라, 구분자 경계 회귀 (끝 구분자 없는 정규화) 를 아홉 사례 모두 못 잡는다 | 흡수 | 서버 `6bf1bd4a` (`test(access-control)`): 시험 「단건 조인 구분자 경계」 추가 (신원 /SYNORGA1/ · 행 회원 9303 의 조직 /SYNORGA10/ → 거부 · 단건 조인 10 건 전부 통과). 반사실: 단건 판정의 접두 비교를 `want.slice(0, -1)` 로 바꾼 사본에서 이 시험이 붉다 (실측 · 원본 cmp 복구) | 6 |
| R2 | fable | LOW | 마감 판정의 조건부 사유가 편차 25 하나뿐이다. 백로그 1805 (HIGH) 와 PRD Outcome 1821 이 (미검증) 짝으로 집계에서 빠진 채 complete 로 읽힌다 | 흡수 (노트) | 마감 판정 본문에 1805 · 1821 을 조건부 사유로 명시했다 (아래 「마감 판정」). PRD 상태는 바꾸지 않았다. 결정 로그 2026-10-22 는 「사용자 몫」, 백로그 1833 은 「M9 몫」 이라 적어 두 기록이 어긋난다 (사용자가 정한다) | 2 |

- 집계: 흡수 6 건 (HIGH 1 · MEDIUM 5, 중복 1 포함) · 기재와 이월 1 건 (MEDIUM, 백로그 행) · 기각 0 건. high_absorbed 1 · high_open 0 · critical_open 0.
- R2 집계: 흡수 3 건 (MEDIUM 1 · LOW 2) · 기각 0 건 · 이월 0 건 (백로그 행 추가 없음). high_absorbed 0 · high_open 0 · critical_open 0. 서버 커밋 `dc0b63d2` (fix) · `6bf1bd4a` (test). 편차 25 와 R1 기재는 그대로다.
- 서버 커밋 `54b3c629` (`test:` 한 건, 네 시험 파일 · 서식 검사 통과 · tsc 오류 23 건 서명 차집합 0). 부모 커밋은 이 노트 · 행 파일 · 요약 · README · 백로그 행을 싣는다.
- 반사실은 임시 사본에서 돌렸고 매번 원본을 `cmp` 로 되돌려 확인했다. 되돌림 뒤 워킹트리에 변이는 남지 않았다.

### 최종 게이트 (R1 흡수 뒤)

- 게이트 본문 전체 (`.claude/PRPs/plans/admin-rbac-m6d.gates.sh`, 격리 MySQL `m6d-iso-` 접두 · 환경 변수 주입 · 우회 사본 없음): `GATES_OK m6d` rc 0. 검사 #1–#20 이 순서대로 끝까지 섰다. 컨테이너는 끝난 뒤 자기 것만 내렸다 (잔존 0).
- 검사 #2 통과 = 짝 없는 HIGH 행 0 (`unpaired_carry` = 0). 검사 #3 · #5 통과 = 1823–1826 실행 검사 표 4 행 · 변이 붉음 행 (1823 · 1824 · 1825 · 1826 각 2 · 2 · 2 · 4 이상).
- 검사 #12 통과 = 행 파일 머리 `54b3c629` 와 서버 HEAD 일치 · `after_sha` 일치 · 차이 두 줄 모두 `b19-edit` · readback 제외 본문 같음 · 쓰기 행 12 · 일곱 쓰기 행 이름 존재.
- 검사 #14 (tsc) 는 이 게이트 본문에서 돌았다 (오류 23 · 서명 차집합 0). 검사 #19 (m6c 회귀 편입) · #18 (단위 스위트) 통과.

### 마감 판정 (M6 complete)

- 조건 가 (이월 차단): 「admin-rbac-m6」 계획서 HIGH 행 중 「다음 계획 반영 대상」 짝 없는 수 = 0. 근거: 게이트 검사 #2 통과 (`unpaired_carry` = 0).
- 조건 나 (행별 실행 검사 · 변이 붉음 표): 1823–1826 실행 검사 표 (「실행 검사명 · 결과」) 4 행 · 변이 표 11 행 전부 붉음 (검사 #3 · #5 통과). 이번 라운드 반사실 6 건 (위 표) 이 추가 증거다.
- 판정: 두 조건 충족 → **M6 complete (조건부)**. 조건부 사유는 셋이다 (R2 에서 1) 와 2) 를 명시했다. 집계 (조건 가 · 나) 가 이 둘을 세지 않기 때문이다. 백로그 1843 (MEDIUM) 이 이 집계 규칙의 문제를 이미 적어 두었다).
  1) 편차 25 의 운영 동작 확인 (stg · 결정 로그). 사용자 몫.
  2) 백로그 1805 (HIGH · 결제 취소의 PG 프로세스 종료 조정). 이 보완은 닫지 않았다. 짝 행 1827 은 「범위 밖 · 해소 아님」 이다. 검사 #2 는 이 짝을 세므로 0 으로 보인다. 별도 결제 정합성 과제. 사용자 몫.
  3) 백로그 1821 (PRD Outcome 「신규 엔드포인트의 범위 선언 강제는 M2 단언 항목으로 두고 M6 에서 활성화한다」). 짝 행 1833 은 「(미검증) · M9 몫」 이다. Outcome 문장은 아직 M6 활성화를 말한다. 사용자가 Outcome 문장을 M9 로 고치거나 결정 로그 한 줄을 남겨야 한다 (결정 로그 2026-10-22 는 「사용자 몫」, 백로그 1833 은 「M9 몫」 이라 적어 두 기록이 어긋난다).
  범위 밖 · (미검증) · 사용자 몫 (「사용자 몫 · (미검증)」 절) 은 그대로 남는다. PRD 의 마일스톤 6 상태 전환은 하지 않았다 (사용자 확인 몫). 상태를 complete 로 바꾸기 전에 위 2) · 3) 을 읽는다.
- 이번 라운드에서 바뀐 파일: 서버 `apps/api` 시험 네 파일 (커밋 `54b3c629`) · 이 노트 · 측정 디렉터리 (`rows-after-m6d.txt` · `probe-summary-m6d.txt` · README) · 백로그 MEDIUM 행 1 건.

## 마감 재검증

- 대상: 부모 HEAD `16d4d05` (작업 트리 clean) · 서버 `6bf1bd4a` · 관리자 `1301b664` · 사이트 `fc8872a1` (세 서브모듈 모두 clean, 부모 gitlink 와 일치). 서버 HEAD 는 「최종 리뷰」 절이 적은 `54b3c629` 뒤에 `dc0b63d2` · `6bf1bd4a` 두 커밋이 더 있다. 이 재검증은 `6bf1bd4a` 기준이다.
- 실행: 게이트 본문 `admin-rbac-m6d.gates.sh` 를 검사 #1–#20 블록으로 나눠 블록마다 헤더를 붙여 따로 돌렸다 (블록별 rc 를 남기려는 목적). 검사 본문은 바꾸지 않았다. 헤더의 임시 디렉터리 한 줄만 고정했다 (검사 #20 이 앞 시험의 결과 파일을 읽는다). 게이트 하네스의 실행 결함은 없었다.
- 격리 MySQL: 새 컨테이너 `m6d-iso-final` (mysql:8.0.42, 임의 포트 · 임의 비밀번호) 를 띄웠고 끝나면 이 컨테이너만 내린다. 기존 `m6d-iso-mysql-r3` 은 건드리지 않았다. 스키마는 security-review 격리 스키마 (158표 · 데이터 0행 · 10-15, stg 유래 미확인) 의 구조 파일과 저장 함수, 마이그레이션 0031 · 0036, 합성 시드 (m6c) 다. 검사마다 `iso_reset` 으로 다시 적재했다.
- 결과: 20 건 전부 rc 0. 검사 #20 의 마지막 출력은 `GATES_OK m6d` 다.

| 검사 | rc | 핵심 수치 |
|---|---|---|
| #1 | 0 | 서버 `47fcc439` · `ea3c0359` · 부모 `8844bb2` 가 HEAD 의 조상 · 관리자 `1301b664` · 사이트 `fc8872a1` 일치 |
| #2 | 0 | 짝 없는 HIGH 0 · 이 계획을 인용한 백로그 행 39 (하한 12) |
| #3 | 0 | 노트 표 행 1823–1826 각 1 (합 4) |
| #4 | 0 | 단건 조인 시험 통과 10 (S1–S9 아홉 이름 포함) · 실패 0 |
| #5 | 0 | 변이 행 수 1823 2 · 1824 2 · 1825 2 · 1826 4 (저자 기록) |
| #6 | 0 | 조직 경합 시험 통과 14 · 실패 0 · 재시도 세 이름 통과 |
| #7 | 0 | 재확인 창 무잠금 읽기 실물 0 · 변이 네 종 모두 검출 · 「첫 일관 읽기」 주석 1 건 이상 |
| #8 | 0 | B19 잠금 순서 실물 통과 · 음성 fixture 세 건 (회원 잠금 삭제 · 회원·기업 역전 · 목적지 재판정 삭제) 모두 실패 |
| #9 | 0 | 회원 정보 수정 시험 통과 12 · 실패 0 · 여섯 이름 통과 |
| #10 | 0 | 조직 권한 업로드 1 · 지정 적립 · 결제 취소 · 환불 3 통과 · 실패 0 |
| #11 | 0 | 제품 선언 인덱스 2 · 0014 이름 3 · 보정 파일 네 이름 · 시험 파일 5 가 보정 파일 참조 |
| #12 | 0 | 행 파일 6336 행 · 쓰기 행 12 · `after_sha` = `6bf1bd4a` · 본문 차이 2 줄 (b19-edit 의 readback 칸, 편차 16) · readback 을 뺀 비교 차이 0 |
| #13 | 0 | `SCOPE_ENFORCE_SALES_CORP` · `MENU_SOURCE_DERIVED` 각 false 1 · `= true as const` 0 |
| #14 | 0 | `api.yml` 착수 sha 대비 diff 0 · 기준선 오류 23 · tsc 파일 3509 (기준선 3505 이상) · 새 서명 0 |
| #15 | 0 | 계획 · 노트 · 측정 디렉터리 호스트 경로 0 |
| #16 | 0 | 열두 번호 각 짝 행 1 |
| #17 | 0 | 측정 목록 26 줄 = 변경 목록 26 줄 |
| #18 | 0 | 단위 스위트 21 / 21 통과 · 시험 230 · 실패 · 런타임 오류 · 보류 0 · 「fc-ownership」 · 「scope-enforce」 포함 |
| #19 | 0 | m6c 회귀 시험 20 전부 통과 (fc-bulk-scope · agency-fc.race) |
| #20 | 0 | 인용 행 11 · 실패 0 · 통과 시험 · 커밋 인용 실재 |

- 노트 「최종 리뷰」 절의 시험 수 (edit 10 · org 13) 와 실측 (edit 12 · org 14) 이 다르다. 하한은 모두 넘는다. 이 절이 실측이다.
- 응답 불변: 검사 #12 가 「응답 불변」 검사다 (OFF 응답 행 파일 비교, 통과). 검사 #8 은 B19 잠금 순서 검사이고 응답 비교가 아니다.

### 반사실 (임시 사본, 커밋 없음)

- #20: 1824 짝 행의 시험 이름을 「가짜 시험 이름」 으로 바꾼 백로그 사본 → 인용 실패 1 (`통과 시험 없음`) · 게이트 기대 0 이라 붉음.
- #12: 행 파일 셋째 줄 본문에 칸 하나를 더한 사본 → readback 을 뺀 비교 차이 2 줄 · 기대 0 이라 붉음.
- #2: 1824 짝 행의 증거 칸 (「증거: 검사 #K」) 을 지운 사본 → 짝 없는 HIGH 1 · 기대 0 이라 붉음.
- #17: 측정 목록에 한 줄 더한 사본 → 27 줄 ≠ 26 줄 · 붉음.
- #8: 음성 fixture 세 건이 게이트 본문 안에 있고 세 건 모두 실패 판정을 받았다 (내장 반사실).

### 미검증

- 이 재검증은 격리 합성 데이터와 격리 스키마로만 돌렸다. 운영 동일 환경 (복제본) 1회 재생은 사용자 몫이다 (사람 승인 전 생성 금지).
- 검사 #5 의 변이 행 수는 저자 기록이다. 게이트는 행의 존재와 수만 본다 (기계 재현 아님).
- 검사 #19 는 m6c 게이트 전체가 아니라 회귀 시험 두 파일만 돈다 (백로그 MEDIUM 행으로 남김).
- 반사실 네 건은 임시 사본에서 게이트 판정 부분을 따로 돌린 것이다. 게이트 본문 자체는 건드리지 않았다.

### 재검증 뒤 변경 (2026-10-23 마감)

- 서버 커밋 `ed95501f` (`test(agency)`): 시험 「회원 정보 수정 ORG 신원 orgPath 없음 거부 (강제 상수 OFF)」 한 건 (편차 26). 격리 MySQL (`m6d-iso-final2`) 에서 `fc-agency-edit-scope.itspec.ts` 13 건 전부 통과 · 실패 0 · 런타임 오류 0.
- 반사실 (임시 변경, 원본 `cmp` 복구): `getCorpOrgPath` 의 orgPath 거부 줄을 지우면 ON 시험과 OFF 시험이 둘 다 붉다 (실패 2 · 통과 11).
- 응답 행 파일: 서버 HEAD 가 바뀌어 probe 를 `ed95501f` 빌드로 다시 쟀다 (`GK_BUILD=after`, 행 6336 · 쓰기 행 12). 착수 쪽은 같은 날 `47fcc439` 빌드를 `before-rerun` 으로 다시 쟀다 (편차 27). 두 측정의 본문 차이는 `b19-edit` 의 readback 두 줄뿐이다.
- 정정: 위 「게이트 결과」 · 「최종 게이트 실행」 과 이 절의 앞 문단이 말하는 「기존 `m6d-iso-mysql-r3` 은 건드리지 않았다」 는 사실과 다르다. 이 세션을 시작했을 때 docker 에 `m6d-iso-mysql-r3` 컨테이너가 없었다 (`docker ps -a` 실측). 이 마감의 게이트 실행과 재측정은 `m6d-iso-final2` 로 돌렸고, 끝나면 그 컨테이너만 내린다.
- 게이트 재실행 (이 마감, 서버 `ed95501f` · 부모 커밋 뒤): 결과는 아래 「## 마감 판정」 의 「게이트」 줄에 적는다.

## 마감 판정 (2026-10-23 · 보완 3차 마감)

- 판정: **PRD 마일스톤 6 Status complete.** 근거: 조건 가 (검사 #2: 짝 없는 HIGH 0 · 짝 행의 증거 인용 실재 검사 #20) · 조건 나 (검사 #3 · #5: 행별 실행 검사 4 행 · 변이 붉음 행) 와 증명 6 행의 넷 (단건 3의미 spec · ORG orgPath 누락 거부 spec · count · 집계 · 내보내기 일치 · 범위 누락 타 기업 행 0) 이 게이트 실행으로 덮인다. 남은 것은 사용자 몫 또는 다음 단계다 (아래 표).
- 게이트: `bash .claude/PRPs/plans/admin-rbac-m6d.gates.sh` (서버 `ed95501f` · 격리 MySQL `m6d-iso-final2`) → `GATES_OK m6d`, rc 0, 약 65 초. 검사 #1–#20 이 `set -e` 로 끝까지 섰다. 이 실행의 검사 #12 는 `ed95501f` 로 다시 잰 행 파일을 읽는다.
- 갈린 자리 (최종 fable 판정 · 최종 codex 판정 · 이 마감의 채택):

| 항목 | fable | codex | 채택 | 이유 (근거 확인 위치) |
|---|---|---|---|---|
| 1) M6 Status | complete (조건부) | in-progress (보완 계획 필요) | complete | codex 의 근거는 항목 2) 한 건이고, 그 항목은 아래 2) 에서 사용자 몫으로 판정됐다. 조건 가 · 나와 증명 6 행은 게이트 실행으로 덮인다. 갈린 사실은 이 표에 남기고 최종 리뷰가 다시 본다 |
| 2) 신규 엔드포인트 범위 선언 강제 (1821) | 사용자 몫 (fail 전환) | M6 보완 계획으로 백로그 | 사용자 몫 (defer-unverified) | PRD M2 행 「처음 릴리즈는 경고, 다음 릴리즈부터 실패한다」 · 결정 로그 2026-10-21 「경고 모드 복제 환경 1회 부팅 · 미선언 0 확인 뒤 실패 전환」 · 결정 로그 2026-10-22 「신규 엔드포인트 강제 활성화 (1821) 는 이번 보완의 범위 밖이며 사용자 몫」. 코드 배선은 확인했다: `access-contract.assert.ts:16` (`ACCESS_CONTRACT_MODE = 'warn'`) · `:159` (`checkScopeDeclarations` 를 합친다) · `main.ts:86` (부팅 때 호출). 미해결 해석: PRD 문구 「M6 에서 활성화」 를 warn 상수 그대로 두는 것으로 읽을지 여부 (codex 는 이것을 M6 미달로 읽는다). 최종 리뷰가 다시 본다 |
| 3) PG 취소 조정 (1805) | M9 판단 입력 (later-milestone) | accept-as-is (M6 비차단 · 해소 아님) | M9 판단 입력 (백로그 HIGH 행 · `→ M9 계획 반영 대상`) | PRD Outcome · NOT Building 에 PG 조정 행이 없다. 창은 상수 ON · 영업 신원에서만 열린다 (`money.service.ts:2445`, 「scoped = SCOPE_ENFORCE_SALES_CORP && !!salesOwner」). 해소로 세지 않는다 (1827 「해소 아님」 유지) |
| 4) orgPath 없는 ORG 쓰기 (dc0b63d2) | fix-now · 편차 기재 · stg 확인 목록 | fix-now · OFF 명시 시험 · 변경 기록 | fix-now (코드 유지 · 시험 `ed95501f` · 편차 26) + stg 확인 사용자 몫 | 두 판정의 처리가 같다. 정책 §5-5 멈춤 조건은 발동하지 않았다: 응답 행 파일의 본문 차이는 readback 두 줄뿐이고 (검사 #12), 원인은 PRD M6 Outcome 「ORG 신원에 orgPath 가 없으면 거부한다」 의 의도된 요구다 (`fc-agency.service.ts:126-130`). 다만 OFF 응답이 바뀌므로 편차와 stg 확인이 필요하다 |
| 5) 편차 25 (B19 소속 유지 · 빈 소속 403) | defer-unverified | defer-unverified | defer-unverified | stg 확인과 결정 로그 예외 기록은 사용자 몫이다 (「사용자 몫」 절) |

- 이 마감에서 한 것: PRD M6 행 Status · 결정 로그 「2026-10-22 보완 3차 마감」 절 · 백로그 2026-10-22 행 2 건 (1805 M9 판단 입력 · 1833 정정) · 서버 `ed95501f` · 측정 산출 (행 파일 · 요약) 재측정 · 편차 26 · 27 · 이 절과 사용자 몫 항목.
- 백로그 2026-10-22 행 추가 (기존 행은 고치지 않았다): 1805 → `→ M9 계획 반영 대상` (HIGH) · 1833 정정 (`(미검증)`, 사용자 몫). 짝 검사 (검사 #2) 는 `다음 계획 반영 대상` 표지의 HIGH 만 센다. 새 HIGH 행에 그 표지를 달지 않았다.
- 다음 마일스톤이 알 것 (M7 · M8 · M9):
  - 강제 상수 3개 · 전환 스위치 OFF 유지 (검사 #13).
  - M9 판단 입력: 1805 (머니 영역 전환 전), 편차 26 (stg 결과), 관리자 단건 래퍼의 admin 통과 (「관리자 래퍼 우회」 3, 정책 2 의 admin 의미와 다르다).
  - 행 파일 (`rows-after-m6d.txt`) 의 기준 빌드는 `ed95501f` 다. 서버 HEAD 가 바뀌면 검사 #12 는 붉어진다 (머리 줄 · after_sha 가 HEAD 와 같아야 한다). 그때는 probe 를 다시 돌린다. 날짜 의존 경로 (`analysis/day` · `treat-monitor/daily`) 는 다른 날 재측정하면 본문이 다르므로, 착수 쪽도 같은 날 다시 잰다 (편차 27).
- (미검증) · 사용자 몫 (이 마감에서 닫지 않은 것): 운영 동일 환경 (복제본) 1회 재생 (2026-10-21 규칙 · 복제본 생성은 사람 승인) · 편차 25 · 26 의 stg 확인 · 1821 fail 전환 (복제 환경 1회 부팅 뒤 한 줄 커밋) · 1805 (M9 판단) · 변이 행 수 (저자 기록 · 게이트는 행 존재와 수만 본다) · 검사 #19 는 m6c 시험 두 파일만 돈다 (m6c 나머지 검사는 백로그 MEDIUM 행).
