---
title: C3 — detail_steps focus rewrite 폐기 (upstream token-complete strip) v1 (fix-critical-1 Tier α #3)
date: 2026-05-20+
status: closed (C3 v1 W1-W2 atomic execution 완료 2026-05-20+ — Codex per-wave APPROVED + W1 plan-gap absorb; §10 Closure 참조)
roadmap_ref: docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md (Track B Tier α #3, G4 §4.4 boundary §225) + docs/fix-critical-1/index.html C3 section (locked persistent guide)
fix_critical_doc_ref: docs/fix-critical-1/index.html C3 section (lines 746-817)
audit_ref: docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/01-code-side-regex-audit.md §7 (Excluded — Closed ID syntax cleanup row, detail_steps.py:2641-2826) + roadmap §4.4/§225 reclassification (audit 01 #10 은 t2i_review 별건 — C3 무관)
prerequisites:
  - C1 perception_mode enum SOT v1 (closed 2026-05-20+) — 변경 0.
  - C2 owned_object_usage[] echo v1 (closed 2026-05-20+, origin/main HEAD `fd9c4e3`) — HEAD anchor. C2 owned path / sentinel v2 변경 0.
non_supersedes:
  - C7 #12 shot_dependency_t2i drift / C6 #10 color_palette_intent / C9 L-7 body-light verb / C4 #8 background classifier / C5 #9 action segment SOT / C8 #7 잔여 sub-area split / C10 future structured carries.
---

# C3 — detail_steps focus rewrite 폐기 v1

## 1. Purpose + Scope

### 1.1 Purpose

**audit 01 §7 (Excluded — Closed ID syntax cleanup row) + roadmap §4.4/§225**: `backend/app/core/steps/detail_steps.py` scene_detail post-process 안 t2i_prompt cleanup loop 이 3개 regex substitution 으로 prompt body 를 blind mutation. audit 01 §7 는 본 detail_steps cleanup 을 "Closed ID syntax cleanup" (benign) 으로 분류했으나, roadmap §225 가 본 site (`detail_steps.py focus on rewrite`) 를 G4 "prompt body mutation → 폐기" 로 재분류 — Area #6 (t2i_review mutation redesign, audit 01 #10) boundary 와 별개 routing. (audit 01 #10 자체는 t2i_review 별건 — C3 무관.)

문제의 3 regex (HEAD `fd9c4e3`, `detail_steps.py:2872-2882`):

```python
# 빈 ID 패턴 정리 (엑스트라 인물 등으로 인한 빈 문자열 잔류)
for var in t2i_vars:
    prompt = var.get("t2i_prompt", "")
    if prompt:
        # "showing  lying_down" → "showing lying_down"
        prompt = re.sub(r'\s{2,}', ' ', prompt)                          # :2877
        # "focus on 's" → "focus on the figure's"
        prompt = re.sub(r"focus on\s+'s", "focus on the figure's", prompt)  # :2879
        # " 's " (빈 소유격) → "the figure's "
        prompt = re.sub(r"\s+'s\s+", " the figure's ", prompt)           # :2881
        var["t2i_prompt"] = prompt.strip()                                # :2882
```

- `:2879` / `:2881` 은 prompt body 에 **semantic 구절 `"the figure's"` 를 삽입** — open-world prompt body rewrite, 4-Gate (Semantic Regex Ban) 위반.
- `:2877` 의 `\s{2,}` collapse 는 모든 t2i variation 에 무조건 실행되는 blind cosmetic mutation.
- cleanup 대상인 orphan `'s` / 빈 공백 은 **별도 upstream 코드 site 의 산물** (§2.2/2.3) — cleanup 은 그 결함의 downstream band-aid.

본 spec v1 = **OQ option (b) upstream fix** — orphan 을 만드는 VE-violation 강제 제거 strip 을 token-complete 하게 고쳐 cleanup loop 자체를 폐기. Codex C3 entry sanity (APPROVED_FOR_C3_SPEC_DRAFTING_WITH_NARROW_SCOPE_GUARDS, 본 session 2회 일치) 의 narrow scope guard 흡수.

### 1.2 In-scope

- **`_strip_invalid_sids()` helper 신설** — `detail_steps.py` module-level private function. 무효 closed-world SID 토큰 + 후행 소유격(`'s`) 을 한 단위로 token-complete 제거 (양쪽 공백이면 단일 공백, 한쪽이라도 비공백이면 공백 없이 연결 — 제거 위치 국소 정규화만). 무효 SID 무매치 시 원본 그대로 반환 (`subn` count 0 → no-op). semantic 구절 재삽입 없음.
- **Upstream strip site A 교체** — `detail_steps.py:2703-2706` (`if pure_remove:` t2i_prompt strip) 의 ad-hoc `strip_pat.sub("", prompt)` → `_strip_invalid_sids(prompt, pure_remove)`.
- **Upstream strip site B 교체** — `detail_steps.py:2774-2775` (`if bad_in_sfp:` representative_moment strip) 의 ad-hoc `strip_sfp.sub("", sfp)` → `_strip_invalid_sids(sfp, bad_in_sfp)`.
- **Cleanup loop 폐기** — `detail_steps.py:2872-2882` 3-regex cleanup loop 전체 delete.
- **C3 integrated regression test 신설** (`backend/tests/integration/test_c3_detail_steps_focus_rewrite.py`) — no live LLM, NO VLM. **W1 동일 commit** (Codex spec review IMPORTANT 2 — helper + 그 canary 동시 landing).
- **W2 closure docs atomic**.

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

- **scene_detail prompt-pack / schema / version**: v30 prompt-pack (`prompts/_base/scene_detail/30.202605200303/`), `detail_schema.json`, `SCENE_DETAIL_SCHEMA_VERSION`, `SCENE_DETAIL_PROMPT_VERSION`, `STEP_MANIFEST`, `version_registry.py`, cp `schema_version` — **모두 변경 0**. C3 = code post-process 동작 변경 only, output shape 불변.
- **producer non-compliance 자체 수정**: producer scene_detail LLM 이 `"focus on C##'s <body part>"` 류를 emit 하는 것은 runtime 가능성으로 다루되, v30 system.md grep 상 정확한 active residue 미확인 — **prompt-pack 변경 대상 아님** (Codex 명시).
- **VE-violation 강제 제거 path 자체 (retry → replace_map/pure_remove forced cleanup)**: graceful degradation 설계 보존. fail-fast (AppError raise) **채택 안 함** (§3.1).
- **body-part / face close-up phrase 정책, article 보정, subject reference 휴리스틱**: C3 범위 밖. 필요 시 별도 VE / id_policy area (Codex 명시).
- **`_check_prompts()` / `_extract_base()` / `_ve_pattern` / `replace_map` 로직**: 변경 0 — detector + variant-resolution path 보존, strip 산출 단계만 helper 로 교체.
- **detail_steps.py 그 외 모든 t2i_prompt mutation** (bare C## → C##O## 치환 `:2853`, replace_map 치환 `:2696`): closed-world ID→ID 치환 — C3 무관, 변경 0.
- **out-of-path empty-ID cleanup sites** (`scene_validator.py` `_clean_prompt`, `prompt_service.py` re.sub 등): C3 detail_steps runtime path 밖 — full-repo empty-ID closure 주장 안 함, 별도 area.
- **VLM / live LLM / semantic regex / open-world phrase regex**: 모두 ban.
- **C1·C2 변경 사항 / C4-C10**: 별도 area.

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

### 2.1 Mutation site — `detail_steps.py:2872-2882` cleanup loop

scene_detail per-shot post-process loop 끝부분. **모든 t2i variation 에 무조건 실행** (대부분 no-op — orphan 이 없으면 substitution 미발동). 3 regex:

| 줄 | substitution | 분류 |
|----|--------------|------|
| `:2877` | `re.sub(r'\s{2,}', ' ', prompt)` | blind cosmetic — double-space collapse |
| `:2879` | `re.sub(r"focus on\s+'s", "focus on the figure's", prompt)` | **semantic 삽입** — open-world prompt body rewrite |
| `:2881` | `re.sub(r"\s+'s\s+", " the figure's ", prompt)` | **semantic 삽입** — open-world prompt body rewrite |
| `:2882` | `var["t2i_prompt"] = prompt.strip()` | 무조건 trailing/leading whitespace trim |

코드 주석 `# 빈 ID 패턴 정리 (엑스트라 인물 등으로 인한 빈 문자열 잔류)` — empty C##/O## ID 잔류로 broken 된 `focus on 's` 류 cleanup 목적. cleanup 대상 orphan 의 발생원 = §2.2 / §2.3.

### 2.2 Upstream source A — `detail_steps.py:2703-2706` strip_pat (t2i_prompt)

post-retry `if post_remove:` 블록 (`post_remove = _check_prompts(result)` — §2.4). VE 위반 SID 중 variant 로 resolve 안 되는 것 = `pure_remove`. 현재 strip:

```python
if pure_remove:
    strip_pat = re.compile(
        r'\b(?:' + '|'.join(re.escape(v) for v in pure_remove) + r')\b'
    )
    prompt = strip_pat.sub("", prompt).strip()          # :2706
```

`\b(?:ID)\b` 는 **bare ID 만** 제거 — 후행 소유격 `'s` 와 인접 공백 미포함. `"focus on C08's hand"` (C08 ∈ pure_remove) → `"focus on 's hand"` orphan. 이 orphan 이 §2.1 cleanup loop 의 `:2879` 대상.

### 2.3 Upstream source B — `detail_steps.py:2774-2775` strip_sfp (representative_moment)

`representative_moment` (legacy `still_frame_prompt` fallback) 에서 동일 패턴:

```python
bad_in_sfp = {sid for sid in _ve_pattern.findall(sfp) if _extract_base(sid) not in allowed_set}
if bad_in_sfp:
    strip_sfp = re.compile(r'\b(?:' + '|'.join(re.escape(v) for v in bad_in_sfp) + r')\b')
    sfp = strip_sfp.sub("", sfp).strip()                # :2775
result["representative_moment"] = sfp
```

동일하게 bare ID 만 strip → orphan `'s` 생성. **단 `representative_moment` 은 §2.1 cleanup loop 대상이 아님** (cleanup loop 은 `t2i_vars` 만 순회) — orphan 이 cleanup 없이 그대로 downstream 진행. C3 가 같은 helper 로 동시 수정 (sibling same-defect source — Codex 명시, detail_steps 범위 안에서 widening 아님).

### 2.4 Detector — `_check_prompts()` `:2600-2625`

nested function. `_ve_pattern.findall(t2i_prompt)` 로 SID 추출 → base invalid 또는 composite outlook invalid 면 `to_remove` 집합 반환. post-retry 호출 `post_remove = _check_prompts(result)` → `replace_map` (variant resolve 가능) / `pure_remove` (resolve 불가) 로 분기. **C3 는 detector 미변경** — strip 산출 단계만 helper 화.

### 2.5 SID grammar — `_ve_pattern` / `_base_pattern` / `_extract_base` (`:2573-2598`)

```python
_ve_pattern = re.compile(r'[CLP]\d{2,3}(?:O\d{2,3})?')   # C##/L##/P## + optional O## composite
_base_pattern = re.compile(r'[CLP]\d{2,3}')              # base only
```

- SID = closed-world ID — `C12` / `L03` / `P123` / `C08O02` 등. 2~3 digit (3-digit ID `P123` 존재 — `sid[:3]` truncate 금지, `_extract_base` 정확 prefix 매치 사용).
- `pure_remove` / `bad_in_sfp` 원소 = 이 grammar 의 string. helper 의 regex 는 **이 escaped SID literal set 만** 대상 — closed-world contract, 4-Gate 정합.

### 2.6 Boundary — out-of-path sites (C3 미포함)

- `outlook_dedup.py` / `scene_reference_service.py`: composite-aware marker rename / re.sub — empty-ID emit 아님. C3 무관.
- `t2i_visual_converter.py` (`backend/app/modules/`): legacy 모듈, `image_service_helpers.py` runtime fallback path only — v4 detail_steps 경로 무관. C3 무관.
- `scene_validator.py` `_clean_prompt` / `prompt_service.py` re.sub: detail_steps runtime path 밖 — C3 미포함, full-repo empty-ID closure 주장 안 함.

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

### 3.1 OQ-C3 — fail-fast 결정 (audit 수정방법 step 2, 옵션 a/b/c)

**결정: option (b) upstream fix.**

- **(a) 폐기 + fail-fast (AppError raise)**: 기각. VE 위반 강제 제거는 CLAUDE.md "VE 위반 검사: scene_detail T2I 프롬프트에 VE 밖 엔티티 사용 시 retry + 강제 제거" 의 **의도된 graceful degradation**. retry 후에도 잔존하는 위반을 강제 제거하는 path (replace_map / pure_remove) 는 코드 주석에 명시된 설계. 알려진 recoverable 조건에 AppError 를 raise 하면 pipeline abort — 설계와 충돌 (Codex 동의).
- **(b) upstream fix**: 채택. orphan 을 만드는 §2.2/§2.3 strip 을 token-complete 하게 (SID + 후행 소유격 `'s` + 인접 공백 한 단위 제거) → orphan 미발생 → §2.1 cleanup loop 전체 dead → delete. strip 대상이 closed-world SID literal set 이라 4-Gate (Semantic Regex Ban / No Silent Fallback) 정합.
- **(c) cleanup 유지 + diagnostic log**: 기각. roadmap §225 가 본 site 를 "폐기" 로 명시 분류 — cleanup 유지 시 Track B 목표와 충돌.

### 3.2 OQ-C3-2 — orphan possessive 처리

**결정: token-complete 제거 후 잔존 phrase 그대로 ("focus on hand" 허용). semantic 재삽입 없음.**

`"focus on C08's hand"` (C08 무효) → helper → `"focus on hand"`. article 부재로 문법은 거칠지만, `"the figure's"` 류 semantic subject 삽입 / phrase 전체 삭제 / article·body-part 휴리스틱 추가보다 안전 (Codex 명시). C3 는 새 semantic subject / article 정책 / phrase-level body-part heuristic 을 추가하지 않는다. 후속 body-part phantom 처리가 필요하면 별도 VE / id_policy area.

### 3.3 OQ-C3-3 — helper 배치

**결정: `detail_steps.py` module-level private function `_strip_invalid_sids()`.**

- 신규 모듈 신설 안 함 — 사용처가 detail_steps.py 2 site 뿐, scope 최소.
- nested function 아닌 module-level — W1 regression test 가 직접 import / unit-test 가능 (`_check_prompts` / `_extract_base` 는 nested 라 직접 호출 단계는 helper 만 module-level 로 노출).
- 기존 module-level helper (예: `_detect_state_variant_chars` `:283`, `_compute_used_outlook_pairs`) 인접 위치.

## 4. Invariants

### 4.1 No semantic injection invariant
helper 출력 / detail_steps.py source 어디에도 `"the figure's"` 등 semantic 구절 삽입 없음. 무효 SID 제거는 글자 삭제 + 국소 공백 정규화만.

### 4.2 Token-complete removal invariant
무효 SID 제거 시 후행 소유격 `'s` 가 함께 제거됨 — 출력에 orphan `'s` (`"focus on 's"`, `" 's "`) 잔존 0.

### 4.3 Closed-world SID-only regex invariant
helper regex 는 `pure_remove` / `bad_in_sfp` 의 escaped SID literal 만 alternation 으로 포함. open-world phrase / 일반 명사 / 동사 매칭 0. `re.escape` + 길이 내림차순 정렬 (composite ID 가 bare prefix 보다 먼저 매칭).

### 4.4 3-digit ID preservation invariant
유효 SID (3-digit `P123` 포함) 는 helper 통과 후 보존. 무효 SID set 에 없는 ID 는 절대 제거 안 됨.

### 4.5 Graceful-degradation preserve invariant
VE-violation retry → replace_map / pure_remove forced cleanup path 의 제어 흐름 불변. AppError raise / pipeline abort 추가 0. `_check_prompts` / `_extract_base` / detector 로직 불변.

### 4.6 Output shape / version 불변 invariant
`t2i_variations` / `representative_moment` 의 key·구조·schema 불변. prompt-pack / schema_version / version constant / STEP_MANIFEST / version_registry / cp config_hash 변경 0.

### 4.7 NO VLM / no live LLM invariant
C3 코드·테스트 어디에도 VLM 의존 / live LLM 호출 없음. C3 regression test = helper unit + source-level canary only.

### 4.8 No-match no-op invariant
`invalid_sids` 가 non-empty 라도 text 에 해당 SID 가 없으면 (`subn` substitution 0건) helper 는 원본 text 를 **그대로** 반환 — `.strip()` / `\s{2,}` collapse 등 blind global mutation 0. (Codex spec review IMPORTANT 1 — `pure_remove` 존재 시 strip path 가 모든 variation 에 적용되므로, 무효 SID 무매치 variation 의 blind trim 차단 필수.)

## 5. W1-W2 Implementation Plan

### 5.1 W1 — helper + 2-site apply + cleanup loop 폐기 + C3 regression test (2 file: 1 modify + 1 new, 1 atomic commit)

Codex spec review IMPORTANT 2 — helper semantics 가 C3 의 전체 risk surface 이므로 helper landing 과 그 canary (G1-G7) 를 **동일 commit** 으로 묶는다. code-only commit 금지.

**W1-A 대상**: `backend/app/core/steps/detail_steps.py` (modify)

**W1.1 — helper 신설** (module-level, 기존 module-level helper 인접):

```python
def _strip_invalid_sids(text: str, invalid_sids: Iterable[str]) -> str:
    """무효 closed-world SID 토큰을 text 에서 token-complete 하게 제거.

    invalid_sids = _check_prompts() pure_remove / bad_in_sfp 가 산출한 VE 위반
    SID 집합 (C##/L##/P##/C##O## — _ve_pattern 형식의 closed-world ID). 각 SID 와
    그 후행 소유격('s) 를 한 단위로 소거하되, 양쪽이 모두 공백이면 단일 공백으로,
    한쪽이라도 비공백이면 공백 없이 잇는다 (제거 위치 국소 정규화만). 무효 SID 가
    text 에 없으면 (subn substitution 0건) 원본 text 를 그대로 반환 — blind
    global trim/collapse 없음. 제거 자리에 semantic subject phrase 재삽입 없음
    (글자 삭제 + 국소 공백 정규화만).
    """
    sids = sorted({s for s in invalid_sids if s}, key=len, reverse=True)
    if not sids or not text:
        return text
    pattern = re.compile(
        r"(?P<left>\s*)\b(?:" + "|".join(re.escape(s) for s in sids)
        + r")\b(?:'s)?(?P<right>\s*)"
    )

    def _repl(m: "re.Match[str]") -> str:
        return " " if (m.group("left") and m.group("right")) else ""

    cleaned, n = pattern.subn(_repl, text)
    return cleaned if n else text
```

- `Iterable` 이 `detail_steps.py` typing import 에 없으면 `from typing import ...` 줄에 추가.
- `subn()` substitution count `n` == 0 → 원본 그대로 반환 (Codex spec review IMPORTANT 1 — `pure_remove` 존재 시 strip path 가 모든 variation 에 적용되므로 무매치 variation 의 blind trim 차단).
- `_repl` callback — 제거 위치 양쪽이 모두 공백이면 단어 사이 단일 공백, 한쪽이라도 비공백이면 공백 없이 연결. 제거 위치에서 떨어진 pre-existing 공백은 매치 밖이라 불변.
- `(?:'s)?` — SID 후행 소유격 optional 소거.

**W1.2 — strip site A 교체** (`:2702-2706`):
```python
if pure_remove:
    prompt = _strip_invalid_sids(prompt, pure_remove)
```
(기존 `strip_pat` 컴파일 + `.sub("", prompt).strip()` 4줄 → 1줄)

**W1.3 — strip site B 교체** (`:2772-2776`):
```python
if bad_in_sfp:
    sfp = _strip_invalid_sids(sfp, bad_in_sfp)
result["representative_moment"] = sfp
```
(기존 `strip_sfp` 컴파일 + `.sub("", sfp).strip()` → helper 호출)

**W1.4 — cleanup loop 폐기** — `:2872-2882` `# 빈 ID 패턴 정리` for loop 전체 delete (주석 포함).

**W1-B 대상**: `backend/tests/integration/test_c3_detail_steps_focus_rewrite.py` (new)

**W1.5 — C3 regression test 신설** — 7 Gate (G1-G7, §7). no live LLM, NO VLM — `_strip_invalid_sids` helper unit-test + `detail_steps.py` source-level canary. C1/C2 `test_c1_*` / `test_c2_*` 명명 정합.

**W1 gate**: 신규 test 파일 전수 PASS + `pytest backend/tests` 중 detail_steps / scene_detail 관련 전수 PASS + 백엔드 전체 회귀 (pre-existing failure 제외 동일). repo-root cwd 에서 실행. W1 = `detail_steps.py` modify + `test_c3_detail_steps_focus_rewrite.py` new → 1 atomic commit.

### 5.2 W2 — Closure docs atomic (path-limited)

- 본 spec frontmatter `status` → closed + §10 Closure 기재.
- C3 plan frontmatter `status` → closed + plan Closure section.
- roadmap `2026-05-16-track-b-semantic-debt-roadmap-design.md` §5.15 C3 entry 신설 (§5.13 C1 / §5.14 C2 정합 형식).
- `docs/fix-critical-1/index.html` C3 marker — badge ✓ CLOSED + priority list C3→CLOSED / C7→NEXT + priority matrix row 3 status.
- `docs/visual-reliability-audit/.../01-code-side-regex-audit.md` §7 (Excluded — Closed ID syntax cleanup) detail_steps row 에 C3 closure/reclassification inline note (audit 01 #10 = t2i_review 별건이라 marker 배치 X — Codex W2 audit-01 marker verdict 정합).
- **W2 self-hash 금지** (C2 N-W4-2 정합) — closure docs 안 W2 자체 commit hash literal 없음. "W1-W2" / "W2 closure commit" wording. 실제 hash 는 push 후 external memory 기록.

**W2 gate**: closure docs 정합 + W1-W2 range review.

## 6. Boundary Preserve

- `_check_prompts()` / `_extract_base()` / `_ve_pattern` / `_base_pattern` / `replace_map` 분기 / outfit_assignments 정합 로직 — 변경 0.
- bare C## → C##O## 치환 (`:2853`), replace_map ID→ID 치환 (`:2696`) — closed-world ID 치환, C3 무관 변경 0.
- C1 perception_mode helper / C2 owned path·sentinel v2 — 변경 0.
- scene_detail prompt-pack v30 / detail_schema.json / 모든 version artifact — 변경 0.

## 7. Test Gates (G1-G7)

| Gate | 내용 |
|------|------|
| **G1** | t2i_prompt 무효 ID 소유격 제거 — `_strip_invalid_sids("focus on C08's hand", {"C08"})` == `"focus on hand"` (orphan `'s` 0). |
| **G2** | representative_moment 무효 ID 소유격 제거 — sfp 형 텍스트에 대해 동일 helper 동작 검증. |
| **G3** | composite ID 소유격 제거 — `_strip_invalid_sids("a man C08O02's coat", {"C08O02"})` == `"a man coat"` (composite 한 단위 제거). |
| **G4** | 3-digit ID 보존 — invalid `{"C12"}`, text `"P123 holds C12"` → `"P123 holds"` (유효 3-digit `P123` 보존, prefix misalign 0). |
| **G5** | semantic 삽입 0 — helper 출력에 `"the figure's"` 없음 + `detail_steps.py` source 에 `"the figure's"` / `"focus on the figure's"` literal 0. |
| **G6** | 국소 정규화 + no-match no-op (Codex spec review MINOR 1) — (a) 무매치 no-op: `_strip_invalid_sids("  clean prompt  ", {"C08"})` == `"  clean prompt  "` (원본 그대로, trim 0); (b) far-whitespace 보존: `_strip_invalid_sids("alpha  beta C08's gamma", {"C08"})` == `"alpha  beta gamma"` (제거 위치에서 떨어진 alpha/beta double space 보존); 무효 SID 만 제거, 유효 ID 불변. |
| **G7** | cleanup loop 폐기 증명 — `detail_steps.py` source 에 `:2872-2882` 3-regex cleanup loop 부재 (`re.sub(r"focus on\s+'s"` / `re.sub(r"\s+'s\s+"` / 무조건 `\s{2,}` cleanup for-loop literal 0). |

## 8. Risk + Open Issues

- **R1 — 무조건 `.strip()` / `\s{2,}` collapse 제거**: 기존 cleanup loop 은 모든 var 의 t2i_prompt 에 무조건 `.strip()` + double-space collapse 적용. 폐기 후 producer-emit leading/trailing whitespace·double-space 는 더 이상 cosmetic 보정 안 됨. Track B "no blind mutation" 원칙상 의도된 결과 (producer 출력을 code 가 blind 보정하지 않음). helper 는 §4.8 no-match no-op 으로 무효 SID 가 없는 prompt 를 절대 변형 안 하고, 무효 SID 가 있어도 제거 위치 국소 공백만 정규화 — blind global trim/collapse 경로 0. T2I 이미지 모델은 잉여 공백에 관대 — 영향 무시 가능. W1 gate 백엔드 회귀로 downstream exact-match 의존 부재 확인.
- **R2 — helper `\b` + 소유격 경계**: SID 는 digit 으로 끝나고 `'` 은 non-word → `\bC08\b` 후 `(?:'s)?` 정상. composite `C08O02` 도 digit 종료. W1 G1/G3 가 명시 검증.
- **OQ-잔여**: 없음 — Codex C3 entry sanity 4개 질문 모두 resolve (§3, §1.3).

## 9. Doctrine references

- [[feedback_llm_based_judgment]] — 4-Gate. C3 = Semantic Regex Ban 적용 (semantic `"the figure's"` 삽입 폐기), helper regex 는 closed-world SID literal set only.
- [[feedback_no_vlm_dependency]] — NO VLM. C3 코드·테스트 VLM 의존 0.
- [[feedback_codex_mcp_discussion_workflow]] / [[feedback_codex_mcp_claude_mcp_response_trigger]] — 매 wave Codex MCP per-wave review, tmux-bridge raw typing + prefix/suffix.
- [[feedback_no_user_ask_codex_only]] — OQ·결정 사용자 ask 0, Codex MCP 의논.
- [[feedback_channel_separation_4way]] — 4채널 fact source 구분.
- [[project_fix_critical_1_persistent]] — fix-critical-1 Tier α #3, C3 → C7 priority.

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

### 10.1 W1-W2 commit chain
- W1 `4740b03` — `_strip_invalid_sids()` module-level helper 신설 + VE-violation 강제 제거 2 site (t2i_prompt `pure_remove` / representative_moment `bad_in_sfp`) token-complete helper 호출 교체 + cleanup loop `:2872-2882` 폐기 + `test_c3_detail_steps_focus_rewrite.py` G1-G7 신설 (3 file: detail_steps.py modify + test_c3 new + test_scene_detail_analyze_one.py modify — W1 plan-gap absorb).
- W2 closure commit — closure docs atomic (5 docs: spec/plan status closed + roadmap §5.15 + fix-critical-1 index.html C3 marker + audit 01 §7 detail_steps row 정정).

### 10.2 Gate result
- W1.1 helper import PASS / W1.2 C3 test G1-G7 7/7 PASS / W1.3 detail_steps·scene_detail 201 passed 0 failed / W1.4 cleanup 폐기 assertion PASS (cleanup 주석 0 / semantic literal 0 / `_strip_invalid_sids` count 3) / W1.5 백엔드 4043 passed / 5 failed (전부 pre-existing — clean HEAD `fd9c4e3` git stash 대조 + C2 closure 5 pre-existing 기록 일치).

### 10.3 Codex review trace
- entry sanity `APPROVED_FOR_C3_SPEC_DRAFTING_WITH_NARROW_SCOPE_GUARDS` (본 session 2회 일치) → spec review `NEEDS_REVISION_NARROW` (2 IMPORTANT + 1 MINOR) → amend 1 → re-review `APPROVED_FOR_PLAN` → plan review `NEEDS_REVISION_NARROW` (3 IMPORTANT) → amend 1 → re-review `APPROVED_FOR_EXECUTION` → W1 plan-gap 의논 `APPROVED_W1_SCOPE_EXPANSION_WITH_TEST_REPURPOSE` → W1 per-wave `APPROVED_FOR_W2_ENTRY` → W2 audit-01 marker 의논 `APPROVED_FOR_AUDIT_01_SECTION_7_MARKER_WITH_DOC_REF_CORRECTION` → W1-W2 range review.

### 10.4 함정 / lesson learned
- **N-1**: spec/plan helper docstring 의 `the figure's` literal 이 G5 source canary 자기-trip — docstring wording 을 "semantic subject phrase" 로 교체.
- **N-2**: gate bare `python` → system python 은 fastapi 등 미설치라 app import fail — `PYTHONPATH=backend backend/.venv/bin/python|pytest` 의무 (C1/C2 N-exec 정합).
- **N-3**: Gate `grep -c` 는 0-hit 시 exit 1 → `set -e`/automation 에서 false-fail — python `assert` heredoc 으로 대체.
- **N-4**: W1.4 cleanup loop 폐기가 stale test `test_analyze_one_cleans_double_spaces` (무조건 연속-공백 collapse 검증) 1건 regression 유발 = §8 R1 materialize. plan-gap absorb — option (b) `test_analyze_one_preserves_valid_entity_prompt_spacing` 로 repurpose (delete 아님, Track B no-blind-mutation integration guard). 나머지 5 failure 는 git stash clean HEAD 대조로 pre-existing 확정.
- **N-5**: audit 01 #10 (`t2i_review` blind `.replace()`) ≠ C3 site. C3 의 detail_steps cleanup site 는 audit 01 §7 "Excluded — Closed ID syntax cleanup" detail_steps row. closure marker 를 #10 아닌 §7 에 배치, spec/plan/index.html 의 "audit 01 #10" 참조 전부 정정 (false source mapping 방지).

### 10.5 carry priority post-C3
C7 #12 shot_dependency_t2i drift (다음 진입) → C6 #10 color_palette_intent → C9 L-7 body-light verb → C4 #8 background classifier → C5 #9 action segment SOT → C8 #7 잔여 sub-area split → C10 future structured carries.
