---
title: C4 — Background / floor-plan semantic classifier prompt hygiene + P043 substring fallback 폐기 v1 (fix-critical-1 Tier β #7)
date: 2026-05-20+
status: closed (C4 v1 — W1-W5 5-wave 실행 완료, Codex per-wave APPROVED. W1 `2c018f3` / W2 `7c9a605` / W3 `4bf2c72` + fix-up 1 `1130cf9` / W4 `ddcd115`. §10 Closure 참조.)
roadmap_ref: docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md §5.8 (Area #8 — Background / floor-plan semantic classifier)
fix_critical_doc_ref: docs/fix-critical-1/index.html C4 section (lines 1010-1105)
audit_ref: backend/tests/_audit_outputs/semantic_string_debt/full_20260516_0105/triage_report.md — P043 (line 103, semantic_string_judgment) + A001/A002/A003/A004/A005 (lines 160-164, llm_closed_list_instruction) + A031 (line 190) + A033/A034 (lines 192-193) + roadmap §5.8
prerequisites:
  - C1 perception_mode enum SOT v1 / C2 owned_object_usage[] echo v1 / C3 detail_steps focus rewrite v1 / C7 shot_dependency_t2i subject_kind v1 / C6 detail_steps 색감-감정 closed-list v1 / C9 scene_detail body-light verb v1 — 모두 closed + pushed (origin/main HEAD `16abb40`). HEAD anchor. 변경 0.
non_supersedes:
  - C5 #9 action segment SOT / C8 #7 잔여 sub-area split / C10 future structured carries — 별도 area.
  - A020 entity_extractor_v2 allowed_space_keys controlled vocab — C4 scope 제외 (§3.3). A031 floor_plan_prompt color/glyph — boundary-reviewed KEEP, 변경 0 (§3.2).
---

# C4 — Background / floor-plan semantic classifier prompt hygiene + P043 substring fallback 폐기 v1

## 1. Purpose + Scope

### 1.1 Purpose

**roadmap §5.8 Area #8 (G6 신규 영역)**: 배경/도면 파이프라인의 indoor/outdoor · skip_chain · room-count 판단이 **LLM-facing closed-list reasoning** + **code-side semantic substring 매칭** 으로 구현되어 있다.

audit (triage_report.md, full_20260516_0105) 9 row:

| row | grade | site | category | 본질 |
|-----|-------|------|----------|------|
| P043 | P1 | `background_chain_planning.py:151-154` | semantic_string_judgment | `loc_raw in ename or ename in loc_raw` location 이름 substring 매칭 |
| A001 | P2 | `background_chain_planning/4/schema.json:5-12` | llm_closed_list_instruction | `skip_chain` description 안 OUTDOOR/OPEN-AIR 닫힌 목록 (street/road/coast/sea/forest/yard/...) |
| A002 | P2 | `background_chain_planning/4/user_template.md:14-21` | llm_closed_list_instruction | TASK 분기 `(outdoor / open-air)` vs `(indoor / enclosed / fixed-set)` |
| A003 | P1 | `background_chain_planning/4/system.md:21-25` | llm_closed_list_instruction | SKIP DECISION — INDOOR/ENCLOSED/FIXED-SET vs DETACHED OPEN AREA 닫힌 예시 enumeration |
| A004 | P1 | `background_chain_planning/4/system.md:45-51` | llm_closed_list_instruction | node state 닫힌 목록 `(clean / lived-in / disturbed / heavily-ransacked)` |
| A005 | P1 | `background_classify/3/system.md:18-20` | llm_closed_list_instruction | indoor/outdoor 판단용 **한국어 keyword 닫힌 목록** (`내부/안/방/실/층` · `외부/옥상/거리/도로/...`) |
| A031 | P1 | `floor_plan_prompt/4/system.md:10-13` | llm_closed_list_instruction | floor plan 색 매핑 (living=pale yellow) + 가구 glyph (bed=rectangle) |
| A033 | P1 | `location_floor_plan/3/system.md:21-26` | llm_closed_list_instruction | room-count cue — 한국어 방 이름 (`거실/안방, 부엌/거실`) + 한국어 이동 동사 (`문을 열고 들어간다`) |
| A034 | P2 | `location_floor_plan/3/system.md:31` | scenario_dependent_prompt | slash-zone 한국어 예시 (`옥탑방 안 / 실내`, `한옥 / 안방 / 마루`) |

**핵심 진단 (Codex C4 entry sanity 흡수)**: 위 prompt-side row 의 LLM 은 **이미 자체 structured output 을 emit** 한다 — `background_classify` 는 `members[].is_indoor` bool (schema required), `background_chain_planning` 은 `skip_chain` bool (schema required), `location_floor_plan` 은 free-text t2i prompt. 따라서 doctrine 위반은 "code 가 semantic string 을 소비" 가 아니라 **prompt 안에 닫힌 enumeration 이 주입되어 있다는 점** — C9/C6 와 동일한 **Prompt Closed-List Ban** 패턴. P043 만 code-side **Semantic Regex Ban** 위반.

**index.html 원안 (location_space_profile 신규 producer 9-step) = 채택 안 함**. Codex C4 entry sanity (OQ-C4-1) 가 NO-GO 판정: live 구조상 이미 (a) `background_classify/3` schema 의 `members[].is_indoor` bool required + step 이 `indoor_loc_ids=None` 으로 LLM bool 을 authoritative 보존, (b) `background_chain_planning/4` schema 의 `skip_chain` bool required, (c) D6 계열의 `entity_canon.metadata_json.location.space_profile` + `bg_state_vocab.validate_location_space_profile()` + `bg_catalog.normalize_space_key()` 가 존재 — 신규 producer 는 기존 SOT 와 중복되고 신규 step/DB/downstream rewiring/routing risk 만 키운다. **C4 v1 = P043 substring 제거 + prompt 닫힌 enumeration 추상화 (narrow, C9/C6 pattern)**.

본 spec v1 = **8 row 해소** (P043 + A001/A002/A003/A004/A005/A033/A034) — code substring fallback 폐기 + 4개 prompt-pack stem 의 닫힌 enumeration 을 generic semantic principle 로 추상화. **A031 = boundary-reviewed KEEP** (§3.2). **A020 = C4 scope 제외** (§3.3).

### 1.2 In-scope

- **W1 — P043 code fix**: `background_chain_planning.py:146-157` location resolution 의 substring fallback loop (`:151-154`) 폐기 + no-match silent fallback (`loc_id = loc_raw`, `:155-157`) 를 explicit 처리로 치환. `L\d+` direct path + exact `name_to_sid.get(loc_raw)` path 보존. C4 regression test 신설. (§3.6, §5.1)
- **W2 — `background_chain_planning` v5 prompt-pack**: `5.<W2_UTC>/` 신규 디렉토리 — `system.md` (A003 SKIP DECISION + A004 node state 추상화) + `schema.json` (A001 `skip_chain` description 추상화) + `user_template.md` (A002 TASK 분기 추상화). `PROMPT_VERSION "4"→"5"` bump + path-scoped residue/loader test. (§5.2)
- **W3 — `background_classify` v4 prompt-pack**: `4.<W3_UTC>/` 신규 디렉토리 — `system.md` (A005 한국어 keyword 닫힌 목록 폐기) + `schema.json`/`user_template.md` byte-identical sibling. `PROMPT_VERSION "3"→"4"` bump + path-scoped residue/loader test. (§5.3)
- **W4 — `location_floor_plan` v4 prompt-pack**: `4.<W4_UTC>/` 신규 디렉토리 — `system.md` (A033 한국어 방 이름/이동 동사 cue + A034 slash-zone 예시 추상화) + `user_template.md` byte-identical sibling. `PROMPT_VERSION "3"→"4"` bump + path-scoped residue/loader test. (§5.4)
- **W5 — integrated gate + closure docs atomic**: cross-cutting static/residue/compat gate + closure docs (본 spec + plan status / roadmap §5.8 / index.html C4 marker / triage 참조). (§5.5)

### 1.3 Out-of-scope (Codex C4 entry sanity scope guard 흡수)

- **신규 producer module (`location_space_profile.py`) — 미신설** (OQ-C4-1). index.html 9-step 원안 폐기. cross-step 독립 재도출 (`background_classify.is_indoor` vs `background_chain_planning.skip_chain`) 통합은 C4 v1 필수조건 아님 — future architecture/carry 로만 남김.
- **A031 `floor_plan_prompt/4/system.md:10-13` — 변경 0, boundary-reviewed KEEP** (OQ-C4-2, §3.2). schematic diagram 의 deterministic rendering convention (색 구역 식별 + top-down glyph convention) 이지 open-world semantic classifier 가 아님. `floor_plan_prompt` 디렉토리 C4 변경 0. closure docs 에 "fixed" 아닌 "boundary-reviewed KEEP" 으로 명시.
- **A020 `entity_extractor_v2/.../system.md` `allowed_space_keys` controlled vocab — C4 scope 제외** (OQ-C4-3, §3.3). roadmap §5.8 audit row 미포함, audit row A020 = v9 기준 — file-fallback 기준 latest pack 은 v10 으로 이동했으나 `version_registry` 는 아직 v9 (별도 carry 영역), D6 `location.space_profile` SOT/validator 와 묶인 큰 영역. C8/C10 또는 별도 carry.
- **A032 `floor_plan_prompt/4/system.md:16` (ondol-friendly bed scenario_dependent) — C4 scope 외**. index.html 명시 ("#8 scope 외, 가구 표현 영역"). C8 sub-area (d) 소관.
- **schema 구조 변경 (field / enum / required / type) — 0**. A001 은 `skip_chain` 의 **description 산문** 만 추상화 — `type: boolean` / `required` 구조 불변. background_classify/location_floor_plan schema 는 byte-identical sibling.
- **SCHEMA_VERSION bump — 0**. 세 step 의 `SCHEMA_VERSION` 상수 (background_chain_planning=2 / background_classify=1 / location_floor_plan=3) 불변 — 구조 schema 무변경. `PROMPT_VERSION` bump 만으로 `_config_hash` resume 무효화 충족.
- **step lifecycle 변경 — 0**. `background_chain_planning` (lifecycle deprecated / applicability disabled) + `location_floor_plan` (lifecycle deprecated) 의 step_manifest entry 불변 — C4 는 active latest prompt/code residue 만 좁게 다룬다 (§3.7).
- **VLM / live LLM / semantic regex**: 모두 ban ([[feedback_no_vlm_dependency]] / [[feedback_llm_based_judgment]]).
- **C1·C2·C3·C6·C7·C9 변경 사항 / C5·C8·C10**: 별도 area, 변경 0.

## 2. W0 Evidence Inventory (live, HEAD `16abb40`)

### 2.1 P043 — `background_chain_planning.py:146-157` (W1 대상)

`_phase0_group_by_location` 의 location resolution (현 코드):

```python
# primary_location → short_id 정규화
if loc_raw.startswith("L") and loc_raw[1:].isdigit():
    loc_id = loc_raw                                          # (a) L\d+ direct — 보존
else:
    loc_id = name_to_sid.get(loc_raw, "")                     # (b) exact name match — 보존
    if not loc_id:
        for ename, esid in name_to_sid.items():               # (c) P043 substring — 폐기
            if len(ename) >= 2 and (loc_raw in ename or ename in loc_raw):
                loc_id = esid
                break
    if not loc_id:
        loc_id = loc_raw                                      # (d) silent fallback — explicit 치환
        logger.warning("Phase 0: primary_location '%s' not matched", loc_raw)
```

- (c) `loc_raw in ename or ename in loc_raw` = audit P043 — location 이름 양방향 substring 매칭. semantic string judgment (Semantic Regex Ban 위반).
- (d) 현재 no-match 시 `loc_id = loc_raw` 로 비정규 key 를 그대로 써서 group 을 만든다 — 해당 group 은 `locs_meta.get(loc_id, {})` 가 빈 dict 를 반환해 metadata 없이 chain 설계됨. Codex C4 entry sanity P043 narrow guard: silent fallback 로 계속 가지 말 것.
- caller contract: `_phase0_group_by_location` → `run_background_chain_planning` (try/except 없음) → `BackgroundChainPlanningStep.run`. 기존 test `test_phase0_legacy_korean_location_name_mapped_to_short_id` 는 **exact match** (`name_to_sid.get("거실")`) 만 사용 — substring fallback (c) 를 exercise 하는 test 0건. (c) 폐기 = 기존 test 회귀 0.

### 2.2 A001/A002/A003/A004 — `background_chain_planning/4.202604291315/` (W2 대상)

active prompt-pack = **v4** (`4.202604291315/`), 3 stem (`system.md` / `schema.json` / `user_template.md`).

| row | site | 현재 내용 | 분류 |
|-----|------|----------|------|
| A003 | `system.md:21-25` | SKIP DECISION B 분기 — `INDOOR / ENCLOSED / FIXED-SET (room, hallway, store interior, office, kitchen, basement, closed vehicle interior, fixed studio set, etc.)` vs `DETACHED OPEN AREA — a wide outdoor space ... (a road far from any building, an unrelated forest, a wide beach, a public street block, a mountain trail, a public square not attached to a tracked building)` | **C4 TARGET — 닫힌 예시 enumeration 추상화** |
| A004 | `system.md:48` | `* different room state (clean / lived-in / disturbed / heavily-ransacked)` | **C4 TARGET — 닫힌 목록 추상화** |
| A001 | `schema.json:6-12` | `skip_chain` description — `true if this location is OUTDOOR/OPEN-AIR (street, road, coast, sea, forest, yard, park, exterior rooftop, public square, beach, dock, vehicle exterior, etc.) where shot-to-shot continuity is naturally accepting of variation ...` | **C4 TARGET — description 산문 추상화** |
| A002 | `user_template.md:14-21` | TASK 분기 — `A) If SKIP applies (outdoor / open-air):` / `B) Otherwise (indoor / enclosed / fixed-set):` | **C4 TARGET — 분기 label 추상화** |

- **보존**: `system.md:17-19` (SKIP DECISION 개념 — "whether this group's location belongs to a SHARED BUILDING SET (an architectural complex with persistent visible structure) or is a DETACHED OPEN AREA (no fixed architecture to maintain consistency for)"). 이미 generic 개념 — A003 의 닫힌 예시는 이 개념을 *예시 enumeration* 으로 다시 펼친 것. `system.md:51` ("When the lighting OR the visible surface state differs enough that a single image cannot serve both shots, split into separate nodes") — 이미 generic principle, A004 의 닫힌 목록은 이 principle 의 *예시*.
- code/schema 소비자: `skip_chain` bool 은 `validate_plan` (`background_chain_planning.py:388`) 이 구조적 invariant 만 검사 (semantic string 매칭 0) — A003/A004 추상화로 validator 회귀 0. `skip_chain` schema field 의 `type:boolean`/`required` 불변.

### 2.3 A005 — `background_classify/3.202604300520/system.md:16-24` (W3 대상)

active prompt-pack = **v3** (`3.202604300520/`), 3 stem (`system.md` / `schema.json` / `user_template.md`). 위반 = `## Indoor / outdoor judgment` 섹션 (`:16-24`):

| 줄 | 내용 | 분류 |
|----|------|------|
| `:18` | `Mark is_indoor=true when the location is inside an enclosed structure with walls and a ceiling (e.g., a room, an office, a vehicle cabin, a shop interior, a hallway inside a building, a basement, a cellar). Korean clues that often imply indoor: 내부 / 안 / 방 / 실 / 층 / 차내 / 매장 안 / 사무실 (when paired with structural words). Examples: "옥탑방 내부" → indoor, "거실" → indoor, "탑승차 차내" → indoor, "커피숍 내부" → indoor.` | 개념 + 영문 e.g. **보존**; `Korean clues ... 내부/안/방/...` 닫힌 한국어 목록 + `Examples: 옥탑방 내부 → ...` 한국어 매핑 예시 **C4 TARGET — 제거** |
| `:20` | `Mark is_indoor=false when the location is outside with sky overhead (a street, a park, a field, a yard, a forest, a beach, a rooftop terrace open to sky, a parking lot exterior). Korean clues: 외부 / 옥상 / 거리 / 도로 / 공터 / 골목 / 해안 / 숲 / 마당.` | 개념 + 영문 e.g. **보존**; `Korean clues: 외부/옥상/거리/...` 닫힌 한국어 목록 **C4 TARGET — 제거** |

- 개념 문장 ("inside an enclosed structure with walls and a ceiling" / "outside with sky overhead") + 영문 `(e.g., a room, an office, ...)` 는 generic 개념 illustration — audit 미flag, **보존**. C4 TARGET = 닫힌 **한국어 keyword 목록** (`내부 / 안 / 방 / ...`) + 한국어 매핑 예시만.
- code 소비자: `is_indoor` bool 은 `validate_classify_output` 의 chain_bg heuristic 이 소비. step 은 `indoor_loc_ids=None` 을 전달 (`background_classify_step.py:130`) → LLM 의 `is_indoor` bool 이 authoritative. A005 추상화로 schema/validator 회귀 0 (`is_indoor` field 불변).

### 2.4 A033/A034 — `location_floor_plan/3.202604291130/system.md:21-31` (W4 대상)

active prompt-pack = **v3** (`3.202604291130/`), 2 stem (`system.md` / `user_template.md` — **schema.json 없음**). 위반 = "CRITICAL — derive the layout" 섹션:

| row | 줄 | 내용 | 분류 |
|-----|----|------|------|
| A033 | `:22-25` | `B. Room-count inference. Cues to look for:` — `Multiple Korean room words appearing in the same scene as separate spaces (e.g., 거실/안방, 부엌/거실, 침실/욕실, 방1/방2)...` / `Movement verbs that imply crossing a wall ("문을 열고 들어간다", "방으로 들어선다", "건너편에서", "문 너머")` / `A scene heading or stage direction like "거실에서 안방으로" / "방에서 거실로 나온다"` | **C4 TARGET — 한국어 토큰 예시 추상화** |
| A034 | `:31` | `E. Zone markers. The scene heading often lists slash-separated zones (e.g., "옥탑방 안 / 실내", "한옥 / 안방 / 마루"). Treat each slash-separated label as a candidate room or zone ...` | **C4 TARGET — 한국어 slash-zone 예시 추상화** |

- **보존**: section B/E 의 generic principle ("Determine the REAL number of distinct rooms by reading every scene segment line-by-line" / "Treat each slash-separated label as a candidate room or zone and confirm by scanning the scene body"). C4 TARGET = 한국어 토큰 예시만.
- **boundary preserve**: `system.md:14` (`Korean common nouns are OK in parentheses for room labels (e.g., "main bedroom (안방)")`) — OUTPUT 허용 규칙, audit 미flag, **불변**. (한국어가 output 라벨에 허용된다는 별개 규칙.)
- code 소비자: `location_floor_plan` LLM 출력 = free-text t2i prompt (구조 schema 없음). A033/A034 추상화로 code 회귀 surface 0.

### 2.5 Version cascade — per-step `PROMPT_VERSION` 상수 (scene_detail 4-point MODULE_VERSIONS 패턴 아님)

세 step 모두 step 파일 안 `PROMPT_VERSION` 문자열 상수 1개 — `version_registry.py` 미참조 (grep 확인). prompt_loader 가 디렉토리명 `_version_sort_key` 로 최신 자동 픽 → 새 dir 생성이 곧 activation. `PROMPT_VERSION` 상수는 `_config_hash` (resume 무효화) 용.

| step 파일 | 상수 | 현재 | C4 목표 |
|-----------|------|------|---------|
| `background_chain_planning_step.py:22` | `PROMPT_VERSION` | `"4"` | `"5"` (W2) |
| `background_chain_planning_step.py:21` | `SCHEMA_VERSION` | `2` | **불변** |
| `background_classify_step.py:28` | `PROMPT_VERSION` | `"3"` | `"4"` (W3) |
| `background_classify_step.py:27` | `SCHEMA_VERSION` | `1` | **불변** |
| `location_floor_plan_step.py:39` | `PROMPT_VERSION` | `"3"` | `"4"` (W4) |
| `location_floor_plan_step.py:38` | `SCHEMA_VERSION` | `3` | **불변** |

W2/W3/W4 각 wave 는 (a) 새 prompt-pack 디렉토리 + (b) 해당 step 의 `PROMPT_VERSION` 상수 bump + 주석 — 2-point lockstep. `version_registry` cascade 없음. W2/W3/W4 에서 각 step 의 `PROMPT_VERSION` literal 을 grep 으로 pin 하는 test 유무는 wave 진입 시 `rg` 전수 확인 (현 W0 grep — `test_prompt_versions.py` 등에 background_* PROMPT_VERSION pin 미발견; wave 시 재확인 의무).

## 3. OQ Resolution (Codex C4 entry sanity 흡수)

### 3.1 OQ-C4-1 — 신규 producer vs narrow prompt hygiene

**결정: 옵션 B (narrow, C9/C6 pattern) — 신규 producer 미신설.** (Codex C4 entry sanity 명시.)

각 step 이 이미 자체 structured SOT 를 emit (`is_indoor` bool / `skip_chain` bool) 하고 D6 계열에 `location.space_profile` SOT + validator 가 존재하므로, `location_space_profile.py` 신규 producer 는 기존 SOT 중복 + 신규 step/DB/downstream rewiring/routing risk 만 키운다. C4 v1 = P043 code 수정 + 4 prompt-pack stem 닫힌 enumeration 추상화. cross-step 독립 재도출 통합은 C4 v1 필수조건 아님 — future architecture/carry.

### 3.2 OQ-C4-2 — A031 floor_plan color/glyph boundary review

**결정: A031 KEEP — `floor_plan_prompt` C4 변경 0.** (Codex C4 entry sanity 동의.)

`floor_plan_prompt/4/system.md:10-13` 의 색 매핑 (`living = pale yellow, bedroom = pale blue, ...`) + 가구 glyph (`bed = rectangle with pillow shape, ...`) 는 **schematic floor-plan diagram 의 deterministic rendering convention** — 색으로 구역을 식별하고 top-down glyph 로 가구를 표기하는 기술적 도식 규약이지, open-world semantic 분류기가 아니다. open-world 판단 (어느 방이 living 인지) 은 LLM 이 scene text 로 수행하고, 색/glyph 는 그 판단 결과에 적용되는 closed-world 표현 규약. Track B 4-Gate 위반 아님. closure docs (W5) 에서 A031 을 **"fixed" 아닌 "boundary-reviewed KEEP"** 으로 명시.

### 3.3 OQ-C4-3 — A020 entity_extractor allowed_space_keys

**결정: A020 — C4 scope 제외.** (Codex C4 entry sanity 동의.)

`entity_extractor_v2 allowed_space_keys` controlled vocab 는 (a) roadmap §5.8 audit row 목록 미포함, (b) audit row A020 = v9 기준 — file-fallback 기준 latest pack 은 v10 으로 이동했으나 `version_registry` 는 아직 `entity_extractor_v2/v9` 를 가리킨다 (DB/registry vs file-fallback 불일치 — C4 가 건드리지 않는 별도 carry 영역), (c) `entity_extractor_v2` 는 4턴 거대 prompt-pack (blast radius 큼), (d) `allowed_space_keys` 는 D6 `location.space_profile` SOT/validator 와 묶인 큰 영역. index.html C4 site table 에 cascade 로 들어왔으나 C4 closure claim 에 미포함 — C8/C10 또는 별도 carry. W5 closure docs 에서 index.html C4 marker 에 "A020 = C4 scope 제외, deferred" 명시.

### 3.4 OQ-C4-4 — wave 구조

**결정: 5-wave 분리.** (Codex C4 entry sanity 명시 — prompt-pack 3개를 한 wave 로 묶지 말 것.)

- **W1** = P043 code fix + C4 regression test (atomic commit).
- **W2** = `background_chain_planning` v5 prompt-pack (A001+A002+A003+A004) + `PROMPT_VERSION` bump + path-scoped residue/loader test (atomic commit).
- **W3** = `background_classify` v4 prompt-pack (A005) + `PROMPT_VERSION` bump + path-scoped residue/loader test (atomic commit).
- **W4** = `location_floor_plan` v4 prompt-pack (A033+A034) + `PROMPT_VERSION` bump + path-scoped residue/loader test (atomic commit).
- **W5** = integrated cross-cutting static/residue/compat gate + closure docs atomic.

각 prompt wave = 새 dir + schema sibling (background_classify 는 byte-identical, background_chain_planning 은 A001 description 만 변경 / 구조 byte-identical, location_floor_plan 은 schema 없음) + path-scoped residue gate + no live LLM / NO VLM.

### 3.5 OQ-C4-5 — C4 vs C8/#9 overlap

**결정: C4 row 와 C8 sub-area row 는 별개 — 중복 closure claim 없음.** (Codex C4 entry sanity 확인.)

C4 rows = `background_chain_planning` (P043+A001-A004), `background_classify` (A005), `location_floor_plan` (A033/A034). C8 sub-area (d) = `background_chain_render`, `background_master_plan`, `floor_plan_prompt:16` (A032). `floor_plan_prompt` 만 파일 공유지만 C4 의 A031 (`:10-13`, KEEP) 과 C8 의 A032 (`:16`) 는 다른 row. 중복 closure claim 금지 — W5 closure docs 가 명시.

### 3.6 P043 — no-match 처리 설계 (Codex spec review v1 NEEDS_REVISION_NARROW_1 BLOCKING 1 흡수)

Codex C4 entry sanity P043 narrow guard: `L\d+` direct + exact `name_to_sid.get(loc_raw)` 보존, substring fallback `(c)` 만 제거, no-match 시 `loc_id = loc_raw` silent fallback 금지.

**현 caller contract**: `_phase0_group_by_location` → `run_background_chain_planning` (try/except 없음) → `BackgroundChainPlanningStep.run`. no-match shot 은 현재 `loc_id = loc_raw` 비정규 key 로 group 화 → metadata 없는 group 이 chain 설계됨 (silent corruption).

**결정: substring loop `(c)` 폐기 + no-match 시 `ValueError` raise (fail-loud).** substring fallback loop (`:151-154`) 제거 후, exact match (`name_to_sid.get(loc_raw)`) + `L\d+` direct 모두 실패하면 `loc_id = loc_raw` silent fallback 줄 (`:155-157`) 을 **`ValueError` raise** 로 치환 — 진단 메시지에 unresolved `loc_raw` 명시.

**claude v1 제안 (옵션 1 — `logger.warning` + `continue` skip) 기각** (Codex spec review v1 BLOCKING 1): nonempty `loc_raw` no-match 는 empty `loc_raw` skip (`:141-143`) 과 성격이 다르다 — plain `continue` 시 selected shot 이 `LocationGroup` 생성 전에 사라져 checkpoint-visible `locations_out` entry / `_failed_count` / `status` 가 남지 않는다. `validate_plan()` 의 "every input shot exactly once" 검사는 이미 만들어진 `LocationGroup` 내부만 보므로 group 미진입 shot 을 검출 못 한다. low-frequency/skip_chain 의 prev-shot fallback 은 explicit `locations_out[lid] = {status:"skipped"}` 경로 전용 — phase0 no-match `continue` 에는 해당하지 않는다. 따라서 plain skip+warning 은 invisible drop = **No Silent Fallback 위반 risk**.

**fail-loud (`ValueError`) 채택 근거** (Codex spec review v1 권장 = option A): (a) `background_chain_planning` 은 deprecated+disabled legacy path — 한 shot 데이터 문제로 step abort 해도 production 영향 0 (자동 실행 없음), blast radius 허용 가능, (b) 구현이 가장 좁다 — `loc_id = loc_raw` 줄을 `raise ValueError(...)` 로 치환, 신규 plumbing 0, (c) `primary_location` 은 scene_director 가 short_id 로 emit 하는 contract — resolve 실패 = 상위 데이터 contract 위반이므로 fail-loud 가 doctrine 정합. 신규 custom exception class 미신설 — `ValueError` + 진단 메시지 (over-engineering 회피, `background_classify.ClassifyError` 같은 모듈 전용 class 는 deprecated 모듈에 불필요).

**대안 (option1b — checkpoint-visible failed/unresolved entry + `_failed_count` 반영)** 은 Codex 가 허용했으나 신규 counter/list plumbing 필요 — narrow 우선상 미채택.

W1 regression test 가 (a) substring fallback 부재 (substring-overlap 하지만 exact 불일치 `loc_raw` 가 더 이상 esid 로 resolve 안 됨 → `ValueError`), (b) no-match `ValueError` raise, (c) exact/`L\d+` path 보존을 검증.

### 3.7 deprecated step 처리

`background_chain_planning` = step_manifest `lifecycle: "deprecated"` + `applicability: "disabled"` (Phase 7 deprecation, "코드는 보류 — Phase 7.1 cleanup PR 에서 별도 삭제 예정"). `location_floor_plan` = `lifecycle: "deprecated"` (Phase 7 — floor_plan_prompt+floor_plan_render 가 대체). `background_classify` = `lifecycle: "active"`.

**결정**: C4 는 audit 가 CONFIRMED P1/P2 로 flag 한 **active latest prompt/code residue** 를 좁게 닫는다 — repo 에 prompt/code 가 존재하는 한 doctrine debt 는 실재. step lifecycle/applicability entry 는 **변경 0**. W5 closure docs 에 `background_chain_planning` (W1+W2) 와 `location_floor_plan` (W4) 의 deprecated 상태를 명시 (Codex C4 entry sanity 추가 spec guard).

deprecated 모듈에 v5/v4 신규 prompt-pack dir 신설 — **Codex spec review v1 APPROVED**: CLAUDE.md "프롬프트 파일 덮어쓰기 금지" + prompt_loader 자동 latest-pick 규칙상 새 v5/v4 dir 이 정답 (deprecated 여부와 무관하게 적용). step_manifest lifecycle/applicability 변경 0 유지. W2/W4 gate (G6/G12) 가 latest source + `PROMPT_VERSION` sync 를 증명.

## 4. Invariants

### 4.1 No closed-list / closed-enumeration invariant
W2/W3/W4 신규 prompt-pack 의 대상 섹션 어디에도 (a) `background_chain_planning` SKIP DECISION 의 indoor/outdoor 예시 enumeration (`room, hallway, store interior, ...` / `a road far from any building, an unrelated forest, ...`) + node state 닫힌 목록 (`clean / lived-in / disturbed / heavily-ransacked`) + `skip_chain` description OUTDOOR 목록 (`street, road, coast, sea, forest, ...`), (b) `background_classify` 의 한국어 indoor/outdoor keyword 목록 (`내부 / 안 / 방 / 실 / 층` · `외부 / 옥상 / 거리 / 도로 / ...`), (c) `location_floor_plan` 의 한국어 방 이름 cue (`거실/안방, 부엌/거실`) + 한국어 이동 동사 (`문을 열고 들어간다` 등) + slash-zone 한국어 예시 (`옥탑방 안 / 실내`, `한옥 / 안방 / 마루`) 잔존 0.

### 4.2 Intent-preserve invariant
각 추상화는 *판단 의도* 를 generic semantic principle 로 보존 — skip 결정 ("persistent 건축 구조를 유지해야 하는 공간인가"), indoor/outdoor 판단 ("벽·천장으로 둘러싸인 내부 공간인가"), room-count 추론 ("scene text 가 별개 공간으로 다루는가"). 닫힌 토큰/예시 목록만 제거.

### 4.3 Structured emit preserve invariant
`background_classify` schema 의 `members[].is_indoor` (`type:boolean`, required) + `background_chain_planning` schema 의 `skip_chain` (`type:boolean`, required) — field 구조 byte-identical 보존. LLM 의 structured SOT emit 계약 불변.

### 4.4 Schema structure invariant
- `background_chain_planning` v5 `schema.json` — v4 대비 **JSON structural diff allowlist = `properties.skip_chain.description` 한 곳만** (A001 description 산문 추상화). 그 외 모든 field/enum/required/type/`additionalProperties`/구조 동일. **전체 file byte-identical 아님** — description 1곳만 상이 (Codex spec review v1 §4.4 APPROVED: gate 는 "전체 byte-identical" 아닌 JSON structural diff allowlist = only `properties.skip_chain.description`).
- `background_classify` v4 `schema.json` — v3 와 **sha256 동일** (byte-identical sibling — A005 는 system.md only).
- `location_floor_plan` — schema.json 없음 (해당 없음).
- 세 step `SCHEMA_VERSION` 상수 — 불변 (구조 schema 무변경; description-only 변경은 SCHEMA_VERSION bump 대상 아님).

### 4.5 PROMPT_VERSION 2-point sync invariant
W2/W3/W4 각각: 새 prompt-pack 디렉토리 + 해당 step 의 `PROMPT_VERSION` 상수 (`"5"` / `"4"` / `"4"`) lockstep. `SCHEMA_VERSION` 상수 불변.

### 4.6 Code path preserve invariant (P043)
W1 후 `background_chain_planning.py` location resolution 의 `L\d+` direct path + exact `name_to_sid.get(loc_raw)` path 동작 byte-equivalent 보존. substring fallback loop (`:151-154`) 만 제거 + no-match 시 `ValueError` fail-loud (silent `loc_id = loc_raw` 폐기, §3.6).

### 4.7 NO VLM / no live LLM invariant
C4 코드·테스트 어디에도 VLM 의존 / live LLM 호출 0. C4 regression/residue test = 파일 read + 문자열 검사 + (background_classify) sha256 + (P043) 순수 함수 단위 test only.

### 4.8 Boundary preserve invariant
- `floor_plan_prompt/4/` 디렉토리 (A031 KEEP) — 변경 0.
- `entity_extractor_v2/` (A020) — 변경 0.
- 세 step 의 `SCHEMA_VERSION` 상수 + step_manifest entry (lifecycle/applicability/depends_on) — 변경 0.
- `background_classify` system.md 의 개념 문장 + 영문 `(e.g., a room, ...)` illustration — 보존.
- `location_floor_plan` system.md `:14` (`Korean common nouns are OK in parentheses for room labels`) — 보존.
- v4/v3 (및 이전) prompt-pack 디렉토리 — 변경 0 (새 v5/v4 디렉토리만 추가).
- C1·C2·C3·C6·C7·C9 산출물 — 변경 0.

## 5. W1-W5 Implementation Plan

### 5.1 W1 — P043 code fix + C4 regression test (atomic commit)

- `background_chain_planning.py:146-157` location resolution 수정 — substring fallback loop (`:151-154`) 폐기 + no-match silent fallback (`:155-157` `loc_id = loc_raw`) 를 §3.6 결정 (exact·`L\d+` 모두 실패 시 `ValueError` raise, fail-loud — 진단 메시지에 unresolved `loc_raw` 명시) 으로 치환. `L\d+` direct + exact match path 불변.
- `backend/tests/pipeline/test_background_chain_planning.py` 에 C4 regression test 추가 (또는 신규 `test_c4_*` file — plan 에서 확정) — Gate G1-G3 (§7).
- **W1 gate**: 신규 test PASS / `test_background_chain_planning.py` 전수 PASS (기존 phase0 test 회귀 0) / 백엔드 전체 회귀 pre-existing 제외 동일.

### 5.2 W2 — `background_chain_planning` v5 prompt-pack (atomic commit)

- `prompts/_base/background_chain_planning/5.<W2_UTC>/` 신규 — `system.md` (A003 SKIP DECISION 예시 enumeration + A004 node state 목록 추상화) + `schema.json` (A001 `skip_chain` description 추상화, 구조 byte-identical) + `user_template.md` (A002 분기 label 추상화). 나머지 모든 줄 byte-identical.
- `background_chain_planning_step.py:22` `PROMPT_VERSION "4"→"5"` + 주석 (C4 v1).
- path-scoped residue test + loader latest-pick test (Gate G4-G6).
- **W2 gate**: residue/loader test PASS / `test_background_chain_planning.py`·`test_background_chain_planning_step` 전수 PASS / 백엔드 회귀 동일.

### 5.3 W3 — `background_classify` v4 prompt-pack (atomic commit)

- `prompts/_base/background_classify/4.<W3_UTC>/` 신규 — `system.md` (A005 한국어 keyword 닫힌 목록 + 한국어 매핑 예시 제거, 개념+영문 e.g. 보존) + `schema.json`/`user_template.md` byte-identical sibling (sha256 동일).
- `background_classify_step.py:28` `PROMPT_VERSION "3"→"4"` + 주석.
- path-scoped residue test + loader test + schema sibling sha256 test (Gate G7-G9).
- **W3 gate**: residue/loader/sha256 test PASS / `test_background_classify*` 전수 PASS / 백엔드 회귀 동일.

### 5.4 W4 — `location_floor_plan` v4 prompt-pack (atomic commit)

- `prompts/_base/location_floor_plan/4.<W4_UTC>/` 신규 — `system.md` (A033 한국어 방 이름/이동 동사 cue + A034 slash-zone 예시 추상화) + `user_template.md` byte-identical sibling.
- `location_floor_plan_step.py:39` `PROMPT_VERSION "3"→"4"` + 주석.
- path-scoped residue test + loader test (Gate G10-G12).
- **W4 gate**: residue/loader test PASS / `test_location_floor_plan*` 전수 PASS / 백엔드 회귀 동일.

### 5.5 W5 — integrated gate + closure docs atomic (path-limited)

- cross-cutting static gate — 4 신규 prompt-pack + P043 code 의 통합 residue 재검 + 세 step `PROMPT_VERSION`/`SCHEMA_VERSION` 정합 + 백엔드 전체 회귀.
- closure docs atomic:
  - 본 spec frontmatter `status` → closed + §10 Closure.
  - C4 plan frontmatter `status` → closed + plan Closure section.
  - roadmap §5.8 — C4 closed marker (인접 §5.10 C6 / §5.16 C9 형식 정합).
  - `docs/fix-critical-1/index.html` C4 marker — badge ✓ CLOSED + priority list C4→CLOSED / C5→NEXT + **index.html 9-step 원안 정정** ("superseded by existing D6 location.space_profile / C4 v1 narrow option") + A031 boundary-reviewed KEEP 명시 + A020 C4 scope 제외 명시 + `background_chain_planning`/`location_floor_plan` deprecated 상태 명시.
  - audit `triage_report.md` 참조 — C4 closure 위치는 plan 에서 확정 (triage_report.md 가 untracked artifact 면 git-tracked closure 위치는 roadmap/index.html 로 한정 — wave 시 `git ls-files` 확인).
- **W5 self-hash 금지** — closure docs 안 W5 자체 commit hash literal 없음. 실제 hash 는 push 후 external memory 기록.
- **W5 gate**: closure docs 정합 + W1-W5 range review.

## 6. Boundary Preserve

- `floor_plan_prompt/4/` (A031 KEEP) + `entity_extractor_v2/` (A020) — 변경 0.
- 세 step `SCHEMA_VERSION` 상수 + step_manifest entry — 변경 0.
- `background_chain_planning.py` 의 `L\d+`/exact match path + `validate_plan` + Phase 1/2 + planner-driven path — P043 substring loop 외 변경 0.
- `background_classify` 의 개념 문장 + 영문 e.g. + schema + `validate_classify_output` + step 의 `indoor_loc_ids=None` — 변경 0.
- `location_floor_plan` system.md `:14` Korean-label-OK 규칙 + multi-turn context 섹션 — 변경 0.
- v4/v3 (및 이전) prompt-pack 디렉토리 — 변경 0 (새 디렉토리만 추가).
- C1·C2·C3·C6·C7·C9 산출물 — 변경 0.

## 7. Test Gates

C4 test = 파일 read + 문자열 검사 + sha256 + 순수 함수 단위 test only. no live LLM, NO VLM. path-scoped — 닫힌-form 안정 substring marker 검사 (bare 토큰 global grep 금지).

### W1 — P043 (`test_background_chain_planning.py` 또는 신규 `test_c4_*`)

| Gate | 내용 |
|------|------|
| **G1** | substring fallback 부재 — substring-overlap 하지만 exact 불일치하는 `loc_raw` (어떤 entity name 의 부분 문자열이지만 정확히 일치하지 않는 값) 가 더 이상 esid 로 resolve 되지 않고 `ValueError` 발생. `_phase0_group_by_location` 단위 호출로 검증. |
| **G2** | no-match fail-loud — unresolved `loc_raw` (exact·`L\d+` 모두 실패) 시 `_phase0_group_by_location` 이 `ValueError` raise (§3.6). 비정규 key group 미생성, `loc_id = loc_raw` silent fallback 부재 — `pytest.raises(ValueError)` 로 검증. |
| **G3** | exact/`L\d+` path 보존 — `L04` direct + 한국어 exact name (`거실` → `L02`) resolve 정상 (기존 `test_phase0_*` 회귀 0). |

### W2 — `background_chain_planning` v5

| Gate | 내용 |
|------|------|
| **G4** | closed enumeration 부재 — v5 `system.md` SKIP DECISION 섹션 + node-state 섹션에 indoor/outdoor 예시 enumeration 안정 marker (`store interior, office, kitchen` / `a road far from any building` / `clean / lived-in / disturbed`) 0; v5 `schema.json` `skip_chain` description 에 `street, road, coast, sea, forest` 0; v5 `user_template.md` 에 `(outdoor / open-air)`·`(indoor / enclosed / fixed-set)` 0. |
| **G5** | intent + schema 구조 보존 — v5 `system.md` 에 SKIP DECISION 개념 ("SHARED BUILDING SET" / "DETACHED OPEN AREA") + node-split principle 존재; v5 `schema.json` 의 v4 대비 **JSON structural diff = `properties.skip_chain.description` 한 곳만** (allowlist 검사 — 양 schema 를 JSON parse 후 description 키를 제외한 구조 비교 시 diff 0; field/type/enum/required/`additionalProperties` 모두 동일). 전체 file byte-identical 검사 아님. |
| **G6** | `PROMPT_VERSION` sync — `background_chain_planning_step.PROMPT_VERSION == "5"` + `5.<W2_UTC>/` 디렉토리 존재 + prompt_loader latest-pick = v5. |

### W3 — `background_classify` v4

| Gate | 내용 |
|------|------|
| **G7** | 한국어 keyword 닫힌 목록 부재 — v4 `system.md` 에 `내부 / 안 / 방 / 실 / 층` · `외부 / 옥상 / 거리 / 도로` 슬래시-form 한국어 keyword 목록 + `Korean clues` 문구 0. |
| **G8** | 개념 보존 + schema sibling — v4 `system.md` 에 indoor/outdoor 개념 문장 ("enclosed structure with walls and a ceiling" / "outside with sky overhead") 존재; v4 `schema.json` sha256 == v3 `schema.json` sha256; v4 `user_template.md` sha256 == v3. |
| **G9** | `PROMPT_VERSION` sync — `background_classify_step.PROMPT_VERSION == "4"` + `4.<W3_UTC>/` 디렉토리 존재 + prompt_loader latest-pick = v4. |

### W4 — `location_floor_plan` v4

| Gate | 내용 |
|------|------|
| **G10** | 한국어 토큰 예시 부재 — v4 `system.md` 에 `거실/안방, 부엌/거실` 방 이름 cue + `문을 열고 들어간다` 이동 동사 + `옥탑방 안 / 실내`·`한옥 / 안방 / 마루` slash-zone 예시 0. |
| **G11** | 개념 보존 + boundary — v4 `system.md` 에 room-count/zone-marker generic principle 존재 + `:14` Korean-label-OK 규칙 (`main bedroom (안방)`) 보존; v4 `user_template.md` sha256 == v3. |
| **G12** | `PROMPT_VERSION` sync — `location_floor_plan_step.PROMPT_VERSION == "4"` + `4.<W4_UTC>/` 디렉토리 존재 + prompt_loader latest-pick = v4. |

### W5 — integrated

| Gate | 내용 |
|------|------|
| **G13** | 통합 residue — G4/G7/G10 의 닫힌-form marker 가 4 신규 prompt-pack 전체에서 0 (cross-pack 재검). |
| **G14** | 백엔드 전체 회귀 — pre-existing failure 제외 동일 (clean HEAD `16abb40` git stash 대조). |

## 8. Risk + Open Issues

- **R1 — bare 토큰 grep self-hit**: `room` / `office` / `forest` / `방` 등은 prompt 의 무관 위치에 정당하게 등장. residue gate (G4/G7/G10) 는 반드시 (a) 해당 prompt-pack 의 대상 섹션 path-scoped slice, (b) 닫힌-form 안정 multi-word/slash-form marker (`store interior, office, kitchen` / `내부 / 안 / 방 / 실 / 층` / `거실/안방, 부엌/거실`) 검사 — bare 단어 grep 금지. C9 R1 (cutout/windshield self-hit) 선례 흡수.
- **R2 — production routing 영향 (roadmap §5.8 Risk)**: `background_chain_planning` 의 skip_chain 결정 분포가 prod chain image rendering 비용에 영향. 그러나 (a) C4 는 닫힌 *예시 목록* 만 제거하고 skip 결정 *개념/지점* 은 보존 — LLM 의 판단 축 불변, (b) `background_chain_planning` 은 deprecated+disabled (자동 실행 0). production canary 는 prompt 의미 동치 (개념 보존) + 백엔드 회귀로 갈음 — live LLM/VLM canary 금지 ([[feedback_no_vlm_dependency]]).
- **R3 — deprecated 모듈 prompt-pack 신설**: `background_chain_planning`/`location_floor_plan` 은 deprecated. v5/v4 신규 dir 신설 — **Codex spec review v1 APPROVED** (CLAUDE.md overwrite 금지 + prompt_loader latest-pick 규칙상 정당). step_manifest lifecycle/applicability 변경 0 유지, W2/W4 gate (G6/G12) 가 latest source + `PROMPT_VERSION` sync 증명.
- **R4 — A001 schema description 변경**: A001 = schema.json 의 `skip_chain.description` 산문 추상화 — **Codex spec review v1 APPROVED**. `skip_chain.description` 은 LLM-facing closed list 이므로 추상화 대상. field/type/required/`additionalProperties` 구조 + `SCHEMA_VERSION` 불변. Gate G5 = "전체 byte-identical" 아닌 **JSON structural diff allowlist = only `properties.skip_chain.description`** (§4.4 / §7 G5).
- **OQ-잔여**: OQ-C4-1~5 (§3.1-3.5) + Codex spec review v1 3개 item (P043 no-match → §3.6 fail-loud `ValueError` 채택 / deprecated prompt-pack → R3 APPROVED / A001 schema → R4 APPROVED) 모두 resolve. **미해소 OQ 0.**

## 9. Doctrine references

- [[feedback_llm_based_judgment]] — 4-Gate. C4 = **Prompt Closed-List Ban** (A001-A005/A033/A034 — 닫힌 enumeration → generic principle) + **Semantic Regex Ban** (P043 — code substring 매칭 폐기) + **No Silent Fallback** (P043 no-match explicit 처리). indoor/outdoor·skip·room-count open-world 판단은 producer LLM 소관, structured SOT (`is_indoor`/`skip_chain` bool) 는 이미 emit 중 — code 는 enum/bool 만 소비.
- [[feedback_no_vlm_dependency]] — NO VLM. C4 코드·테스트 VLM 의존 0. production canary 도 VLM/live LLM 금지.
- [[feedback_codex_mcp_discussion_workflow]] / [[feedback_codex_mcp_claude_mcp_response_trigger]] — 매 wave Codex MCP per-wave review, tmux-bridge raw typing + prefix/suffix.
- [[feedback_no_user_ask_codex_only]] — OQ·결정 사용자 ask 0, Codex MCP 의논.
- [[feedback_channel_separation_4way]] — 4채널 fact source 구분.
- [[project_fix_critical_1_persistent]] — fix-critical-1 Tier β #7, C4 → C5 priority.

## 10. Closure (2026-05-20+)

**Status: closed** — C4 v1 W1-W5 5-wave 실행 완료. fix-critical-1 Tier β #7, roadmap §5.8 Area #8. 8 audit row (P043 + A001/A002/A003/A004/A005/A033/A034) 해소.

### 10.1 Commit chain
- W1 `2c018f3` — P043 `background_chain_planning.py` location resolve substring fallback 폐기 (exact·L-id 실패 시 `ValueError` fail-loud) + C4 regression test 3건. 2 file, 73+/7-.
- W2 `7c9a605` — `background_chain_planning` v5 prompt-pack (`5.202605200932/`) — SKIP DECISION/node-state/`skip_chain` description 닫힌 enumeration 추상화 (A001/A002/A003/A004) + `PROMPT_VERSION` 4→5 + C4 residue test G4-G6. 5 file, 288+/1-.
- W3 `4bf2c72` — `background_classify` v4 prompt-pack (`4.202605200945/`) — indoor/outdoor 한국어 keyword 닫힌 목록 폐기 (A005) + `PROMPT_VERSION` 3→4 + C4 residue test G7-G9. 5 file, 162+/1-.
- W3 fix-up 1 `1130cf9` — `background_classify` v4 `system.md` header `v3`→`v4` 정정 (cp 후 미갱신 stale version 자기서술). 1 file, 1+/1-.
- W4 `ddcd115` — `location_floor_plan` v4 prompt-pack (`4.202605201000/`) — room-count cue 한국어 토큰 + slash-zone 예시 추상화 (A033/A034) + `PROMPT_VERSION` 3→4 + C4 residue test G10-G12. 4 file, 114+/1-.
- W5 closure commit — 본 closure docs atomic (4 docs path-limited: spec + plan + roadmap §5.8 + index.html C4 marker). hash 는 push 후 external memory 기록 (W5 self-hash 금지).

### 10.2 Gate result
- W1 — Gate W1.1 `test_background_chain_planning.py` 38 passed (기존 35 + 신규 3) / W1.2 백엔드 전체 5 failed / 4068 passed / 37 skipped.
- W2 — W2.1 C4 test 3 passed / W2.2 background_chain_planning 41 passed / W2.3 executable residue OK / W2.4 5 failed / 4071 passed / 37 skipped.
- W3 — W3.1 C4 test 6 passed / W3.2 background_classify 21 passed / W3.3 residue OK / W3.4 5 failed / 4074 passed / 37 skipped.
- W4 — W4.1 C4 test 9 passed / W4.2 location_floor_plan 42 passed·1 skipped / W4.3 residue OK / W4.4 5 failed / 4077 passed / 37 skipped.
- W5 — W5.1 C4 전체 test 47 passed (G1-G12 + 기존 phase0/validate_plan) / W5.2 cross-pack residue OK / W5.3 3-step PROMPT_VERSION/SCHEMA_VERSION sync OK / W5.4 백엔드 전체 회귀.
- 5 failures = pre-existing (`test_analysis_dispatch_service` ×1 / `test_prompt_service` ×1 / `test_text_cleanup` ×3 — `clean_text` ImportError, `test_text_cleanup.py` untracked 신규 파일). W1 에서 clean HEAD `16abb40` git stash 대조로 pre-existing 확정. C4 W1-W5 신규 failure 0.

### 10.3 Codex review trace
- C4 entry sanity — `APPROVED_FOR_SPEC_DRAFTING_WITH_REVISED_SCOPE` (옵션 B narrow, 신규 producer NO-GO, 5-wave, A031 KEEP, A020 제외).
- spec review v1 `NEEDS_REVISION_NARROW_1` (BLOCKING 1: P043 no-match fail-loud) → narrow revision → spec re-review v1 `APPROVED_FOR_PLAN`.
- plan review v1 `NEEDS_REVISION_NARROW_1` (BLOCKING 1: triage_report.md generated-output 미수정) → narrow revision → plan re-review v1 `APPROVED_FOR_EXECUTION`.
- W1 entry sanity `APPROVED_TO_ENTER_W1` / W1 per-wave `APPROVED_FOR_W2_ENTRY`.
- W2 entry sanity `APPROVED_TO_ENTER_W2` / W2 per-wave `APPROVED_FOR_W3_ENTRY`.
- W3 entry sanity `APPROVED_TO_ENTER_W3` / W3 per-wave `NEEDS_REVISION_NARROW_1` (header v3→v4) → fix-up 1 → W3 re-review `APPROVED_FOR_W4_ENTRY`.
- W4 entry sanity `APPROVED_TO_ENTER_W4` / W4 per-wave `APPROVED_FOR_W5_ENTRY`.
- W1-W5 range review — W5 closure commit 후 기재 (push 후 external memory).

### 10.4 함정 (lessons)
- **N-1 prompt-pack cp 후 version-stamped self-description 미갱신**: W3 `background_classify` v4 `system.md:1` header 가 `cp -r` 직후 `(v3 — LLM determines is_indoor)` 그대로 잔존 → Codex W3 per-wave `NEEDS_REVISION_NARROW_1` → W3 fix-up 1 (`1130cf9`) 로 `(v4 — ...)` 정정. 향후 prompt-pack cp wave 는 새 dir 의 header 등 version-stamped 자기서술도 동기 의무. (`background_chain_planning`/`location_floor_plan` system.md 는 version stamp 부재 → W2/W4 무이슈.)
- **N-2 residue marker 와 markdown bold**: W3 G8 의 outdoor 개념 marker `outside with sky overhead` 가 system.md 의 `**outside** with sky overhead` (markdown bold) 때문에 부분 문자열 부재 → G8 최초 실패. 안정 marker `with sky overhead` 로 정정. plan §5.5 G8 literal 은 doc-vs-actual 미세 불일치 (비권위).
- **N-3 A001 schema description-only 변경**: `skip_chain.description` 산문만 추상화 — gate 는 "전체 byte-identical" 아닌 JSON structural diff allowlist (`properties.skip_chain.description` 한 곳만). `SCHEMA_VERSION` 불변.
- **N-4 generated audit-output 미수정**: `triage_report.md` (full_20260516_0105) 는 git-tracked 이나 generated audit-output — W5 closure marker 대상 아님 (source evidence only). closure marker = narrative docs (roadmap §5.8 + index.html) 한정 (Codex plan review v1 BLOCKING 1 흡수).

### 10.5 carry priority
C4 closed → 다음 = C5 (#9 action segment SOT, Tier β #8, roadmap §5.9). 잔여 = C5/C8/C10.
