# GROUNDING-V2 전수 감사 — 근거 문서

> 계약 = `2026-08-29-grounding-v2-contract.md` · 계획 = `2026-08-29-grounding-v2-plan.md`
>
> ★**이 감사의 산출은 「충돌 목록」이 아니라 「어디를 얼마나 느슨하게 할지」**다
> (사용자 지시). 그리고 **감사 완료가 최종 설계 승인의 전제**다.
>
> 각 행에 열 칸을 채운다 — 계획서 §3 참조.
> **모든 인용은 실물 파일:줄이다. 기억으로 적지 않는다.**

## 활성 팩 (실측 · `prompt_loader` 가 module latest 를 자동 선택)

| module | active |
|---|---|
| `entity_character_list` | `2.202605011057` |
| `entity_all` | `7.202607101540` |
| `entity_extract_v4` | `10.202607021935` |
| `entity_filter` | `3.202605201316` |
| `entity_extractor_v2` | `14.202606181515` |
| `outlook_extractor` | `14.202607190154` |

---

## ① 인물 목록 — `entity_character_list` ★**가장 앞이고 가장 세다**

**충돌 문구** (`prompts/_base/entity_character_list/2.202605011057/system.md`):

```
「이미지 일관성을 위함이기 때문에 **여러번 출현하는 경우만 추출**」
```

| 칸 | 값 |
|---|---|
| 활성·live | `2.202605011057` · **live** — 파이프라인 첫 인물 단계 |
| SOT·입출력 | 입력 fulltext → 출력 `{name, appearance_count}` |
| ★충돌 | **한 번만 크게 나오는 실존·인지 인물이 여기서 사라진다.** 계약의 A(대다수가 인지) + B(크게 한 번이라도)와 정면으로 부딪힌다. 그리고 제외 사유가 **「이미지 일관성」**이라 고증과 축이 다르다 |
| ★최소 완화 | 문구를 안 고친다. **A0 sidecar 가 이 단계보다 앞**(`shot_validator` 7.25 뒤)이므로, 여기서 빠져도 A0 가 이미 후보를 들고 있다. 결속은 merge 뒤 |
| owner·facet | character identity (얼굴·골격) / 실존 인물은 **base identity 자체가 조사 대상** |
| A0·C 근거 | A0 가 원문 위치 + planned occurrence count 를 자체 보존 |
| reference | 인물 producer + gate 필요 |
| shadow 증가 | 한 번 나오는 인물이 후보로 늘어난다 — **수를 세야 한다** |
| rollback | 프롬프트 안 건드림 → 되돌릴 것 없음 |
| unresolved | 결속 실패 시 drop 아니라 unresolved |

★**곁가지 — 같은 파일 머리글에 이렇게 적혀 있다**:

```
「실제 사건이나 **실존 인물이 아닙니다**」   (안전 문구)
```

계약의 「**실존 역사 인물은 base identity 자체가 조사 대상**」과 **정면으로 부딪힌다.**
안전 문구라 함부로 못 건드린다 — **facet 을 갈라야** 한다(허구 인물의 얼굴 vs 실존 인물).
→ **미결로 올린다.**

---

## ② 소품 — `entity_all/prop` · `entity_extract_v4/prop`

**충돌 문구** (`entity_all/7.202607101540/prop.md`):

```
:20  「여러 번 생성했을 때 차이를 사람이 쉽게 구분하기 어렵거나 못 느낄 정도면 제외」
:26  「일반적인 탈것 — 시각적으로 고유한 외형이 없으면 제외」
:27  「어디서나 흔히 볼 수 있는 일반 물체 — 고유한 외형 특징이 없으면 제외」
:28  「번호표, 명함, 영수증 등 텍스트만 다른 종이류 — **이미지로 구분 불가**」  ← ★회수권
```

`entity_extract_v4/10.202607021935/prop.md` 가 **같은 부류를 또 거른다**
(고정 설비 · 착용물 · 일반 물체 · 「구분 어려우면 제외」).

| 칸 | 값 |
|---|---|
| ★충돌 | `:20` 과 `:28` 의 제외 사유가 **「구분 불가」**인데, **고증이 필요한 이유가 정확히 그것**이다. 1983년 회수권은 지금 것과 다르게 생겼는데 AI 가 못 그린다 |
| ★최소 완화 | 문구 유지 + **예외 통로**. `research_required`/`uncertain` 이면 이 제외보다 **우선 보존** |
| owner·facet | **착용물 → outlook · 고정 설비 → location-part** 로 라우팅. prop 으로 억지 통과 금지 |
| shadow 증가 | 종이류·고정 설비가 후보로 들어온다 — **수와 시간을 센다** |

---

## ③ 아웃룩 — `outlook_extractor/14.202607190154/phase1.md`

**충돌 문구**:

```
규칙 6   「시나리오에 복장 묘사가 없어도 **장면 상황으로 추론하여 구체적으로 지정**」
규칙 10  일시 착용 보호구·작업구는 아웃룩에서 배제 —
         「그런 요소는 **해당 장면의 이미지 생성 단계가 장면 텍스트로 직접 처리**」
규칙 11  「**2씬 이상** 등장 인물은 반드시 최소 1개 아웃룩」
```

| 칸 | 값 |
|---|---|
| ★충돌 6 | **근거 없이 의상을 구체화한다.** 계약이 막으려는 바로 그 환각이다. 1983 차장 제복을 「추론」으로 지으면 조사가 무의미해진다 |
| ★충돌 10 | 일시 착용 장비를 **이미지 단계로 미는데 거기엔 조사가 없다.** 시대 작업구가 근거 없이 그려진다 |
| ★충돌 11 | **2씬 미만 인물은 아웃룩이 없다** → 한 번 나오는 제복 인물이 빠진다 |
| ★최소 완화 | 규칙 6 에 **조건**을 붙인다 — 「단, `research_required` 로 표시된 복장은 **추론하지 말고 조사 결과를 기다린다**」. 규칙 10·11 은 **예외 통로**(고증 대상이면 아웃룩/변형으로 보존) |
| owner·facet | **outlook / visual variant** — 인물 얼굴과 갈라야 한다 |
| rollback | ★프롬프트를 건드리므로 **v2 전용 module namespace 필수** |

---

## ④ 저빈도 필터 — `EntityFilterStep` · `entity_filter.py`

**실물**:

```
entity_steps.py:353   entity_all 에서 shot_count 복원
entity_steps.py:363~  entity_relation 의 visual_similarity 를 protected_short_ids 로
entity_filter.py:31   if sid and sid in _protected: continue        ← ★보호 통로 (이미 있다)
entity_filter.py:34   count = shot_count or scene_count or len(appearances)   ← ★or 사슬
entity_steps.py:381   max_scenes=2
```

| 칸 | 값 |
|---|---|
| 충돌 | count < 2 면 LLM 에게 물어 remove 가능. **자동 삭제는 아니다** |
| ★최소 완화 | **새 기구 없음** — `protected_short_ids` 에 research 확정 후보를 **union** |
| ★결함 | `or` 사슬 — `shot_count` 가 **0이면 falsy 라 넘어간다**. **별도 선행 PR** 로 고친다(계획서 §1.6) |

---

---

## ~~★★가장 큰 발견 — 파이프라인 앞뒤가 정반대로 말한다~~ → **내가 잘못 읽었다** (Codex, 2026-08-30)

원래 여기에 「앞은 빼라, 뒤는 지켜라 — 앞을 뒤 문구에 맞추면 된다」고 적었다.
**틀렸다.** 두 문구를 나란히 놓으면 **같은 규칙을 양쪽에서 말한 것**이다:

```
entity_all/7/prop.md:20        제외: 여러 번 생성해도 차이를 **못 느낄 정도면**   (= 안 변하면 뺀다)
entity_filter/3/system.md:8    유지: 여러 번 생성했을 때 **매번 다르게 보이기 쉬운** 것  (= 변하면 지킨다)
```

**「안 변하면 뺀다」와 「변하면 지킨다」는 상보적이다.** 정반대가 아니다.
따라서 **「앞 단계를 뒤 문구에 맞추면 된다」는 수리안은 성립하지 않는다** — 맞춰 봐야
같은 말이 된다.

### 그럼 회수권은 왜 죽나 — 진짜 충돌 셋

| | 무엇 |
|---|---|
| **① 범주 일괄 제외** | `entity_all/7/prop.md:28` 「번호표·명함·영수증 등 **텍스트만 다른 종이류** — 이미지로 구분 불가」. 생성 분산과 **무관하게 범주로** 자른다. 회수권이 정확히 여기 |
| **② 1회성 제외** | `entity_character_list` 「여러 번 출현하는 경우만」 · `entity_filter` 저빈도 · `episode_reference_policy` 선택 샷 2회 |
| **③ 순서** | 셋 다 **externally grounded 인지 판정하기 전에** 지운다. 판정할 대상이 이미 없다 |

★그리고 ①의 「이미지로 구분 불가」는 **모델 성능** 얘기지 **역사** 얘기가 아니다 —
계약 §2 의 「글자만 비슷하고 정반대」가 여기에 걸린다. 1983 회수권과 오늘 번호표는
**인쇄·재질·규격이 실제로 다르다.**

## ★두 번째 — 「2씬 이상」이 **세 겹**이다

```
entity_character_list       「**여러번 출현하는 경우만** 추출」          (인물)
entity_extract_v4/prop:16   「**2개 이상의 서로 다른 씬**에서 …」        (소품)
entity_filter               「제거: 시각적 출현이 **1씬뿐**인 것」        (전부)
outlook_extractor phase1:11 「**2씬 이상** 등장 인물은 반드시 …」        (아웃룩)
EntityFilterStep            `max_scenes=2`                              (코드)
```

**한 번 나오는 것은 네 군데에서 걸린다.** 계약의 B(「크게 **한 번이라도**」)와 정면으로 부딪힌다.

## ★세 번째 — 착용물·고정 설비 제외가 **네 겹**

`entity_all/prop` · `entity_extract_v4/prop` · `entity_filter` · (그리고 reference gate).
같은 문구가 네 곳에 복사돼 있어 **한 곳만 고치면 나머지가 되돌린다.**

## ★네 번째 — 복장 소유가 **둘로 갈려 있다**

```
entity_extract_v4/character.md   description 에 「**기본 복장**」을 넣으라 한다
outlook_extractor phase1         복장은 **아웃룩**이 소유한다
```
인물 description 과 아웃룩이 **같은 것을 두 번** 적는다.
고증이 복장에 붙으면 **어느 쪽을 고쳐야 하는지 모호하다** → facet 소유를 못박아야 한다.

## ★다섯 번째 — 장소는 오히려 **관대하다**

```
entity_all/location.md        「**1개 씬에만 등장해도 포함**합니다」
entity_extract_v4/location.md 같은 문구
```
장소는 제외 규칙이 없다. **완화가 필요 없다** — 대신 `entity_filter` 의
「1씬뿐이면 제거」와 부딪히는지 확인이 필요하다.

---

---

## ★★★가장 중요한 발견 — **필요한 계약이 이미 있다**

`entity_extractor_v2/14.202606181515/turn1_7_detail_batch.md` 에
**evidence-gated 저작 계약**이 이미 서 있다:

```
distinctive_visual_traits (evidence-gated)
  source_quote        「본문에서 그 특징을 **직접 묘사한 부분을 그대로 인용**.
                       본문에 없으면 이 항목을 **만들지 않는다**
                       (빈 인용·**지어낸 인용 금지**)」
  attribution_note    본문이 다른 이름으로 가리켰을 때의 **연결 근거**
  interpretation_kind literal_visual / metaphorical_impression / ambiguous
  「**추론 금지(invention 차단)**」 — 조명·분위기에서 신체색을 옮겨오지 마라
```

★**계약이 요구하는 모양과 같다**:
- **claim + source 결속** → `trait` + `source_quote`
- **3갈래 판정** → `literal / metaphor / ambiguous` (내 `yes/no/uncertain` 과 같은 꼴)
- **근거 없으면 만들지 않는다** → `unresolved` 의 원형
- **다른 이름으로 가리켜도 연결** → provenance

★**다른 점은 하나뿐이다 — 근거의 출처.**
지금은 **시나리오 본문**만 근거로 인정한다. 고증은 **외부 기록**이 근거다.
→ **새 스키마를 발명할 필요가 없다. 같은 모양을 외부 근거로 확장하면 된다.**

## ★★두 번째 — `reference_required` 가 **이미 prop 마다 있다**

`entity_extractor_v2/system.md` · `turn_entity_detail.md`:

```
metadata_json.visual_identity.reference_required   (prop only, boolean)

  True  「visually unique identity that must remain stable across shots,
         so that text description alone is likely to lose important shape,
         markings, **written/printed content**, layout, or distinctive design」
  False 「generic, replaceable, or visually interchangeable」
  ★「**Do not infer from a fixed object category list.** Judge the specific prop」
```

★**「written/printed content 를 텍스트 설명만으로는 잃는다」 — 회수권이 정확히 이것이다.**
그리고 **「고정 목록으로 추론하지 마라」**까지 이미 적혀 있다.

→ 계약의 `reference_required` 축은 **새로 만들 것이 아니라 이미 있는 칸**이다.
 문제는 이 칸이 **prop 에만** 있고 **character/outlook/location 에는 없다**는 것.

## ★세 번째 — 「1회성 제외」가 **다섯 겹**이 됐다

```
entity_extractor_v2/system.md   「**1회성 등장 요소 제외**」     ← 새로 발견
entity_character_list           「여러번 출현하는 경우만」
entity_extract_v4/prop:16       「2개 이상의 서로 다른 씬」
entity_filter                   「1씬뿐이면 제거」
outlook phase1:11               「2씬 이상 인물은 반드시」
EntityFilterStep                max_scenes=2
```

## ★네 번째 — 복장 소유가 **사실은 갈려 있다** (앞선 내 지적 정정)

```
turn_entity_detail.md  인물 T2I: 「**의상/복장/바디 묘사 절대 금지** (아웃룩은 별도 관리)」
turn1_7_detail_batch   「인물: 얼굴과 머리 특징만. **의상/복장/바디 묘사 절대 금지**」
```
★**T2I·상세 단계에서는 이미 갈려 있다.** 앞서 본 `entity_extract_v4/character.md`
의 「기본 복장」은 **더 앞 단계**의 것이고 하류에서 덮인다.
→ **소유는 이미 outlook 이다.** 고증도 outlook facet 에 붙이면 된다.

## ★다섯 번째 — 아웃룩 phase2 **안에서 규칙이 부딪힌다**

```
규칙 5    「**해당 캐릭터에게만** 그 아웃룩을 배정할 것」
중요 규칙 「카탈로그에 해당 인물의 아웃룩이 없으면,
          **가장 유사한 다른 인물의 아웃룩을 배정**하세요」   ← ★정면 충돌
```
후자가 **남의 옷을 입히는 경로**다. 고증한 제복이 엉뚱한 인물에게 갈 수 있다.

---

## ⑤ 세계관 — `entity_extractor_v2/turn0_style.md` ★**고증 입력이 이미 여기 있다**

```
era             「시대 배경 — 시나리오 본문에서 드러나는 시간적 배경을 **그대로 기술**」
region          「지역 — 본문 근거대로」
clothing_style  「의상 스타일 — 등장인물들의 복장 양식」
vehicle_style   「탈것/기계 스타일」
building_style  「건물/공간 스타일」
must_avoid      「이 세계관에 맞지 않는 것 (한 줄)」        ← ★「금지 사실」 칸이 이미 있다
```

| 칸 | 값 |
|---|---|
| ★발견 | 고증 분류기가 필요로 하는 입력(**시대·지역·복장 양식·탈것 양식**)이 **이미 산출된다.** `must_avoid` 는 계약의 **「무엇은 아니다」**와 같은 자리 |
| 최소 완화 | **없음** — 그대로 소비하면 된다. 다만 `must_avoid` 가 **한 줄**이라 claim 목록으로 확장이 필요 |
| 충돌 | 없음. **오히려 재료다** |

## ⑥ 나머지 추출 turn — `turn1` ~ `turn4`

```
turn1  「인물: 주요 등장인물 (**1회성 엑스트라 제외**)」
       「배경: **반복 등장하는** 주요 장소」
       「물체: 핵심 소품/장비 (**일반적인 물건 제외**)」
turn2  변형은 「매우 제한적으로만」 · 「의복만 바뀌는 경우 금지 — 씬 프롬프트로 처리」
turn4  「**일반적인 물건은 제외**하고 장면의 핵심 시각 정보인 것만」
```

★**「1회성 제외」가 여기서 또 나온다.** `turn1` 이 배경까지 「반복 등장하는」으로 좁힌다 —
`entity_all/location` 의 「1개 씬에만 등장해도 포함」과 **정반대**다.

| 칸 | 값 |
|---|---|
| ★충돌 | 같은 저장소 안에서 장소 규칙이 **정반대로 두 벌**이다 (`entity_all/location` vs `turn1`) |
| 최소 완화 | v2 는 `entity_all` 계열을 쓰므로 `turn1` 은 **legacy 경로**로 보인다 — **live/dead 확인 필요** |

## ⑦ 참조 이미지 프롬프트 — ★**내가 죽은 팩을 감사했다** (Codex, 2026-08-30)

원래 여기에 `reference_image/v2` 를 적었다. **비활성이다.**
실제 live 는 `ref_image_pipeline._load_ref_image_prompt` →
**`prompts/_base/ref_image_prompts/5.202603311724`** 이고 stem 이 **여섯** 이다.

전문을 다 읽었다 (합쳐 31줄):

| stem | 무엇을 지시하나 | 변수 |
|---|---|---|
| `prop_ref.md` (6줄) | Isolated object · product photography · **「Object fills the frame」** · 착용물이면 회색 실루엣 신체부위 최소 포함 | `{entity_description}` |
| `character_ref.md` (2줄) | 여권 사진식 정면 상반신 · 흰 배경 | `{entity_description}` |
| `character_nonhuman_ref.md` (2줄) | 전신 · 흰 배경 · 형태/질감/특징 | `{entity_description}` |
| `character_outlook_ref.md` (7줄) | **회색 마네킹에 입힌 전신 의상** · 옷과 액세서리만 사실적으로 | `{outlook_description}` |
| `character_composite_ref.md` (12줄) | 참조1=얼굴 고정, 참조2+=의상 조각을 입혀 합성 | (참조 이미지) |
| `location_ref.md` (2줄) | 사람·차량·이동물 없는 establishing shot | `{entity_description}` |

| 칸 | 값 |
|---|---|
| 발견 | 여섯 전부 **스타일 지시뿐**이다. `시대`·`era`·`period` 가 **0건** |
| 변수 | `{entity_description}` / `{outlook_description}` **하나뿐** — 조사 claim 이 들어갈 칸이 **구조적으로 없다** |
| ★★큰 것 | `prop_ref.md:1` 「**Object fills the frame**」 — **참조 층에는 40% 제한이 없다.** 인쇄면·표장을 알아볼 크기로 이미 그릴 수 있다 → **고증 개선을 참조 층에서 지금 잴 수 있다** (계획 §2-6 순환 해소) |
| 아웃룩 | `character_outlook_ref.md` 가 **이미 있다** — 아웃룩 참조를 못 굽는 것은 **프롬프트가 없어서가 아니라 producer/gate 가 타입으로 뺐기 때문**이다 |
| 최소 완화 | v2 전용 `ref_image_prompts` 버전 + **claims / 금지 claims / 근거 이미지** 슬롯 추가. 검증기도 같은 evidence 로 본다 |

## ⑧ 배경 분류 — `background_classify/4`

```
:39  「Do **not** invent a building group from thin air — when in doubt, leave a location as a solo group」
:48  「`anchor_loc` MUST be one of the group's member `loc_id` **verbatim** — never invent a new label」
```

| 칸 | 값 |
|---|---|
| ★발견 | **「지어내지 마라」가 이미 두 줄 있다.** 계약의 invention 차단과 같은 결 |
| 충돌 | 없음 — **본받을 문구다** |

## ⑨ 참조 필요성 정책 — `episode_reference_policy` ★**네 번째 저빈도 관문**

프롬프트 module 이 **없다** — 코드에만 있다(`LLM 없음, deterministic`).

```
app/core/episode_reference_policy.py:11-13
  character / nonhuman (C##):
      **visible_shot_count >= 2** 또는 variant pole  →  reference_required
      아니면                                          →  **text_only**
  prop (P##):
      provisional — reference_candidate / text_only

:67  「visible_shot_count={vsc} — **selected-shot 저빈도**」
```

| 칸 | 값 |
|---|---|
| 활성·live | live · `scene_detail` **직전**. LLM 없이 결정 |
| SOT·입출력 | `visible_shot_count` = **selected-shot 등장 횟수** |
| ★충돌 | ★**여기는 planned 가 아니라 `selected` shot 기준이다.** 계약이 C 를 planned `shot_count` 로 닫았는데, **이 관문만 selected 를 쓴다.** 한 번 나오는 고증 대상이 `text_only` 로 강등되어 **참조가 아예 안 만들어진다** |
| ★최소 완화 | `research_required` 면 **`visible_shot_count` 와 무관하게 `reference_required`** (계약의 「research → reference 강제」가 정확히 이 자리에 꽂힌다) |
| owner·facet | character / prop 각각 갈래가 이미 나뉘어 있다 — **outlook·location 은 없다** |
| ★주의 | `compute_visible_shot_count_from_checkpoints` 주석: 「그 shot 의 visible 을 못 세면 undercount 되어 **recurring character 가 text_only 로 잘못 강등될 수 있다**」 → **이미 알려진 취약점** |
| rollback | 코드만 — 프롬프트 없음 |

---

## ⑩ 병합 — `EntityMergeStep` (`entity_steps.py:464`)

```
하는 일   「타입이 다르지만 **실질적으로 같은 대상**」만 제거
          (예: 같은 물체가 location 과 prop 에 동시 등록)
입력      extract 셋의 `short_id / name / description`
세계관    era · region 을 visual_world_rules 에서 **이미 읽는다** (:485-486)
출력      제거할 short_id 목록
```

| 칸 | 값 |
|---|---|
| ★발견 | **범위가 좁다** — cross-type 중복만. **DB canon 을 만들지 않는다**(Codex 확인과 일치) |
| ★발견 2 | **`era` · `region` 을 이미 여기서 읽는다.** 분류기가 필요로 하는 세계관 입력이 **이 자리에 이미 와 있다** |
| ★충돌 | LLM 입력줄이 `short_id name (type): description` 뿐 — **`shot_count` 가 안 실린다.** 그래서 `EntityFilterStep:353` 이 「extract/merge 에서 유실됨」이라 적고 복원한다 |
| 최소 완화 | **없음** — 손댈 필요 없다. A0 sidecar 가 별도로 운반한다 |

## ⑪ 수동 수정 — `PATCH /entities/{entity_id}` (`entities.py:226`)

```
사람이 EntityCanon 의 name / description / t2i_prompt 를 **직접 고칠 수 있다**
before/after 를 남기고 활동 기록에 적는다
```

| 칸 | 값 |
|---|---|
| ★충돌 | 계약이 「v2 는 canon 을 **덮지 않고** revision 에 쓴다」인데, **사람은 canon 을 직접 고친다.** 그러면 **사람 수정과 v2 revision 중 무엇이 이기는가**가 정해져 있지 않다 |
| ★최소 완화 | 중앙 resolver 의 **우선순위를 명시**한다 — 사람 수정 > v2 revision > legacy canon 이 자연스럽다. 다만 **사람이 고친 뒤 v2 가 다시 조사하면** 그 수정이 묻히지 않아야 한다 |
| ★미결 | 사람 수정에 **provenance 표시**가 필요하다(누가·언제·무엇을). 지금은 활동 기록에만 남고 canon 자체엔 표시가 없다 |

## ⑫ 지문 · resume

| 칸 | 값 |
|---|---|
| 확인된 것 | `SAFE_DOWNLOAD_POLICY_VERSION` 을 `config_hash` 에 편입하는 관례가 **이미 있다**(`search_grounded_ref.py`) — v2 도 같은 결로 간다 |
| ★계약 | `shadow_plan` 은 **shadow CP 지문에만**. v2 하류엔 **mode 문자열이 아니라 소비한 research revision/content hash** |
| ★rollback | prompt **source path + content hash** 를 acceptance 에 포함 |
| 미결 | 각 층(classifier/text/image) 캐시 키의 실제 구성은 **구현 시 확정** |

---

## ★감사 완료 — 남은 미결

| # | 미결 | 닫는 자리 |
|---|---|---|
| 1 | `entity_character_list` 안전 문구(「실존 인물이 아닙니다」) vs 「실존 인물은 조사 대상」 | facet 분리로 — 설계 gate |
| 2 | 아웃룩 phase2 자체 모순(「해당 인물만」 vs 「남의 옷 배정」) | **별도 결함** — grounding 밖에서 고칠 것 |
| 3 | ~~장소 규칙 두 벌 — live/dead 확인 필요~~ → ★**닫음: `turn1` 은 dead** (Codex · 실측). 호출부가 `analysis_steps_legacy.py:66` · caller 없는 `entity_extractor_v3.py:263` · `entity_extractor_v2_legacy.py:355` 뿐이다. 현행 `EntityDetailStep` 은 `system`+`turn1_7_detail_batch`, `EntityT2iStep` 은 `system`+`turn_entity_detail` 만 쓴다. **저장소 문구 차이일 뿐 live chain 의 이중 규칙이 아니다** | **닫음** |
| 4 | **사람 수정 vs v2 revision 우선순위** | resolver 설계 시 |
| 5 | `or` 사슬 (`entity_filter.py:34`) | **선행 PR** |
| 6 | 참조 이미지 프롬프트에 시대 경로 없음 | v2 전용 프롬프트 버전 |

---

## 총평 — 감사가 설계를 어떻게 바꿨나

| | |
|---|---|
| **새로 만들 것이 줄었다** | evidence-gated 저작(`source_quote`·3갈래) · `reference_required` 칸 · 시대/지역/양식 산출 · 「지어내지 마라」 문구 — **다 이미 있다** |
| **문제는 「없어서」가 아니라 「앞에서 지워서」** | 「1회성/저빈도 제외」가 **여섯 겹**(character_list · extractor_v2 · extract_v4 · filter · outlook · reference_policy) |
| **완화의 첫 자리** | ~~앞 단계를 뒤 문구에 맞춘다~~ → **취소** (Codex). 두 문구는 상보적이라 맞춰도 같은 말이다. 실제 자리는 **범주 일괄 제외(`prop.md:28`) · 1회성 제외 · 판정보다 먼저 지우는 순서** 셋 |
| **꽂을 자리가 분명해졌다** | `research_required` → `episode_reference_policy` 의 `visible_shot_count` 판정을 **우회** |
| **못 보던 구멍** | 참조 이미지 프롬프트에 **시대 언급이 아예 없다** — 조사가 거기까지 오는 경로가 없다. 실측: 활성 `ref_image_prompts/5.202603311724/prop_ref.md` 에 `시대`·`era`·`period` **0건** |
| **자체 모순** | 아웃룩 phase2 (「해당 인물만」 vs 「없으면 남의 옷」). ★장소 규칙 두 벌은 **미결이 아니라 dead** 로 닫았다 — 아래 |

### ★총평을 좁힌다 (Codex 요구 · 받아들임)

앞 표의 「새로 만들 것이 줄었다」는 **너무 넓게 읽힌다.** 정확히는:

> 재사용 가능한 **부분 계약**은 이미 있다. 그러나 **외부 고증의 durable claim ·
> 후보 승격 · 사람 override · evidence 기반 reference 생성·검증 · 후속 owner 봉인**은
> **새로 필요하다.** 그리고 문제는 **앞에서 지우는 것에만 있지 않다** —
> **뒤에서 다시 덮거나, 틀린 reference 를 정본으로 굳히는 경로**에도 있다.

---

## 미결 — 감사 중 나온 것

1. ★`entity_character_list` 안전 문구(**「실존 인물이 아닙니다」**)와 계약의
   「실존 역사 인물은 조사 대상」이 **정면으로 부딪힌다.** 안전 문구라 함부로 못 고친다 —
   facet 을 갈라야 한다
2. 아웃룩 규칙 6 을 조건부로 바꾸면 **프롬프트를 건드리므로** v2 전용 namespace 가 필수
3. `or` 사슬은 **grounding 밖 선행 PR**

---

## 부록 — Codex 재분석 9건 실물 대조 (2026-08-30)

Codex 가 활성 경로 프롬프트를 본문·스키마까지 다시 읽고 **BLOCK 9건**을 냈다.
받아 적기 전에 **근거로 든 file:line 을 전부 열어 확인**했다.

| Codex 근거 | 실물 | 판정 |
|---|---|---|
| `entity_all` 이 원문이 아니라 **검증된 샷 description 만** 읽는다 | `entity_lister.py:170` `list_entities_from_shots` — shot description 만 이어붙임 | ✅ |
| shot schema 의 `characters` 가 **필수가 아니다** | `shot_extract/10*/shot_schema.json` `required=["shot_index","description","based_on_beat"]` | ✅ |
| `framing_scale` 은 **shot_staging(19.5)** 에서 처음 정해진다 | `shot_staging/22.202608130110/system.md:67` | ✅ (Codex 가 적은 팩 시각 `…251558` 은 없음 · 실제 `…130110`) |
| 프레임 점유 제한이 작은 소품을 막는다 | 같은 파일 `:264` 「단일 비인간 물체 40% 이상 금지」 | ✅ |
| `episode_reference_policy` 가 선택 샷 2회 미만을 강등 | `:61`(character) · `:80`(prop) — **prop 도 같은 문턱** | ✅ |
| ref 생성이 일반 템플릿에 description 만 넣는다 | `ref_image_pipeline.py:322` `_load_ref_image_prompt` | ✅ |
| `prompt_used` 가 실제 보낸 prompt 가 아니다 | `reference_phase1_service.py:174` `"prompt_used": t2i` | ✅ (줄은 :169 아니라 **:174**) |
| VCA 가 소품 물리형태를 다시 쓰고 canon 보다 앞선다 | `visual_continuity_anchor_plan.py:638` `build_prop_ref_prompt_overlay` | ✅ 단 **canon 을 덮지는 않는다** — docstring 이 「canon 자체는 절대 변경하지 않는다 — 그것이 provenance」. **ref/detail prompt 안에서의 우선순위**다 |
| `prompt_loader` 가 팩이 아니라 **stem 단위** 최신 | `prompt_loader.py` 머리글 = `problems.md #6`. **이미 `PROMPT_VERSION_PACK_STRICT` opt-in 이 있다** | ✅ |

`printed_content` 의 저작 자리도 확인했다 — `visual_continuity_anchor_step.py:553,584`
가 만들고 `detail_steps.py:3226,3239` 가 소비한다. **회수권 인쇄면이 바로 이 칸**이라
grounding 이 소유해야 하는 자리가 맞다.

### 반영한 곳

| Codex BLOCK | 반영 |
|---|---|
| ① A0 sidecar → **후보 승격 overlay** | 계획 §1.8 |
| ② 후보를 버리는 **네 자리** 전부 | 계획 §1.8 표 |
| ③ B → **visibility_intent + 사후 확인** | 계약 §2 |
| ④ outlook·background·scene_detail·VCA **owner 제한** | **계약 §13**(신설) |
| ⑤ `research_required` 가 빈도 규칙보다 우선 | 계약 §6 |
| ⑥ `location_part`·`outlook facet` owner | 계약 §6 · 계획 §5-8 |
| ⑦ claims/evidence 기반 reference | 계약 §6 · 계획 §2-5.5 (★대상 프롬프트는 **활성 `ref_image_prompts/5*`** — 부록 2 ⑥) |
| ⑧ 실제 provider prompt + revision 저장 | 계약 §6 · 계획 §4-14 |
| ⑨ 인라인 프롬프트도 지문에 | 계획 §4-13 |

### 그대로 안 받은 것 — 되묻는다

1. **⑥ producer 구현 시점.** 소유 모델(base location 을 prop 으로 우회 등록 금지)은
   계약에서 닫았지만, `location_part`·`outlook facet` **producer 를 지금 짓는 것은
   범위가 크다.** 확인된 결함(요금통·회수권)은 prop/character owner 라 여기서 막히지
   않는다 → **§2-5.5 뒤 별도 단계**로 밀었다. 이견 있으면 알려 달라.
2. **⑧ 저장 방식.** `prompt_used` 의 뜻을 바꾸면 rollback acceptance 의
   baseline 비교가 함께 흔들린다 → **새 칸 additive** 로 넣었다.
3. **프레임 점유 완화.** 화면 구도를 바꾸는 변경이라 자동으로 켜지 않고
   **§2-6 눈가림 A/B 뒤** gate 로 뒀다.
4. **④ 적용 범위.** 저작 금지를 전역으로 걸면 generic 소품이 전부 `unresolved` 로
   서서 파이프라인이 멈춘다 → **`research_required` 대상에만** 건다.

---

## 부록 2 — Codex 통합 재리뷰 **BLOCK 8건** 반영 (2026-08-30)

PR #59 inline 8건. 사실 주장을 **전부 열어 확인했고 8/8 맞다.**
★그중 ⑦은 **내 분석이 틀린 것**이고, ⑥은 **내가 죽은 팩을 감사한 것**이다.

| # | Codex BLOCK | 확인한 실물 | 반영 |
|---|---|---|---|
| 1 | 사람 override 가 resolver 문구만으로 성립 안 함 | `entities.py:226` 이 표식 없이 canon 변경 · `entity_sync_service.py:170-174` 가 다음 sync 에서 무조건 덮음 · `existing` 을 **이름으로** 찾음(rename → 새 canon) | 계약 §11 재작성 — append-only field override + alias/identity key 분리 + 끝점 시험 넷 · 계획 §5-11 |
| 2 | owner gate 를 `research_required` 에만 걸면 uncertain·unresolved 가 샌다 | 계약 §3 이 이미 「모른다를 아니다로 닫지 않는다」인데 §13 에서 내가 어겼다 | 계약 §13 — `grounding_controlled` = research_required ∪ uncertain ∪ unresolved |
| 3 | `PROMPT_VERSION_PACK_STRICT` 는 namespace 별이 아니라 **process-global** | `_is_version_pack_strict()` (`prompt_loader.py:273-288`) **인자 없음**, ENV → settings | 계획 §4-6 — strict 안 취소, v2 호출부가 version pin + pack completeness/content hash 검증 · §5-12 |
| 4 | 프레임 완화를 최종 A/B 뒤로 두면 **순환** | ★`ref_image_prompts/5*/prop_ref.md:1` 「**Object fills the frame**」 — **참조 층엔 40% 제한이 없다** | 계약 §2 — ①참조 층 A/B 먼저 ②최종 스틸 3-arm · 계획 §2-6 · §5-9 |
| 5 | 「5.5 뒤 별도 단계」만 쓰면 `/goal` 이 둘 없이 끝난다 | — | 계획 §2-6.5 **명시 단계 + acceptance** 신설 · 5단계를 「**prop/character 한정 canary**」로 개명 · full V2 완료 = 6.5 뒤 |
| 6 | 감사 §⑦ 이 **비활성** `reference_image/v2` 를 봤다 | active 는 `ref_image_pipeline._load_ref_image_prompt` → `ref_image_prompts/5.202603311724` **6 stem** | 감사 §⑦ **통째로 재작성** (6 stem 전문 31줄 다 읽음) |
| 7 | 「앞뒤가 정반대」 분석이 **틀렸다** | `entity_all/prop:20`(안 변하면 뺀다) 과 `entity_filter:8`(변하면 지킨다)은 **상보적** | 감사 「가장 큰 발견」 절 **철회·재작성** — 진짜 충돌은 ①범주 일괄 제외 ②1회성 제외 ③판정보다 먼저 지우는 순서 |
| 8 | 장소 규칙 두 벌 미결은 **닫을 수 있다** | `turn1` 호출부가 `analysis_steps_legacy.py:66` · caller 없는 `entity_extractor_v3.py:263` · `entity_extractor_v2_legacy.py:355` 뿐. active 는 `system`+`turn1_7_detail_batch` / `system`+`turn_entity_detail` | 미결 3번 **닫음 — dead** |

### 되물은 넷에 대한 Codex 답 — 전부 받았다

| 되물음 | 답 | 반영 |
|---|---|---|
| location/outlook producer 지금 짓나 | 보류 가능, 단 **명시 단계 + full-V2 완료 gate 필수** | §2-6.5 |
| `prompt_used` additive 새 칸 | 승인. ★**sanitizer/재생성까지 거친 최종** provider prompt + revision hash | 계약 §6 |
| 40% 완화 자동 ON 반대 | 동의. 단 **최종 A/B 뒤가 아니라 독립 3-arm/staging A/B** 에서 판정 | 계약 §2 |
| owner 금지를 generic 제외 | 동의. 단 **uncertain/unresolved 포함** | 계약 §13 |

★Codex 는 GitHub 계정이 PR 작성자라 `REQUEST_CHANGES` 가 422 로 거부돼
**COMMENT review 로 등록**했다 — 판정 자체는 **BLOCK** 이다.
