# C8 — Area #7 잔여 sub-area marathon (Residual Hygiene) Implementation Plan

**Status:** closed — W1a-W5 closed (8-wave 10-commit, push range `e23ae86..HEAD` — W5 range review `APPROVED_FOR_PUSH` 후 push origin main, 실제 hash git log 기록).

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** index.html C8 의 21개 FIX site (13 prompt-pack + 4 production-code 파일) 의 닫힌 enumeration·시나리오 leak 을 generic semantic principle 로 추상화한다 — 신규 schema / producer / mechanism 0.

**Architecture:** 각 prompt-pack FIX = 신규 version 디렉토리 (`cp -r` 최신 → 다음 major bump) + residue stem 만 edit + 나머지 stem byte-identical sibling. `version_registry` provenance sync 는 **이미 entry 가 있는 4 module 한정** (entity_extractor / shot_extractor / shot_selector / location_consistency) — 신규 entry 신설 금지. code-path constant 3 module (t2i_visual_converter / background_master_plan / floor_plan_prompt) 은 code 상수 bump. code-carry FIX = in-place 문자열/dict-value 내용 치환, carrier shape 보존. 8 top-level wave (W1a/W1b/W2a/W2b/W3a/W4a/W4b/W5), W1a 는 atomic commit 3개 (W1a-1/W1a-2/W1a-3) 로 분할 (Codex plan-entry sanity Q3). NO VLM / no live LLM / no semantic regex (production routing).

**Tech Stack:** Python 3.12, pytest, prompt-pack file-fallback loader (`app.modules.prompt_loader`), `app.core.version_registry`, `app.core.step_manifest`.

**Spec:** `docs/superpowers/specs/2026-05-20-c8-area7-residual-hygiene-v1-design.md` (APPROVED_FOR_PLAN, Codex 3-iter).
**HEAD anchor:** `origin/main` = `e23ae86` (C5 closed). 변경 0.
**Doctrine:** [[feedback_llm_based_judgment]] 4-Gate (Prompt Closed-List Ban 중심) / [[feedback_no_vlm_dependency]] / [[feedback_codex_mcp_discussion_workflow]].

---

## §0. W0 — Live-Source Re-Verification (file edit 전 의무)

> Codex plan-entry sanity Q2: **W0 inventory 는 memory-only 금지** — 아래 표는 plan 작성 시점 (HEAD `e23ae86`) 의 캡처본이며, **각 wave 진입 직전 Task W0 의 실제 명령으로 재검증**한다. 명령 출력이 SSOT — 본 표와 불일치 시 명령 출력 우선 + Codex consult.

### §0.1 prompt_loader activation model (`backend/app/modules/prompt_loader.py` direct-read)

- **file-fallback 우선순위**: `load_prompt(module, name, db=None)` — `db` 전달 시 `prompt_template` DB row (`is_active=true`) 우선, 미전달 시 file-only. 대부분 pipeline caller 는 `db` 미전달.
- **stem 별 latest 해석**: `_resolve_stem_in_pack` 가 `_version_sort_key` (numeric primary) 로 module 내 version 디렉토리를 desc 순회 — **stem 마다 독립적으로** 자기를 가진 가장 최신 version 을 픽. 같은 module 안에서 stem 이 서로 다른 version 에서 로드될 수 있음 (의도된 backward-compat — `shot_extract` shot_schema.json 이 그 사례).
- **신규 numeric 디렉토리 = 자동 active** (numeric major 가 가장 크면). 단 **편집 stem 이 신규 dir 에 있어야** 신규로 픽됨 — 신규 dir 에 없는 stem 은 직전 dir 에서 fallback.
- **strict 모드**: `PROMPT_VERSION_PACK_STRICT` env truthy → latest dir 에 stem 부재 시 `RuntimeError`. **기본 OFF** (drift warning only). C8 은 strict 를 켜지 않으며, `shot_extract` v14 의 shot_schema.json 의도적 부재 (C5 OPTION_1) 가 strict 모드에서 깨지지 않도록 wave gate 는 기본 모드로 실행.
- **activation proof API**: `get_effective_source(module, name, db=None)` → `candidates.file.{version,path}` + `module_pack.{versions,latest,stem_drift}`. `.md` stem 만 고려 (schema `.json` drift 는 `_resolve_stem_in_pack(..., ext=".json")` 로 별도 확인 — `get_effective_schema_source` 함수는 미존재, docstring line 306 의 언급은 미구현).

### §0.2 13 prompt-pack module activation inventory — 6/4/3 분류 (SSOT)

| # | module | latest dir (e23ae86) | 다음 bump | stems (개수) | activation 분류 + proof |
|---|--------|---------------------|----------|-------------|------------------------|
| A | entity_all | `5.202605181858` | `6.<UTC>` | character.md, character_schema.json, location.md, location_schema.json, prop.md, prop_schema.json, system.md (7) | **pure file-fallback** — `entity_lister.py:14 _MODULE="entity_all"`. registry/constant edit 0 |
| B | entity_extract_v4 | `8.202603290500` | `9.<UTC>` | character.md, character_schema.json, location.md, location_schema.json, prop.md, prop_schema.json, system.md (7) | **pure file-fallback** — `entity_extractor_v4.py:9 _MODULE="entity_extract_v4"`. wiring proof = §0.3-(4) |
| C | entity_filter | `2.202603241900` | `3.<UTC>` | filter_schema.json, system.md (2) | **pure file-fallback** |
| D | entity_extractor_v2 | `10.202605181858` | `11.<UTC>` | system.md, turn_entity_detail.md, turn0_style_schema.json, turn0_style.md, turn1_7_detail_batch_schema.json, turn1_7_detail_batch.md, turn1_review_schema.json, turn1.md, turn2.md, turn3.md, turn4.md (11) | **version_registry provenance** — key `entity_extractor`. `_MODULE_INFO["entity_extractor"].prompt_dependency` = `entity_extractor_v2/v9` (**STALE** — 실 v10) |
| E | outlook_extractor | `12.202605181928` | `13.<UTC>` | phase1_schema.json, phase1.md, phase2_schema.json, phase2.md, phase3_schema.json, phase3.md (6) | **pure file-fallback** |
| F | location_consistency | `2.202604201230` | `3.<UTC>` | location_schema.json, system.md (2) | **version_registry provenance** — key `location_consistency`. **step DISABLED** (manifest 제거) — provenance-only, §0.3-(4) dormancy 검증 |
| G | shot_extract | `13.202605201049` | `14.<UTC>` | system.md, user.md (2) — **shot_schema.json 부재** (C5 OPTION_1, v10 per-stem fallback) | **version_registry provenance** — key `shot_extractor`. db= caller — §0.3-(1) DB-shadow |
| H | shot_essence_extraction | `3.202605081814` | `4.<UTC>` | schema.json, system.md (2) | **pure file-fallback** — db= caller — §0.3-(1) DB-shadow |
| I | shot_selection | `4.202604191600` | `5.<UTC>` | selection_schema.json, system.md, user.md (3) | **version_registry provenance** — key `shot_selector`. db= caller — §0.3-(1) DB-shadow |
| J | scene_extractor_v2 | `20.202605190631` | `21.<UTC>` | scene_detail_schema.json, system.md, turn_scene_detail.md, turn0_context.md, turn1_split_long.md (5) | **pure file-fallback** — `test_prompt_versions.py` Group F pin, §0.3-(2) |
| K | t2i_visual_converter | `v4` (vN form) | `v5` | system.md (1) | **code-path constant** — `t2i_visual_converter.py:11` `PROMPT_DIR` 하드코딩 `.../t2i_visual_converter/v4` (loader 미사용) |
| L | background_master_plan | `3.202605092023` | `4.<UTC>` | schema.json, system.md, user_template.md (3) | **code-path constant** — `background_master_plan_step.py:40` `PROMPT_VERSION = "3.202605092023"` |
| M | floor_plan_prompt | `4.202605091200` | `5.<UTC>` | schema.json, system.md, user_template.md (3) | **code-path constant** — `floor_plan_prompt_step.py:29` `PROMPT_VERSION = "4.202605091200"` (`SCHEMA_VERSION=2` line 22 불변) |

분류 합계: **pure file-fallback 6** (A,B,C,E,H,J) / **version_registry provenance 4** (D,F,G,I) / **code-path constant 3** (K,L,M). **신규 `version_registry` entry 신설 절대 금지** — 위 4 module 만 sync.

### §0.3 W0 guard checklist (Codex plan-entry sanity Q2 — wave 진입 직전 실행)

**(1) DB-shadow check** — `shot_extract` / `shot_essence_extraction` / `shot_selection` 은 `load_prompt(db=...)` caller. 신규 file dir 가 active 가 되려면 DB 에 active `prompt_template` row 가 없어야 함. wave 진입 직전 실행:
```bash
cd backend && PYTHONPATH=. .venv/bin/python -c "
from app.modules.prompt_loader import get_effective_source
from app.core.database import SessionLocal
db = SessionLocal()
for m,n in [('shot_extract','system'),('shot_extract','user'),('shot_essence_extraction','system'),('shot_selection','system'),('shot_selection','user')]:
    src = get_effective_source(m, n, db=db)
    print(m, n, 'db_candidate=', src['candidates']['db'], 'with_db_winner=', src['effective']['with_db']['winner'])
db.close()
"
```
기대: 모든 `db_candidate = None` (seeded active row 부재) → file 이 winner. **DB candidate 가 None 이 아니면 STOP + Codex consult** (file bump 무효화 위험). **DB mutation 절대 금지 — 조회만.** SessionLocal 경로가 다르면 `backend/app/core/database.py` 의 실제 export 명으로 정정.

**(2) test-pin broad grep** — `backend/tests` + `backend/app` 전체에서 13 module 의 version 디렉토리/literal pin 을 enumerate (Codex Q2 — `test_prompt_versions.py`/`test_area_7b_no_drift.py` 에 한정 금지):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1 && grep -rn -E "(entity_all|entity_extract_v4|entity_filter|entity_extractor_v2|outlook_extractor|location_consistency|shot_extract|shot_essence_extraction|shot_selection|scene_extractor_v2|t2i_visual_converter|background_master_plan|floor_plan_prompt)/(v?[0-9])" backend/tests backend/app --include='*.py' | grep -v ".pyc"
```
분류 의무: (a) **latest-resolving** (`startswith` / `_list_module_versions()[0]` — bump 무관, 통과) / (b) **version-literal pin** (특정 dir 명 하드코딩 — bump 시 갱신 또는 historical pin 으로 분류) / (c) **historical pin** (의도적으로 옛 version 검사 — 불변). 알려진 pin: `test_prompt_versions.py` Group F (`scene_extractor_v2` `startswith("19.")` — latest-resolving, v21 bump 무관 통과; `test_scene_extractor_v2_v19_latest_loaded` 는 `turn_scene_detail` content marker 검사 — C8 이 turn_scene_detail.md 미수정이므로 통과). **실제 grep 출력 = SSOT.**

**(3) entity_extractor_v2 stale registry repair** — `_MODULE_INFO["entity_extractor"].prompt_dependency` = `entity_extractor_v2/v9` 인데 파일시스템 실 latest = v10. **이는 C8 신규 결함 아님 — v9→v10 bump 시 registry sync 가 누락된 pre-existing stale.** C8 W1a-2 가 entity_extractor_v2 를 소유 (v10→v11) 하므로 provenance 를 `v11` 로 동기 — plan/closure 에 **"pre-existing stale repair (registry v9 → 실 파일 v10 → C8 v11)"** 로 명시 문서화.

**(4) dormancy / wiring proof** — entity_extract_v4 (B) + location_consistency (F):
```bash
cd backend && grep -rn "entity_extractor_v4\|extract_entities_by_type\|extract_all_entities" app/core/steps/entity_steps.py
grep -rn "location_consistency" app/core/step_manifest.py app/core/steps/ --include='*.py' | grep -v ".pyc"
```
- entity_extract_v4 (B): plan 작성 시점 검증 결과 = **WIRED** (`entity_steps.py:175,218,222,239,243,260,264` 가 `extract_all_entities`/`extract_entities_by_type[_with_list]` import·호출). → 정상 active prompt hygiene 으로 처리.
- location_consistency (F): **step manifest 에서 제거됨 (disabled)** — wave 진입 시 재확인. dormant 이면 W1b 는 **runtime activation claim 금지** — "active-latest prompt residue hygiene + registry provenance sync" 로만 closure 기술 (C4 `background_chain_planning` 선례).

**(5) OpenAI strict schema gate — executable (Codex plan review NARROW 3)** — 신규 latest dir 에 schema sibling 을 복사하면 dormant strict failure 가 노출될 수 있음 (C5 `shot_schema.json` precedent). 발견용 grep 이 아니라 **실행 gate** — `.json` schema sibling 을 가진 신규 latest dir 를 만드는 모든 wave (W1a-1 / W1a-2 / W1a-3 / W1b / W2b / W3a) 의 PASS 단계 + **W2a** (shot_schema v10 fallback 이 새로 노출 안 됨을 증명) 에서 아래 명령 실행 — 검증 완료 경로:
```bash
cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q
```
PASS 의무. **FAIL 시 = dormant pre-existing schema 결함 노출 → STOP + 즉시 Codex consult, C8 안에서 silent schema 구조 edit 절대 금지** (schema 구조 결함은 C8 scope 밖, §C Known Carry). C8 은 schema sibling 을 byte-identical 복사만 (구조 변경 0) 하므로 신규 위반을 도입하지 않음.

**(6) STEP_MANIFEST 불변** — `backend/app/core/step_manifest.py:73 STEP_MANIFEST` 는 prompt-pack version 을 저장하지 않음 (확인됨). schema 구조 변경 0 이므로 schema_version / lifecycle / applicability entry 전부 불변 — wave gate G-manifest 가 증명.

**(7) 신규 version_registry entry 신설 금지** — §0.2 의 4 module 외 어떤 module 에도 `MODULE_VERSIONS` / `_MODULE_INFO` 신규 key 추가 금지.

### §0.4 code-carry 파일 inventory (4 FIX 파일 + 1 KEEP + 1 DEFER)

| site | 파일 | C8 처리 |
|------|------|---------|
| 19 | `backend/app/core/steps/scene_consistency_step.py` (P020 prefix shim) | **DEFER** — edit 0 (W5 boundary 기록) |
| 20 | `backend/app/core/steps/render_prompt_card.py` (P025) | **SPLIT** — `camera_frame_rule.applies_when` prose FIX (W4b) / `_SPATIAL_CAMERA_*` canary tuple KEEP |
| 21 | `backend/app/core/steps/render_prompt_card.py` (P026 `_ID_ETHNICITY_COMPONENTS`/`_ID_AGE_BANDS`) | **KEEP** — edit 0 (W5 boundary 기록) |
| 22 | `backend/app/core/steps/entity_steps.py` (P028) | **FIX** (W4a) |
| 23 | `backend/app/modules/pipeline/outlook_dedup.py` (P054) | **FIX** (W4a) |
| 24 | `backend/app/modules/pipeline/scene_validator.py` (P059) | **FIX** (W4a) |
| 25 | `backend/app/modules/reference_image_generator.py` (P070) | **FIX** (W4a) |

---

## §P. Procedure P — Prompt-Pack Version Bump (재사용 절차, 1회 완전 명세)

각 prompt-pack FIX task 는 아래 절차를 **module 별 파라미터** 와 함께 호출한다. 절차 자체는 여기서만 완전 명세 — wave task 는 파라미터만 제공.

**입력 파라미터**: module `M`, 직전 latest dir `D_old`, 다음 major `N+1`, edit 대상 stem 목록 `E`, 전체 stem 목록 `S`.

- **P-1. UTC 캡처 — tool-call 경계 금지 (Codex plan review NARROW 2)**: dir 생성 bash 블록 **안에서만** `UTC=$(date -u +%Y%m%d%H%M)` 를 캡처한다. **`$UTC` shell 변수를 별도 tool call / 별도 bash 블록으로 넘기지 않는다** (tool 호출 간 shell state 미보장). 한 wave/subwave 의 모든 신규 dir 생성은 **단일 bash 블록** 안에서 — 그 블록이 UTC 캡처 + mkdir + cp + `ls -d` readback 까지 완결한다.
- **P-2. 신규 dir 경로**: numeric form → `D_new = prompts/_base/M/<N+1>.$UTC` ; `t2i_visual_converter` vN form → `D_new = prompts/_base/t2i_visual_converter/v5` (UTC 무관).
- **P-3. 전체 복사 + readback**: `cp -r prompts/_base/M/D_old/. D_new/` — `D_old` 에 **실재하는 stem 만** 복사된다. 원래 없던 stem (예: `shot_extract` 의 shot_schema.json) 은 **새로 만들지 않는다** (Codex Q2). `mkdir -p D_new` 선행. 블록 말미에 `ls -d prompts/_base/M/<N+1>.*` 로 **실제 생성된 dir 명을 출력**하고, 이후 모든 단계 (stem edit / code 상수 / commit `git add` path) 는 이 readback 한 **실제 dir 명 literal** 을 사용한다 — assumed `$UTC` 재사용 금지.
- **P-4. residue stem edit**: `E` 의 stem 만 `D_new` 안에서 edit. `S \ E` 의 stem 은 cp 결과 그대로 (byte-identical sibling) — 손대지 않는다.
- **P-5. version-stamped header 동기** (C4 함정 N-1): edit 대상 stem 안에 version 토큰을 담은 header/주석 (예: `# System (v4 / D6)`, `_MODULE_INFO` header) 이 있으면 `N+1` 로 갱신. (해당 wave task 가 명시.)
- **P-6. 직전 dir 불변**: `D_old` 변경 0.

**code-path constant module (K/L/M) 추가**: 해당 code 상수 (`PROMPT_DIR` / `PROMPT_VERSION`) 를 신규 dir 에 일치하도록 bump — 그렇지 않으면 dir bump 만으론 무효 (W3a task 가 명시). **`PROMPT_VERSION` 은 P-3 의 `ls` readback 으로 확인한 실제 dir 명 (예: `4.202605211530`) 을 literal 로 기입** — design-time 추정값 / stale `$UTC` 금지. 기입 후 `grep` 으로 code 상수 == 실제 dir 명 동치 검증 (Codex plan review NARROW 2).

**version_registry-provenance module (D/F/G/I) 추가**: `backend/app/core/version_registry.py` 의 해당 key 만 sync (§P-reg 아래) — 신규 entry 신설 금지.

**§P-reg. version_registry sync 절차** (D/F/G/I 한정):
- `MODULE_VERSIONS[key]` semantic version 을 minor bump (예: `1.3.0` → `1.4.0`).
- `_MODULE_INFO[key].prompt_dependency` 를 `<module>/v<N+1>` 로 갱신, `updated_at` 을 `2026-05-20` 로 갱신.
- 동일 줄/인접 줄의 주석이 version 을 언급하면 자기모순 없게 동기 (C5 함정 N-5 — `_MODULE_INFO` block 양쪽 comment).
- registry key ≠ dir 이름 주의: `entity_extractor`(dir `entity_extractor_v2`) / `shot_extractor`(dir `shot_extract`) / `shot_selector`(dir `shot_selection`) / `location_consistency`(dir 동명).

---

## §G. Gate 정의 (spec §7 — wave 별 C8 regression test)

test 파일 = `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (wave 별 append, C5/C4 와 동일 디렉토리). harness = `test_c5_action_segment_sot.py` 패턴 — `_PROMPTS = Path(__file__).resolve().parents[3] / "prompts" / "_base"`, `_latest_dir(module)`, `_sha256(p)`. **residue needle 은 split-string 상수** (test 파일 / plan / 문서가 doc-wide grep 에 self-trip 하지 않도록).

- **G-residue**: 해당 wave FIX site 의 닫힌 토큰 목록 / 시나리오 고유명사 가 신규 active pack / 수정 파일에 **부재** (closed-world exact-phrase membership, no semantic regex). path-scope = 신규 dir / 특정 code 파일 한정 (doc 비대상).
- **G-sibling**: 신규 version-pack 의 미수정 stem 이 `D_old` 와 sha256 byte-identical.
- **G-activation**: `get_effective_source` / `_resolve_stem_in_pack` 가 신규 version 디렉토리를 active 로 resolve (source-path proof — path substring + `module_pack.latest`).
- **G-manifest**: `STEP_MANIFEST` schema_version / lifecycle 보존 (해당 module 의 step entry 가 manifest 에 있으면).
- **G-registry** (D/F/G/I 한정): 해당 module 의 `version_registry` provenance entry 가 신규 version 과 일치. file-fallback module 은 본 gate 비적용 (G-activation 으로 대체).
- **G-intent**: 추상화된 문구가 원 의도 keyword 를 보존 (positive assertion — 무엇을 추출/제외/분류하라는 의도가 살아있는지).
- **G-canary (W4b)**: `g4_5a_camera_frame_consistency.py` canary + `camera_frame_rule` builder unit test 가 prose narrow edit 후 PASS — no live LLM.
- **G-boundary (W5)**: §8 closed-area 기 추상화 + KEEP site (16, 21) byte-identical, DEFER site (19) edit 0.

각 wave atomic commit 직전: ① 해당 wave G-test PASS ② baseline regression (영향 받는 기존 test) PASS ③ Codex per-wave review.

---

## Task W1a-1: entity_all + entity_filter prompt-pack (Site 1, 3) — pure file-fallback

**Files:**
- Create: `prompts/_base/entity_all/6.<UTC>/` (cp from `5.202605181858/`, 7 stems)
- Modify: `prompts/_base/entity_all/6.<UTC>/prop.md` (Site 1 — lines 21,22,25)
- Create: `prompts/_base/entity_filter/3.<UTC>/` (cp from `2.202603241900/`, 2 stems)
- Modify: `prompts/_base/entity_filter/3.<UTC>/system.md` (Site 3 — lines 12-13)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (신규 파일 생성)

- [ ] **Step 1: W0 재검증** — §0.3-(2) test-pin grep 실행, entity_all/entity_filter 에 version-literal pin 이 없음을 확인 (pure file-fallback). `git rev-parse HEAD` = `e23ae86` 확인. `ls prompts/_base/entity_all/` 로 현재 latest = `5.202605181858`, `entity_filter/` latest = `2.202603241900` 확인.

- [ ] **Step 2: C8 test 파일 생성 + W1a-1 G-test 작성**

`backend/tests/prompts/test_c8_area7_residual_hygiene.py` 신규 생성:

```python
"""C8 — Area #7 잔여 sub-area marathon (Residual Hygiene) regression tests.

fix-critical-1 Tier γ #9. 닫힌 enumeration·시나리오 leak → generic semantic
principle 추상화. 신규 schema / producer / mechanism 0.

파일 read + 문자열 검사 + sha256 only — no live LLM, NO VLM.
residue needle 은 split-string 상수 (test 파일 / 문서 self-hit 방지).

Gate map (spec §7 / plan §G): wave 별 append.
"""
from __future__ import annotations

import hashlib
from pathlib import Path

from app.core.version_registry import MODULE_VERSIONS, _MODULE_INFO
from app.modules.prompt_loader import (
    _list_module_versions,
    _resolve_stem_in_pack,
    get_effective_source,
    load_prompt,
)

_PROMPTS = Path(__file__).resolve().parents[3] / "prompts" / "_base"


def _latest_dir(module: str) -> Path:
    """module 의 numeric-latest prompt-pack 디렉토리."""
    return _PROMPTS / module / _list_module_versions(module)[0]


def _sha256(p: Path) -> str:
    return hashlib.sha256(p.read_bytes()).hexdigest()


# ──────────────── W1a-1 — entity_all + entity_filter (Site 1, 3) ────────────────

# 닫힌 목록 needle (split-string — test self-hit 방지)
_WEAR_LIST = "수트, 우주" + "복, 갑옷, 제복"
_EQUIP_LIST = "벽면 모니터, " + "TV, CCTV"
_PHENOM_LIST = "피, 물, " + "불, 연기"


def test_w1a1_g_residue_entity_all_prop():
    """G-residue: entity_all 신규 prop.md 에 닫힌 의복/장비/현상 목록 부재."""
    prop = (_latest_dir("entity_all") / "prop.md").read_text(encoding="utf-8")
    assert _WEAR_LIST not in prop, "Site1 의복 닫힌 목록 잔존"
    assert _EQUIP_LIST not in prop, "Site1 배경장비 닫힌 목록 잔존"
    assert _PHENOM_LIST not in prop, "Site1 자연현상 닫힌 목록 잔존"


def test_w1a1_g_residue_entity_filter_system():
    """G-residue: entity_filter 신규 system.md 에 닫힌 의복/장비 목록 부재."""
    sysmd = (_latest_dir("entity_filter") / "system.md").read_text(encoding="utf-8")
    assert _WEAR_LIST not in sysmd, "Site3 의복 닫힌 목록 잔존"
    assert _EQUIP_LIST not in sysmd, "Site3 배경장비 닫힌 목록 잔존"


def test_w1a1_g_intent_preserved():
    """G-intent: 제외 기준 의도 (입는 것 제외 / 장비 제외 / 효과 구분) 보존."""
    prop = (_latest_dir("entity_all") / "prop.md").read_text(encoding="utf-8")
    assert "제외 기준" in prop, "prop.md 제외 기준 섹션 손실"
    assert "시각 효과" in prop, "자연현상→시각효과 구분 의도 손실"
    sysmd = (_latest_dir("entity_filter") / "system.md").read_text(encoding="utf-8")
    assert "제거 (물체)" in sysmd, "entity_filter 제거 기준 의도 손실"


def test_w1a1_g_sibling_entity_all():
    """G-sibling: entity_all 신규 dir 의 미수정 6 stem 이 직전 dir 와 byte-identical."""
    vers = _list_module_versions("entity_all")
    new_d, old_d = _PROMPTS / "entity_all" / vers[0], _PROMPTS / "entity_all" / vers[1]
    for stem in ("character.md", "character_schema.json", "location.md",
                 "location_schema.json", "prop_schema.json", "system.md"):
        assert _sha256(new_d / stem) == _sha256(old_d / stem), f"sibling drift: {stem}"


def test_w1a1_g_sibling_entity_filter():
    """G-sibling: entity_filter 신규 dir 의 미수정 stem (filter_schema.json) byte-identical."""
    vers = _list_module_versions("entity_filter")
    new_d, old_d = _PROMPTS / "entity_filter" / vers[0], _PROMPTS / "entity_filter" / vers[1]
    assert _sha256(new_d / "filter_schema.json") == _sha256(old_d / "filter_schema.json")


def test_w1a1_g_activation():
    """G-activation: loader 가 entity_all v6 / entity_filter v3 를 active 로 resolve."""
    src = get_effective_source("entity_all", "prop")
    assert src["module_pack"]["latest"].startswith("6."), "entity_all v6 미활성"
    assert "/6." in src["candidates"]["file"]["path"], "entity_all prop.md v6 경로 아님"
    src2 = get_effective_source("entity_filter", "system")
    assert src2["module_pack"]["latest"].startswith("3."), "entity_filter v3 미활성"
```

- [ ] **Step 3: W1a-1 G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -v`
Expected: `test_w1a1_g_residue_*` FAIL (현재 latest = v5/v2 에 닫힌 목록 잔존), `g_activation` FAIL (v6/v3 미존재). G-intent 는 PASS 가능.

- [ ] **Step 4: entity_all v6 + entity_filter v3 dir 생성 (Procedure P — 단일 bash 블록)**

Procedure P — entity_all (M=`entity_all`, D_old=`5.202605181858`, N+1=`6`, E=`{prop.md}`, S=7) + entity_filter (M=`entity_filter`, D_old=`2.202603241900`, N+1=`3`, E=`{system.md}`, S=2). **양 module dir 를 단일 bash 블록에서 생성** (P-1 UTC discipline — `$UTC` tool-call 경계 금지):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/entity_all/6.$UTC prompts/_base/entity_filter/3.$UTC
cp -r prompts/_base/entity_all/5.202605181858/. prompts/_base/entity_all/6.$UTC/
cp -r prompts/_base/entity_filter/2.202603241900/. prompts/_base/entity_filter/3.$UTC/
ls -d prompts/_base/entity_all/6.* prompts/_base/entity_filter/3.*   # 실제 dir 명 readback — 이후 모든 단계가 이 literal 사용
```
`prompts/_base/entity_all/6.<readback>/prop.md` 편집 — 아래 3줄 치환 (P-5 N/A — prop.md 에 version header 없음):

치환 1 (line 21):
```
- 사람이 입는 형태의 모든 것 (수트, 우주복, 갑옷, 제복, 의상, 입는 장치나 로봇은 제외)
```
→
```
- 사람이 몸에 입거나 착용하는 형태의 모든 것 — 착용 시 인물 외형의 일부가 되는 것은 별도 소품이 아님
```

치환 2 (line 22):
```
- 배경에 설치된 장비 (벽면 모니터, TV, CCTV 등)
```
→
```
- 배경 구조물에 고정 설치되어 독립적으로 들고 다닐 수 없는 장비류
```

치환 3 (line 25):
```
- 물질/액체/자연현상 (피, 물, 불, 연기, 안개, 먼지, 눈, 비 등) — 이것들은 소품이 아니라 시각 효과
```
→
```
- 고정된 형태·외형이 없는 물질·액체·기체·자연현상 — 소품이 아니라 시각 효과
```

- [ ] **Step 5: entity_filter v3 system.md 편집**

entity_filter v3 dir 는 Step 4 단일 블록에서 생성됨. `prompts/_base/entity_filter/3.<readback>/system.md` 편집 — lines 12-13 치환:
```
- **제거 (물체)**: 사람이 입는 형태의 모든 것 (수트, 우주복, 갑옷, 제복 등, 완전 몸을 깜사는 형태 장치나 입는 로봇 등도 제거)
- **제거 (물체)**: 배경에 설치된 장비 (벽면 모니터, TV, CCTV 등)
```
→
```
- **제거 (물체)**: 사람이 몸에 입거나 착용하는 형태의 모든 것 — 착용 시 인물 외형의 일부가 되는 것
- **제거 (물체)**: 배경 구조물에 고정 설치되어 독립적으로 들고 다닐 수 없는 장비류
```

- [ ] **Step 6: W1a-1 G-test 실행 → PASS 확인 + baseline regression + strict schema gate**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -v`
Expected: W1a-1 6개 test 전부 PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py -q` → PASS (entity_all/entity_filter 는 Group F 무관, 회귀 없음 확인).
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (신규 entity_all v6 / entity_filter v3 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 7: W1a-1 commit**

```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
git add prompts/_base/entity_all/6.* prompts/_base/entity_filter/3.* backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W1a-1(c8-area7-residual-hygiene-v1): entity_all v6 + entity_filter v3 prompt-pack — 의복·배경장비·자연현상 닫힌 명사 목록 추상화 (Site 1/3, generic semantic principle, pure file-fallback) + C8 test 신설

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 8: Codex per-wave review** — W1a-1 commit hash + diff 요약을 Codex MCP 에 송신, `APPROVED_FOR_W1a-2_ENTRY` verdict 수령 후 다음 진입.

---

## Task W1a-2: entity_extractor_v2 prompt-pack (Site 4) — version_registry provenance + stale repair

**Files:**
- Create: `prompts/_base/entity_extractor_v2/11.<UTC>/` (cp from `10.202605181858/`, 11 stems)
- Modify: `prompts/_base/entity_extractor_v2/11.<UTC>/turn2.md` (Site 4 — lines 10-11)
- Modify: `backend/app/core/version_registry.py` (key `entity_extractor` — §P-reg + stale repair)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

- [ ] **Step 1: W0 재검증** — `ls prompts/_base/entity_extractor_v2/` 로 latest = `10.202605181858` 확인 (numeric — `10.` lexical sort 함정 주의). `version_registry.py` 의 `_MODULE_INFO["entity_extractor"]` 가 `prompt_dependency="entity_extractor_v2/v9"` (STALE), `MODULE_VERSIONS["entity_extractor"]="1.3.0"` 인지 확인. §0.3-(3) stale repair 명시: registry v9 ≠ 실 파일 v10 — pre-existing stale.

- [ ] **Step 2: W1a-2 G-test 작성** (test 파일에 append)

```python
# ──────────────── W1a-2 — entity_extractor_v2 (Site 4) ────────────────

# 닫힌 목록 needle (split-string)
_AGE_EX = "20대" + "→60대"
_OUTFIT_LIST = "군복/" + "정장/일상복"
_STATE_LIST = "부상/" + "결박/사망"


def test_w1a2_g_residue_turn2():
    """G-residue: entity_extractor_v2 신규 turn2.md 에 나이/의상/상태 닫힌 목록 부재."""
    turn2 = (_latest_dir("entity_extractor_v2") / "turn2.md").read_text(encoding="utf-8")
    assert _AGE_EX not in turn2, "Site4 나이밴드 example 잔존"
    assert _OUTFIT_LIST not in turn2, "Site4 의상 닫힌 목록 잔존"
    assert _STATE_LIST not in turn2, "Site4 상태 닫힌 목록 잔존"


def test_w1a2_g_intent_preserved():
    """G-intent: 변형 허용/금지 판단 의도 보존."""
    turn2 = (_latest_dir("entity_extractor_v2") / "turn2.md").read_text(encoding="utf-8")
    assert "허용:" in turn2 and "금지:" in turn2, "변형 허용/금지 구조 손실"
    assert "변형이 없으면 빈 배열" in turn2, "변형 없음 지시 손실"


def test_w1a2_g_sibling():
    """G-sibling: 미수정 10 stem byte-identical."""
    vers = _list_module_versions("entity_extractor_v2")
    new_d = _PROMPTS / "entity_extractor_v2" / vers[0]
    old_d = _PROMPTS / "entity_extractor_v2" / vers[1]
    for stem in ("system.md", "turn_entity_detail.md", "turn0_style_schema.json",
                 "turn0_style.md", "turn1_7_detail_batch_schema.json",
                 "turn1_7_detail_batch.md", "turn1_review_schema.json",
                 "turn1.md", "turn3.md", "turn4.md"):
        assert _sha256(new_d / stem) == _sha256(old_d / stem), f"sibling drift: {stem}"


def test_w1a2_g_activation():
    """G-activation: loader 가 entity_extractor_v2 v11 을 active 로 resolve."""
    src = get_effective_source("entity_extractor_v2", "turn2")
    assert src["module_pack"]["latest"].startswith("11."), "v11 미활성"


def test_w1a2_g_registry():
    """G-registry: version_registry provenance 가 v11 로 sync (stale v9→v11 repair)."""
    assert _MODULE_INFO["entity_extractor"]["prompt_dependency"] == "entity_extractor_v2/v11"
    assert _MODULE_INFO["entity_extractor"]["updated_at"] == "2026-05-20"
    assert MODULE_VERSIONS["entity_extractor"] == "1.4.0"
```

- [ ] **Step 3: W1a-2 G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w1a2 -v`
Expected: `g_residue` / `g_activation` / `g_registry` FAIL.

- [ ] **Step 4: entity_extractor_v2 v11 prompt-pack 생성 (Procedure P)**

Procedure P — M=`entity_extractor_v2`, D_old=`10.202605181858`, N+1=`11`, E=`{turn2.md}`, S=11 stems. **단일 bash 블록** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/entity_extractor_v2/11.$UTC
cp -r prompts/_base/entity_extractor_v2/10.202605181858/. prompts/_base/entity_extractor_v2/11.$UTC/
ls -d prompts/_base/entity_extractor_v2/11.*   # 실제 dir 명 readback — 이후 단계가 이 literal 사용
```
`prompts/_base/entity_extractor_v2/11.<readback>/turn2.md` 편집 — lines 10-11 치환 (P-5 N/A — turn2.md 에 version header 없음):
```
   - 허용: 20대→60대 같은 큰 나이 변화, 완전히 다른 실루엣(전신 갑옷 등), 변장으로 다른 사람처럼 보이는 경우
   - 금지: 의상만 바뀌는 경우(군복/정장/일상복 → 씬 프롬프트로 처리), 부상/결박/사망 상태, 장비 착용 상태
```
→
```
   - 허용: 인물의 정체를 다시 인식해야 할 만큼 큰 외형 변화 — 큰 연령대 변화, 전신 실루엣이 근본적으로 달라지는 경우, 변장으로 다른 사람처럼 보이는 경우
   - 금지: 의복만 바뀌는 경우(씬 프롬프트로 처리), 일시적·가역적 신체 상태나 착용 상태 — 변형이 아니라 씬 단위로 처리
```

- [ ] **Step 5: version_registry sync — entity_extractor (§P-reg + stale repair)**

`backend/app/core/version_registry.py`:
- `MODULE_VERSIONS["entity_extractor"]` line 7: `"1.3.0"` → `"1.4.0"`, 주석에 ` + 2026-05-20 C8: entity_extractor_v2 v11 — turn2 변형 닫힌 목록 추상화` append.
- `_MODULE_INFO["entity_extractor"]` lines 44-47: `prompt_dependency` `"entity_extractor_v2/v9"` → `"entity_extractor_v2/v11"`, `updated_at` `"2026-05-13"` → `"2026-05-20"`.
- **stale repair 주석 의무** — `_MODULE_INFO["entity_extractor"]` block 에 주석 1줄 추가: `# C8: pre-existing stale repair — registry 가 v9 에 머물러 있었으나 실 파일은 v10 (v9→v10 sync 누락). C8 이 v11 로 동기.`

- [ ] **Step 6: W1a-2 G-test 실행 → PASS + baseline regression**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k "w1a1 or w1a2" -v` → 전부 PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py tests/test_step_manifest_v3.py -q` → PASS.
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (entity_extractor_v2 v11 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 7: W1a-2 commit**

```bash
git add prompts/_base/entity_extractor_v2/11.* backend/app/core/version_registry.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W1a-2(c8-area7-residual-hygiene-v1): entity_extractor_v2 v11 prompt-pack — turn2 나이/의상/상태 닫힌 목록 추상화 (Site 4, generic principle) + version_registry entity_extractor provenance sync (pre-existing stale v9→v10 repair → v11) + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 8: Codex per-wave review** — `APPROVED_FOR_W1a-3_ENTRY` 수령 후 진입.

---

## Task W1a-3: entity_extract_v4 prompt-pack (Site 2) — pure file-fallback (post wiring-proof)

**Files:**
- Create: `prompts/_base/entity_extract_v4/9.<UTC>/` (cp from `8.202603290500/`, 7 stems)
- Modify: `prompts/_base/entity_extract_v4/9.<UTC>/prop.md` (Site 2 — lines 22,23,26)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

- [ ] **Step 1: W0 wiring-proof 재검증 (§0.3-(4))** — `grep -rn "extract_entities_by_type\|extract_all_entities" backend/app/core/steps/entity_steps.py` 실행, entity_extract_v4 가 import·호출됨을 재확인. **WIRED 이면** 정상 active prompt hygiene. **dormant 로 밝혀지면 STOP + Codex consult** — runtime activation claim 금지, "documented dormant hygiene" 로 closure 기술 결정.

- [ ] **Step 2: W1a-3 G-test 작성** (append)

```python
# ──────────────── W1a-3 — entity_extract_v4 (Site 2) ────────────────


def test_w1a3_g_residue_prop():
    """G-residue: entity_extract_v4 신규 prop.md 에 닫힌 의복/장비/현상 목록 부재."""
    prop = (_latest_dir("entity_extract_v4") / "prop.md").read_text(encoding="utf-8")
    assert _WEAR_LIST not in prop, "Site2 의복 닫힌 목록 잔존"
    assert _EQUIP_LIST not in prop, "Site2 배경장비 닫힌 목록 잔존"
    assert _PHENOM_LIST not in prop, "Site2 자연현상 닫힌 목록 잔존"


def test_w1a3_g_intent_preserved():
    """G-intent: 제외 기준 의도 보존."""
    prop = (_latest_dir("entity_extract_v4") / "prop.md").read_text(encoding="utf-8")
    assert "제외 기준" in prop, "제외 기준 섹션 손실"


def test_w1a3_g_sibling():
    """G-sibling: 미수정 6 stem byte-identical."""
    vers = _list_module_versions("entity_extract_v4")
    new_d = _PROMPTS / "entity_extract_v4" / vers[0]
    old_d = _PROMPTS / "entity_extract_v4" / vers[1]
    for stem in ("character.md", "character_schema.json", "location.md",
                 "location_schema.json", "prop_schema.json", "system.md"):
        assert _sha256(new_d / stem) == _sha256(old_d / stem), f"sibling drift: {stem}"


def test_w1a3_g_activation():
    """G-activation: loader 가 entity_extract_v4 v9 를 active 로 resolve."""
    src = get_effective_source("entity_extract_v4", "prop")
    assert src["module_pack"]["latest"].startswith("9."), "v9 미활성"
```

- [ ] **Step 3: W1a-3 G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w1a3 -v`
Expected: `g_residue` / `g_activation` FAIL.

- [ ] **Step 4: entity_extract_v4 v9 prompt-pack 생성 (Procedure P)**

Procedure P — M=`entity_extract_v4`, D_old=`8.202603290500`, N+1=`9`, E=`{prop.md}`, S=7 stems. **단일 bash 블록** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/entity_extract_v4/9.$UTC
cp -r prompts/_base/entity_extract_v4/8.202603290500/. prompts/_base/entity_extract_v4/9.$UTC/
ls -d prompts/_base/entity_extract_v4/9.*   # 실제 dir 명 readback
```
`prompts/_base/entity_extract_v4/9.<readback>/prop.md` 편집 — lines 22,23,26 치환:
```
- 사람이 입는 형태의 모든 것 (수트, 우주복, 갑옷, 제복, 의상, 입는 장치나 로봇은 제외)
- 배경에 설치된 장비 (벽면 모니터, TV, CCTV 등)
```
→
```
- 사람이 몸에 입거나 착용하는 형태의 모든 것 — 착용 시 인물 외형의 일부가 되는 것은 별도 소품이 아님
- 배경 구조물에 고정 설치되어 독립적으로 들고 다닐 수 없는 장비류
```
그리고 line 26:
```
- 물질/액체/자연현상 (피, 물, 불, 연기, 안개 등) — 소품이 아님
```
→
```
- 고정된 형태·외형이 없는 물질·액체·기체·자연현상 — 소품이 아님
```

- [ ] **Step 5: W1a-3 G-test 실행 → PASS + baseline regression + strict schema gate**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k "w1a" -v` → W1a-1/W1a-2/W1a-3 전부 PASS.
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (entity_extract_v4 v9 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 6: W1a-3 commit**

```bash
git add prompts/_base/entity_extract_v4/9.* backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W1a-3(c8-area7-residual-hygiene-v1): entity_extract_v4 v9 prompt-pack — prop.md 의복·배경장비·자연현상 닫힌 명사 목록 추상화 (Site 2, generic principle, pure file-fallback, wiring-proof 후) + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 7: Codex per-wave review** — `APPROVED_FOR_W1b_ENTRY` 수령 후 진입.

---

## Task W1b: outlook_extractor + location_consistency prompt-pack (Site 5, 6, 7)

**Files:**
- Create: `prompts/_base/outlook_extractor/13.<UTC>/` (cp from `12.202605181928/`, 6 stems)
- Modify: `prompts/_base/outlook_extractor/13.<UTC>/phase2.md` (Site 5 — lines 15-17)
- Modify: `prompts/_base/outlook_extractor/13.<UTC>/phase3.md` (Site 6 — line 6)
- Create: `prompts/_base/location_consistency/3.<UTC>/` (cp from `2.202604201230/`, 2 stems)
- Modify: `prompts/_base/location_consistency/3.<UTC>/system.md` (Site 7 — lines 24-30)
- Modify: `backend/app/core/version_registry.py` (key `location_consistency` — §P-reg)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **OQ-PLAN-1 — RESOLVED (Codex plan review: `APPROVED`):** `location_consistency/system.md` **Site 7 FIX = lines 24-30 닫힌 환경상태 영문 토큰 목록만**. lines 41-57 example block (apartment / back-alley — 시나리오 고유명사 0 의 범용 output example, prior v2 fix 로 de-bias 완료) + line 18 재료 example (`(wood/concrete/tile/brick/stone/metal 등)` — `등` open-form) = **boundary-reviewed KEEP** (W5 closure 기록, W5 G-boundary 검증). wave entry 재확인 / conditional FIX 분기 없음.

- [ ] **Step 1: W0 재검증** — `ls prompts/_base/outlook_extractor/` latest = `12.202605181928`, `location_consistency/` latest = `2.202604201230` 확인. §0.3-(4) location_consistency dormancy: `grep -rn "location_consistency" backend/app/core/step_manifest.py` — step manifest 부재(disabled) 재확인 → W1b 는 location_consistency 를 **active-latest prompt residue hygiene + registry provenance sync** 로만 기술 (runtime activation claim 금지). §8 boundary: outlook_extractor 는 #7b 가 phase1.md 등 추상화 — C8 은 phase2.md:15-17 / phase3.md:6 만 touch, #7b 추상화 섹션 byte-identical 보존. OQ-PLAN-1 = RESOLVED (위 box, Codex APPROVED) — Site 7 FIX 는 lines 24-30 만, line 18 + 41-57 KEEP. wave entry 재확인 없음.

- [ ] **Step 2: W1b G-test 작성** (append)

```python
# ──────────────── W1b — outlook_extractor + location_consistency (Site 5,6,7) ────────────────

# 닫힌 목록 needle (split-string)
_OUTFIT_CHANGE_EX = "평상복→전투복, " + "정장→피의갑옷"
_SEONBI_EX = "고급선비" + "복1"
_WEATHER_TOKENS = "rain, storm, " + "clear sky, snow, fog"
_TIME_TOKENS = "dawn, noon, " + "dusk, night, golden hour"


def test_w1b_g_residue_outlook_phase2():
    """G-residue: outlook_extractor 신규 phase2.md 에 의상변화 시나리오 example 부재."""
    p2 = (_latest_dir("outlook_extractor") / "phase2.md").read_text(encoding="utf-8")
    assert _OUTFIT_CHANGE_EX not in p2, "Site5 의상변화 시나리오 example 잔존"


def test_w1b_g_residue_outlook_phase3():
    """G-residue: outlook_extractor 신규 phase3.md 에 작품 고유명사 example 부재."""
    p3 = (_latest_dir("outlook_extractor") / "phase3.md").read_text(encoding="utf-8")
    assert _SEONBI_EX not in p3, "Site6 고급선비복 고유명사 example 잔존"


def test_w1b_g_residue_location_consistency():
    """G-residue: location_consistency 신규 system.md 에 환경상태 닫힌 영문 토큰 목록 부재."""
    sysmd = (_latest_dir("location_consistency") / "system.md").read_text(encoding="utf-8")
    assert _WEATHER_TOKENS not in sysmd, "Site7 날씨 닫힌 토큰 목록 잔존"
    assert _TIME_TOKENS not in sysmd, "Site7 시간 닫힌 토큰 목록 잔존"


def test_w1b_g_intent_preserved():
    """G-intent: 의상 변화 배정 의도 / 병합 판별 의도 / 환경상태 제외 의도 보존."""
    p2 = (_latest_dir("outlook_extractor") / "phase2.md").read_text(encoding="utf-8")
    assert "최대 2개 아웃룩 배정" in p2, "의상변화 2-아웃룩 배정 의도 손실"
    p3 = (_latest_dir("outlook_extractor") / "phase3.md").read_text(encoding="utf-8")
    assert "병합" in p3, "병합 판별 의도 손실"
    sysmd = (_latest_dir("location_consistency") / "system.md").read_text(encoding="utf-8")
    assert "절대 포함 금지" in sysmd, "환경상태 제외 의도 손실"


def test_w1b_g_sibling():
    """G-sibling: outlook_extractor 미수정 4 stem + location_consistency 미수정 1 stem byte-identical."""
    ov = _list_module_versions("outlook_extractor")
    on_d, oo_d = _PROMPTS / "outlook_extractor" / ov[0], _PROMPTS / "outlook_extractor" / ov[1]
    for stem in ("phase1_schema.json", "phase1.md", "phase2_schema.json", "phase3_schema.json"):
        assert _sha256(on_d / stem) == _sha256(oo_d / stem), f"outlook sibling drift: {stem}"
    lv = _list_module_versions("location_consistency")
    ln_d, lo_d = _PROMPTS / "location_consistency" / lv[0], _PROMPTS / "location_consistency" / lv[1]
    assert _sha256(ln_d / "location_schema.json") == _sha256(lo_d / "location_schema.json")


def test_w1b_g_activation():
    """G-activation: loader 가 outlook_extractor v13 / location_consistency v3 active resolve."""
    assert get_effective_source("outlook_extractor", "phase2")["module_pack"]["latest"].startswith("13.")
    assert get_effective_source("location_consistency", "system")["module_pack"]["latest"].startswith("3.")


def test_w1b_g_registry():
    """G-registry: location_consistency provenance v3 sync."""
    assert _MODULE_INFO["location_consistency"]["prompt_dependency"] == "location_consistency/v3"
    assert _MODULE_INFO["location_consistency"]["updated_at"] == "2026-05-20"
    assert MODULE_VERSIONS["location_consistency"] == "1.2.0"
```

- [ ] **Step 3: W1b G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w1b -v`
Expected: `g_residue` / `g_activation` / `g_registry` FAIL.

- [ ] **Step 4: outlook_extractor v13 + location_consistency v3 dir 생성 (Procedure P — 단일 bash 블록)**

Procedure P — outlook_extractor (M=`outlook_extractor`, D_old=`12.202605181928`, N+1=`13`, E=`{phase2.md, phase3.md}`, S=6) + location_consistency (M=`location_consistency`, D_old=`2.202604201230`, N+1=`3`, E=`{system.md}`, S=2). **양 module dir 를 단일 bash 블록에서 생성** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/outlook_extractor/13.$UTC prompts/_base/location_consistency/3.$UTC
cp -r prompts/_base/outlook_extractor/12.202605181928/. prompts/_base/outlook_extractor/13.$UTC/
cp -r prompts/_base/location_consistency/2.202604201230/. prompts/_base/location_consistency/3.$UTC/
ls -d prompts/_base/outlook_extractor/13.* prompts/_base/location_consistency/3.*   # 실제 dir 명 readback
```
`prompts/_base/outlook_extractor/13.<readback>/phase2.md` 편집 — lines 15-17 치환 (P-5 N/A):
```
4. **한 씬에서 의상이 바뀌는 경우** (변신, 환복, 갑옷 착용 등): 같은 인물에 최대 2개 아웃룩 배정 가능
   - 예: 평상복→전투복, 정장→피의갑옷 등
   - 의상 변화가 없으면 1개만 배정
```
→
```
4. **한 씬에서 의상이 바뀌는 경우** (씬 도중 복장·외형이 전환되는 모든 경우): 같은 인물에 최대 2개 아웃룩 배정 가능
   - 예: 한 씬 안에서 인물이 다른 복장으로 갈아입거나 외형이 전환되는 경우
   - 의상 변화가 없으면 1개만 배정
```
`phase3.md` 편집 — line 6 치환:
```
- 이름만 다르고 설명이 거의 같은 아웃룩 (예: "고급선비복1"과 "고급선비복2"가 색상/재질까지 동일)
```
→
```
- 이름만 다르고 설명이 거의 같은 아웃룩 (예: 번호만 다른 두 아웃룩이 색상·재질까지 동일한 경우)
```

- [ ] **Step 5: location_consistency v3 system.md 편집**

location_consistency v3 dir 는 Step 4 단일 블록에서 생성됨. `prompts/_base/location_consistency/3.<readback>/system.md` 편집 — lines 24-30 치환 (P-5 N/A — system.md 에 version header 없음):
```
- 날씨 (rain, storm, clear sky, snow, fog)
- 시간 (dawn, noon, dusk, night, golden hour)
- 조명 (bright sunlight, neon glow, lamp light, darkness)
- 대기 상태 (mist, smoke, haze)
- 인물·차량·동물 (씬마다 등장인원이 다름)
- 감정·분위기 (tense, calm, ominous)
- 순간적 상태 (door open, lights flickering, table scattered)
```
→
```
- 날씨 — 씬마다 달라지는 기상 조건
- 시간대 — 하루 중 시점
- 조명 — 광원의 종류·세기·색
- 대기 상태 — 공기 중 가시 입자나 흐림
- 인물·차량·동물 — 씬마다 등장 대상이 다름
- 감정·분위기 — 장면의 정서적 톤
- 순간적 상태 — 그 한 순간에만 해당하는 가변 상태
```
**lines 41-57 (example block) + line 18 (재료 example) = boundary-reviewed KEEP (OQ-PLAN-1 RESOLVED, Codex APPROVED) — 미수정 (같은 stem 내 미편집 영역). FIX 대상은 lines 24-30 뿐.**

- [ ] **Step 6: version_registry sync — location_consistency (§P-reg)**

`backend/app/core/version_registry.py`:
- `MODULE_VERSIONS["location_consistency"]` line 37: `"1.1.0"` → `"1.2.0"`, 주석에 ` + 2026-05-20 C8: v3 — 환경상태 닫힌 영문 토큰 목록 추상화` append.
- `_MODULE_INFO["location_consistency"]` lines 133-136: `prompt_dependency` `"location_consistency/v2"` → `"location_consistency/v3"`, `updated_at` `"2026-04-20"` → `"2026-05-20"`.

- [ ] **Step 7: W1b G-test 실행 → PASS + baseline regression + strict schema gate**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k "w1a or w1b" -v` → 전부 PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py -q` → PASS.
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (outlook_extractor v13 / location_consistency v3 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 8: W1b commit**

```bash
git add prompts/_base/outlook_extractor/13.* prompts/_base/location_consistency/3.* backend/app/core/version_registry.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W1b(c8-area7-residual-hygiene-v1): outlook_extractor v13 + location_consistency v3 prompt-pack — 의상변화 시나리오 example·고유명사 example·환경상태 닫힌 영문 토큰 목록 추상화 (Site 5/6/7) + version_registry location_consistency provenance sync + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 9: Codex per-wave review** — `APPROVED_FOR_W2a_ENTRY` 수령 후 진입.

---

## Task W2a: shot_extract v14 prompt-pack (Site 8, 9, 10 — A069 cluster) — version_registry provenance

**Files:**
- Create: `prompts/_base/shot_extract/14.<UTC>/` (cp from `13.202605201049/`, **2 stems only** — system.md, user.md; shot_schema.json 미복사)
- Modify: `prompts/_base/shot_extract/14.<UTC>/user.md` (Site 8 — line 65 인종 example + OQ-PLAN-2 변형 block lines 50-57)
- Modify: `prompts/_base/shot_extract/14.<UTC>/system.md` (Site 9 — line 32, Site 10 A069 — lines 37-47)
- Modify: `backend/app/core/version_registry.py` (key `shot_extractor` — §P-reg)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **OQ-PLAN-2 — RESOLVED (Codex plan review: `APPROVED_WITH_TEST_GUARD`):** spec §2.2 Site 10 = `system.md:37-47` 만 열거하나 `user.md:46-66` 에 동일 구조의 변형 기준 block 이 닫힌 목록을 담음 (line 51 `나이 변화, 변장/성형 전후`, line 52 `인간→동물, 인간→비인간형`, line 55 `부상, 출혈, 창백해짐, 피멍, 화상`, line 56 `눈 색, 비늘, 손톱/송곳니, 꼬리`, line 57 `"해골처럼", "짐승 같은"`). **W2a 는 user.md:46-66 변형 block 을 system.md:37-47 과 동일 원칙으로 반드시 함께 추상화** — 같은 module·wave consistency (scope creep 아님). G-test 가 user.md 변형 block residue 를 강제 (Step 2 `test_w2a_g_residue_user_transformation_block`) — system.md 만 고치고 user.md 를 stale 로 남기면 G-test FAIL.

> **§8 boundary (C5 overlap):** shot_extract v13 = C5 가 시간연결어 (user.md line 18, system.md lines 14-17) 추상화. C8 v14 는 user.md:65 (인종 example) + user.md:46-66 (변형 block, OQ-PLAN-2) + system.md:32 (인간형 sub-type) + system.md:37-47 (A069) 를 touch — **C5 가 추상화한 시간연결어 섹션 (user.md line 18, system.md lines 14-17) 은 byte-identical 보존** (G-boundary `test_w2a_g_boundary_c5_time_connective_preserved` 로 검증). C8 closure 는 A069 만 closed claim, C5 action-segment 재-claim 금지.

- [ ] **Step 1: W0 재검증 + DB-shadow check** — `ls prompts/_base/shot_extract/` latest = `13.202605201049` 확인. **shot_schema.json 이 v13 dir 에 부재** 재확인 (`ls prompts/_base/shot_extract/13.202605201049/` → system.md, user.md 만). §0.3-(1) DB-shadow 명령 실행 — `shot_extract` system/user 의 `candidates.db = None` 확인. user.md 변형 block (lines 46-66) + system.md A069 block (lines 37-47) 의 현재 라인 위치 재확인 (drift 시 실제 위치 SSOT). OQ-PLAN-2 = RESOLVED (위 box) — user.md 변형 block 추상화 의무.

- [ ] **Step 2: W2a G-test 작성** (append)

```python
# ──────────────── W2a — shot_extract v14 (Site 8,9,10 — A069) ────────────────

# 닫힌 목록 needle (split-string)
_RACE_EX = '"한국인 경찰"' + ', "동양인 노파"'
_HUMANOID_LIST = "외계인/" + "귀신/돌연변이"
_A069_STATE_LIST = "부상, 얼룩, " + "창백해짐, 멍, 화상"
# OQ-PLAN-2 — user.md 변형 block 닫힌 목록 needle (split-string)
_USER_FULLTRANSFORM = "인간→동물, " + "인간→비인간형"
_USER_STATE_LIST = "부상, 출혈, " + "창백해짐, 피멍, 화상"
_USER_BODYPART_LIST = "눈 색, 비늘, " + "손톱/송곳니, 꼬리"
_USER_METAPHOR_EX = '"해골처럼", ' + '"짐승 같은"'


def test_w2a_g_residue_user_race_example():
    """G-residue: shot_extract v14 user.md 에 인종 시나리오 example 부재."""
    user = (_latest_dir("shot_extract") / "user.md").read_text(encoding="utf-8")
    assert _RACE_EX not in user, "Site8 인종 example 잔존"


def test_w2a_g_residue_user_transformation_block():
    """G-residue (OQ-PLAN-2): shot_extract v14 user.md 변형 block 닫힌 목록 부재."""
    user = (_latest_dir("shot_extract") / "user.md").read_text(encoding="utf-8")
    assert _USER_FULLTRANSFORM not in user, "user.md 완전변형 닫힌 목록 잔존"
    assert _USER_STATE_LIST not in user, "user.md 일시적 상태 닫힌 목록 잔존"
    assert _USER_BODYPART_LIST not in user, "user.md 부분변화 닫힌 목록 잔존"
    assert _USER_METAPHOR_EX not in user, "user.md 비유적 표현 example 잔존"


def test_w2a_g_residue_system_humanoid_a069():
    """G-residue: shot_extract v14 system.md 에 인간형 sub-type + A069 닫힌 목록 부재."""
    sysmd = (_latest_dir("shot_extract") / "system.md").read_text(encoding="utf-8")
    assert _HUMANOID_LIST not in sysmd, "Site9 인간형 sub-type 닫힌 목록 잔존"
    assert _A069_STATE_LIST not in sysmd, "Site10 A069 일시적 상태 닫힌 목록 잔존"


def test_w2a_g_boundary_c5_time_connective_preserved():
    """G-boundary: C5 가 추상화한 시간연결어 wording 이 v14 에 byte-보존 (재-claim 금지)."""
    user = (_latest_dir("shot_extract") / "user.md").read_text(encoding="utf-8")
    sysmd = (_latest_dir("shot_extract") / "system.md").read_text(encoding="utf-8")
    needle = "시간 흐름을 나타내는 표현(언어·형태 불문)"
    assert needle in user, "C5 시간연결어 추상화 wording 손실(user)"
    assert needle in sysmd, "C5 시간연결어 추상화 wording 손실(system)"


def test_w2a_g_intent_preserved():
    """G-intent: 인종/국적 포함 의도 + 변형 판정 의도 보존."""
    sysmd = (_latest_dir("shot_extract") / "system.md").read_text(encoding="utf-8")
    assert "인종/국적을 반드시 포함" in sysmd, "인종/국적 포함 의도 손실"
    assert "변형 기준" in sysmd, "변형 기준 섹션 손실"
    assert "완전변형" in sysmd, "완전변형 판정 의도 손실"


def test_w2a_g_activation():
    """G-activation: shot_extract v14 active + shot_schema.json 은 v10 per-stem fallback."""
    src = get_effective_source("shot_extract", "system")
    assert src["module_pack"]["latest"].startswith("14."), "shot_extract v14 미활성"
    # shot_schema.json (C5 OPTION_1) — v14 에 부재, _resolve 가 v10 으로 fallback
    _path, found_ver, _vers = _resolve_stem_in_pack("shot_extract", "shot_schema", ext=".json")
    assert found_ver.startswith("10."), f"shot_schema.json per-stem fallback v10 아님: {found_ver}"


def test_w2a_g_registry():
    """G-registry: shot_extractor provenance v14 sync."""
    assert _MODULE_INFO["shot_extractor"]["prompt_dependency"] == "shot_extract/v14"
    assert MODULE_VERSIONS["shot_extractor"] == "2.3.0"
```

- [ ] **Step 3: W2a G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w2a -v`
Expected: `g_residue` / `g_activation` / `g_registry` FAIL. `g_boundary` PASS (C5 wording 현존).

- [ ] **Step 4: shot_extract v14 prompt-pack 생성 (Procedure P)**

Procedure P — M=`shot_extract`, D_old=`13.202605201049`, N+1=`14`, E=`{system.md, user.md}`, S=2 stems. **shot_schema.json 미복사 — `cp -r` 가 v13 dir 의 실재 stem 만 복사 (v13 에 shot_schema.json 부재이므로 자동으로 제외).** **단일 bash 블록** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/shot_extract/14.$UTC
cp -r prompts/_base/shot_extract/13.202605201049/. prompts/_base/shot_extract/14.$UTC/
ls -d prompts/_base/shot_extract/14.*           # 실제 dir 명 readback
ls prompts/_base/shot_extract/14.*/             # system.md user.md 두 개만 — shot_schema.json 없어야 정상
```
`prompts/_base/shot_extract/14.<readback>/user.md` 편집 — line 65 치환:
```
- 인물 목록에 없는 인물을 묘사할 때, 인간형이면 인종/국적을 반드시 포함 (예: "한국인 경찰", "동양인 노파")
```
→
```
- 인물 목록에 없는 인물을 묘사할 때, 인간형이면 인종/국적을 반드시 포함 (외형으로 식별 가능한 인종·국적을 보통명사 형용사로)
```
`system.md` 편집 — line 32 치환:
```
- 인물 목록에 없는 인물(엑스트라)을 묘사할 때, 인간형(외계인/귀신/돌연변이 포함)이면 인종/국적을 반드시 포함
```
→
```
- 인물 목록에 없는 인물(엑스트라)을 묘사할 때, 인간형(인간 또는 인간을 닮은 형상의 존재)이면 인종/국적을 반드시 포함
```
`system.md` 편집 — Site 10 A069, lines 39/43/44 치환 (38·40·42·45·46·47 보존):
```
- 얼굴이 크게 달라지는 변화: 나이 변화, 분장/변장 전후, 외모가 근본적으로 바뀌는 상황 (원문에 명시된 경우만)
```
→
```
- 얼굴이 크게 달라지는 변화: 인물의 정체를 다시 인식해야 할 만큼 얼굴 외형이 근본적으로 바뀌는 경우 (원문에 명시된 경우만)
```
그리고:
```
- 일시적 상태: 부상, 얼룩, 창백해짐, 멍, 화상 등
- 부분적 변화: 눈 색, 신체 일부의 일시적 변화, 표피 변화
```
→
```
- 일시적·가역적 신체 상태 (장면 단위로 나타났다 사라지는 표면적 외형 변화)
- 부분적 변화: 신체 일부에 국한된 일시적 외형 변화
```
**OQ-PLAN-2 RESOLVED — `user.md` 변형 block (lines 50-57) 도 반드시 동일 원칙으로 치환** (G-test `test_w2a_g_residue_user_transformation_block` 강제 — system.md 만 고치고 user.md 를 stale 로 남기면 wave FAIL):
```
- 얼굴이 크게 달라지는 변화: 나이 변화, 변장/성형 전후, rule_type=possession 등 VWR SOT 가 가리키는 외형 변화 상태
- 완전변형: 인간→동물, 인간→비인간형
```
→
```
- 얼굴이 크게 달라지는 변화: 인물의 정체를 다시 인식해야 할 만큼 얼굴 외형이 근본적으로 바뀌거나, VWR SOT 가 가리키는 외형 변화 상태
- 완전변형: 종·형상 자체가 인간이 아닌 형태로 바뀌는 경우
```
및
```
- 일시적 상태: 부상, 출혈, 창백해짐, 피멍, 화상
- 부분적 변화: 눈 색, 비늘, 손톱/송곳니, 꼬리
- 비유적 표현: "해골처럼", "짐승 같은"
```
→
```
- 일시적·가역적 신체 상태 (장면 단위로 나타났다 사라지는 표면적 외형 변화)
- 부분적 변화: 신체 일부에 국한된 일시적 외형 변화
- 비유적 표현
```

- [ ] **Step 5: version_registry sync — shot_extractor (§P-reg)**

`backend/app/core/version_registry.py`:
- `MODULE_VERSIONS["shot_extractor"]` line 29: `"2.2.0"` → `"2.3.0"`, 주석 갱신 — `2026-05-20 — C8 v1: v14 prompt — 인종 example·인간형 sub-type·A069 변형 판정 닫힌 목록 추상화. v14 = system.md+user.md only; shot_schema.json 은 v10 per-stem fallback 유지 (C5 OPTION_1). 이전: 2.2.0 v13.`
- `_MODULE_INFO["shot_extractor"]` lines 105-108: `prompt_dependency` `"shot_extract/v13"` → `"shot_extract/v14"`, `updated_at` `"2026-05-20"` 유지.

- [ ] **Step 6: W2a G-test 실행 → PASS + baseline regression + strict schema gate**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w2a -v` → PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c5_action_segment_sot.py tests/test_prompt_versions.py -q` → PASS (C5 회귀 0).
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (shot_extract v14 가 shot_schema.json 미포함 — v10 per-stem fallback 이 새로 strict 노출되지 않음 증명). FAIL 시 STOP + Codex consult.

- [ ] **Step 7: W2a commit**

```bash
git add prompts/_base/shot_extract/14.* backend/app/core/version_registry.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W2a(c8-area7-residual-hygiene-v1): shot_extract v14 prompt-pack — 인종 example·인간형 sub-type·A069 변형 판정 닫힌 목록 추상화 (Site 8/9/10, generic principle, C5 시간연결어 byte-보존) + version_registry shot_extractor provenance sync + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 8: Codex per-wave review** — `APPROVED_FOR_W2b_ENTRY` 수령 후 진입.

---

## Task W2b: shot_essence_extraction + shot_selection + scene_extractor_v2 prompt-pack (Site 11, 12, 13, 14)

**Files:**
- Create: `prompts/_base/shot_essence_extraction/4.<UTC>/` (cp from `3.202605081814/`, 2 stems)
- Modify: `prompts/_base/shot_essence_extraction/4.<UTC>/system.md` (Site 11 — lines 13/83, Site 12 — line 66)
- Create: `prompts/_base/shot_selection/5.<UTC>/` (cp from `4.202604191600/`, 3 stems)
- Modify: `prompts/_base/shot_selection/5.<UTC>/system.md` (Site 13 — lines 60-64)
- Create: `prompts/_base/scene_extractor_v2/21.<UTC>/` (cp from `20.202605190631/`, 5 stems)
- Modify: `prompts/_base/scene_extractor_v2/21.<UTC>/system.md` (Site 14 — line 47)
- Modify: `backend/app/core/version_registry.py` (key `shot_selector` — §P-reg)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **Site 11 note:** spec §2.2 = `system.md:13 (+ 56 반복)`. 닫힌 목록 `혈흔/깨진유리/부상흔적` 은 line 13 + line 83 양쪽에 나타남 — closed list 를 실제 제거하려면 양쪽 정리 의무 (line 13 = `(혈흔, 깨진 유리, 부상 흔적)`, line 83 = `혈흔 / 깨진 유리 / 부상 흔적`). line 56 (`핏자국`) 은 worked-example 내부 → Site 12 narrow FIX 가 포괄. **W2b 는 line 13 + 83 (Site 11) + line 66 (Site 12) 처리.**
> **Site 12 OQ-C8-B narrow:** worked-example block (44-65) 의 구조는 보존 — case A/B/C, ❌/✓ 분류 패턴 모두 유지. 시나리오 고유 noun `인형 백팩` (line 66) 만 범용 noun 으로 치환. case A 의 `여인/거실/깨진 창문/달빛/핏자국` = 범용 보통명사이므로 보존.
> **scene_extractor_v2 Group F:** `test_prompt_versions.py` Group F 는 `startswith("19.")` (latest-resolving) + `turn_scene_detail` content marker 검사 — C8 은 turn_scene_detail.md 미수정이므로 v21 bump 후에도 PASS. test 명에 "v19" 가 남으나 historical naming (C8 결함 아님) — 갱신은 scope creep, 미변경.

- [ ] **Step 1: W0 재검증 + DB-shadow** — 3 module latest 확인 (`shot_essence_extraction/3.x`, `shot_selection/4.x`, `scene_extractor_v2/20.x`). §0.3-(1) DB-shadow — `shot_essence_extraction` / `shot_selection` 의 `candidates.db = None` 확인. §0.3-(2) test-pin grep — scene_extractor_v2 Group F + Area7b pin 분류 재확인.

- [ ] **Step 2: W2b G-test 작성** (append)

```python
# ──────────────── W2b — shot_essence + shot_selection + scene_extractor_v2 (Site 11-14) ────────────────

# 닫힌 목록 / 시나리오 needle (split-string)
_TRACE_LIST = "혈흔, 깨진 " + "유리, 부상 흔적"
_DOLL_BAG = "인형 " + "백팩"
_LINK_CAT_VEHICLE = "교통수단 이용 " + "장면"
_MONTAGE_MARKER = "<몽" + "타주>"


def test_w2b_g_residue_shot_essence():
    """G-residue: shot_essence v4 system.md 에 사건흔적 닫힌 목록 + 인형 백팩 부재."""
    sysmd = (_latest_dir("shot_essence_extraction") / "system.md").read_text(encoding="utf-8")
    assert _TRACE_LIST not in sysmd, "Site11 사건흔적 닫힌 목록 잔존"
    assert _DOLL_BAG not in sysmd, "Site12 인형 백팩 시나리오 noun 잔존"


def test_w2b_g_residue_shot_selection():
    """G-residue: shot_selection v5 system.md 에 연결순간 카테고리 닫힌 목록 부재."""
    sysmd = (_latest_dir("shot_selection") / "system.md").read_text(encoding="utf-8")
    assert _LINK_CAT_VEHICLE not in sysmd, "Site13 교통수단 카테고리 example 잔존"


def test_w2b_g_residue_scene_extractor_montage():
    """G-residue: scene_extractor_v2 v21 system.md 에 <몽타주> literal marker 부재."""
    sysmd = (_latest_dir("scene_extractor_v2") / "system.md").read_text(encoding="utf-8")
    assert _MONTAGE_MARKER not in sysmd, "Site14 <몽타주> literal marker 잔존"


def test_w2b_g_intent_preserved():
    """G-intent: essence 분류 / ROI 선택 / montage 분류 의도 보존."""
    se = (_latest_dir("shot_essence_extraction") / "system.md").read_text(encoding="utf-8")
    assert "영구 흔적" in se, "사건 영구 흔적 → essence 의도 손실"
    assert "잘못된 예시" in se or "올바른 분류" in se, "worked-example 구조 손실"
    ss = (_latest_dir("shot_selection") / "system.md").read_text(encoding="utf-8")
    assert "선택하지 말 것" in ss, "shot_selection 비선택 원칙 손실"
    sx = (_latest_dir("scene_extractor_v2") / "system.md").read_text(encoding="utf-8")
    assert "montage:" in sx, "montage 씬 유형 분류 손실"


def test_w2b_g_sibling():
    """G-sibling: 미수정 stem byte-identical (shot_essence schema.json / shot_selection user.md+schema / scene_extractor 4 stem)."""
    sev = _list_module_versions("shot_essence_extraction")
    assert _sha256(_PROMPTS / "shot_essence_extraction" / sev[0] / "schema.json") == \
           _sha256(_PROMPTS / "shot_essence_extraction" / sev[1] / "schema.json")
    ssv = _list_module_versions("shot_selection")
    for stem in ("user.md", "selection_schema.json"):
        assert _sha256(_PROMPTS / "shot_selection" / ssv[0] / stem) == \
               _sha256(_PROMPTS / "shot_selection" / ssv[1] / stem), f"shot_selection sibling drift: {stem}"
    sxv = _list_module_versions("scene_extractor_v2")
    for stem in ("scene_detail_schema.json", "turn_scene_detail.md", "turn0_context.md", "turn1_split_long.md"):
        assert _sha256(_PROMPTS / "scene_extractor_v2" / sxv[0] / stem) == \
               _sha256(_PROMPTS / "scene_extractor_v2" / sxv[1] / stem), f"scene_extractor sibling drift: {stem}"


def test_w2b_g_activation():
    """G-activation: 3 module 신규 version active resolve."""
    assert get_effective_source("shot_essence_extraction", "system")["module_pack"]["latest"].startswith("4.")
    assert get_effective_source("shot_selection", "system")["module_pack"]["latest"].startswith("5.")
    assert get_effective_source("scene_extractor_v2", "system")["module_pack"]["latest"].startswith("21.")


def test_w2b_g_registry():
    """G-registry: shot_selector provenance v5 sync."""
    assert _MODULE_INFO["shot_selector"]["prompt_dependency"] == "shot_selection/v5"
    assert MODULE_VERSIONS["shot_selector"] == "4.1.0"
```

- [ ] **Step 3: W2b G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w2b -v`
Expected: `g_residue` 3개 / `g_activation` / `g_registry` FAIL.

- [ ] **Step 4: 3 module 신규 dir 생성 (Procedure P — 단일 bash 블록) + shot_essence_extraction v4 편집**

Procedure P — shot_essence_extraction (D_old=`3.202605081814`, N+1=`4`, E=`{system.md}`, S=2) + shot_selection (D_old=`4.202604191600`, N+1=`5`, E=`{system.md}`, S=3) + scene_extractor_v2 (D_old=`20.202605190631`, N+1=`21`, E=`{system.md}`, S=5). **3 module dir 를 단일 bash 블록에서 생성** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/shot_essence_extraction/4.$UTC prompts/_base/shot_selection/5.$UTC prompts/_base/scene_extractor_v2/21.$UTC
cp -r prompts/_base/shot_essence_extraction/3.202605081814/. prompts/_base/shot_essence_extraction/4.$UTC/
cp -r prompts/_base/shot_selection/4.202604191600/. prompts/_base/shot_selection/5.$UTC/
cp -r prompts/_base/scene_extractor_v2/20.202605190631/. prompts/_base/scene_extractor_v2/21.$UTC/
ls -d prompts/_base/shot_essence_extraction/4.* prompts/_base/shot_selection/5.* prompts/_base/scene_extractor_v2/21.*   # 실제 dir 명 readback
```
`prompts/_base/shot_essence_extraction/4.<readback>/system.md` 편집 — line 13 치환 (Site 11):
```
- **사건의 영구 흔적** — 사망/부상/파손 결과로 남은 흔적은 essence (혈흔, 깨진 유리, 부상 흔적). chain bg는 일관성만 보장하지 일회성 사건 흔적은 책임지지 않습니다
```
→
```
- **사건의 영구 흔적** — 사망·부상·파손 등 사건의 결과로 그 장소에 남은 물리적 흔적은 essence. chain bg는 일관성만 보장하지 일회성 사건 흔적은 책임지지 않습니다
```
line 83 치환 (Site 11 반복분):
```
- 그 인물 옆 **혈흔** / 깨진 유리 / 부상 흔적 → **essence** (사건의 영구 흔적, chain bg가 처리 X)
```
→
```
- 그 인물 옆에 남은 사건의 물리적 흔적 (부상·파손의 결과로 생긴 흔적) → **essence** (사건의 영구 흔적, chain bg가 처리 X)
```
line 66 치환 (Site 12 — OQ-C8-B narrow, `인형 백팩` → 범용 noun, worked-example 구조 보존):
```
- essence: ["여인이 인형 백팩을 들어 올린다"]
```
→
```
- essence: ["여인이 가방을 들어 올린다"]
```

- [ ] **Step 5: shot_selection v5 system.md 편집**

shot_selection v5 dir 는 Step 4 단일 블록에서 생성됨. `prompts/_base/shot_selection/5.<readback>/system.md` 편집 — lines 60-64 치환 (Site 13 — 닫힌 카테고리 4개 → generic principle 2줄, mini-list 재도입 금지):
```
## 선택하지 말 것 (판단 예시 — 범용 원칙)
- 인물 A가 인물 B에게 다가가 접촉하는 장면 → "접촉 순간"만 의미 있음. "접촉 직전"은 제외
- 추격/달리기 장면 → 1-2 대표 순간만 (출발, 따라잡힘 등 서사 전환점)
- 교통수단 이용 장면 → 타는 순간, 내리는 순간, 예기치 못한 사건(충돌·만남·목격) 같은 전환점만. 단순 이동은 제외
- 작업 수행 장면 → 작업 전체를 쪼개지 말고, 완료/실패/중단의 전환 순간만
```
→
```
## 선택하지 말 것 (범용 원칙)
- 어떤 동작이든 그 시작 직전·직후의 준비/연결 순간은 그 자체로 서사 전환점이 아니면 제외
- 시간에 걸쳐 진행되는 활동은 전체를 잘게 쪼개지 말고, 그 안에서 의미가 전환되는 순간(전환점·결과·예기치 못한 사건)만 선택
```

- [ ] **Step 6: scene_extractor_v2 v21 system.md 편집**

scene_extractor_v2 v21 dir 는 Step 4 단일 블록에서 생성됨. `prompts/_base/scene_extractor_v2/21.<readback>/system.md` 편집 — line 47 치환 (Site 14):
```
- montage: 여러 짧은 장면의 연속 (<몽타주> 표시 또는 빠른 컷 전환)
```
→
```
- montage: 여러 짧은 장면의 연속 (명시적 몽타주/장면 모음 지시 또는 빠른 컷 전환)
```

- [ ] **Step 7: version_registry sync — shot_selector (§P-reg)**

`backend/app/core/version_registry.py`:
- `MODULE_VERSIONS["shot_selector"]` line 31: `"4.0.0"` → `"4.1.0"`, 주석에 ` + 2026-05-20 C8: v5 — 연결순간 카테고리 닫힌 목록 추상화` append.
- `_MODULE_INFO["shot_selector"]` lines 113-116: `prompt_dependency` `"shot_selection/v4"` → `"shot_selection/v5"`, `updated_at` `"2026-04-19"` → `"2026-05-20"`.

- [ ] **Step 8: W2b G-test 실행 → PASS + baseline regression + strict schema gate**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w2b -v` → PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py tests/integration/test_area_7b_no_drift.py -q` → PASS (Group F + Area7b 회귀 0).
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (shot_essence v4 / shot_selection v5 / scene_extractor_v2 v21 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 9: W2b commit**

```bash
git add prompts/_base/shot_essence_extraction/4.* prompts/_base/shot_selection/5.* prompts/_base/scene_extractor_v2/21.* backend/app/core/version_registry.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W2b(c8-area7-residual-hygiene-v1): shot_essence_extraction v4 + shot_selection v5 + scene_extractor_v2 v21 prompt-pack — 사건흔적 닫힌 목록·worked-example 시나리오 noun·연결순간 카테고리·<몽타주> literal marker 추상화 (Site 11/12/13/14) + version_registry shot_selector provenance sync + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 10: Codex per-wave review** — `APPROVED_FOR_W3a_ENTRY` 수령 후 진입.

---

## Task W3a: t2i_visual_converter + background_master_plan + floor_plan_prompt prompt-pack (Site 15, 17, 18) — code-path constant

**Files:**
- Create: `prompts/_base/t2i_visual_converter/v5/` (cp from `v4/`, 1 stem)
- Modify: `prompts/_base/t2i_visual_converter/v5/system.md` (Site 15 — lines 45-49)
- Modify: `backend/app/modules/t2i_visual_converter.py:11` (`PROMPT_DIR` `v4`→`v5`)
- Create: `prompts/_base/background_master_plan/4.<UTC>/` (cp from `3.202605092023/`, 3 stems)
- Modify: `prompts/_base/background_master_plan/4.<UTC>/system.md` (Site 17 — lines 74-78)
- Modify: `backend/app/core/steps/background_master_plan_step.py:40` (`PROMPT_VERSION`)
- Create: `prompts/_base/floor_plan_prompt/5.<UTC>/` (cp from `4.202605091200/`, 3 stems)
- Modify: `prompts/_base/floor_plan_prompt/5.<UTC>/system.md` (Site 18 — line 16, header line 1)
- Modify: `backend/app/core/steps/floor_plan_prompt_step.py:29` (`PROMPT_VERSION`)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **§8 boundary:** Site 16 `background_chain_render` = KEEP (W3a edit 대상 아님). floor_plan_prompt A031 (`system.md:10-13` 색/glyph 매핑) = C4 boundary-reviewed KEEP — Site 18 (`:16`) edit 시 lines 10-13 byte-identical 보존. `floor_plan_prompt_step.py:22 SCHEMA_VERSION=2` 불변.

- [ ] **Step 1: W0 재검증** — 3 module latest 확인 (`t2i_visual_converter/v4`, `background_master_plan/3.x`, `floor_plan_prompt/4.x`). code 상수 현재값 확인: `grep -n "PROMPT_DIR\|v4" backend/app/modules/t2i_visual_converter.py` / `grep -n "PROMPT_VERSION" backend/app/core/steps/background_master_plan_step.py backend/app/core/steps/floor_plan_prompt_step.py`. floor_plan system.md line 1 header `(v4 / D6)` + A031 lines 10-13 위치 확인.

- [ ] **Step 2: W3a G-test 작성** (append)

```python
# ──────────────── W3a — t2i / background / floor_plan (Site 15,17,18) ────────────────

_BACKEND = Path(__file__).resolve().parents[2]

# 시나리오 leak needle (split-string)
_SCIFI_EX = "지하 극저온 저장 " + "시설의 투명 원통형 캡슐"
_MART_GROUP = "bg_large" + "_mart"
_ONDOL_EX = "ondol-friendly " + "bed"


def test_w3a_g_residue_t2i():
    """G-residue: t2i_visual_converter v5 system.md 에 sci-fi 시나리오 example 부재."""
    sysmd = (_PROMPTS / "t2i_visual_converter" / "v5" / "system.md").read_text(encoding="utf-8")
    assert _SCIFI_EX not in sysmd, "Site15 sci-fi 시나리오 example 잔존"


def test_w3a_g_residue_background_master_plan():
    """G-residue: background_master_plan v4 system.md 에 구체 location ID example 부재."""
    sysmd = (_latest_dir("background_master_plan") / "system.md").read_text(encoding="utf-8")
    assert _MART_GROUP not in sysmd, "Site17 bg_large_mart 구체 group example 잔존"


def test_w3a_g_residue_floor_plan():
    """G-residue: floor_plan_prompt v5 system.md 에 region-specific 가구 example 부재."""
    sysmd = (_latest_dir("floor_plan_prompt") / "system.md").read_text(encoding="utf-8")
    assert _ONDOL_EX not in sysmd, "Site18 ondol-friendly bed region example 잔존"


def test_w3a_g_boundary_floor_plan_a031():
    """G-boundary: floor_plan_prompt A031 (색/glyph rule 2·5, lines 10-13) byte-보존."""
    sysmd = (_latest_dir("floor_plan_prompt") / "system.md").read_text(encoding="utf-8")
    assert "single flat pastel color" in sysmd, "A031 색 매핑 rule 2 손실"
    assert "symbolic glyphs" in sysmd, "A031 glyph rule 5 손실"


def test_w3a_g_intent_preserved():
    """G-intent: 콘티 단순성 / fp↔bg link / cultural-cues-derive 의도 보존."""
    t2i = (_PROMPTS / "t2i_visual_converter" / "v5" / "system.md").read_text(encoding="utf-8")
    assert "나쁜 예" in t2i and "좋은 예" in t2i, "콘티 나쁜/좋은 예 구조 손실"
    bmp = (_latest_dir("background_master_plan") / "system.md").read_text(encoding="utf-8")
    assert "depends_on_fp" in bmp, "fp↔bg link example 의도 손실"
    fp = (_latest_dir("floor_plan_prompt") / "system.md").read_text(encoding="utf-8")
    assert "do NOT use specific work nouns" in fp, "cultural-cues no-hardcoding 의도 손실"


def test_w3a_g_constant_t2i():
    """G-activation: t2i_visual_converter.py PROMPT_DIR 가 v5 로 bump."""
    code = (_BACKEND / "app" / "modules" / "t2i_visual_converter.py").read_text(encoding="utf-8")
    assert '"t2i_visual_converter" / "v5"' in code, "PROMPT_DIR v5 미bump"
    assert (_PROMPTS / "t2i_visual_converter" / "v5" / "system.md").exists(), "v5 dir 미생성"


def test_w3a_g_constant_background_floor():
    """G-activation: background_master_plan / floor_plan_prompt PROMPT_VERSION 가 신규 dir 와 일치."""
    bmp_code = (_BACKEND / "app" / "core" / "steps" / "background_master_plan_step.py").read_text(encoding="utf-8")
    bmp_latest = _list_module_versions("background_master_plan")[0]
    assert f'PROMPT_VERSION = "{bmp_latest}"' in bmp_code, "background_master_plan PROMPT_VERSION 불일치"
    assert bmp_latest.startswith("4."), "background_master_plan v4 미활성"
    fp_code = (_BACKEND / "app" / "core" / "steps" / "floor_plan_prompt_step.py").read_text(encoding="utf-8")
    fp_latest = _list_module_versions("floor_plan_prompt")[0]
    assert f'PROMPT_VERSION = "{fp_latest}"' in fp_code, "floor_plan_prompt PROMPT_VERSION 불일치"
    assert fp_latest.startswith("5."), "floor_plan_prompt v5 미활성"
    assert "SCHEMA_VERSION = 2" in fp_code, "floor_plan SCHEMA_VERSION 불변 위반"


def test_w3a_g_sibling():
    """G-sibling: background_master_plan / floor_plan_prompt 미수정 stem byte-identical."""
    bv = _list_module_versions("background_master_plan")
    for stem in ("schema.json", "user_template.md"):
        assert _sha256(_PROMPTS / "background_master_plan" / bv[0] / stem) == \
               _sha256(_PROMPTS / "background_master_plan" / bv[1] / stem), f"bmp sibling drift: {stem}"
    fv = _list_module_versions("floor_plan_prompt")
    for stem in ("schema.json", "user_template.md"):
        assert _sha256(_PROMPTS / "floor_plan_prompt" / fv[0] / stem) == \
               _sha256(_PROMPTS / "floor_plan_prompt" / fv[1] / stem), f"floor_plan sibling drift: {stem}"
```

- [ ] **Step 3: W3a G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w3a -v`
Expected: `g_residue` 3개 / `g_constant` 2개 FAIL.

- [ ] **Step 4: 3 module 신규 dir 생성 (Procedure P — 단일 bash 블록) + t2i_visual_converter v5 편집 + 코드 상수**

Procedure P — t2i_visual_converter (D_old=`v4`, vN form → `v5`, E=`{system.md}`, S=1) + background_master_plan (D_old=`3.202605092023`, N+1=`4`, E=`{system.md}`, S=3) + floor_plan_prompt (D_old=`4.202605091200`, N+1=`5`, E=`{system.md}`, S=3). **3 module dir 를 단일 bash 블록에서 생성** (P-1 UTC discipline):
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1
UTC=$(date -u +%Y%m%d%H%M)
mkdir -p prompts/_base/t2i_visual_converter/v5 prompts/_base/background_master_plan/4.$UTC prompts/_base/floor_plan_prompt/5.$UTC
cp -r prompts/_base/t2i_visual_converter/v4/. prompts/_base/t2i_visual_converter/v5/
cp -r prompts/_base/background_master_plan/3.202605092023/. prompts/_base/background_master_plan/4.$UTC/
cp -r prompts/_base/floor_plan_prompt/4.202605091200/. prompts/_base/floor_plan_prompt/5.$UTC/
ls -d prompts/_base/t2i_visual_converter/v5 prompts/_base/background_master_plan/4.* prompts/_base/floor_plan_prompt/5.*   # 실제 dir 명 readback — background/floor 의 PROMPT_VERSION 상수에 이 literal 사용
```
`prompts/_base/t2i_visual_converter/v5/system.md` 편집 — lines 46, 49 치환 (P-5: line 19 `(v5 — short_id 기반)` 은 pack 버전 아닌 내부 라벨 — 미변경):
```
"중년의 보안 요원이 검은 전술 조끼를 입고 지하 극저온 저장 시설의 투명 원통형 캡슐들 사이를 걸어가며, 산업용 파이프와 경고등과 디지털 디스플레이와 반사 바닥이 있는 디스토피아 분위기"
```
→
```
"한 인물이 특정 복장을 입고 여러 설비와 구조물이 빼곡한 공간을 가로지르며, 조명과 바닥과 장식까지 모두 나열한 과잉 묘사"
```
및
```
"[경비원]이 발광 장치들이 늘어선 어두운 시설 복도를 걸어간다, 산업적 조명, 와이드 숏"
```
→
```
"[인물]이 어두운 복도를 걸어간다, 산업적 조명, 와이드 숏"
```
`backend/app/modules/t2i_visual_converter.py:11` 편집:
```
    / "prompts" / "_base" / "t2i_visual_converter" / "v4"
```
→
```
    / "prompts" / "_base" / "t2i_visual_converter" / "v5"
```

- [ ] **Step 5: background_master_plan v4 system.md 편집 + 코드 상수**

background_master_plan v4 dir 는 Step 4 단일 블록에서 생성됨 (실제 dir 명 = Step 4 `ls` readback). `prompts/_base/background_master_plan/4.<readback>/system.md` 편집 — lines 74-78 치환 (P-5 N/A — line 1 header 에 version token 없음):
```
Example (group `bg_large_mart` covers L09 외부 + L10 매장 + L14 사무실):
- `fp_sales_floor` → loc_id=L10, space_key_hint=main
- `fp_exterior_entrance` → loc_id=L09, space_key_hint=main
- `fp_office` → loc_id=L14, space_key_hint=main
- Background `loc_id=L10/main/morning/quiet` → `depends_on_fp=["fp_sales_floor"]` (NOT `fp_exterior_entrance`).
```
→
```
Example (a group covering three distinct locations under one building group):
- `fp_main_hall` → loc_id=L01, space_key_hint=main
- `fp_entrance` → loc_id=L02, space_key_hint=main
- `fp_side_room` → loc_id=L03, space_key_hint=main
- Background `loc_id=L01/main/morning/quiet` → `depends_on_fp=["fp_main_hall"]` (NOT `fp_entrance`).
```
`backend/app/core/steps/background_master_plan_step.py:40` 편집 — `PROMPT_VERSION` 을 **실제 생성된 dir 명** (`4.$UTC`) 으로:
```
PROMPT_VERSION = "3.202605092023"
```
→
```
PROMPT_VERSION = "4.<생성된 UTC>"
```

- [ ] **Step 6: floor_plan_prompt v5 system.md 편집 + 코드 상수 + P-5 header**

floor_plan_prompt v5 dir 는 Step 4 단일 블록에서 생성됨 (실제 dir 명 = Step 4 `ls` readback). `prompts/_base/floor_plan_prompt/5.<readback>/system.md` **P-5 header 갱신** — line 1:
```
# Floor Plan Prompt — System (v4 / D6)
```
→
```
# Floor Plan Prompt — System (v5 / D6)
```
`system.md` 편집 — line 16 rule 8 치환 (lines 10-13 A031 byte-보존):
```
8. **Cultural cues derive (no hardcoding) — geometry only, NOT visual style**: analyze `visual_world_rules` (era + region + description) + `scene_segments` (verbatim) and reflect period/region-specific *layout* cues in `t2i_prompt` (typical room sizes, window/door placement conventions, furniture types appropriate to the era — e.g., low ondol-friendly bed vs western mattress; wall-mounted air-con position; built-in wardrobe on a specific wall). LLM must derive these from the data — do NOT use specific work nouns. Architectural style differences (Korean/Japanese/American/etc) appear as *layout convention*, NOT as photoreal rendering — the diagram still stays flat schematic.
```
→
```
8. **Cultural cues derive (no hardcoding) — geometry only, NOT visual style**: analyze `visual_world_rules` (era + region + description) + `scene_segments` (verbatim) and reflect period/region-specific *layout* cues in `t2i_prompt` (typical room sizes, window/door placement conventions, and furniture types appropriate to the era and region the data specifies). LLM must derive these from the data — do NOT use specific work nouns and do NOT assume any particular culture. Architectural style differences across regions appear as *layout convention*, NOT as photoreal rendering — the diagram still stays flat schematic.
```
`backend/app/core/steps/floor_plan_prompt_step.py:29` 편집 — `PROMPT_VERSION` 을 실제 dir 명 (`5.$UTC`) 으로 (`SCHEMA_VERSION = 2` line 22 불변):
```
PROMPT_VERSION = "4.202605091200"
```
→
```
PROMPT_VERSION = "5.<생성된 UTC>"
```

- [ ] **Step 7: W3a G-test 실행 → PASS + baseline regression + strict schema gate**

먼저 code 상수 동치 검증 (Codex plan review NARROW 2): `grep -n "PROMPT_VERSION" backend/app/core/steps/background_master_plan_step.py backend/app/core/steps/floor_plan_prompt_step.py` 결과의 값이 Step 4 `ls` readback 의 실제 dir 명과 정확히 일치하는지 확인.
Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w3a -v` → PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py -q && PYTHONPATH=. .venv/bin/python -c "import app.modules.t2i_visual_converter"` → import 성공 (PROMPT_DIR 유효).
Run strict schema gate (§0.3-(5)): `PYTHONPATH=. .venv/bin/python -m pytest tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible -q` → PASS (background_master_plan v4 / floor_plan_prompt v5 schema sibling strict 위반 미노출). FAIL 시 STOP + Codex consult.

- [ ] **Step 8: W3a commit**

```bash
git add prompts/_base/t2i_visual_converter/v5 prompts/_base/background_master_plan/4.* prompts/_base/floor_plan_prompt/5.* backend/app/modules/t2i_visual_converter.py backend/app/core/steps/background_master_plan_step.py backend/app/core/steps/floor_plan_prompt_step.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W3a(c8-area7-residual-hygiene-v1): t2i_visual_converter v5 + background_master_plan v4 + floor_plan_prompt v5 prompt-pack — sci-fi 콘티 example·구체 location ID example·region-specific 가구 example 추상화 (Site 15/17/18) + code-path PROMPT_DIR/PROMPT_VERSION bump (A031·SCHEMA_VERSION 보존) + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 9: Codex per-wave review** — `APPROVED_FOR_W4a_ENTRY` 수령 후 진입.

---

## Task W4a: code carry — 시나리오 leak prose (Site 22, 23, 24, 25)

**Files:**
- Modify: `backend/app/core/steps/entity_steps.py:460` (Site 22 — P028)
- Modify: `backend/app/modules/pipeline/outlook_dedup.py:140` (Site 23 — P054)
- Modify: `backend/app/modules/pipeline/scene_validator.py:144` (Site 24 — P059)
- Modify: `backend/app/modules/reference_image_generator.py:66,67,84,85` (Site 25 — P070)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **§5.5 carrier shape invariant:** 4 site 모두 prompt 문자열 / dict-value 의 **내용만** 치환 — 함수 signature / dict key / f-string 구조 / return shape 불변.

- [ ] **Step 1: W0 재검증** — 4 파일의 해당 라인 현재 내용 확인 (`grep -n "중복될 가능성\|검은정장\|동녘\|현대 한국\|present-day / near-future Korean" backend/app/...`). 줄번호 drift 시 실제 위치 SSOT.

- [ ] **Step 2: W4a G-test 작성** (append)

```python
# ──────────────── W4a — code carry prose (Site 22,23,24,25) ────────────────

# 시나리오 leak / heuristic needle (split-string)
_CHAR_HEURISTIC = "다른 타입과 중복될 " + "가능성이 거의 없"
_BLACKSUIT_EX = "검은" + "정장1"
_PROPER_NAME = "동" + "녘(강의원)"
_KOREA_HARDCODE_KO = "현대 " + "한국/근미래 한국"
_KOREA_HARDCODE_EN = "present-day / " + "near-future Korean"


def test_w4a_g_residue_entity_steps():
    """G-residue: entity_steps.py 에 character 중복 heuristic 부재."""
    code = (_BACKEND / "app" / "core" / "steps" / "entity_steps.py").read_text(encoding="utf-8")
    assert _CHAR_HEURISTIC not in code, "Site22 character heuristic 잔존"


def test_w4a_g_residue_outlook_dedup():
    """G-residue: outlook_dedup.py 에 검은정장 시나리오 example 부재."""
    code = (_BACKEND / "app" / "modules" / "pipeline" / "outlook_dedup.py").read_text(encoding="utf-8")
    assert _BLACKSUIT_EX not in code, "Site23 검은정장 example 잔존"


def test_w4a_g_residue_scene_validator():
    """G-residue: scene_validator.py 에 작품 고유명사 character 이름 부재."""
    code = (_BACKEND / "app" / "modules" / "pipeline" / "scene_validator.py").read_text(encoding="utf-8")
    assert _PROPER_NAME not in code, "Site24 동녘(강의원) 고유명사 잔존"
    assert "<visible_body_name>" in code, "Site24 placeholder form 미도입"


def test_w4a_g_residue_reference_image_generator():
    """G-residue: reference_image_generator.py 에 한국 하드코딩 부재."""
    code = (_BACKEND / "app" / "modules" / "reference_image_generator.py").read_text(encoding="utf-8")
    assert _KOREA_HARDCODE_KO not in code, "Site25 현대 한국 하드코딩 잔존(ko)"
    assert _KOREA_HARDCODE_EN not in code, "Site25 Korean 하드코딩 잔존(en)"
    assert "visual_world_rules" in code, "Site25 visual_world_rules derive 지시 미도입"


def test_w4a_g_carrier_shape():
    """G-carrier: f-string / dict 구조 보존 (함수·key 불변)."""
    rig = (_BACKEND / "app" / "modules" / "reference_image_generator.py").read_text(encoding="utf-8")
    assert "def _entity_type_reference_rules(" in rig, "_entity_type_reference_rules signature 손실"
    es = (_BACKEND / "app" / "core" / "steps" / "entity_steps.py").read_text(encoding="utf-8")
    assert "user_prompt = (" in es, "entity_steps user_prompt f-string 구조 손실"
```

- [ ] **Step 3: W4a G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w4a -v`
Expected: `g_residue` 4개 FAIL.

- [ ] **Step 4: Site 22 — entity_steps.py:460**

```
            "인물(character)은 다른 타입과 중복될 가능성이 거의 없으니 주로 배경/소품 간 중복을 확인하세요."
```
→
```
            "각 타입 쌍을 고르게 검토하되, 실질적으로 같은 대상이 서로 다른 타입으로 등록된 경우를 찾으세요."
```

- [ ] **Step 5: Site 23 — outlook_dedup.py:140**

```
- 비슷하지만 다른 의상은 병합하지 마세요 (예: "검은정장1"과 "검은정장2"는 다를 수 있음)
```
→
```
- 비슷하지만 다른 의상은 병합하지 마세요 (예: 이름이 비슷한 두 의상이라도 설명이 다르면 다른 의상)
```

- [ ] **Step 6: Site 24 — scene_validator.py:144**

```
- "동녘(강의원)" 같은 표현 → 동녘의 몸만 있고, 강의원은 원격에서 조종 중이므로 강의원은 false
```
→
```
- "<visible_body_name>(<remote_identity_name>)" 형태 표현 → visible_body 의 몸만 현장에 있고, remote_identity 는 원격에서 조종 중이므로 remote_identity 는 false
```

- [ ] **Step 7: Site 25 — reference_image_generator.py:66/67 (ko) + 84/85 (en)**

line 66:
```
                "장면성 포즈나 포박 상태보다 기본 외형과 기본 복장 톤을 우선한다.",
```
→
```
                "장면성 포즈나 일시적 상태보다 기본 외형과 기본 복장 톤을 우선한다.",
```
line 67:
```
                "현대 한국/근미래 한국 기준의 현실적 기본 복장을 사용하라.",
```
→
```
                "visual_world_rules 의 시대·지역 설정에 맞는 현실적 기본 복장을 사용하라.",
```
line 84:
```
                "Prefer stable appearance over scene-specific action or restraint state.",
```
→
```
                "Prefer stable appearance over scene-specific action or transient state.",
```
line 85:
```
                "Use realistic present-day / near-future Korean baseline clothing.",
```
→
```
                "Use realistic baseline clothing appropriate to the era and region defined in visual_world_rules.",
```

- [ ] **Step 8: W4a G-test 실행 → PASS + baseline regression**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w4a -v` → PASS.
Run baseline: `PYTHONPATH=. .venv/bin/python -m pytest tests/ -k "entity or outlook or scene_validator or reference_image" -q` → 회귀 0 (영향 test 확인 — grep 결과에 따라 대상 조정).

- [ ] **Step 9: W4a commit**

```bash
git add backend/app/core/steps/entity_steps.py backend/app/modules/pipeline/outlook_dedup.py backend/app/modules/pipeline/scene_validator.py backend/app/modules/reference_image_generator.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W4a(c8-area7-residual-hygiene-v1): code carry 시나리오 leak prose 추상화 — entity_steps character heuristic / outlook_dedup 검은정장 example / scene_validator 작품 고유명사 → placeholder form / reference_image_generator 한국 하드코딩 → visual_world_rules derive (Site 22/23/24/25, carrier shape 보존) + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 10: Codex per-wave review** — `APPROVED_FOR_W4b_ENTRY` 수령 후 진입.

---

## Task W4b: code carry — render_prompt_card camera prose (Site 20 FIX 부분)

**Files:**
- Modify: `backend/app/core/steps/render_prompt_card.py:550-555` (`camera_frame_rule.applies_when` prose)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (append)

> **Site 20 SPLIT:** `_SPATIAL_CAMERA_LOW/HIGH/HIP_TOKENS` canary tuple (lines 309-332) + `_SPATIAL_FRAME_EDGE_POSITION_TOKENS` (337+) = **canary ground-truth, KEEP byte-identical**. FIX 대상 = production `camera_frame_rule` dict 의 `applies_when` parenthetical 닫힌 camera-height token 목록 **전체** (spec §9 OQ-C8-E — `quay level` 한 토큰만 제거 금지). residue gate 는 old parenthetical 목록 전체를 잡는다.

- [ ] **Step 1: W0 재검증 + test coupling grep** — `render_prompt_card.py:550-555` 의 `applies_when` 현재 문자열 확인. **`applies_when` / `camera_frame_rule` / `core_principles` 에 substring assert 하는 test·canary 전수 grep:**
```bash
cd /Users/manta/Documents/Projects/TheRoad-I1 && grep -rn "applies_when\|camera_frame_rule\|core_principles\|g4_5a\|quay level\|hip-height" backend/tests --include='*.py' | grep -v ".pyc"
find backend -name "g4_5a*" -o -name "*camera_frame*"
```
`applies_when` 문자열 자체에 substring assert 가 걸려 있으면 (예: `"quay level" in applies_when`) → 그 test 는 W4b Step 4 후 갱신 대상으로 분류 + Codex consult. canary 가 `_SPATIAL_CAMERA_*` tuple / `core_principles` 만 참조하면 (예상) prose edit 무관.

- [ ] **Step 2: W4b G-test 작성** (append)

```python
# ──────────────── W4b — render_prompt_card camera prose (Site 20) ────────────────

# 닫힌 camera-height token 목록 needle (split-string)
_CAMERA_TOKEN_LIST = "low / hip-height / overhead / " + "ground level / quay level / floor level / from above"


def test_w4b_g_residue_camera_applies_when():
    """G-residue: render_prompt_card.py applies_when 에 닫힌 camera-height token 목록 부재."""
    code = (_BACKEND / "app" / "core" / "steps" / "render_prompt_card.py").read_text(encoding="utf-8")
    assert _CAMERA_TOKEN_LIST not in code, "Site20 applies_when 닫힌 camera token 목록 잔존"


def test_w4b_g_keep_canary_tuples():
    """G-canary KEEP: _SPATIAL_CAMERA_* canary tuple 이 byte-보존 (KEEP — 폐기 금지)."""
    code = (_BACKEND / "app" / "core" / "steps" / "render_prompt_card.py").read_text(encoding="utf-8")
    assert "_SPATIAL_CAMERA_LOW_TOKENS" in code, "canary LOW tuple 손실"
    assert "_SPATIAL_CAMERA_HIGH_TOKENS" in code, "canary HIGH tuple 손실"
    assert "_SPATIAL_CAMERA_HIP_TOKENS" in code, "canary HIP tuple 손실"
    assert "_SPATIAL_FRAME_EDGE_POSITION_TOKENS" in code, "canary frame-edge tuple 손실"
    # KEEP site 21 — _ID_ETHNICITY/_ID_AGE_BANDS 도 불변
    assert "_ID_ETHNICITY_COMPONENTS" in code, "Site21 KEEP — ethnicity tuple 손실"
    assert "_ID_AGE_BANDS" in code, "Site21 KEEP — age band tuple 손실"


def test_w4b_g_intent_camera_frame_rule():
    """G-intent: camera_frame_rule applies_when 의 frame-edge 판정 의도 보존."""
    code = (_BACKEND / "app" / "core" / "steps" / "render_prompt_card.py").read_text(encoding="utf-8")
    assert "frame-edge surfaces" in code, "frame-edge 판정 의도 손실"
    assert '"applies_when":' in code, "camera_frame_rule applies_when key 손실"
```

- [ ] **Step 3: W4b G-test 실행 → FAIL 확인**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w4b -v`
Expected: `g_residue` FAIL. `g_keep_canary` / `g_intent` PASS.

- [ ] **Step 4: Site 20 FIX — render_prompt_card.py:550-555 applies_when prose**

```python
        "applies_when": (
            "camera_direction specifies a vertical height/position "
            "(low / hip-height / overhead / ground level / quay level / "
            "floor level / from above) and the scene description includes "
            "frame-edge surfaces"
        ),
```
→
```python
        "applies_when": (
            "camera_direction expresses a vertical camera height or "
            "viewpoint position and the scene description includes "
            "frame-edge surfaces"
        ),
```

- [ ] **Step 5: W4b G-test 실행 → PASS + canary 재실행 (G-canary)**

Run: `cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -k w4b -v` → PASS.
Run G-canary (Step 1 grep 으로 확정한 canary/builder test 경로): `PYTHONPATH=. .venv/bin/python -m pytest <g4_5a canary 경로> <camera_frame_rule builder unit test 경로> -v` → PASS (no live LLM). canary 가 FAIL 하면 STOP + Codex consult (canary 가 prose 에 결합돼 있던 것 — spec §9 OQ-C8-E 예상 밖).

- [ ] **Step 6: W4b commit**

```bash
git add backend/app/core/steps/render_prompt_card.py backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W4b(c8-area7-residual-hygiene-v1): render_prompt_card camera_frame_rule.applies_when 닫힌 camera-height token 목록 전체 추상화 (Site 20 FIX — generic vertical-height principle, _SPATIAL_CAMERA_* canary tuple KEEP) + C8 test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 7: Codex per-wave review** — `APPROVED_FOR_W5_ENTRY` 수령 후 진입.

---

## Task W5: integrated gate + closure docs atomic

**Files:**
- Modify: `docs/superpowers/specs/2026-05-20-c8-area7-residual-hygiene-v1-design.md` (status → closed)
- Modify: `docs/superpowers/plans/2026-05-20-c8-area7-residual-hygiene-v1-implementation.md` (본 plan — status → closed)
- Modify: `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` (§5.7 C8 closure marker)
- Modify: `docs/fix-critical-1/index.html` (C8 section closed marker + 상단 matrix + entry-order navigation)
- Test: `backend/tests/prompts/test_c8_area7_residual_hygiene.py` (G-boundary append)

> W3b (boundary-reviewed KEEP) 별도 wave 미신설 — Site 16 KEEP / Site 19 DEFER / Site 21 KEEP 는 본 W5 closure docs 에 boundary-reviewed / carry 로 기록 (doc-only, code/prompt edit 0).

- [ ] **Step 1: G-boundary test 작성** (append)

```python
# ──────────────── W5 — G-boundary (KEEP / DEFER site 불변 검증) ────────────────


def test_w5_g_boundary_keep_defer_sites():
    """G-boundary: KEEP (Site 16, 21) + DEFER (Site 19) + OQ-PLAN-1/3 KEEP site 불변."""
    # Site 16 — background_chain_render KEEP (C8 신규 dir 미생성)
    bcr_vers = _list_module_versions("background_chain_render")
    assert bcr_vers[0].startswith("2."), "Site16 background_chain_render KEEP 위반 — 신규 dir 생성됨"
    # Site 19 — scene_consistency_step P020 prefix shim DEFER (edit 0)
    scs = (_BACKEND / "app" / "core" / "steps" / "scene_consistency_step.py").read_text(encoding="utf-8")
    assert '"분석 실패"' in scs or "분석 실패" in scs, "Site19 DEFER — P020 prefix shim 이 의도치 않게 제거됨"
    # Site 21 — render_prompt_card _ID_ETHNICITY/_ID_AGE_BANDS KEEP (W4b test 와 중복 확인)
    rpc = (_BACKEND / "app" / "core" / "steps" / "render_prompt_card.py").read_text(encoding="utf-8")
    assert "_ID_ETHNICITY_COMPONENTS" in rpc and "_ID_AGE_BANDS" in rpc, "Site21 KEEP 위반"
    # OQ-PLAN-3 KEEP — render_prompt_card role_hint_from_outfit (G4.3 SOT) 보존
    assert "role_hint_from_outfit" in rpc, "OQ-PLAN-3 KEEP 위반 — role_hint_from_outfit 손실"
    # OQ-PLAN-1 KEEP — location_consistency v3 example block (lines 41-57) 보존
    lc = (_latest_dir("location_consistency") / "system.md").read_text(encoding="utf-8")
    assert "compact one-room apartment" in lc, "OQ-PLAN-1 KEEP 위반 — 실내 output example 손실"
    assert "narrow back alley" in lc, "OQ-PLAN-1 KEEP 위반 — 야외 output example 손실"
```

- [ ] **Step 2: 통합 gate 실행 — C8 전체 + cross-cutting regression**

```bash
cd backend && PYTHONPATH=. .venv/bin/python -m pytest tests/prompts/test_c8_area7_residual_hygiene.py -v
PYTHONPATH=. .venv/bin/python -m pytest tests/test_prompt_versions.py tests/test_step_manifest_v3.py tests/integration/test_area_7b_no_drift.py tests/prompts/test_c5_action_segment_sot.py tests/prompts/test_c4_background_classifier.py -q
```
Expected: C8 test 전부 PASS (W1a-1~W5 G-test), cross-cutting regression 0 (Group F / STEP_MANIFEST / Area7b / C5 / C4 회귀 0). FAIL 시 STOP + Codex consult.

- [ ] **Step 3: spec status 갱신** — `docs/superpowers/specs/2026-05-20-c8-area7-residual-hygiene-v1-design.md` frontmatter `status:` 를 `draft (...)` → `closed — C8 v1 W1a-W5 closed+pushed (origin/main <W5_COMMIT>). 8-wave 10-commit. FIX 21 추상화 완료.` 로 갱신. §12 Codex Consultation Log 에 iter 3 APPROVED + plan-entry sanity + per-wave review 결과 append.

- [ ] **Step 4: plan status 갱신** — 본 plan frontmatter (없으면 헤더 하단) 에 `**Status:** closed — W1a-W5 closed+pushed` 추가.

- [ ] **Step 5: roadmap §5.7 갱신** — `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` §5.7 Area #7 항목에 C8 closure marker 추가 (`C8 = Area #7 잔여 sub-area marathon — 21 FIX site 추상화 완료, push <W5_COMMIT>`). 실제 §5.7 위치는 grep 으로 확인.

- [ ] **Step 6: index.html C8 closure marker** — `docs/fix-critical-1/index.html`:
  - C8 section (lines ~1180-1264 — 실제 위치 grep) 에 `✓ CLOSED` marker + 25 site disposition 결과 (FIX 21 추상화 / KEEP 2 / SPLIT 1 / DEFER 1) 기록.
  - **상단 matrix row** C8 → `✓ CLOSED` (C5 함정 N-6 — 상단 matrix 동기 의무).
  - **entry-order navigation** → C8 `✓ CLOSED`, C10 `NEXT` 로 정정.
  - boundary-reviewed / carry 기록: Site 16 KEEP / Site 19 DEFER / Site 21 KEEP / **OQ-PLAN-1 location_consistency line 18 + lines 41-57 KEEP** / **OQ-PLAN-3 render_prompt_card `role_hint_from_outfit` reviewed and kept as G4.3 SOT, not C8 closure claim** / shot_schema strict carry / schema-strict carry (§C Known Carry).

- [ ] **Step 7: W5 commit (closure docs atomic)**

```bash
git add docs/superpowers/specs/2026-05-20-c8-area7-residual-hygiene-v1-design.md docs/superpowers/plans/2026-05-20-c8-area7-residual-hygiene-v1-implementation.md docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md docs/fix-critical-1/index.html backend/tests/prompts/test_c8_area7_residual_hygiene.py
git commit -m "$(cat <<'EOF'
W5(c8-area7-residual-hygiene-v1): closure docs atomic — spec/plan status closed + roadmap §5.7 C8 marker + index.html C8 section ✓CLOSED + 상단 matrix/entry-order 동기 + G-boundary test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

- [ ] **Step 8: W1a-W5 range review + push** — Codex MCP 에 W1a-1~W5 전체 commit range diff 요약 송신 → `APPROVED_FOR_PUSH` verdict 수령 시 즉시 `git push origin main` (사용자 ask 0 — [[feedback_push_no_user_ask]]). `NEEDS_REVISION_NARROW` 시 fix-up commit 후 재요청.

---

## §C. Known Carry (C8 scope 밖 — 별도 처리)

- **shot_extract `shot_schema.json` OpenAI strict compatibility** — C5 W1 OQ blocker (`OPTION_1_APPROVED`). v14 도 shot_schema.json 미통합 (v10 per-stem fallback). 본질이 closed-list/scenario-leak hygiene 가 아니라 schema 구조 결함 — C8 어떤 wave 도 재통합 시도 안 함. → C10 또는 dedicated SchemaStrict carry.
- **schema-strict carry** (spec §1.3 / OQ-C8-G) — `shot_schema.json` `characters` properties 존재하나 required 부재 등 OpenAI strict 위반. C8 scope 밖. W0 §0.3-(5) linter 가 신규 dir 에서 dormant 위반을 노출하면 carry 기록만, silent schema edit 금지.
- **Site 19 P020 prefix shim DEFER** — `scene_consistency_step.py` 의 `analysis_summary.startswith("분석 실패"/"분석 차단")` backward-compat shim. 폐기 = 옛 checkpoint 의 `status` 백필 / checkpoint migration 설계 필요 → C8 hygiene scope 외. closure 에 carry 기록.
- **Site 16 background_chain_render KEEP** — wall/floor/ceiling/lighting palette description-completeness checklist. semantic classifier 아님 (C4 A031 선례). boundary-reviewed KEEP, fixed claim 금지.
- **Site 21 `_ID_ETHNICITY_COMPONENTS` / `_ID_AGE_BANDS` KEEP** — G4.3 R1-I1 ground-truth + `demographic_descriptor_policy.scenario_dependency_ban` 명시 SOT. hygiene-debt leak 아님. boundary-reviewed KEEP.
- **`role_hint_from_outfit` (`render_prompt_card.py:1072-1077`) KEEP** (OQ-PLAN-3, Codex `APPROVED_KEEP`) — `demographic_descriptor_policy.components.role_hint_from_outfit` 닫힌 목록. C8 Site 21 scope (ethnicity/age) 밖 + G4.3 SOT 일부. reviewed and kept as G4.3 SOT — C8 closure claim 아님.
- **`location_consistency/system.md` line 18 재료 example + lines 41-57 example block KEEP** (OQ-PLAN-1, Codex `APPROVED`) — `등` open-form / 시나리오 고유명사 0 의 범용 output example. C8 Site 7 FIX = lines 24-30 닫힌 토큰 목록만.

## §OQ. Plan-time Open Questions — RESOLVED (Codex plan review)

plan v1 작성 중 live-source 재확인으로 발견된 spec 대비 불일치 — **Codex plan review (NEEDS_REVISION_NARROW_3) 가 3건 모두 verdict 확정. 실행 시 wave entry 에서 재질의 금지 — 아래 verdict 그대로 적용.**

- **OQ-PLAN-1 — location_consistency Site 7 example block (W1b) — `APPROVED`**: Codex 확인 — `system.md` line 18 재료 example 은 `등` open-form, lines 41-57 (apartment/back-alley) 은 시나리오 고유명사 0 의 범용 output example. **C8 Site 7 FIX = lines 24-30 닫힌 환경상태 영문 토큰 목록만. line 18 + lines 41-57 = boundary-reviewed KEEP** (W5 closure 기록, W5 G-boundary 검증).
- **OQ-PLAN-2 — shot_extract user.md 변형 block (W2a) — `APPROVED_WITH_TEST_GUARD`**: Codex 확인 — `user.md:46-66` 평행 변형 block 의 닫힌 목록을 **W2a 가 system.md:37-47 과 함께 반드시 추상화** (같은 module·wave consistency). W2a G-test `test_w2a_g_residue_user_transformation_block` 가 강제 (Step 2).
- **OQ-PLAN-3 — render_prompt_card `role_hint_from_outfit` — `APPROVED_KEEP`**: Codex 확인 — `render_prompt_card.py:1072-1077 role_hint_from_outfit` 은 C8 Site 21 scope (ethnicity/age) 밖 + G4.3 `demographic_descriptor_policy` SOT 일부. **W4b 에 편입 금지 — KEEP.** W5 closure 에 "role_hint_from_outfit reviewed and kept as G4.3 SOT, not C8 closure claim" boundary note 기록 + W5 G-boundary 검증.

## §SR. Self-Review (writing-plans skill)

- **Spec coverage**: spec §3 disposition 25 site 전부 매핑 — FIX 21 (Site 1-15,17,18,22-25 → W1a-1/W1a-2/W1a-3/W1b/W2a/W2b/W3a/W4a/W4b) / KEEP 2 (16,21 → W5 boundary) / SPLIT 1 (20 → W4b FIX + canary KEEP) / DEFER 1 (19 → W5 carry). spec §6 version-bump rules → §P Procedure. spec §7 gates → §G. spec §8 boundary → W2a §8 note / W3a §8 note / W5 G-boundary. spec §9 OQ → 본문 반영 (OQ-C8-A entity active = §0.3-(4), OQ-C8-B Site12 narrow = W2b, OQ-C8-C 몽타주 = W2b, OQ-C8-D Site19 DEFER = W5, OQ-C8-E Site20 = W4b, OQ-C8-F Site21 KEEP = W5, OQ-C8-G schema carry = §C).
- **Placeholder scan**: 모든 FIX site 에 verbatim before + 구체 after wording 제시. UTC 만 runtime 캡처 (`<UTC>` = 의도적 runtime 값, design-time 하드코딩 금지 — placeholder 아님). `<W5_COMMIT>` = W5 후 기록되는 실제 hash.
- **Type consistency**: Procedure P 파라미터 (M/D_old/N+1/E/S) 전 wave 일관. version_registry sync = §P-reg 4 module 한정. test helper (`_PROMPTS`/`_latest_dir`/`_sha256`/`_BACKEND`) — `_BACKEND` 는 W3a Step 2 에서 최초 정의, W4a/W4b/W5 가 재사용 (정의 순서 = wave 순서와 일치).
- **OQ resolved**: §OQ 3건 모두 Codex plan review (NEEDS_REVISION_NARROW_3) verdict 확정 — OQ-PLAN-1 `APPROVED` / OQ-PLAN-2 `APPROVED_WITH_TEST_GUARD` / OQ-PLAN-3 `APPROVED_KEEP`. 실행 시 wave entry 재질의 0.
- **amend 1 (Codex plan review NARROW 3 반영)**: (1) W2a 에 user.md 변형 block residue gate 추가 (`test_w2a_g_residue_user_transformation_block`) + user.md:46-66 추상화 의무화. (2) §P UTC discipline — wave/subwave 별 **단일 bash 블록** 에서 UTC 캡처 + 전 dir 생성 + `ls` readback, `$UTC` tool-call 경계 재사용 금지, code 상수는 readback literal. (3) §0.3-(5) strict schema gate 를 executable 명령 (`tests/unit/test_prompt_schema_openai_strict.py::test_latest_prompt_schemas_openai_strict_compatible`) 으로 격상 — W1a-1/W1a-2/W1a-3/W1b/W2a/W2b/W3a PASS 단계에 편입.

## §H. Execution Handoff

plan APPROVED_FOR_EXECUTION 수령 후 **inline 실행** (REQUIRED SUB-SKILL: superpowers:executing-plans) — W1a-1 → W5 순차, 각 wave atomic commit + Codex per-wave review. fix-critical-1 marathon autonomous goal (Stop hook) 이므로 본 세션에서 연속 실행.

