# Reference-Necessity Task 14 — Generic Descriptor Fix Wave (addendum)

- **Parent plan**: `docs/superpowers/plans/2026-05-23-reference-necessity-phase012-implementation.md` (v3).
- **Status**: open. branch `feat/reference-necessity-phase012` HEAD `ab57aa8` (unpushed).
- **Trigger**: Task 14 fresh full E2E (project `6cb862d9-590c-4dce-86e6-d10c2977db19` / episode `08ad2cd3-3e96-4d84-808f-869ee628473c`) stopped at step 38/47 with `scene_detail` `AppError` after retry — S1_Shot1 (subject `C06`) + S1_Shot4 (subject `C12`) both `policy='generic_descriptor_allowed' forbids outlook C##O## but prompt uses outlook form`.
- **Codex verdict**: Task 14 acceptance fail. Validator is correct; Phase 2 surfaced an integration gap where prompt/card don’t communicate the new `generic_descriptor_allowed` constraint strongly enough, and the validator itself doesn’t enforce the design’s "no ID at all for text_only" intent (bare `C##` still passes).

## 1. Diagnosis evidence (read-only, captured)

- `episode_reference_policy/manifest.json` (this episode, schema_version 1, 48 entries) correctly marks C06/C12 mode `text_only` with reason `visible_shot_count=1 — selected-shot 저빈도 주변 subject`. C01–C05/C07/C09/C10 `reference_required` (visible≥2), C19/C20 variant-pole `reference_required`. Locations/props all `reference_candidate`/`text_only` (provisional). Phase 2 overlay works as designed.
- `backend/app/core/subject_reference_policy.py:46-55` — `generic_descriptor_allowed = IdUsageRule(base_required=False, outlook_required=False, outlook_forbidden=True)`. **Missing**: `base_forbidden`. Bare `C##` passes today.
- `backend/app/core/visible_entities_validator.py:407-421, 491-523` — per-subject policy loop enforces `outlook_required` / `outlook_forbidden`. No bare-`C##` forbid path.
- `backend/app/core/steps/render_prompt_card.py:3565-3579` — Phase 2 overlay downgrades manifest text_only subjects to `generic_descriptor_allowed`; `2047-2062` skips required_refs for generic subjects. Correct.
- `backend/app/core/steps/render_prompt_card.py:1111-1150` — card `build_id_policy()` constraints still unconditionally say "visible real people with outfit binding use C##O##" and the C##O## template is still strongly present for all visible characters. constraints[3-4] also unconditional. `1562-1565` view_consistency cross-ref only handles part of the case.
- `backend/app/core/steps/detail_steps.py:3270-3299` — existing `canonicalize_base_id_required_outlook_forms()` deterministic repair runs only for `policy == base_id_required`. There is **no** equivalent canonicalization for `generic_descriptor_allowed`, so the old "C##O## hallucinated → C## salvage" path that masked LLM drift in prior E2Es no longer applies once Phase 2 downgrades the subject.
- `backend/app/core/steps/detail_steps.py:1291-1327` — outer retry re-runs the same `_analyze_one` without a corrective prompt; more retries will not help.
- `prompts/_base/scene_detail/33.202605201902/system.md:41-49` weakly states "use generic descriptor if no ID", but `474-510` Rule X-2 strongly mandates "visible character ID 필수" with C##/C##O## template examples that are not policy-conditional.

## 2. Design decision (Codex APPROVED, recorded here)

1. **Validator extends the design contract to "no ID" for `generic_descriptor_allowed`**. Add `IdUsageRule.base_forbidden: bool = False`; `generic_descriptor_allowed` gets `base_forbidden=True`, `outlook_forbidden=True`. Validator orders the two checks: outlook fails first (preserves existing `step.scene_detail.contract_violation.subject_reference_policy.outlook_forbidden` semantics); bare `C##` fails as a **new** code `step.scene_detail.contract_violation.subject_reference_policy.base_id_forbidden`.
2. **Prompt v34 + card builder treat `subject_reference_policy` as the primary id-policy SOT**. C##O## template applies **only** to `id_and_outlook_required`. Bare `C##` applies **only** to `base_id_required`. `generic_descriptor_allowed` is "ID forbidden + descriptor required". Rule X-2 is rewritten as policy-conditional with explicit exceptions.
3. **No deterministic salvage path is added for `generic_descriptor_allowed`**. Stripping `C##O##` → `C##` for text_only subjects would re-introduce the very over-generation Phase 2 aims to prevent. The validator stays fail-closed; the prompt/card must produce ID-free output for these subjects.

## 3. Scope

### In-scope

- **W1 validator** — `backend/app/core/subject_reference_policy.py` (add `base_forbidden` to `IdUsageRule` + set on `generic_descriptor_allowed`). `backend/app/core/visible_entities_validator.py` (new bare-`C##` enforcement branch with new `base_id_forbidden` code). Targeted tests under `backend/tests/unit/test_subject_reference_policy_helper.py` and `backend/tests/core/test_visible_entities_validator.py`.
- **W2 card hardening** — `backend/app/core/steps/render_prompt_card.py::build_id_policy()` constraints around lines 1111-1150 + view_consistency cross-ref `1562-1565`. constraints become policy-conditional (`id_and_outlook_required ⇒ C##O##`; `base_id_required ⇒ bare C## only`; `generic_descriptor_allowed ⇒ no C##/C##O##, descriptor only`). Tests under `backend/tests/core/steps/test_render_prompt_card_episode_policy.py` (extend overlay fixture).
- **W3 prompt v34** — copy `prompts/_base/scene_detail/33.202605201902/` to new dir `prompts/_base/scene_detail/34.202605230753/`, edit `system.md`:
  - Read RenderPromptCard preamble — declare `id_policy.subject_reference_policy` as the per-subject id-policy SOT.
  - ID Policy section — `generic_descriptor_allowed` paragraph promoted above Rule X-2 and made absolute: subject must not use `C##` or `C##O##`; common/demographic descriptor only.
  - Rule X-2 — rewritten as policy-conditional, with explicit exceptions for `generic_descriptor_allowed` (no ID) and explicit `id_and_outlook_required` / `base_id_required` cases. Examples updated.
  - Bump `SCENE_DETAIL_PROMPT_VERSION` constant in `backend/app/core/steps/detail_steps.py` to `34.202605230753`. Prompt-version pin tests under `backend/tests/test_prompt_versions.py` updated. New `backend/tests/prompts/test_scene_detail_id_policy_alignment.py` asserting (a) v34 directory exists, (b) generic no-ID wording present, (c) policy-conditional Rule X-2 wording, (d) `SCENE_DETAIL_PROMPT_VERSION` pin matches.
- **W4 focused regression** — pytest scope: `subject_reference_policy`, `visible_entities_validator`, `render_prompt_card_episode_policy`, `prompt_versions`, `scene_detail_id_policy_alignment`, plus `detail_steps` and `scene_detail` integration tests.
- **W5 operational reverify** — backend restart; same project/episode `scene_detail` force/resume + downstream `shot_dependency_t2i`, `t2i_review`, image steps resume; verify (a) S1_Shot1 / S1_Shot4 generate without `C06`/`C12` ID tokens, (b) `generic_descriptor_allowed` subjects produce no required_refs, (c) `scene_image_pipeline` completes without `missing required ref`.
- **W6 full acceptance** — Phase 0 GATE re-run (this episode); `§8.2` acceptance checklist; if time permits, one fully fresh project/episode full E2E for the strongest evidence.
- **W7 Codex range review** — `feat/reference-necessity-phase012` HEAD vs origin; on `APPROVED_FOR_PUSH`, `git push origin feat/reference-necessity-phase012:main` (after the standard merge/push protocol the user prefers).

### Non-scope (explicit)

- **No validator weakening**. `outlook_forbidden` for `generic_descriptor_allowed` remains fail-closed; bare `C##` becomes equally fail-closed.
- **No `max_retry` increase**. Retries reuse the same `_analyze_one`; increasing the count costs LLM tokens without changing the failure surface.
- **No deterministic `C##O## → C##` salvage for generic subjects**. That re-introduces the over-generation Phase 2 prevents.
- **No `scene_detail_redo_service` lucky retry as the acceptance evidence**. Manual redo may pass by LLM nondeterminism but does not satisfy `§8.2` "text_only subject t2i_prompt 에 C## 없음".
- **No edits to Phase 2 overlay code paths**. The overlay is correct; only id-policy producer (prompt/card) and consumer (validator) change.

## 4. Wave order and subagent split

| Wave | Owner | Output |
| ---- | ----- | ------ |
| W0   | Main  | This addendum plan, task list, branch sanity. |
| W1   | Worker A | `subject_reference_policy.IdUsageRule.base_forbidden` + validator branch + 3 tests (`generic+bare C## FAIL`, `generic+C##O## FAIL` preserved, `base_id_required+bare C## PASS` regression). |
| W2   | Worker B | `render_prompt_card.build_id_policy()` policy-conditional constraints + overlay-card fixture test. |
| W3   | Worker C | v34 prompt dir + version bump + version-pin and alignment tests. |
| W4   | Main  | targeted pytest regression run (see scope list). |
| W5   | Main  | backend restart + same-episode `scene_detail` force/resume + downstream image resume + acceptance probe. |
| W6   | Main  | Phase 0 GATE re-run + §8.2 checklist; optionally fresh full E2E. |
| W7   | Main  | Codex range review on the Phase 3 commits; push on approval. |

Workers run on the **same branch**, on **disjoint file sets** (Worker A: validator + tests for it; Worker B: card builder + tests for it; Worker C: prompt + version pin + alignment test). They must not revert each other’s changes. Each worker leaves at least one commit and reports test counts. Main integrates and gates further with Codex.

## 5. Acceptance gates

1. **W1 gate** — Worker A’s new tests pass; existing `outlook_forbidden` test stays red until card/prompt produce ID-free output; `base_id_required` regressions stay green.
2. **W2 gate** — Worker B’s overlay-card test passes; old unconditional-C##O## constraint string is absent from generic-subject branches.
3. **W3 gate** — `SCENE_DETAIL_PROMPT_VERSION == "34.202605230753"`; new alignment test passes; old v33 dir remains untouched (archive on disk, not deleted).
4. **W4 gate** — full targeted regression green; no new pre-existing failures introduced.
5. **W5 gate** — `scene_detail` re-run completes 0 failures; S1_Shot1/S1_Shot4 manifests render C06/C12 as descriptors with no `C##`/`C##O##` token; `episode_reference_policy/manifest.json` unchanged (overlay is upstream of W5); `scene_image_pipeline` finishes without `missing required ref`.
6. **W6 gate** — Phase 0 audit GATE PASS unchanged; §8.2 checklist fully satisfied; if a second fresh E2E runs, same `scene_detail` clean pass.
7. **W7 gate** — Codex `APPROVED_FOR_PUSH` on the range; `git push` succeeds.

## 6. Codex consultation cadence

- W0 → no separate approval needed; this addendum is the design pin.
- W1/W2/W3 each → Codex per-wave review before integration if any blocking judgment surfaces; otherwise integrate into branch.
- W5 evidence → Codex read-only acceptance review on the manifests before W6 starts.
- W7 → final range review on the full Phase 3 wave.
- Anytime: BLOCKING judgment, scope drift, destructive operation, push.

## 7. Risks / known unknowns

- LLM still emits bare `C##` despite v34 wording. Mitigation: validator now catches it; the wave can land a deterministic descriptor template suggestion in v34 (e.g. "Korean middle-aged man") if reverify still fails — but treat that as a follow-up addendum, not in this wave’s scope.
- `prompts/_archive` interaction with prompt-version pin tests — keep v33 dir intact; pin tests assert exact v34 string.
- Force-rerunning `scene_detail` cascades invalidation downstream (shot_dependency_t2i / t2i_review). resume mode should re-execute these; verify in W5.
