# P0 구현 스펙 — 씬 스틸 실제 첨부 UUID 영속화 (Codex 합의)

## ★ 상태 (2026-07-01, canary 진행 — 미커밋)
- **Codex NEEDS_REVISION_NARROW_2 반영**: bg BLOCKING(bg_id는 L04B06 구조키≠UUID)→resolve_chain_bg_asset_id(chain_bg variant_type==bg_id→실UUID) 신설, collect의 bg_id fallback 제거. persistence 테스트 보강. → **Codex APPROVED_TO_CANARY**.
- **단건 경로도 배선**(canary가 단건 endpoint /stills/{id}/generate-image 사용 발견 — 단건은 save_single_scene_asset 별도 경로): build_scene_attached_refs가 _payload 5번째 반환→_build_single_scene_prompt_and_refs(4-tuple)→_single_scene_generate_and_build_result(attached_payload)→scene_result에 _attached_lineage_fields→save_single_scene_asset가 input_image_ids/gen_call_id(operation_type=single_scene_image_gen)/actual_attached_refs 영속화. test_build_scene_attached_refs unpack 5-tuple 갱신.
- **canary #1(단건 배선 후) 결과**: bg BLOCKING fix ✓(input_image_ids=f78e4149=실 chain_bg UUID, LxxBxx아님), generation_call_id=실UUID ✓, reference_image_ids 무오염 ✓. **BUT state_variant+registered_pose_guide가 unresolved**.
- **원인1 수정완료**: build_scene_ref_image_map의 composite/state_variant loop에 out_asset_id_map 누락(outlook만 있었음)→둘 다 추가. → state/composite 이제 resolve 예상(재canary 검증중).
- **★남은 gap = registered_pose_guide(immobilized_pose_guide) asset_id**: attach_registered_pose_guide_ref(visual_continuity_anchor_step.py:279)는 worker-thread(DB접근0) 헬퍼라 asset_id 미보유(metadata=group_id/character_short_id/visible_focus/subject_state, asset_id 없음). guide는 generation_context(still_id=..)로 capture돼 registered_pose_guide asset에 still_id 있음. **follow-up 옵션**: (a) persistence에서 still_id로 generated pose-guide asset(role registered_pose_guide/underlay) post-hoc 조회→input_image_ids 병합(stale run 위험 주의) 또는 (b) attach 후 coordinator에서 구조키(group_id+still)로 asset_id resolve해 metadata stamp(indoor/composition의 _resolve_guide_asset_ids 패턴 이식). indoor_pose_guide/composition_guide는 이미 asset_id 보유(post-hoc loader). registered만 미이식. ★다음 세션 처리.

## ★★ 다음 세션 즉시 구현 = immobilized_pose_guide post-hoc resolve (Codex 합의 상세 계획)
Codex 선호=(a) persistence post-hoc, 단 still_id+role만 말고 구조키 보강 후 resolve(worker헬퍼에 DB넣지 말 것). 5-point:
1. attach_registered_pose_guide_ref(visual_continuity_anchor_step.py:341 metadata)에 `pipeline_role:"registered_pose_guide"`, `group_id`, `bg_key`, `guide_hash` 추가(diag에 bg_key/hash/status 이미 있음). ref role은 immobilized_pose_guide 유지하되 DB lookup role은 capture role인 registered_pose_guide.
2. registered_pose_guide_service.build_registered_pose_guide()의 capture_extra_metadata에 `guide_hash: h` 추가(현재 group_id/bg_key/variant만→exact match 약함).
3. collect_actual_attached_refs(): unresolved에 role,label만 말고 구조필드(pipeline_role, group_id, bg_key, guide_hash, visible_focus, subject_state) 보존→persistence가 라벨없이 resolve.
4. save_scene_variations/save_single_scene_asset: annotate 전 unresolved 중 pipeline_role=='registered_pose_guide' resolve→_input_ids+actual_attached_refs 병합, 성공시 unresolved 제거. 우선순위=(group_id,bg_key,guide_hash) exact on ImageAsset.pipeline_role='registered_pose_guide' AND is_intermediate=true / fallback=(group_id,bg_key) 정확히 1개일때만(2+면 ambiguous unresolved 유지, wrong edge보다 missing edge) / still_id fallback은 최후보조만(그룹캐시 cache_hit시 still_id 다를 수 있음).
5. 테스트 3: exact triple→추가+unresolved제거 / pair fallback 1개→resolve / pair 2+→unresolved유지+ambiguous 마킹.
★Codex 추가노트(최종합의): resolve 성공시 _input_ids+actual_attached_refs 둘다 병합(동일 asset_id/role/label) + unresolved에서 제거한 항목은 metadata에 `resolved_from_unresolved` 마커. ambiguous(pair 2+)는 unresolved 유지하되 `reason:"registered_pose_guide_ambiguous_pair"` 명시.
**canary 최종 기준**: S12 sh7 unresolved_attached_refs=빈배열 or prev-shot류만. registered_pose_guide가 unresolved에 남으면 P0 미완.
**커밋**: P0 하나로 묶음(같은 root=actual attached lineage 영속화). 메시지에 ①단건path 배선 ②bg variant→UUID fix ③registered guide post-hoc resolve 명시. Codex와 커밋 GO 의논.
그 후: 모달 육안(vite 3000) → 커밋 → P1(1b/2a/2b/3b) → P2(4a/4b/4d).

## (이력) 구현 완료 + 유닛검증 (2026-07-01). 미커밋. canary 대기.
- 편집 4파일: scene_reference_service.py(out_asset_id_map + resolve 11 stamp사이트) / scene_generation_coordinator.py(collect_actual_attached_refs + _attached_lineage_fields 헬퍼, 단건+배치 build_scene_ref_image_map/resolve/bg asset_id, var_result) / scene_image_service.py(배치 asset_id_map threading) / scene_persistence_service.py(input_image_ids=actual_attached + generation_call_id best-effort + actual_attached_refs metadata).
- 신규 테스트 tests/services/test_p0_attached_asset_id_lineage.py (4). 회귀: 51 passed(관련). 전체 250 passed·1 pre-existing fail(test_close_zoom_in_detail_allow, 무관 label, stash로 확인).
- byte-identical(map None) 검증 완료. 남음: uvicorn 재기동 + S12 sh7 등 targeted 재생성 → DB input_image_ids/generation_call_id 확인 + 모달 육안 → Codex 리뷰 → 커밋.



목표: 씬 이미지 생성 시 **실제 Gemini에 붙은 모든 image asset UUID**를 `image_asset.input_image_ids`에 기록 + `actual_attached_refs` 메타 + `generation_call_id`. → pipeline_graph `generated_input` 엣지로 모달/캔버스가 배경/composite/마네킹/state ref 정확 표시. **reference_image_ids는 건드리지 않음(char/prop lineage 유지).**

## 데이터 흐름 현황
- `build_scene_attached_refs` (scene_generation_coordinator.py, 단건 ~594-880 / batch ~1290-1490): payload(labeled_refs, ref_roles, ref_role_metadata, attached_meta) 구성.
- ref_role_metadata 이종적:
  - bg chain: `{"bg_id": _bg_id}` (line 710/1343) — bg_id = chain_bg asset UUID
  - prev_shot: `_metadata` from build_prev_shot_background_ref (asset_id 불명 → unresolved 처리)
  - char/composite/state/outlook/prop: resolve_refs_for_prompt에서 `{"sid":...}`/`{"state":...}` — **asset_id 없음(추가 필요)**
  - guides(indoor/registered/composition): attach_*_ref helper가 `asset_id`+`pipeline_role` 이미 stamp
- var_result (line 2336-2367 단건, batch 유사): 현재 `pose_guide_asset_ids`만 수집(indoor_pose_guide/composition_guide role).
- save_scene_variations (scene_persistence_service.py:68-134): input_image_ids=_guide_ids(pose만), generation_call_id 미설정.
- generation_call_id 원천: gemini_image_client.py:278 log_llm_call 반환 call_id → capture_generated_image(Phase B 중간물)만 씀. primary 씬 asset은 save_scene_variations라 NULL. gemini return=2-tuple(bytes, elapsed)라 threading 광범위 → **still_id 구조키 링크(llm_call_log.metadata_json.still_id) best-effort**.

## 구현 (4파일)

### 1. scene_reference_service.py — char/composite/state에 asset_id stamp
- `build_scene_ref_image_map`: bytes 맵과 병행 `scene_ref_asset_id_map: Dict[key→asset_id]` 구성·반환(2-tuple 또는 별 attr). key: entity_id(base char/prop/outlook), `composite:{cid}:{oid}`, `state_variant:{cid}:{state}`. 이미 ImageAsset row 쿼리하므로 asset.id 확보 쉬움. base entity ref(ref_image_map)의 asset_id는 reference_face/prop asset 쿼리로 보강.
- `resolve_refs_for_prompt`: labeled_refs.append 시점마다 ref_role_metadata dict에 `asset_id`(asset_id_map[key])와 `pipeline_role`(reference_face/reference_outlook/reference_composite/character_state_variant/prop) 추가. key 없으면 asset_id 생략(unresolved).
- ★ caller가 asset_id_map을 넘겨야 함 → resolve_refs_for_prompt(_set) 시그니처에 `scene_ref_asset_id_map` kwarg 추가(기본 None=byte-identical). None이면 asset_id stamp 스킵.

### 2. scene_generation_coordinator.py — 수집 일반화 + call_id
- bg attach(710/1343): metadata에 `asset_id`=_bg_id, `pipeline_role`="background_render" 추가(bg_id 유지).
- var_result(2336, batch 유사): `pose_guide_asset_ids` → 유지하되 추가로
  - `actual_attached_image_ids`: ref_role_metadata 전체에서 asset_id(또는 bg_id) 수집, dedup, 순서보존.
  - `actual_attached_refs`: `[{asset_id, role(pipeline_role or ref_role), label(labeled_refs[i][0])}]` — label은 표시/감사용.
  - `unresolved_attached_refs`: asset_id 없는 ref의 label/role 목록(prev_shot 등).
  - labeled_refs와 ref_role_metadata는 length-parity(invariant) → zip으로 label 매칭.
- build_scene_ref_image_map 호출부에 asset_id_map 받아 resolve_refs에 전달.

### 3. scene_persistence_service.py — save_scene_variations write
- input_image_ids = vr["actual_attached_image_ids"](없으면 기존 _guide_ids fallback), dedup.
- generation_call_id: vr.get("generation_call_id") 또는 still_id 구조키로 llm_call_log 조회(scene_image_gen, status success, metadata_json.still_id 매칭, 최신) best-effort.
- pipeline_metadata에 `actual_attached_refs`, `unresolved_attached_refs` 병합.
- annotate_generated_asset input_image_ids 인자 확장.

### 4. (best-effort) generation_call_id 링크 헬퍼
- save_scene_variations 안에서 still_id로 llm_call_log 조회. 라벨 파싱 아님(구조키). 한 still에 variation N개 → 감사수준 per-still 링크 허용(문서화).

## Acceptance (Codex, 좁게)
- 재생성 2~3 scene asset: generation_call_id NOT NULL, input_image_ids에 실제 bg/ref/guide/composite UUID.
- pipeline graph generated_input 엣지로 모달 direct input에 배경/마네킹/composite/state 표시.
- llm_call_log label count ≈ actual_attached_refs count.
- reference_image_ids char/prop lineage 유지(무변경).
- OFF/기본(asset_id_map None) byte-identical.

## 검증
- 결정론 유닛(asset_id 수집/dedup, resolve stamp) + 재생성 canary(S12 sh7 등 시신샷 1개 redo → DB 확인 + 모달 육안).
