# Code-Side Regex Audit

Date: 2026-05-14 (audit 시작) / 2026-05-15 (작성)
Scope: `backend/app/**/*.py` 전수 — code 안에서 open-world visual/story semantic 판단을 regex / substring matching / keyword list 로 수행하는 site.

## 1. Audit Boundary

본 파일은 본 audit 시리즈의 **code-side regex** 영역. prompt-side / LLM-side / DB row 는 별도 파일 (`02-`, `03-`, `04-`).

Included:
- 자연어 시나리오 텍스트 (camera_direction / shot_description / prompt body / scenario heading) 에서 visual/story 의미를 추출/분류/routing 하는 production code regex.
- LLM 이 emit 한 자연어 target/suggestion 으로 prompt 를 blind `.replace()` 하는 mutation site.
- Visual mutation 범위 외 generic helper / placeholder substitution 은 §7 Excluded.

Excluded (다른 영역 위임):
- LLM-facing classifier instruction (prompts/_base 안 system.md 에 정의된 closed phrase list) → `03-llm-side-heuristic-audit.md`
- prompt 내 시나리오 leak / 도메인 nomenclature 분산 → `02-prompt-side-hygiene-audit.md`
- prompt_loader DB row override / file fallback / code 상수 pin → `04-db-row-verification.md`

분리 기준 (Codex map §1 인용 + 흡수):
> Regex is acceptable for closed technical syntax. Regex is debt when it tries to decide open-world visual meaning such as close framing, gaze, offscreen presence, body-part focus, reference usage, or physical/spatial relation.

## 2. Verification Passes Performed

### 2.1 Grep 전수 (raw vs audit-count after exclusion)

| Pattern | Raw Hits | Audit-count (after Excluded class 분리) |
|---|---|---|
| `re.compile` in `backend/app/**/*.py` | **63** | 18 (P0+P1+P2 site, closed-world ID/path/hash regex Excluded) |
| `_RE` / `_REGEX` identifier (overlap with `re.compile`) | 44 | — (subset of above) |
| `_TOKENS` / `_KEYWORDS` / `_PHRASES` / `_PATTERNS` | 11 | 7 (audit row 에 직접 매핑) |
| `.lower() in (...)` | 3 | 1 (`render_prompt_card.py:1292` perception_mode — #18; 나머지 2 = closed bool literal in prompt_loader / llm_client) |
| `in prompt_lower` / `in lowered` | 4 | 2 (`frame_spatial_contract.py:322-324` soft #7 + `visible_entities_validator.py:217` substring #16) |
| Visual prompt `.replace()` (scope-narrowed `t2i_review.py`) | **3** (`:395`, `:402`, `:451`) | 3 (#10 — entity 2 site `:395, :402` + scene 1 site `:451`) |

Raw vs audit-count 차이 = Excluded class (§7) 로 분리된 closed-world technical regex / generic helper / placeholder substitution.

### 2.2 Targeted Reread (17 files)

- `services/scene_generation_coordinator.py` 70-82, 370-405, 655-695
- `core/steps/detail_steps.py` 100-115, 821-878, 2630-2830
- `core/steps/render_prompt_card.py` 100-260, 329-505, 540-571, 920-998, 1040-1180, 1280-1340
- `core/ref_contract_validator.py` 40-190
- `core/visible_entities_validator.py` 50-281, 540-572, 740-790
- `modules/pipeline/shot_visibility.py` 1-150, 280-340
- `modules/pipeline/shot_director.py` 170-201
- `core/steps/scene_consistency_step.py` 55-115
- `core/frame_spatial_contract.py` 1-100, 280-332
- `modules/pipeline/t2i_review.py` 375-455
- `modules/llm/safety.py` 1-90
- `services/prompt_service.py` 40-210
- `core/keep_elements.py` 1-131
- `modules/pipeline/scene_extractor_v2.py` 240-263
- `modules/semantic_contract_router.py` 1-153
- `core/asset_readiness.py` 425-442
- `core/name_matcher.py` (full)

## 3. Severity Schema (4-tuple)

각 row 에 적용:
- **Priority**: `P0` (runtime routing / fail-fast 변경 가능, fix = 별도 area) / `P1` (diagnostic / LLM hint / canary scope, hard 승격 금지) / `P2` (closed-world technical regex, keep + boundary) / `Excluded` (non-counted, visual mutation scope 외)
- **Evidence**: `confirmed` (file:line read 직접 검증) / `plausible` (가설 — case review 필요) / `unverified` (production 측정 / 별도 audit 필요)
- **Fix Cost**: `small` (~50 LOC, 단일 site) / `medium` (~200-500 LOC, schema 1 field + consumer 2-3) / `large` (~500+ LOC, schema/prompt/consumer/test cascade 동시)
- **Dependency**: `prerequisite: [#N]` / `blocked-by: [#N]` / `independent`

## 4. P0 Inventory (7) — Runtime routing risk

### #1. `_CLOSE_FRAMING_RE` 3-site shared routing SOT

- **file:line**:
  - `services/scene_generation_coordinator.py:76-81`
  - `core/steps/detail_steps.py:104-109`
  - `core/steps/render_prompt_card.py:560-565` (imports `detail_steps._CLOSE_FRAMING_RE`)
- **동작**: `render_prompt_card._derive_framing_scale` 가 detail_steps regex import → `RPC.render_strategy.framing_scale` 산출. coordinator 는 chain_bg skip 분기.
- **위험**: regex 가 close framing routing SOT. 시나리오 synonym 누락 시 silent miss. `RPC.framing_scale` → `render_contracts` → validator cascade.
- **4-tuple**: P0 / confirmed / medium / prerequisite for #2, #16, #17, #18 (framing_scale enum SOT prerequisite)
- **Codex 매핑**: P0-1 / **사용자 메모**: P0 #1
- **Fix direction**: shot_staging schema 에 `framing_scale: enum["close", "medium", "wide", "insert"]` LLM-emit field. code 는 enum consume. 기존 regex 는 transitional diagnostic 만.

### #2. close framing 만 chain_bg skip, prev_shot_ref 항상 시도

- **file:line**: `services/scene_generation_coordinator.py:371-405, 655-695` + broad `except Exception` `:393, :678`
- **동작**: close framing 시 chain_bg 만 skip, `build_prev_shot_background_ref` 는 항상 호출. 실패 시 broad except → entity-only fallback.
- **위험**: close + non-`zoom_in_detail` previous-shot ref 가 silent attach → wide BG 와 close 인물 scale 충돌, "허공 얼굴" 결함 재발.
- **4-tuple**: P0 / confirmed / medium / depends-on #1
- **Codex 매핑**: P0-2 / **사용자 메모**: P0 #2
- **Fix direction**: 명시적 `close_framing × ref_usage` attach matrix. `close + zoom_in_detail` preserve (same-frame push-in). 그 외 close ref 는 `RefContractError` fail-fast + broad except 에서 reraise (single + batch 양쪽).

### #3. `shot_visibility` Korean lexicon regex → `visible_entity_ids` mutation

- **file:line**:
  - `modules/pipeline/shot_visibility.py:36-94` (6 lexicon — `_KOREAN_GAZE_STEMS/_TAILS/_KOREAN_FRAMING_NOUNS/_BODY_PART_NOUNS/_DIRECTIONAL_TAILS/_GAZE_NOUNS/_CLOSE_UP_MARKERS/_OFFSCREEN_PHRASES`)
  - `shot_visibility.py:108/116/121/132` (4 compiled regex)
  - `modules/pipeline/shot_director.py:188-190` (actual mutation: `ls["visible_entity_ids"] = [...]`)
- **자기 모순**: `shot_visibility.py:22-24` docstring "auto-fix 안 함 — visible 리스트 변조가 silent SOT 변조" 명시하면서 `shot_director.py:188-190` 가 실제 변조.
- **위험**: open-world Korean lexicon 매칭 → 새 시나리오 silent miss + canary false-positive.
- **4-tuple**: P0 / confirmed / large / independent (Area 2 — shot_visibility drift, no mutation)
- **Codex 매핑**: P0-3 / **사용자 메모**: P0 #3
- **Fix direction**: mutation 폐기 + drift diagnostic only (raise → log). 장기 SOT = shot_director / shot_staging 의 structured `visibility_relation: {entity_id, status, reason_short}` LLM emit.
- **추가 발견 (Subagent C)**: prompt 가 instruct 하는 blocking 패턴 (`차단/막다/가리다/block/obstruct`) 이 code lexicon regex 에 부재 — **dual-sync silent miss**.

### #4. `ref_contract_validator` "from the reference" token window classifier

- **file:line**:
  - `core/ref_contract_validator.py:47-89` (3 noun lists CHARACTER/BACKGROUND/OBJECT + GENERIC_VERB/NOUN)
  - `:91-118` `_classify_window` rfind-nearest
  - `:156-190` `classify_from_the_reference` 30/60자 window
  - `:425-445` phantom guard consumer (RefContractError raise / warning)
- **동작**: "from the reference" phrase 의 character/background/object 분류를 nearest-token rfind 로 결정.
- **위험**: synonym 누락 시 character ↔ object 잘못 분류 → phantom guard 가 silent skip 또는 hard raise. "her hand holding the object from the reference" 같은 양쪽 token 충돌 시 oscillate.
- **4-tuple**: P0 / confirmed / medium / independent (Area 3 — phantom guard replacement)
- **Codex 매핑**: P0-4 / **사용자 메모**: P0 #4
- **Fix direction**: attached metadata / render-contract 비교로 전환. token list 동결. classify_from_the_reference 를 warning-only 로 격하.

### #5. `scene_detail_owned_judge` verb whitelist (LLM-side, code post-process 0)

- **file:line**: `prompts/_base/scene_detail_owned_judge/3.202605051746/system.md:18-61`
- **동작**: judge LLM 이 redraw verb whitelist 16개 (`create/render/draw/generate/paint/build/furnish/add/place/put/insert/hang/mount/install/set up`) + 7 anchor phrase + 4 spatial preposition + 3 형용사 예시로 redraw/anchor 결정. code post-process 0 (verdict enum 만 consumer).
- **위험**: "closed list of open-world verbs" failure mode — 새 verb 누락 silent pass.
- **4-tuple**: P0 / confirmed (LLM-side, code post-process 0) / medium / independent
- **Codex 매핑**: P0-5 / **사용자 메모**: P0 #5
- **본 audit 영역 외** — 본 row 는 reference 목적. 실제 fix detail 은 `03-llm-side-heuristic-audit.md` L-2.

### #16. `visible_entities_validator` face/eye close-up exemption regex + substring

- **file:line**:
  - `core/visible_entities_validator.py:139-145` `_FACE_CLOSE_UP_PATTERNS` (2 regex)
  - `:212-217` `id_policy.body_part_focus_rule.trigger_phrases` substring `t.lower() in prompt_lower`
- **동작**: body_part_focus trigger phrase 매칭 시 forward enforcement 면제. 단 face/eye close-up regex 매치 시 exemption 거부.
- **위험**: exemption routing 이 regex/substring 결정. Source 1 (visible_entities ID 가 prompt 에 없을 때 raise) 의 silent fail-fast 결정 직결. 동의어 누락 시 wrongly exempt → ID 누락 통과.
- **4-tuple**: P0 / confirmed / medium / depends-on #1 (structured `focus_scope` / `body_part_focus` enum)
- **Codex 매핑**: P0-6 / **사용자 메모**: P0 #16 (사용자 첫 메시지 group 3 — 15 항목 표 누락, 본 audit 보강)
- **Fix direction**: structured `focus_scope` / `body_part_focus` 필드 후 `_FACE_CLOSE_UP_PATTERNS` 폐기 + trigger_phrases substring → enum consume.

### #18. `render_prompt_card` perception_mode 4-token `.lower() in (...)`

- **file:line**: `core/steps/render_prompt_card.py:1292-1297`
- **동작**: `(perception_mode or "").lower() in ("reflection", "mirror", "through_device", "projection")` — id_policy constraints[6] entry. 매치 시 reflective/projected surface common-noun constraint inject.
- **위험**: perception_mode = free string. routing 결정이 4 string token 비교. shot_staging 의 perception_mode 가 다른 token ("screen"/"lens"/"camera_feed" 등) 시 silent miss.
- **Schema description drift (Subagent C 발견)**: `shot_staging/10.../schema.json:13` = `{"type": "string", "description": "How the scene is perceived: direct, hallucination, dream, memory, reflection, through_device, projection."}` — **JSON schema enum 아님 (type: string + description token list, non-enforced)**. prompt 4-token (`mirror / reflection / through_device / projection`) + code 4-token (동일) + schema description 7-token (`direct / hallucination / dream / memory / reflection / through_device / projection` — **`mirror` description 에도 부재**). schema 미강제 → arbitrary string silent miss + fail-fast 부재.
- **4-tuple**: P0 / confirmed / medium / depends-on representation_binding enum SOT
- **Codex 매핑**: P0-7 / **사용자 메모**: P0 #18 (사용자 group 누락, 본 audit 보강)
- **Fix direction**: representation_binding enum 통일 (schema 7 ↔ prompt+code 4 align). `.lower() in (...)` 폐기.

## 5. P1 Inventory (6) — Diagnostic / LLM hint / canary scope

### #6. Entity variant nomenclature 분산 (LLM-side, 5 prompt)

- **file:line**: `entity_all/.../character.md:24-31` + `entity_extractor_v2/.../system.md:16-36` + `entity_relation/.../system.md:11-13,30-37` + `outlook_extractor/.../phase1.md:16` + `visual_world_rules/.../system.md:30-36, :51` (SOT owner)
- **위험**: 분산 nomenclature drift — 한 prompt splitter, 다른 prompt binder.
- **4-tuple**: P1 / confirmed (LLM-side) / medium / independent
- **Codex 매핑**: P1-1 + §6 / **사용자 메모**: P1 #6
- **본 audit 영역 외** — 자세한 11+ site 발견 + visual_world_rules SOT 일원화는 `02-prompt-side-hygiene-audit.md` 별도 area carry.

### #7. `frame_spatial_contract` phrase diagnostic (soft only)

- **file:line**: `core/frame_spatial_contract.py:25-41` `ZONE_PHRASES/DEPTH_PHRASES` + `:309-332` `phrase_diagnostic()`
- **동작**: label/zone/depth phrase 가 t2i_prompt 안에 있는지 substring 검사 → soft warning only (raise X).
- **위험**: 현재 soft 라 안전. **hard 승격 금지** — open-world phrase 분류기로 변질 위험.
- **4-tuple**: P1 / confirmed / small (keep) / independent
- **Codex 매핑**: P1-2 / **사용자 메모**: P1 #7
- **Fix direction**: 유지. Closed area (Area frame-spatial-contract closed 2885f8c) — 흔들지 말 것.

### #8. `render_prompt_card` LLM-facing hint constants

- **file:line**:
  - `core/steps/render_prompt_card.py:131-146` `_ID_CLOSE_FACE_FORBIDDEN_PHRASES` (6) + `_ID_CLOSE_FACE_RECOMMENDED_PHRASINGS` (4)
  - `:207-215` `_CONTINUITY_GENERIC_PERSON_NOUNS` (7)
  - `:223-231` `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` (7)
  - `:336-438` 8 `_SPATIAL_*_TOKENS` + `_SPATIAL_FRAMING_CLOSE_KEYWORDS` (9) + `_SPATIAL_FRAMING_WIDE_KEYWORDS` (5)
- **동작**: LLM hint constants — `build_id_policy` / `build_continuity_elements` / `build_spatial_consistency` inject.
- **위험**: acceptable only as LLM hint/canary scope. hard 승격 금지. drift 시 prompt-side ↔ code-side dual sync 의무.
- **4-tuple**: P1 / confirmed / small (keep + boundary guard) / independent
- **Codex 매핑**: P1-3 / **사용자 메모**: P1 #8
- **Triple-list 위험 (Subagent C 발견)**: LLM-facing 11 (close keywords inject) / canary 9 (`_SPATIAL_FRAMING_CLOSE_KEYWORDS`) / production regex `_CLOSE_FRAMING_RE` — production regex 변경 시 3-way sync 의무.

### #9. `visible_entities_validator` entity ID anchor window

- **file:line**: `core/visible_entities_validator.py:540-572` + `_entity_specific_id_in_window` (line 102-128) ±`_ANCHOR_WINDOW=60` + `_SENTENCE_BOUNDARY_CHARS`
- **동작**: entity_canon.name 이 prompt 등장 시 ±60 char window 안 specific C##/C##O## ID 동반 여부 validate.
- **위험**: closed-world OK — entity ID anchor 기준. name window 가 semantic visibility 추론으로 확장되면 P0 승격.
- **4-tuple**: P1 / confirmed / small (keep) / independent
- **Codex 매핑**: P1-4 / **사용자 메모**: P1 #9
- **Fix direction**: 유지. 의미 추론 확장 금지.

### #10. `t2i_review` blind substring `.replace()` mutation

- **file:line**: `modules/pipeline/t2i_review.py:383-403` `_apply_entity_fixes` + `:407-454` `_apply_scene_fixes`
- **동작**: LLM emit `target -> suggestion` 으로 blind `.replace()`.
- **위험**: item_id fan-out 차단 (Patch A 완료), 그러나 target substring 다중 등장 시 의도 외 위치도 변조 가능. prompt-side `t2i_review/.../scene_system.md:87-90` dual sync 의무.
- **4-tuple**: P1 / confirmed / medium / independent (Area 5)
- **Codex 매핑**: P1-5 / **사용자 메모**: P1 #10
- **Fix direction**: patch ID / span position / regeneration diagnostic 으로 전환. Codex map §8 Area 5.

### #17. `scene_consistency` element_id + description close keywords

- **file:line**:
  - `core/steps/scene_consistency_step.py:65-72` `_ELEMENT_ID_CLOSE_REGEX` (suffix patterns)
  - `:73-80` `_DESCRIPTION_CLOSE_KEYWORDS` (10 entry)
- **동작**: element_id 접미사 + description keyword 로 close/full framing 분류 (`_classify_framing`).
- **위험**: open-world close-up text classifier. 새 phrasing 누락 silent miss.
- **4-tuple**: P1 / confirmed / medium / depends-on #1 (framing_scale SOT)
- **Codex 매핑**: P1-6 / **사용자 메모**: P1 #17 (사용자 첫 메시지 group 5 — 15 항목 표 누락, 본 audit 보강)
- **Fix direction**: framing_scale SOT 후 fixed_elements 의 `element_scope` enum 으로 대체.

## 6. P2 Inventory (5) — Closed-world technical regex, keep + boundary

### #11. INT/EXT scenario heading regex

- **file:line**: `modules/pipeline/scene_extractor_v2.py:244` `re.compile(r"^\s*((?:INT|EXT|INT/EXT|EXT/INT)\..+)$", re.MULTILINE)` + `modules/scene_still_extractor_legacy.py:19`
- **동작**: 시나리오 heading 정형 syntax 분할 (`INT./EXT./INT/EXT./EXT/INT.` prefix).
- **이유 keep**: closed industry-standard syntax. 의미 추론 아님.
- **4-tuple**: P2 / confirmed / small (keep) / independent
- **Codex 매핑**: (미명시) / **사용자 메모**: P2 #11

### #12. `location_consistency_step` name-in-heading substring fallback

- **file:line**: `core/steps/location_consistency_step.py:95-105`
- **동작**: scenes_by_location 빌드 — ID-based path primary, 없으면 `location.name in heading` substring fallback.
- **이유 keep**: fallback path only. primary = ID-based. heading 정형 (`L##. 항구 - 낮`) 안에서만 매칭.
- **4-tuple**: P2 / confirmed (fallback only) / small (keep, boundary 명시) / independent
- **Codex 매핑**: (미명시) / **사용자 메모**: P2 #12
- **Guard**: ID-based primary 깨지면 P1 승격 검토.

### #13. `safety` KO/EN moderation dictionary

- **file:line**: `modules/llm/safety.py:23-73` `_SAFETY_REPLACEMENTS_KO` (16) + `_SAFETY_REPLACEMENTS_EN` (13 regex `\b…\b`) + `:158-180` `_SAFETY_MESSAGE_KEYWORDS` (15+)
- **동작**: 픽션 graphic 단어 → 영화 set 표현 치환. `is_safety_related_error()` 가 provider moderation signal 분류.
- **이유 keep**: closed-world safety vocabulary (단어 → 단어). provider error string = closed technical signal.
- **4-tuple**: P2 / confirmed / small (keep) / independent
- **Codex 매핑**: (미명시) / **사용자 메모**: P2 #13

### #14. `prompt_service` reference label parser

- **file:line**:
  - `services/prompt_service.py:43-58` `_IMAGE_N_PAREN_HEADER_RE` (57) + `_IMAGE_N_BARE_PREFIX_RE` (58)
  - `:60-72` `_HEADER_CHARACTER_TAGS` frozenset + `_BARE_LABEL_CHARACTER_PREFIXES` tuple
  - `:75-116` `_classify_label` 10-branch
- **동작**: reference image label 분류 — producer (scene_reference_service) emit 한 `Image N (<tag>): <brief>` / `Image N: <bare_label>` 정형 syntax.
- **이유 keep boundary**: producer label = 시스템 emit closed syntax. 단 `_classify_label` 의 9 inner substring branch (`"previous shot" in / "continuity" in / ...`) 은 partial open-world risk (→ #19 신규 발견).
- **4-tuple**: P2 (boundary) / confirmed / medium (전체 closed-token 화) / independent
- **Codex 매핑**: P2-1 partial / **사용자 메모**: P2 #14
- **Guard**: 새 producer label 종류 추가 시 P1 승격 검토.

### #15. `keep_elements` enum (Area D-next-min closed)

- **file:line**: `core/keep_elements.py:24-26` `KEEP_ELEMENT_KINDS = frozenset({"environment", "static_prop"})` + producer schema v7 enforcement
- **동작**: shot_dependency_t2i v7 schema 가 LLM emit `keep_elements[].kind ∈ {environment, static_prop}`. code 는 enum check + shape validate.
- **이유 keep**: closed LLM-produced enum. gate 3 (Structured SOT Required) 모범. Area D-next-min closure (commit 5f3da38).
- **4-tuple**: P2 / confirmed (closure) / n/a (closed area) / independent
- **Codex 매핑**: (미명시) / **사용자 메모**: P2 #15
- **Do-not-touch**: closed area (memo 함정 6 강조).
- **C7 closure note (2026-05-20+)**: C7 (fix-critical-1, roadmap §5.12) 가 본 closed area 에 `keep_elements[].subject_kind` structured signal (인물/비인물 enum `{non_human_visual_element, human_or_character_reference}`) 을 orthogonal 차원으로 추가 — `KEEP_ELEMENT_KINDS` + `validate_keep_elements_entry()` 의 `kind` enum check 본체 보존. producer v8 prompt-pack + schema_version 4. 위 bullet 의 `v7` 언급은 Area D-next-min closure 시점 기준 historical — 현재 active 는 v8. W1 `c8d2391` / W1 fix-up 1 `e4ee3ed`.

## 7. Excluded (Non-counted) — Visual mutation scope 외

Codex P2-6 (generic `.replace()` calls outside visual prompt mutation) 흡수 + 추가 분류.

본 audit boundary 외 reasoning:

- **Closed marker / placeholder substitution**:
  - `core/steps/image_steps.py:805-808, 307, 711` — `composite/state_variant:` marker rename + 4 placeholder (`{state_type}`, `{character_name}`, `{character_description}`, `{state_description}`)
  - `core/steps/_owned_judge.py:82-84` — `{t2i_prompt}`, `{owned_list_block}`, `{camera_direction}` 치환
  - `modules/pipeline/background_prompt.py:140-154` / `floor_plan_prompt.py:104-111` / `background_chain_planning.py:325` / `background_classify.py:67-68` / `background_chain_render.py:102` — template `{placeholder}` substitution
- **Closed ID syntax cleanup**:
  - `core/steps/detail_steps.py:2641-2826` — entity ID composite/bare rewrite (C##/C##O## via `re.escape`). `:2824, :2826` `re.sub` for 보통명사 ID escape 잔류 자국 제거
    - **C3 closure (2026-05-20+, W1-W2)**: roadmap §4.4/§225 reclassified only the downstream orphan-possessive / `focus on 's` cleanup portion of this row as prompt-body semantic mutation. Closed by C3 W1: `detail_steps.py` cleanup loop (`빈 ID 패턴 정리`, `focus on\s+'s`, `\s+'s\s+`, semantic subject insertion) removed; invalid SID stripping now uses `_strip_invalid_sids()` closed-world token-complete helper. Entity-ID composite/bare rewrite remains closed-world keep. Do not map this closure to audit 01 #10 (`t2i_review`).
  - `modules/pipeline/scene_validator.py:228-236` — entity ID 안전 cleanup
  - `modules/pipeline/outlook_dedup.py:271-294, 82` — `[{entity_name}]` bracket marker (closed delimiter)
  - `services/prompt_service.py:216-293` — entity ID `re.finditer` / `re.sub`
  - `services/scene_reference_service.py:65-128, 367-425` — entity ID label + `[[X]+[Y]]` marker
  - `api/v1/entities.py:433-448` — entity ID `re.finditer` / `re.findall`
  - `core/steps/analysis_steps_legacy.py:845, 860` — closed prefix strip (`CHAR_/BG_/PROP_`)
- **Closed hash/UUID extraction**:
  - `core/asset_readiness.py:260, 331` + `core/pipeline_gate.py:153` + `core/steps/image_steps.py:331, 422, 946` — `composite:/state_variant:` hash extraction
- **OS / time / atomic file rename**:
  - `core/checkpoint_io.py:52` + `core/pipeline_cache.py:39` + `core/security.py:30` + `core/step_runner.py:705, 716` + `modules/image_checkpoint.py:69` — os.replace / datetime
- **Boundary 모호 keep (closed shot_name dedup)**:
  - `services/scene_generation_coordinator.py:714` — `shot_name.lower() not in var_t2i.lower()` (closed cinematic technique name dedup)

추가 closed-world ID regex (P2 keep, boundary 명시):

- `core/frame_spatial_contract.py:56-57` (`_CHARACTER_ID_RE`, `_PROP_ID_RE`)
- `core/bg_state_vocab.py:64, 69, 100` (`BG_ID_RE`, `LLM_INTENT_ID_RE`, `_LOC_SHORT_ID_RE`)
- `core/steps/shot_validator_step.py:30, 32`
- `core/steps/scene_context_loader.py:519, 662, 736, 801` (`^S(\d+)_Shot(\d+)$`)
- `services/scene_checkpoint_loaders.py:129` (same)
- `core/visible_entities_validator.py:59-74` (C##/C##O##/P##/L## ID)
- `core/steps/render_prompt_card.py:530` (`_HASH_FORMAT_RE`), `:1806` (`_PROP_ENTITY_ID_RE`)
- `core/steps/render_prompt_card.py:553-557` (`_INSERT_HINT_RE`) — **LLM hint level**, Codex P2-5 ↔ #8 family
- `core/steps/floor_plan_render_step.py:37` + `core/steps/background_render_step.py:50` (BG/FP safe id)
- `core/step_runner.py:200` (`_ARCHIVE_PATTERN`)
- `api/v1/steps.py:127` + `services/snapshot_service.py:27, 121` (snapshot timestamp)
- `modules/pipeline/background_*.py` (_NON_ASCII / _SAFE_ID, Codex P2-4)
- `services/export_service.py:51` (closed short id)
- `modules/pipeline/scene_extractor_v2.py:75/110` (LLM-emit Python regex caller — closed `scene_heading_pattern` input)

## 8. 추가 발견 (사용자 인벤토리 외) — 1개

### #19. `prompt_service._classify_label` 10-branch substring routing partial migration

- **file:line**: `services/prompt_service.py:75-116`
- **동작**: 10-branch substring routing — `"previous shot" in / "same frame zoomed" in / "same room" in / "continuity" in / "background chain ref" in / "wearing" in / "outfit" in / "prop" in / "object" in / "background" in / "location" in`. producer emit closed label 이지만 inner substring 검사는 producer free-text. 결과: ref_role + ref_instructions 분기.
- **위험**: producer label format 진화 → silent miss. 단 RC-D fix 로 character_ref 만 exact-token (line 60-72). 다른 9 branches 는 아직 substring (partial migration in progress).
- **4-tuple**: P2 (boundary) / confirmed / medium (전체 closed-token migration) / independent
- **Codex 매핑**: P2-1 underspec — Codex `_is_explicit_character_ref_label` 만 명시, `_classify_label` 9 inner substring 명시 부재. **본 audit 신규 발견**.
- **사용자 메모**: #14 (label string routing) 와 결합 가능. 사용자 인벤토리 외 신규.
- **Fix priority**: P2 유지. 새 ref label 종류 추가 시 P1 승격 검토. Subagent A scope 외 fix 권장 낮음.

기타 자체 substring matching site — open-world semantic 분류 0 확인:

- `core/steps/location_consistency_step.py:104` `name in heading` (#12 와 동일, closed-syntax fallback)
- `modules/pipeline/shot_visibility.py:281, 287, 442` `name in subject_span/target_span/post_text` (entity name window, ID-based fallback, 의미 추론 0)
- `modules/pipeline/entity_lister.py:132, 335` `name in existing_names` (set membership dedup)
- `services/scene_generation_coordinator.py:714` (closed shot_name dedup, boundary 모호 but acceptable)
- `core/visible_entities_validator.py:540-572` `prompt_lower.find(name_lower)` (#9 동일, closed-world OK)

## 9. Codex Absorption Mapping

| Codex 항목 | 본 audit # | 검증 결과 |
|---|---|---|
| P0-1 (`_CLOSE_FRAMING_RE` 3 site) | #1 | confirm + 정확 |
| P0-2 (close × prev_shot_ref) | #2 | confirm + 정확 (broad except file:line 일치) |
| P0-3 (shot_visibility 6 lexicon + 4 regex + shot_director mutation) | #3 | confirm + 정확 + 자기 모순 docstring 재확인 |
| P0-4 (ref_contract_validator token window) | #4 | confirm + 정확 |
| P0-5 (scene_detail_owned_judge verb whitelist) | #5 | confirm — LLM-side, 본 audit 영역 외 reference |
| P0-6 (face/eye close-up regex + trigger phrases substring) | #16 | confirm + 정확 |
| P0-7 (perception_mode string token) | #18 | confirm + 정확 + 3-way drift 보강 |
| P1-1 (variant nomenclature 5 prompt) | #6 | confirm — LLM-side, 자세는 `02-` |
| P1-2 (frame_spatial_contract phrase_diagnostic) | #7 | confirm |
| P1-3 (render_prompt_card spatial tokens) | #8 | confirm + Triple-list 위험 보강 |
| P1-4 (entity ID anchor) | #9 | confirm |
| P1-5 (t2i_review blind replace) | #10 | confirm |
| P1-6 (scene_consistency element_id close keywords) | #17 | confirm |
| P2-1 (reference label parser) | #14 + #19 | **보강** — `_classify_label` 9 inner substring 신규 발견 |
| P2-2 (shot-id parser) | (Excluded closed-world ID) | confirm |
| P2-3 (hash / prop-id format) | (Excluded closed-world ID) | confirm |
| P2-4 (background master plan ID filter) | (Excluded closed-world ID) | confirm |
| P2-5 (insert/cutaway hint regex) | #8 family / Excluded | confirm — LLM hint scope |
| P2-6 (generic .replace) | Excluded | confirm + 분리 (본 audit Excluded class) |

**Codex 결과 차이/오류 보강 (2건)**:
1. **P2-1 underspec** — `_classify_label` 9 inner substring 누락 → #19 신규.
2. **사용자 분류 누락 보강** — Codex P0-6 (face) / P0-7 (perception_mode) 모두 사용자 첫 메시지 5 group 안에 있었으나 15 항목 표 변환 시 누락. 본 audit memo 직접 인용으로 #16 / #18 명시.

## 10. Dependency Graph

```
[framing_scale enum SOT]  ← First Implementation Candidate (사용자 결정)
  shot_staging schema 1 field: enum["close", "medium", "wide", "insert"]
  │
  ├─→ #1 (close framing 3 site)  — atomic patch, replaces _CLOSE_FRAMING_RE production SOT
  ├─→ #2 (close × ref_usage matrix)  — close + zoom_in_detail preserve / others fail-fast RefContractError
  ├─→ #16 (body-part/face exemption)  — structured focus_scope/body_part_focus → _FACE_CLOSE_UP_PATTERNS 폐기
  └─→ #17 (scene_consistency close/full classifier)  — element_scope enum

[representation_binding enum SOT]
  shot_staging perception_mode schema description token list 정렬 (현재 type: string + description 7-token, non-enforced) + 가능하면 JSON schema `enum` 강제 + prompt+code 4-token 정렬
  │
  └─→ #18 (perception_mode .lower() in (...) routing)  — `mirror` schema gap 해결

[shot_visibility drift only — Area 2]  (independent)
  │
  ├─→ #3 fix: shot_director.py:188-190 mutation 폐기
  ├─→ shot_visibility.py 6 lexicon → drift diagnostic only
  └─→ visibility_relation / offscreen_reason enum SOT (별도 area)

[ref-contract phantom guard replacement — Area 3]  (independent)
  │
  ├─→ #4 fix: classify_from_the_reference → warning only OR attached metadata 비교
  └─→ token list 동결

[t2i_review mutation redesign — Area 5]  (independent)
  │
  └─→ #10 fix: patch ID / span / regen-trigger only (prompt-side dual sync)
```

## 11. Closed Area — Do Not Touch (memo 함정 6 carry)

- `keep_elements.py` (Area D-next-min closed, commit 5f3da38) — #15
- `frame_spatial_contract.py` (Area frame-spatial-contract closed, commit 2885f8c) — #7
- `semantic_contract_router.py` (Patch B-min closed) — grep 결과 `KEYWORDS/TOKENS/_RE` 0, closed-world enum (`IMMOBILIZED_GAZE` frozenset) confirmed clean

## 12. Boundary Notes

- LLM-side classifier instruction (`shot_director/system.md:29-49`, `scene_detail_owned_judge/system.md:18-61`, `t2i_review/scene_system.md:31-91`) → `03-llm-side-heuristic-audit.md`
- Prompt scenario leak / domain nomenclature 분산 → `02-prompt-side-hygiene-audit.md`
- DB row override / file fallback / code 상수 pin → `04-db-row-verification.md`
- 본 fix 진입 시 prompt_loader DB row 확인 의무 (memo 함정 7 — file+code dual sync 필요).
