---
title: C4 — Background / floor-plan semantic classifier prompt hygiene + P043 substring fallback 폐기 v1 implementation plan (fix-critical-1 Tier β #7)
date: 2026-05-20+
status: closed (C4 v1 plan — W1-W5 5-wave 실행 완료, Codex per-wave APPROVED. W1 `2c018f3` / W2 `7c9a605` / W3 `4bf2c72` + fix-up 1 `1130cf9` / W4 `ddcd115`. §8 Closure 참조. 종합 closure 는 spec §10.)
spec_ref: docs/superpowers/specs/2026-05-20-c4-background-classifier-v1-design.md (status APPROVED_FOR_PLAN)
roadmap_ref: docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md §5.8 (Area #8)
fix_critical_doc_ref: docs/fix-critical-1/index.html C4 section
prerequisites:
  - C4 spec v1 APPROVED_FOR_PLAN (Codex C4 entry sanity APPROVED_FOR_SPEC_DRAFTING_WITH_REVISED_SCOPE → spec review v1 NEEDS_REVISION_NARROW_1 → narrow revision → spec re-review v1 APPROVED_FOR_PLAN).
  - C1 / C2 / C3 / C7 / C6 / C9 v1 (closed + pushed, origin/main HEAD `16abb40`) — HEAD anchor. 변경 0.
non_supersedes:
  - C5 #9 / C8 #7 / C10 (fix-critical-1 잔여 area). A020 entity_extractor / A031 floor_plan_prompt KEEP / A032 — C4 scope 외.
---

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

## 1. Purpose + Spec Reference

C4 = roadmap §5.8 Area #8. **8 audit row 해소** — code-side `background_chain_planning.py:151-154` location 이름 substring 매칭 (P043) 폐기 + 3개 prompt-pack stem 의 닫힌 enumeration (A001/A002/A003/A004 background_chain_planning, A005 background_classify, A033/A034 location_floor_plan) 을 generic semantic principle 로 추상화. **A031 floor_plan_prompt = boundary-reviewed KEEP (변경 0)**, **A020 entity_extractor = C4 scope 제외**.

- spec: `docs/superpowers/specs/2026-05-20-c4-background-classifier-v1-design.md` §1-§10.
- **5-wave** (Codex C4 entry sanity OQ-C4-4 — prompt-pack 3개 한 wave 묶음 금지):
  - **W1** = P043 code fix + C4 regression test (1 atomic commit).
  - **W2** = `background_chain_planning` v5 prompt-pack (A001+A002+A003+A004) + `PROMPT_VERSION` bump + residue/loader test (1 atomic commit).
  - **W3** = `background_classify` v4 prompt-pack (A005) + `PROMPT_VERSION` bump + residue/loader test (1 atomic commit).
  - **W4** = `location_floor_plan` v4 prompt-pack (A033+A034) + `PROMPT_VERSION` bump + residue/loader test (1 atomic commit).
  - **W5** = integrated cross-pack residue/compat gate + closure docs atomic.
- HEAD anchor = `16abb40` (origin/main, C9 v1 closed).
- 세 step `SCHEMA_VERSION` 상수 / step_manifest entry (lifecycle/applicability) / `floor_plan_prompt` / `entity_extractor_v2` — 변경 0 (spec §1.3 / §4 / §6).
- `background_chain_planning` (lifecycle deprecated / applicability disabled) + `location_floor_plan` (lifecycle deprecated) — active latest prompt/code residue 만 좁게 처리, lifecycle entry 불변 (spec §3.7).

## 2. Plan Iter Trace

- iter 0 (2026-05-20+): plan v1 drafting.
- iter 1 (2026-05-20+): Codex plan review v1 `NEEDS_REVISION_NARROW_1` 흡수 — BLOCKING 1 (§7.3 `triage_report.md` 는 generated audit-output, git-tracked 여부와 무관하게 W5 미수정 — closure marker 대상은 narrative docs 한정) + IMPORTANT 3 (§7.2 Gate W5.2 count wording "4 신규"→"3 신규 prompt-pack dir" / W2.3·W4.3 executable residue gate needle 보강 — `(indoor / enclosed / fixed-set)` + `한옥 / 안방 / 마루` 추가 / §6.3 W4.1a movement bullet 을 영어 mini-list 대신 generic 문장으로).
- iter 2 (2026-05-20+): Codex plan re-review v1 `APPROVED_FOR_EXECUTION`. MINOR execution note 흡수 — §7.2 Gate W5.2 heredoc `checks` 에 A002 indoor marker (`(indoor / enclosed / fixed-set)`) + A034 second slash-zone marker (`한옥 / 안방 / 마루`) 추가 (gate 설명 정합).

## 3. W1 — P043 code fix + C4 regression test (1 atomic commit)

### 3.1 Pre-W1 Entry Sanity

- W1 진입 전 Codex W1 entry sanity 받을 것 (production code edit 0 상태에서 송신).
- 진입 직전 read: 승인된 spec + 본 plan + `background_chain_planning.py:140-160` 현재 상태 (P043 anchor `:151-154` 가 현 HEAD `16abb40` 와 line drift 없는지 확인) + `test_background_chain_planning.py` phase0 test 섹션 (`:245-304`).
- HEAD = `16abb40` verify.

### 3.2 W1 File Operations (1 code + 1 test = 2 file, 1 atomic commit)

| op | file |
|----|------|
| modify | `backend/app/modules/pipeline/background_chain_planning.py` (location resolution `:146-157`) |
| modify | `backend/tests/pipeline/test_background_chain_planning.py` (+P043 regression test 3건) |

`git add` 명시 경로만 (global add 금지).

### 3.3 P043 code fix (W1.1)

`background_chain_planning.py` location resolution 의 substring fallback loop + silent fallback 치환. 현재 (`:146-157`):

```python
            # primary_location → short_id 정규화
            if loc_raw.startswith("L") and loc_raw[1:].isdigit():
                loc_id = loc_raw
            else:
                loc_id = name_to_sid.get(loc_raw, "")
                if not loc_id:
                    for ename, esid in name_to_sid.items():
                        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
                    logger.warning("Phase 0: primary_location '%s' not matched", loc_raw)
```

치환 후 (substring loop `:151-154` + silent fallback `:155-157` 폐기 → fail-loud):

```python
            # primary_location → short_id 정규화
            if loc_raw.startswith("L") and loc_raw[1:].isdigit():
                loc_id = loc_raw
            else:
                # C4/P043: name substring 양방향 매칭 fallback 폐기 (Semantic Regex Ban).
                # exact name match·L-id 모두 실패 = primary_location contract 위반 →
                # silent loc_id=loc_raw fallback 금지, fail-loud (No Silent Fallback).
                loc_id = name_to_sid.get(loc_raw, "")
                if not loc_id:
                    raise ValueError(
                        f"Phase 0: primary_location {loc_raw!r} did not resolve to "
                        f"a known location short_id or exact entity name "
                        f"(substring fallback removed — C4/P043)"
                    )
```

- `L\d+` direct (`:147-148`) + exact `name_to_sid.get(loc_raw)` (`:150`) path 동작 불변 (spec §4.6).
- substring loop (`:151-154`) + `loc_id = loc_raw` silent fallback + `logger.warning` (`:155-157`) 폐기 → `ValueError` fail-loud (spec §3.6).
- 신규 custom exception class 미신설 — `ValueError` + 진단 메시지.
- `logger` 모듈 변수는 다른 site 에서 계속 사용 — import 변경 0.

### 3.4 C4 W1 regression test (W1.2)

`backend/tests/pipeline/test_background_chain_planning.py` phase0 test 섹션 (`:245` 이후) 에 3건 추가. 순수 함수 단위 test — no live LLM, NO VLM.

| Gate | test 함수 | assertion |
|------|----------|-----------|
| G1 | `test_phase0_substring_fallback_removed` | `loc_raw` 가 어떤 entity name 의 부분 문자열이지만 exact 불일치 (예: entity name `"옥탑방 거실"`, `primary_location="거실"`) → 더 이상 substring 으로 resolve 안 됨 → `pytest.raises(ValueError)`. |
| G2 | `test_phase0_unresolved_location_raises` | `loc_raw` 가 `L\d+` 도 아니고 exact name match 도 없음 → `_phase0_group_by_location` 이 `ValueError` raise (`pytest.raises(ValueError)`, 메시지에 `loc_raw` 포함). 비정규 key group 미생성. |
| G3 | `test_phase0_exact_and_lid_path_preserved` | `L04` direct id + 한국어 exact name (`거실` → entity name 정확 일치 → `L02`) resolve 정상. (기존 `test_phase0_groups_selected_shots_by_location` / `test_phase0_legacy_korean_location_name_mapped_to_short_id` 와 함께 path 보존 확인.) |

- 기존 `test_phase0_legacy_korean_location_name_mapped_to_short_id` (exact match `거실`→`L02`) — P043 폐기 후에도 PASS (exact path 보존). 회귀 0.

### 3.5 W1 Pre-commit Gate

repo-root cwd (`/Users/manta/Documents/Projects/TheRoad-I1`). venv python + `PYTHONPATH=backend` 의무. `cd backend` cwd drift 금지 (Carry-P021 N-exec-1 정합).

```bash
# Gate W1.1: P043 신규 test 3건 PASS
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests/pipeline/test_background_chain_planning.py -q

# Gate W1.2: 백엔드 전체 회귀 (pre-existing failure 제외 동일 — clean HEAD 16abb40 git stash 대조)
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -q
```

- Gate W1.1 = `test_background_chain_planning.py` 전수 PASS (기존 phase0/validate_plan test 회귀 0 + 신규 G1-G3).
- Gate W1.2 신규 failure 가 C4 변경 기인이면 commit 금지 → 원인 수정. pre-existing 여부 = clean HEAD `16abb40` git stash 대조.

### 3.6 W1 Commit + per-wave Codex review

- commit message: `W1(c4-background-classifier-v1): P043 background_chain_planning location resolve substring fallback 폐기 — exact·L-id 실패 시 ValueError fail-loud + C4 regression test (2 file modify)`
- staged: §3.2 표 2 file (`git add` 명시 경로).
- commit 후 Codex W1 per-wave review 송신 → `APPROVED_FOR_W2_ENTRY` 받고 W2 진입.

## 4. W2 — `background_chain_planning` v5 prompt-pack (1 atomic commit)

### 4.1 Pre-W2 Entry Sanity

- W1 per-wave `APPROVED_FOR_W2_ENTRY` 후 진입. Codex W2 entry sanity 송신.
- 진입 직전 read: `4.202604291315/{system.md,schema.json,user_template.md}` 현재 상태 (A001/A002/A003/A004 anchor line drift 확인) + `background_chain_planning_step.py:21-22`.

### 4.2 W2 File Operations (3 신규 prompt + 1 code + 1 신규 test = 5 file, 1 atomic commit)

| op | file |
|----|------|
| new | `prompts/_base/background_chain_planning/5.<W2_UTC>/system.md` |
| new | `prompts/_base/background_chain_planning/5.<W2_UTC>/schema.json` |
| new | `prompts/_base/background_chain_planning/5.<W2_UTC>/user_template.md` |
| modify | `backend/app/core/steps/background_chain_planning_step.py` (`PROMPT_VERSION`) |
| new | `backend/tests/prompts/test_c4_background_classifier.py` (W2 gate G4-G6) |

### 4.3 v5 prompt-pack 신설 (W2.1)

```bash
# repo-root cwd. W2_UTC = W2 실행 시점 1회 생성.
W2_UTC=$(date -u +%Y%m%d%H%M)
cp -r prompts/_base/background_chain_planning/4.202604291315 "prompts/_base/background_chain_planning/5.${W2_UTC}"
echo "v5 dir = 5.${W2_UTC}"
```

`cp -r` 후 v5 디렉토리 안 3 stem 편집. v4 원본 디렉토리 변경 0.

**W2.1a — `system.md` A003 (SKIP DECISION B 분기, `:23-25`)**:

old_string:
```
B) Otherwise, evaluate the location independently:
   - INDOOR / ENCLOSED / FIXED-SET (room, hallway, store interior, office, kitchen, basement, closed vehicle interior, fixed studio set, etc.) → plan a full chain (skip_chain: false).
   - DETACHED OPEN AREA — a wide outdoor space with no specific architecture to preserve across shots (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) → set skip_chain: true.
```

new_string:
```
B) Otherwise, evaluate the location independently:
   - INDOOR / ENCLOSED / FIXED-SET — an enclosed space whose persistent built structure (walls, ceiling, fixed architectural elements) must stay visually consistent across shots → plan a full chain (skip_chain: false).
   - DETACHED OPEN AREA — a wide open space with no specific persistent architecture to preserve across shots → set skip_chain: true.
```

**W2.1b — `system.md` A004 (node state, `:48`)**:

old_string: `    * different room state (clean / lived-in / disturbed / heavily-ransacked)`

new_string: `    * different room state (the visible surface and clutter condition of the space)`

- 형제 줄 `:47` (`different time-of-day (day / night / dusk / dawn)`) + `:49` (`different lighting setup ...`) — audit A004 미flag (A004 = room state 단독), boundary-preserve 불변 (C6 sibling-line precedent).

**W2.1c — `schema.json` A001 (`skip_chain` description)**:

old_string (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, so chain rendering adds little value. When true, nodes / execution_order / unassigned_shots may all be empty arrays — the renderer will fall back to a previous-related-shot reference. When false, plan a full chain as usual.
```

new_string:
```
true if this location is a detached open area with no specific persistent architecture to preserve across shots, where shot-to-shot continuity is naturally accepting of variation, so chain rendering adds little value. When true, nodes / execution_order / unassigned_shots may all be empty arrays — the renderer will fall back to a previous-related-shot reference. When false, plan a full chain as usual.
```

- `skip_chain` 의 `type:boolean` / `required` / 그 외 모든 field/enum/`additionalProperties` byte-identical — `properties.skip_chain.description` 값 한 곳만 변경 (spec §4.4).

**W2.1d — `user_template.md` A002 (`:16` / `:20`)**:

- `:16` old `A) If SKIP applies (outdoor / open-air):` → new `A) If SKIP applies (detached open area — see SKIP DECISION):`
- `:20` old `B) Otherwise (indoor / enclosed / fixed-set):` → new `B) Otherwise (enclosed / fixed-set space):`

### 4.4 PROMPT_VERSION bump (W2.2)

`background_chain_planning_step.py:22`:
```python
# before
PROMPT_VERSION = "4"
# after
PROMPT_VERSION = "5"   # C4 v1 — v5 prompt: SKIP DECISION indoor/outdoor 닫힌 예시 enumeration + node state 닫힌 목록 + skip_chain description OUTDOOR 목록 추상화 (generic semantic principle). config_hash invalidation. SCHEMA_VERSION 2 유지.
```

- `SCHEMA_VERSION = 2` 불변 (구조 schema 무변경).

### 4.5 W2 residue/loader test 신설 (W2.3)

`backend/tests/prompts/test_c4_background_classifier.py` 신규 — W2 gate G4-G6. 파일 read + 문자열 검사 + JSON structural diff only.

| Gate | assertion |
|------|-----------|
| G4 | v5 `system.md` SKIP DECISION + node-state 섹션에 닫힌-form 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 | v5 `system.md` 에 SKIP DECISION 개념 (`SHARED BUILDING SET` / `DETACHED OPEN AREA`) + node-split principle 존재; v5 `schema.json` JSON parse 후 `properties.skip_chain.description` 제외 구조가 v4 `schema.json` (`4.202604291315/`) 와 deep-equal (structural diff allowlist) + `skip_chain.description` 값은 v4 와 상이. |
| G6 | `background_chain_planning_step.PROMPT_VERSION == "5"` + `5.<W2_UTC>/` 디렉토리 존재 + `prompt_loader._list_module_versions("background_chain_planning")[0]` 가 `"5."` prefix (latest source = v5). |

- residue marker 는 닫힌-form multi-word substring — bare 단어 grep 금지 (spec §8 R1). test needle self-hit 방지 위해 split-string 상수 권장.
- 본 file 은 W3/W4 에서 append (G7-G12). W2 commit 시점엔 G4-G6 만.

### 4.6 W2 Pre-commit Gate

```bash
# Gate W2.1: C4 test (W2 gate) PASS
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests/prompts/test_c4_background_classifier.py -q

# Gate W2.2: background_chain_planning step/pipeline regression
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -k "background_chain_planning" -q

# Gate W2.3: residue 증명 — executable assertion (v5 prompt-pack path-scoped, split-string self-hit 방지)
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from pathlib import Path
import glob
d = sorted(glob.glob("prompts/_base/background_chain_planning/5.*"))[-1]
sysmd = Path(f"{d}/system.md").read_text(encoding="utf-8")
schema = Path(f"{d}/schema.json").read_text(encoding="utf-8")
tmpl = Path(f"{d}/user_template.md").read_text(encoding="utf-8")
assert ("store interior, " + "office, kitchen") not in sysmd, "A003 indoor 닫힌 예시 잔존"
assert ("a road far from " + "any building") not in sysmd, "A003 outdoor 닫힌 예시 잔존"
assert ("clean / lived-in" + " / disturbed") not in sysmd, "A004 room state 닫힌 목록 잔존"
assert ("street, road, " + "coast, sea, forest") not in schema, "A001 skip_chain OUTDOOR 목록 잔존"
assert ("(outdoor " + "/ open-air)") not in tmpl, "A002 outdoor 분기 label 잔존"
assert ("(indoor / enclosed " + "/ fixed-set)") not in tmpl, "A002 indoor 분기 label 잔존"
assert "SHARED BUILDING SET" in sysmd and "DETACHED OPEN AREA" in sysmd, "SKIP DECISION 개념 손실"
print("Gate W2.3 OK")
PY

# Gate W2.4: 백엔드 전체 회귀
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -q
```

### 4.7 W2 Commit + per-wave Codex review

- commit message: `W2(c4-background-classifier-v1): background_chain_planning v5 prompt-pack — SKIP DECISION/node-state/skip_chain description 닫힌 enumeration 추상화 (A001/A002/A003/A004) + PROMPT_VERSION bump + C4 residue test (5 file)`
- staged: §4.2 표 5 file.
- commit 후 Codex W2 per-wave review → `APPROVED_FOR_W3_ENTRY`.

## 5. W3 — `background_classify` v4 prompt-pack (1 atomic commit)

### 5.1 Pre-W3 Entry Sanity

- W2 per-wave `APPROVED_FOR_W3_ENTRY` 후 진입. Codex W3 entry sanity 송신.
- 진입 직전 read: `3.202604300520/system.md:16-24` (A005 anchor) + `background_classify_step.py:27-28`.

### 5.2 W3 File Operations (3 신규 prompt + 1 code + 1 test modify = 5 file, 1 atomic commit)

| op | file |
|----|------|
| new | `prompts/_base/background_classify/4.<W3_UTC>/system.md` |
| new | `prompts/_base/background_classify/4.<W3_UTC>/schema.json` (v3 byte-identical sibling) |
| new | `prompts/_base/background_classify/4.<W3_UTC>/user_template.md` (v3 byte-identical sibling) |
| modify | `backend/app/core/steps/background_classify_step.py` (`PROMPT_VERSION`) |
| modify | `backend/tests/prompts/test_c4_background_classifier.py` (+W3 gate G7-G9) |

### 5.3 v4 prompt-pack 신설 (W3.1)

```bash
W3_UTC=$(date -u +%Y%m%d%H%M)
cp -r prompts/_base/background_classify/3.202604300520 "prompts/_base/background_classify/4.${W3_UTC}"
```

`cp -r` 후 `system.md` 만 편집 — `schema.json` + `user_template.md` 자동 byte-identical (G8 sha256 동일 보장).

**W3.1a — `system.md` A005 (`:18` indoor)**:

old_string:
```
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.
```

new_string:
```
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). Judge this from the spatial semantics of the location's label and description — whether the framed space is enclosed overhead and on its sides.
```

**W3.1b — `system.md` A005 (`:20` outdoor)**:

old_string:
```
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: 외부 / 옥상 / 거리 / 도로 / 공터 / 골목 / 해안 / 숲 / 마당.
```

new_string:
```
Mark `is_indoor=false` when the location is **outside** with sky overhead (e.g., a street, a park, a field, a yard, a forest, a beach, a rooftop terrace open to sky, a parking lot exterior).
```

- 개념 문장 ("inside an enclosed structure with walls and a ceiling" / "outside with sky overhead") + 영문 e.g. 보존 — audit A005 미flag. 닫힌 한국어 keyword 목록 + 한국어 매핑 예시만 제거 (spec §2.3).

### 5.4 PROMPT_VERSION bump (W3.2)

`background_classify_step.py:28`:
```python
# before
PROMPT_VERSION = "3"
# after
PROMPT_VERSION = "4"   # C4 v1 — v4 prompt: indoor/outdoor 판단 한국어 keyword 닫힌 목록 폐기 (generic semantic principle). config_hash invalidation. SCHEMA_VERSION 1 유지.
```

- `SCHEMA_VERSION = 1` 불변.

### 5.5 W3 residue/loader test (W3.3)

`test_c4_background_classifier.py` 에 G7-G9 append.

| Gate | assertion |
|------|-----------|
| G7 | v4 `system.md` 에 `내부 / 안 / 방 / 실 / 층` + `외부 / 옥상 / 거리 / 도로` 한국어 keyword slash-form 목록 0 + `Korean clues` 문구 0. |
| G8 | v4 `system.md` 에 indoor/outdoor 개념 문장 (`enclosed structure with walls and a ceiling` / `outside with sky overhead`) 존재; v4 `schema.json` sha256 == v3 (`3.202604300520/`) `schema.json` sha256; v4 `user_template.md` sha256 == v3 `user_template.md` sha256. |
| G9 | `background_classify_step.PROMPT_VERSION == "4"` + `4.<W3_UTC>/` 디렉토리 존재 + `_list_module_versions("background_classify")[0]` 가 `"4."` prefix. |

### 5.6 W3 Pre-commit Gate

```bash
# Gate W3.1: C4 test (W2+W3 gate) PASS
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests/prompts/test_c4_background_classifier.py -q

# Gate W3.2: background_classify step/pipeline regression
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -k "background_classify" -q

# Gate W3.3: residue 증명 — executable assertion
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from pathlib import Path
import glob, hashlib
d = sorted(glob.glob("prompts/_base/background_classify/4.*"))[-1]
sysmd = Path(f"{d}/system.md").read_text(encoding="utf-8")
assert "Korean clues" not in sysmd, "A005 'Korean clues' 문구 잔존"
assert ("내부 / 안 " + "/ 방 / 실 / 층") not in sysmd, "A005 indoor 한국어 keyword 목록 잔존"
assert ("외부 / 옥상 " + "/ 거리 / 도로") not in sysmd, "A005 outdoor 한국어 keyword 목록 잔존"
assert "enclosed structure with walls and a ceiling" in sysmd, "indoor 개념 손실"
old = "prompts/_base/background_classify/3.202604300520"
for stem in ("schema.json", "user_template.md"):
    a = hashlib.sha256(Path(f"{d}/{stem}").read_bytes()).hexdigest()
    b = hashlib.sha256(Path(f"{old}/{stem}").read_bytes()).hexdigest()
    assert a == b, f"{stem} sibling byte-identical 위반"
print("Gate W3.3 OK")
PY

# Gate W3.4: 백엔드 전체 회귀
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -q
```

### 5.7 W3 Commit + per-wave Codex review

- commit message: `W3(c4-background-classifier-v1): background_classify v4 prompt-pack — indoor/outdoor 한국어 keyword 닫힌 목록 폐기 (A005, generic semantic principle) + PROMPT_VERSION bump + C4 residue test (5 file)`
- staged: §5.2 표 5 file.
- commit 후 Codex W3 per-wave review → `APPROVED_FOR_W4_ENTRY`.

## 6. W4 — `location_floor_plan` v4 prompt-pack (1 atomic commit)

### 6.1 Pre-W4 Entry Sanity

- W3 per-wave `APPROVED_FOR_W4_ENTRY` 후 진입. Codex W4 entry sanity 송신.
- 진입 직전 read: `3.202604291130/system.md:21-31` (A033/A034 anchor) + `location_floor_plan_step.py:38-39`.

### 6.2 W4 File Operations (2 신규 prompt + 1 code + 1 test modify = 4 file, 1 atomic commit)

| op | file |
|----|------|
| new | `prompts/_base/location_floor_plan/4.<W4_UTC>/system.md` |
| new | `prompts/_base/location_floor_plan/4.<W4_UTC>/user_template.md` (v3 byte-identical sibling) |
| modify | `backend/app/core/steps/location_floor_plan_step.py` (`PROMPT_VERSION`) |
| modify | `backend/tests/prompts/test_c4_background_classifier.py` (+W4 gate G10-G12) |

- `location_floor_plan` 모듈 = schema.json 없음 (2 stem 만).

### 6.3 v4 prompt-pack 신설 (W4.1)

```bash
W4_UTC=$(date -u +%Y%m%d%H%M)
cp -r prompts/_base/location_floor_plan/3.202604291130 "prompts/_base/location_floor_plan/4.${W4_UTC}"
```

`cp -r` 후 `system.md` 만 편집 — `user_template.md` 자동 byte-identical.

**W4.1a — `system.md` A033 (room-count cue, `:22-25`)**:

old_string:
```
B. Room-count inference. Determine the REAL number of distinct rooms by reading every scene segment line-by-line. Cues to look for:
   - Multiple Korean room words appearing in the same scene as separate spaces (e.g., 거실/안방, 부엌/거실, 침실/욕실, 방1/방2). When two such words co-occur as distinct settings within a scene, draw them as SEPARATE rooms with a wall and a door between them.
   - Movement verbs that imply crossing a wall ("문을 열고 들어간다", "방으로 들어선다", "건너편에서", "문 너머").
   - Stage directions that describe what is "behind", "across from", or "next to" another room.
   - A scene heading or stage direction like "거실에서 안방으로" / "방에서 거실로 나온다" → two distinct rooms.
```

new_string:
```
B. Room-count inference. Determine the REAL number of distinct rooms by reading every scene segment line-by-line. Cues to look for:
   - Multiple distinct named spaces appearing in the same scene as separate settings. When two such spaces co-occur as distinct settings within a scene, draw them as SEPARATE rooms with a wall and a door between them.
   - Movement or staging that implies a character crossing a physical boundary between named spaces.
   - Stage directions that describe what is "behind", "across from", or "next to" another room.
   - A scene heading or stage direction that moves a character from one named space into another → two distinct rooms.
```

**W4.1b — `system.md` A034 (zone markers, `:31`)**:

old_string:
```
E. Zone markers. The scene heading often lists slash-separated zones (e.g., "옥탑방 안 / 실내", "한옥 / 안방 / 마루"). Treat each slash-separated label as a candidate room or zone and confirm by scanning the scene body for separation cues.
```

new_string:
```
E. Zone markers. The scene heading often lists slash-separated zone labels. Treat each slash-separated label as a candidate room or zone and confirm by scanning the scene body for separation cues.
```

- `:14` (`Korean common nouns are OK in parentheses for room labels (e.g., "main bedroom (안방)")`) — OUTPUT 허용 규칙, audit 미flag, **불변** (spec §2.4 boundary preserve).

### 6.4 PROMPT_VERSION bump (W4.2)

`location_floor_plan_step.py:39`:
```python
# before
PROMPT_VERSION = "3"  # location_floor_plan v3 (multi-room from scene texts)
# after
PROMPT_VERSION = "4"  # C4 v1 — v4 prompt: room-count cue 한국어 방 이름/이동 동사 + slash-zone 한국어 예시 추상화 (generic semantic principle). config_hash invalidation. SCHEMA_VERSION 3 유지.
```

- `SCHEMA_VERSION = 3` 불변.

### 6.5 W4 residue/loader test (W4.3)

`test_c4_background_classifier.py` 에 G10-G12 append.

| Gate | assertion |
|------|-----------|
| G10 | v4 `system.md` 에 `거실/안방, 부엌/거실` 방 이름 cue + `문을 열고 들어간다` 이동 동사 + `옥탑방 안 / 실내` · `한옥 / 안방 / 마루` slash-zone 예시 0. |
| G11 | v4 `system.md` 에 room-count/zone-marker generic principle 존재 (`Multiple distinct named spaces` / `slash-separated zone labels`) + `:14` Korean-label-OK 규칙 (`main bedroom (안방)`) 보존; v4 `user_template.md` sha256 == v3 (`3.202604291130/`) `user_template.md` sha256. |
| G12 | `location_floor_plan_step.PROMPT_VERSION == "4"` + `4.<W4_UTC>/` 디렉토리 존재 + `_list_module_versions("location_floor_plan")[0]` 가 `"4."` prefix. |

### 6.6 W4 Pre-commit Gate

```bash
# Gate W4.1: C4 test (W2+W3+W4 gate) PASS
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests/prompts/test_c4_background_classifier.py -q

# Gate W4.2: location_floor_plan step/pipeline regression
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -k "location_floor_plan" -q

# Gate W4.3: residue 증명 — executable assertion
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from pathlib import Path
import glob, hashlib
d = sorted(glob.glob("prompts/_base/location_floor_plan/4.*"))[-1]
sysmd = Path(f"{d}/system.md").read_text(encoding="utf-8")
assert ("거실/안방, " + "부엌/거실") not in sysmd, "A033 한국어 방 이름 cue 잔존"
assert ("문을 열고 " + "들어간다") not in sysmd, "A033 한국어 이동 동사 잔존"
assert ("옥탑방 안 " + "/ 실내") not in sysmd, "A034 slash-zone 예시 잔존"
assert ("한옥 / 안방 " + "/ 마루") not in sysmd, "A034 slash-zone 예시 잔존"
assert "Multiple distinct named spaces" in sysmd, "room-count generic principle 손실"
assert "main bedroom (안방)" in sysmd, "boundary :14 Korean-label-OK 규칙 손실"
old = "prompts/_base/location_floor_plan/3.202604291130"
a = hashlib.sha256(Path(f"{d}/user_template.md").read_bytes()).hexdigest()
b = hashlib.sha256(Path(f"{old}/user_template.md").read_bytes()).hexdigest()
assert a == b, "user_template.md sibling byte-identical 위반"
print("Gate W4.3 OK")
PY

# Gate W4.4: 백엔드 전체 회귀
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -q
```

### 6.7 W4 Commit + per-wave Codex review

- commit message: `W4(c4-background-classifier-v1): location_floor_plan v4 prompt-pack — room-count cue 한국어 토큰 + slash-zone 예시 추상화 (A033/A034, generic semantic principle) + PROMPT_VERSION bump + C4 residue test (4 file)`
- staged: §6.2 표 4 file.
- commit 후 Codex W4 per-wave review → `APPROVED_FOR_W5_ENTRY`.

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

### 7.1 Pre-W5 Entry Sanity

- W4 per-wave `APPROVED_FOR_W5_ENTRY` 후 진입. W5 = closure docs only — code/test/prompt 변경 0 (integrated gate 는 pre-commit 검증, 신규 test code 0).

### 7.2 W5 integrated gate (pre-commit 검증, 신규 file 0)

```bash
# Gate W5.1: C4 전체 test PASS (G1-G12 — W1 P043 + W2/W3/W4 residue)
PYTHONPATH=backend backend/.venv/bin/python -m pytest \
  backend/tests/pipeline/test_background_chain_planning.py \
  backend/tests/prompts/test_c4_background_classifier.py -q

# Gate W5.2: cross-pack 통합 residue 재검 (G13) — 3 신규 prompt-pack dir (5 stem) 닫힌-form marker 0
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from pathlib import Path
import glob
checks = {
    "background_chain_planning/5.*/system.md": ["store interior, " + "office, kitchen", "clean / lived-in" + " / disturbed"],
    "background_chain_planning/5.*/schema.json": ["street, road, " + "coast, sea, forest"],
    "background_chain_planning/5.*/user_template.md": ["(outdoor " + "/ open-air)", "(indoor / enclosed " + "/ fixed-set)"],
    "background_classify/4.*/system.md": ["Korean clues", "내부 / 안 " + "/ 방 / 실 / 층"],
    "location_floor_plan/4.*/system.md": ["거실/안방, " + "부엌/거실", "옥탑방 안 " + "/ 실내", "한옥 / 안방 " + "/ 마루"],
}
for pat, needles in checks.items():
    f = sorted(glob.glob(f"prompts/_base/{pat}"))[-1]
    txt = Path(f).read_text(encoding="utf-8")
    for n in needles:
        assert n not in txt, f"{f}: residue {n!r} 잔존"
print("Gate W5.2 OK")
PY

# Gate W5.3: 세 step PROMPT_VERSION/SCHEMA_VERSION 정합
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from app.core.steps.background_chain_planning_step import PROMPT_VERSION as bcp_pv, SCHEMA_VERSION as bcp_sv
from app.core.steps.background_classify_step import PROMPT_VERSION as bcl_pv, SCHEMA_VERSION as bcl_sv
from app.core.steps.location_floor_plan_step import PROMPT_VERSION as lfp_pv, SCHEMA_VERSION as lfp_sv
assert (bcp_pv, bcp_sv) == ("5", 2), (bcp_pv, bcp_sv)
assert (bcl_pv, bcl_sv) == ("4", 1), (bcl_pv, bcl_sv)
assert (lfp_pv, lfp_sv) == ("4", 3), (lfp_pv, lfp_sv)
print("Gate W5.3 OK")
PY

# Gate W5.4: 백엔드 전체 회귀 (G14 — pre-existing failure 제외 동일)
PYTHONPATH=backend backend/.venv/bin/python -m pytest backend/tests -q
```

### 7.3 W5 File Operations (closure docs, path-limited)

| op | file |
|----|------|
| modify | `docs/superpowers/specs/2026-05-20-c4-background-classifier-v1-design.md` (status closed + §10 Closure) |
| modify | `docs/superpowers/plans/2026-05-20-c4-background-classifier-v1-implementation.md` (status closed + §8 Closure) |
| modify | `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` (§5.8 C4 closed marker) |
| modify | `docs/fix-critical-1/index.html` (C4 marker) |

- audit `triage_report.md` (full_20260516_0105) 는 **generated audit-output** — git-tracked 여부와 무관하게 W5 에서 **수정하지 않음** (source evidence 로만 참조). closure marker 대상은 narrative docs (roadmap §5.8 + fix-critical-1 index.html C4 marker) 로 한정 — generated audit-output 에 closure marker 미기입 (C6 narrow precedent: generated audit output 은 source evidence only). P043/A001-A005/A033/A034 closure 기록은 roadmap §5.8 + fix-critical-1 C4 marker 로 충분.

### 7.4 W5 closure docs 내용

1. **spec** — frontmatter `status` → closed + §10 Closure (commit chain / Gate result / Codex review trace / 함정).
2. **plan** — frontmatter `status` → closed + §8 Closure.
3. **roadmap §5.8** — C4 closed marker (인접 §5.10 C6 / §5.16 C9 의 Status / Fix applied / Boundary preserve / Cost / Closure verify 형식 정합).
4. **fix-critical-1 index.html** — C4 marker: badge `✓ CLOSED` + priority list `C4 → CLOSED` / `C5 → NEXT` + priority matrix row 7 status + **index.html 9-step 원안 정정** ("superseded by existing D6 location.space_profile / C4 v1 narrow option — 신규 producer 미신설") + **A031 = boundary-reviewed KEEP** 명시 + **A020 = C4 scope 제외 (deferred, file-fallback latest v10 vs version_registry v9)** 명시 + **`background_chain_planning` (deprecated+disabled) / `location_floor_plan` (deprecated)** 상태 명시 + C4 site table stale 정정 (entity_extractor row 등).

- **W5 self-hash 금지** — closure docs 안 W5 자체 commit hash literal 없음. "W1-W5" / "W5 closure commit" wording. W1-W4 hash 는 각 commit 후 확정 — closure docs 기재 가능. W5 hash 는 push 후 external memory.

### 7.5 W5 Pre-commit Gate

```bash
# Gate W5.5: closure docs staged 만 (code/test/prompt 변경 0)
git diff --cached --name-only   # 위 4 docs 만

# Gate W5.6: W5 self-hash 부재 — closure docs 안 W5 commit hash literal 0
```

### 7.6 W5 Commit + W1-W5 range review + push

- commit message: `W5(c4-background-classifier-v1): closure docs atomic — spec/plan status closed + roadmap §5.8 + fix-critical-1 C4 marker (4 docs path-limited)`
- commit 후 Codex W1-W5 range review 송신 → `APPROVED_FOR_PUSH`.
- `APPROVED_FOR_PUSH` 시 사용자 ask 0 즉시 push ([[feedback_push_no_user_ask]]).
- push 후 §8 Closure / external memory 기록.

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

**Status: closed** — C4 v1 W1-W5 5-wave atomic execution 완료. 종합 closure (commit chain / gate result / Codex review trace / 함정 / carry priority) 는 spec §10.

- W1 `2c018f3` — P043 code fix (substring fallback 폐기 → `ValueError` fail-loud) + regression test. 2 file, 73+/7-.
- W2 `7c9a605` — `background_chain_planning` v5 prompt-pack (`5.202605200932/`, A001/A002/A003/A004) + `PROMPT_VERSION` 4→5 + C4 residue test. 5 file, 288+/1-.
- W3 `4bf2c72` — `background_classify` v4 prompt-pack (`4.202605200945/`, A005) + `PROMPT_VERSION` 3→4 + C4 residue test. 5 file, 162+/1-.
- W3 fix-up 1 `1130cf9` — `background_classify` v4 `system.md` header v3→v4 정정. 1 file, 1+/1-.
- W4 `ddcd115` — `location_floor_plan` v4 prompt-pack (`4.202605201000/`, A033/A034) + `PROMPT_VERSION` 3→4 + C4 residue test. 4 file, 114+/1-.
- W5 closure commit — closure docs atomic (4 docs path-limited: 본 plan + spec + roadmap §5.8 + index.html C4 marker). W5 self-hash 금지 — push 후 external memory.
- Codex — entry sanity / spec·plan review (각 NEEDS_REVISION_NARROW_1 → 흡수 → APPROVED) / W1-W4 per-wave APPROVED (W3 per-wave 는 NEEDS_REVISION_NARROW_1 → fix-up 1 → re-review APPROVED_FOR_W4_ENTRY) / W1-W5 range review (W5 후).
- 함정 — N-1 prompt-pack cp 후 version-stamped header 미갱신 (W3 fix-up 1) / N-2 residue marker vs markdown bold (`**outside**`) / N-3 A001 schema description-only JSON structural diff allowlist / N-4 generated audit-output (`triage_report.md`) closure marker 비대상. spec §10.4 참조.
- A031 floor_plan_prompt = boundary-reviewed KEEP (변경 0). A020 entity_extractor = C4 scope 제외 (C8/C10 carry). `background_chain_planning`/`location_floor_plan` deprecated step — active latest residue 만 처리, lifecycle entry 불변.
- C4 closed → 다음 = C5 (#9 action segment SOT). 잔여 = C5/C8/C10.

## 9. Doctrine references

- [[feedback_llm_based_judgment]] — 4-Gate. C4 = Prompt Closed-List Ban (A001-A005/A033/A034) + Semantic Regex Ban (P043) + No Silent Fallback (P043 fail-loud).
- [[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 per-wave review, tmux-bridge raw typing + prefix/suffix.
- [[feedback_no_user_ask_codex_only]] — OQ·결정 사용자 ask 0, Codex MCP 의논.
- [[feedback_push_no_user_ask]] — APPROVED_FOR_PUSH 즉시 push.
- [[feedback_channel_separation_4way]] — 4채널 fact source 구분.
- [[project_fix_critical_1_persistent]] — fix-critical-1 Tier β #7, C4 → C5 priority.
