# 2026-05-05 Structural Remediation Plan

## 목적

이 문서는 [current repo full code review](./2026-05-05-current-repo-full-code-review.md)의 후속 문서다. 앞 문서가 현재 코드 결함과 테스트 실패를 정리했다면, 이 문서는 구조 문제, 새로 수정된 G4.x 파이프라인의 방향성, 그리고 수정안을 별도로 정리한다.

핵심 질문:

- 최근 G4.5a까지 반영된 `scene_detail` / `RenderPromptCard` 파이프라인 구조가 맞는 방향인가?
- 코드 결함이 아니라 구조적으로 반복될 위험은 무엇인가?
- 다음 수정은 어느 경계에서 해야 하며, 어떤 검증 gate가 필요한다?

Reviewed HEAD:

```text
51685d4ec9e8498e1cffecaca49cf57338e93806
```

Additional uncommitted changes observed while writing this document:

- `backend/app/core/steps/_owned_helpers.py`
- `backend/app/core/steps/detail_steps.py`
- `backend/tests/unit/test_owned_helpers.py`
- `prompts/_base/scene_detail_owned_judge/2.202605051641/`

These are treated as current-worktree review inputs, not as changes made by this document.

## 현재 구조 요약

### 1. G4 계열의 실제 목표

G4 전략 문서의 초기 결정은 명확했다.

- `RenderPromptCard`는 처음부터 별도 step이 아니라 `scene_detail` 내부 helper로 시작한다.
- card는 `scene_detail` LLM 입력 앞에 deterministic contract로 inject된다.
- card와 hash는 `scene_detail` checkpoint에 CP-only 필드로 저장한다.
- rule lift는 별도 human doc 링크가 아니라 card의 5 semantic field 안에 들어가야 한다.

Evidence:

- `docs/superpowers/specs/2026-05-04-g4-render-prompt-card-strategy.md:125-139`
- `docs/superpowers/specs/2026-05-04-g4-render-prompt-card-strategy.md:159-165`
- `docs/superpowers/specs/2026-05-04-g4-render-prompt-card-strategy.md:183-192`

이 결정 자체는 아직 타당하다. 초기에 별도 step으로 만들었다면 manifest, checkpoint lifecycle, UI status, cascade semantics를 동시에 설계해야 했고, G4.1~G4.5a의 빠른 rule lift에는 과했다.

### 2. G4.5a에서 새로 바뀐 부분

G4.5a는 v19 prompt의 spatial prose 3개 섹션을 `render_strategy.spatial_consistency`로 lift했다.

Evidence:

- `docs/superpowers/plans/2026-05-05-g4.5a-spatial-rules-lift-implementation.md:5-12`
- `backend/app/core/steps/render_prompt_card.py:578-820`
- `backend/app/core/steps/render_prompt_card.py:844-865`
- `backend/app/core/steps/render_prompt_card.py:2100-2113`

현황:

- `prompts/_base/scene_detail/19.202605050814/system.md`: 534 lines / 25 headings
- `prompts/_base/scene_detail/20.202605051240/system.md`: 423 lines / 23 headings
- v20 `## Spatial consistency` section: `prompts/_base/scene_detail/20.202605051240/system.md:144-155`
- `SCENE_DETAIL_PROMPT_VERSION = "20.202605051240"`: `backend/app/core/steps/detail_steps.py:104-105`
- `scene_detail_composer = "1.20.0"` and `scene_detail/v20`: `backend/app/core/version_registry.py:34`, `backend/app/core/version_registry.py:125-127`

구조적으로는 올바른 방향이다. rule이 prompt prose에서 card data로 이동했고, v20 prompt도 줄었다. 다만 최종 목표였던 “작은 system prompt”에는 아직 도달하지 않았다. 현재 v20은 여전히 423 lines / 23 headings이며, G4.5a plan이 명시한 carry 항목이 남아 있다.

남은 carry:

- Rule B lift: 조명/색조가 신체를 변형하지 않게
- Rule D lift: 큰 prop은 본체가 frame에 보이게
- outfit 묘사 rule lift
- evidence / inference disclosure lift
- dense compression / final 250-line target
- `_derive_framing_scale()` producer 개선

Evidence:

- `docs/superpowers/plans/2026-05-05-g4.5a-spatial-rules-lift-implementation.md:20-25`

### 3. 현재 scene_detail 주변 DAG

현재 핵심 흐름:

```text
background_prompt
scene_consistency
shot_staging
shot_dependency
outlook_phase3
entity_t2i
        │
        ▼
scene_detail
  - SceneContextLoader raw checkpoint load
  - _collect_card_inputs()
  - build_render_prompt_card()
  - inject [RenderPromptCard v1]
  - LLM scene_detail call
  - store render_prompt_card + render_prompt_card_hash
        │
        ▼
shot_dependency_t2i
        │
        ▼
t2i_review
  - modifies scene_detail / entity_t2i
        │
        ▼
scene_still / scene_image generation
```

Important details:

- `scene_detail` order is `21.70` and depends on `background_prompt`: `backend/app/core/step_manifest.py:626-648`
- `scene_detail` has `allow_partial_downstream=False`: `backend/app/core/step_manifest.py:660-663`
- `scene_detail` also declares `consumes_downstream=["shot_dependency_t2i"]`: `backend/app/core/step_manifest.py:653-658`
- `shot_dependency_t2i` depends on `scene_detail`: `backend/app/core/step_manifest.py:665-678`
- `t2i_review` modifies `scene_detail` and `entity_t2i`: `backend/app/core/step_manifest.py:679-694`
- StepRunner disables editorial cascade by default: `backend/app/core/step_runner.py:681-699`

이 구조는 “계약 생성”에는 강하지만, “계약 수정 후 downstream 반영”에는 아직 약하다.

## 종합 구조 판정

현재 방향은 맞다. `RenderPromptCard`는 `scene_detail` prompt slimming과 contract hardening에 필요한 중간 계층이다.

다만 현재 구조는 세 가지 층이 서로 다른 성숙도를 가진다.

| 층 | 현재 상태 | 판정 |
|---|---|---|
| scene_detail 내부 contract | G4.1~G4.5a로 강해짐 | 긍정적 |
| downstream image generation contract consumption | legacy fallback이 여전히 강함 | 수정 필요 |
| edit / resume / cascade semantics | StepRunner는 강해졌지만 editorial edit 반영은 약함 | 구조 보강 필요 |

따라서 다음 수정 방향은 “card에 더 많은 rule을 넣기”만이 아니다. card가 만들어진 뒤의 lifecycle, edit propagation, image generation resolver, test lanes를 같이 정리해야 한다.

## Structural Issues and Directional Fixes

### S1. Contract가 scene_detail 안에서는 강하지만 image generation까지 전파되지 않는다

Evidence:

- `scene_detail` inject path: `backend/app/core/steps/detail_steps.py:1958-1978`
- card 저장: `backend/app/core/steps/detail_steps.py:2144-2149`
- scene image prompt loader fallback: `backend/app/services/scene_checkpoint_loaders.py:242-303`
- scene image fallback to still prompt: `backend/app/services/scene_generation_coordinator.py:703-710`

문제:

`scene_detail`은 `[RenderPromptCard v1]`를 읽고 생성한 `t2i_variations`를 저장한다. 그런데 scene image generation은 `camera_json.t2i_variations`를 먼저 보고, 없으면 `scene_detail` checkpoint를 읽고, 그래도 없으면 `still_frame_prompt`로 fallback한다. 현재 G4 schema 기준에서는 이 fallback이 너무 넓다.

구조적 영향:

- G3.1 evidence / inference fields
- G3.2 owned sentinel
- G4 RenderPromptCard contract
- G4.5a spatial consistency

위 계약이 모두 `scene_detail` 안에서만 강하고, scene image 단계에서는 “없으면 기존 prompt로 간다”는 legacy 경로가 남는다. 이러면 contract 위반이 아니라 contract 미적용 상태로 이미지가 생성될 수 있다.

수정 방향:

`load_shot_t2i_variations()`를 단순 list 반환 함수에서 contract-aware resolver로 바꾼다.

제안 API:

```python
@dataclass(frozen=True)
class T2IPromptBundle:
    source: Literal[
        "camera_json",
        "scene_detail_current",
        "scene_detail_legacy",
        "fallback_still_prompt",
    ]
    scene_index: int | None
    shot_index: int | None
    variations: list[dict]
    render_prompt_card_hash: str | None
    schema_version: int | None
    warnings: list[str]
```

정책:

- current schema (`scene_detail` schema 7)에서 `scene_detail` cp parse 실패 → hard fail.
- current schema에서 selected `(scene_index, shot_index)` miss → hard fail.
- current schema에서 `t2i_variations` empty/malformed → hard fail.
- legacy schema 또는 explicitly marked legacy row만 `fallback_still_prompt` 허용.
- fallback 사용 시 output에 `source="scene_detail_legacy"` 또는 `source="fallback_still_prompt"`를 남겨 UI/operation이 알 수 있게 한다.

구현 순서:

1. 기존 함수 옆에 `resolve_shot_t2i_prompt_bundle()` 신설.
2. `scene_generation_coordinator.py`에서 새 resolver 사용.
3. legacy 허용 테스트와 current-schema fail-fast 테스트 분리.
4. 한 release 뒤 기존 `load_shot_t2i_variations()`를 thin wrapper로 축소.

검증 gate:

- corrupted `scene_detail/manifest.json` + schema 7 → scene image generation fails.
- selected shot missing in schema 7 → fails.
- old schema fixture → warning source with legacy fallback.
- G3.2 sentinel + G4 card hash가 있는 cp는 fallback 없이 variation을 사용.

### S2. `t2i_review`가 scene_detail을 수정하지만 downstream invalidation은 기본 비활성이다

Evidence:

- `t2i_review` modifies checkpoints: `backend/app/core/step_manifest.py:682-694`
- StepRunner editorial cascade disabled: `backend/app/core/step_runner.py:681-699`
- `t2i_review` in-place edit: `backend/app/modules/pipeline/t2i_review.py:340-355`

문제:

`t2i_review`가 `scene_detail`의 `t2i_prompt`를 수정해도, downstream image-related checkpoints는 기본적으로 stale 처리되지 않는다. 이 정책은 무한 cascade를 막기 위해 의도된 것이지만, 구조적으로는 “수정된 prompt가 downstream에 자동 반영되지 않는” 상태를 만든다.

구조적 영향:

- 사용자는 t2i_review가 완료됐다고 보고도, 이미 생성된 scene image가 이전 prompt 기반일 수 있다.
- `scene_detail` card hash는 shot-level이고, variation-level prompt edit은 G3.2 sentinel 계열이 담당한다. 하지만 downstream image generation은 이 drift를 lifecycle로 보지 않는다.
- `modifies_checkpoints`가 “수정했다”는 사실만 있고, 어떤 target/downstream이 dirty인지에 대한 명시적 edit epoch가 없다.

수정 방향:

무조건 cascade on으로 되돌리기보다 edit epoch / dirty marker를 도입한다.

제안 구조:

```json
{
  "status": "completed",
  "data": {...},
  "editorial_revision": {
    "t2i_review": {
      "revision": 3,
      "modified_at": "...",
      "modified_fields": ["scene_detail.t2i_variations[].t2i_prompt"]
    }
  }
}
```

downstream checkpoint에는 다음을 저장한다.

```json
{
  "input_revisions": {
    "scene_detail.editorial_revision.t2i_review": 2
  }
}
```

정책:

- `t2i_review`가 수정하면 `scene_detail.editorial_revision.t2i_review.revision += 1`.
- downstream step의 resume verify가 `input_revisions`를 비교한다.
- 불일치 시 자동 force 또는 `partial/stale` 마킹.
- cascade는 여전히 선택적일 수 있지만, stale detection은 자동이어야 한다.

구현 순서:

1. StepRunner에 generic input revision compare를 바로 넣지 말고, 먼저 `scene_image` / `shot_dependency_t2i`에 국소 적용.
2. `t2i_review`가 수정 count > 0일 때 revision field 저장.
3. scene image generation checkpoint에 consumed revision 저장.
4. resume 시 revision drift 검출.

검증 gate:

- t2i_review 수정 전 scene image completed.
- t2i_review가 scene_detail 수정.
- scene image resume 진입 시 stale/re-run 또는 explicit blocked status.
- cascade disabled 상태에서도 stale 여부가 보인다.

### S3. `RenderPromptCard` helper-first 전략은 맞았지만, 파일 단위가 이미 과대해졌다

Evidence:

- `backend/app/core/steps/render_prompt_card.py`: 3127 lines
- `backend/app/core/steps/detail_steps.py`: 2451 lines
- G4 strategy의 “new step later” exit criteria: `docs/superpowers/specs/2026-05-04-g4-render-prompt-card-strategy.md:153-158`

문제:

helper-first는 G4.1에서는 맞았다. 하지만 G4.2~G4.5a가 모두 같은 파일에 누적되면서 `render_prompt_card.py`가 사실상 mini subsystem이 되었다. 지금은 “별도 step”까지는 아직 아니더라도, “단일 파일 helper”로 유지하기에는 검증·소유권·리뷰 비용이 커졌다.

구조적 영향:

- 작은 rule lift마다 3000-line 파일에 변경이 들어간다.
- hash/canonicalization, shape assertion, field builder, canary constants가 한 파일에 섞인다.
- future G4.5b/c가 Rule B/D/outfit/evidence를 추가하면 더 커진다.

수정 방향:

새 pipeline step으로 바로 승격하지 말고, 먼저 package modularization을 한다.

제안 구조:

```text
backend/app/core/steps/render_prompt_card/
  __init__.py
  models.py                 # top-level envelope constants / types
  canonical.py              # canonical JSON, hashes, metadata stripping
  render_strategy.py         # mode/framing/spatial consistency
  id_policy.py
  background_binding.py
  continuity.py
  asset_requirements.py
  shape_assertions.py
  builder.py                # build_render_prompt_card public entry
```

public API는 유지한다.

```python
from app.core.steps.render_prompt_card import (
    build_render_prompt_card,
    assert_card_shape,
    compute_card_hash,
    compute_render_strategy_snapshot_hash,
)
```

별도 step 승격은 아직 보류한다. 승격 조건은 다음 세 가지 중 2개 이상 충족될 때다.

- scene image generation 또는 UI가 card를 독립적으로 소비해야 한다.
- card schema가 G4.5b/c 이후 1회 이상 production canary에서 안정화된다.
- card drift/hash가 `scene_detail` 외 step의 resume gate로 사용된다.

검증 gate:

- import compatibility test.
- existing G4.1~G4.5a tests unchanged green.
- no new top-level card field.
- `_card_metadata` remains CP-only and hash-excluded.

### S4. 5-field envelope는 지켜야 하지만 sub-field ownership map이 필요하다

Evidence:

- v20 prompt declares 5 semantic fields: `prompts/_base/scene_detail/20.202605051240/system.md:3-17`
- G4.5a explicitly bans new top-level field: `docs/superpowers/plans/2026-05-05-g4.5a-spatial-rules-lift-implementation.md:26`

문제:

5-field top-level envelope를 유지하는 결정은 맞다. 하지만 sub-field가 계속 늘어나면 “어떤 upstream 사실이 어느 field의 책임인가”가 흐려진다.

현재 mapping:

| Field | 현재 책임 | 구조상 owner |
|---|---|---|
| `render_strategy` | framing, camera, perception, spatial consistency | shot_staging + scene_detail builder |
| `id_policy` | C##/C##O##, body-part, reproduction, demographics | entity/outlook + builder |
| `background_binding` | bg ref, owned objects, close skip | background_prompt + loader |
| `continuity_elements_used` | fixed elements, previous refs, forward zoom | scene_consistency + shot_dependency |
| `asset_requirements` | required/forbidden refs | asset readiness + builder |

수정 방향:

G4.5b/c 전에 `docs/contracts/render_prompt_card_field_ownership.md`를 추가한다.

내용:

- field/sub-field별 producer
- source checkpoint
- stale condition
- fallback policy
- hash scope
- downstream consumers
- tests/canaries

이 문서는 runtime contract가 아니라 reviewer/operator contract다. system prompt에는 링크만 넣지 않는다. 실제 LLM contract는 계속 card field에 둔다.

검증 gate:

- 새 sub-field 추가 PR은 ownership table row 없으면 reject.
- canary/test가 ownership row의 stale condition을 하나 이상 검증.

### S5. `scene_detail`이 downstream을 역참조하는 구조는 장기적으로 취약하다

Evidence:

- `scene_detail.consumes_downstream=["shot_dependency_t2i"]`: `backend/app/core/step_manifest.py:653-658`
- `shot_dependency_t2i.depends_on=["scene_detail", "scene_consistency"]`: `backend/app/core/step_manifest.py:665-678`

문제:

`scene_detail`이 downstream인 `shot_dependency_t2i`의 refined ref_usage를 역참조한다. manifest에 이를 보존하기 위한 특별 선언이 있지만, 구조적으로는 DAG가 아니라 feedback loop에 가깝다.

구조적 영향:

- force/cascade 시 어떤 파일을 지우고 어떤 파일을 보존해야 하는지 예외가 필요하다.
- `scene_detail` 재실행 후 `shot_dependency_t2i` 수동 재실행 권장이 남는다.
- resume correctness가 일반 dependency rule이 아니라 주석과 operator discipline에 기대게 된다.

수정 방향:

두 단계 중 하나로 정리한다.

Option A. feedback artifact formalization

- `shot_dependency_t2i`가 만든 refined ref_usage를 `scene_detail_feedback` 같은 별도 artifact로 분리.
- artifact는 deleting cascade 대상이 아니라 versioned input cache로 취급.
- `scene_detail`은 이 artifact의 hash를 consumed input으로 기록한다.

Option B. upstream seed / downstream refine split

- `shot_dependency_seed`를 `scene_detail` 전 upstream으로 둔다.
- `shot_dependency_t2i`는 `scene_detail` 후 refine-only step으로 둔다.
- `scene_detail`은 seed만 소비하고, refine 결과는 다음 run/canary에서만 사용한다.

추천:

- 단기: Option A. 현재 구조를 덜 흔든다.
- 중기: Option B. DAG 순수성이 더 높다.

검증 gate:

- `scene_detail force` 후 어떤 downstream artifact가 삭제/보존되는지 test.
- feedback hash drift 시 scene_detail resume이 stale을 감지하는지 test.
- operator manual rerun 문구 없이도 system이 일관 상태를 판단하는지 test.

### S6. file_path invariant와 legacy compatibility helper가 서로 충돌한다

Evidence:

- DB CHECK: `backend/app/models/project.py:149-158`
- Alembic CHECK: `backend/alembic/versions/005_file_path_relative_check.py:29-34`
- startup CHECK: `backend/app/core/database.py:157-173`
- helper still returns root-external absolute path: `backend/app/core/file_paths.py:136-175`
- backend full pytest failed with `ck_image_asset_file_path_relative`

문제:

DB invariant는 “absolute path 저장 금지”다. 그런데 `to_relative_image_path()`는 root 밖 absolute path를 warning 후 그대로 반환한다. 과거 legacy compatibility로는 맞았지만, 현재 DB CHECK가 있는 write path에서는 더 이상 호환되지 않는다.

구조적 영향:

- production producer는 root 내부 경로만 쓰므로 안전할 수 있다.
- test/helper/raw insert 경로는 DB CHECK에서 실패한다.
- helper docstring은 “test fixture 등 root 밖 경로 호환”을 말하지만 DB는 거부한다.

수정 방향:

read compatibility와 write invariant를 분리한다.

제안 API:

```python
def resolve_image_path(value) -> Path | None:
    """Read path. Legacy absolute rows are accepted."""

def to_db_image_path(value, *, allow_external_for_legacy: bool = False) -> str:
    """Write path. Root-external absolute path is AppError/ValueError by default."""
```

정책:

- ORM `ImagePathType.process_bind_param()`는 `to_db_image_path(..., allow_external_for_legacy=False)` 사용.
- migration/backfill만 legacy external을 별도 처리.
- tests는 fixture factory를 통해 root 내부 파일을 만든다.

검증 gate:

- root-internal absolute → relative stored.
- relative input → stored as-is.
- root-external absolute ORM write → deterministic ValueError before DB.
- legacy absolute DB row read → resolve accepted if already present in old DB.
- all image tests stop failing on CHECK violation.

### S7. build/test lane 설계가 현재 변경 속도를 못 따라간다

Evidence:

- backend `pytest.ini` defines lanes but default still runs broad non-pg suite: `backend/pytest.ini:1-15`
- backend full suite currently fails 20 tests.
- frontend build command includes `tsc -b && vite build`: `frontend/package.json:6-12`
- frontend app tsconfig includes all `src`: `frontend/tsconfig.app.json:27`
- test globals duplicate app globals: `frontend/src/test-setup.ts:29-35`, `frontend/src/vite-env.d.ts:3-5`

문제:

G4.x처럼 큰 contract migration을 할 때, 필요한 검증은 세 층이다.

- fast contract tests: card shape/hash/prompt alignment/canary preflight
- runtime smoke: selected backend API + minimal pipeline
- full regression: image API, legacy v2/v3, projection, frontend build

현재는 full suite가 fixture drift로 깨져 있고, frontend build는 test support file 때문에 깨진다. 결과적으로 “현재 변경이 깨뜨린 것인지, 오래된 fixture가 깨진 것인지”를 매번 수동 분리해야 한다.

수정 방향:

Backend:

```bash
pytest -m core
pytest backend/tests -k "g4_1 or g4_2 or g4_3 or g4_4 or g4_5a or render_prompt_card or scene_detail"
pytest backend/tests/test_images_api.py backend/tests/test_variation_pipeline.py
pytest backend/tests -q
```

Frontend:

- `tsconfig.app.json`에서 test support files exclude.
- `tsconfig.test.json`을 별도로 만들어 vitest가 사용.
- `npm run build`는 app bundle만 검증.
- `npm run test:run`이 test setup/type globals를 검증.

검증 gate:

- PR gate는 `contract + build`를 기본으로 한다.
- full suite는 merge 전 필수 또는 nightly로 둔다.
- 실패가 있으면 “known fixture drift” label이 아니라 test owner와 target date를 둔다.

### S8. manual upload는 API boundary 책임이 분산되어 있다

Evidence:

- backend reads full upload bytes: `backend/app/api/v1/images.py:457-480`
- backend writes bytes directly: `backend/app/services/image_upload_service.py:104-113`
- frontend entity upload uses raw fetch: `frontend/src/pages/Entities.tsx:350-360`
- shared API wrapper handles timeout/status: `frontend/src/api/client.ts:11-43`
- episode upload has dedicated timeout/status handling: `frontend/src/hooks/api/mutations/useCreateEpisode.ts:34-50`

문제:

upload boundary는 보안/안정성 boundary다. 그런데 backend에는 image byte validation/size cap이 없고, frontend entity upload는 non-OK response를 success처럼 취급할 수 있다.

구조적 영향:

- bad file이 ImageAsset row로 등록될 수 있다.
- large file이 memory pressure를 만들 수 있다.
- UI는 upload 실패 후 composite regeneration을 진행할 수 있다.

수정 방향:

Backend:

- `ImageUploadPolicy` 도입.
- max bytes, allowed MIME, allowed extension, PIL decode validation, dimensions extraction.
- validation 실패는 DB write 전 `AppError`.

Frontend:

- `uploadFormData()` helper 추가.
- `api()`와 같은 error normalization / timeout / credentials.
- `Entities.tsx`는 helper 사용.

검증 gate:

- oversized image rejected.
- non-image bytes rejected.
- HTTP 400/500 upload response does not trigger refetch/regenerate flow.
- valid image upload still sets primary and logs activity.

### S9. prompt slimming은 줄었지만 아직 “운영 가능한 작은 prompt” 단계는 아니다

Evidence:

- v19 → v20: 534 lines → 423 lines.
- headings: 25 → 23.
- G4.5a plan says final 250-line target is G4.5c carry: `docs/superpowers/plans/2026-05-05-g4.5a-spatial-rules-lift-implementation.md:24`

문제:

G4.5a는 spatial lift를 잘 수행했지만, system prompt는 아직 크다. 남은 Rule B/D/outfit/evidence sections는 prompt 안에 계속 LLM reminder로 남아 있다.

수정 방향:

G4.5b:

- Rule B → `render_strategy.lighting_body_integrity_rule` 또는 `render_strategy.spatial_consistency.lighting_body_integrity_rule`
- Rule D → `asset_requirements.large_prop_visibility_rule` 또는 `render_strategy.prop_framing_rule`
- outfit rule → `id_policy.outfit_descriptor_policy` 또는 `asset_requirements.character_ref_usage`
- evidence disclosure → CP-only / review-time validator로 분리. LLM prompt에는 compact rule만.

G4.5c:

- `_derive_framing_scale()`를 close/medium/insert에서 wide까지 명시할지 결정.
- v20의 residual prose를 10-12 sections 이하로 압축.
- line gate는 “250 lines” 하나보다 다음 3개를 함께 본다:
  - system.md total lines
  - token delta
  - canary behavior delta

검증 gate:

- 각 lift는 source prose line range → card field mapping table 필요.
- prompt deletion 전후 canary 1 PID baseline/candidate 비교.
- 삭제된 prose와 같은 의미가 card field에 존재하는지 unit test.

### S10. 새 owned-judge v2 수정은 방향이 맞지만, field semantics가 더 정리되어야 한다

Observed new change:

- `prompts/_base/scene_detail_owned_judge/2.202605051641/system.md:10-18` changes the judge output semantics: every owned-object finding is emitted, then `verdict` classifies it.
- `prompts/_base/scene_detail_owned_judge/2.202605051641/schema.json:9-15` requires `verdict`.
- `_owned_helpers.assert_owned_sentinel_shape()` now accepts optional `verdict` and validates enum if present: `backend/app/core/steps/_owned_helpers.py:219-255`.
- New `has_redraw_violation()` distinguishes v1 legacy from v2 verdict schema: `backend/app/core/steps/_owned_helpers.py:257-277`.
- `SceneDetailStep` now marks contract violation only when `has_redraw_violation(violations)` returns true: `backend/app/core/steps/detail_steps.py:2279-2343`.
- Unit tests were added in `backend/tests/unit/test_owned_helpers.py:608-693`.

Positive direction:

- This fixes the false-positive class where the LLM recognizes an owned object as an anchor reference but still puts it in `violations`.
- The new `verdict` enum is structurally better than asking the caller to infer violation vs non-violation from free-form `reason`.
- The helper keeps v1 legacy behavior, which is pragmatic for existing checkpoints.

Remaining structural issue:

The array is still named `violations`, but in v2 it no longer means “violations.” It means “findings.” This creates semantic drift:

```text
violations[] item with verdict="anchor_reference" is not a violation.
```

That is acceptable as a short-term compatibility patch, but long-term it makes CP inspection, metrics, and operator dashboards confusing.

Recommended direction:

Do not rename immediately in this tactical patch. Instead, introduce a sentinel v2 shape in the next ownership cleanup:

```json
{
  "schema_version": 2,
  "validator": "scene_detail_owned_objects.v2",
  "findings": [
    {
      "owned_object": "door",
      "phrase": "near the doorway",
      "reason": "anchor only",
      "verdict": "anchor_reference"
    }
  ],
  "redraw_violation_count": 0,
  "anchor_reference_count": 1
}
```

Compatibility policy:

- v1 sentinel: `violations` non-empty means violation.
- v2 tactical prompt output: `violations[]` with `verdict` is accepted.
- future sentinel v2: store `findings[]` and counts; optionally preserve `violations[]` as derived redraw-only list for old readers.

Prompt versioning concern:

`_owned_judge.py` uses `load_prompt("scene_detail_owned_judge", ...)`, which selects latest file prompt by numeric version. That is normal for this repo, but this specific change alters judge semantics. It should have a visible version/contract marker in runtime output.

Recommended marker:

```json
{
  "owned_judge_prompt_version": "2.202605051641",
  "owned_judge_semantics": "findings_with_verdict"
}
```

This marker can live inside `owned_validation` sentinel or step summary. It should not affect LLM prompt tokens.

Additional tests needed:

- Integration test: v2 `anchor_reference` findings do not set `SceneDetailStep` result status to `contract_violation`.
- Verify test: stored sentinel with only anchor references passes `verify_completion()`.
- Prompt-loader test: `scene_detail_owned_judge` latest version resolves to `2.202605051641`.
- Mixed-schema test at SceneDetailStep level: partial verdict schema stays conservative.

Decision:

This patch is directionally good and should be kept, but it should be treated as a transitional compatibility layer. The structural endpoint should be `findings + verdict + counts`, not “violations that may not be violations.”

## Prioritized Remediation Roadmap

### P0. Build and test gate 복구

목표:

- repo가 기본 검증에서 실패하지 않게 한다.

작업:

1. frontend build failure 수정.
2. backend image-path fixture 수정.
3. full backend suite 재실행.

완료 조건:

- `npm run build` green.
- `backend/.venv/bin/python -m pytest backend/tests -q` green 또는 남은 실패가 명확히 별도 issue로 격리.

### P1. Contract propagation hardening

목표:

- `scene_detail` card contract가 image generation까지 유지되게 한다.
- owned-judge v2 semantics도 downstream/verify에서 명확한 source로 남긴다.

작업:

1. `T2IPromptBundle` resolver 도입.
2. current schema malformed/missing variation fail-fast.
3. legacy fallback은 explicit source로만 허용.
4. scene image checkpoint에 consumed schema/card/edit revision 저장.
5. owned sentinel v2 design: `findings`, `verdict`, counts, prompt-version marker.

완료 조건:

- corrupt schema-7 `scene_detail` cannot silently generate images from `still_frame_prompt`.

### P2. Editorial revision and stale detection

목표:

- `t2i_review` 수정이 downstream lifecycle에 보이게 한다.

작업:

1. `editorial_revision` 저장.
2. downstream consumed revision 저장.
3. resume verify에서 revision mismatch 감지.

완료 조건:

- cascade disabled여도 stale state가 보인다.

### P3. RenderPromptCard modularization

목표:

- G4.5b/c를 3000-line 파일에 계속 누적하지 않는다.

작업:

1. package split.
2. public API compatibility 유지.
3. field ownership doc 추가.

완료 조건:

- G4.5a tests green with unchanged imports.

### P4. Pipeline feedback loop 정리

목표:

- `scene_detail` ↔ `shot_dependency_t2i`의 bidirectional semantics를 주석이 아니라 artifact/hash로 관리한다.

작업:

1. feedback artifact 또는 seed/refine split 결정.
2. artifact hash를 consumed input으로 기록.
3. force/cascade behavior test.

완료 조건:

- manual rerun recommendation 없이 system이 stale 판단 가능.

### P5. G4.5b/c prompt slimming

목표:

- residual prompt prose를 card field 또는 validator로 이동한다.

작업:

1. Rule B/D/outfit/evidence lift spec.
2. canary baseline/candidate.
3. line/token gate + behavior gate.

완료 조건:

- prompt size reduction과 behavior preservation이 동시에 증명됨.

## Recommended Architecture Direction

최종 방향은 “LLM prompt에 점점 더 많은 규칙을 쓰는 구조”가 아니라 다음 구조다.

```text
Upstream deterministic facts
  ├─ entity / outlook / references
  ├─ background ownership
  ├─ shot staging
  ├─ scene consistency
  └─ dependency / continuity feedback
        │
        ▼
RenderPromptCard builder
  ├─ 5-field stable envelope
  ├─ sub-field ownership map
  ├─ canonical hash
  └─ shape assertions
        │
        ▼
scene_detail LLM
  ├─ minimal prompt prose
  ├─ card-wins precedence
  └─ variation-level sentinel/hash
        │
        ▼
Contract-aware downstream resolver
  ├─ current schema hard fail
  ├─ explicit legacy fallback
  ├─ consumed revision/hash record
  └─ image generation
```

핵심 원칙:

1. Prompt prose는 reminder이고, runtime contract는 card/checkpoint/hash다.
2. Fallback은 source가 기록될 때만 허용한다.
3. Editorial edit는 반드시 revision을 남긴다.
4. DB와 checkpoint가 함께 움직이지 않으면 hard failure 또는 explicit degraded state로 남긴다.
5. G4.5b/c는 “더 많은 prompt 삭제”보다 “삭제된 의미가 어느 deterministic field에 갔는지”를 우선한다.

## Change Proposal Summary

| Priority | Proposal | Main files | Type |
|---|---|---|---|
| P0 | frontend build fix | `frontend/src/test-setup.ts`, `frontend/src/test-utils.tsx`, tsconfig | build |
| P0 | image-path fixture repair | `backend/tests/*image*`, test fixture helpers | test infra |
| P1 | contract-aware T2I resolver | `scene_checkpoint_loaders.py`, `scene_generation_coordinator.py` | pipeline |
| P1 | upload boundary policy | `images.py`, `image_upload_service.py`, frontend upload helper | API/security |
| P1 | owned-judge v2 sentinel semantics | `_owned_helpers.py`, `_owned_judge.py`, `detail_steps.py`, prompt v2 | pipeline contract |
| P2 | editorial revision tracking | `t2i_review.py`, `step_runner.py` or targeted downstream steps | pipeline lifecycle |
| P3 | RenderPromptCard package split | `backend/app/core/steps/render_prompt_card*` | architecture |
| P4 | feedback artifact formalization | `step_manifest.py`, `detail_steps.py`, `shot_dependency_t2i` | DAG semantics |
| P5 | G4.5b/c lift | prompt v21/v22, `render_prompt_card` submodules, canaries | prompt contract |

## Open Decisions

1. `t2i_review` 수정 후 downstream은 자동 force할 것인가, stale 표시만 할 것인가?
   - 추천: stale 표시 + resume verify mismatch. 무조건 force는 비용/loop 위험이 있다.

2. `scene_detail`이 계속 `shot_dependency_t2i` feedback을 소비할 것인가?
   - 추천: 단기 artifact formalization, 중기 seed/refine split.

3. `RenderPromptCard`를 언제 별도 step으로 승격할 것인가?
   - 추천: G4.5b/c 후 안정화 전까지는 package split만. scene image/UI가 직접 소비하기 시작하면 step 승격 재논의.

4. root-external absolute image path는 write path에서 완전 금지할 것인가?
   - 추천: yes. read path만 legacy compatible.

5. G4.5c의 prompt target은 250 lines strict인가?
   - 추천: strict 단일 gate가 아니라 line/token/canary 3-way gate. line target은 250-300으로 두고 behavior gate 우선.

## Immediate Next Patch Set

권장 순서:

1. `frontend` build green patch.
2. backend image fixture path patch.
3. owned-judge v2 integration/verify tests and sentinel-v2 design decision.
4. `T2IPromptBundle` resolver spec + implementation.
5. `t2i_review` editorial revision mini-spec.
6. RenderPromptCard package split plan.

이 순서가 좋은 이유:

- build/test gate를 먼저 복구해야 이후 구조 변경의 regression을 신뢰할 수 있다.
- 그 다음 contract propagation을 고쳐야 G4.5a의 의미가 image generation까지 이어진다.
- package split은 기능 변경 전후 어느 쪽에서도 가능하지만, G4.5b/c 전에 해두는 편이 리뷰 비용을 줄인다.
