# Notes: insuniverse-docs-40-runtime-vendor-legal-m2

Plan: `.claude/plans/insuniverse-docs-40-runtime-vendor-legal-m2.plan.md`

이 파일은 `/mccp:prp-implement` 구현 창의 리뷰 기록이다. **계획서 본문에 쓰지 않는다** —
`mccp-plan-codex` receipt 가 `plan_hash` 로 계획서를 결박하고 있어 본문 편집은 그 receipt 를
stale 로 떨어뜨린다. 계획서 §NOT Building 도 착수 후 본문 편집을 명시적으로 금지한다.

## Codex Implementation Review

- 호출: `node C:/Users/madsc/.claude/plugins/cache/mccp/mccp/1.32.1/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, v0.2.2)
- 라운드 수: 0
- **Codex skipped per `MCCP_CODEX_DISABLED=1`** — env-level 정책이며 도구 실패가 아니다.
  근거는 실행 관측이다: 최소 프로브
  `adversarial-review --focus "Reply with the single word OK." --timeout-ms 180000 --json` 이
  `{"ok":true,"classification":"disabled","blocking":false,"advisory":false,"durationMs":1}` 를
  냈다. `durationMs=1` 이 short-circuit 경로임을 보인다 — 프로세스 spawn 이 없었다.
  설정 출처는 `C:/Users/madsc/.claude/settings.json` 의 `env.MCCP_CODEX_DISABLED = "1"` 이다.
- 게이트 기록값: `codex_verdict = skipped` · `meta.codex_disabled = true`.
  **`converged` 가 아니다** — cross-gate dedupe(`dedupe.js#evaluateForDedupe`)는 `converged`
  이외의 값에 fail-closed 이므로 이 receipt 는 하류 PR-Codex 를 건너뛰게 만들지 않는다.
- `advisory`(=`unavailable`)를 쓰지 않은 이유: `disabled` 는 사용자가 내린 env 정책이고
  `unavailable` 은 도구 실패의 은폐다. 기록의 의미가 다르다. 계획서 §Codex Adversarial Review 가
  같은 구분을 M2 plan 봉인에서 이미 썼다.

### 이 창에서 새로 생기는 implement-time 결정 (2.5.2 — 리뷰를 못 받은 목록)

계획서가 pre-commit 하지 않은 것만 적는다. Codex 가 꺼져 있어 **아래 5건은 리뷰를 받지 못했다.**

1. `insuniverse-docs-40-runtime-vendor-legal-m2.gates.mjs` 의 심볼 스팬 유도 구현 —
   관측 14 가 스팬 *규칙*(`메서드` = 선행 주석 블록 시작 ~ 같은 들여쓰기 닫는 중괄호,
   `상수` = 선행 주석 블록 시작 ~ 선언 행, `파일전체`)만 정했고 그것을 정규식으로 풀지
   들여쓰기 스캔으로 풀지는 미정이다. 파서 도입은 INV1 이 막는다.
2. `_MS` 상수의 정수 곱 초기자 파싱 형태 — 관측 13 이 "정수 곱" 만 정했고 허용 토큰
   집합(공백·주석 꼬리·괄호)의 경계는 미정이다.
3. `lint-docs.mjs` 에서 재사용할 export 의 실재 여부 — 계획서 Task 0 이 5종
   (`parseDoc` · `tableRowsInRange` · `parseFlatYaml` · `statusPlacementMap` · `APPENDIX_SECTION`)
   을 이름으로 적었으나 그 이름이 실제 export 인지는 착수 시 확인 대상이다.
4. 대조표 markdown 표를 하네스가 읽는 방식 — 계획서는 "node 로만 읽는다" 까지만 정했다.
5. `#12d` 사각 3축의 계수 정의 — 출력 형식만 정해졌고 무엇을 1건으로 셀지는 미정이다.

### Security Reviewer

- **해당 없음 (not-applicable, skip 아님).** 이 창의 산출물은 (a) `docs/05-schedules/` 문서 3건과
  (b) 동결 git 트리를 **읽기만** 하는 node 게이트 하네스다. 2.5.5 가 열거한 보안 민감 축
  (auth · crypto · secrets · input validation · SQL/cmd injection · SSRF · path traversal ·
  privilege escalation) 중 어느 것도 이 diff 에 없다. 하네스의 외부 프로세스 호출은
  M1 하네스의 `execFileSync` **배열 인자** 형태를 그대로 미러하며 셸을 경유하지 않는다
  (계획서 Task 0 · Patterns to Mirror `동결 트리 독출`).
- 따라서 receipt 에 `security_skipped` 를 세우지 않는다. 세우면 `/mccp:pr` validator 가
  non-approving 으로 접는데, 그것은 *리뷰를 못 받았다* 는 뜻이고 여기 사실은
  *리뷰 대상 축이 없다* 이다.

### Design Review

- `impeccable-detect.js detect --mode implement` 가 `skill_available=true` ·
  `design_signal=false` · `silent_skip=true` · `silent_skip_reason="no-signal"` 을 냈다.
  렌더링 표면(`.tsx`/`.css`/`.html`) 이 diff 에 0건이라 design surface 가 없다.
- receipt 에 `--impeccable-silent-skip --impeccable-silent-skip-reason no-signal` 로 전달한다.
  `impeccable_skipped` 가 아니다 — skill 은 살아 있고 신호가 없을 뿐이다.
- 그 귀결로 critique retry loop · stage-aware routing · Phase 2.5.5c direction capture ·
  Phase 3.6 finish pass · Phase 3.7 grounding verify 는 전부 no-op 이다.

## Open Questions

없음.

## Task 0 — 음성 케이스 실행 기록

계획서 Validation 의 `# 음성:` 주석은 **저자가 한 번 돌려본 관찰의 기록**이고, 그 관찰을 실제로
얻은 자리가 여기다. 계획서 본문은 `plan_hash` 앵커라 착수 후 편집하지 않는다(CLAUDE.md §1).

프로브는 원본 5파일(문서 3 + M1 문서 + 하네스)을 전량 백업하고 `process.on("exit")` 훅에서
복원한다 — 하네스의 `die` 가 `finally` 를 건너뛰므로 mutate 후 `restore()` 만으로는 잔재가 남는다.
실행 뒤 `git status --porcelain -- docs/05-schedules/` 로 M1 문서 무변경을 확인했다.

### 문서 부재 상태의 실패 (Task 0 종료 시점)

하네스가 서고 문서가 없는 시점에 `check1`~`check10` · `check12` 11건이 전부 rc 1 이었다.
`check0` · `check11` 만 통과했다 — 그 둘이 문서를 보지 않는 검사이기 때문이다. 즉 **하네스가
문서에 맞춰 휘지 않았다**는 관찰이며, 계획서 Risks 의 *하네스가 문서에 맞춰 휜다* 행이
겨냥한 자리다.

| 검사 | rc | 첫 실패 행 |
|---|---|---|
| `check1` | 1 | `#1 FAIL: 문서가 없다 — docs/05-schedules/01-retention.md` |
| `check2` | 1 | `#2a ok` 출력 뒤 `#2c` 에서 문서 부재로 죽는다 |
| `check3`~`check10` · `check12` | 1 | `FAIL: 문서가 없다 — docs/05-schedules/01-retention.md (Task 1~5 미완)` |

### 음성 프로브 25건 (문서 완성 후)

전건 rc 1. 아래는 관측된 실패 행이다.

| 검사 | 음성 케이스 | 관측된 실패 행 |
|---|---|---|
| `#0` | M1 배치표 행 1개 삭제 | `#0 FAIL: M1 하네스 rc=1` |
| `#0` | M1 문서에 무해한 행 추가 | `#0 FAIL: … 워킹트리에서 변했다 — M docs/05-schedules/04-runtime-topology.md` |
| `#1` | 정책 하나를 `## 정책 (기획)` 으로 올림 | `#1 FAIL: lint 가 exit 1` (lint 가 `placement` 2건을 낸다) |
| `#2` | 문서에서 정책 1건 통째 제거 | `#2c FAIL: 01-retention.md 유도에만 retention:delete-fc` |
| `#3` | status 를 `data-dependent-unknown` 으로 | `#3a FAIL: monitoring:analysis-status 기대 conditional-unknown 실제 data-dependent-unknown` |
| `#4` | 기준값 `1시간` → `60일` | `#4 FAIL: monitoring:fax-status 기준값 '60일' 이 재유도값 '1시간' 과 다르다` |
| `#4` | 코드 앵커 행을 한 줄 옮김 | `#4 FAIL: monitoring:fax-status 코드 원문 불일치 (… fax.service.ts:1196)` |
| `#4` | settlement 주석 전용 행 기준값 → `1개월` | `#4 FAIL: billing:settlement-agency 기준값 '1개월' 이 재유도값 '1일' 과 다르다` |
| `#5` | FAX 판정 `불일치` → `일치` | `#5 FAIL: … 판정 '일치' 인데 재계산은 '불일치' (주석 3시간 · 코드 1시간)` |
| `#5` | stall 판정 `일치` → `불일치` | `#5 FAIL: … 판정 '불일치' 인데 재계산은 '일치' (주석 1시간 · 코드 1시간)` |
| `#5` | 정규 쌍 둘인 원문 행에 `일치` | `#5 FAIL: … 재계산은 '정규화 불가' — 복수 수량 행은 정규화 불가로 고정` |
| `#5` | 판정 어휘 밖 값(`같음`) | `#5 FAIL: … 판정 '같음' 이 어휘 6종 밖이다` |
| `#6` | `unknown_reason` 참조 문자열 삭제 | `#6 FAIL: billing:expired-money 의 unknown_reason 이 … 를 담지 않는다` |
| `#7` | 부록에 `이 삭제 배치는 안 돈다` 한 줄 | `#7 FAIL: 1건 — 01-retention.md:220 '안 돈다'` |
| `#8` | 앵커 행 번호를 파일 행수 초과로 | `#8 FAIL: … delete-treat.schedule.ts:9999 는 파일 행수 40 초과` |
| `#9` | 연혁 SHA 한 글자 변경 | `#9 FAIL: 0e470688 를 commits.server.jsonl 에서 못 찾는다` |
| `#9` | 무관한 커밋을 연혁에 넣음 | `#9 FAIL: 2ec78694 는 01-retention.md 의 앵커 경로를 건드리지 않는다` |
| `#10` | 적용 범위 `영업관리자` 열 삭제 | `#10a FAIL: 01-retention.md 헤더 누락 영업관리자` |
| `#10` | 적용 범위 칸 하나 비움 | `#10b FAIL: 빈 칸 — retention:delete-fc / 관리자` |
| `#11` | 하네스 파일 삭제 | `Error: Cannot find module … m2.gates.mjs` (양성 프로브가 먼저 떨어진다) |
| `#12` | `analysis-status` 앵커에서 33행 제거 | `#12a FAIL: … 유도에만 … analysis-status.schedule.ts:33` |
| `#12` | FAX `3시간` 행을 대조표에서 제거 | `#12c FAIL: 커버 안 된 단위 1건 — (monitoring:fax-status, fax-status.schedule.ts:12, 3시간, 주석)` |
| `#12` | `expired-money` 임계 없음 행 제거 | `#12c FAIL: 영단위 로케이터 의무 미충족 1건 — (billing:expired-money, money.repository.ts#getExpiredSources :1746)` |
| `#12` | 로케이터 심볼 이름을 틀림 | `#12c FAIL: 심볼 미해석 modules/money/money.repository.ts#getExpiredSourcesX` |

### 관찰을 얻지 못한 음성 케이스 1건

`#6` 의 **두 번째 다리**(*없는 스케줄러 id 를 가리키면 `M1 프론트매터에 없다`*)는 이 창에서
발화시키지 못했다. 참조 id 를 없는 이름으로 바꾸면 첫 다리(문자열 포함)가 먼저 떨어져 그
메시지에 도달하지 않고, 두 번째 다리가 단독으로 떨어지려면 *문서 정책 집합에는 있고 M1
프론트매터에는 없는 스케줄러* 가 필요한데 `#2c` 가 정책 집합을 PRD 펜스에 결박하고 M1
프론트매터가 17종 전건을 담아 그 상태가 성립하지 않는다. **fail-closed 후위 검사이며 다른
게이트가 무너질 때만 발화한다** — backlog 에 그 사실로 이월했다.

### 프로브 자체가 틀렸다가 정정된 4건

1차 실행에서 4건이 프로브 오류였고(정책 블록을 id 행만 지워 프론트매터를 깨뜨림 · 인용 원문을
손으로 적어 틀림 · 치환 패턴이 2회 등장 · 출력 선택기가 lint OK 행을 집음) 2차에서 전부 정정해
rc 1 과 기대 메시지를 얻었다. **프로브가 틀리면 게이트가 통과한 것으로 보인다** — 그래서 1차
표의 rc 만 보고 넘기지 않고 메시지까지 대조했다.

## Task 0 — implement-time 결정 5건의 착지

§2.5.2 가 리뷰 없이 남겼다고 적은 5건이 실제로 어떻게 결정됐는지의 기록이다.

1. **심볼 스팬 유도** — 정규식 + 들여쓰기 스캔. `메서드` 는 선언 행의 들여쓰기와 같은 깊이의
   닫는 중괄호까지, `상수` 는 선언 행까지, 둘 다 시작은 선행 주석 블록의 첫 행. 심볼이 0회 또는
   2회 이상 해석되면 이름을 찍고 죽는다(관용 없음). 파서 도입 없음(INV1).
2. **`_MS` 정수 곱 파싱** — `\b([A-Za-z_][A-Za-z0-9_]*_MS)\s*=\s*([0-9][0-9\s*]*?)\s*;` 로 잡고
   `*` 로 쪼갠 조각이 **전부 정수 리터럴일 때만** 곱한다. 하나라도 아니면 정규화 실패로 빠진다.
3. **`lint-docs.mjs` export 5종** — 전건 실재를 확인했다(`parseDoc` · `tableRowsInRange` ·
   `parseFlatYaml` · `statusPlacementMap` · `APPENDIX_SECTION`). `schedules.yaml` 은 중첩 맵이라
   `parseFlatYaml` 이 거부하므로 그 파일 전용 제한 리더를 두었다 — 읽는 사실은 둘뿐
   (`baseline.stg.server` · `schedulers` 키와 `source`)이라 M1 처럼 파서를 복제하지 않았다.
4. **대조표 표 독출** — `tableRowsInRange` 재사용. **원문 열은 백틱 코드 스팬 필수**로 계약을
   좁혔다 — 표 셀을 파서가 `trim()` 하므로 백틱 없이 적으면 소스 행의 선행 공백이 사라져 원문
   대조가 성립하지 않는다(docblock 연속행은 전부 ` * ` 로 시작한다). 백틱 안쪽은 trim 대상이
   아니다.
5. **`#12d` 사각 계수** — **(파일, 행) 단위로 한 번만** 센다. `파일전체` 로케이터의 스팬이 같은
   스케줄러의 docblock 을 다시 훑으므로 스캔 횟수를 그대로 찍으면 같은 행이 두 번 계수된다
   (초판 실측: 시점 표기 4건 → 정정 후 2건). 부풀린 사각은 거짓 통과는 아니지만 거짓 보고다.

## 계획서와 어긋난 자리 (착수 후 계획서 미편집 원칙 아래 기록만)

| 자리 | 계획서 | 실측 | 처리 |
|---|---|---|---|
| §근거 대조 로케이터 산문 | 로케이터 19건 · 수량 주석 20행 | 표 자체가 21행 · 하네스 유도 21건/21행 | 게이트 기대값은 `<L>`·`<c>` 유도이므로 검사 무영향. backlog 이월 |
| 영단위 로케이터 | 산문이 3건을 이름으로 듦 | 유도 5건 (`getAgencyListForSchedule` · `regular-subscription.schedule.ts` 파일전체 추가) | `#12c` 가 `<z>` 를 유도해 찍는다. 계획서가 의도한 형태 |
| Task 1 `evidence` | `S-schema` 병기 가능 | `[A-scheduler, A-comment]` 만 사용 | `#8` 이 앵커를 유도 좌표(동작 행 ∪ 로케이터 스팬)로 제한하므로 스키마 앵커를 붙일 수 없다. 근거 없는 등급 표기를 피했다 |
| Task 6 backlog 형식 | `\| Date \| Severity \| Source plan \| Finding \|` | 원장 실제 형식은 선행 파이프 없는 평문 행 | `fixtask-projection.mjs` `parseBacklogRow` 가 `^(\d{4}-\d{2}-\d{2}) \| …` 를 요구한다. 파이프를 붙이면 기계 파서가 전건 못 읽는다 — 원장 3행의 자기 선언 형식을 따랐다 |

## /mccp:code-review (Local Review Mode) 흡수 — 2026-08-25

커밋 전 자가 리뷰 1패스. 지적 9건 중 8건 흡수, 1건 이월. 계획서 본문은 `plan_hash` 앵커라
이 라운드에서도 편집하지 않았다.

| # | 등급 | 층위 | 지적 | 처리 |
|---|---|---|---|---|
| 1 | HIGH | 1·2 | `01-retention.md` 두 자리의 *"다른 배치 15종은 `})();`"* 가 동결 트리 실측과 어긋난다. `schedules/` 16파일 중 `})();` 는 14개이고 `samsung-s2-report` 는 `require.main === module` 명명 호출, `push.scheduler` 는 `@Cron` 이다. M1 게이트 `#9c` 자신이 `증거 보유 16종 · 미호출 1종` 을 찍는다 | 흡수 — 문면을 형태 3종 서술로 고치고 **`#6b` 신설**로 M1 `#9c` 출력에서 두 수를 유도해 담당 문서 문면에 결박 |
| 2 | HIGH | 2 | 세 문서 `## 연혁` 의 `그 집합 전체(288/224/244건)` 이 문서 자신이 선언한 규칙(*앵커 경로 접촉 유니크*)으로 재현되지 않는다. 실측 235 / 96 / 199 — 정산은 2.3배 | 흡수 — 값을 유도값으로 교체하고 **`#9b` 신설**로 매 실행 재계산·대조 (INV4) |
| 3 | MEDIUM | 1 | `codePairs` 의 `_MS` 정규화만 `MS_LADDER` 로 단위를 넘는다(`7*24*60*60*1000` → `1주`). 주석 측은 원문 단위 그대로(`7일`)라 표기 비교가 거짓 `불일치` 를 낸다 | 흡수 — `pairEq` 도입. 양측이 고정 단위면 ms 로, 아니면 표기로 본다. `년`·`개월` 은 달력 단위라 환산하지 않는다. 실데이터가 이 분기를 안 밟으므로 `#11` 에 `pairEq` 자기검증 6건을 걸었다 |
| 4 | MEDIUM | 6·8 | `#12c` 는 유도 단위가 표에 **있는가**만 본다. 역방향이 없어 무관한 두 좌표를 한 행에 묶어 `일치` 를 늘려도 전 게이트가 통과한다 | 흡수 — **`#12e` 신설**. 대조표의 각 측면이 유도 단위 또는 영단위 의무인지 본다. `#12d` 문면도 *짝의 옳음* 과 *측면 소속* 을 갈라 적도록 고쳤다 |
| 5 | LOW | 7 | `NEGATIVE_VOCAB` 14종이 M1 하네스에서 축자 복제인데 드리프트 검사가 없다 | 흡수 — `#7` 이 M1 리터럴을 다시 읽어 집합 동일성을 먼저 본다 |
| 6 | LOW | — | 계획서 §Validation 펜스가 ~8분. `check0`(37초)이 `check1`~`check12` 진입마다 재실행된다 — `PRE_ENV` 핸드오프는 `all` 만 자식에게 넘기기 때문 | **이월** — 고칠 자리가 계획서 본문이고 `plan_hash` 봉인이다. backlog 로 넘겼다 |
| 7 | LOW | 6 | `파일전체` 로케이터의 대표 좌표가 무조건 1행이라 `임계 없음` 행의 코드 원문이 `import …` 로 나온다 | 흡수 — 대표 좌표를 선두 docblock 첫 내용 행으로. docblock 이 없을 때만 1행. `02-billing-cycles.md` 해당 행을 `:8` 로 갱신 |
| 8 | LOW | — | `EXCLUDE_RULES` 문자열 괄호 불균형이 `#12a` 출력에 그대로 나온다 | 흡수 — 인용 소스 토큰을 백틱으로 감쌌다 |
| 9 | LOW | — | 리포트 §Files Changed 가 `6` 이라 적고 표는 8행, 실 미커밋 집합은 10파일 | 흡수 — 표를 실 집합으로 맞췄다 |

### 신설 검사 4종의 음성 프로브 (전건 rc 1)

| 검사 | 음성 케이스 | 관측된 실패 행 |
|---|---|---|
| `#6b` | 진입 호출 증거 계수를 옛 오기(`15종`)로 되돌림 | `#6b FAIL: 01-retention.md 가 'delete-fc' 를 담는데 M1 유도값 '16종' 을 문면에 담지 않는다` |
| `#7` | M2 쪽 어휘 목록에만 낱말 하나 추가 | `#7 FAIL: 부정 어휘 복제가 드리프트했다 — M1 에만 [] · M2 에만 [놀고 있다]` |
| `#9b` | 연혁 코호트 계수를 1 틀리게 적음 | `#9 FAIL: 01-retention.md 연혁 산문의 '전체(236건)' 이 유도값 235 과 다르다 (앵커 경로 10개 · commits.server.jsonl 유니크 접촉)` |
| `#12e` | 유도 집합 밖 측면을 갖는 행 1개 추가 | `#12e FAIL: 유도 집합 밖 측면 2건 — monitoring:analysis-stall 코드 … fax.service.ts:1195 (docs/05-schedules/03-monitoring.md:103)` |
| `#11` | `pairEq` 를 표기 비교로 되돌림 | `#11 FAIL: pairEq('1주','7일') 가 false 다 — true 여야 한다` |

프로브는 원본 3파일을 백업하고 `process.on("exit")` 훅에서 복원한다. 실행 뒤
`git status --porcelain` 으로 `04-runtime-topology.md` 무변경을 재확인했다.

### 검사 라벨 정책 — 계획서 기대 문면과의 드리프트 최소화

신설분은 전부 **접미 라벨 추가**(`#6b` · `#9b` · `#12e`)이고 기존 `#6 ok` · `#9 ok` 머리 문면은
축자 그대로 뒀다. 계획서 §Validation 의 `# 기대:` 가 그 문자열을 들고 있고 계획서는 봉인이라,
`#6a`/`#9a` 로 고쳐 대칭을 맞추면 얻는 것 없이 기대 문면 두 줄이 어긋난다. 남은 드리프트는
**출력이 늘어난 것뿐**이며 줄어든 문면은 없다.
