---
title: C1 — perception_mode enum SOT v1 (Track B Tier α #1, NEW area)
date: 2026-05-20+
status: closed (C1 v1 W1-W4 closed at W4 closure-docs commit, 2026-05-20+; Codex spec 3 round narrow re-review N-1~N-8 + M-1 흡수 APPROVED_FOR_PLAN + plan 4 round narrow re-review N-1~N-13 흡수 APPROVED_FOR_EXECUTION + W1-W3 per-wave APPROVED + W3 mid-execution blocker W3_PLAN_AMEND_OPTION_가 + W4 closure-docs atomic + W1-W4 range review)
roadmap_ref: docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md §5 (Track B Tier α #1 NEW area, 미routed) + docs/fix-critical-1/index.html C1 section (locked persistent guide, APPROVED_FOR_DOC_LOCK 2026-05-20+)
fix_critical_doc_ref: docs/fix-critical-1/index.html (persistent remaining-work guide, 사용자 명시 2026-05-20+ verbatim "여기 적힌 모든 테스크 끝날때까지 꼭 기억해 끝나면 메모리서 지우고")
prerequisites:
  - Carry-P021 v1 (scene_consistency v7→v8 prompt hygiene narrow abstraction, closed 2026-05-20+ push `67775ec`) — HEAD anchor.
  - Carry-A100 v1 (VWR system.md t2i_context demonym redesign V8+/V9 bump, closed 2026-05-19+ push `0f24492`) — V9 prompt-pack anchor + TEMPLATE ONLY doctrine source.
  - Carry-A099 v1 (VWR system.md director_notes prose redesign V8+, closed 2026-05-19+ push `146401c`) — V8 prompt-pack anchor + N-3/N-5 active resolution → historical pin doctrine source.
  - Area C (directionality_class enum SOT v1, closed 2026-05-12 commit history) — boundary preserve, 변경 0 의무.
non_supersedes:
  - C2 owned_object_usage[] echo (NEW area, scene_detail v_next, OQ-C2 = C2 spec entry 시 결정).
  - C7 shot_dependency_t2i drift (#12, OQ-C7 = C7 spec entry 시 결정).
  - C6 detail_steps 색감-감정 (#10, color_palette_intent structured emit, `_perception_guides` 안 dream/memory/hallucination color prose = C6 영역).
  - Future Carry-P021-structured / Carry-A099-structured / Carry-A100-structured (field-shape migration).
---

# C1 — perception_mode enum SOT v1

## 1. Purpose + Scope

### 1.1 Purpose

**Codex APPROVED scope verbatim**: "C1 owns only `perception_mode` SOT: shot_staging producer schema/prompt, render_prompt_card consumer subset, detail_steps guide/fail-fast, scene_detail consumer prompt cross-reference, plus tests/docs."

본 spec v1 = `perception_mode` field validation contract hardening (not field-shape addition):
- **Producer schema (Layer 1)**: `shot_staging/13/schema.json:18` `type:string + description 7-token (mirror 부재)` → v14 JSON `enum` 8-token strict (`mirror` 추가).
- **Consumer code subset (Layer 2)**: `render_prompt_card.py:1144-1146` 4-tuple literal → closed-world constant `REPRODUCTION_PERCEPTION_MODES` consume.
- **Cascade code (Layer 3)**: `detail_steps.py:2221-2231` 6-key dict (mirror 부재) + generic fallback → non-direct 7-key full coverage + fail-fast (no silent fallback).
- **Consumer prompt (Layer 4)**: `scene_detail/28/system.md:64` 4-token closed list → enum cross-reference.

**핵심 인식 (Codex required guard 1)**: `perception_mode` 이미 `shot_staging/13/schema.json:169` `required` list 안 포함 → C1 = **enum/value contract hardening**, 출력 필드 shape 추가 X. cp invalidation = OQ-5 결정 dependent.

### 1.2 In-scope

- **shot_staging v14 prompt-pack 신규 dir**: `prompts/_base/shot_staging/14.UTC_TIMESTAMP/` (2 file, post-Carry-A100/D7d-1 fix-up 1 TEMPLATE ONLY doctrine — underscore-escaped UTC placeholder per W2 controller shell substitute):
  - `schema.json`: copied from v13 first, line 18 `perception_mode` → JSON `enum` 강제 8-token (`direct/hallucination/dream/memory/reflection/mirror/through_device/projection`) + description sync. Carrier preserve = `required` list line 169 안 `perception_mode` 유지.
  - `system.md`: copied from v13 first, **새 perception_mode 전용 section 신규 추가** (현 v13 system.md:57 "reflection" = composition technique 일부만, 전용 section 부재). enum emit instruction + 8 token semantics.
- **`backend/app/core/perception_mode.py` 신설 helper** (OQ-2 Codex 권장):
  ```python
  PERCEPTION_MODES: frozenset[str] = frozenset({
      "direct", "hallucination", "dream", "memory",
      "reflection", "mirror", "through_device", "projection",
  })
  REPRODUCTION_PERCEPTION_MODES: frozenset[str] = frozenset({
      "reflection", "mirror", "through_device", "projection",
  })
  # field 분리 의무 (Codex Guard 1): directionality_class 영역 = Area C closed boundary, 본 module 안 포함 X

  def _validate_perception_mode(pm: str) -> None:
      """Validation gate (N-1 fix): non-empty value outside PERCEPTION_MODES → AppError.

      None / empty / "direct" legacy default coerce는 caller 의무 (helper 안 silent coerce X).
      """
      if pm not in PERCEPTION_MODES:
          raise AppError(code="perception_mode.unknown", message=f"perception_mode={pm!r} not in PERCEPTION_MODES")

  def is_reproduction_perception(pm: str) -> bool:
      """Validate first, then subset check. (N-1: no silent false on unknown.)

      caller convention: legacy default coerce 는 caller 책임 (`pm = perception_mode or "direct"` 식).
      empty string "" 들어오면 _validate_perception_mode 안 AppError raise.
      """
      _validate_perception_mode(pm)
      return pm in REPRODUCTION_PERCEPTION_MODES

  _PERCEPTION_GUIDES: Mapping[str, str] = {
      # N-5 fix: helper-owned guide mapping (detail_steps local var X, importable Mapping).
      # non-direct 7-key full coverage (Codex OQ-3 (c) policy).
      # 각 guide string 안 C6 color/emotion prose 영역 preserve (C6 future area boundary, color_palette_intent migration 시 separate).
      "through_device": "이 장면은 화면/렌즈를 통해 보이는 것이지만, t2i_prompt에서 '디바이스 프레임/화면 테두리'를 묘사하지 마세요. 화면 속 내용물만 직접 촬영한 것처럼 묘사하세요. 약간의 디지털 질감이나 해상도 차이만 반영.",
      "hallucination": "이 장면은 환각/왜곡된 시각이므로 현실과 다른 색감/왜곡 효과를 반영하세요. 하지만 장면 자체는 직접 촬영한 것처럼 묘사.",
      "dream": "이 장면은 꿈/몽환적 상태이므로 부드러운 포커스, 비현실적 색감을 반영하세요.",
      "memory": "이 장면은 회상이므로 탈색된 색감, 부드러운 빛을 반영하세요.",
      "reflection": "이 장면은 거울/수면 반사이므로 좌우 반전된 구도를 고려하세요.",
      "mirror": "이 장면은 거울(mirror) surface 안에 보이는 것이므로, 거울면의 좌우 반전 + 반사면 quality (광택/표면 상태/변형 정도) + 거울 frame 가시 여부를 t2i_prompt 에 반영하세요. reflection (일반 반사면) 과 다른 mirror specific 행동: 거울 가장자리/거울 frame/mirror image 의 별도 surface 명시. (OQ-3 mirror specific wording, plan v1 안 exact Korean string final lock.)",
      "projection": "이 장면은 프로젝터/스크린에 투사된 것이지만, 프로젝터 장비나 스크린 프레임을 묘사하지 마세요. 투사된 내용물만 직접 촬영한 것처럼 묘사.",
  }

  def get_perception_guide(pm: str) -> str:
      """non-direct 7-key full coverage. unknown 또는 direct → AppError contract error.

      Codex N-7 fix: 2-stage check (raw KeyError 노출 X):
        1. _validate_perception_mode(pm) — enum membership (PERCEPTION_MODES). unknown → AppError("perception_mode.unknown").
        2. pm in _PERCEPTION_GUIDES (= non-direct 7-key) check. False → AppError("perception_mode.guide_not_applicable") raise BEFORE dict lookup.
        3. Then dict lookup _PERCEPTION_GUIDES[pm] return (always safe).

      Codex OQ-3 권장: raw KeyError 노출 X. internal dict indexing = implementation detail.

      pm == "direct" → AppError("perception_mode.guide_not_applicable", "direct는 guide 대상 X, caller가 non-direct branch 안에서만 호출 의무").
      pm ∈ non-direct 7-key → _PERCEPTION_GUIDES[pm] return.
      pm ∉ PERCEPTION_MODES → AppError("perception_mode.unknown") (stage 1 raise).
      """
  ```
- **`render_prompt_card.py:1144-1146` consumer subset rewrite**:
  ```diff
  - if (perception_mode or "").lower() in (
  -     "reflection", "mirror", "through_device", "projection",
  - ):
  + from app.core.perception_mode import is_reproduction_perception
  + # N-6 fix wording: helper direct-call empty string raises AppError;
  + # this legacy caller intentionally coerces None/empty to "direct" before validation.
  + pm_normalized = (perception_mode or "direct").lower()
  + if is_reproduction_perception(pm_normalized):
  ```
- **`detail_steps.py:2221-2231` perception guide block rewrite** (N-5 fix: detail_steps local map 제거):
  - detail_steps local `_perception_guides = {...}` block **제거**.
  - non-direct branch 안 `_pguide = get_perception_guide(perception)` helper consume (helper-owned `_PERCEPTION_GUIDES` 안 mirror entry 포함, non-direct 7-key full coverage).
  - generic fallback path 폐기 → unknown perception_mode → helper 안 AppError contract error raise.
  - cascade boundary: dream/memory/hallucination color prose = **C6 영역 preserve** (mirror 추가는 enum coverage만, color SOT migration X). color prose 가 helper-owned guide string 안 보존 (detail_steps-local map 제거 후 helper 안 owned 형태).
- **scene_detail v_next consumer prompt cross-reference** (OQ-6 결정 dependent):
  - `scene_detail/v_next/system.md` line 64 4-token closed list → enum cross-reference (`perception_mode ∈ REPRODUCTION_PERCEPTION_MODES` helper SOT).
- **W3 integrated test 신규** (file-level new, no live LLM + NO VLM + no regex semantic):
  - `backend/tests/integration/test_c1_perception_mode_enum_sot.py` (G1-G7 verify gate per §7).
- **W4 closure docs** (path-limited):
  - 본 spec frontmatter status closed + §10 closure section.
  - Roadmap §5 신설 C1 entry append (Track B Tier α #1 closure).
  - `docs/fix-critical-1/index.html` C1 section closure marker + remaining-work guide update (C2-C10 잔여).
  - audit 02 §15 신설 closure section.

### 1.3 Out-of-scope (Codex NARROW scope guard 흡수)

- **`directionality_class` field 영역**: Area C closed boundary 변경 0. `_ALLOWED_DIRECTIONALITY_CLASSES` + `_REPRODUCTION_SURFACE_CLASSES` + fail-fast (`render_prompt_card.py:124, :134, :1038-1043`) + `shot_staging/13/schema.json:80-86` 5-enum required 모두 preserve.
- **`perception_mode` 와 `directionality_class` field 분리 (Codex Guard 1)**: 같은 helper module 또는 spec 안 묶어도 **field 분리 의무**. `perception_mode.py` helper 안 directionality 영역 import/사용 X.
- **C2 `owned_object_usage[]` echo**: 별도 area (Tier α #2). OQ-C2 = C2 spec entry 시 결정. 본 spec 안 cross-cut X.
- **C7 `shot_dependency_t2i` drift**: 별도 area (Tier β #4). OQ-C7 = C7 spec entry 시 결정. 본 spec 안 cross-cut X.
- **C6 `detail_steps` 색감-감정 (`color_palette_intent`)**: 별도 area (Tier β #5). helper-owned `_PERCEPTION_GUIDES` (N-5 fix, 본 spec W2 시 detail_steps local map → helper module migrate) 안 dream/memory/hallucination color prose 영역 = C6 owner. 본 C1 spec 안 mirror entry 추가만, color SOT migration X.
- **shot_staging schema_version bump** (`step_manifest.shot_staging.schema_version=5` 현재): OQ-5 결정 dependent. Codex 권장 = prompt/schema version bump only (output shape 변경 X). 6 bump 선택 시 stale CP risk 명시 의무.
- **scene_detail schema_version bump**: OQ-6 (a-amended) — `SCENE_DETAIL_SCHEMA_VERSION` 11 preserve (output cp data shape 변경 0). 단 `SCENE_DETAIL_PROMPT_VERSION` bump (v28 → v29) = cp config_hash invalidation accepted (consumer prompt 변경 정상 결과, W3 blocker W3_PLAN_AMEND_OPTION_가 정합).
- **VLM / live LLM / semantic regex / standalone-token residue gates**: 모두 ban ([[feedback_no_vlm_dependency]] + [[feedback_llm_based_judgment]]).
- **Future C1-structured carry**: 본 spec scope X.

## 2. W0 Evidence Inventory (live, 본 instance 2026-05-20+, HEAD `67775ec`)

### 2.1 Layer 1 — Producer schema (`shot_staging/13/schema.json:18`)

<!-- HISTORICAL AUDIT TRAIL — BEGIN (line 18 perception_mode literal) -->

Path `prompts/_base/shot_staging/13.202605170422/schema.json:18`:

```
"perception_mode": {"type": "string", "description": "How the scene is perceived: direct (normal vision), hallucination, dream, memory, reflection, through_device (CCTV/phone/binoculars), projection. Affects visual treatment."},
```

- Description token list = 7-token: `direct / hallucination / dream / memory / reflection / through_device / projection`.
- **`mirror` 부재** in description.
- `type: string` — JSON enum 강제 0 (arbitrary string silent miss).
- `required` list line 169 안 `perception_mode` 포함 (carrier preserve).

<!-- HISTORICAL AUDIT TRAIL — END -->

### 2.2 Layer 2 — Consumer code subset (`render_prompt_card.py:1144-1146`)

Path `backend/app/core/steps/render_prompt_card.py:1142-1149` (Codex Fix 2 wording):

```python
# perception_mode reflection branch (defense in depth, R1-I4).
# base 5 위에 6번째 constraint — base 의 일부가 아님 (R1-I4 / R2-I5 carry).
if (perception_mode or "").lower() in (
    "reflection", "mirror", "through_device", "projection",
):
    constraints.append(
        "this shot's perception_mode involves a reflective/projected surface — "
        "all reproduced faces use common nouns even if the source identity is known"
    )
```

- 4-tuple literal subset routing branch (single material decision site).
- Consumer subset = 4-token (`reflection / mirror / through_device / projection`).
- Producer 8 enum 중 reproduction surface subset만 routing.

### 2.3 Layer 3 — Cascade code (`detail_steps.py:2221-2231`)

Path `backend/app/core/steps/detail_steps.py:2221-2231`:

```python
perception = staging.get("perception_mode", "direct")
if perception and perception != "direct":
    _perception_guides = {
        "through_device": "이 장면은 화면/렌즈를 통해 보이는 것이지만, t2i_prompt에서 '디바이스 프레임/화면 테두리'를 묘사하지 마세요. ...",
        "hallucination": "이 장면은 환각/왜곡된 시각이므로 현실과 다른 색감/왜곡 효과를 반영하세요. ...",
        "dream": "이 장면은 꿈/몽환적 상태이므로 부드러운 포커스, 비현실적 색감을 반영하세요.",
        "memory": "이 장면은 회상이므로 탈색된 색감, 부드러운 빛을 반영하세요.",
        "reflection": "이 장면은 거울/수면 반사이므로 좌우 반전된 구도를 고려하세요.",
        "projection": "이 장면은 프로젝터/스크린에 투사된 것이지만, ...",
    }
    _pguide = _perception_guides.get(perception, f"이 장면은 {perception}으로 보이는 것이므로 해당 시각 효과를 t2i_prompt에 반영하세요.")
    user_prompt += f"지각 모드: {perception} — {_pguide}\n"
```

- `perception != "direct"` 조건 — direct exclusion (line 2222).
- 6-key dict — `through_device / hallucination / dream / memory / reflection / projection`.
- **`mirror` 부재** — mirror entry 들어오면 generic fallback path (line 2231 `_perception_guides.get(perception, f"이 장면은 {perception}으로...")`).
- color prose (dream/memory/hallucination/projection 안 `색감`, `포커스` 등) = C6 영역 boundary.

### 2.4 Layer 4 — Consumer prompt (`scene_detail/28/system.md:64`)

Path `prompts/_base/scene_detail/28.202605190927/system.md:64`:

```
- `perception_mode` 가 mirror / reflection / through_device / projection 이면 `reproduction_surface_rule` 와 별도로 perception_mode constraint 가 우선 적용.
```

- 4-token closed list (`mirror / reflection / through_device / projection`).
- 본 file 안 perception_mode 등장 site = **1 site only** (line 64 grep verified).

### 2.5 Cascade audit — 4 material passthrough sites

Path `backend/app/core/steps/render_prompt_card.py`:

| site | actual call |
|---|---|
| `:827` | `pm = perception_mode or RENDER_MODE_DIRECT` (default coerce) |
| `:1144` | reproduction subset routing branch (4-tuple literal, **single material decision**) |
| `:3449` | `card["render_strategy"] = build_render_strategy(seg=seg, shot_info=shot_info, staging=staging, perception_mode=perception_mode, ...)` passthrough |
| `:3486` | `card["id_policy"] = build_id_policy(visible_entities=visible_entities, outlook_pairs=outlook_pairs, perception_mode=perception_mode, ...)` passthrough |

**제외**: `:780, :857, :884, :942, :3409` = signature/dict-key/parameter only (material 아님) / `:963, :1098, :1181, :2321, :3545` = comment lines (counting 잘못 — Codex Fix 3 정합).

### 2.6 Boundary — Area C closed (변경 0 의무)

Path `backend/app/core/steps/render_prompt_card.py:124, :134, :1038-1043`:

```python
_ALLOWED_DIRECTIONALITY_CLASSES: FrozenSet[str] = frozenset({
    "content_surface", "reflective_surface", "transparent_surface",
    "directional_3d", "non_directional",
})

_REPRODUCTION_SURFACE_CLASSES: FrozenSet[str] = frozenset({...})

# line 1038-1043 fail-fast:
if dc not in _ALLOWED_DIRECTIONALITY_CLASSES:
    raise AppError(
        code="render_prompt_card.directionality_class_invalid",
        message=(
            f"directionality_class={dc!r} not in allowed enum "
            f"{sorted(_ALLOWED_DIRECTIONALITY_CLASSES)}."
        ),
    )
```

Path `prompts/_base/shot_staging/13.202605170422/schema.json:80-86`:

```json
"directionality_class": {
  "type": "string",
  "enum": ["content_surface", "reflective_surface", "transparent_surface", "directional_3d", "non_directional"],
  "description": "..."
}
```

`required` list line 86 안 `directionality_class` 포함. 5-enum strict.

본 Area C closed boundary = **변경 0 의무**. perception_mode 와 directionality_class 같은 helper 또는 spec 안 묶어도 **field 분리** (Codex Guard 1).

## 3. OQ Resolutions (Spec v1 결정 흡수)

### 3.1 OQ-1 — Producer enum cardinality

**결정 = (a) 8-token enum** (Codex 권장 정합):

```
direct / hallucination / dream / memory / reflection / mirror / through_device / projection
```

- `mirror` ↔ `reflection` 의미 분리 유지 (reflection = 반사면 일반, mirror = 거울 surface 특정).
- 7-token 합병 옵션 (b) reject — mirror specific surface 행동 보존 의무.

### 3.2 OQ-2 — `REPRODUCTION_PERCEPTION_MODES` constant location

**결정 = (b) `backend/app/core/perception_mode.py` 신설 helper module** (Codex 권장):

- module-level constants: `PERCEPTION_MODES` (8-set) + `REPRODUCTION_PERCEPTION_MODES` (4-set, strict subset).
- API: `_validate_perception_mode(pm: str) -> None` (private validation gate, AppError on unknown) + `is_reproduction_perception(pm: str) -> bool` (validate first, then subset check — N-1 fix) + `get_perception_guide(pm: str) -> str` (validate first, then dict lookup, non-direct 7-key full coverage).
- **field 분리 의무**: `directionality_class` 영역 (Area C closed) import/use X. `representation_binding.py` 신설은 future scope (C1 v1 안 X).

### 3.3 OQ-3 — `_perception_guides` mirror policy

**결정 = (c) generic fallback 폐기 + non-direct 7-key full coverage + contract error fail-fast** (Codex 권장):

- mirror entry 추가 → non-direct 7-key (`hallucination / dream / memory / reflection / mirror / through_device / projection`) full coverage.
- generic fallback path 폐기 → unknown perception_mode 들어오면 `AppError` contract error raise.
- **Codex OQ-3 wording guard 정합**: raw `KeyError` 노출 X. helper `get_perception_guide()` 안 `AppError` 또는 equivalent contract error로 wrap. internal dict indexing = implementation detail.
- **N-7 2-stage check 정합**: stage 1 = `_validate_perception_mode(pm)` enum membership (unknown → AppError `perception_mode.unknown`). stage 2 = `pm in _PERCEPTION_GUIDES` check (direct ∉ → AppError `perception_mode.guide_not_applicable` raise BEFORE dict lookup). raw `KeyError` 노출 0.
- **mirror entry wording = OQ-3 spec v1 resolved (Codex N-4 정합)**: 별도 wording 채택 (reflection wording 복사 reject). policy = non-direct 7-key coverage + AppError no-fallback. mirror specific behavior wording (Korean draft, plan v1 안 final lock):
  ```
  "이 장면은 거울(mirror) surface 안에 보이는 것이므로, 거울면의 좌우 반전 + 반사면 quality (광택 / 표면 상태 / 변형 정도) + 거울 frame 가시 여부를 t2i_prompt 에 반영하세요. reflection (일반 반사면) 과 다른 mirror specific 행동: 거울 가장자리 / 거울 frame / mirror image 의 별도 surface 명시."
  ```
  plan v1 안 exact Korean wording lock (W2 commit body 안 명시). reflection entry wording 과 구분 (reflection = 일반 반사면 = "거울/수면 반사이므로 좌우 반전된 구도를 고려하세요" 유지; mirror = 거울 surface 특정 행동).

### 3.4 OQ-4 — shot_staging v14 bump scope

**결정 = (a) v14 prompt-pack 신설 (2 file) + version_registry sync + JSON enum 강제** (Codex 권장):

- `prompts/_base/shot_staging/14.UTC_TIMESTAMP/`:
  - `schema.json` (line 18 `perception_mode` → `"enum": [...]` JSON enum 강제 8-token)
  - `system.md` (perception_mode 전용 section 신규 추가, 8-token enum semantics)
- `backend/app/core/version_registry.py` shot_staging entry bump
- `prompt_dep` 4-point sync verify
- v13 narrow description-only `mirror` 추가 옵션 (b) reject (validation contract 강화 의무, JSON enum 강제 = Structured SOT Required gate 3 정합)

### 3.5 OQ-5 — `step_manifest.shot_staging.schema_version` bump policy

**결정 = (b) prompt/schema version bump only** (Codex 권장 = no cp invalidation):

- 현재 `step_manifest.py:600 schema_version=5` (Area #2 W3 2026-05-17 bump).
- C1 v1 = enum/value validation contract 강화 (output 필드 shape 추가 X, cp consumer cross-check 영향 X).
- `perception_mode` 이미 required field (carrier preserve), schema 강화는 producer-side LLM emit stricter 만, downstream cp shape 변경 0.
- schema_version 6 bump (옵션 (a)) reject — cp invalidation cascade 회피.
- **단, source proof 의무** (Codex required guard): spec 안 `shot_staging/13/schema.json:169` `required` 안 `perception_mode` 포함 명시 + cp consumer cross-check 영향 0 proof.

### 3.6 OQ-6 — `scene_detail` v29 bump policy

**결정 = (a-amended) v29 prompt-pack + `SCENE_DETAIL_PROMPT_VERSION` bump + cp config_hash invalidation accepted; schema_version 11 preserve** (Codex W3 mid-execution blocker W3_PLAN_AMEND_OPTION_가 정합).

**W3 blocker 발견 (W3 execution 중)**: `detail_steps.py:107` `SCENE_DETAIL_PROMPT_VERSION` 는 cp `config_hash` 입력 (`detail_steps.py:108-111` comment + `:987-990` `_config_hash()` payload `"prompt_version"` + `:1449-1450` checkpoint config_hash 영속). scene_detail v29 prompt 활성화 = `SCENE_DETAIL_PROMPT_VERSION` bump 불가피 → config_hash 변경 불가피. 초기 OQ-6 (c) "cp preserve" 는 false assumption.

- `prompts/_base/scene_detail/29.UTC_TIMESTAMP/`:
  - `system.md` (line 64 4-token closed list → `REPRODUCTION_PERCEPTION_MODES` helper SOT cross-reference)
  - `detail_schema.json` (변경 0, sibling byte-identical)
- `backend/app/core/version_registry.py` scene_detail_composer entry bump (1.28.0 → 1.29.0, v28 → v29).
- **`backend/app/core/steps/detail_steps.py` `SCENE_DETAIL_PROMPT_VERSION` bump** (`"28.202605190927"` → `"29.UTC_TIMESTAMP"`). cp config_hash 변경 = stale cp re-run trigger = **consumer prompt 변경의 정상 결과로 수용**.
- `SCENE_DETAIL_SCHEMA_VERSION = 11` preserve. output checkpoint data shape 변경 0. **본 변경 = config_hash invalidation, NOT schema_version/data-shape invalidation**.
- `step_manifest.scene_detail.schema_version` bump 0 (output shape 변경 X).
- v28 narrow edit 옵션 (b) reject (prompt-pack/versioning doctrine 위배). Layer 4 defer 옵션 (다) reject (C1 source map 안 scene_detail consumer prompt cross-reference 포함, defer 시 C1 incomplete).

## 4. Invariants (Codex required spec drafting guard)

### 4.1 Strict subset invariant

`REPRODUCTION_PERCEPTION_MODES ⊂ PERCEPTION_MODES` (strict subset). `assert REPRODUCTION_PERCEPTION_MODES < PERCEPTION_MODES` test gate.

### 4.2 Schema enum sync invariant

`set(shot_staging/v14/schema.json#perception_mode.enum) == PERCEPTION_MODES` (N-2 fix: set equality, JSON enum literal order canonical 가능, lexicographic ordering 비교 X). 8-token 1:1 match. canonical literal order = `direct/hallucination/dream/memory/reflection/mirror/through_device/projection` (spec drafting 안 명시 권장, but test gate는 set 비교).

### 4.3 Non-direct guide coverage invariant

**helper-owned `_PERCEPTION_GUIDES` mapping coverage (N-5 fix)**: `set(perception_mode._PERCEPTION_GUIDES) == PERCEPTION_MODES - {"direct"}` (7-key full coverage). detail_steps-local map X (W2 시 제거). 또는 behavioral verification: `for pm in PERCEPTION_MODES - {"direct"}: get_perception_guide(pm)` returns non-empty string and does not raise.

### 4.4 Single material decision site invariant

`render_prompt_card.py` 안 perception_mode literal 4-tuple branch = 0 (helper consume). cascade 4 material sites = `{:827, :1144, :3449, :3486}` (Codex Fix 1/3 정합).

### 4.5 Boundary preserve invariant (Area C closed)

`_ALLOWED_DIRECTIONALITY_CLASSES` + `_REPRODUCTION_SURFACE_CLASSES` + fail-fast (`:124, :134, :1038-1043`) + `shot_staging/13/schema.json:80-86` 변경 0. **Scoped diff -u empty** (N-3 fix): full file diff X (v14 schema.json line 18 perception_mode change → v13 vs v14 full diff non-empty 필연). G6 test gate = marker/slice-scoped extraction (directionality_class block + key_bg_elements required list 만 extract 후 `diff -u`).

### 4.6 NO VLM invariant

본 spec 안 VLM 기반 판단 / 검증 / 성능 전제 / 의존 구조 0 ([[feedback_no_vlm_dependency]]).

### 4.7 No silent fallback invariant (Gate 4)

Unknown perception_mode (PERCEPTION_MODES 외 string) → `AppError` contract error raise. generic fallback path 0. Test gate.

## 5. W1-W4 Implementation Plan

### 5.1 W1 — Producer + helper atomic (1 commit)

Wave scope:
- **W1.1 v14 prompt-pack 신설**: `prompts/_base/shot_staging/14.${UTC}/` 2 file (schema.json + system.md). actual UTC = W1 commit time bash substitute.
- **W1.2 helper 신설**: `backend/app/core/perception_mode.py` (constants + 2 API).
- **W1.3 version_registry sync**: `backend/app/core/version_registry.py` shot_staging entry bump.
- **W1.4 STEP_MANIFEST verify**: `step_manifest.py:600 schema_version=5` preserve (OQ-5 (b) 채택).

Gate 5 verify:
1. v14 dir file count = 2 (no sibling byte-identical).
2. schema.json `set(perception_mode.enum) == PERCEPTION_MODES` (N-2 fix: set equality).
3. helper module imports clean (no Area C boundary import).
4. version_registry shot_staging entry version-bumped.
5. STEP_MANIFEST shot_staging.schema_version == 5 (preserve).

### 5.2 W2 — Consumer code switch atomic (1 commit)

Wave scope:
- **W2.1 render_prompt_card.py:1144-1146 switch**: 4-tuple literal → `is_reproduction_perception()` helper consume.
- **W2.2 detail_steps.py:2221-2231 switch**:
  - **N-8 fix wording**: helper-owned `_PERCEPTION_GUIDES` mirror entry already added in W1 (helper sketch §1.2). detail_steps local `_perception_guides` block 제거 (W2). non-direct branch consumes `get_perception_guide(perception)` (helper-owned mapping, no local map).
  - generic fallback path 폐기 → `get_perception_guide()` helper consume + AppError contract error (unknown → `perception_mode.unknown` / direct → `perception_mode.guide_not_applicable`, N-7 2-stage check 정합).
  - C6 color prose boundary = helper-owned guide string 안 preserve (detail_steps-local map 제거 후, helper module 안 owned 형태).
- **W2.3 cascade 4 sites verify**: `:827, :1144, :3449, :3486` material site preserve, comment lines `:963, :1098, :1181, :2321, :3545` 변경 0.
- **W2.4 Area C boundary preserve verify**: `_ALLOWED_DIRECTIONALITY_CLASSES` + fail-fast 변경 0 diff verify.

Gate 6 verify:
1. render_prompt_card no inline 4-tuple literal branch (grep negative).
2. helper-owned `_PERCEPTION_GUIDES` keys == PERCEPTION_MODES - {"direct"} (N-5 fix: 7-key full coverage, set equality not order). detail_steps local `_perception_guides` block 제거 verify (grep negative on `detail_steps.py` 안 `_perception_guides` literal).
3. detail_steps generic fallback line `.get(perception, f"이 장면은 {perception}...")` 0 hit (grep negative).
4. cascade 4 material sites preserve diff -u empty (signature/call site unchanged).
5. Area C `_ALLOWED_DIRECTIONALITY_CLASSES` + fail-fast scoped diff -u empty (marker/slice extraction per G6 N-3 fix).
6. helper module imports verify (no circular import, no Area C boundary leak — `directionality_class`/`_ALLOWED_DIRECTIONALITY_CLASSES`/`_REPRODUCTION_SURFACE_CLASSES` import 0).
7. **Helper invalid input fail-fast (N-1 fix)**: `is_reproduction_perception("future_unknown_mode")` raises `AppError` (not silent false). `get_perception_guide("future_unknown_mode")` raises `AppError("perception_mode.unknown")` (not generic fallback). `_validate_perception_mode("")` raises `AppError` (empty outside PERCEPTION_MODES).
8. **`direct` guide-not-applicable fail-fast (N-7 fix)**: `get_perception_guide("direct")` raises `AppError("perception_mode.guide_not_applicable")` (not raw `KeyError`, not silent default). 2-stage check verify: stage 1 enum membership (direct ∈ PERCEPTION_MODES pass), stage 2 `pm in _PERCEPTION_GUIDES` check (direct ∉ → AppError raise BEFORE dict lookup).

### 5.3 W3 — scene_detail v29 + integrated test atomic (1 commit)

Wave scope:
- **W3.1 v29 prompt-pack 신설**: `prompts/_base/scene_detail/29.${UTC}/` (system.md narrow edit + detail_schema.json sibling byte-identical).
- **W3.2 system.md:64 cross-reference rewrite**: 4-token closed list → enum helper cross-reference.
- **W3.3 version_registry sync**: scene_detail entry bump.
- **W3.4 integrated test 신설**: `backend/tests/integration/test_c1_perception_mode_enum_sot.py` (G1-G7).

Gate 7 verify (W3 integrated test gates):
- **G1 Producer schema enum 8-token strict**: v14 schema.json `set(perception_mode.enum) == PERCEPTION_MODES` (N-2 fix: set equality, lexicographic order 비교 X).
- **G2 mirror presence (N-5 fix)**: `"mirror"` ∈ schema enum + helper PERCEPTION_MODES + REPRODUCTION_PERCEPTION_MODES + helper-owned `_PERCEPTION_GUIDES` keys (detail_steps local map 제거 후, helper module 안 mapping owner). Behavioral verify: `get_perception_guide("mirror")` returns non-empty string and does not raise.
- **G3 Consumer code helper consume**: `render_prompt_card.py:1144` no inline 4-tuple literal branch + `is_reproduction_perception()` import present.
- **G4 Cascade no generic fallback**: `detail_steps.py:2221-2231` block no `f"이 장면은 {perception}..."` literal + `get_perception_guide()` consume + AppError on unknown.
- **G5 Consumer prompt cross-reference**: `scene_detail/v29/system.md:64` (line drift OK) no 4-token literal list + helper cross-reference wording.
- **G6 Area C boundary preserve (scoped, N-3 fix)**: marker/slice-scoped extraction + diff:
  - code anchor: `_ALLOWED_DIRECTIONALITY_CLASSES` block (line 124-) + `_REPRODUCTION_SURFACE_CLASSES` block (line 134-) + fail-fast block (`:1038-1043`) v13 working tree → unchanged at W2 commit (Python AST or line slice extraction, then `diff -u` empty).
  - schema anchor: `shot_staging/13/schema.json:80-86` directionality_class block + `required` list line 86 안 `directionality_class` 포함 → `shot_staging/v14/schema.json` 안 same block extraction → `diff -u` empty. v14 schema.json 안 `perception_mode` line 18 change는 본 G6 scope 외 (Area C 영역 외 변경 expected non-empty).
  - 즉 **full file diff 비교 X**, marker block 또는 line-range slice extraction 후 비교.
- **G7 NO VLM canary + N-1 invalid input + N-7 direct guide-not-applicable fail-fast**:
  - helper module + test module no VLM import + no live LLM dependency
  - closed-world residue catalog 0 count
  - **Invalid input fail-fast verify (N-1 fix)**: `is_reproduction_perception("future_unknown_mode")` raises `AppError` (not silent false). `get_perception_guide("future_unknown_mode")` raises `AppError("perception_mode.unknown")` (not generic fallback). `is_reproduction_perception("")` raises `AppError` (empty outside PERCEPTION_MODES).
  - **direct guide-not-applicable fail-fast verify (N-7 fix)**: `get_perception_guide("direct")` raises `AppError("perception_mode.guide_not_applicable")` (not raw `KeyError`, not silent default). 2-stage check: stage 1 = enum membership pass (direct ∈ PERCEPTION_MODES), stage 2 = `pm in _PERCEPTION_GUIDES` check (direct ∉) → AppError raise BEFORE dict lookup. raw `KeyError` 노출 0 verify.

Total PASS count target = G1-G7 + regression preserve.

### 5.4 W4 — Closure docs atomic (1 commit)

Wave scope (path-limited):
- 본 spec frontmatter `status: closed` + §10 closure section append.
- `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` §5 신설 C1 entry append (Track B Tier α #1 closure).
- `docs/fix-critical-1/index.html` C1 section closure marker + remaining-work guide update (C2-C10 잔여 명시).
- `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md` §15 신설 C1 closure section.
- W4 commit body 안 self-hash literal 0 (TEMPLATE ONLY doctrine, post-Carry-D7d-1 fix-up 1 + post-Carry-A100 N-5 정합).
- W4 marker-aware staged gate stage ordering (post-Carry-P021 plan amend 3 doctrine 정합).

## 6. Boundary Preserve (Area C closed, 변경 0)

| boundary | site | preserve 의무 |
|---|---|---|
| `_ALLOWED_DIRECTIONALITY_CLASSES` frozenset | `render_prompt_card.py:124` | 변경 0 (Area C SOT) |
| `_REPRODUCTION_SURFACE_CLASSES` frozenset | `:134` | 변경 0 (Area C subset) |
| fail-fast `directionality_class_missing/invalid` | `:1038-1043` | 변경 0 (Area C contract) |
| shot_staging directionality_class 5-enum | `shot_staging/13/schema.json:80-86` | 변경 0 (Area C producer) |
| `perception_mode.py` helper field 분리 | new module | directionality_class 영역 import/use X (Codex Guard 1) |
| C2 owned_object_usage[] cross-cut | scene_detail v_next | 본 spec 안 진입 X (별도 area) |
| C7 shot_dependency_t2i cross-cut | shot_dependency_t2i v_next | 본 spec 안 진입 X (별도 area) |
| C6 color_palette_intent cross-cut | detail_steps v_next | 본 spec 안 진입 X (별도 area). **helper-owned `_PERCEPTION_GUIDES`** (N-5 fix: detail_steps local map 제거 후) 안 dream/memory/hallucination color prose = C6 영역 boundary preserve, 본 spec 안 mirror entry 추가만. color_palette_intent migration 시 helper-owned guide string 안 color prose 별도 area 가 처리 |

## 7. Test Gates (G1-G7 + canary)

§5.3 W3.4 안 명시. summary:

- G1 Producer schema enum 8-token strict
- G2 mirror presence (4-layer cross-check)
- G3 Consumer code helper consume (no inline literal)
- G4 Cascade no generic fallback (fail-fast AppError on unknown)
- G5 Consumer prompt cross-reference (no 4-token literal)
- G6 Area C boundary preserve (diff -u empty)
- G7 NO VLM canary + closed-world residue catalog 0 count

Regression preserve target (M-1 fix): existing tests anchored on `perception_mode` propagation = `backend/tests/unit/test_render_prompt_card.py` + `backend/tests/unit/test_render_prompt_card_integration.py` (render_prompt_card reflection branch propagation tests). 현재 `_perception_guides` 직접 consumer test 없음 — W3 integrated test (`test_c1_perception_mode_enum_sot.py`) 안 신규 추가 (mirror entry coverage + non-direct 7-key dispatch + AppError on unknown). existing unit test modification = render_prompt_card 안 4-tuple literal expectation → helper consume expectation.

## 8. Risk + Open Issues

### 8.1 Risk

- **OQ-3 resolved (N-4 fix)**: spec v1 안 mirror specific wording 결정 (§3.3 채택 — 별도 wording, reflection wording 복사 reject). policy = non-direct 7-key coverage + AppError no-fallback. plan v1 안 exact Korean guide string final lock (W2 commit body 안 명시).
- **OQ-5 schema_version bump 결정 source proof**: spec 안 `shot_staging/13/schema.json:169 required perception_mode 포함` 명시 + cp consumer cross-check 영향 0 proof 의무.
- **W1 v14 dir TEMPLATE ONLY doctrine**: actual UTC = W1 commit time bash substitute (`$(date -u +%Y%m%d%H%M)` runtime, plan §6.3 awk marker-aware gsub post-Carry-P021 lesson 정합).
- **Helper invalid input fail-fast (N-1)**: `is_reproduction_perception()` + `get_perception_guide()` 안 `_validate_perception_mode()` gate via AppError contract error (not raw KeyError, not silent false). Caller convention = legacy default coerce 책임 (`pm = perception_mode or "direct"`).

### 8.2 Open issues (plan v1 drafting 안 lock)

- mirror entry exact Korean guide wording final lock = plan v1 안 (policy resolved, wording detail only).
- v14/v29 actual UTC timestamp = W1/W3 execution 안 bash substitute.
- W3 regression test count target = plan v1 drafting 안 estimate.

## 9. Doctrine references

- [[feedback_llm_based_judgment]] — 4-Gate Boundary Doctrine (Semantic Regex Ban / Prompt Closed-List Ban / Structured SOT Required / No Silent Fallback)
- [[feedback_no_vlm_dependency]] — NO VLM hard constraint
- [[feedback_audit_analysis_vs_spec_entry]] — audit/analysis ≠ spec entry, 명시 결정 의무
- [[feedback_codex_mcp_discussion_workflow]] — Codex MCP 매 결정 단계 의논 mechanical doctrine
- [[feedback_codex_mcp_claude_mcp_response_trigger]] — prefix `[claude 로부터 요청]` + suffix `claude mcp 로 응답필요함` 의무
- [[feedback_subagent_model_opus]] — subagent dispatch 시 model=opus 강제
- [[feedback_session_50pct_codex_close_check]] — session 50%+ 도달 시 Codex MCP close 의논 의무
- [[project_fix_critical_1_persistent]] — `docs/fix-critical-1/index.html` persistent task source

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

### 10.1 W1-W4 commit chain

| Wave | commit | scope |
|---|---|---|
| W1 | `4655741` | producer + helper atomic — shot_staging v14 prompt-pack (2 file) + `perception_mode.py` helper + version_registry shot_staging bump (2.6.0 → 2.7.0, v13 → v14) + test_shot_staging_v13_schema alignment (5 file: 3 new + 2 modify) |
| W2 | `c486993` | consumer code switch atomic — render_prompt_card.py `is_reproduction_perception()` consume + detail_steps.py `get_perception_guide()` consume (local map 폐기) (2 file modify) |
| W3 | `cfc0347` | scene_detail v29 consumer prompt + `SCENE_DETAIL_PROMPT_VERSION` bump + integrated test G1-G7 + 4 existing version-alignment test update (9 file: 2 new + 2 modify + 1 new test + 4 existing test modify) |
| W4 | 본 closure commit | closure docs atomic (path-limited) + plan-only correction (Codex W3 plan-only narrow re-review 흡수) |

### 10.2 OQ resolution 최종 (W3 amend 반영)

- OQ-1 8-token enum / OQ-2 `perception_mode.py` helper / OQ-3 generic fallback 폐기 + AppError fail-fast / OQ-4 v14 prompt-pack 2 file + JSON enum.
- OQ-5 = shot_staging schema_version 5 preserve (no bump, no cp invalidation).
- **OQ-6 (a-amended, W3 blocker W3_PLAN_AMEND_OPTION_가)**: scene_detail v29 + `SCENE_DETAIL_PROMPT_VERSION` bump (28.* → 29.*) = cp config_hash invalidation accepted (consumer prompt 변경 정상 결과). `SCENE_DETAIL_SCHEMA_VERSION` 11 preserve (output cp data shape 변경 0).

### 10.3 Codex review trace

- spec 3 round narrow re-review: N-1~N-8 + M-1 흡수 → APPROVED_FOR_PLAN.
- plan 4 round narrow re-review: N-1~N-13 흡수 → APPROVED_FOR_EXECUTION.
- W1 per-wave: W1_APPROVED + plan-only amend (PYTHONPATH → venv python) → APPROVED_FOR_W2_ENTRY.
- W2 per-wave: APPROVED_FOR_W3_ENTRY.
- W3 mid-execution blocker: `SCENE_DETAIL_PROMPT_VERSION` = cp config_hash 입력 발견 → W3_PLAN_AMEND_OPTION_가 (spec OQ-6 + plan amend).
- W3 per-wave: W3_APPROVED + plan-only narrow correction (`cp preserve` wording) → APPROVED_FOR_W4_ENTRY.

### 10.4 함정 / lesson learned

- **N-exec-1 (W3 blocker)**: `SCENE_DETAIL_PROMPT_VERSION` (`detail_steps.py:107`) 는 cp `config_hash` 입력. consumer prompt version bump = config_hash invalidation 불가피. "cp preserve" 는 false assumption — prompt version 이 config_hash 입력인지 spec drafting 안 source verify 의무. config_hash invalidation ≠ schema_version/data-shape invalidation 구분.
- **N-exec-2 (W1 gate)**: `PYTHONPATH=backend python3` (system python3) 에 fastapi 미설치 → app.core.errors (fastapi 의존) import gate fail. venv python (`backend/.venv/bin/python`) 의무. plan gate 작성 시 venv python 사용 명시.
- **N-exec-3 (W3 test alignment)**: `SCENE_DETAIL_PROMPT_VERSION` bump 시 `test_prompt_versions.py` 안 `startswith("28.")` assertion (line 214) + `test_g4_5a` config_hash surrogate (line 771) 까지 모두 영향 — version constant bump 시 grep -rn `"28."` 전수 의무.

### 10.5 carry priority post-C1

C1-C10 잔여 (fix-critical-1 index.html): **C2 owned_object_usage[] echo** → C3 detail_steps focus rewrite → C7 #12 shot_dependency_t2i drift → C6 #10 color_palette_intent → C9 L-7 body-light verb → C4 #8 background classifier → C5 #9 action segment SOT → C8 #7 잔여 sub-area split → C10 future structured carries.
