# Implementation Report: 사실 인덱스 · 코드 축 — 마일스톤 6a (정책 후보 통합)

**계획**: `.claude/plans/insuniverse-docs-11-index-code-m6-r12.plan.md` (봉인 — 이 사이클이 건드리지 않았다)
**브랜치**: `feat/index-code`
**게이트 receipt**: `.claude/receipts/mccp-implement-codex/insuniverse-docs-11-index-code-m6-r12.json`
**Implement 리뷰 기록**: `.claude/notes/insuniverse-docs-11-index-code-m6-r12.md`

## Summary

코드 축 인덱스 13종(8,143항목)을 **집행 지점 단위**로 통합해 두 산출물을 만들었다. 소스 저장소를
스캔하지 않는다 — 입력은 전부 커밋된 `docs/_meta/index/**` 이고 이 마일스톤은 그 위의 순수 함수다.

- `policy-candidates.code.jsonl` — 확정 후보 **1,991**
- `candidates-unqualified.code.jsonl` — 잠정 후보 **3,208** (이름이 부모 PRD 의 `policy-candidates.*`
  `cat` glob **밖**인 것이 요구사항이다)

## Assessment vs Reality

| Metric | Predicted (Plan) | Actual |
|---|---|---|
| Complexity | Medium | Medium |
| Files Changed | 6 (CREATE 4 · UPDATE 2) | 6 (+ 게이트 산출물 2: notes · receipt) |
| 확정 후보 | 1,991 | **1,991** |
| 잠정 후보 | 3,208 (`local_validator` 2,463 · `global_whitelist_only` 745) | **3,208** (2,463 · 745) |
| 축별 확정 | access 356 · contract 296 · value 74 · data 1,265 | **동일** |
| status 확정 / 미확정 | 11 / 5,188 (0.21%) | **11 / 5,188** |
| 사유 분포 | `release_axis_absent` 단독 63 · 두 사유 5,125 | **63 / 5,125** |
| 후보 밖 | 2,588 | **2,588** |

계획의 예측 수치가 **한 건도 어긋나지 않았다.** 다만 그 수치는 어디에도 하드코딩하지 않았다
(Plan-Codex R8 F0) — `entry_count` · `status_determined` · `status_undetermined` ·
`excluded_axes[].count` 는 전부 생성기가 만든 행에서 세고 판정기가 독립 재열거로 다시 센다.

## Tasks Completed

| # | Task | Status | Notes |
|---|---|---|---|
| 0 | 착수 전제 확인 | 완료 | PRD 개정 4건 전부 성립. `grep -c policy-candidates` = 9 (게이트 8 이상) · 소유 목록 17개에 두 `.mjs` 포함 · 배타 소유 경로에 두 산출물 · 6a `in-progress` / 6b `pending` + 선행조건 `10-index-git` |
| 1 | `verify-policy-candidates.mjs` — 판정기 먼저 | 완료 | 1,606행. selftest **59 cases PASS**. (a)~(l) 각각에 음성 픽스처 |
| 2 | `build-policy-candidates.mjs` — 생성기 | 완료 | 801행. selftest **32 cases PASS**. 큐레이션 픽스처 6종 포함 |
| 3 | 산출 + 재현성 | 완료 | 두 파일 생성 · 판정기 exit 0 · 2회 생성 `diff` 양쪽 빈 출력 |
| 4 | findings + 이월 | 완료 | §1 발견 **67~72** 6건 · §2 규약 개정 요청 6건 (a)~(f) · §4 이월 **11·12** |
| 5 | 마일스톤 6a 종료 전이 | 완료 | 6a `complete` + Outcome 실산출 기재 · **6b 는 `pending` 그대로** |

## Validation Results

| Level | Status | Notes |
|---|---|---|
| Static Analysis | N/A | 이 저장소에 lint/type-check/build 파이프라인이 없다 (`package.json`·`tsconfig.json`·eslint 설정 부재). `.mjs` 는 `node` 직접 실행이며 기존 17개 스크립트도 같은 규약이다 |
| Unit Tests (selftest) | PASS | 신규 2종 91 cases + 기존 15종 전부 통과 (회귀 0) |
| Build | N/A | 동상 |
| Integration (라이브 완주) | PASS | 생성 → 판정 → 재현성 → 상류 판정기 6종, 전부 exit 0 |
| Edge Cases | PASS | Acceptance 기계 검사 29항목 ALL PASS |

### selftest 상세

| 파일 | cases |
|---|---|
| `verify-policy-candidates.mjs` | **59 PASS** |
| `build-policy-candidates.mjs` | **32 PASS** |
| `jsonl-doc.mjs` | 14 PASS |
| 기존 축 15종 (`verify-*` 6 · `build-*` 6 · `tests-vocab` · `tests-hop` · `check-tests-crosscheck`) | 전부 exit 0 — **회귀 0건** |

### 라이브 1회 완주

```
build-policy-candidates.mjs --index-dir docs/_meta/index
  → policy-candidates.code.jsonl: 1991 rows · candidates-unqualified.code.jsonl: 3208 rows
verify-policy-candidates.mjs --file … --provisional-file … --index-dir … --repos-root ../..
  → 확정 1991 (access 356 · contract 296 · value 74 · data 1265)
    잠정 3208 (local_validator 2463 · global_whitelist_only 745)
    status 확정 11 · 미확정 5188 · 후보 밖 2588        exit 0
```

`set_sha256` — 확정 `cba21ce179f38a48…` · 잠정 `a5787b2e67565ea6…`

### 재현성 (UI5)

두 파일 모두 `--out-dir` 로 2회 생성해 `diff` **빈 출력**. 임시 산출이 인덱스 디렉터리의 실파일과도
바이트 동일하다 — 기존 큐레이션은 언제나 `--index-dir` 의 실파일에서 읽으므로 `--out-dir` 이
그 결과를 바꾸지 않는다.

### 상류 판정기 6종

`verify-tests` · `verify-schema-constraints` · `verify-api-contracts` · `verify-policy-anchors` ·
`verify-policy-values` · `verify-code-index` — **전부 exit 0**. 입력 digest 대조가 물고 있는 대상이
여전히 유효하다.

### 흡수가 신설한 검사 (R2 이후)

| 검사 | 위치 |
|---|---|
| access 조인 1:1 (중복·고아·축 키) | 판정기 `independentRebuild` · 생성기 `buildRows` · `verify()` 행 루프 |
| 전역 whitelist 집행 사실 | `assertGlobalWhitelistEnforced`(export) + `independentRebuild` 독립 구현 |
| YAML 항목 배열 키 파일명 결속 · 반대 키 거부 | `readYamlIndex(path, rel)` |
| 상류 `meta.entry_count` 헤더 교차 검사 | `readIndexArtifact` |
| `gap` 재렌더 정확 비교 · 두 파일 동일성 | `gapFacts`/`renderGap` + `verify()` |
| 큐레이션 필드 ⟂ 생성 필드 (모듈 적재 시 강제) | `CURATED_FIELDS` · `PRESERVE` 상호 검사 |

### Design Grounding

**N/A (no design trigger)** — `impeccable-detect --mode implement` 가 `design_signal=false`
(`reason=no-signal`) 를 냈다. 이 마일스톤은 렌더 표면(`.tsx`/`.jsx`/`.vue`/`.svelte`/`.astro`/
`.css`/`.scss`/`.html`/`.claude/cache/*`)을 하나도 건드리지 않는다 — Node `.mjs` 두 개와
JSONL·마크다운뿐이다. 따라서 Phase 2.5.5c capture 미발동 · Phase 3.6 DESIGN FINISH 게이트 미충족 ·
Phase 3.7 DESIGN GROUNDING VERIFY 완전 no-op. receipt 에 `impeccable_silent_skip=true`
(`silent_skip_reason: no-signal`) 로 정직하게 실었다.

## Files Changed

| File | Action | Lines |
|---|---|---|
| `docs/_meta/verify/verify-policy-candidates.mjs` | CREATED | +1,606 |
| `docs/_meta/verify/build-policy-candidates.mjs` | CREATED | +801 |
| `docs/_meta/index/policy-candidates.code.jsonl` | CREATED | +1,992 (meta 1 + 항목 1,991) |
| `docs/_meta/index/candidates-unqualified.code.jsonl` | CREATED | +3,209 (meta 1 + 항목 3,208) |
| `docs/_meta/findings/11-index-code.md` | UPDATED | +37 |
| `.claude/prds/insuniverse-docs-11-index-code.prd.md` | UPDATED | +1 / -1 (6a 행) |
| `.claude/notes/insuniverse-docs-11-index-code-m6-r12.md` | CREATED (게이트 기록) | — |
| `.claude/receipts/mccp-implement-codex/insuniverse-docs-11-index-code-m6-r12.json` | CREATED (게이트) | — |

계획의 `Files to Change` 6종과 정확히 일치한다. `plan-conflict-detector` `conflict: false`.

## Deviations from Plan

계획이 정하지 않은 자리를 구현이 정한 것 4건이다. 어느 것도 계획의 결정을 뒤집지 않는다.

1. **`status_basis` 의 구체 형상.** 계획은 "적용 필터·불가 필터·사유" 만 적었다. 4필드 객체로
   확정했다 — `determined_from` · `filters_decided` · `filters_undecided_in_code_axis` ·
   `filters_owned_by_other_axes`. 셋째와 넷째를 **가른 이유**: "코드 축이 오늘 못 정했다"(필터 1 —
   env 게이트가 조인되지 않은 행)와 "원리적으로 다른 축 소관이다"(필터 3·4·5)를 한 배열에 섞으면
   6b 가 무엇을 채울 수 있는지 구별할 수 없다.

2. **env 게이트의 앵커 승계원.** `env-gates.yaml` 항목에는 `defined_in` 이 없다(키: `read_at`).
   `read_at` 을 `defined_in` 으로 승계하고, 비어 있으면 중단한다. 지어낸 값이 아니라 그 축에서
   앵커에 해당하는 유일한 필드다.

3. **인구조사를 두 모집단으로 싣는다.** 계획 실측표의 `class_kind`(dto 1,677 / entity 786)와
   이름 일치(499)는 **`local_validator` 보유분 기준**이고, R7 F0 이 validator 없는 745행을 잠정으로
   옮긴 뒤로는 전수 기준(dto 2,243 / entity 965 · 619)이 따로 있다. `gap` 에 **둘 다** 적었다 —
   하나만 실으면 어느 기준인지가 문면 밖 지식이 되고, 그것이 R8 F0 이 잡은 결함 부류다.

4. **`gap` 의 모집단 이름표를 데이터에서 뽑는다.** 계획 Task 3 (1)·(3)은 참조 계수 보유분을
   "`constants.yaml` 유래 63행" 이라고 적는다. 그 **개수**는 사유 배열 길이에서 나오는데 **이름표**만
   손으로 적으면, 상류가 다른 축에 `active_reference_count` 를 더한 날 숫자는 맞고 문장은 틀린 상태가
   된다 — R8 F0 이 잡은 결함과 같은 부류다. 해당 행들의 `sources[]` namespace 집합을 실제로 뽑아
   싣는다. 오늘 출력은 `policy_constants` 이며 같은 63행을 가리킨다(파일명 대신 namespace 표기 —
   조인 어휘와 일치한다).

## Issues Encountered

### Implement-Codex R2 — 산출 코드 대상 재리뷰에서 6건 (5 흡수 · 1 이월)

Phase 2.5 게이트는 명세상 Phase 3 EXECUTE **이전**에 도는데 래퍼는 브랜치 diff 를 대상으로 삼는다.
그래서 R1 은 "구현이 diff 에 없다" 만 냈고 2.5.2 의 결정 7건 어느 것도 실제로 검증되지 않았다.
그 상태를 수렴으로 읽지 않고, 구현이 존재하는 시점에 파일 경로를 명시해 **재실행**했다.
지적 6건이 나왔고 **인용 지점 전부 실코드와 대조해 전부 사실이었다.**

셋(F1·F2·F3)은 같은 부류다 — **오늘 데이터에서만 우연히 참인 가정을 게이트가 강제하지 않는다.**
실측으로 오늘 값은 전부 성립하지만(access 조인 356↔356 1:1 · `whitelist`·`forbidNonWhitelisted`
둘 다 `true` · 입력 13종 중 `role_matrix` 를 가진 것은 `policy-anchors.yaml` 하나), 그 성립을
검사하는 코드가 없었다. 이 마일스톤이 만드는 것이 fail-closed 게이트이므로 그 자체가 결함이다.

| # | 지적 | 판정 | 닫은 방법 |
|---|---|---|---|
| F1 | access 조인이 1:1 을 강제하지 않는다 (앵커 중복 시 마지막이 조용히 이김 · 고아 앵커 통과 · 두 원 id 의 축 키 동일성 미검사) | ACCEPT_NOW | 판정기·생성기 양쪽에 중복/고아/축 키 불일치 중단. 문서 수준에서도 access 행의 `sources[]` 2건 · 축 키 1종 검사. 음성 픽스처 3종 |
| F2 | 전역 whitelist 가 꺼져 있어도 `global_whitelist_only` 를 찍는다 | ACCEPT_NOW | 두 플래그가 참이 아니면 **값을 고르지 않고 중단**(UI2). 독립 재구성은 같은 검사를 자기 손으로 구현 — export 된 것을 부르면 셋째 축이 첫째 축이 된다. 음성 픽스처 2종 |
| F3 | 공용 YAML 리더가 항목 배열 키를 **파일 내용**으로 골라 모집단을 통째로 버릴 수 있다 | ACCEPT_NOW | 키를 **파일명으로 결속** + 반대 키 존재 거부 + 상류 `meta.entry_count` 헤더 교차 검사(실측 13종 전부 보유). 음성 픽스처 3종 |
| F4 | `gap` 문면이 검증되지 않는다 (5,188 → 5 손편집도 통과) | ACCEPT_NOW | `gapFacts`/`renderGap` 을 판정기가 소유·export, 생성기가 import. 판정기는 **자기 독립 재구성**으로 facts 를 만들어 렌더 결과를 정확 비교하고 두 파일 `gap` 동일성도 본다. 산문 파서는 만들지 않았다. 음성 픽스처 2종 |
| F5 | 큐레이션 보존 기전이 **구조적으로 작동할 수 없다** | ACCEPT_NOW | 보존 도메인이 `CANONICAL_PAYLOAD_FIELDS` 였는데 그 필드는 판정기가 재계산하므로 사람 값이 언제나 거부된다. 생성 필드와 **서로소인** `CURATED_FIELDS`(6a 빈 목록, 모듈 적재 시 강제) 신설 + **build → serialize → verify 완주** 픽스처 |
| F6 | 산출물 두 개가 원자적으로 커밋되지 않는다 | DEFER_TO_BACKLOG | `writeDocs` 는 마일스톤 1 소유 공용 자산(다른 축 6종 사용)이고 그 잔여를 자기 주석에 이미 명시한다. 처방은 계약 변경이라 「계약 변경 규칙」이 별도 correction 마일스톤으로 보낸다. **검출은 실증했다** — 한쪽만 새 것인 쌍을 만드니 `meta.sibling.set_sha256` 불일치로 거부. 못 잡는 잔여(id 집합 동일·payload 만 다름)를 backlog 에 그대로 적었다 |

흡수 후: 판정기 selftest **45 → 59 cases**, 생성기 **29 → 32 cases**, 기존 축 15종 회귀 **0건**,
실산출 수치 **전부 불변**(확정 1,991 · 잠정 3,208 · status 11/5,188 · 후보 밖 2,588), 재현성 유지,
Acceptance 29항목 ALL PASS 재확인. 흡수 5건 각각에 **그 수정이 없으면 실패하는** 음성 픽스처를 뒀다.

### R3 — 실행했으나 결과 없음

흡수가 구멍을 닫았는지와 지적 실질 곡선을 보려고 R3 을 띄웠으나 완료 전에 중단됐다(출력 0바이트).
**흡수 5건에 대한 독립 재리뷰는 이 사이클에 없다.** 가진 증거는 반사실 음성 픽스처(그 수정이 없으면
실패하는 검사)와 전 회귀 통과뿐이며, 그것은 "의도한 구멍을 막았다" 를 보이지만 "다른 구멍이 없다" 는
보이지 못한다. 그래서 receipt 의 `codex_verdict` 를 `divergent` 로 **남긴다** — 흡수했다는 사실만으로
`converged` 로 올리면 receipt 가 하지 않은 일을 했다고 적는 것이다. 산출 diff 의 적대적 리뷰는
`/mccp:pr` 의 PR-Codex 소유다.
### 그 밖

계획의 실측 전제가 전부 재현됐다 — 착수 전 probe 로 앵커 5,199건 전수 `checkAnchor`
통과 · id 충돌 0 · access 조인 356↔356 정확 일치 · `policy_anchors` 와 `api_contracts` 의 축 키
불일치 0 을 먼저 확인하고 코드를 썼다.

selftest 단계에서 잡힌 결함 2건은 전부 **검사 자신의 결함**이었고 같은 사이클에서 닫았다:
(i) 픽스처 기대치가 `data` 축 2행을 빠뜨렸다, (ii) `status_undetermined_reasons` 변조 픽스처가
이미 `[]` 인 행(status 확정 행)을 골라 변조가 무동작이었다 — 검사가 조용히 통과하던 자리다.
변조 대상 행 선택을 필드마다 명시하도록 고쳤다.

## Tests Written

| Test | Cases | Coverage |
|---|---|---|
| `verify-policy-candidates.mjs --selftest` | 59 | 판정 규칙 (a)~(l) 각각의 음성 픽스처 · `CANONICAL_PAYLOAD_FIELDS` **에서 파생된** 필드별 변조 픽스처 11종 · id 안정성 회귀(access↔contract 이동) · 3자 대조 · 입력 digest · 앵커 승계 · 조인 대칭성 · 제외 digest · sibling 양방향 · 침묵 금지 2종 |
| `build-policy-candidates.mjs --selftest` | 32 | 축별 계수 · 산출물 분리 · `evidence_anchors` 채널 분리 · 후보별 사유 도출 · 2회 생성 바이트 동일 · **큐레이션 픽스처 6종**(소실 중단+전수 열거 / 값 없는 소실 진행 / 목록 밖 필드 중단 / 같은 파일 복사 / established→provisional 복사 / 반대 방향 복사, `false`·`null` 포함) · 기존 두 파일 id 충돌 중단 · 상류 계약 위반 3종 중단 |

`PRESERVE` 가 6a 시점 **빈 목록**이라 병합·소실 기전을 실운영 경로로는 돌려 볼 수 없다. 그래서
`build()` 가 allowlist 를 인자로 받고 selftest 가 비지 않은 목록으로 **같은 코드 경로**를 검사한다 —
실운영 호출은 언제나 `PRESERVE` 를 그대로 넘긴다. "필드가 생기는 순간 자동으로 유효해진다" 를
검증 없는 약속으로 두지 않기 위해서다.

## Acceptance (기계 검사 29항목 ALL PASS)

- [x] All tasks complete
- [x] Validation passes
- [x] Patterns mirrored, not reinvented — `jsonl-doc.mjs`(round-trip·큐레이션 읽기) · `writeDocs`(전부-아니면-전무) · `checkAnchor`/`sha256OfIds`/`parseYamlSubset`/`makeSelftestDir`(`verify-code-index.mjs`) · 3자 대조와 침묵 금지 규율(`verify-schema-constraints.mjs`)
- [x] 게이트/경로를 실제로 1회 완주하고 산출물을 확인
- [x] 두 파일 존재 · 첫 줄 `kind: meta` · `inputs[]` 가 입력 13종 sha256 · 항목 수 == `entry_count` == `observed` == 판정기 독립 재열거 · `set_sha256` 대응
- [x] 두 파일 id 집합 서로소 · `eligibility` 각각 · `sibling` 양방향
- [x] 확정 1,991 · 잠정 3,208 · `provisional_reason` 2,463 / 745
- [x] 잠정 전 행이 `relation_unresolved` · `provisional_reason` · `parent_class_kind` · `parent_name_in_param_bindings` 를 함께 단다
- [x] `CANONICAL_PAYLOAD_FIELDS` 단일 정본 export · 목록 파생 변조 픽스처 전부 거부 · **개수를 문서 어디에도 적지 않았다**
- [x] `undetermined` 가 산출물 어디에도 없다 (어휘 3낱말)
- [x] 후보 id 전부 `policy_candidates:` · access↔contract 이동에도 id 동일
- [x] access 356행이 policy-anchors id 와 endpoint id 둘 다 · 역방향 집합 정확 일치
- [x] `status` 전 행 존재 · constants 유래 **63행이 `["release_axis_absent"]` 하나** · 나머지 5,125행 두 원소 · `meta.status_determined`/`status_undetermined` = 11/5,188
- [x] `PRESERVE` 닫힌 목록 · 6a 시점 빈 목록 · 그 사실이 코드 주석에 있다
- [x] 큐레이션 픽스처 6종 통과
- [x] 잠정 이름이 `policy-candidates.*` glob 밖
- [x] `sources[]` 에 `파일:행` 없음 · 전역 파이프 근거는 `evidence_anchors[]` (745행 × 앵커 5)
- [x] `meta` 전 계수가 실계수 파생 · 판정기 재열거와 일치
- [x] 후보 밖 2,588 이 축·개수·사유·`ids_sha256`
- [x] 두 파일 2회 생성 `diff` 빈 출력
- [x] findings 발견 6건 + 이월 2건
- [x] 6a `complete` · **6b `pending`**

## Next Steps

- [ ] `/mccp:pr` 로 PR 생성 — **주의**: implement receipt 가 `security_skipped=true` (Agent(Task) 도구 금지 세션)이고 `codex_verdict=divergent` 다. 둘 다 정직한 기록이며 PR 게이트에서 판단이 필요하다
- [ ] 마일스톤 **6b** — 선행조건 `10-index-git`. `status_basis.filters_owned_by_other_axes` 가 필터별 소관을 담고 있어 재생성만으로 채워진다
- [ ] `00-protocol` 규약 개정 요청 6건 (findings §2 마일스톤 6a 요청) 회신 대기
- [ ] PRD 전 마일스톤 완료 후 `/mccp:archive-complete` (이 사이클은 계획을 옮기지 않았다)
