---
title: Area #7d — cinematography closed-list SOT dual-site (Track B Tier 3 #4, v1) — Implementation Plan
date: 2026-05-19
status: drafting
spec_ref: docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md (commit `424c8e9`, status APPROVED_FOR_PLAN; spec drafting stack = `e0cf96d` spec v1 351 ins + `424c8e9` amend 1 narrow N-1/N-2)
prerequisites:
  - Spec Area #7d v1 (commit `424c8e9`, Codex spec iter 1 narrow re-review 2nd round APPROVED_FOR_PLAN).
  - Hygiene v1 (closed 2026-05-15, commit `91d1dfd`).
  - Area #6 (T2I Review Mutation Redesign, closed 2026-05-18+, push `0a659f6`) — boundary preserved.
  - Area #7a (VWR SOT v1, closed 2026-05-19, push `d0410eb`) — boundary preserved.
  - Area #7b (VWR Downstream Cascade v1, closed 2026-05-19+, push `e751e72`) — scene_detail/27.202605181937 4-point sync 정합 source, Carry-D7d-1 boundary reaffirm only.
  - Area #7c (t2i_review v6 placeholder example abstraction v1, closed 2026-05-19+, push `62aa2ef`) — t2i_review v7 boundary preserved.
---

# Area #7d — cinematography closed-list SOT dual-site (v1) — Implementation Plan

## 1. Purpose + Wave Overview

본 plan = Area #7d spec v1 (`424c8e9`, 351 line) implementation 의 W1/W2/W3/W4 atomic wave 실행 명세. spec §1.2 dual-site (variation_recommender/v2/system.md + scene_extractor_v2/20.<UTC-timestamp>/turn_scene_detail.md) + spec §1.2 E6 mandatory (prompt_service.py:347-352) + spec §6 wave plan + spec §7 5 verify gate + spec §8 doctrine (4-Gate + NO VLM + TEMPLATE ONLY + W3/W4 self-reference + Substitution self-referential bug) 정합. Area #7c plan template (commit `3bf7c22`, 697 line) 계승.

### 1.1 Wave Summary Table

| Wave | Purpose | Atomic commit | Path-limited scope | Codex review |
|---|---|---|---|---|
| **W1** | Producer prompt-pack + schema-description sync atomic (Site 1 + Site 2 + variation_recommender_v2.py schema-description) | 1 commit | `prompts/_base/scene_extractor_v2/20.<UTC>/` + `prompts/_base/variation_recommender/v2/system.md` + `backend/app/modules/variation_recommender_v2.py` | W1 per-wave APPROVED |
| **W2** | E6 prompt_service.py:347-352 regex narrow/remove atomic (cleaned consumer cascade verify obligatory) | 1 commit | `backend/app/services/prompt_service.py` | W2 per-wave APPROVED |
| **W3** | Integrated test atomic wave (5 verify gate, no live LLM + NO VLM + no semantic regex) | 1 commit | `backend/tests/integration/test_area_7d_cinematography_sot.py` | W3 per-wave APPROVED |
| **W4** | Closure docs atomic (spec status closed + §11 Closure + roadmap §5.7 Sub-area (e) verbatim + audit 02 narrow cross-ref + audit 03 L-8 신설) | 1 commit | 3-5 explicit doc paths | W4 per-wave + W1-W4 range review → APPROVED_FOR_PUSH |

### 1.2 Preflight Checklist (W0 inline)

- venv: `backend/.venv/bin/python` / `backend/.venv/bin/pytest` (absolute path).
- Spec commit anchor: `424c8e9` (HEAD).
- origin/main HEAD: `62aa2ef` (Area #7c closure).
- Unpushed before W1: 2 commits (`e0cf96d` spec v1 + `424c8e9` amend 1 narrow). W1-W4 모두 완료 후 push (Area #7c 패턴 정합).
- Working tree: untracked artifacts (db.sqlite + audit_outputs/ + docs/canvas-storyboard-research-2026/ + docs/code-reviews/ + `docs/superpowers/specs/2026-05-18-area-7a-w0-inventory.md`) 보존 — Area #7d scope 외, path-limited git add 의무.
- UTC timestamp resolution at W1 execution: `date -u +%Y%m%d%H%M` → `20.YYYYMMDDHHmm` dir name (e.g. `20.202605192100`). Area #7b W1 date drift trap 정합 — placeholder를 실제 UTC 날짜로 narrow amend 필요 시 별도 amend wave 분리.
- Codex MCP pane `%0` (gpt-5.5 xhigh) idle, ready for per-wave review.
- NO VLM hard constraint inline (image-side VLM 의존 X — production variation_recommender_v2.py GPT Vision = 정상 모듈, hygiene scope 외).
- 4-Gate Boundary Doctrine inline (Semantic Regex Ban / Prompt Closed-List Ban refined full-phrase only / Structured SOT Required / No Silent Fallback).
- pane label: claude=%1 / codex=%0 (양쪽 setup 완료 plan iter 0 entry 시).

### 1.3 Doctrine Reaffirm (inline)

- **Standalone token global ban X** (Codex iter 0 권고 6 흡수): plan W3 test 안 standalone token (`low angle` / `warm amber` / `bird's eye` 등) 0-hit 검사 절대 금지. spec §2.6 R1-R9 full-phrase exact 만 검사. Area #7b refined doctrine 정합.
- **canonical wording verbatim 강도** (Codex iter 0 권고 3 흡수): spec §2.6 R1-R9 = 글자-단위 그대로 W3 test STRICT_RESIDUE_* 안 복사. Python implicit string concat 금지 (눈으로 drift 위험), triple-quoted string 또는 단일 문자열 escape 명시. regex literal (R7-R9) = regex pattern 실행 X, plain string membership 만 검사.
- **W2 closed-list regex 제거 우선** (Codex iter 0 권고 4 흡수): 본 v1 = E6 regex 자체 폐기 권장. 남기는 경우도 cinematography semantic token-list regex 절대 금지 (4-Gate Semantic Regex Ban 정합).
- **W1 bundle abort condition** (Codex iter 0 권고 5 흡수): W1 implementation 중 consumer behavior / code flow 변경 발생 시 → W1 atomic 중단 → W2 또는 별도 amend 분리.
- **W4 push 완료 pre-claim 금지 + audit 02 §11.2 untouched** (Codex iter 0 추가 guard 흡수): live closure docs 안 push-complete pre-claim 0. final pushed range/count = external memory only. audit 02 §11.2 VWR downstream table heading-anchored diff = W4 pre-commit gate.

## 2. Plan Iter Trace

- **iter 0** (본 plan session, strategy 본 instance proposed outline + Codex MCP 의논): **APPROVED_FOR_PLAN_V1_DRAFT_WITH_NARROW_GUARDS** (Codex 10 narrow guidance — §1-§10 OK / §3-§6 wave concise-but-complete inline / 750-850 line OK 950 까지 수용 / W0.3 4 item OK scene_detail/27 4-point sync = Carry-D7d-1 boundary reaffirm only W1 blocker X / canonical wording 강하게 verbatim Python implicit string concat 금지 plain string membership 만 / Pre-Commit count W1 8 W2 5 W3 5 W4 6 OK W2 cleaned cascade 의미 명시 / W1 bundle OK consumer behavior 변경 시 중단 / standalone token global ban 테스트 금지 명시 R1-R9 only / W4 roadmap wording exact `Sub-area (e) cinematography closed-list SOT dual-site` 앞 공백 X / §10 carry 2층 Carry-D7d-1 full inline Carry-D7d-2 conditional / line target 750-850 900 OK Area #7b 과대화 회피 / workflow OK plan body 작성 전 iter 0 verdict Plan Iter Trace 반영 / 추가 guard W1 UTC date-drift 절차 + W4 push 완료 pre-claim 금지 audit 02 §11.2 untouched heading-anchored diff pre-commit gate).
- **iter 1** (예정 post-commit): plan v1 full quality review.
- **iter 2+** (예정 narrow): amend cycle if needed.
- **iter N**: **APPROVED_FOR_EXECUTION** → W1 entry session health 재평가.

## 3. W1 — Producer Prompt-Pack + Schema-Description Sync Atomic Wave

### 3.1 Action

3 atomic action bundle 1 commit (Codex iter 0 권고 5 흡수 = scene_extractor_v2/20 pack + variation_recommender/v2/system.md + variation_recommender_v2.py schema-description sync = 1 producer contract atomic):

1. `prompts/_base/scene_extractor_v2/20.<UTC-timestamp>/` 신규 dir 생성 + 5 file write (2 modified + 3 byte-identical sibling from v19).
2. `prompts/_base/variation_recommender/v2/system.md` narrow edit (line 23 + 29 examples 4-phrase block 폐기 → open-world derivation hint). v3 dir 생성 X (variation_recommender_v2.py PROMPTS_DIR hardcode `prompts/_base/variation_recommender/v2/` 정합).
3. `backend/app/modules/variation_recommender_v2.py` `_VARIATION_ITEM_SCHEMA` composition (line 45) + color (line 49) description narrow edit (examples hard-prime 제거, shape preserve).

### 3.2 W0.3 Entry Sanity Hard Gate (W1 entry 직전 plan §3 안 inline)

Codex iter 0 권고 2 흡수 = 4 hard gate W1 blocker. scene_detail/27 4-point sync = Carry-D7d-1 boundary reaffirm only (W1 blocker X).

**Gate (a)** — scene_extractor_v2 file fallback latest = 19.202605161434 확인:
```bash
ls -d prompts/_base/scene_extractor_v2/*/ | sort -V | tail -1
# Expected: prompts/_base/scene_extractor_v2/19.202605161434/
```

**Gate (b)** — 19.202605161434/ pack 5 file inventory:
```bash
ls prompts/_base/scene_extractor_v2/19.202605161434/ | sort
# Expected (5 file exactly):
#   scene_detail_schema.json
#   system.md
#   turn0_context.md
#   turn1_split_long.md
#   turn_scene_detail.md
test "$(ls prompts/_base/scene_extractor_v2/19.202605161434/ | wc -l)" -eq 5 || { echo "FAIL: v19 pack file count != 5"; exit 1; }
echo "OK 19. pack 5 file exact"
```

**Gate (c)** — prompt_service E6 reachability + cleaned cascade exact flow fact:
```bash
# E6 regex block reachable verify (line 346-351 exact location)
sed -n '345,352p' backend/app/services/prompt_service.py | grep -q "카메라 앵글 제거" \
  && echo "OK E6 regex block reachable at line 345-352" \
  || { echo "FAIL: E6 regex block location drift"; exit 1; }
# cleaned cascade exact flow fact (replace_entity_ids → translate_if_korean → build_scene_text)
rg -n "def replace_entity_ids|cleaned = replace_entity_ids|cleaned = translate_if_korean|build_scene_text\(" backend/app/services/prompt_service.py
# downstream caller flow (build_final_scene_prompt alias _build_final_scene_prompt)
rg -n "build_final_scene_prompt|_build_final_scene_prompt" backend/app/services/scene_generation_coordinator.py backend/app/services/scene_reference_service.py
# Expected:
#   prompt_service.py:265 def replace_entity_ids
#   prompt_service.py:497 cleaned = replace_entity_ids(...)
#   prompt_service.py:498 cleaned = translate_if_korean(...)
#   prompt_service.py:509 build_scene_text(
#   scene_generation_coordinator.py:55 build_final_scene_prompt as _build_final_scene_prompt
```

**Gate (d)** — scene_detail/27 4-point sync reaffirm (Carry-D7d-1 boundary only):
```bash
# 4-point sync confirm — W1 blocker 아님, Carry-D7d-1 deferred boundary reaffirm 정합
grep -n "SCENE_DETAIL_PROMPT_VERSION = \"27.202605181937\"" backend/app/core/steps/detail_steps.py
grep -n '"scene_detail_composer": "1.27.0"' backend/app/core/version_registry.py
grep -n 'prompt_dependency.*scene_detail/v27' backend/app/modules/pipeline/scene_detail_composer.py
ls prompts/_base/scene_detail/27.202605181937/
# Expected: all 4-point sync confirm — Carry-D7d-1 boundary preserved (W1 변경 0)
```

**4 hard gate 모두 PASS 후 W1 execution 진입**. Gate (c) cleaned cascade fact = W2 plan §4 안 활용.

### 3.3 File Creation Specification (Site 2 v20 pack)

새 dir `prompts/_base/scene_extractor_v2/20.<UTC-timestamp>/` 안 5 file. **HISTORICAL AUDIT TRAIL / TEMPLATE ONLY marker** — design-time `20.<UTC-timestamp>` = W1 execution 시 controller awk substitute target (Substitution self-referential bug doctrine 정합 = doctrine description block 안 placeholder marker wrap 의무, plan §8.3 참조).

| New file (under `prompts/_base/scene_extractor_v2/20.<UTC-timestamp>/`) | Source | Action |
|---|---|---|
| `turn_scene_detail.md` | v19 `19.202605161434/turn_scene_detail.md` + §3.4 line 87-91 replacement | new file with 7+7 closed-list 폐기 + carrier description narrow-align (4 replacement site) |
| `scene_detail_schema.json` | v19 byte-identical copy + §3.5 line 15 narrow-align edit | new file with `camera_effect` description narrow update (type=string preserve + required preserve) |
| `system.md` | v19 byte-identical copy | new file (sha256 match obligatory) |
| `turn0_context.md` | v19 byte-identical copy | new file (sha256 match obligatory) |
| `turn1_split_long.md` | v19 byte-identical copy | new file (sha256 match obligatory) |

### 3.4 Site 2 Replacement Edit Table (turn_scene_detail.md line 87-91, exact After text)

spec §2.6 R1-R4 strict residue 0 obligatory + open-world derivation wording exact After text 확정 (Codex iter 1 F-1 흡수). W1 execution 시 본 table After wording 그대로 사용 (재설계 X).

| line (v19 reference) | legacy phrase (R-id verbatim from spec §2.6) | exact After text |
|---|---|---|
| 87 | `카메라 구도 선택지: low angle / high angle / dutch angle / over-the-shoulder / bird's eye / extreme wide / tight medium` (R1) | `카메라 구도는 시나리오 context와 camera_direction reference (shot_staging) 기반 자유 derivation. 각 변형은 시나리오 cinematic intent에 적합한 형식을 선택.` |
| 88 | `색감 선택지: warm amber / cold blue / high contrast / desaturated / golden hour / neon-lit / silhouette backlight` (R2) | `색감/조명은 시나리오 cinematic intent와 시간대, 공간감 기반 자유 derivation. mood와 분위기를 reflect.` |
| 90 | `각 변형의 camera_effect 필드에 "카메라구도 + 색감"을 명시하세요.` (R3) | `각 변형의 camera_effect 필드에 카메라 구도와 색감/조명을 자유 텍스트로 표현 (closed-list 강제 X, 시나리오 derivation only).` |
| 91 | `예: "low angle, warm amber lighting" / "high angle, cold blue-toned" / "dutch angle, high contrast silhouette"` (R4) | (line 폐기 — 예시 hard-prime 제거, LLM 자유 emit) |

**carrier shape preserve obligatory**: spec §4.1 정합 = `camera_effect` string field + required list 정합 preserve. line 83-86 surrounding context (`{variation_count}개 구성 — 모든 변형에 카메라 구도+색감 필수` 등) 변경 0.

### 3.5 Site 2 Schema Narrow-Align (scene_detail_schema.json line 15, exact After text)

| location | legacy description | exact After text |
|---|---|---|
| `scene_detail_schema.json:15` (under `camera_effect.description`) | `"카메라 구도 + 색감 (필수)"` (v19) | `"카메라 구도 + 색감 (필수, 시나리오 derivation; closed-list 강제 X)"` |

**Shape preserve**: `type="string"` + required list 안 `camera_effect` 포함 preserve. JSON Schema 다른 field 변경 0.

### 3.6 Site 1 Replacement Edit Table (variation_recommender/v2/system.md line 23 + 29, exact After text)

spec §2.6 R5-R6 strict residue 0 obligatory. Codex iter 1 F-1 흡수 exact After text 확정.

| line (v2 reference) | legacy phrase (R-id verbatim from spec §2.6) | exact After text |
|---|---|---|
| 23 | `- Examples: "tight close-up on face", "wide establishing shot", "over-shoulder framing", "low angle hero shot"` (R5, examples 4-phrase block) | `- Describe the LENS and FRAMING in your own words, derived from the scene's cinematic intent and the camera_direction reference (from shot_staging). Avoid hard-prime examples.` |
| 29 | `- Examples: "warm golden hour lighting", "cold blue moonlight", "high contrast noir shadows", "desaturated muted tones"` (R6, color examples 4-phrase block) | `- Describe the LIGHTING and COLOR tone change in your own words, derived from the scene's cinematic intent. Avoid hard-prime examples.` |

**Surrounding context preserve**: line 21-22 + 24-25 (composition axis explanation) + line 27-28 + 30-31 (color axis) = 변경 0 (examples block 만 교체, axis 분리 narrative preserve).

### 3.7 Site 1 Schema-Description Narrow (variation_recommender_v2.py _VARIATION_ITEM_SCHEMA, exact After text)

spec §2.5 S4 — examples hard-prime 제거. shape preserve. Codex iter 1 F-1 흡수 exact After text 확정.

| location | legacy description | exact After text |
|---|---|---|
| `variation_recommender_v2.py:45` (`composition` property description) | `"Lens/framing note for i2i (NOT angle). e.g. 'tight close-up on face', 'wide establishing shot', 'over-shoulder framing'. Empty if no composition change."` | `"Lens/framing note for i2i (NOT angle). Derive from scene cinematic intent and camera_direction reference. Empty if no composition change."` |
| `variation_recommender_v2.py:49` (`color` property description) | `"Lighting/color change only. e.g. 'warm golden hour', 'cold blue moonlight'. Empty if no color change."` | `"Lighting/color change only. Derive from scene cinematic intent. Empty if no color change."` |

**Shape preserve obligatory**: `_VARIATION_ITEM_SCHEMA` type / properties / required list 정합 preserve (line 24-54 deep dict structure 변경 0).

### 3.8 Pre-Commit Verification (W1, 8 check)

**(a) UTC timestamp resolution + new dir name**:
```bash
UTC=$(date -u +%Y%m%d%H%M)
NEW_DIR="prompts/_base/scene_extractor_v2/20.${UTC}"
test -d "$NEW_DIR" && echo "OK new dir: $NEW_DIR" || { echo "FAIL: new dir missing"; exit 1; }
```

**(b) New dir 5 file count**:
```bash
test "$(ls $NEW_DIR | wc -l)" -eq 5 || { echo "FAIL: 20.<UTC> file count != 5"; exit 1; }
ls "$NEW_DIR" | sort
# Expected:
#   scene_detail_schema.json
#   system.md
#   turn0_context.md
#   turn1_split_long.md
#   turn_scene_detail.md
echo "OK 5 file exact in $NEW_DIR"
```

**(c) Schema sibling 3 file v19 → v20 sha256 byte-identical**:
```bash
V19="prompts/_base/scene_extractor_v2/19.202605161434"
for fname in system.md turn0_context.md turn1_split_long.md; do
  s19=$(sha256sum "$V19/$fname" | cut -d' ' -f1)
  s20=$(sha256sum "$NEW_DIR/$fname" | cut -d' ' -f1)
  [ "$s19" = "$s20" ] && echo "OK sha256 match: $fname" || { echo "DRIFT: $fname"; exit 1; }
done
```

**(d) R1-R4 strict residue 0 in v20 dir (Site 2)**:
```bash
grep -F "카메라 구도 선택지: low angle / high angle / dutch angle / over-the-shoulder / bird's eye / extreme wide / tight medium" "$NEW_DIR/turn_scene_detail.md" \
  && { echo "FAIL: R1 residue in turn_scene_detail.md"; exit 1; } || true
grep -F "색감 선택지: warm amber / cold blue / high contrast / desaturated / golden hour / neon-lit / silhouette backlight" "$NEW_DIR/turn_scene_detail.md" \
  && { echo "FAIL: R2 residue in turn_scene_detail.md"; exit 1; } || true
grep -F '각 변형의 camera_effect 필드에 "카메라구도 + 색감"을 명시하세요.' "$NEW_DIR/turn_scene_detail.md" \
  && { echo "FAIL: R3 residue in turn_scene_detail.md"; exit 1; } || true
grep -F '예: "low angle, warm amber lighting" / "high angle, cold blue-toned" / "dutch angle, high contrast silhouette"' "$NEW_DIR/turn_scene_detail.md" \
  && { echo "FAIL: R4 residue in turn_scene_detail.md"; exit 1; } || true
echo "OK Site 2 R1-R4 strict residue 0 in v20 dir"
```

**(e) R5-R6 strict residue 0 in Site 1 + schema-description (variation_recommender)**:
```bash
SITE1="prompts/_base/variation_recommender/v2/system.md"
grep -F '"tight close-up on face"' "$SITE1" \
  && { echo "FAIL: R5 fragment 1 in $SITE1"; exit 1; } || true
grep -F '"wide establishing shot"' "$SITE1" \
  && { echo "FAIL: R5 fragment 2 in $SITE1"; exit 1; } || true
grep -F '"over-shoulder framing"' "$SITE1" \
  && { echo "FAIL: R5 fragment 3 in $SITE1"; exit 1; } || true
grep -F '"low angle hero shot"' "$SITE1" \
  && { echo "FAIL: R5 fragment 4 in $SITE1"; exit 1; } || true
grep -F '"warm golden hour lighting"' "$SITE1" \
  && { echo "FAIL: R6 fragment 1 in $SITE1"; exit 1; } || true
grep -F '"cold blue moonlight"' "$SITE1" \
  && { echo "FAIL: R6 fragment 2 in $SITE1"; exit 1; } || true
grep -F '"high contrast noir shadows"' "$SITE1" \
  && { echo "FAIL: R6 fragment 3 in $SITE1"; exit 1; } || true
grep -F '"desaturated muted tones"' "$SITE1" \
  && { echo "FAIL: R6 fragment 4 in $SITE1"; exit 1; } || true
# variation_recommender_v2.py schema-description examples 제거 verify
V2PY="backend/app/modules/variation_recommender_v2.py"
grep -F "tight close-up on face" "$V2PY" \
  && { echo "FAIL: R5 leak in variation_recommender_v2.py schema description"; exit 1; } || true
grep -F "warm golden hour" "$V2PY" \
  && { echo "FAIL: R6 leak in variation_recommender_v2.py schema description"; exit 1; } || true
echo "OK Site 1 + schema-description R5-R6 strict residue 0"
```

**(f) load_prompt/load_schema latest v20 smoke (scene_extractor_v2 stems)**:
```bash
cd backend && .venv/bin/python -c "
from app.modules.prompt_loader import load_prompt, load_schema, _resolve_stem_in_pack
# scene_extractor_v2 file-only mode
for stem, ext in [('turn_scene_detail', '.md'), ('scene_detail_schema', '.json'), ('system', '.md'), ('turn0_context', '.md'), ('turn1_split_long', '.md')]:
    fp, ver, _all = _resolve_stem_in_pack('scene_extractor_v2', stem, ext=ext)
    assert fp is not None, f'stem {stem}{ext} not resolved'
    assert ver is not None and ver.startswith('20.'), f'stem {stem}{ext} ver drift: {ver}'
print('OK 5 stem resolve to 20.* numeric-desc latest')
"
```

**(g) _VARIATION_ITEM_SCHEMA shape preservation (Site 1 schema-description sync)**:
```bash
cd backend && .venv/bin/python -c "
from app.modules.variation_recommender_v2 import _VARIATION_ITEM_SCHEMA, _RESPONSE_SCHEMA
schema = _VARIATION_ITEM_SCHEMA
# Shape preserve verify (type / properties keys / required list)
assert schema['type'] == 'object'
assert set(schema['properties'].keys()) == {'type', 'angle', 'composition', 'color', 'reason'}, f'properties drift: {set(schema[\"properties\"].keys())}'
assert schema['required'] == ['type', 'angle', 'composition', 'color', 'reason'], f'required drift: {schema[\"required\"]}'
assert schema['properties']['angle']['properties']['horizontal']['type'] == 'number'
assert schema['properties']['angle']['properties']['vertical']['type'] == 'number'
assert schema['properties']['angle']['properties']['zoom']['type'] == 'number'
assert schema['properties']['composition']['type'] == 'string'
assert schema['properties']['color']['type'] == 'string'
print('OK _VARIATION_ITEM_SCHEMA shape preserved (5 properties + 3D angle + required)')
"
```

**(h) Codex W1 per-wave APPROVED 의무** (entry sanity + per-wave + session health 3 모드).

### 3.9 Path-Limited Git Add Scope

```bash
git add prompts/_base/scene_extractor_v2/20.*/
git add prompts/_base/variation_recommender/v2/system.md
git add backend/app/modules/variation_recommender_v2.py
git status --short
# Expected (Site 2 v20 5 file + Site 1 1 file + Site 1 schema-description 1 file = 7 line):
#   A  prompts/_base/scene_extractor_v2/20.<UTC>/scene_detail_schema.json     (Site 2)
#   A  prompts/_base/scene_extractor_v2/20.<UTC>/system.md                    (Site 2)
#   A  prompts/_base/scene_extractor_v2/20.<UTC>/turn0_context.md             (Site 2)
#   A  prompts/_base/scene_extractor_v2/20.<UTC>/turn1_split_long.md          (Site 2)
#   A  prompts/_base/scene_extractor_v2/20.<UTC>/turn_scene_detail.md         (Site 2)
#   M  prompts/_base/variation_recommender/v2/system.md                       (Site 1)
#   M  backend/app/modules/variation_recommender_v2.py                        (Site 1 schema-description)
```

### 3.10 Commit Message Convention

```
prompts(area-7d-cinematography-sot-dual-site-v1): W1 producer atomic — scene_extractor_v2/20.<UTC> new dir (5 file = 2 modified turn_scene_detail/scene_detail_schema + 3 byte-identical sibling) + variation_recommender/v2/system.md examples 4+4 phrase 폐기 + variation_recommender_v2.py _VARIATION_ITEM_SCHEMA composition/color description examples hard-prime 제거 (spec §2.6 R1-R6 strict residue 0)
```

## 4. W2 — E6 prompt_service Regex Narrow/Remove Atomic Wave

### 4.1 Action

`backend/app/services/prompt_service.py` line 345-352 7-token cinematography regex block (R7-R9 verbatim spec §2.6) 제거 우선 (Codex iter 0 권고 4 흡수 = closed-list regex 제거 우선, 남기는 경우도 cinematography semantic token-list regex 금지). 4-Gate Semantic Regex Ban 정합.

### 4.2 cleaned Consumer Cascade 의미 + Exact Flow Fact (Codex iter 0 권고 4 + iter 1 F-4 흡수)

**의미**: prompt_service.py 안 `cleaned` 변수 = `replace_entity_ids → translate_if_korean → build_scene_text` cascade 의 통과 변수. W2 narrow/remove 시 다음 4 exact flow fact 검증 의무:

1. **`replace_entity_ids` def site**: `prompt_service.py:265 def replace_entity_ids(t2i_prompt, labeled_refs, entity_text_map)` — t2i_prompt 안 short_id 치환 + cleanup pipeline. E6 regex block (line 345-352) = 본 def 내부 cinematography 7-token 자동 제거 step.
2. **cascade chain** (`prompt_service.py:497-510` `build_final_scene_prompt` 내부):
   ```python
   cleaned = replace_entity_ids(t2i_prompt, payload.labeled_refs, entity_text_map)
   cleaned = translate_if_korean(cleaned, ...)
   final = build_scene_text(ref_roles_text=..., cleaned=cleaned, ref_instructions_text=...)
   ```
   `cleaned` 변수 = `final` 안 inserted (T2I prompt assembly final form).
3. **downstream caller**: `scene_generation_coordinator.py:53-57` 안 `from app.services.prompt_service import build_final_scene_prompt as _build_final_scene_prompt` import (alias). E6 regex 폐기 후 cinematography token 자동 제거 X → LLM emit raw 형식이 `final` 안 그대로 inserted (이는 spec §3 Gate 1 Semantic Regex Ban 정합 의도된 결과).
4. **비-cinematography cleanup preserve**: line 340 `cleaned = cleaned.replace(sid, replacement)` (SID 치환) + line 343 `cleaned = re.sub(r",?\s*in O\d{2,3}\b", "", cleaned)` (O## 제거) = E6 폐기 후도 정상 동작. W2 commit 안 line 345-352 만 narrow remove (line 340 + line 343 변경 0).

### 4.3 Edit Specification

**Preferred (v1 권장)** — regex block 자체 폐기:

| line (current) | legacy content (R-id verbatim spec §2.6) | required action |
|---|---|---|
| 345 | `    # 카메라 앵글 제거` (R9 comment) | 폐기 (line 자체 삭제) |
| 346-351 | `    cleaned = re.sub(\n        r"In a (low angle\|dutch angle\|high angle\|bird eye\|wide\|tracking\|over the shoulder)\s*"\n        r"(frame\|composition\|shot\|view)\s*",\n        "",\n        cleaned,\n    )` (R7 + R8 regex literal block) | 폐기 (6 line 삭제, blank line 1 잔존 권고) |
| 352 | `    return cleaned` | preserve (변경 0) |

**Alternative (carry 시)** — regex 남기되 non-semantic structured handling 으로 교체 (spec §4.3 alternative 정합): 본 v1 = preferred 권장. alternative 채택 시 Carry-D7d-2 audit 01 row 신설 의무 (plan §10.2 참조).

### 4.4 Pre-Commit Verification (W2, 5 check)

**(a) E6 regex block literal residue 0 (R7-R9 plain string membership)**:
```bash
PS="backend/app/services/prompt_service.py"
grep -F '# 카메라 앵글 제거' "$PS" \
  && { echo "FAIL: R9 comment residue in $PS"; exit 1; } || true
grep -F 'In a (low angle|dutch angle|high angle|bird eye|wide|tracking|over the shoulder)' "$PS" \
  && { echo "FAIL: R7 regex literal residue in $PS"; exit 1; } || true
grep -F '(frame|composition|shot|view)' "$PS" \
  && { echo "FAIL: R8 regex literal residue in $PS"; exit 1; } || true
echo "OK E6 R7-R9 strict residue 0 in prompt_service.py"
```

**(b) cleaned consumer cascade exact flow preserve verify** (Codex iter 1 F-4 흡수):
```bash
# 비-cinematography cleanup preserve (line 340 SID + line 343 O##)
grep -nE 'cleaned\.replace\(sid|cleaned = re\.sub\(r",\?\\\\s\*in O' "$PS" \
  || { echo "FAIL: non-E6 cleanup pipeline missing"; exit 1; }
echo "OK non-cinematography cleanup preserved (line 340 SID + line 343 O##)"
# cascade chain exact flow verify (replace_entity_ids → translate_if_korean → build_scene_text)
rg -n "def replace_entity_ids|cleaned = replace_entity_ids|cleaned = translate_if_korean|build_scene_text\(" "$PS"
# downstream caller alias verify (build_final_scene_prompt as _build_final_scene_prompt)
rg -n "build_final_scene_prompt|_build_final_scene_prompt" backend/app/services/scene_generation_coordinator.py
```

**(c) NO VLM grep (W2 source-of-change file 안)**:
```bash
grep -nE "vision_language|image_validation|vlm_client|run_vlm|call_vlm" "$PS"
# Expected: empty (NO VLM hard constraint)
```

**(d) Cinematography closed-list block 재도입 ban** (Codex iter 1 F-3 흡수 — semantic token-list regex 검사 제거, exact literal 만):
```bash
# R7-R9 strict residue 0 (Pre-Commit (a) 안 이미 검증) + cinematography 7-token re.sub( closed-list block 재도입 ban
grep -nE 're\.sub\([[:space:]]*r"In a \(' "$PS" \
  && { echo "FAIL: cinematography closed-list re.sub( block re-introduced"; exit 1; } || true
echo "OK no cinematography closed-list re.sub( block in $PS"
```

**(e) Codex W2 per-wave APPROVED 의무**.

### 4.5 Path-Limited Git Add Scope

```bash
git add backend/app/services/prompt_service.py
git status --short  # Expected: 1 modified file
```

### 4.6 Commit Message Convention

```
fix(area-7d-cinematography-sot-dual-site-v1): W2 prompt_service E6 regex 폐기 atomic — 7-token cinematography stripping regex (line 345-352) 제거 + 4-Gate Semantic Regex Ban 정합 + cleaned consumer cascade 정상 동작 verify (non-cinematography cleanup line 340 SID + line 343 O## preserve)
```

## 5. W3 — Integrated Test Atomic Wave (5 Verify Gate)

### 5.1 Test File Path

`backend/tests/integration/test_area_7d_cinematography_sot.py` (Area #7c plan template 정합 prior area integration gate pattern `test_area_7a_*` / `test_area_7b_*` / `test_area_7c_*`).

### 5.2 5 Verify Gate Outline

- **Gate 1** — Site 2 7+7 closed-list residue 0 (spec §2.6 R1-R4 full-phrase strict, closed-world exact membership).
- **Gate 2** — Site 1 examples 4+4 phrase residue 0 (spec §2.6 R5-R6 full-phrase strict).
- **Gate 3** — E6 regex literal residue 0 (spec §2.6 R7-R9 plain string membership, regex pattern 실행 X).
- **Gate 4** — carrier shape preservation (`camera_effect` string field + `_VARIATION_ITEM_SCHEMA` shape preserve + `scene_variation_service.py:344` consumer call signature 변경 0).
- **Gate 5** — loadsite + 4-Gate compliance (pipeline/scene_extractor_v2 latest dir activation = 20.<UTC> file fallback _version_sort_key numeric-desc latest + detail_steps.py:1182-1188 fallback path preserve + 4-Gate inline assertion).

### 5.3 Test Body Inline (canonical wording 강하게 verbatim)

Codex iter 0 권고 3 흡수 — spec §2.6 R1-R9 글자-단위 복사. Python implicit string concat 금지, triple-quoted string + 단일 string literal escape 명시. regex literal (R7-R9) = plain string membership 검사 only.

**W3 execution finding 흡수 (plan amend 1, Codex PLAN_AMEND_NARROW_FIRST_THEN_W3_EXECUTION 권고)**: Gate 4 `test_gate4_camera_effect_schema_shape_preserved` + `test_gate4_camera_effect_schema_required_preserved` schema path navigation = `schema["properties"]["t2i_variations"]["items"]` 아래 `properties.camera_effect` + `required` (root-level `schema.properties.camera_effect` 가 아님). 본 amend = `_camera_effect_schema_item()` helper 도입 + 2 test 함수 narrow 정정. 다른 §5.3 inline body 부분 (Gate 1/2/3/5 + Gate 4 `_VARIATION_ITEM_SCHEMA` + `scene_variation_service` test) = 변경 0.

```python
"""
Area #7d — cinematography closed-list SOT dual-site W3 integrated test wave.

5 verify gate (closed-world exact phrase membership, no live LLM, NO VLM, no semantic regex):
(1) Gate 1 — Site 2 R1-R4 strict residue 0 in scene_extractor_v2/20.<UTC>/turn_scene_detail.md.
(2) Gate 2 — Site 1 R5-R6 strict residue 0 in variation_recommender/v2/system.md + variation_recommender_v2.py.
(3) Gate 3 — E6 R7-R9 strict residue 0 in backend/app/services/prompt_service.py.
(4) Gate 4 — carrier shape preserve (camera_effect string + _VARIATION_ITEM_SCHEMA shape + consumer call signature).
(5) Gate 5 — loadsite + 4-Gate compliance (latest v20 numeric-desc activation + fallback path preserve).

Standalone token global ban X (Codex iter 0 권고 6 흡수): low angle / warm amber / bird's eye 등
standalone token 0-hit 검사 절대 금지. R1-R9 full exact phrase only.
"""

from __future__ import annotations

from pathlib import Path

import pytest


REPO_ROOT = Path(__file__).resolve().parents[3]
PROMPTS_BASE = REPO_ROOT / "prompts" / "_base"
SCENE_EXTRACTOR_V2_BASE = PROMPTS_BASE / "scene_extractor_v2"
VARIATION_RECOMMENDER_V2 = PROMPTS_BASE / "variation_recommender" / "v2"
PROMPT_SERVICE = REPO_ROOT / "backend" / "app" / "services" / "prompt_service.py"
VARIATION_RECOMMENDER_V2_PY = REPO_ROOT / "backend" / "app" / "modules" / "variation_recommender_v2.py"


# ============================================================================
# spec §2.6 R1-R9 strict residue catalog (글자-단위 verbatim from spec, NO paraphrase)
# Codex iter 0 권고 3 흡수: Python implicit string concat 금지, single-line literal only.
# regex literal R7-R9 = plain string membership 검사, regex pattern 실행 X.
# ============================================================================

# R1 — Site 2 carrier line (full-phrase strict)
R1_SITE2_CAMERA_LIST = "카메라 구도 선택지: low angle / high angle / dutch angle / over-the-shoulder / bird's eye / extreme wide / tight medium"

# R2 — Site 2 carrier line
R2_SITE2_COLOR_LIST = "색감 선택지: warm amber / cold blue / high contrast / desaturated / golden hour / neon-lit / silhouette backlight"

# R3 — Site 2 carrier description line
R3_SITE2_CARRIER_DESC = '각 변형의 camera_effect 필드에 "카메라구도 + 색감"을 명시하세요.'

# R4 — Site 2 verbatim 예시 line
R4_SITE2_EXAMPLE_LINE = '예: "low angle, warm amber lighting" / "high angle, cold blue-toned" / "dutch angle, high contrast silhouette"'

# R5 — Site 1 composition examples 4-phrase block (4 separate phrase, all 4 must be absent)
R5_SITE1_COMPOSITION_EXAMPLES: tuple[str, ...] = (
    '"tight close-up on face"',
    '"wide establishing shot"',
    '"over-shoulder framing"',
    '"low angle hero shot"',
)

# R6 — Site 1 color examples 4-phrase block
R6_SITE1_COLOR_EXAMPLES: tuple[str, ...] = (
    '"warm golden hour lighting"',
    '"cold blue moonlight"',
    '"high contrast noir shadows"',
    '"desaturated muted tones"',
)

# R7 — E6 prompt_service regex literal first line (spec §2.6 line 123 verbatim full source literal,
#       prefix `r"`, suffix `\s*"` 포함; regex pattern로 실행 X, plain string membership only)
R7_E6_REGEX_LITERAL_A = 'r"In a (low angle|dutch angle|high angle|bird eye|wide|tracking|over the shoulder)\\s*"'

# R8 — E6 prompt_service regex literal second line (spec §2.6 line 124 verbatim full source literal,
#       prefix `r"`, suffix `\s*",` 포함; comma 끝까지 verbatim)
R8_E6_REGEX_LITERAL_B = 'r"(frame|composition|shot|view)\\s*",'

# R9 — E6 prompt_service comment line (line 345 verbatim)
R9_E6_COMMENT = "# 카메라 앵글 제거"


# ============================================================================
# Helper — v20 latest dir resolution
# ============================================================================

def _latest_v20_dir() -> Path:
    """scene_extractor_v2 file fallback latest = 20.<UTC> 가장 큰 dir (numeric-desc sort)."""
    candidates = sorted(
        p for p in SCENE_EXTRACTOR_V2_BASE.iterdir()
        if p.is_dir() and p.name.startswith("20.")
    )
    assert candidates, (
        f"no v20 scene_extractor_v2 dir found under {SCENE_EXTRACTOR_V2_BASE}"
    )
    # sorted ascending; tail = latest (Python sorted on string preserves numeric-asc since UTC YYYYMMDDHHmm).
    return candidates[-1]


# ============================================================================
# Gate 1 — Site 2 R1-R4 strict residue 0 in v20 turn_scene_detail.md
# ============================================================================

def test_gate1_site2_r1_camera_list_absent() -> None:
    v20 = _latest_v20_dir()
    content = (v20 / "turn_scene_detail.md").read_text(encoding="utf-8")
    assert R1_SITE2_CAMERA_LIST not in content, (
        f"R1 7-token camera list still in v20 turn_scene_detail.md: {R1_SITE2_CAMERA_LIST!r}"
    )


def test_gate1_site2_r2_color_list_absent() -> None:
    v20 = _latest_v20_dir()
    content = (v20 / "turn_scene_detail.md").read_text(encoding="utf-8")
    assert R2_SITE2_COLOR_LIST not in content, (
        f"R2 7-token color list still in v20 turn_scene_detail.md: {R2_SITE2_COLOR_LIST!r}"
    )


def test_gate1_site2_r3_carrier_description_absent() -> None:
    v20 = _latest_v20_dir()
    content = (v20 / "turn_scene_detail.md").read_text(encoding="utf-8")
    assert R3_SITE2_CARRIER_DESC not in content, (
        f"R3 carrier 'camera_effect 필드에 카메라구도+색감 명시' line still in v20: {R3_SITE2_CARRIER_DESC!r}"
    )


def test_gate1_site2_r4_example_line_absent() -> None:
    v20 = _latest_v20_dir()
    content = (v20 / "turn_scene_detail.md").read_text(encoding="utf-8")
    assert R4_SITE2_EXAMPLE_LINE not in content, (
        f"R4 verbatim 예시 line still in v20 turn_scene_detail.md: {R4_SITE2_EXAMPLE_LINE!r}"
    )


# ============================================================================
# Gate 2 — Site 1 R5-R6 strict residue 0 in variation_recommender (system.md + .py schema-description)
# ============================================================================

def test_gate2_site1_r5_composition_examples_absent_in_system_md() -> None:
    content = (VARIATION_RECOMMENDER_V2 / "system.md").read_text(encoding="utf-8")
    for phrase in R5_SITE1_COMPOSITION_EXAMPLES:
        assert phrase not in content, (
            f"R5 composition example phrase still in variation_recommender/v2/system.md: {phrase!r}"
        )


def test_gate2_site1_r6_color_examples_absent_in_system_md() -> None:
    content = (VARIATION_RECOMMENDER_V2 / "system.md").read_text(encoding="utf-8")
    for phrase in R6_SITE1_COLOR_EXAMPLES:
        assert phrase not in content, (
            f"R6 color example phrase still in variation_recommender/v2/system.md: {phrase!r}"
        )


def test_gate2_site1_r5_r6_absent_in_schema_description() -> None:
    """variation_recommender_v2.py _VARIATION_ITEM_SCHEMA composition/color description examples hard-prime 제거 verify."""
    content = VARIATION_RECOMMENDER_V2_PY.read_text(encoding="utf-8")
    for phrase in R5_SITE1_COMPOSITION_EXAMPLES + R6_SITE1_COLOR_EXAMPLES:
        # quote-stripped substring membership (schema description string 안 quoted form 변형 가능성 회피, raw substring 검사)
        stripped = phrase.strip("'\"")
        assert stripped not in content, (
            f"R5/R6 example phrase still in variation_recommender_v2.py schema-description: {stripped!r}"
        )


# ============================================================================
# Gate 3 — E6 R7-R9 strict residue 0 in prompt_service.py
# (regex literal = plain string membership only, regex pattern 실행 X)
# ============================================================================

def test_gate3_e6_r7_regex_literal_a_absent() -> None:
    content = PROMPT_SERVICE.read_text(encoding="utf-8")
    assert R7_E6_REGEX_LITERAL_A not in content, (
        f"R7 E6 regex literal first line still in prompt_service.py: {R7_E6_REGEX_LITERAL_A!r}"
    )


def test_gate3_e6_r8_regex_literal_b_absent() -> None:
    content = PROMPT_SERVICE.read_text(encoding="utf-8")
    assert R8_E6_REGEX_LITERAL_B not in content, (
        f"R8 E6 regex literal second line still in prompt_service.py: {R8_E6_REGEX_LITERAL_B!r}"
    )


def test_gate3_e6_r9_comment_absent() -> None:
    content = PROMPT_SERVICE.read_text(encoding="utf-8")
    assert R9_E6_COMMENT not in content, (
        f"R9 E6 comment '# 카메라 앵글 제거' still in prompt_service.py: {R9_E6_COMMENT!r}"
    )


def test_gate3_e6_no_cinematography_closed_list_resub_reintroduced() -> None:
    """W2 narrow/remove 후 cinematography 7-token closed-list re.sub( block 재도입 ban
    (4-Gate Semantic Regex Ban 정합, standalone token global ban X 정합 — R7-R9 exact literal 만 검사).
    """
    content = PROMPT_SERVICE.read_text(encoding="utf-8")
    # Codex iter 1 F-2 흡수: standalone token / partial token-group fragment 검사 X.
    # R7 full source literal 자체가 재도입 = 곧 cinematography 7-token closed-list re.sub( block 재도입.
    # Gate 3 R7/R8/R9 검사 (test_gate3_e6_r7_*/r8_*/r9_*) 이 본 contract 의 충분 조건.
    assert R7_E6_REGEX_LITERAL_A not in content, (
        f"R7 full source literal (= 7-token closed-list regex) re-introduced: {R7_E6_REGEX_LITERAL_A!r}"
    )


# ============================================================================
# Gate 4 — carrier shape preservation
# ============================================================================

def _camera_effect_schema_item() -> dict:
    """v20 scene_detail_schema.json 안 t2i_variations.items 위치 helper (camera_effect 가 nested under array items)."""
    import json
    v20 = _latest_v20_dir()
    schema = json.loads((v20 / "scene_detail_schema.json").read_text(encoding="utf-8"))
    return schema["properties"]["t2i_variations"]["items"]


def test_gate4_camera_effect_schema_shape_preserved() -> None:
    """scene_detail_schema.json nested t2i_variations.items camera_effect type=string preserve."""
    t2i_items = _camera_effect_schema_item()
    properties = t2i_items["properties"]
    assert "camera_effect" in properties, "camera_effect property missing under t2i_variations.items.properties"
    cam = properties["camera_effect"]
    assert cam.get("type") == "string", f"camera_effect type drift: {cam.get('type')!r}"


def test_gate4_camera_effect_schema_required_preserved() -> None:
    """scene_detail_schema.json nested t2i_variations.items.required contains camera_effect."""
    t2i_items = _camera_effect_schema_item()
    required = t2i_items.get("required", [])
    assert "camera_effect" in required, (
        f"camera_effect missing in t2i_variations.items.required (preserve obligatory): required={required!r}"
    )


def test_gate4_variation_item_schema_shape_preserved() -> None:
    """variation_recommender_v2._VARIATION_ITEM_SCHEMA shape preserve (type/angle/composition/color/reason)."""
    from app.modules.variation_recommender_v2 import _VARIATION_ITEM_SCHEMA, _RESPONSE_SCHEMA
    schema = _VARIATION_ITEM_SCHEMA
    assert schema["type"] == "object"
    expected_props = {"type", "angle", "composition", "color", "reason"}
    assert set(schema["properties"].keys()) == expected_props, (
        f"_VARIATION_ITEM_SCHEMA properties drift: {set(schema['properties'].keys())!r}"
    )
    assert schema["required"] == ["type", "angle", "composition", "color", "reason"], (
        f"_VARIATION_ITEM_SCHEMA required drift: {schema['required']!r}"
    )
    # 3D angle preserve
    angle = schema["properties"]["angle"]
    assert set(angle["properties"].keys()) == {"horizontal", "vertical", "zoom"}, (
        f"angle 3D drift: {set(angle['properties'].keys())!r}"
    )
    assert schema["properties"]["composition"]["type"] == "string"
    assert schema["properties"]["color"]["type"] == "string"
    # _RESPONSE_SCHEMA shape preserve
    assert set(_RESPONSE_SCHEMA["properties"].keys()) == {
        "variation_a", "variation_b", "recommended", "reasoning",
    }, f"_RESPONSE_SCHEMA properties drift: {set(_RESPONSE_SCHEMA['properties'].keys())!r}"


def test_gate4_scene_variation_service_consumer_signature_preserved() -> None:
    """scene_variation_service.py:344 v2 import + 403-411 var.get key list preserve."""
    svc = (REPO_ROOT / "backend" / "app" / "services" / "scene_variation_service.py").read_text(encoding="utf-8")
    assert "from app.modules.variation_recommender_v2 import VariationRecommenderV2" in svc, (
        "scene_variation_service.py v2 import missing/drift"
    )
    # var.get key list preserve (angle / composition / color persist)
    for key in ("angle", "composition", "color"):
        assert f'.get("{key}"' in svc or f".get('{key}'" in svc, (
            f"scene_variation_service.py var.get('{key}') missing/drift"
        )


# ============================================================================
# Gate 5 — loadsite + 4-Gate compliance
# ============================================================================

def test_gate5_scene_extractor_v2_latest_resolves_v20() -> None:
    """pipeline/scene_extractor_v2 load_prompt/load_schema latest = 20.<UTC> dir (numeric-desc latest)."""
    from app.modules.prompt_loader import _resolve_stem_in_pack
    for stem, ext in (
        ("turn_scene_detail", ".md"),
        ("scene_detail_schema", ".json"),
        ("system", ".md"),
        ("turn0_context", ".md"),
        ("turn1_split_long", ".md"),
    ):
        fp, ver, _all = _resolve_stem_in_pack("scene_extractor_v2", stem, ext=ext)
        assert fp is not None, f"stem {stem}{ext} not resolved"
        assert ver is not None and ver.startswith("20."), (
            f"stem {stem}{ext} ver drift (expected 20.* numeric-desc latest, got {ver!r})"
        )


def test_gate5_detail_steps_dual_owner_fallback_path_preserved() -> None:
    """detail_steps.py:1182-1188 primary scene_detail/27 + fallback scene_extractor_v2 dual-owner cascade preserve."""
    ds = (REPO_ROOT / "backend" / "app" / "core" / "steps" / "detail_steps.py").read_text(encoding="utf-8")
    # primary path scene_detail (Carry-D7d-1 boundary reaffirm — v1 변경 0)
    assert "scene_detail" in ds, "detail_steps.py primary scene_detail loadsite missing"
    # fallback path scene_extractor_v2 + turn_scene_detail (W1 변경 = scene_extractor_v2/20.<UTC> 활성화)
    assert "scene_extractor_v2" in ds, "detail_steps.py fallback scene_extractor_v2 loadsite missing"
    assert "turn_scene_detail" in ds, "detail_steps.py fallback turn_scene_detail stem missing"


def test_gate5_4_gate_inline_assertion() -> None:
    """4-Gate Boundary Doctrine inline assertion (Semantic Regex Ban / Prompt Closed-List Ban refined / Structured SOT Required / No Silent Fallback)."""
    # Gate 1 Semantic Regex Ban — E6 R7-R9 residue 0 (covered by test_gate3_*)
    # Gate 2 Prompt Closed-List Ban refined full-phrase only — R1-R6 residue 0 (covered by test_gate1_* + test_gate2_*)
    # Gate 3 Structured SOT Required — carrier shape preserve (covered by test_gate4_*)
    # Gate 4 No Silent Fallback — variation_recommender output shape preserve + scene_variation_service consumer signature preserve (covered by test_gate4_*)
    # Inline reaffirm assertion:
    assert True, "4-Gate Boundary Doctrine all 4 gate enforced by Gate 1-4 above"
```

### 5.4 Test Count Target

Gate 1 (4 test) + Gate 2 (3 test) + Gate 3 (4 test) + Gate 4 (4 test) + Gate 5 (3 test) = **18 PASS** target. 추가 optional canary 0-2 edge-case (필요 시 Codex narrow 권고 흡수).

### 5.5 Pre-Commit Verification (W3, 5 check)

**(a) pytest pass count**:
```bash
cd backend && .venv/bin/pytest tests/integration/test_area_7d_cinematography_sot.py -v
# Expected: 18 PASS (or close), 0 FAIL, 0 SKIP
```

**(b) No live LLM grep**:
```bash
grep -nE "openai|gemini|anthropic|fal\.ai|completion|chat|call_structured|call_llm|llm_client" backend/tests/integration/test_area_7d_cinematography_sot.py
# Expected: empty (no live LLM call in W3 test)
```

**(c) NO VLM grep**:
```bash
grep -nE "vision_language|image_validation|vlm_client|run_vlm|call_vlm" backend/tests/integration/test_area_7d_cinematography_sot.py
# Expected: empty (NO VLM hard constraint)
```

**(d) No actual `re` import / call — AST-based verify (plan amend 3 narrow, Codex W3 per-wave review verdict APPROVED_FOR_W3_PER_WAVE + PLAN_AMEND3_NOW_THEN_CLOSE_HANDOFF_W4_FRESH 권고 흡수)**:

W3 execution finding (plan amend 2 `8eef6f7` 이후 plan §5.5 (d) verbatim grep 결과 = 2 hit FALSE POSITIVE: line 187 docstring + line 192 comment 안 `re.sub(` mention, 즉 cinematography 7-token closed-list re.sub( block 재도입 ban 설명 wording). 실제 regex usage = 0 (`import re` / `from re` grep = 0 hit). broad grep pattern 가 docstring/comment mention catch → AST-based check 으로 narrow. docstring/comment 안 `re.sub(` mention 은 audit 설명 wording 허용 (W3 test gate3 안 ban contract 설명 wording 필수).

```bash
python3 - <<'PY'
import ast
from pathlib import Path
p = Path('backend/tests/integration/test_area_7d_cinematography_sot.py')
tree = ast.parse(p.read_text(encoding='utf-8'))
imports = []
re_calls = []
for node in ast.walk(tree):
    if isinstance(node, ast.Import):
        for alias in node.names:
            if alias.name == 're':
                imports.append((node.lineno, 'import re'))
    elif isinstance(node, ast.ImportFrom) and node.module == 're':
        imports.append((node.lineno, 'from re import ...'))
    elif isinstance(node, ast.Call):
        fn = node.func
        if isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name) and fn.value.id == 're':
            re_calls.append((node.lineno, f're.{fn.attr}'))
if imports or re_calls:
    raise SystemExit(f're usage found: imports={imports!r}, re_calls={re_calls!r}')
print('OK no actual re import/call')
PY
# Expected: OK no actual re import/call (docstring/comment 안 `re.sub(` mention 은 AST 안 식별되지 않음 — audit 설명 wording 허용)
```

**(e) Codex W3 per-wave APPROVED 의무**.

### 5.6 Path-Limited Git Add Scope

```bash
git add backend/tests/integration/test_area_7d_cinematography_sot.py
git status --short  # Expected: 1 new file
```

### 5.7 Commit Message Convention

```
test(area-7d-cinematography-sot-dual-site-v1): W3 integrated test wave — 5 verify gate (Gate 1 Site 2 R1-R4 + Gate 2 Site 1 R5-R6 + Gate 3 E6 R7-R9 + Gate 4 carrier shape + Gate 5 loadsite/4-Gate), 18 PASS, no live LLM + NO VLM + no semantic regex
```

## 6. W4 — Closure Docs Atomic Wave

### 6.1 Action

3-5 repo-local docs file modify (path-limited explicit paths, 1 audit 02 안 narrow cross-ref + 1 audit 03 안 L-8 row 신설 + spec frontmatter status + spec §11 Closure append + roadmap §5.7 Sub-area (e) verbatim bullet).

### 6.2 Spec File Edit

**Path**: `docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md`

**Frontmatter edit** (line 4):
- Before: `status: draft (spec v1, ...)`
- After: `status: closed (spec Codex APPROVED_FOR_PLAN + plan Codex APPROVED_FOR_EXECUTION + W1-W3 atomic + per-wave APPROVED; range review/push gate pending at W4 closure commit time — Codex APPROVED_FOR_PUSH 후 즉시 push origin main)`

**Spec §11 Closure section append template**:

<!-- HISTORICAL AUDIT TRAIL / TEMPLATE ONLY: 다음 section 은 W4 execution 시 spec end 에 append; 본 plan 안 = template, controller substitute pending. design-time `20.<UTC-timestamp>` placeholder = W4 execution 시 controller awk substitute target. -->

```markdown
## 11. Closure (2026-05-19+)

### 11.1 Status + Closure Anchor

- **Status**: closed at W4 closure commit anchor (range review + push gate pending at W4 closure commit time). Final pushed range/count = recorded in external memory only after `APPROVED_FOR_PUSH` + `git push origin main` (live closure docs 안 push-complete pre-claim 0).
- **Closure commit anchor**: W4 closure commit hash (path-limited 3-5 explicit paths). 후속 range-review fix-up commits 가능 — final pushed range/count = external memory only.

### 11.2 Wave Commit Table

| Wave | Commit | Action | Line stats |
|---|---|---|---|
| W1 | `<W1 hash>` | Producer atomic (scene_extractor_v2/20.<UTC> 5 file + variation_recommender/v2/system.md narrow + variation_recommender_v2.py schema-description narrow) | `<W1 ins/del>` |
| W2 | `<W2 hash>` | E6 prompt_service regex 폐기 atomic (line 345-352 7-token cinematography regex 제거 + non-cinematography cleanup preserve) | `<W2 ins/del>` |
| W3 | `<W3 hash>` | Integrated test atomic (5 verify gate, 18 PASS, no live LLM + NO VLM + no semantic regex) | `<W3 ins/del>` |
| W4 | `<W4 hash>` | Closure docs atomic (3-5 file: spec + roadmap + audit 02 + audit 03 + optional audit 01) | `<W4 ins/del>` |

### 11.3 In-Scope Dual-Site Mapping

| site | line (legacy) | residue gate? | result |
|---|---|---|---|
| `variation_recommender/v2/system.md:23` | R5 composition examples 4-phrase | strict residue | 0 count ✓ |
| `variation_recommender/v2/system.md:29` | R6 color examples 4-phrase | strict residue | 0 count ✓ |
| `scene_extractor_v2/19.202605161434/turn_scene_detail.md:87` (→ 20.<UTC>/) | R1 7-token camera list | strict residue | 0 count ✓ |
| `scene_extractor_v2/19.202605161434/turn_scene_detail.md:88` (→ 20.<UTC>/) | R2 7-token color list | strict residue | 0 count ✓ |
| `scene_extractor_v2/19.202605161434/turn_scene_detail.md:90` (→ 20.<UTC>/) | R3 carrier description line | strict residue | 0 count ✓ |
| `scene_extractor_v2/19.202605161434/turn_scene_detail.md:91` (→ 20.<UTC>/) | R4 verbatim 예시 line | strict residue | 0 count ✓ |
| `prompt_service.py:347-348` | R7-R8 E6 regex literal | strict residue | 0 count ✓ |
| `prompt_service.py:345` | R9 E6 comment | strict residue | 0 count ✓ |

### 11.4 Strict Residue Catalog Result

9 entry (R1-R9) exact legacy phrase substring membership check — 0 strict count in v20 dir + variation_recommender + prompt_service (closed-world membership only, no regex semantic, standalone token global ban 엄금).

### 11.5 Carrier Shape Preservation Result

- `scene_extractor_v2/20.<UTC>/scene_detail_schema.json` `camera_effect` type=string + required preserve ✓.
- `variation_recommender_v2._VARIATION_ITEM_SCHEMA` shape preserve (5 properties + 3D angle + required list) ✓.
- `scene_variation_service.py:344` v2 import preserve + 403-411 var.get key list (angle/composition/color) preserve ✓.
- `detail_steps.py:1182-1188` dual-owner fallback path preserve (primary scene_detail/27 + fallback scene_extractor_v2/20.<UTC>) ✓.

### 11.6 STEP_MANIFEST + loadsite Preservation

- pipeline/scene_extractor_v2 `load_prompt` / `load_schema` file-only file fallback latest = 20.<UTC> dir (numeric-desc, _version_sort_key) ✓.
- STEP_MANIFEST entry-level 변경 0 (scene_extractor_v2 STEP_MANIFEST direct entry 0; manifest fact 정합 — Area #7a step_manifest schema_version 정합).

### 11.7 Closure Verdict

- Codex spec iter 1-5+ + plan iter 0-N + W1-W4 per-wave + W1-W4 range review `APPROVED_FOR_PUSH`.
- 4-Gate Boundary Doctrine reaffirm: Semantic Regex Ban (E6 폐기) / Prompt Closed-List Ban refined full-phrase only (R1-R6) / Structured SOT Required (carrier shape preserve) / No Silent Fallback (variation_recommender output preserve).
- NO VLM hard constraint reaffirm.
- TEMPLATE ONLY doctrine reaffirm: controller-substitute targets only, HISTORICAL AUDIT TRAIL marker preserved.

### 11.8 Carry Follow-up Post #7d

Priority (Codex post-push verdict 정합):
1. **Carry-D7d-1** — scene_detail v28 description align (spec Q-DR4 verbatim full inline, plan §10.1 참조).
2. **Carry-D7d-2** (conditional W2 outcome) — prompt_service E6 regex narrow/remove alternative structured handling (W2 = preferred 폐기 채택 시 carry 없음; alternative 채택 시 audit 01 row 신설).
3. **Carry-A099** — VWR director_notes prose redesign V8+.
4. **Carry-A100** — VWR t2i_context demonym redesign V8+.
5. **Carry-P021** — scene_consistency 7 enumeration broad (source unresolved).
```

<!-- end TEMPLATE ONLY -->

### 6.3 Roadmap File Edit

**Path**: `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md`

**§5.7 Area #7 carry follow-up new bullet append** — Codex iter 0 권고 7 정합 = `Sub-area (e) cinematography closed-list SOT dual-site` exact verbatim, 앞 공백 없이.

Insert after Sub-area (d) Area #7c entry:

<!-- HISTORICAL AUDIT TRAIL / TEMPLATE ONLY: 다음 bullet 은 W4 execution 시 roadmap §5.7 안 Sub-area (d) Area #7c entry 뒤 append -->

```markdown
- **Sub-area (e) cinematography closed-list SOT dual-site**: **closed (2026-05-19+, W1-W4 atomic — W1 producer / W2 E6 prompt_service regex / W3 integrated test / W4 closure docs; range review/push gate pending at W4 closure commit time — Codex APPROVED_FOR_PUSH 후 즉시 push origin main, final pushed range/count = external memory only)**. In-scope = dual-site (variation_recommender/v2/system.md + scene_extractor_v2/20.<UTC>/turn_scene_detail.md) + E6 mandatory (prompt_service.py:347-352 7-token cinematography regex 폐기). 9 strict residue R1-R9 closed-world exact phrase membership. 1 new prompt dir v20 major bump (5 file: 2 modified + 3 byte-identical sibling) + 1 v2 narrow edit + 1 schema-description narrow + 1 prompt_service.py E6 regex 폐기 + 1 W3 integrated test (18 PASS, 5 gate) + 3-5 W4 closure docs. carrier shape preserve obligatory (`camera_effect` string + `_VARIATION_ITEM_SCHEMA` shape + scene_variation_service consumer + i2i_editor dispatch). dual-owner fully fixed claim X — scene_detail/27 Carry-D7d-1 deferred (4-point sync + pinned tests cascade 부담 회피). 4-Gate Doctrine + closed-world exact residue contract + TEMPLATE ONLY + NO VLM + standalone token global ban 엄금. Carry post #7d = Carry-D7d-1 / Carry-D7d-2 conditional / Carry-A099 / Carry-A100 / Carry-P021.
```

<!-- end TEMPLATE ONLY -->

### 6.4 Audit `02-` File Edit (narrow cross-reference, §11.2 VWR table untouched 의무)

**Path**: `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md`

spec §9.2 정합 = narrow cross-reference 한 줄 추가. **§11.2 VWR downstream table 추가 절대 X** (Codex R6 권고 정합 + plan §1.3 추가 guard 정합).

Insert after §5 P-4 entry (`turn_scene_detail.md:83-85` Korean/Joseon hard-prime entry 직후):

<!-- HISTORICAL AUDIT TRAIL / TEMPLATE ONLY: 다음 line 은 W4 execution 시 §5 P-4 entry 직후 narrow cross-reference 한 줄 append (또는 §10 Boundary Notes 안 narrow cross-reference) -->

```markdown
- **cinematography 5-line block (line 87-91)** = Area #7d (audit 03 L-8) 처리됨 (W1-W4 atomic, 2026-05-19+).
```

<!-- end TEMPLATE ONLY -->

### 6.5 Audit `03-` File Edit (L-8 row 신설)

**Path**: `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/03-llm-side-heuristic-audit.md`

spec §9.1 정합 = L-8 row 신설 primary closure target. L-1~L-7 inventory 직후 추가.

<!-- HISTORICAL AUDIT TRAIL / TEMPLATE ONLY: 다음 row 는 W4 execution 시 audit 03 L-1~L-7 inventory 직후 L-8 row append -->

```markdown
- **L-8: cinematography closed-list SOT dual-site (Area #7d)** = closed (2026-05-19+, W1-W4 atomic). Site 1 (`variation_recommender/v2/system.md`) + Site 2 (`scene_extractor_v2/19.* → 20.<UTC>/turn_scene_detail.md:87-91`) + E6 (`prompt_service.py:347-352` 7-token cinematography stripping regex 폐기). 9 strict residue (R1-R9 closed-world exact phrase membership) — 0 count obligatory. carrier shape preserve (`camera_effect` string + `_VARIATION_ITEM_SCHEMA` shape + scene_variation_service consumer signature). dual-owner fully fixed claim X (scene_detail/27 Carry-D7d-1 deferred). 4-Gate Boundary Doctrine (Semantic Regex Ban + Prompt Closed-List Ban refined full-phrase only + Structured SOT Required + No Silent Fallback). NO VLM hard constraint. standalone token global ban 엄금.
```

<!-- end TEMPLATE ONLY -->

### 6.6 Pre-Commit Verification (W4, 6 check)

**(a) Exact 3-5 repo docs file modified** — W4 commit only (Area #7c plan template §5.5 (a) 정합):
```bash
git diff-tree --no-commit-id --name-only -r HEAD
# Expected: 3-5 lines (W4 closure commit only):
#   docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md
#   docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md
#   docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md
#   docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/03-llm-side-heuristic-audit.md
#   [optional] docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/01-code-side-routing-debt-audit.md (Carry-D7d-2 audit 01 row 신설 시)
COUNT="$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l)"
test "$COUNT" -ge 3 && test "$COUNT" -le 5 || { echo "FAIL: W4 commit file count out of range [3,5]: got $COUNT"; exit 1; }
echo "OK 3-5 file in W4 commit"
```

**(b) Audit 02 §11.2 VWR downstream table untouched** — heading-anchored block compare (Codex iter 0 추가 guard 흡수, line range fragility 회피):
```bash
AUDIT="docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md"
diff \
  <(git show HEAD^:"$AUDIT" | awk '/^### 11\.2 /{flag=1; next} /^### 11\.3 /{flag=0; exit} flag') \
  <(awk '/^### 11\.2 /{flag=1; next} /^### 11\.3 /{flag=0; exit} flag' "$AUDIT") \
  && echo "OK §11.2 VWR table heading-anchored block untouched HEAD^→HEAD" \
  || { echo "FAIL: §11.2 block drift"; exit 1; }
```

**Shared file array** (W4 closure docs 4 file — Codex iter 1 MINOR 2 압축):
```bash
W4_DOCS=(
  "docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md"
  "docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md"
  "docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md"
  "docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/03-llm-side-heuristic-audit.md"
)
```

**(c) Substitute design-time placeholder + unresolved placeholder 0** — W4 substitution step before grep (HISTORICAL AUDIT TRAIL marker block 안 제외):

Step 1: Substitute actual W1 v20 dir name (replacing design-time `20.<UTC-timestamp>` / `20.<UTC>`):
```bash
W1_DIR_NAME=$(basename $(ls -d prompts/_base/scene_extractor_v2/20.*/ | sort -V | tail -1))  # e.g. 20.202605192100
for f in "${W4_DOCS[@]}"; do
  awk -v dir="$W1_DIR_NAME" '
    /HISTORICAL AUDIT TRAIL \/ TEMPLATE ONLY/{flag=1; print; next}
    /end TEMPLATE ONLY/{flag=0; print; next}
    !flag{gsub(/20\.<UTC-timestamp>/, dir); gsub(/20\.<UTC>/, dir); print; next}
    flag{print}
  ' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"
done
echo "OK substituted 20.<UTC-timestamp>/<UTC> -> $W1_DIR_NAME in 4 W4 docs"
```

Step 2: Verify 0 unresolved placeholder / self-reference / stale wording — angle form + plain placeholder + `본 commit` + `iter 5+` 모두 catch (Codex iter 1 F-5 흡수, marker block 제외):
```bash
for f in "${W4_DOCS[@]}"; do
  awk '/HISTORICAL AUDIT TRAIL \/ TEMPLATE ONLY/{flag=1; next} /end TEMPLATE ONLY/{flag=0; next} !flag' "$f" \
    | grep -nE '<W[0-9]+ hash>|<W[0-9]+ commit hash>|<W[0-9]+ ins/del>|<pushed_range>|<base>\.\.<W[0-9]+>|<UTC-timestamp>|<UTC>|<N>|W[1-4] hash|W[1-4] ins/del|본 commit|iter 5\+' \
    && { echo "FAIL: unresolved placeholder / self-reference / stale wording in $f"; exit 1; } || true
done
echo "OK no unresolved placeholder / self-reference / stale wording in 4 W4 docs"
```

**(d) Push-complete pre-claim 0** (marker block 제외, Codex iter 0 추가 guard):
```bash
for f in "${W4_DOCS[@]}"; do
  awk '/HISTORICAL AUDIT TRAIL \/ TEMPLATE ONLY/{flag=1; next} /end TEMPLATE ONLY/{flag=0; next} !flag' "$f" \
    | grep -nE 'push 완료|push complete|pushed origin' \
    && { echo "FAIL: push-complete pre-claim in $f"; exit 1; } || true
done
echo "OK no push-complete pre-claim in 4 W4 docs"
```

**(e) TEMPLATE ONLY marker pair balance** (start/end count match):
```bash
for f in "${W4_DOCS[@]}"; do
  S=$(grep -c "HISTORICAL AUDIT TRAIL / TEMPLATE ONLY" "$f")
  E=$(grep -c "end TEMPLATE ONLY" "$f")
  test "$S" -eq "$E" || { echo "FAIL: TEMPLATE marker unbalanced in $f (start=$S end=$E)"; exit 1; }
done
echo "OK TEMPLATE ONLY marker balanced in 4 W4 docs"
```

**(f) Codex W4 per-wave APPROVED 의무**.

### 6.7 Path-Limited Git Add Scope

```bash
git add docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md
git add docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md
git add docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md
git add docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/03-llm-side-heuristic-audit.md
# (optional, Carry-D7d-2 채택 시)
# git add docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/01-code-side-routing-debt-audit.md
git status --short  # Expected: 3-5 modified file
```

### 6.8 Range Review + Push Gate

- W4 closure commit 후 Codex W1-W4 range review (cumulative + per-wave 정합).
- `APPROVED_FOR_PUSH` 후 즉시 `git push origin main` (사용자 ask 0, [[feedback_push_no_user_ask]] 정합).
- NEEDS_REVISION_* 시 narrow amend (path-limited git add scope, 1 wave 1 file 기준) 후 narrow re-review.
- Repo-local post-push amend X (Codex range review 요청 시 제외).

### 6.9 External Memory Write Spec

push 후 closure session memory file (`memory/session_20260519_area_7d_v1_closure.md` 또는 sibling slug) 안 final pushed range/count anchor 기록:

```
- Final pushed range: 62aa2ef..<W4 commit hash> = X commits (e0cf96d spec v1 + 424c8e9 amend 1 + W1 + W2 + W3 + W4 + 후속 range-review fix-up).
- origin/main HEAD = <W4 commit hash>.
- Unpushed count = 0.
- Push range = spec v1 + amend 1 + W1 producer + W2 prompt_service regex + W3 integrated test + W4 closure docs + 후속 range-review fix-up commits.
```

### 6.10 Commit Message Convention

```
docs(area-7d-cinematography-sot-dual-site-v1): W4 closure — spec status closed + spec §11 Closure section + roadmap §5.7 Sub-area (e) cinematography closed-list SOT dual-site bullet + audit 02 §5 P-4 narrow cross-reference + audit 03 L-8 row 신설
```

## 7. 4-Gate Boundary Doctrine (inline reaffirm)

본 plan 적용 (spec §8.1 정합):

- **Gate 1 Semantic Regex Ban**: W2 E6 prompt_service.py:347-352 7-token cinematography regex 폐기. token regex 로 교체 X. W3 Gate 3 inline assertion.
- **Gate 2 Prompt Closed-List Ban refined (full-phrase ban only)**: Site 2 R1-R4 7+7 closed-list + Site 1 R5-R6 examples 4+4 폐기. **standalone token global ban X** (Codex iter 0 권고 6 흡수, semantic word matching 회피, Area #7b refined doctrine 정합). W3 Gate 1 + Gate 2 inline assertion.
- **Gate 3 Structured SOT Required**: v1 = carrier shape preserve (camera_effect string + variation_recommender output `type / angle / composition / color` shape). field-shape migration = major carry (v1 외). W3 Gate 4 inline assertion.
- **Gate 4 No Silent Fallback**: LLM 자유 텍스트 emit 후 code-side silent default 추가 0. variation_recommender output schema preserve. W3 Gate 5 inline assertion.

## 8. TEMPLATE ONLY Doctrine (inline reaffirm)

본 plan 적용 (spec §8.3 정합):

- **Template placeholders are controller-substitute targets only, not live closure claims**.
- **`HISTORICAL AUDIT TRAIL / TEMPLATE ONLY` marker** ([[session_20260519_area_7c_v1_closure]] 함정 1 정합): 본 plan §6.2/§6.3/§6.4/§6.5 안 closure section content template = historical/published verbatim block, grep 오해 + substitution awk pattern 트리거 방지 wording marker (`<!-- HISTORICAL AUDIT TRAIL / TEMPLATE ONLY: ... -->` / `<!-- end TEMPLATE ONLY -->`) 명시.
- **Live closure docs requirement**: unresolved placeholder 0 + push-complete pre-claim 0. W4 Pre-Commit Verification (c)/(d) check.
- **W3/W4 self-reference doctrine** ([[session_20260519_area_7c_v1_closure]] 함정 2 정합): W4 closure docs body 안 `본 commit` 사용 X. explicit hash literal + "후속 range-review fix-up commits 가능; final pushed range/count = external memory only" suffix 의무.
- **Substitution self-referential bug doctrine** ([[session_20260519_area_7c_v1_closure]] 함정 1 정합): plan §3.3 안 design-time `20.<UTC-timestamp>` placeholder = doctrine description block 안 marker wrap (`<UTC-timestamp>` 본문 사용 시 HISTORICAL AUDIT TRAIL marker 안에 위치, substitution awk pattern 트리거 회피).
- **Final pushed range/count = external memory only**. repo-local closure docs = closure commit anchor + range-review/push gate pending wording.
- **Repo-local post-push amend X** (Codex range review 요청 시 제외).
- **W1 UTC timestamp/date-drift 절차** (Codex iter 0 추가 guard 흡수): W1 new dir = `20.<UTC timestamp>` (e.g. `20.202605192100`). 날짜 drift 발생 시 placeholder 를 실제 UTC 날짜로 narrow amend 의무 (Area #7b W1 date drift trap 정합). 본 amend = 별도 wave 분리, W1 atomic 안 포함 X.

## 9. References

- **Spec HEAD**: commit `424c8e9` (`docs/superpowers/specs/2026-05-19-area-7d-cinematography-sot-dual-site-v1-design.md`, 351 line). spec drafting stack = `e0cf96d` spec v1 + `424c8e9` amend 1 narrow N-1/N-2.
- **Audit `02-`**: `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/02-prompt-side-hygiene-audit.md` §5 P-4 (cinematography line 87-91 narrow cross-reference target) + §10 Boundary Notes + §11.2 VWR downstream table (W4 untouched obligatory).
- **Audit `03-`**: `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/03-llm-side-heuristic-audit.md` L-1~L-7 inventory + L-8 신설 target (cinematography closed-list SOT dual-site).
- **Roadmap**: `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` §5.7 Area #7 carry follow-up (Sub-area (e) cinematography closed-list SOT dual-site = W4 신설 target verbatim).
- **Area #6 closure**: push `0a659f6` (T2I Review Mutation Redesign v1, 2026-05-18+).
- **Area #7a closure**: push `d0410eb` (VWR SOT v1, 2026-05-19).
- **Area #7b closure**: push `e751e72` (VWR Downstream Cascade v1, 2026-05-19+, scene_detail/27.202605181937 4-point sync source).
- **Area #7c closure**: push `62aa2ef` (t2i_review v6 placeholder example abstraction v1, 2026-05-19+, plan template `3bf7c22` 본 plan 계승 source).
- **Codex MCP pane**: `%0` (gpt-5.5 xhigh) — per-wave review + range review + push gate verdict.
- **Memory references**: [[feedback_llm_based_judgment]] (4-Gate Doctrine) + [[feedback_no_vlm_dependency]] (NO VLM) + [[feedback_codex_mcp_discussion_workflow]] (Codex first) + [[feedback_codex_mcp_claude_mcp_response_trigger]] (prefix/suffix + raw typing 5-step) + [[feedback_subagent_model_opus]] (opus subagent) + [[feedback_push_no_user_ask]] (push no user ask) + [[feedback_session_50pct_codex_close_check]] (session 50%+ Codex close) + [[feedback_session_reset_clear_not_exit]] (/clear ONLY) + [[next_session_area_7d_plan_drafting]] (본 plan 진입점) + [[session_20260519_area_7d_spec_v1_closure]] (직전 spec drafting closure) + [[session_20260519_area_7c_v1_closure]] (Area #7c closure + 함정 1/2 doctrine).

## 10. Carry Follow-up Post Area #7d

### 10.1 Carry-D7d-1 — scene_detail v28 description align (full inline, spec Q-DR4 verbatim)

Codex iter 0 권고 8 흡수 — full inline (spec §10.1 정합 + Codex Q-DR4 D2 verbatim):

> `scene_detail/27.202605181937 is the current primary scene_detail prompt pack after Area #7b W3 and is already 4-point synced. It contains no 7+7 cinematography closed-list; only the camera_effect string description remains. Area #7d v1 does not open scene_detail/28 because a description-only edit would require a full scene_detail 4-point sync and pinned-test cascade. Area #7d therefore does not claim full dual-owner remediation; scene_detail description alignment is carried as Carry-D7d-1.`

**Scope**: `prompts/_base/scene_detail/27.202605181937/detail_schema.json:15` 안 `"카메라 구도 + 색감 (필수)"` description 의 open-world align 추가 wave (별도 Area v1 외 carry).

**Cost**: medium (4-point sync `SCENE_DETAIL_PROMPT_VERSION` + `MODULE_VERSIONS["scene_detail_composer"]` + `_MODULE_INFO.prompt_dependency` + pinned tests 4 file cascade obligatory).

**Trigger condition**: scene_detail dual-owner fully fixed claim 의무 발생 시 (예: production canary 결과 description-only edit 효과 검증 후).

**Status (post-Carry-D7d-1)**: was deferred from Area #7d v1 per Codex Q-DR4 D2 verdict; resolved by Carry-D7d-1 W1 `05af1d5` + W2 closure-docs (scene_detail/28.202605190927 신설, 4-point sync + pinned tests cascade 완료, 127 pytest PASS). Area #7d v1 historical fact preserve — Area #7d v1 self-scope 안 scene_detail/28 신설 0.

### 10.2 Carry-D7d-2 — prompt_service E6 alternative structured handling (conditional, W2 outcome 따라)

Codex iter 0 권고 8 흡수 — conditional carry:

- **W2 preferred (regex 폐기) 채택 시**: carry 없음 (E6 mandatory in-scope closed by W2 atomic).
- **W2 alternative (regex 남기되 non-semantic structured handling) 채택 시**: audit 01 row 신설 의무 (W4 closure docs §6.5 추가 + audit 01 file 안 row 추가). field-shape migration (`cinematography.derived_phrasing` 등 structured field) = major carry → Carry-A100 또는 별도 area.

**Plan default 권장**: preferred (W2 regex 폐기). Carry-D7d-2 trigger = W2 implementation 시 cleaned consumer cascade 안 cinematography token preserve 의무 발견 시 only.

### 10.3 Carry-A099 — VWR director_notes prose redesign V8+

Area #7a 정합 보존 carry. cost: medium-large. Area #7d 영향 0.

### 10.4 Carry-A100 — VWR t2i_context demonym redesign V8+

Area #7a 정합 보존 carry. cost: medium. Area #7d 영향 0.

### 10.5 Carry-P021 — scene_consistency 7 enumeration broad (source unresolved low-priority)

Area #7a 정합 보존 carry. cost: small (source 파악 후). Area #7d 영향 0.
