# findings · 12-index-runtime

> **형식**: 00-protocol PRD §발견 기록 규약의 필수 6항목 — 발견 일시 · 관찰 사실 · 재현 절차 · 근거 앵커 · 심각도 · 상태.
> 형식 정본은 00-protocol 마일스톤 7(pending)이며 그때 차이만 맞춘다. **판단과 수정 제안은 적지 않는다 — 사실만.**
>
> **앵커 형식 주기**: 근거 앵커는 `파일:행` 을 기본으로 한다. 다만 F-02 는 **부재**(16파일 전수에 cron 표현이 없음)를
> 관찰한 것이라 가리킬 행이 존재하지 않는다. 없는 행 번호를 지어내지 않고 **집합 앵커**(`디렉터리/패턴 (N파일 전수)`)로
> 적고, 그 부재를 뒷받침하는 별개 사실은 `파일:행` 으로 병기한다.
>
> **조사 기준점**: manifest `repos[].head` — server `4d798f55` · site `767d1e44` · admin `fb001e1d` (전부 `stg`, 2026-08-05 동결).
> 아래 재현 절차의 `git -C <repo> rev-parse HEAD` 가 이 값과 문자열 일치할 때만 값이 재현된다.
>
> **상태 어휘 (2026-08-10, 마일스톤 5 Task 5 · F-28)**: `상태` 값은 잠정 형식 3값
> (`미판정` / `사용자 통보` / `처리 결정됨`) **안에서만** 쓴다. 종전에 `정정`(F-01) · `해소`(F-22·F-23)
> 로 적혀 있던 3건은 실질이 전부 `처리 결정됨` 이므로 값만 어휘 안으로 넣고 **기존 서술은 지우지 않고
> 뒤에 붙였다.** 형식 정본(00-protocol 마일스톤 7)이 확정되면 그때 차이만 맞춘다.
>
> **심각도 어휘**: `낮` / `중` / `높`. 00-protocol §발견 기록 규약은 `심각도` 를 필수 항목으로만 적고
> 값을 열거하지 않는다. F-01~F-23 이 `낮`·`중` 만 썼고 **`높` 은 F-24·F-25 에서 처음 쓴다** — 두 건이
> 겨눈 것이 이 PRD 가 스스로 가장 중요하다고 선언한 두 축(계약 표 · 재현 오라클)이라 `중` 으로 적으면
> 실측보다 낮게 적게 된다. 값을 조용히 늘리지 않기 위해 여기 적어 둔다.

---

## F-01 · `samsung-s2-report` 가 소스에 있으나 배포 선언 양쪽에 없다

- **발견 일시**: 2026-08-05
- **관찰 사실**: `apps/api/src/schedules/` 의 스케줄 소스 16종 중 `samsung-s2-report` 1종이 dockerfile `CMD` 의 `case` 라벨 15개에도, CI `api.yml` 의 `TYPES` 배열 원소 15개에도 없다. dockerfile 과 CI 의 15는 서로 집합이 일치한다(순서만 다름). 즉 `source_only = ["samsung-s2-report"]`, `docker_only = []`, `ci_only = []`.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-scale.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.schedulers.source_only)'
  # → [ 'samsung-s2-report' ]
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/schedules/samsung-s2-report.schedule.ts:9` (`export async function runSamsungS2Report` — 소스에 실재)
  - `insuniverse-server/apps/api/dockerfile:41-57` (case 구간, 라벨 15 — `samsung-s2-report` 없음)
  - `insuniverse-server/.github/workflows/api.yml:200-216` (`TYPES=( ... )`, 원소 15 — `samsung-s2-report` 없음)
- **심각도**: 중
- **상태**: **처리 결정됨** — 정정: prod 선언 있음, 폐기 후보 아님 (2026-08-06, 아래 정정 블록)

### 정정 (2026-08-06) — 위 관찰은 **stg 한정** 진술이다

- **관찰 사실**: 위 본문은 조사 기준점(manifest `repos[].head` = `4d798f55f021d93c889725e50f18cbce20272fd1`, `stg`) 한 축만 스캔한 결과다. `origin/prod` 축(`2d3b238e8e8feea176d828ea21c4b215a711dd3b`, **미동결**)에서는 dockerfile `CMD` `case` 에 `samsung-s2-report` 라벨이 **2개** 존재하고 CI `TYPES` 원소도 17이다. 즉 `samsung-s2-report` 는 prod 에 **배포 선언돼 있다**. 브랜치별 선언 수는 stg 15 · prod 17 이며 라벨 합집합은 17이다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-schedules.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.branches.stg.declared.length, j.branches.prod.declared.length)'
  # → 15 17
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/dockerfile:54-55` (prod `2d3b238e` — `samsung-s2-report-preview` · `samsung-s2-report-final` 라벨 2개)
  - `insuniverse-server/.github/workflows/api.yml:200-218` (prod `2d3b238e` — `TYPES` 원소 17)
- **상태 변경 사유**: `10-index-git` 의 `dead-code-candidate` 회부는 stg 기준으로만 참이었다. prod 에 배포 선언이 있으므로 폐기 후보가 아니다.

---

## F-02 · 스케줄 파일 16/16 에 cron 표현이 없다

- **발견 일시**: 2026-08-05
- **관찰 사실**: `apps/api/src/schedules/` 전수에서 cron 표현·`@nestjs/schedule` 참조가 0건이다. 16/16 이 `NestFactory.createApplicationContext` 로 시작하는 **독립 실행 진입점**이며, 발화 주기를 담고 있지 않다. CI 주석이 "EventBridge 는 task-def 패밀리(리비전 미지정)를 참조하므로 최신 리비전이 자동 적용된다"고 적고 있어, 발화 주기의 소재는 AWS EventBridge 규칙이다 — **저장소 밖이며 `repos[].head` 로 동결되지 않는다.**
- **재현 절차**:
  ```bash
  cd insuniverse-server
  grep -rniE 'cron|EVERY_|@nestjs/schedule' apps/api/src/schedules/ | wc -l      # → 0
  grep -rl 'NestFactory.createApplicationContext' apps/api/src/schedules/ | wc -l # → 16
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/schedules/*.ts (16파일 전수)` — **집합 앵커.** 부재 관측이라 가리킬 행이 없다
  - `insuniverse-server/apps/api/src/schedules/samsung-s2-report.schedule.ts:12` (독립 진입점 형태의 대표 예 — `NestFactory.createApplicationContext`, cron 표현 없음)
  - `insuniverse-server/.github/workflows/api.yml:182` (EventBridge 가 task-def 패밀리를 참조한다는 주석)
- **심각도**: 중
- **상태**: 미판정 — 마일스톤 1 `schedules.yaml` 의 "cron 표현" 열이 저장소 근거로 채워지지 않는다는 사실이 여기 걸려 있다

---

## F-03 · `RUN_MODE`/`SCHEDULER_TYPE` 이 TypeScript 소스에서 0회 참조된다

- **발견 일시**: 2026-08-05
- **관찰 사실**: manifest `env_gate_rules.known_gates` 에 등재된 `RUN_MODE`·`SCHEDULER_TYPE` 이 `apps`·`libs` 의 `.ts` 소스에서 한 번도 읽히지 않는다(0건). 두 값은 dockerfile 에서 `ARG`/`ENV` 로 선언되고, 분기는 `CMD` 의 셸 `case` 문에서만 일어난다. 애플리케이션 코드에는 이 분기가 나타나지 않는다.
- **재현 절차**:
  ```bash
  cd insuniverse-server
  grep -rn 'RUN_MODE\|SCHEDULER_TYPE' apps libs --include=*.ts | wc -l   # → 0
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/dockerfile:35-38` (ARG/ENV 선언)
  - `insuniverse-server/apps/api/dockerfile:40-58` (`CMD` 셸 `case` 분기 — 유일한 소비 지점)
  - `insuniverse-server/.github/workflows/api.yml:181` (task-def env 주입 주석)
- **심각도**: 낮 (구조 사실)
- **상태**: 미판정

---

## F-04 · `delete-fc.schdule.ts` 파일명 오타가 dockerfile 배포 경로에 전파돼 있다

- **발견 일시**: 2026-08-05
- **관찰 사실**: 스케줄 소스 파일 1개의 파일명이 `delete-fc.schdule.ts` 다(`schedule` 이 아니라 `schdule`). dockerfile `CMD` 의 대응 `case` 본문도 같은 오타 경로 `dist/apps/api/src/schedules/delete-fc.schdule.js` 를 가리킨다. 두 이름이 서로 일치하므로 **현재 해결된다** — 추출기의 라벨→소스 파일 해결 검사 15/15 가 통과한다. 나머지 15개 소스 파일은 `.schedule.ts` 다.
- **재현 절차**:
  ```bash
  cd insuniverse-server
  ls apps/api/src/schedules/ | grep schdule      # → delete-fc.schdule.ts
  grep -n 'delete-fc' apps/api/dockerfile        # → 52: ... delete-fc.schdule.js ...
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/schedules/delete-fc.schdule.ts:14` (`NestFactory.createApplicationContext` — 오타 파일명이지만 실제 진입점)
  - `insuniverse-server/apps/api/dockerfile:52` (`node dist/apps/api/src/schedules/delete-fc.schdule.js` — 같은 오타)
- **심각도**: 낮
- **상태**: 미판정

---

## F-05 · `push.scheduler.ts` 가 API 프로세스 안에서 `@Cron` 으로 돈다

- **발견 일시**: 2026-08-05
- **관찰 사실**: `apps/api/src/modules/push/push.scheduler.ts` 가 `@Cron(CronExpression.EVERY_HOUR, { timeZone: 'Asia/Seoul' })` 데코레이터를 가진다. 이것은 `SCHEDULER_TYPE` 분기를 거치지 않고 **API 프로세스 내부에서** 발화하는 별개 기제이며, 서버 저장소 `apps`·`libs` 전체에서 `@Cron` 은 이 1건이다. 독립 진입점 16종과 합치면 시간기반 기제는 17이다.
- **재현 절차**:
  ```bash
  cd insuniverse-server
  grep -rn '@Cron' apps libs --include=*.ts      # → 1건
  ```
  ```bash
  node docs/_meta/verify/extract-runtime-scale.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(j.schedulers.in_process, j.schedulers.inventory.length)'
  # → [ { file: 'apps/api/src/modules/push/push.scheduler.ts', name: 'push.scheduler' } ] 17
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/modules/push/push.scheduler.ts:15`
- **심각도**: 중
- **상태**: 미판정 — 스케줄러 "종수" 의 분모 정의에 영향(마일스톤 0 이 `inventory = 17` 로 확정)

---

## F-06 · 배포 라벨과 스케줄 소스 파일이 1:1 이 아니다 (라벨 1:N + argv 분기)

- **발견 일시**: 2026-08-06
- **관찰 사실**: prod dockerfile `CMD` `case` 에서 소스 파일 1개(`apps/api/src/schedules/samsung-s2-report.schedule.ts`)가 `SCHEDULER_TYPE` 라벨 **2개**(`samsung-s2-report-preview` · `samsung-s2-report-final`)를 뒷받침한다. 두 라벨의 구분은 `node <path>.js` 뒤의 **위치 인자**(`preview` / `final`)로만 이루어진다. 나머지 15개 라벨은 인자가 없다. 그 결과 `scheduler_id` 총원 17(소스 16 + 인프로세스 1)과 라벨 합집합 17은 **개수만 같고 서로 다른 집합**이다 — `samsung-s2-report` 는 id 1개에 라벨 2개를, `push.scheduler` 는 id 1개에 라벨 0개를 기여한다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-schedules.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));
        console.log(j.branches.prod.variants.filter(v=>v.argv.length).map(v=>[v.label,v.argv]))'
  # → [ [ 'samsung-s2-report-final', [ 'final' ] ], [ 'samsung-s2-report-preview', [ 'preview' ] ] ]
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/dockerfile:54` (prod `2d3b238e` — `... samsung-s2-report.schedule.js preview ;;`)
  - `insuniverse-server/apps/api/dockerfile:55` (prod `2d3b238e` — `... samsung-s2-report.schedule.js final ;;`)
  - `docs/_meta/index/schedules.yaml` (`samsung-s2-report.variants` 2건)
- **심각도**: 중
- **상태**: 미판정 — 인덱스 키 모델에 반영됨(`scheduler_id` 최상위 · 라벨은 `variants[]`). 마일스톤 0 의 `docker_declared` 는 라벨 축이므로 소스 축 `inventory` 와 직접 조인되지 않는다는 사실이 여기 걸려 있다

---

## F-07 · prod CI 에만 존재하는 라벨 한정 env 게이트가 1건 있다

- **발견 일시**: 2026-08-06
- **관찰 사실**: prod `.github/workflows/api.yml` 의 task-def env 구성이 `RUN_MODE`·`SCHEDULER_TYPE` 을 전 라벨에 무조건 주입하는 것과 별개로, **`samsung-s2-report-preview` 라벨에만** 조건부로 `SAMSUNG_S2_REPORT_PREVIEW_RECIPIENTS` 를 주입한다(`if $type == "samsung-s2-report-preview"`). 동결 stg 축의 같은 파일에는 이 조건 분기가 없다. 이 키는 manifest `env_gate_rules.known_gates` 에 등재돼 있지 않다.
- **값 미기록 주기**: 이 키의 **값은 개인 식별 정보**(수신자 주소 목록)이며 prod CI 에 평문으로 들어 있다. PRD §Out of scope 가 "개인정보 실제 값 열람 — 마스킹 규칙만 인덱스화" 를 못박고 있으므로 **키 이름과 "prod · `samsung-s2-report-preview` 라벨 한정 주입" 이라는 조건만** 기록하고 값은 이 문서·`schedules.yaml`·PRD 어디에도 옮기지 않았다. 기계 강제는 Validation `#6`(패턴 검출) · `#6b`(`env_gates` 는 대문자 키 이름만) · `#6c`(prod CI 실 값을 토큰화해 산출물 전체에 0건 대조)가 진다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-schedules.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));
        console.log(j.schedulers["samsung-s2-report"].variants.map(v=>[v.label,v.env_gates]))'
  # preview 쪽에만 게이트 3개, final 쪽은 2개
  ```
- **근거 앵커**:
  - `insuniverse-server/.github/workflows/api.yml:230` (prod `2d3b238e` — 기존 env 에서 같은 키를 제외하는 `select`)
  - `insuniverse-server/.github/workflows/api.yml:233-235` (prod `2d3b238e` — 라벨 조건부 주입 분기)
  - `docs/_meta/extraction-manifest.yaml` `env_gate_rules.known_gates` (이 키가 없다)
- **심각도**: 중
- **상태**: 미판정 — `known_gates` 등재 여부는 00-protocol 소유이므로 **회부 4** 로 올렸다(`12-index-runtime` PRD Open Questions)

---

## F-08 · `codef` 맵에만 `NO_DATA` 로 분류된 코드가 0건이다

- **발견 일시**: 2026-08-07
- **관찰 사실**: 통합 택소노미의 벤더 맵 5블록 중 `codef` 블록에만 `NO_DATA` category 로 분류된 native 코드가 **0건**이다. 나머지 4벤더는 `coocon` 1 · `octover` 1 · `hyphen` 6 · `inhouse` 1 건씩 갖는다. `codef` 는 `treatProvider` 스위치의 **기본값**이며(`configuration.ts:29`), 미수록 코드는 `UNKNOWN` 으로 폴백한다(`treat-error-resolver.ts:38`). 즉 기본 경로에서 "조회된 진료내역 없음" 이 별도 분류로 존재하지 않는다. **코드 목록이 불완전한 것인지 벤더가 실제로 구분하지 않는 것인지는 저장소 안 근거로 갈라지지 않는다** — 산출물은 그 갈라지지 않음 자체를 `no_data_discrimination: unmapped` 로 적는다.
- **재현 절차**:
  ```bash
  node -e '
  const t=require("fs").readFileSync("insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts","utf8").replace(/\r\n/g,"\n");
  for (const n of ["CODEF","COOCON","OCTOVER","HYPHEN","INHOUSE"]) {
    const s=t.indexOf("const "+n+":"), b=t.slice(s,t.indexOf("\n};",s));
    console.log(n, [...b.matchAll(/C\.([A-Z_]+)/g)].filter(m=>m[1]==="NO_DATA").length);
  }'
  # → CODEF 0 / COOCON 1 / OCTOVER 1 / HYPHEN 6 / INHOUSE 1
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts:9-29` (`CODEF` 블록 21항목 — `NO_DATA` 없음)
  - `insuniverse-server/apps/api/src/config/configuration.ts:29` (`treatProvider` 기본값 `'codef'`)
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-resolver.ts:38` (미수록 코드 → `UNKNOWN` 폴백)
  - `docs/_meta/index/vendor-behaviors.yaml` (`vendors.codef.outcomes.no_data`)
- **심각도**: 중
- **상태**: 미판정 — PRD "반드시 답할 것" 2번("벤더별로 자료없음과 실패를 구분하는가")의 진료수집 축 답이 여기 걸려 있다

---

## F-09 · 폴백 트리거 코드가 의미 분류 맵에 미수록이다

- **발견 일시**: 2026-08-07
- **관찰 사실**: 멀티 공급자 디스패처의 `PROVIDER_FAULT_CODES` 는 원소 1개(`CF-12103`)뿐이고, 1차 실패 코드가 이것일 때만 다음 공급자로 폴백한다. 그런데 이 코드는 `VENDOR_MAP` **5블록 어디에도 없다**(파일 전체 0건). 즉 **폴백을 트리거하는 코드와 그 코드의 의미를 분류하는 맵이 서로를 모른다** — 폴백 경로에 들어간 코드는 category 해석에서 `UNKNOWN` 으로 떨어진다.
- **재현 절차**:
  ```bash
  grep -n "PROVIDER_FAULT_CODES" insuniverse-server/apps/api/src/modules/treat/dispatch/treat-dispatcher.service.ts
  # → 35 (선언) · 185 (사용)
  node -e '
  const t=require("fs").readFileSync("insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts","utf8");
  console.log(t.includes("CF-12103"));'
  # → false
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/modules/treat/dispatch/treat-dispatcher.service.ts:35` (`PROVIDER_FAULT_CODES` 선언)
  - `insuniverse-server/apps/api/src/modules/treat/dispatch/treat-dispatcher.service.ts:185` (폴백 판정 사용 지점)
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts (파일 전수)` — **집합 앵커.** 부재 관측이라 가리킬 행이 없다
- **심각도**: 중
- **상태**: 미판정

---

## F-10 · 택소노미의 설계 출처 문서가 저장소에 존재하지 않는다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `treat-error-category.ts:3` 과 `treat-error-vendor-map.ts:3` 이 설계 출처로 `server/docs/treat-vendor-error-handling.md` 를 지목하는데, **그 경로가 서버 저장소에 존재하지 않는다**(`docs/` 디렉터리 자체가 없다). 그 결과 코드에서 읽을 수 있는 것은 **매핑 결과**뿐이고 **매핑 사유**는 읽을 수 없다. `12-index-runtime` PRD 의 근거 등급 규약상 이 축의 등급은 `code-only` 이며, 사유를 요구하는 칸은 `unknown` 으로 남는다.
- **재현 절차**:
  ```bash
  cd insuniverse-server
  find . -name "treat-vendor-error-handling*" -not -path "*/node_modules/*" | wc -l   # → 0
  ls docs                                                                             # → No such file or directory
  ```
- **근거 앵커**:
  - `insuniverse-server/ (저장소 전수)` — **집합 앵커.** 부재 관측이라 가리킬 행이 없다
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-category.ts:3` (없는 경로를 출처로 지목하는 주석)
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts:3` (동일)
- **심각도**: 중
- **상태**: 미판정 — `vendor-behaviors.yaml` 의 `basis` 가 전 칸 `code-only`\|`not-observed` 인 이유가 여기 있다

---

## F-11 · manifest `code_namespaces.vendor_codes` 정규식이 실측 코드의 9%만 통과한다

- **발견 일시**: 2026-08-07
- **관찰 사실**: manifest `code_namespaces.vendor_codes` 는 `'^(CF|CODEF|COOCON|HYPHEN|OCTOVER)[-_A-Z0-9]+$'` 다. 벤더 맵의 실측 native 코드 242건에 대입하면 **22건(9.1%)만 통과**한다 — `codef` 21건 전수와 `hyphen` 1건(어댑터 폴백용 `CF-` 접두 코드)이며, `coocon` 68 · `octover` 89 · `hyphen` 나머지 49 · `inhouse` 14 는 전부 미통과다. 정규식이 접두어 열거로 짜여 있는데 실제 코드 형식은 벤더별로 접두어 없는 8자리 hex · 7자리 숫자 · 4자리 숫자 · 알파벳1+숫자3 등으로 흩어져 있다.
- **재현 절차**:
  ```bash
  node -e '
  const t=require("fs").readFileSync("insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts","utf8").replace(/\r\n/g,"\n");
  const RE=/^(CF|CODEF|COOCON|HYPHEN|OCTOVER)[-_A-Z0-9]+$/;
  let pass=0,total=0;
  for (const n of ["CODEF","COOCON","OCTOVER","HYPHEN","INHOUSE"]) {
    const s=t.indexOf("const "+n+":"), b=t.slice(s,t.indexOf("\n};",s));
    let p=0,c=0;
    for (const m of b.matchAll(/(?:.([^.]+).|([A-Za-z_$][\w$]*))\s*:\s*C\.[A-Z_]+/g)) {
      const k=m[1]!==undefined?m[1]:m[2]; c++; if (RE.test(k)) p++;
    }
    console.log(n, p+"/"+c); pass+=p; total+=c;
  }
  console.log("합계", pass+"/"+total);'
  # → CODEF 21/21 · COOCON 0/68 · OCTOVER 0/89 · HYPHEN 1/50 · INHOUSE 0/14 · 합계 22/242
  ```
  > 개수만 출력한다. **코드값 목록은 `11-index-code` 소유**라 이 문서에도 옮기지 않는다.
  >
  > **이 수치는 `extract-runtime-vendors.mjs` 가 강제하지 않는다.** 추출기는 manifest
  > `code_namespaces.vendor_codes` 정규식을 평가하지 않으며(그 정규식은 00-protocol 소유이고
  > 이 마일스톤은 거기 의존하지 않는다 — 유출 검사는 형식 열거가 아니라 실집합 exact-token
  > 교차 비교로 한다), `22/242` 는 **위 스크립트로만 재현되는 관찰치**다. 정규식이 고쳐지면
  > 이 수치는 자동으로 갱신되지 않는다. 회부 7 이 그 판정을 들고 있다.
- **근거 앵커**:
  - `docs/_meta/extraction-manifest.yaml:145` (`code_namespaces.vendor_codes` 정규식)
  - `insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts:8-176` (5블록 전수)
- **심각도**: 중
- **상태**: 미판정 — 정규식은 manifest(00-protocol) 소유, 코드 인벤토리는 `11-index-code` 소유라 **회부 7·8** 로 나눠 올렸다(`12-index-runtime` PRD Open Questions). 이 마일스톤은 집계만 쓰므로 회신을 기다리지 않는다

---

## F-12 · 서브모듈 워킹트리가 CRLF 라 블록 헤더 정규식이 0/5 로 무매칭된다

- **발견 일시**: 2026-08-07
- **관찰 사실**: 루트 `.gitattributes` 는 `docs/_meta/index/*.yaml` 과 `docs/_meta/verify/*.mjs` 두 glob 에만 `text eol=lf` 를 건다(회부 5 수용, 2026-08-06). **서브모듈은 그 밖이며 자체 `.gitattributes` 도 없다.** 저장소가 `core.autocrlf=true` 이므로 서브모듈 워킹트리 파일은 CRLF 로 체크아웃된다 — `treat-error-vendor-map.ts` 실측 CRLF 줄 184. 이 상태에서 파서의 **블록 헤더** 패턴(`= {` 다음이 개행)은 `{` 와 `\n` 사이에 `\r` 이 끼어 **5블록 전부 무매칭(0/5)** 이다. 반면 **블록 종결자** 패턴(`\n};`)은 `\r` 이 개행 앞에 오므로 영향을 받지 않는다(raw 6 · 정규화 후 6). 즉 CRLF 파손은 패턴 위치에 따라 갈리며, 헤더만 조용히 0건이 되므로 fail-closed 가 없으면 **"벤더 없음" 으로 통과한다**.
- **재현 절차**:
  ```bash
  node -e '
  const fs=require("fs");
  const P="insuniverse-server/apps/api/src/modules/treat/error/treat-error-vendor-map.ts";
  const raw=fs.readFileSync(P,"utf8"), lf=raw.replace(/\r\n/g,"\n");
  let crlf=0; for (let i=1;i<raw.length;i++) if (raw.charCodeAt(i)===10 && raw.charCodeAt(i-1)===13) crlf++;
  const hdr=(t)=>["CODEF","COOCON","OCTOVER","HYPHEN","INHOUSE"]
    .filter(n=>new RegExp("const "+n+": Record<string, C> = \\{\\n").test(t)).length;
  console.log("CRLF 줄", crlf, "| 헤더 raw", hdr(raw)+"/5", "정규화", hdr(lf)+"/5",
              "| 종결자 raw", (raw.match(/\n};/g)||[]).length, "정규화", (lf.match(/\n};/g)||[]).length);'
  # → CRLF 줄 184 | 헤더 raw 0/5 정규화 5/5 | 종결자 raw 6 정규화 6
  ```
- **근거 앵커**:
  - `.gitattributes:14-15` (두 glob 만 `text eol=lf` — 서브모듈 미포함)
  - `insuniverse-server/ (저장소 전수, `.gitattributes` 부재)` — **집합 앵커**
  - `docs/_meta/verify/extract-runtime-vendors.mjs` (`readSource` — 전 입력을 읽는 유일한 진입점에서 `\r\n → \n` 정규화)
- **심각도**: 중
- **상태**: 미판정 — 이 마일스톤의 추출기는 정규화 + fail-closed 로 덮었다. 서브모듈 EOL 정책 자체는 서브모듈 저장소 소유라 여기서 다루지 않는다

---

## F-13 · 전역·기업별 약관 테이블에 버전 컬럼이 없고 unique 제약이 타입당 1행을 강제한다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `tn_terms`(5컬럼) 과 `tn_agency_terms`(10컬럼) 어느 쪽에도 이름에 `version` 을 포함하는 컬럼이 **0건**이다. 두 테이블은 각각 `uniq_tem_type`(`tem_type`) 과 `uniq_agc_id_agt_type`(`agc_id`,`agt_type`) 을 갖는다 — 타입당(기업별 타입당) 1행이다. 기업별 약관 갱신은 `agt_content` 를 in-place 로 덮어쓴다. 즉 이 두 축에서 약관 본문의 이전 판은 행으로 남지 않는다. **저장소 안에서 관측된 "약관 버전" 컬럼은 `tn_treat_car_request.tcr_consent_version` 1건이며 그것은 자동차 카나리 경로다.**
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const j=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.terms_corpus;
  console.log("column_present", j.versioning.column_present, "matched", j.versioning.columns_matched);
  console.log(j.sources.filter(s=>s.kind==="db-table").map(s=>s.table+" uniques="+s.uniques.join("|")).join("\n"));'
  # → column_present false / columns_matched 0
  ```
- **근거 앵커**:
  - `insuniverse-server/drizzle/schema.ts:1947-1951` (`tn_terms` 컬럼 블록)
  - `insuniverse-server/drizzle/schema.ts:1961-1970` (`tn_agency_terms` 컬럼 블록)
  - `insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts` (`update()` 가 `agt_content` 를 갱신)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.terms_corpus.versioning`)
- **심각도**: 중
- **상태**: 미판정 — PRD MVP 표의 "약관 버전" 열이 이 두 축에서 저장소 근거로 채워지지 않는다는 사실만 기록한다. 열의 소재 판정은 `40-runtime-vendor-legal` 소관이다

---

## F-14 · 선언된 탐색 universe 안에서 동의 시각 컬럼이 관측된 스키마 경로가 1건이다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `drizzle/schema.ts` 의 `mysqlTable` 블록 155개 전 컬럼을 동의 형태소 8종(`consent`·`agree`·`동의`·`revoke`·`철회`·`terms`·`약관`·`privacy`)으로 훑으면 후보 테이블이 **2건**이다 — `tn_treat_car_request`(5컬럼: 버전·본문·해시·동의시각·철회시각 전부 있음) 과 `tn_b2c_order_detail`(`bod_marketing_agree` 1컬럼, 5축 전부 없음). 동의 시각 컬럼이 관측된 경로는 그중 **1건**이다. **이것은 전수 주장이 아니라 관측 주장이다.** 근거 파일이 introspection 산출물이고 탐색이 형태소 기반이라 두 겹으로 비전수이며, 산출물은 그 한계를 `completeness: unknown` · `complete: false` · `search_universe`(형태소 + sha256) · `blind_spots` 4종 · `write_paths_traced: false` 로 함께 싣는다. 4종 맹점은 introspection 한계 · 직렬화 컬럼 내부 · 형태소 없는 감사 기록 테이블 · DB 밖 영속화다. 컬럼 존재와 실제 기록 여부는 다른 층이며 쓰기 경로는 이번 조사에서 추적하지 않았다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const c=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.consent_record;
  console.log("paths", c.paths.length, "with_timestamp_observed", c.paths_with_timestamp_observed);
  console.log("completeness", c.completeness, "complete", c.complete, "write_paths_traced", c.write_paths_traced);
  console.log("blind_spots", c.blind_spots.length);'
  # → paths 2 / with_timestamp_observed 1 / completeness unknown / complete false / write_paths_traced false / blind_spots 4
  ```
- **근거 앵커**:
  - `insuniverse-server/drizzle/schema.ts:1767-1773` (`tn_treat_car_request` 동의 5축)
  - `insuniverse-server/drizzle/schema.ts:2495` (`tn_b2c_order_detail.bod_marketing_agree`)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.consent_record`)
- **심각도**: 중
- **상태**: 미판정 — 관측 범위 밖 경로의 존재 여부는 저장소 안 근거로 갈라지지 않는다

---

## F-15 · `LEGAL_KEYWORDS` 의 `철회` 가 정산 업무 취소와 동의 철회를 함께 센다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `extract-runtime-scale.mjs` 의 `LEGAL_KEYWORDS` 는 한글을 부분문자열 전역 매칭으로 센다. `철회` 는 3저장소 필터 후 **40회 / 20파일**이며, 문맥 분류 규칙(동의 축 우선, 앞 14자 · 뒤 12자 창)을 걸면 **동의 철회 14 · 업무 취소 21 · 미분류 5** 로 갈린다. 업무 취소 21회는 기업/고객사 정산 **입금철회**, 기업FC **탈퇴철회**, 처리방침의 **청약철회** 기록 보유 조항이다. 미분류 5회는 보험계약 `만기, 해지, 취소, 철회일` 나열과 `컨설턴트의 연결을 철회` 로, 어느 통에도 규칙이 걸리지 않는다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const j=JSON.parse(require("fs").readFileSync(0,"utf8"));
  console.log(JSON.stringify(j.keyword_scale.ambiguity["철회"]));
  console.log("scale 총계", JSON.stringify(j.keyword_scale.legal_filtered));'
  # → consent_revocation 14 / business_cancel 21 / unclassified 5 (합 40)
  ```
- **근거 앵커**:
  - `docs/_meta/verify/extract-runtime-scale.mjs` (`LEGAL_KEYWORDS` · `match_rule` 한글 부분문자열 항)
  - `insuniverse-server/apps/api/src/modules/agency/settlement/settlement-admin.controller.ts:185` (기업 정산 입금철회)
  - `insuniverse-server/apps/api/src/modules/fc/agency/fc-agency.service.ts:817` (기업FC 탈퇴철회)
  - `insuniverse-server/drizzle/schema.ts:1773` (`tcr_revoked_at` — 동의 철회 축)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.absent.철회` · `keyword_scale.ambiguity`)
- **심각도**: 중
- **상태**: 미판정 — 미분류 5회가 남아 `docs/_meta/term-requests/12-index-runtime.md` T-02 로 회부했다

---

## F-16 · 수탁업체가 코드·스키마에 없고 약관 본문 텍스트 7파일에만 있다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `수탁` 은 3저장소 필터 후 **25회 / 7파일**이며 7파일 전부가 약관 본문 코퍼스다 — 서버 seed SQL 1(`0024_company_terms_defaults.sql`), site 프론트 상수 3(`alimtalk`·`invoicesOutput`·`join` AgreementContents), site 퍼블 정적 HTML 3(`join.html`·`printBill2.html`·`secondary/customerVerification1.html`). 코퍼스 밖 히트는 **0건**이다. `tn_terms`·`tn_agency_terms` 어느 쪽에도 수탁업체를 담는 컬럼이 없고, 수탁 관계를 나타내는 테이블도 스키마에 없다. 즉 저장소 안에서 수탁업체는 정책 구조가 아니라 문서 문구로 관측된다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const a=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.absent["수탁"];
  console.log("code", a.code_hits, "terms_text", a.terms_text_hits, "files", a.sources.length, "basis", a.basis);'
  # → code 0 / terms_text 25 / files 7 / basis terms-text-only
  ```
- **근거 앵커**:
  - `insuniverse-server/drizzle/0024_company_terms_defaults.sql` (`PRIVACY_THIRD` seed 본문 안 수탁업체 표)
  - `insuniverse-site/publish/secondary/customerVerification1.html`
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.absent.수탁`)
- **심각도**: 중
- **상태**: 미판정 — 저장소 밖 계약·관리 체계의 존재 여부는 여기서 갈라지지 않는다

---

## F-17 · 마스킹 구현이 3저장소에 10곳 병존하며 상호 대조 장치가 없다

- **발견 일시**: 2026-08-07
- **관찰 사실**: 마스킹 구현 신호 3종(`mask*` 함수 선언 · `masking` 플래그 삼항 분기 · `redact*` 선언)으로 3저장소를 훑으면 **10파일**이 걸린다. 정본 후보는 서버 `mask.helper.ts` 하나로 `maskString` 이 5종(`name`·`phone`·`birth`·`email`·`id`)을 디스패치한다. 나머지 9곳은 서버 `invoice.drawer.ts`·`invoice-admin.dto.ts`·`treat-test/vendors/types.ts`·`libs/webclient/webclient.service.ts`, site `invoice.helper.ts`·`invoicesApi.ts`, admin `common.helper.ts`·`invoice.helper.ts`·`invoiceApi.ts` 다. 같은 항목을 가리는 규칙이 파일마다 따로 있고, 세 저장소 어디에도 이들이 서로 같은 결과를 내는지 검사하는 코드가 없다. 이 산출물은 정본 후보 1곳의 규칙만 파싱했고 나머지 9곳은 `rules_unparsed: 9` 로 미조사임을 명시한다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const m=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.masking;
  console.log("total", m.implementations_total, "parsed", m.rules_parsed, "unparsed", m.rules_unparsed);
  console.log(m.parallel_implementations.map(p=>p.path).join("\n"));'
  # → total 10 / parsed 1 / unparsed 9
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/helpers/mask.helper.ts:70-88` (`maskString` 디스패치)
  - `insuniverse-server/libs/webclient/src/webclient.service.ts:50-72` (로그 마스킹 독립 구현)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.masking.parallel_implementations`)
- **심각도**: 중
- **상태**: 미판정 — 규칙 간 어긋남의 실재 여부는 미조사분 9곳을 파싱해야 갈라진다

---

## F-18 · `maskPhone` 주석의 예시가 코드가 만드는 출력 형태와 다르다

- **발견 일시**: 2026-08-07
- **관찰 사실**: `maskPhone` 의 11자리 분기 주석은 `01012345678 → 010-****-5678` 이라 적혀 있고 10자리 분기 주석은 `0212345678 → 02-****-5678` 이라 적혀 있다. 두 분기의 코드는 동일하게 `digits.slice(0, 3)` + `****` + `digits.slice(7)` 조립이며 하이픈을 넣지 않는다. 즉 주석 예시에는 하이픈이 있고 코드 출력 형태에는 없다. 추출기가 이 대조를 기계로 판정해 `comment_matches_code: false` 를 방출한다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const c=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.masking.canonical;
  console.log("comment_matches_code", c.comment_matches_code);
  console.log(c.rules.find(r=>r.fn==="maskPhone").output_forms.join(" "));'
  # → comment_matches_code false / slice(0,3) slice(7)
  ```
- **근거 앵커**:
  - `insuniverse-server/apps/api/src/helpers/mask.helper.ts:17-24` (주석 예시와 반환 표현식)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.masking.canonical.comment_matches_code`)
- **심각도**: 낮
- **상태**: 미판정 — 주석과 코드 중 어느 쪽이 의도인지는 저장소 안 근거로 갈라지지 않는다

---

## F-19 · 약관 본문이 `publish/` 정적 HTML 에도 실려 있고 DB·프론트 상수와 동기화 장치가 없다

- **발견 일시**: 2026-08-07
- **관찰 사실**: site `publish/` 아래 정적 HTML **5파일**에 약관·처리방침 본문이 실려 있다 — `join.html`(456줄) · `printBill2.html`(352줄) · `secondary/customerVerification1.html`(3,244줄) · `privacyPolicy.html`(562줄) · `termsOfUse.html`(597줄). `customerVerification1.html` 에는 `개인정보 제3자 제공` · `수탁업체` · `취급 위탁` 문구가 함께 있다. `publish/` 는 manifest `exclude_glob` 11항목 어디에도 없어 선언된 스캔 universe 안이며, `extract-runtime-scale` 도 이 파일들을 센다. 같은 약관 본문이 DB(`tn_terms`·`tn_agency_terms`) · 프론트 상수 · 프론트 페이지 컴포넌트 · 이 정적 HTML 네 곳에 각각 있고, 네 곳을 대조하는 코드는 세 저장소 어디에도 없다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const s=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.terms_corpus.sources;
  console.log(s.filter(x=>x.kind==="static-html").map(x=>x.lines+"  "+x.path).join("\n"));'
  # → 5줄 (456 / 352 / 3244 / 562 / 597)
  ```
- **근거 앵커**:
  - `insuniverse-site/publish/secondary/customerVerification1.html`
  - `insuniverse-site/publish/privacyPolicy.html` · `insuniverse-site/publish/termsOfUse.html`
  - `docs/_meta/extraction-manifest.yaml` (`exclude_glob` 11항목 — `publish/` 미포함)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.terms_corpus.sources` `kind: static-html`)
- **심각도**: 중
- **상태**: 미판정 — 네 소재가 실제로 어긋나 있는지는 본문 대조를 해야 갈라지며 이 마일스톤은 원문을 다루지 않는다

---

## F-20 · 약관 본문 소재를 손으로 열거한 목록이 도출 결과보다 6파일 적었다

- **발견 일시**: 2026-08-07
- **관찰 사실**: 마일스톤 3 계획은 약관 본문 소재를 4부류 10항목으로 열거했다(`db-table` 2 · `front-const` 4 · `static-html` 3 · `seed-sql` 1). 같은 계획이 명령한 도출 절차 — 3저장소 `git ls-files` universe 에 manifest `exclude_glob` 을 적용하고 약관 형태소 8종으로 거르기 — 를 실행하면 **14파일**이 나온다. 차이 6파일은 `front/src/pages/terms/TermsPage.tsx`(453줄) · `front/src/pages/privacy/PrivacyPage.tsx`(386줄) · `front/src/pages/terms/B2cPersonalInfoPage.tsx`(58줄) · `front/src/pages/terms/B2cSensitiveInfoPage.tsx`(58줄) · `publish/privacyPolicy.html`(562줄) · `publish/termsOfUse.html`(597줄) 이며, 앞의 4파일은 계획의 부류 어휘 어디에도 자리가 없었다. 같은 부류의 누락이 이 계획에서 두 번 관측됐다 — 최초 판은 `publish/` 3파일을 놓쳤고(santa R1 에서 확인), 정정판은 계획의 골든 자체가 손으로 적은 목록이었다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-legal.mjs --json | node -e '
  const t=JSON.parse(require("fs").readFileSync(0,"utf8")).axes.terms_corpus;
  const by={}; t.sources.forEach(s=>by[s.kind]=(by[s.kind]||0)+1);
  console.log("corpus_files", t.corpus_files, "items", t.sources.length, JSON.stringify(by));'
  # → corpus_files 14 / items 16 / db-table 2 · front-const 4 · front-page 4 · static-html 5 · seed-sql 1
  ```
- **근거 앵커**:
  - `.claude/plans/insuniverse-docs-12-index-runtime-m3.plan.md` (Validation `#2` KIND 맵 — implement 정정 주석 포함)
  - `docs/_meta/verify/extract-runtime-legal.mjs` (`deriveCorpus` · `TERMS_CORPUS_MARKERS` · `corpusKind` 의 미분류 `throw`)
  - `docs/_meta/index/legal-surfaces.yaml` (`axes.terms_corpus.derivation` · `file_set_sha256`)
- **심각도**: 중
- **상태**: 미판정 — 도출 형태소 8종 자체도 사람이 고른 집합이라 이 관측이 소재 전수를 보증하지 않는다

---

## F-21 · spec §3 status 8값 중 이 축의 저장소 근거만으로 성립하는 값이 2개다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `.claude/meta/05-final-spec.md` §3 이 정의한 status 8값을 마일스톤 1~3 의 세 인덱스와 대조하면, 값의 정의가 저장소 안 근거만으로 **문자 그대로 성립**하는 것은 `conditional-unknown` 과 `data-dependent-unknown` 둘뿐이다. 나머지 여섯은 정의 안에 배포 도달·게이트 활성 확인·삭제 이력에 대한 주장을 담고 있다 — `active-prod`·`prod-hotfix-unreconciled` 는 prod **도달**을, `conditional-enabled` 는 게이트 **활성 확인**을, `staged-only` 는 prod **미도달**을, `dead-code-candidate` 는 참조 0/도달 불가를, `deprecated-confirmed` 는 삭제·revert 확정을 각각 요구한다. 세 인덱스가 들고 있는 것은 도달이 아니라 **선언**이고(`schedules.yaml` 의 `activation_grade` 최대치가 `deployment-declared` · `active` 17/17 `unknown`), 게이트 운영값은 server `.env` 미수령이다. 그 결과 정책 후보 49행 중 §3 값이 붙는 것은 **33행**이고 16행은 8값 어디에도 자리가 없다 — 게이트가 없어 `conditional-unknown` 조차 성립하지 않는 행(게이트 없는 벤더 3종 × 3 outcome · 법적 표면 6 · 인프로세스 스케줄러 1)이다. 이 16행에 `conditional-unknown` 을 주면 **존재하지 않는 게이트를 주장**하고 `staged-only` 를 주면 **prod 미도달이라는 거짓**을 주장한다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-policy-candidates.mjs
  # → assignment.assigned 33 / assignment.pending 16
  #   status.conditional-unknown 32 · status.data-dependent-unknown 1 · 나머지 6값 전부 0
  ```
- **근거 앵커**:
  - `docs/_meta/index/policy-candidates.runtime.jsonl` (`meta.status_distribution` · `meta.assignment` · `meta.reachability.blocked_by`)
  - `docs/_meta/verify/extract-runtime-policy-candidates.mjs` (`RULE_TABLE.status_rules` · `FORBIDDEN_STATUS` · `meta.rule_table_sha256`)
  - `docs/_meta/index/schedules.yaml` (`coverage.activation_coverage` 0/17 · `baseline.prod.frozen: false`)
  - `.claude/meta/05-final-spec.md` §3 · §6-1
- **심각도**: 중
- **상태**: 미판정 — 8값 어휘에 "정책 게이트가 없고 배포 도달만 미확인" 자리를 만들지는 `05-final-spec.md` 소유자 판단이다 (회부 13). 이 마일스톤은 그 행들을 `status: null` + `assignment_state: pending` 으로 남겼다

---

## F-22 · manifest `reproduce:` 에 `runtime_schedules` 가 없어 `schedules.yaml` 이 재현 대상 밖이었다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `docs/_meta/extraction-manifest.yaml` `reproduce:` 는 `version: 6` 시점에 7항목을 열거했고(`screen_codes` · `menu_hardcoded` · `runtime_scale` · `runtime_vendors` · `runtime_vendors_boundary` · `runtime_legal` · `runtime_legal_boundary`) 그 안에 `runtime_schedules` 가 없다. 마일스톤 2 는 회부 6 으로, 마일스톤 3 은 회부 9 로 각자 추출기를 등재했는데 **마일스톤 1 만 자기 추출기를 등재하지 않았다.** `reproduce:` 는 glob 이 아니라 열거이므로(회부 1 원문) 미등재 추출기는 규약을 따르는 소비자가 재실행하지 않는다 — 커밋된 `schedules.yaml`(256줄)이 등재 기간 동안 재현 대조 대상이 아니었다. 산출물 자체는 재방출과 바이트 일치한다.
- **재현 절차**:
  ```bash
  git show HEAD:docs/_meta/extraction-manifest.yaml | node -e '
  const t=require("fs").readFileSync(0,"utf8").split("\n");
  const i=t.findIndex(l=>/^reproduce:/.test(l));
  console.log(t.slice(i+1).filter(l=>/^  \w+:/.test(l)).map(l=>l.split(":")[0].trim()).join(" "));'
  # → screen_codes menu_hardcoded runtime_scale runtime_vendors runtime_vendors_boundary
  #   runtime_legal runtime_legal_boundary   (7항목 · runtime_schedules 없음)
  node docs/_meta/verify/extract-runtime-schedules.mjs --emit-yaml | diff - docs/_meta/index/schedules.yaml && echo "산출물은 바이트 일치"
  ```
- **근거 앵커**:
  - `docs/_meta/extraction-manifest.yaml` (`reproduce:` · `version` 이력 주석)
  - `docs/_meta/verify/extract-runtime-schedules.mjs` (`emitYaml`)
  - `docs/_meta/index/schedules.yaml`
- **심각도**: 중
- **상태**: **처리 결정됨** — 해소: 회부 11 수용으로 `version: 6 → 7` 과 함께 `runtime_schedules` · `runtime_policy_candidates` · `runtime_policy_candidates_boundary` 3항목이 등재됐다 (2026-08-10, 마일스톤 4 Task 0)

---

## F-23 · `.gitattributes` 가 `*.jsonl` 을 덮지 않아 JSONL 축 바이트 일치 검사가 성립하지 않았다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `.gitattributes` 는 `docs/_meta/index/*.yaml` 과 `docs/_meta/verify/*.mjs` 두 glob 만 `text eol=lf` 로 고정했고 전역 `* text=auto` 는 걸지 않는다. 이 저장소는 `core.autocrlf=true` 이므로 두 glob 밖의 커밋 산출물은 워킹트리에서 CRLF 로 체크아웃된다 — 그 상태에서 추출기 `--emit-jsonl` 출력(LF)과 커밋본의 바이트 비교는 **첫 실행부터 항상 실패**한다. 같은 부류가 이미 두 번 관측돼 있다: 마일스톤 1 Task 0b 가 YAML 축에서(회부 5), F-12 가 서브모듈 워킹트리에서 겪었다.
- **재현 절차**:
  ```bash
  git show HEAD:.gitattributes | grep -c 'jsonl'          # → 0
  git config core.autocrlf                                 # → true
  git check-attr text eol -- docs/_meta/index/policy-candidates.runtime.jsonl
  # 등재 전: text: unspecified / eol: unspecified   등재 후: text: set / eol: lf
  ```
- **근거 앵커**:
  - `.gitattributes` (glob 목록 · 회부 5 등재 주석)
  - `docs/_meta/index/policy-candidates.runtime.jsonl`
  - `docs/_meta/findings/12-index-runtime.md` F-12
- **심각도**: 중
- **상태**: **처리 결정됨** — 해소: 회부 12 수용으로 `docs/_meta/index/*.jsonl  text eol=lf` 가 추가됐다 (2026-08-10, 마일스톤 4 Task 0b). 전역 `* text=auto` 는 여전히 걸지 않으며 전역 churn 0건을 Validation `#8b` 가 확인했다

---

## F-24 · PRD MVP 산출물 표가 구 기준점 수치를 강등 표기 없이 싣는다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `12-index-runtime` PRD 머리말 §조사 기준점이 *"구 기준점에서 나온 수치는 이 PRD 본문에서 전부 강등 표기돼 있다"* 를 규칙으로 못박는다. 그런데 §Scope MVP 산출물 표의 두 칸이 그 규칙 밖에 있었다 — `schedules.yaml` 행은 `스케줄러 16종`, `vendor-behaviors.yaml` 행은 `1,762회 분기` 를 **`[구-SHA]` 표기 없이** 싣는다. 신 기준점 실측은 `schedulers.inventory = 17` · `vendor.filtered = 1,053회 / 152파일` 이다. 같은 문서의 Evidence 표와 개정 이력(*"Hypothesis 16종 → 17종"*)은 이미 신 기준점으로 갱신돼 있고 **MVP 표만 빠졌다.** MVP 표는 이 PRD 의 계약 표이며 하류(`40-runtime-vendor-legal`)가 참조하는 자리다.
- **재현 절차**:
  ```bash
  git show HEAD:.claude/prds/insuniverse-docs-12-index-runtime.prd.md | sed -n '92,93p'
  # → | `schedules.yaml` | 스케줄러 16종 + …
  #   | `vendor-behaviors.yaml` | … 1,762회 분기의 정책적 요약 …
  node docs/_meta/verify/extract-runtime-scale.mjs --json \
    | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8"));
               console.log(j.schedulers.inventory.length, j.vendor.filtered.occurrences_total, j.vendor.filtered.files_total)'
  # → 17 1053 152
  ```
- **근거 앵커**:
  - `.claude/prds/insuniverse-docs-12-index-runtime.prd.md:92-93` (정정 전 — `git show HEAD:` 로 재현)
  - `.claude/prds/insuniverse-docs-12-index-runtime.prd.md` §조사 기준점 (규칙 원문) · §Evidence 표 (갱신된 자리)
  - `docs/_meta/verify/extract-runtime-scale.mjs` (`--json` → `schedulers.inventory` · `vendor.filtered`)
- **심각도**: 높
- **상태**: **처리 결정됨** — 정정: 두 칸을 신 기준점 값 + 구값 `[구-SHA]` 강등 병기로 고치고 각주를 달았다 (2026-08-10, 마일스톤 5 Task 1). 기계 대조는 Validation `#5`

---

## F-25 · 마일스톤 0 축만 커밋된 재현 오라클이 없다

- **발견 일시**: 2026-08-10
- **관찰 사실**: manifest `reproduce.runtime_scale` 은 `node … extract-runtime-scale.mjs --json  # schedulers.inventory · vendor/legal filtered · set_sha256 **대조**` 로 대조를 지시한다. 그런데 **대조 대상이 manifest 에도 저장소 어디에도 없었다.** 형제 추출기 2종은 둘 다 갖고 있다 — `screen_codes` 는 `route_count: 282` + `set_sha256`, `menu_codes.hardcoded` 는 `code_count: 18` + `set_sha256` 을 manifest 본문에 싣는다. 마일스톤 1~4 는 산출물 파일 전체가 커밋본과 바이트 일치해야 하는 fail-closed 축인데 **마일스톤 0 축만 그렇지 않았다.** `--selftest` 는 fixture 로 규칙만 보고 코퍼스 수치를 보지 않는다. 그 결과 서브모듈 SHA 나 `exclude_glob` 이 움직이면 PRD Evidence 표의 신 기준점 4축(벤더 1,053/152 · 약관 2,082/156 · `inventory` 17 · CI 파일 수 2/4/5)이 조용히 거짓이 되고 **커밋된 검사 중 아무것도 실패하지 않는다.** m0 는 나머지 넷의 분모를 정한 축이다.
- **재현 절차**:
  ```bash
  git show HEAD:docs/_meta/verify/extract-runtime-scale.mjs | grep -cE '1053|2082|EXPECT|expect|golden'   # → 0
  git show HEAD:docs/_meta/extraction-manifest.yaml | grep -A1 'runtime_scale:'                            # 대조 대상 없음
  ls docs/_meta/verify/*.expect.json                                                                       # → policy-candidates 1건뿐
  ```
- **근거 앵커**:
  - `docs/_meta/extraction-manifest.yaml` (`reproduce.runtime_scale` — 대조 지시 · `screen_codes.set_sha256` · `menu_codes.hardcoded.set_sha256` — 형제 2종의 대조 대상)
  - `docs/_meta/verify/extract-runtime-scale.mjs` (정정 전 — `--selftest` 만 존재)
  - `docs/_meta/verify/extract-runtime-policy-candidates.expect.json` (마일스톤 4 선례)
- **심각도**: 높
- **상태**: **처리 결정됨** — 정정: `extract-runtime-scale.expect.json` 을 커밋하고 `--expect` 를 신설했다. 동결 앵커 3자 결박(manifest ↔ 실 서브모듈 `rev-parse HEAD` ↔ 오라클) + 추적 dirty 0건이 값 대조 **전에** 걸린다 (2026-08-10, 마일스톤 5 Task 2). manifest 등재는 **회부 14** 로 올렸고 반영 전까지 마일스톤 5 는 `complete` 가 되지 않는다. **→ 회부 14 반영됨 (2026-08-10)** — `reproduce.runtime_scale` 이 `--expect` 로 교체되고 `runtime_scale_oracle`(`path` · `schema_version: 1` · `sha256`)이 등재됐다. Validation `#4d` 가 돌았고 통과했다(manifest 등재 sha256 == 커밋 blob sha256 == `5dd967ef…` 문자열 일치)

---

## F-26 · 유출 검사 경계가 채널별로 정의된 적이 없다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `--leak-check` 대상은 `vendor-behaviors.yaml` · `legal-surfaces.yaml` · `policy-candidates.runtime.jsonl` **3파일뿐**이고, PRD 는 어느 채널까지가 경계 안인지 적지 않았다. 실측하면 산출물 채널에서는 규약이 지켜지고(allowlist 밖 native 코드 0건) 문서 채널에서는 지켜지지 않는다 — `.claude/plans/insuniverse-docs-12-index-runtime-m2.plan.md` 에 allowlist 밖 native 코드 **26건**, `.claude/notes/insuniverse-docs-12-index-runtime-m2.md` 에 smartro `ResultCode` 2종이 있다. **26건을 실은 그 파일이 *"그 집합은 어떤 출력에도 방출되지 않는다"* 를 설계로 적은 문서다.** `CF-12103` 은 `extract-runtime-vendors.mjs:813` `LEAK_ALLOWLIST` 로 공개가 명시 승인된 값이라 이 관측에서 제외했고, `4. 동의 철회` · `개인정보 제3자 제공` · `수탁업체` · `취급 위탁` 은 §Out of scope 가 금지한 **원문 전재**가 아니라 절 제목·키워드 토큰이라 제외했다.
- **재현 절차**:
  ```bash
  grep -rnoE "\b(INH-0000|APP00005|LBS00009|42110000|80002230|1000200|CF-00000)\b" \
    docs/_meta/index/ docs/_meta/findings/ .claude/notes/ .claude/plans/insuniverse-docs-12-* | \
    awk -F: '{print $1}' | sort | uniq -c
  # → docs/_meta/index/**           0건   (검사되는 채널)
  #   .claude/plans/…-m2.plan.md   26건   (검사 밖 채널)
  node docs/_meta/verify/extract-runtime-vendors.mjs --leak-check docs/_meta/index/vendor-behaviors.yaml   # exit 0
  ```
- **근거 앵커**:
  - `.claude/plans/insuniverse-docs-12-index-runtime-m2.plan.md` (26건 · 같은 파일이 방출 금지를 설계로 적었다)
  - `.claude/notes/insuniverse-docs-12-index-runtime-m2.md:14,23` (smartro `ResultCode` 2종)
  - `docs/_meta/verify/extract-runtime-vendors.mjs:813` (`LEAK_ALLOWLIST` — 승인된 1건)
  - `.claude/prds/insuniverse-docs-12-index-runtime.prd.md` §Out of scope (정정 전 — 채널 경계 문장 없음)
- **심각도**: 중
- **상태**: **처리 결정됨** — 정정: PRD §Out of scope 에 경계를 값으로 적었다(검사 경계 = `docs/_meta/index/**` 4산출물 · 근거 기록 채널 4종은 정의상 검사 밖) (2026-08-10, 마일스톤 5 Task 3). **정의 뒤에 검사를 세우는 일은 Q3 으로 이월**했으며 이 마일스톤이 닫지 않는다

---

## F-27 · manifest `version` 과 `revised` 가 서로를 반증한다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `docs/_meta/extraction-manifest.yaml` 의 `version: 7` 주석은 **2026-08-10** 변경(회부 11 수용)을 적는데 `revised:` 는 `2026-08-07` 이다. `revised` 줄의 주석이 스스로 *"version 과 함께 올린다 — 이 줄이 뒤처지면 두 값이 서로를 반증한다"* 라고 적어 놓았다. 회부 11·12 를 반영하며 생긴 부산물이다.
- **재현 절차**:
  ```bash
  grep -n '^version:\|^revised:' docs/_meta/extraction-manifest.yaml
  # → 10:version: 7   # 6 → 7 (2026-08-10): …
  #   32:revised: 2026-08-07
  ```
- **근거 앵커**:
  - `docs/_meta/extraction-manifest.yaml:10` (`version: 7` + 2026-08-10 변경 주석)
  - `docs/_meta/extraction-manifest.yaml:32` (`revised: 2026-08-07` + 자기 규칙 주석)
- **심각도**: 중
- **상태**: **처리 결정됨** — 해소: 회부 14 수용으로 `revised: 2026-08-07 → 2026-08-10` 이 `version: 7 → 8` 과 함께 올라갔다 (2026-08-10, 마일스톤 5). **종전 서술은 지우지 않는다** — 회부 상신 시점에는 `manifest 는 00-protocol 배타 소유라 이 PRD 가 직접 편집하지 않는다` 가 참이었고 반영 여부는 수신자 판정이었다. 마일스톤 5 가 마지막 마일스톤이라 이월할 후속 Task 0 가 없어(회부 1·6·9·11 은 각각 후속 마일스톤이 반영했다) 사용자 결정으로 같은 브랜치가 반영했다. 기계 대조는 Validation `#7`(`version` 주석 날짜 == `revised`, 실측 둘 다 `2026-08-10`)

---

## F-28 · findings 상태 어휘가 잠정 형식 3값 밖으로 나갔다

- **발견 일시**: 2026-08-10
- **관찰 사실**: PRD §배타 소유 경로가 인용한 잠정 형식은 `상태` 를 3값(`미판정` / `사용자 통보` / `처리 결정됨`)으로 적는다. 이 파일의 실측은 `미판정` 20 · `정정` 1(F-01) · `해소` 2(F-22·F-23) 로 **3값 밖 값이 3건**이었다. 필수 항목 6종은 23/23 전부 존재해 형식 자체는 건전하다.
- **재현 절차**:
  ```bash
  git show HEAD:docs/_meta/findings/12-index-runtime.md \
    | grep -oP '^- \*\*상태\*\*: \*{0,2}\K[^ *—(]+' | sort | uniq -c
  # → 20 미판정 / 1 정정 / 2 해소
  ```
- **근거 앵커**:
  - `docs/_meta/findings/12-index-runtime.md` (정정 전 — F-01 `정정` · F-22·F-23 `해소`)
  - `.claude/prds/insuniverse-docs-12-index-runtime.prd.md` §배타 소유 경로 (3값 인용 원문)
- **심각도**: 낮
- **상태**: **처리 결정됨** — 정정: 3건의 `상태` 값을 `처리 결정됨` 으로 옮기고 **기존 서술은 지우지 않고 뒤에 붙였다**. 판정을 새로 내리지 않았고 어휘만 맞췄다 (2026-08-10, 마일스톤 5 Task 5). 형식 정본(00-protocol 마일스톤 7)이 확정되면 그때 차이만 맞춘다

---

## F-29 · 마일스톤 4 정정이 tamper fixture 수를 든 두 채널에 도달하지 않았다

- **발견 일시**: 2026-08-10
- **관찰 사실**: santa 라운드 2 가 `--selftest-leak` 의 tamper fixture 를 10 → 15 로 늘렸고 코드가 `results.length !== 15` 를 **throw 로 강제**한다. PRD Success Metric 은 그 시점에 15/15 로 정정됐는데 **두 채널 네 자리에 정정이 도달하지 않았다** — `.claude/PRPs/reports/…-m4-report.md:77·124·140` 세 자리와 `docs/_meta/findings/12-index-runtime.md` 부기 4 (*"tamper fixture 10종"*). 둘 다 면제 사유가 없다: m4 리포트는 git 추적 중이면서 `design_doc_hash: []` 로 **어떤 receipt 에도 앵커되지 않고**, findings 는 이 PRD 배타 소유다. **계획 채널에는 이 표기가 없다** — `plan_hash` 앵커로 면제되는 자리가 있을 것으로 예상했으나 실측하면 `.claude/plans/insuniverse-docs-12-index-runtime-m4-r2.plan.md` 의 `10종`·`fixture 10` 은 **0건**이다(마일스톤 5 계획 Task 6 표는 그 자리에 표기가 있다고 적었고 그 서술이 실측과 다르다). santa 라운드 3(C9)이 잡은 것과 같은 결함이 다른 채널에서 재발했다.
- **재현 절차**:
  ```bash
  node docs/_meta/verify/extract-runtime-policy-candidates.mjs --selftest-leak | tail -1
  # → selftest-leak OK — fixture 15종 전부 탐지 …
  git show HEAD:.claude/PRPs/reports/insuniverse-docs-12-index-runtime-m4-report.md | grep -n '10종\|fixture 10'
  # → 77 · 124 · 140
  git show HEAD:docs/_meta/findings/12-index-runtime.md | grep -n 'tamper fixture 10종'   # → 602 (부기 4)
  grep -c '10종\|fixture 10' .claude/plans/insuniverse-docs-12-index-runtime-m4-r2.plan.md   # → 0
  node -e 'console.log(JSON.parse(require("fs").readFileSync(".claude/receipts/mccp-plan-codex/insuniverse-docs-12-index-runtime-m4-r2.json","utf8")).design_doc_hash)'
  # → []   (리포트가 어떤 receipt 에도 앵커되지 않는다)
  ```
- **근거 앵커**:
  - `.claude/PRPs/reports/insuniverse-docs-12-index-runtime-m4-report.md:77,124,140` (정정 전)
  - `docs/_meta/findings/12-index-runtime.md` 부기 4 (정정 전 — 계획이 열거하지 못한 자리)
  - `.claude/plans/insuniverse-docs-12-index-runtime-m4-r2.plan.md` (실측 0건 — 면제 예상이 성립하지 않은 자리)
  - `docs/_meta/verify/extract-runtime-policy-candidates.mjs:893` (`results.length !== 15` 강제)
  - `.claude/receipts/mccp-plan-codex/insuniverse-docs-12-index-runtime-m4-r2.json` (`design_doc_hash: []`)
- **심각도**: 중
- **상태**: **처리 결정됨** — 정정: 리포트 3곳과 부기 4 를 15종으로 고치고 리포트에 §정정 1 블록을 남겼다. `plan_hash` 앵커된 m4-r2 계획은 **손대지 않았다** (2026-08-10, 마일스톤 5 Task 6). 전수 대조는 Validation `#10` — 넷째 채널은 계획의 손 열거가 아니라 그 전수 grep 이 찾았다

---

## F-30 · 검사기 주석이 같은 파일의 강제값과 어긋난다

- **발견 일시**: 2026-08-10
- **관찰 사실**: `extract-runtime-policy-candidates.mjs:835` 이 *"fixture **10종** 전부 적중 목록 반환으로 탐지한다(실측: 반환 건수 **a~j** …)"*, `:838` 이 *"이 스위트가 **'10종 전부 탐지'** 를 출력한다"* 라고 적는다. 같은 파일 `:893` 은 `15` 를 throw 로 강제하고 `:898` 은 `fixture 15종` 을 출력하며 실제 fixture 는 `a`~`o` 다. santa 라운드 2 가 fixture 를 늘리면서 라운드 1 이 남긴 주석을 갱신하지 않았다. 이 축이 F-18 에서 `comment_matches_code: false` 로 기계 확정한 것과 같은 부류가 **자기 검사기 안에** 있다. 대조군으로 `extract-runtime-legal.mjs` 의 `10종` 표기(`:93` · `:2016` · `:2103`)는 **정확하다** — legal 축 fixture 는 실제로 10종이다.
- **재현 절차**:
  ```bash
  git show HEAD:docs/_meta/verify/extract-runtime-policy-candidates.mjs | sed -n '835p;838p;893p;898p'
  node docs/_meta/verify/extract-runtime-policy-candidates.mjs --selftest-leak | grep -c '^  ok  fixture'   # → 15
  node docs/_meta/verify/extract-runtime-legal.mjs --selftest-leak | tail -1
  # → selftest-leak OK — fixture 10종 전부 탐지   (legal 축은 표기가 맞다)
  ```
- **근거 앵커**:
  - `docs/_meta/verify/extract-runtime-policy-candidates.mjs:835,838` (정정 전 주석)
  - `docs/_meta/verify/extract-runtime-policy-candidates.mjs:893,898` (`15` 강제 · 출력)
  - `docs/_meta/verify/extract-runtime-legal.mjs:93,2016,2103` (대조군 — 정확한 `10종`)
- **심각도**: 낮
- **상태**: **처리 결정됨** — 정정: 주석 2줄을 `15종` · `a~o` 로 고쳤다. `extract-runtime-legal.mjs` 는 값이 정확하므로 **건드리지 않았다** (2026-08-10, 마일스톤 5 Task 6)

---

## 부기 1 · 마일스톤 1 (F-01 ~ F-05) 이 측정한 값의 정의 소재

> **범위 주기**: 이 부기는 **F-01 ~ F-05** 에만 걸린다. 파일 끝에 있어 "위 5건" 이 F-08 ~ F-12 로
> 읽히던 것을 바로잡는다 — 그 5건은 규모 축이 아니라 벤더 축이고, 아래 부기 2 가 담당한다.

F-01 ~ F-05 는 전부 `docs/_meta/verify/extract-runtime-scale.mjs` 의 `--json` 출력으로 재현된다. 그 출력의 `definition` 블록에 스캔 대상 저장소 · 스캔 단위 · 확장자 allowlist · 키워드 집합 · 매칭 규칙 · 적용된 `exclude_glob` 원본과 sha256 이 전부 들어 있다. **구 기준점 수치가 정의를 남기지 않아 복원 불가했던 것이 이 마일스톤의 출발점이므로, 신값은 정의를 값과 같은 자리에 둔다.**

---

## 부기 2 · 마일스톤 2 (F-08 ~ F-12) 이 측정한 값의 재현 소재

벤더 축 5건은 재현 소재가 **한 곳이 아니다.** 어느 수치가 기계로 강제되고 어느 수치가 관찰치인지
여기서 갈라 둔다 — 이 구분이 없으면 "추출기가 보증한다" 가 실제보다 넓게 읽힌다.

| 발견 | 재현 소재 | 기계 강제 |
|---|---|---|
| F-08 `codef` 만 `NO_DATA` 0건 | `extract-runtime-vendors.mjs --emit-yaml` → `vendors.*.outcomes.no_data.code_count` | **예.** 커밋본 바이트 일치 + `#2d` golden 대조(0·1·1·6·1) |
| F-09 `CF-12103` 폴백 트리거가 맵에 미수록 | 같은 추출기 → `fallback_policy.trigger_codes` (트리거 쪽) · 각 항목 `재현 절차` 의 `grep` (미수록 쪽) | **부분.** 트리거 코드 자체는 `parseDispatcher` 가 fail-closed 로 뽑고 `--leak-check` 의 allowlist 계약(`LEAK_ALLOWLIST`)이 값까지 못박는다. "5블록 어디에도 없다" 는 **부재 관찰**이라 추출기 출력에 나타나지 않는다 |
| F-10 택소노미 설계 출처 문서 부재 | 항목 안 `재현 절차` 스크립트 | **아니오.** 저장소 전역 부재 관찰이다. 문서가 새로 생기면 추출기 출력·selftest·leak-check 어디도 바뀌지 않는다 |
| F-11 `code_namespaces.vendor_codes` 22/242 | 항목 안 `재현 절차` 스크립트 | **아니오.** 추출기는 manifest 정규식을 평가하지 않는다. 회부 7 이 판정을 들고 있다 (항목 안에 같은 주기 있음) |
| F-12 CRLF 헤더 0/5 · CRLF 줄 184 | 항목 안 `재현 절차` 스크립트 | **아니오.** 워킹트리 EOL 상태 관찰이라 체크아웃 설정이 바뀌면 값이 달라진다. 다만 **결론**(정규화가 load-bearing) 은 강제된다 — `readSource` 가 유일한 입력 진입점에서 정규화하고, 헤더 무매칭은 `cutBlock` 이 `throw` 로 잡는다 |

정리: **F-08 만 산출물로 재현된다.** 나머지는 항목마다 재현 스크립트를 함께 커밋했고, 그 스크립트가
유일한 재현 경로다. 소스가 바뀌면 이 수치들은 자동으로 갱신되지 않으며 실패하는 게이트도 없다 —
관찰 시점(2026-08-07 · manifest `repos[].head` 동결본) 기준의 사실로 읽어야 한다.

---

## 부기 3 · 마일스톤 3 (F-13 ~ F-20) 이 측정한 값의 재현 소재

마일스톤 3 의 8건은 재현 소재가 **한 곳에 몰려 있다** — 각 항목의 `재현 절차` 가 전부
`extract-runtime-legal.mjs --json` 한 경로를 부르며, 그 출력은 커밋된
`docs/_meta/index/legal-surfaces.yaml` 과 **바이트 일치**가 강제된다(Validation `#5`).
부기 2 가 벤더 축에서 갈라 둔 "기계 강제 / 관찰치" 구분을 여기서도 항목별로 적어 둔다.

| 발견 | 재현 소재 | 기계 강제 |
|---|---|---|
| F-13 버전 컬럼 부재 | `axes.terms_corpus.versioning` | **예.** 컬럼 집합을 파싱해 도출하며 블록 줄 전수 소비(`#2c`)가 부분 파싱을 막는다 |
| F-14 동의 시각 경로 1건 | `axes.consent_record` | **예.** 다만 값이 보증하는 것은 **관측치**이며 `#2` 는 개수를 못박지 않는다 — 검사기가 전수 주장의 보증인이 되지 않게 한 것이 이 마일스톤의 설계다 |
| F-15 `철회` 다의어 | `keyword_scale.ambiguity.철회` | **예.** 분류 규칙 원본이 `rule_sha256` 으로 함께 실리고, 세 통의 합이 `extract-runtime-scale` 의 `per_keyword.철회` 와 대조된다 (추출기 내부 fail-closed) |
| F-16 수탁업체 코드 0 | `axes.absent.수탁` | **예.** `code_hits + terms_text_hits` 와 파일 수가 `extract-runtime-scale` 값과 대조된다(`#3`) |
| F-17 마스킹 병존 10곳 | `axes.masking` | **부분.** 파일 집합과 신호는 도출되지만 9곳의 **규칙**은 미조사이며 `rules_unparsed: 9` 가 그 사실을 값으로 싣는다 |
| F-18 주석 ≠ 코드 | `axes.masking.canonical.comment_matches_code` | **예.** 주석 예시의 하이픈 유무와 코드 표현식의 하이픈 유무를 기계로 대조한다 |
| F-19 `publish/` 약관 본문 | `axes.terms_corpus.sources` (`kind: static-html`) | **예.** 소재 목록이 손으로 적히지 않고 도출되며, `#2` 가 부류별 항목 수를 못박는다 |
| F-20 손목록 < 도출 | `axes.terms_corpus.derivation` · `corpus_files` | **예.** 형태소 집합이 `markers_sha256` 으로 동결되고, 분류 규칙이 못 따라가는 새 소재는 `corpusKind` 가 `throw` 로 세운다 |

한계 하나를 함께 적는다 — **도출 형태소 8종은 사람이 고른 집합**이다. 형태소가 못 보는 약관 본문
소재는 이 층 어디에도 나타나지 않으며, F-20 이 기록한 두 번의 누락은 그 성질의 실례다.
`file_set_sha256` 이 축 A 소재와 `--leak-check` 코퍼스를 결박하므로 **두 목록이 갈라지는 사고**는
기계로 막히지만, **둘 다 같이 못 본 소재**는 막히지 않는다.

---

## 부기 4 · 마일스톤 4 (F-21 ~ F-23) 이 측정한 값의 재현 소재

마일스톤 4 는 **소스 저장소를 다시 스캔하지 않는다.** 입력이 세 인덱스이고 그 세 벌이 이미
결정적이므로, 이 축의 재현 소재는 합성 단계 하나로 좁혀진다 —
`extract-runtime-policy-candidates.mjs` 의 `bindSources()` 가 세 추출기를 부르고 각 재방출을
커밋본과 바이트 비교해 불일치면 그 자리에서 멈춘다. **입력이 흔들리면 값이 나오기 전에 깨진다**는
뜻이며, 부기 2·3 이 축마다 따로 세워야 했던 "기계 강제 / 관찰치" 구분이 여기서는 대부분 전자로
몰린다.

| 발견 | 재현 소재 | 기계 강제 |
|---|---|---|
| F-21 §3 8값 중 2값만 성립 | `--json` → `meta.status_distribution` · `meta.assignment` | **예.** 규칙표(`RULE_TABLE`)에서 파생되고 그 상수의 직렬화가 `meta.rule_table_sha256` 으로 실린다. 규칙 미일치는 `throw` 라 조용한 기본값이 없고, `FORBIDDEN_STATUS` 6값은 `makeRow` 가 부여 시도 자체를 막는다. 전 행 귀속은 손으로 쓴 oracle(`--expect`, 49행)이 정확 일치로 본다 |
| F-22 `runtime_schedules` 미등재 | `git show HEAD:docs/_meta/extraction-manifest.yaml` (등재 전 상태) | **아니오.** 등재 여부는 manifest 파일 상태 관찰이다. 다만 **해소 결과**는 강제된다 — `#7` 이 3항목 등재와 `version: 7` 을 exit≠0 으로 못박는다 |
| F-23 `*.jsonl` EOL 미고정 | `git show HEAD:.gitattributes` · `git check-attr` | **아니오.** 워킹트리 체크아웃 속성 관찰이다. 해소 결과는 `#8` 이 `check-attr` **출력 파싱**으로, 부작용 부재는 `#8b` 가 전역 churn 0건으로 확인한다 — 선언이 아니라 적용을 본다 |

이 축의 한계 두 가지를 함께 적는다.

1. **oracle 은 사람이 쓴 것이다.** `extract-runtime-policy-candidates.expect.json` 49행은 세
   인덱스 YAML 을 직접 읽어 손으로 채웠다. 사람의 오독이 규칙표와 fixture 양쪽에 같이 들어가면
   두 층이 같은 착각을 공유한다. 완전 해소가 아니며, 정확 일치만 허용해 **규칙을 고치면 fixture
   도 손으로 고치게** 만들고 그 편집 자체를 검토 지점으로 남긴 것이 이 층의 전부다.
2. **유출 검사는 두 코퍼스가 보는 것만 본다.** 벤더 native 코드 245건과 약관 코퍼스 14파일은
   마일스톤 2·3 의 실집합을 그대로 재사용하므로 사본 드리프트는 없다. 그러나 **그 두 집합이
   애초에 못 보는 값**(예: 코퍼스 밖 운영 데이터)은 n-gram 층이 구조적으로 못 본다 — 그래서
   PII 구조 규칙 3층을 함께 걸었고, 세 층이 실제로 잡는지는 tamper fixture 15종(`--selftest-leak`)이
   전수 탐지로 확인한다. `allowlist` 는 0건이라 면제 통로 자체가 없다.
