# Notes — insuniverse-docs-11-index-code-m6b

**Plan**: `.claude/plans/insuniverse-docs-11-index-code-m6b.plan.md` (봉인 `sha256:f8c81b85…`)
**Decision slug**: `insuniverse-docs-11-index-code-m6b`

> 구현 게이트 기록은 이 파일에 쓴다. 계획 본문에 쓰면 `plan_hash` 가 바뀌어
> `mccp-plan-codex` receipt 가 stale 로 떨어진다 (봉인 규약 R3 F2).

## Codex Implementation Review

- 호출: `node C:/Users/madsc/.claude/plugins/cache/mccp/mccp/1.27.2/scripts/lib/codex-invoke.js adversarial-review` (fail-closed Bash wrapper, v0.2.2)
- 라운드 수: 1 (착수 전 게이트 라운드)
- 합치 결론: 착수 전 워킹트리에는 6b 구현이 없다. 리뷰어가 그 사실 하나를 HIGH 로 냈고,
  해소책은 Phase 3 실행 그 자체다. 구현 후 파일 경로를 명시해 재실행하고 결과를 아래
  `### 착수 후 라운드` 에 적는다.
- YAGNI Triage:

  | Finding | Severity | Verdict | Why |
  |---|---|---|---|
  | F1 네 대상 파일이 HEAD 와 동일 — 6b 구현이 존재하지 않는다 | HIGH | ACCEPT_NOW | 게이트가 Phase 3 이전에 돌아 워킹트리가 비어 있는 구조적 결과. 해소는 Phase 3 실행이며 착수 후 재실행으로 실코드를 리뷰받는다 |

- Deferred to backlog: 0 → `.claude/plans/codex-findings-backlog.md`
- Open Questions: 없음 (§0 auto-CRITICAL 0건)
- Codex session 참조: thread `01a01373-04f8-7df0-8983-f9396cf14100`
- 구조 verdict: `needs-attention` → 게이트 어휘로 `divergent`. receipt `codex_verdict` 에 그대로 싣는다

### 착수 전 구현 시점 결정 (2.5.2 — Codex 에 제시한 목록)

1. 최소권한 러너를 **한 곳에서만** 정의하고(셸 변수 `PERM`) 판정기·생성기 전 호출이 거기서
   파생된다. 진입점별 회귀 프로브(gitdir 읽기 거부 · 인덱스 읽기 성공)는 **새 파일이 아니라**
   판정기 `--selftest` 안에 둔다 — 계획이 신규 파일 생성을 금지한다 (R11 F0)
2. selftest 는 항상 `--selftest-dir ./docs/_meta/index/.selftest` 로 부르고, 성공·실패 양쪽
   경로에서 그 디렉터리를 지운다 (R11 F1)
3. `STATUS_UNDETERMINED_REASONS_CLOSED` 는 판정기가 유일 정본이고 생성기가 import 한다
4. 필터 3·4 는 `{verdict, undetermined_reason, historical_observation}` 객체이고 드리프트
   아래서 `verdict: null`. 코드 head ≠ `frozen_heads` 인데 Boolean 이면 판정기가 error
5. `reachability` 는 두 갈래뿐 — SHA 상이 → UNKNOWN + `baseline_drift_unverified`,
   SHA 동일 → error(전제 만료)
6. `reference_applicability/v1` 은 `status_basis` 아래, `per_source` 가 `sources[]` 와
   다중집합 일치. `applicable` 과 `reference_verdict` 분리, 모호 행은 null +
   `reference_count_ambiguous`
7. 판정기는 `extraction-manifest.yaml` · `releases.yaml` 을 자기 파서로 다시 읽는다
   (생성기 import 금지)

### Security Reviewer

> security-reviewer unavailable, skipped (auto-fallback): 이 세션은 Agent(Task) 도구 호출이
> 금지돼 있어 `subagent_type: security-reviewer` 를 부를 수 없다.

수동 확인 (경로 처리·권한 경계가 이 마일스톤의 유일한 보안 인접 표면이다):

| 표면 | 확인 |
|---|---|
| `--selftest-dir` 경로 | 호출자가 주는 경로를 `resolve()` 한다. 6b 는 쓰기 루트를 `./docs/_meta/index` 로 좁히므로 그 밖으로 쓰려는 selftest 는 `ERR_ACCESS_DENIED` 로 죽는다 — 경로 검증을 코드로 다시 하지 않고 **런타임 권한 경계**에 맡긴다 |
| gitdir 읽기 | 읽기 루트가 `./docs/_meta` 라 `.git` 접근이 `ERR_ACCESS_DENIED`. selftest must-fail 픽스처가 이것을 회귀 검사한다 |
| 자식 프로세스 | `--allow-child-process` 를 주지 않는다. spawn 시도는 실행 즉시 `ERR_ACCESS_DENIED` |
| 신뢰 경계 | 입력은 전부 이 저장소가 커밋한 산출물이다. 네트워크·자격증명·SQL 실행 경로 없음 |

CRITICAL/HIGH 보안 발견 0건.

### Design Review

> impeccable silent-skip: `design_signal=false` (`reason=no-signal`). 렌더링 표면
> (`.tsx/.css/.html` · `.claude/cache/*`)을 건드리지 않는 제어면 변경이다.

## Task 0 — main 병합 (완료, 커밋 `5c5113e`)

| 게이트 | 결과 |
|---|---|
| 충돌 건수 | 2 (`.claude/plans/codex-findings-backlog.md` · `.gitattributes`) — 예측대로 |
| `docs/_meta/` 하위 충돌 | **0건** |
| `.gitattributes` | `git diff --quiet main` 통과 (바이트 동일, 저술하지 않음) |
| backlog | 양쪽 전부 보존 — 항목 74건 |
| manifest | `version: 9` · `repos[].head` 3종이 코드 축 재핀값 유지 (`c7597fc7` · `b9a90a42` · `7df733a5`) |
| `known_gates` | 11 → 15 |
| git 축 6종 | 전부 실재 |
| 서브모듈 | 3/3 이 manifest head 로 체크아웃 (`git submodule status` 접두 `-`/`+` 0개) |

부수 처리: `insuniverse-server/.claude/state/hook-trace/…` 에 mccp 훅이 남긴 파일 1개가 서브모듈
클론을 막고 있었다. 스크래치패드로 사본을 옮기고 제거했다.

### 계획 Task 6 (2) 의 전제가 실측과 다르다

계획은 "main 의 서브모듈 포인터(`aa3ef1c`)와 main manifest `repos[].head` 가 서로 다르다" 고 적었다.
지금 측정하면 **같다**.

| 트리 | server | site | admin |
|---|---|---|---|
| `main` 서브모듈 포인터 | `c7597fc7` | `b9a90a42` | `7df733a5` |
| `main` manifest `repos[].head` | `c7597fc7` | `b9a90a42` | `7df733a5` |
| 병합 전 `feat/index-code` 포인터 | `4d798f55` | `767d1e44` | `fb001e1d` |

즉 어긋나 있던 것은 **이 브랜치의 포인터**(git 축 `frozen_heads` 와 같은 구 SHA)이고, 병합이
그것을 manifest 에 정렬시켰다. 회부 (ii)의 대상(3자 정합)은 그대로 남지만 **문면은 정정해야 한다**
— 남은 불일치는 `manifest/서브모듈(신 SHA)` ↔ `git 축 산출물 frozen_heads(구 SHA)` 2자다.

## Task 0b — 병합 영향 전수 측정 (**중단 지점**)

| 판정기 | 종료코드 | 첫 실패 사유 |
|---|---|---|
| `verify-code-index` | 0 | — |
| `verify-policy-anchors` | 0 | — |
| `verify-policy-values` | **1** | `known_gates` 4종 하한 위반 (`RUN_MODE` · `SCHEDULER_TYPE` · `REACT_APP_SMARTRO_MODE` · `REACT_APP_TOSS_CLIENT_KEY`) — **예측대로** |
| `verify-schema-constraints` | 0 | — |
| `verify-api-contracts` | 0 | — |
| `verify-tests` | **1** | 아래 별도 절 |
| `verify-policy-candidates` | 0 | — |

### 예측 밖 파손 — 테스트 축 앵커가 병합으로 끊겼다

`verify-tests.mjs` 는 인자 없이 부르면 모드가 없어 usage 를 내고 1 로 끝난다 — 계획 Validation 의
`node docs/_meta/verify/verify-$v.mjs` 루프는 이 축에 대해 **원리적으로 통과하지 못한다**. 5b 의
정식 게이트는 `check-tests-crosscheck.mjs` 이고 생성기 report 를 요구한다. 그래서 읽기 전용으로
정식 경로를 돌렸다:

```
node docs/_meta/verify/build-tests.mjs --repos-root . --report --json   # exit 1
node docs/_meta/verify/verify-tests.mjs --selftest --selftest-dir <scratch>   # exit 1, FAIL 34건
```

실패 사유는 하나다 — `.claude/meta/05-final-spec.md` 로 건 **행 번호 앵커 2개**가 병합으로
어긋났다.

| 앵커 | 요구 토큰 | 병합 전 행 | 병합 후 실제 행 |
|---|---|---|---|
| `rule_anchor` = `meta/05-final-spec.md:172` | `skip` | 172 (`| **S** | 테스트 … skip 아님 + CI 포함 확인 시`) | **246** |
| `not_source_wide_anchor` = `meta/05-final-spec.md:168` | `항목` | 168 (`소스 전체가 아니라 항목 단위로 등급을 매긴다`) | **242** |

병합이 `.claude/meta/05-final-spec.md` 에 **113행을 추가**(그중 74행이 이 두 행 위)했다.
반사실 확인: 병합 전 커밋 `0e3c9dc` 의 168·172 행은 각각 요구 토큰을 담고 있었다.

**영향 반경은 테스트 축뿐이다** (행 핀 전수 grep):
`docs/_meta/verify/build-tests.mjs:139,141` · `docs/_meta/verify/verify-tests.mjs:152,153`(+ selftest
픽스처) · 산출물 `docs/_meta/index/tests.jsonl` 의 `meta.s_grade_rule.{rule_anchor,not_source_wide_anchor}`.

**Task 0b Gate 대로 Task 1 을 시작하지 않고 중단한다** — 비영 종료가 2종이고, 두 번째는 병합이
코드 축의 다른 전제(테스트 축 앵커)를 건드린 결과다. 재핀 대상 3파일은 전부 마일스톤 5b 소유이고
6b 의 Files to Change 밖이다 (UI4).

## Task 1 — `env-gates.yaml` 재생성 (11 → **13**, 계획 예상 15 아님)

| 게이트 | 결과 |
|---|---|
| 재생성 후 `entry_count` | **13** (계획 예상 15) |
| 실린 전 항목 status | `conditional-unknown` 13/13 |
| 2회 생성 `diff` | 빈 출력 |
| 기존 11종 `id`·`default_literal`·앵커 | 변경/소실 **0건** |
| `constants.yaml` | 바이트 무변경 (같은 생성기가 쓰지만 결과 동일) |
| `verify-policy-values` | exit **1** — 하한 위반 2건만 남음 |

`RUN_MODE` · `SCHEDULER_TYPE` 은 **결손이 아니라 스캔 우주 밖**이다. 우주는 manifest
`repos[].src_roots` 아래 `.ts/.tsx` 이고, 이 둘은 컨테이너·CI 선언 파일에서만 선언·독출된다:
`insuniverse-server/apps/api/dockerfile:37-41`(ENV + CMD 의 `$RUN_MODE` 분기 ·
`case "$SCHEDULER_TYPE"`) · `.github/workflows/api.yml:126,226`. 6a gap 이 "넷 다 코드에
실재(4·4·1·1 파일)" 로 적은 것은 이 우주 밖 파일을 센 것이다. backlog·fix-task 등재 후 진행한다.

신규 2종(`REACT_APP_SMARTRO_MODE` · `REACT_APP_TOSS_CLIENT_KEY`)의 `unset_behavior` 는 큐레이션
필드라 손으로 채웠다 — 둘 다 `?? ""` 로 빈 문자열이 벤더 SDK 에 넘어간다는 코드 사실까지만 적고,
**빈 값에 대한 벤더 동작은 `12-index-runtime` 소관**으로 명시했다 (UI3).

생성기 문면 정정 2건: (1) "커밋되지 않은 spec 작업본" → 병합으로 커밋됨, (2) "검증기가 하한이라
이 파일을 안 고쳐도 막히지 않는다" → 하한의 방향을 반대로 읽은 것. 리터럴 `11종` 도 정본에서
재도출하게 바꿨다.

## Task 2 — 인구조사 (쓰기 없음)

**(a) 모집단** — 행 수 `5,199` == 유일 id `5,199` (확정 1,991 · 잠정 3,208), 두 파일 교집합 0건.
`env-gates.yaml` 이 11 → 13 이므로 **재생성 후 N = 5,201** (계획 예상 5,203 은 15종 전제였다).
`8,407` 은 쓰지 않는다.

**(b) source kind 분해** (`sources[]` 원소 5,555 — access 행이 2-source 다)

| kind | 원소 | axis 분포 |
|---|---|---|
| `api_contracts_dto_property` | 3,208 | contract 3,208 |
| `api_contracts_handler` | 652 | access 356 · contract 296 |
| `schema_constraints` | 1,265 | data 1,265 |
| `policy_anchors` | 356 | access 356 |
| `policy_constants` | 63 | value 63 |
| `env_gates` | 11 (→13) | value 11 |

**(c) 참조 계측** — `active_reference_count` 보유는 `policy_constants` **63** 뿐이고 그중
`identifier_ambiguous: true` 가 **15**. 나머지 5종 kind 는 전부 미보유. 계획 실측과 일치.

**(d) 필터 3** — `deleted_paths`(server 83 · site 695 · admin 55) ∩ 후보 경로 = **0건 · 3/3 저장소**.
드리프트 구간 재추가 없음.

**(e) 필터 4** — revert patch-id 그룹 state 별 후보 행:
`absent` **16**(server, 그룹 2 · 경로 3) · `unresolved` **2,133**(server, 그룹 2 · 경로 78) ·
`present` **0**. **`unresolved` 가 0 이 아니므로 `revert_state_unresolved` 신설 조건이 성립한다.**

**(f) 필터 5 전제** — 코드 축 head 3종이 `commits.<repo>.jsonl` 에 **0/0/0**
(인덱스 커밋 수 server 2,587 · site 2,446 · admin 1,157). 두 정본 기준선이 실제로 읽히고 3/3 드리프트:

| repo | 코드 축 head | git 축 `frozen_heads` (releases · dead 동일) |
|---|---|---|
| server | `c7597fc7` | `4d798f55` |
| site | `b9a90a42` | `767d1e44` |
| admin | `7df733a5` | `fb001e1d` |

후보 경로 모집단: server 278 · admin 12 · site 10 (유일 경로).

## Codex Implementation Review — 착수 후 라운드 (R2)

- 호출: 같은 래퍼, 변경 파일 경로를 명시해 재실행
- 구조 verdict: `needs-attention` → 게이트 어휘로 `divergent`
- 합치 결론: 3건. 둘은 이 사이클에서 실코드로 닫았고, 하나는 사용자 결정에 따라 등재 후 분리했다.

| Finding | Severity | Verdict | 처리 |
|---|---|---|---|
| F1 생성된 env-gate 인덱스가 자기 정본 판정기를 통과하지 못한다 (`env-gates.yaml:34`) | HIGH | DEFER_TO_BACKLOG | **사실이다.** `verify-policy-values` 가 `RUN_MODE`·`SCHEDULER_TYPE` 하한 위반으로 exit 1 이다. 원인은 결손이 아니라 **스캔 우주 밖**이고(발견 79) 닫으려면 생성기·판정기의 독립 재스캔 우주를 함께 넓혀야 하는데 `verify-policy-values.mjs` 는 6b Files to Change 밖이며 우주 정의는 manifest(`00-protocol`) 소유다. backlog · fix-task · findings 79 · 이월 14 · 산출물 `gap` 문면 다섯 곳에 등재하고 분리했다 (사용자 결정 2026-08-18) |
| F2 권한 selftest 가 신뢰 경계를 시험하지 않고도 성공을 보고할 수 있다 (`verify-policy-candidates.mjs:2339`) | HIGH | ACCEPT_NOW | **닫았다.** `--permission` 없이 돌면 봉인 픽스처 8종이 통째로 건너뛰는데 그때도 `selftest PASS` 가 찍혔다 — 게이트가 자기 핵심 축을 시험하지 않고 초록을 내는 fail-open 이다. 기본을 **실패**로 바꾸고 `--allow-unsealed` 명시 우회만 남겼다. 실측: 경계 없이 exit 1(FAIL (G12) 봉인 픽스처 미실행) · `--allow-unsealed` exit 0 · 경계 아래 99 cases PASS |
| F3 커밋↔revert 조인 실패를 접촉 경로 0건으로 조용히 접는다 (`verify-policy-candidates.mjs:600`) | MEDIUM | ACCEPT_NOW | **닫았다.** `commits[repo].get(sha) \|\| []` 가 조인 실패를 침묵으로 흡수해 `revert_state_unresolved` 가 조용히 덜 붙을 수 있었다. 미실재를 error 로 올렸다. 실측 미실재 **0 / 67**(server 30 · site 37 · admin 0)이라 산출물은 바이트 무변경이고 침묵 경로만 닫혔다 |

- Deferred to backlog: 1 (F1) → `.claude/plans/codex-findings-backlog.md`
- Open Questions: 없음 (§0 auto-CRITICAL 0건)

### 최소권한 래퍼 — 계획 문면 정정 (R11 F0 착지)

계획은 읽기 루트를 `./docs/_meta` **하나**로 적었다. 실행하면 판정기가 곧바로 죽는다
(`Access to this API has been restricted`) — 앵커 검사가 후보의 `defined_in` **소스 행을 실제로
읽기** 때문이다. R10 F0 이 적은 "두 모듈은 `docs/_meta` 밖을 읽지 않는다" 는 사실이 아니었다.

정본 래퍼는 이것이고, 판정기 헤더 주석·selftest 안내·이 문서가 **같은 문면**을 쓴다:

```
node --permission \
  --allow-fs-read=./docs/_meta \
  --allow-fs-read=./insuniverse-server --allow-fs-read=./insuniverse-site --allow-fs-read=./insuniverse-admin \
  --allow-fs-write=./docs/_meta/index
```

읽기 루트가 넓어져도 봉인은 유지된다 (실측):

| 대상 | 결과 |
|---|---|
| 저장소 `.git` | `ERR_ACCESS_DENIED` |
| 서브모듈 오브젝트 저장소 (`.git/worktrees/index-code/modules/…/logs/HEAD`) | `ERR_ACCESS_DENIED` |
| 서브모듈 `.git` (gitdir 포인터 한 줄) | 읽힘 — 이력이 들어 있지 않다 |
| 서브모듈 소스 파일 · 인덱스 | 읽힘 (앵커 검사에 필요) |
| `child_process` spawn/exec/fork · `createRequire` · worker | 전부 `ERR_ACCESS_DENIED` |

selftest 봉인 픽스처는 **8종**이고 전부 `ERR_ACCESS_DENIED` 만 통과로 센다 — `ENOENT` 로 넘어가는
거짓 양성을 막기 위해서다(초판이 스크래치 상대경로로 `.git` 을 겨눠 ENOENT 로 통과했다).
