
    2j                   	   d Z ddlmZ ddlZddlmZ ddlmZmZm	Z	m
Z
mZmZmZ ddlmZ  ej                   e      ZdZi dd	d
dddddddddddddddddddddd d!d"d#d$d%d&d'd(d)i d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTdUdVdWZdZdZdZdZdXZdXZdFZdFZd ZdYZdZZd[Z d\Z!d0Z"d<Z#d"Z$dZ%d]Z&d^Z'd_Z(d`Z)dHZ*d>Z+daZ,d
dhZ-dhZ.ddhZ/dhZ0dZ1dbZ2dcZ3ddZ4dZ5deZ6dZ7dZ8dZ9dJZ:dZ;d*Z<d*Z=d(Z>dfZ?d4Z@d6ZAddgZBd2ZCedheCdiiZDdjZEddkZFddlZGddmZH	 	 	 	 	 	 ddnZIddoZJdpZKe'e)dqZLdrZMddsZNddtZOe>f	 	 	 	 	 	 	 	 	 dduZPddvZQddwZRddxZSddyZT	 	 	 	 	 	 ddzZU	 	 	 	 	 	 	 	 	 	 	 	 dd{ZV	 	 	 	 dd|ZWdddddd}d}dd}ddddd}dd~dd}d}ddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZXddZY	 	 	 	 	 	 ddZZ	 	 	 	 	 	 ddZ[	 	 	 	 	 	 	 	 ddZ\	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ]	 	 	 	 	 	 	 	 ddZ^	 	 	 	 	 	 	 	 ddZ_	 	 	 	 	 	 ddZ`	 	 	 	 	 	 ddZaddZbddd}dddd}ddd}d
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZcdddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddddddZedddddZf	 dd}d	 	 	 	 	 	 	 ddZgddZhddZiddZj	 d	 	 	 	 	 ddZk	 	 	 	 	 	 ddZl	 d	 	 	 ddZm	 	 	 	 	 	 	 	 ddZn	 dddd	 	 	 	 	 	 	 ddZodddZp	 d	 	 	 	 	 	 	 ddZq	 	 	 	 	 	 ddZrddZsddZtdddddddddddd}d}d}ddddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZu	 	 	 	 	 	 ddZv	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZwddZxddddd}e1d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZye1d}d}f	 	 	 	 	 	 	 	 	 ddZze1dd}d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ{e1f	 	 	 ddZ| e}h d      Z~ddZd}d	 	 	 	 	 	 	 	 	 	 	 d dZdhZde5ddd	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddÄZddĄZdZddƜ	 	 	 	 	 	 	 	 	 ddǄZe5f	 	 	 ddȄZ	 	 	 	 	 	 d	dɄZdd}ddʜ	 	 	 	 	 	 	 	 	 	 	 	 	 d
d˄Ze3ddddΜ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddτZy(  u#  still_recipe — 스틸 프롬프트 조립·참조 규칙 (2026-07-13 레시피 이식).

s39 `_build_prompt` + s40 `stage_stills` 참조 조립의 production 이식(순수
로직 — 파일/DB/LLM 없음, 서비스 어댑터는 still_recipe_service).

참조 규칙 (s40/s41 실증):
  - bgonly 샷: [플레이트만] + NO PEOPLE 절 + 자세 정본 절 생략(인물 유도 방지)
  - prev 샷:   [플레이트 + prev **선정본**(_sel) + 엔티티] + usage_en 절 (콘티 없음)
  - 일반 샷:   [플레이트 + 경량 콘티 + 엔티티]

텍스트 안전망 (전부 범용 계약, prompts/_base/still_recipe 팩):
  head(TIME OF DAY lock) → SHOT TEXT → LOCATION lock → CONTINUES/usage(prev)
  → 자세 정본 → REALIZE → EXPRESSION_REALISM → PROP_ORIENTATION
  → MOVEMENT/FIGURES → CARRIED → PEOPLE(traits 병기)/NO PEOPLE → no_text.

실험의 `_soften`(고어 치환)·국가 하드코딩 head 는 이식하지 않는다 —
world_anchor 는 호출자가 프로젝트 데이터에서 파생해 주입(빈 문자열 허용).
    )annotationsNPath)AnyDictListOptionalSequenceSetTuple)load_promptstill_recipe1z1.2026071323002z2.2026071600303z3.2026071602004z4.2026071623205z5.2026071711006z6.2026071921407z7.2026072015508z8.2026072115109z9.20260721154010z10.20260722112011z11.20260722023712z12.20260727053713z13.20260806233014z14.20260806234215z15.20260807120016z16.20260812183317z17.20260813031018z18.20260813112119z19.20260813144020z20.20260814130021z21.20260814170022z22.20260818010023z23.20260825155524z24.20260825211526z26.20260827052027z27.20260827052528z28.20260827062029z29.20260827140030z30.20260827170031z31.20260827190032z32.20260827191033z33.20260827230034z34.20260827234035z35.202608280010z36.202608281230z37.202608281500z38.202608281501z39.202608281530z40.202608281531z41.202608282130z25.202608270230z44.202609201830z45.202609201831z42.202609201620z43.202609201621)3637383940412544454243r6   r8   r9   r:   r;   r2   r3   r4   r5         zgpt-image-2.5-sunburst1536x864bgfirst_full_v3r7   c                 R    ddl m}  t        t        | dd            rt        S t
        S )uq  이번 판의 cine 팩 — 연출 재료를 넘길 때만 v24 다.

    ★플래그가 꺼졌는데 selector 를 올리면 안 된다: 팩 문자열이 스텝 지문에
     들어가 있어(`image_steps`) **완주본이 전량 재실행**된다. 스템 본문이
     같아도 그렇다 — 지문이 보는 것은 바이트가 아니라 버전 문자열이다.
    r   settings"still_cine_stage_direction_enabledF)app.core.configrB   boolgetattr#CINE_STAGE_DIRECTION_PROMPT_VERSIONCINE_TRANSFORM_PROMPT_VERSIONrA   s    W/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/still_recipe.pycine_pack_selectorrJ   h  s%     )GHBEJK22((    camera_frame_clause_no_dir"camera_frame_clause_no_dir_compactr1   c                    | rt         S t        S )u  `naturalism_clause` 가 **실제로 읽는** 팩 selector (감사 P1-A).

    ★조립과 지문의 **단일 출처**다. `broll_variation_stem` 과 같은 모양이되
     스템 이름은 두 갈래가 같아서 selector 만 돌려준다.

    Args:
        version_selector: `still_guidance_selector()` 결과 — grok 백엔드에서만
            채워지고 nb2 에서는 빈 문자열이다.
    )!NATURALISM_COMPACT_PROMPT_VERSIONNATURALISM_PROMPT_VERSIONversion_selectors    rI   naturalism_packrS     s     2B- ,*,rK   c                    | rdnd}|t         fS )u  b 롤 변주 절이 **실제로 쓰는** (스템 이름, 팩 selector).

    ★조립과 지문의 **단일 출처**다.

    종전에는 갈라져 있었다: 조립은 `grok_stem_version()` 으로 nb2=v17 /
    grok=v22 를 골랐는데, 지문(`image_steps`)은 `STILL_COMPACT_PROMPT_VERSION`
    ("17")을 **박아** 접었다. 그래서 grok 갈래에서는 컴팩트 판 바이트를 고쳐도
    지문이 안 움직여 완주한 샷이 clean skip 한다 — 고친 문안이 그림에
    안 닿는다. `camera_frame_stem` 이 같은 결함을 이미 한 번 겪었다.

    Args:
        version_selector: `still_guidance_selector()` 의 결과. grok 백엔드에서만
            채워지고 nb2 에서는 빈 문자열이다.

    Returns:
        `(스템 이름, 그 스템을 로드할 팩 selector)`. 두 판은 **본문이
        다르다** — grok 은 8,000B 상한 때문에 짧아야 한다.
    #broll_composition_variation_compactbroll_composition_variation)BROLL_VARIATION_PROMPT_VERSION)rR   stems     rI   broll_variation_stemrY     s    & 6F1. 	///rK   c                    | rt         S dS )u  백엔드 → 컴팩트 갈림 신호. **조립과 지문이 같이 부른다.**

    이 값이 `grok_stem_version` 의 유일한 입력이다. 종전에는 조립 쪽
    (`still_recipe_service`)이 제 안에서 직접 만들었고 지문 쪽은 그 값을
    아예 몰랐다 — 두 자리가 갈리면 조립은 새 스템을 쓰고 지문은 옛 값을
    접는 **조용한 clean skip** 이 난다.
     )STILL_COMPACT_PROMPT_VERSION)grok_backends    rI   still_guidance_selectorr^     s     ,8'?R?rK   c               L    t        | t              }|rt        |   t        fS d|fS )u$  CAMERA & FRAME 절이 **실제로 쓰는** (스템 이름, 팩 selector).

    ★조립과 지문의 **단일 출처**다 (2026-08-27 Codex 재지적).

    처음에는 지문이 base 스템만 접어 grok 의 컴팩트 문안 수정이 누락됐다.
    그 다음엔 **둘 다 접어** 반대편을 열었다 — nb2 설정 그대로 컴팩트
    바이트만 고쳐도 nb2 완주 샷이 mismatch 로 다시 산다. 안 쓰는 스템은
    접으면 안 된다.
    ★`still_image_backend` 가 이미 지문에 있다는 것은 **두 백엔드의 지문이
     서로 다르다**는 뜻일 뿐, 안 쓰는 스템이 기여하지 않는다는 뜻이 아니다
     — 내가 그걸 근거로 썼다가 틀렸다.

    Returns:
        `(스템 이름, 그 스템을 로드할 팩 selector)`.
    camera_frame_clause)grok_stem_versionCAMERA_FRAME_PROMPT_VERSIONCAMERA_FRAME_NO_DIR_STEMS"CAMERA_FRAME_NO_DIR_PROMPT_VERSION)rR   omit_camera_directionsels      rI   camera_frame_stemrg     s7    $ ,.I
JC *#.24 	4 #%%rK   c                    | rt         S |S )u  grok 조립에서만 컴팩트 스템으로 가른다 — **갈림의 단일 지점**.

    조건은 하나뿐이다: `version_selector` 가 실려 있는가. 그 값은
    still_recipe_service 가 `still_image_backend == "grok2"` 일 때만 채우고
    nb2 에서는 빈 문자열이다. 비어 있으면 `default`(기존 스템 버전)를 그대로
    돌려주므로 **nb2 조립은 바이트 동일**이다 — 이미 완주한 에피소드의 지문을
    흔들지 않는다는 것이 이 함수가 지키는 계약이고, 시험이 그것을 잠근다
    (tests/unit/test_grok_compact_stems_backend_split.py).

    왜 필요한가: xAI 이미지 모델의 텍스트 상한은 8,000바이트다(모델이 스스로
    선언하고 초과 요청은 400 으로 거부된다). 롤 하나가 상한을 넘으면 그 롤이
    죽고, 롤 하나가 죽으면 샷 전체가 죽는다(multiroll_select 가 예외를 그대로
    올린다). 컴팩트 지도 절(v17)만으로는 −1,752B 라 모자란다.
    )STILL_GROK_STEM_VERSION)rR   defaults     rI   ra   ra     s     '7"CGCrK   )drawn_mark_clauseprop_orientationnaturalism_clauseexpression_realismrealize_stillimmobile_physics)ro   rm   zk7900|drawn_mark_clause,prop_orientation,naturalism_clause,expression_realism,realize_still,immobile_physicsc                     ddl m}  |  ddj                  t              z   dz   dj                  d t	        t
        j                               D              z   S )u`   덜어내기 정책의 현재 서명 — 값에서 뽑는다(손으로 올리는 버전 아님).r   GROK_TEXT_BYTE_LIMIT|,c              3  0   K   | ]  \  }}| d |   yw)=N ).0kvs      rI   	<genexpr>z-grok_shed_policy_signature.<locals>.<genexpr>)  s'      I G -1Aqs!A3Z Gs   )!app.modules.llm.grok_image_clientrs   joinGROK_SHED_ORDERsortedGROK_SHED_STEM_SELECTORitemsrr   s    rI   grok_shed_policy_signaturer   $  s]    F#$A&/)BBHH I &'>'D'D'F GI II JrK   c                
   ddl m} dfd	 t        |      r       nd}|r|| v r|S fd |t              D        D cg c]
  }|r|| v r| }}|rt        |t              S dS # t        $ r dY `w xY wc c}w )u{  이 프롬프트에 **문자 그대로 들어 있는** 그 절의 문안. 없으면 "".

    ★왜 1순위 판만 보면 안 되나 (2026-09-18 컨트리로드 실측).

    조립은 절을 `guidance_version or prompt_version` 판으로 렌더한다. grok 이
    **주 백엔드**면 guidance=컴팩트(v17)라 1순위 판으로 찾으면 맞는다. 그런데
    nb2 가 주 백엔드이고 grok 이 **안전 사다리의 교차 백엔드**로 불릴 때는
    프롬프트에 v1/3/4/5 판 문안이 들어 있어 컴팩트 문자열이 **하나도 안 맞고**,
    한 절도 못 덜어낸 채 상한에 걸려 그 단계가 통째로 죽었다
    (S71sh13: 라벨까지 8,653B > 8,000B, 덜어냄 0개 — 다른 판으로도 찾으면
    두 절이 빠져 라벨까지 7,729B 로 들어간다. 아래 수치는 모두 라벨 포함 총량).

    그래서 1순위가 안 맞으면 **그 스템을 가진 나머지 판**의 문안으로도 찾는다.

    - 판 목록은 디스크에서 읽는다 — 조립 쪽 팩 선택을 여기 다시 적으면
      두 자리가 갈린다.
    - 지우는 것은 언제나 **프롬프트에 문자 그대로 있는 문안**이다.
    - 후보가 여럿이면 **가장 긴 것** — 짧은 판이 긴 판의 부분일 때 가운데만
      도려내 남은 글이 망가지는 것을 막는다.

    ★못 읽는 판·모르는 selector 여도 **선진 않는다** — 1순위만 건너뛰고 다른
     판으로 계속 찾는다(예외를 올리면 덜어내기가 아니라 롤이 죽는다).

    ★한계 두 가지 — 넘겨짚지 말 것 (Codex 리뷰 2026-09-18):
     ① 1순위가 **부분 문자열로** 맞으면 그대로 1순위를 지운다. 다른 판이
        `1순위 + 덧글` 한 덩어리로 실렸다면 앞부분만 잘린다. 「가장 긴 것」은
        fallback 끼리만 걸리는 보호다.
     ② 옛 판에는 절이 **합쳐져** 있는 것이 있다(`realize_still` v1 안에
        인물 실체 절 본문). 지금 조립은 realize 를 v37/v38 로만 싣지만,
        그런 판을 싣는 경로가 생기면 덜어낼 후보가 아닌 절까지 함께 빠진다.
    r   )pack_versions_with_stemc                d    	 t        t        |       j                         S # t        $ r Y yw xY w)Nversionr[   )r   _MODULEstrip	Exception)r   rX   s    rI   _textz!_clause_present_in.<locals>._textO  s2    	wg>DDFF 		s   # 	//r[   c              3  8   K   | ]  }|k7  r
 |        y wNrx   )ry   r{   r   primary_versions     rI   r|   z%_clause_present_in.<locals>.<genexpr>]  s%      
DO# !HDs   )key)r   strreturnr   )app.modules.prompt_loaderr   resolve_prompt_versionr   r   maxlen)	promptrX   primary_selectorr   primarytothersr   r   s	    `     @@rI   _clause_present_inr   -  s    @ B01AB )8eO$RG7f$
5gtD

a f	 	
 
   $*3v31r1  
s   A/ 	B /A=<A=c           	        dd} ||       |z   |k  r| g fS | }g }t         D ]  } ||      |z   |k  r ||fS t        ||t        j                  ||            }|s:d|z   |v r|j	                  d|z   dd      }n1|dz   |v r|j	                  |dz   dd      }n|j	                  |dd      }|j                  |        ||fS )u  xAI 텍스트 상한에 맞춰 일반 규칙 절만 덜어낸다.

    Args:
        prompt: 조립이 끝난 롤 프롬프트.
        extra_bytes: 프롬프트 밖에서 상한에 함께 세어지는 바이트(참조 라벨).
        limit: 상한(바이트).
        version_selector: 덜어낼 절을 렌더할 **1순위** 팩. 안 맞으면
            `_clause_present_in` 이 그 스템을 가진 다른 판으로도 찾는다
            (조립 팩이 다른 사다리 교차 백엔드 경로).

    Returns:
        (맞춘 프롬프트, 덜어낸 스템 이름 목록). 상한 안이면 원본과 빈 목록.

    상한을 넘는 롤은 그대로 두면 롤이 죽고 롤이 죽으면 샷이 죽는다
    (multiroll_select 가 예외를 그대로 올린다). 그림이 아예 없는 것보다
    일반 규칙 문안 몇 절이 빠진 그림이 낫다는 판단이고, 무엇을 덜었는지는
    호출부가 반드시 기록한다(조용한 손실 금지).
    c                6    t        | j                  d            S )Nutf-8)r   encode)texts    rI   _bzfit_grok_prompt.<locals>._b~  s    4;;w'((rK   

r[      )r   r   r   int)r   r   r   getreplaceappend)	r   extra_byteslimitrR   r   outshedrX   r   s	            rI   fit_grok_promptr   f  s    0) 
&zK5(rz
CDc7[ E) 9 "#''.>?
 D=C++ftmR3CF]c!++dVmR3C++dB*CD    9rK   c           	     n    	 t         |    S # t        $ r  t        d| dt        t                d      w xY w)Nz-unknown still_recipe prompt version selector z	 (known: ))PROMPT_VERSION_MAPKeyError
ValueErrorr   )selectors    rI   r   r     sK    
!(++ 
;l)F+=$>#?qB
 	

s    )4c                :    ddl m}  |t        t        |             S )uH  레시피 팩의 **내용** 해시 — judge_pack_content_hash 와 동형 (#77-A).

    버전 문자열 스탬프는 팩 내용이 바뀌어도 안 움직일 수 있다 — 실제 로드
    디렉토리의 bytes 해시를 config_hash 에 접어, 팩만 올린 재실행이
    whole-step clean skip 되던 구멍을 막는다.r   )pack_dir_content_hash)r   r   r   r   )r   r   s     rI   recipe_pack_content_hashr     s     @ *@*JKKrK   c                <    ddl m}  |t        t        |       |      S )u<  레시피 팩 안 **한 스템**의 내용 해시 (2026-08-13 Codex R2).

    소비 범위가 스템 하나인 절(b 변주·몸-지지)은 스템 bytes 만 지문에
    접는다 — 디렉토리 전체 해시는 동거 스템(컴팩트 지도 절·identity 등)
    개정까지 전량 재생성을 일으킨다.r   )pack_stem_content_hash)r   r   r   r   )r   rX   r   s      rI   recipe_stem_content_hashr     s"     A!'149 9rK   c                     i  xs i j                         D ]"  \  }}|xs i j                  d      }|s||<   $ d fd}|S )u  entity_lookup → short id(C##/O##) 를 UUID 로 푸는 callable.

    `extract_outfit_assignments(..., norm_uuid=...)` 에 그대로 넣는 정본이다.
    호출부마다 다시 짜면 **재는 쪽과 도는 쪽이 같이 틀린다** — 2026-08-29 에
    이 자리가 `unknown outlook_id` 로 7샷 중 5샷을 죽였고, 그때 원인은
    entity_lookup 에 outlook canon 이 안 실린 것이었다(episode link 누락).
    무료 preflight 가 **같은 함수**를 불러야 그 결함을 미리 잡는다.
    short_idc                X    t        | t              r| sy | v r| S j                  |       S r   )
isinstancer   r   )r{   entity_lookupshort_to_uuids    rI   
_norm_uuidz+build_short_id_resolver.<locals>._norm_uuid  s0    !S!H  ##rK   )r{   r   r   Optional[str])r   r   )r   _eid_e_sidr   r   s   `    @rI   build_short_id_resolverr     sR     %'M"(b//1bb~~j)"&M$ 2
$ rK   c           
     :   ddl }| si S 	 |j                  |       }t        |t              sddt        |      j                   iS i }g }|D ]p  }|t        |t              s(|j                  dt        |      j                          ?|j                  d      }|St        |t              s(|j                  dt        |      j                          |D ]  }	t        |	t              s(|j                  d	t        |	      j                          ;|	j                  d
      xs d}
|	j                  d      xs d}|
s|s|j                  d       { ||
      } ||      }|s|j                  d|
       |s|j                  d|       |j                  |t                     j                  |        s |r%ddj                  t        t        |                  iS |j                         D ci c]   \  }}t!        |      dkD  s|t        |      " }}}|rd|j#                  |d      iS |j                         D ci c]  \  }}|t%        t'        |             c}}S # |j                  $ r}dd| icY d}~S d}~ww xY wc c}}w c c}}w )u  t2i_variations_json[*].outfit_assignments → {char_uuid: outlook_uuid}.

    production shape(Codex 2차 리뷰 B1): VE 에는 outlook 이 실리지 않고
    배정은 여기(short id C##/O##)에만 있다. norm_uuid 는 short→UUID 정규화
    callable(불일치=None). variation 간 상이 배정은 임의 first 선택 금지 —
    {"__conflict__": <상세 json>} 반환(호출자 fail-closed).

    3차 리뷰 M3: '배정 자체가 없음'(={})과 'SOT 존재하나 손상/미해결'을
    구분 — malformed JSON·unknown character_id/outlook_id 는
    {"__invalid__": <사유>} 반환(호출자 fail-closed, base 얼굴로 조용한
    degrade 금지).
    r   N__invalid__zjson_decode_error: zroot must be list, got zvariation must be dict, got outfit_assignmentsz%outfit_assignments must be list, got zassignment must be dict, got character_idr[   
outlook_idzempty assignment objectzunknown character_id: zunknown outlook_id: ; r   __conflict__F)ensure_ascii)jsonloadsJSONDecodeErrorr   listtype__name__dictr   r   
setdefaultsetaddr~   r   r   r   dumpsnextiter)raw_variations_json	norm_uuid_json
variationsexcper_charinvalidvarassignmentsaraw_cidraw_oidcidoidco	conflictss                    rI   extract_outfit_assignmentsr     s     	<[[!45
 j$')$z*:*C*C)DE
 	
  "HG;#t$NN.tCy/A/A.BC gg23+t,NN7$--.0 Aa&3DG4D4D3EF eeN+1rGeeL)/RG789G$CG$C!7{CD!5g[ABSU+//4) # L tyyG)=>??*2..*:I*:$!Qc!fqjF1I*:IIIE JKK)1)9:)9AAtDG})9::o    <!4SE:;;<h J ;s/   I. 9JJJ.J=J	J	Jc                    |r ||v r||   j                  d      }|r||v r|S |j                  |       }|rd|  d| |v rd|  d| S | |v r| S y)uK   캐릭터 ref 키 우선순위 — state_variant > 의상 composite > base.r   z
composite::Nr   )eidsid
state_sidsoutlook_by_eidscene_ref_image_mapr   ols          rI   resolve_char_ref_keyr   #  s~     sj o!!%(3--J			C	 B	
3%q%)<<C5"&&
!!
rK   c                    g }g }| D ]Y  }|xs i j                  d      }|s||vr|j                  |       |j                  d      dv sD||vsI|j                  |       [ ||fS )u0  (input_image_ids 전체, entity 전용 reference_image_ids) 분리.

    Codex 2차 리뷰 H5: reference_image_ids 는 visible entity/prop 구조
    lineage 전용 — plate/conti/prev 를 넣으면 pipeline_graph 가 거짓
    reference edge 를 만든다. 실첨부 전체는 input 채널에만.
    asset_idrole)character_refprop_ref)r   r   )attached_refs	input_ids
entity_idsraids        rI   partition_lineage_idsr  8  sz     IJwBmmJ'iS!55=99z!c"  j  rK   Fedit)run_fnroll_prompts	roll_refsref_role_metadataroll_ref_metadataparallel_rolls
judge_flipflip_prioritycritique_selected_prompt_onlyjudge_prompt_headerfix_rejudge_fncomposition_critique_fnmake_fix_gen_fnfix_ref_gatefix_missing_textsrepair_methodstill_regen_textswinner_gate_applicablegate_reroll_enabledgate_rejudge_fngate_reroll_allow_fngate_rejudge_identitygate_regen_textsc        $   	        |ddl m}$ |$} ||       }%| ||       n|%}&d4fd}'i }(|||(d<   |||(d<   |||(d<   |||(d<   |rd	|(d
<   |rd	|(d<   |||(d<   |rd	|(d<   |||(d<   |||(d<   |||(d<   |r5d	|(d<   |xs i })|)j                  dd      |(d<   |)j                  dd      |(d<   |dk(  rO||(d<   |%|(d<   |xs i }*|*j                  dd      |(d<   |*j                  dd      |(d<   |*j                  dd      |(d<    |d5i d| d|d|d |d!|%d"|d#|
r|ndd$|
r|&ndd%|	d&|
d'|d'   d(|d(   d)|d)   d*|j                  d*d      d+j                  j                        d,|d-|d.|d/| d0|!d1|"d2|#d3|'|(S )6u  단일/A·B branch 공용 multiroll 실행 (Codex 74ebf365 B1).

    계약: gen callable 은 branch 당 **1회** 생성해 gen_fn/fix_gen_fn
    양쪽에 동일 객체 전달(critique ON 에서 미정의 참조 금지 — NameError
    실행 차단 실측의 재발 방지, 모듈 헬퍼로 추출해 실행 경로를 유닛
    테스트로 잠근다). records=durable persist(rec_key 단위).

    make_fix_gen_fn (2026-08-13 G+G46): 제공 시 fix i2i 만 **별도 gen**
    (branch 당 1회 생성)으로 가른다 — 사용자 확정 "수정은 Grok": 롤 생성
    (nb2)과 수정 편집(grok)의 백엔드가 다른 구성의 이음새다. None(default)
    =기존 계약(gen 재사용) byte-identical.

    still-variants(2026-07-17) kwargs 는 **미사용 시 전달 자체를 생략** —
    OFF 경로의 기존 run_fn 호출·지문·record shape 를 그대로 유지한다
    (Codex 설계 리뷰 R6/R7 조건).
    Nr   )run_multiroll_selectc                D    | j                   <   j                          y r   )datasave)recrec_keyrecordss    rI   _persistz#run_branch_select.<locals>._persist  s     #WrK   r  r  r  r  Tr  r	  r
  r  r  r  r  r  missing_headr[   fix_missing_headmissing_tailfix_missing_tail
regenerater  regen_gen_fn
regen_head
regen_tailregen_faults_headertagr   labeled_refsout_stemgen_fnjudge_fncritique_fn
fix_gen_fn
roll_countcritique_enabledfix_headfix_tail	fix_labelfix_ref_labelrecordextra_fingerprintr  r  r  r  r  r  persist_record_fn)r  Dict[str, Any]r   Nonerx   )%app.modules.pipeline.multiroll_selectr  r   r  )+
branch_tagbranch_refsr   r.  r   r!  make_gen_fnr0  r1  r3  r4  judge_textsr:  r  r  r  r  r  r  r	  r
  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  genfix_genr"  variant_kwargs_mt_rts+     `  `                                     rI   run_branch_selectrH  P  s   z ~	
 &
j
!C'6'B
#  &(N)5~&&/{#$.?*+$.?*++/'('+|$ *7'$:>67&0C,-!+9'(*4K01 *.~&%2-0WW^R-H)*-0WW^R-H)*
 $*7'
 *-~&%2'*ww|R'@|$'*ww|R'@|$03!21',-   ! 	
   $4K /7D  * Z( Z( k*& "ooor:'( ||()* ,+,  6-. 0/0 (12 234 456 *78 #
; rK   c                    | j                  d      ry| j                  d      s| j                  d      sy| j                  d      }t        |t              r%|j                  d      r|j                  d      dk(  S y)	u  수리 단계 산출(fix i2i/재생성 후보)이 최종 `_sel` 이 됐는가 —
    자산 provenance(exact 호출 태그·생성 모델·prompt_used·fix_applied)의
    **단일 판정** (2026-08-13 Codex GG46 R1 BLOCK-2).

    종전에는 소비처마다 `fix_prompt` **존재**로 판정해, 재판정(fix
    rejudge)에서 수정본이 **져서** 원본 롤이 최종인 샷도 fix 호출에
    링크되고 fix_applied=True 로 남았다(반대로 이겼을 땐 모델·프롬프트
    기록이 롤 것). 계약:
      · 재평가 record 가 있으면 그 승부가 SOT — winner=="B"(수정본).
      · 없으면 legacy(수리 산출 무판정 확정) — 수리 프롬프트가 있으면 채택.
      · 수리 시도 자체가 없으면(fix_skipped 포함) False.
    fix_skippedF
fix_promptregen_promptfix_rejudgewinnerBT)r   r   r   )r9  rejudges     rI   fix_stage_wonrQ    sg     zz- JJ|$

>(Bjj'G'4 W[[%:{{8$++rK   c                   t        |       r:| j                  d      dk(  r| j                  d      n| j                  d      }|r|S | j                  d      xs i }| j                  d      }|j                  |      xs |S )uB  최종 asset prompt_used — 실제 생성에 쓰인 프롬프트 (리뷰 NARROW-4).

    수리 산출이 최종이면(`fix_stage_won`) 최종 bytes 를 만든 프롬프트는
    수리 프롬프트다 — 롤 프롬프트를 실으면 모델·지시가 다른 호출의
    산물로 오독된다(Codex GG46 R1 BLOCK-2 — Grok fix 에서 교차 모델로
    드러났지만 nb2 fix 도 같은 결함이었다). 그 외에는 변형 모드=선정
    라벨의 roll_prompts 전문(시각 접근 provenance 보존), legacy/결손=
    base 그대로 (기존 값 불변).
    repair_moder'  rL  rK  r  selected)rQ  r   )r9  base_promptrepair_promptrprf   s        rI   effective_prompt_usedrX    s}     V zz-(L8 JJ~&L) 	   	N	#	)rB
**Z
 C66#;%+%rK   c                    t        |      r|j                  d      dk(  rdnd}|  | S |j                  d      xs dj                         j                         }|r|  d| S dS )u?  승자 branch record → 최종 산출을 만든 exact 호출 태그 (Codex H2).

    수리 산출이 최종이면(`fix_stage_won` — 재평가 승부 반영) 수리 호출
    태그('{branch}_fix'/'{branch}_regen'), 아니면 '{branch}_{selected
    소문자}' (run_multiroll_select 태그 관례). 종전의 fix_prompt 존재
    판정은 재판정에서 진 fix 의 호출로 최종 자산을 잇는 거짓 링크였다
    (Codex GG46 R1 BLOCK-2). selected 결손이면 빈 문자열 — resolver 는
    exact miss 를 None 으로 반환(거짓 링크 금지).
    rS  r'  _regen_fixrT  r[   _)rQ  r   r   lower)r?  r9  suffixrf   s       rI   winner_exact_multiroll_tagr_    ss     V$jj7<G( 	fX&&::j!'R
.
.
0
6
6
8C$'j\3% /R/rK   c                0    |s| sy|st        d|  d      y)u$  (DEPRECATED — 2026-07-16 복잡 구조물=A/B 재설계로 생산처 소멸)
    direct_seed 샷 장소 권위 결정 — classify prev 판정이 SOT.
    서비스 live 분기는 제거됨; 함수는 보존만(삭제 금지 원칙).

    Codex f7c81336 BLOCKING-1: prev_tag(classify SOT)가 있으면 mode 는
    prev_only 로 **고정** — prev asset 결손은 seed_only 로의 조용한 정책
    전환이 아니라 ValueError(fail-closed, 샷 실패 격리). bg_only 는
    classify 가 prev 를 무효화하는 경로라 seed_only.
    	seed_onlyu+   direct_seed prev_only 요구(classify prev=uN   )인데 prev 선정본/primary 결손 — seed_only 전환 금지(fail-closed)	prev_onlyr   )prev_tagprev_sel_existsbg_onlys      rI   $decide_direct_seed_location_ref_moderg  2  s5     h9( DU U
 	
 rK   c                6    |s|ry|r| r|rdS dS |ry| s|rdS dS )ui  스틸 플레이트 처리 모드 결정 (재리뷰 HIGH-2 — caller 조건의 순수
    함수 분리).

    - "bg_late_select": bg_only(콘티 없음) 즉석 VLM 판정 — flag ON +
      플레이트 실재 시에만 (기존 계약).
    - "reconcile": 콘티형 샷(non-bg·non-prev) 권위/콘티 플레이트
      reconciliation — flag ON **또는** complex_ab 면 플레이트 mapping
      이 비어 있어도 호출(R1 배선 완성: 재개 시 mapping 소실이 권위
      해석을 건너뛰던 누락 차단). flag OFF 일반 샷 = legacy 유지.
    - "none": lane/맵 플레이트/그 외.
    nonebg_late_select	reconcilerx   )plate_select_on
complex_abrf  	prev_used	lane_usedis_map_plateplate_presents          rI   plate_flow_moderr  K  s?    * L = 	
6<	
 *j;EvErK   c                   | r| j                  d      xs d}|rt        |      j                         st        d|d      |r)t	        |      t	        |      k7  rt        d| d| d      t        |      | j                  d      xs i fS |rYt        |      }|j                         st        d	|d
      |)t	        |      t	        |      k7  rt        d| d| d      |dfS |dfS )u  콘티형 샷의 스틸 플레이트 해석 — 권위 기록 우선 (R1 선행 고정).

    Codex 설계 리뷰 BLOCKING-1: 스틸 시점 재판정은 콘티가 그려진 플레이트
    와 다른 플레이트를 스틸에 붙여 A/B 가 '콘티 유무' 외 변수를 비교하게
    만든다. 콘티(+A/B 양 브랜치)와 스틸은 정확히 같은 plate path 를
    소비해야 한다.

    - authority_entry 존재: 그 plate_path 가 SOT — 파일 결손·콘티가 다른
      플레이트로 생성된 경우 = ValueError(fail-closed, 상류 재실행 필요).
    - authority_entry 부재(판정 비대상/flag 조합): **콘티가 그려진
      플레이트가 SOT** (배치 리뷰 BLOCKING-2 — current mapping 이 비어도
      콘티 플레이트로 해석, 결손·불일치=fail-closed).
    - 콘티 기록도 없으면 현재 배정 유지 (콘티 생략 샷).
    
plate_pathr[   u,   plate authority 기록 플레이트 결손: u&    — shot_conti_light 재실행 필요uD   plate authority 와 콘티 생성 플레이트 불일치 (authority=z	 / conti=u'   ) — shot_conti_light 재실행 필요r9  u#   콘티 생성 플레이트 결손: u*    — 상류 배경/콘티 재실행 필요NuJ   콘티 생성 플레이트와 현재 배정 플레이트 불일치 (conti=z / assigned=u+   ) — 상류 배경/콘티 재실행 필요)r   r   is_filer   r   )authority_entryconti_plate_pathcurrent_platepcps        rI   resolve_conti_plate_authorityr{  l  s:   ( -3Q)>qe D4 4  $4 5Q ?S	*:); <44 
 Aw++H5;;;"#zz|56F5I J8 8  $]);s2w)F*+< G88 
 4x$rK   c                    |s
t               S t               }| xs g D ]S  }|xs i j                  d      }t        |j                  d      xs g       }|s9||v s>||v sC|j                  |       U |S )u  prev 참조 샷에서 캐릭터 참조를 제외할 정본 자세 인물 short_id.

    E2E6 피드백 ④ (2026-07-16): pose_canon(LLM 가동성 판정 SOT)이 현재
    샷과 prev 샷을 **모두** 커버하는 인물 — prev 스틸이 이미 자세·외형
    LOCK 이라 별도 캐릭터 참조는 이중 참조 충돌(S12sh9 실측). short_id
    미해소(None)면 제외하지 않는다(참조 유지=안전측, 글자 매칭 금지).
    character_short_idshots)r   r   r   )
pose_canonr,  rd  r   pcr   covs          rI   locked_pose_short_idsr    sr     uECBxRnn12"&&/'R(3#:(c/GGCL	 
 JrK   c                     t        | |      d   S )uR   샷 VE, 비면 같은 씬 선택 샷 VE 합집합 fallback (실험 공통 규칙).r   )ve_ids_for_shot_ex)	ve_by_keyr   s     rI   ve_ids_for_shotr    s     i-a00rK   c                    | j                  |      xs g }|rt        |      dfS t               }| j                         D ]&  \  \  }}}||d   k(  s|j	                  |xs g        ( t        |      dfS )ug  `ve_ids_for_shot` + **확정 배정인지** 여부.

    두 번째 값 False = 샷 자체의 VE 가 없어 같은 씬 다른 샷에서 끌어온 추측이다.
    호출자는 이걸로 PEOPLE 절의 배타 조항을 붙일지 결정한다 — 추측에 "never
    anyone else" 를 붙이면 정답을 배제한다(실측 S11sh3, 팩 v14 주석 참조).
    Tr   F)r   r   r   r   updater   )r  r   idsscene_unionsi_shir{   s          rI   r  r    sw     --

"C
Cy$EK"*
TAQ<qwB' + +%%rK   c                    ddl }ddlm} t        | t        |f      rd|  S t        | t
        t        f      r+d|j                  t        |             j                          S d| S )u~  참조 원본의 신원 — 라벨·순서에 기대지 않는 접합 키.

    라벨로 맞출 수 없다: `_attach` 는 이름(`김선영`)을 쓰고 `build_still_refs`
    는 서식을 입힌 문안(`CHARACTER REFERENCE — 김선영: …`)을 쓴다. 순서로도
    못 맞춘다: `handled_by` 갈래는 char_refs 의 부분집합만 싣는다.

    원본 자체는 두 자리가 같은 객체를 주고받으므로 신원이 어긋날 수 없다.
    ★`id()` 를 쓰지 않는다 — 중간에 바이트를 갈아 끼우는 자리가 있다
    (`outdoor_site_layout_step.py:357`·`visual_continuity_anchor_step.py:490`).
    r   Nr   zpath:zsha256:zrepr:)	hashlibpathlibr   r   r   bytes	bytearraysha256	hexdigest)srcr  _Ps      rI   ref_source_keyr    sc     "#Ry!se}#y)*c
3==?@AA3'?rK   r[   )
lane_sketch	lane_seedlane_direct_seedstructure_seedseed_bgprompt_versiongeom_authority
handled_byprev_people_ruleframing_scale_ownedplatec           	     |   t        |      }|
B|t        vrt        d| d      ||	t        d      |t        d      ||t        d      |	3|t        vrt        d| d      |t        d      ||t        d      ||r[|X|t        vrt        d| d	      g }|K|rd
t        t
              fnd|f\  }}|j                  t        t        ||      j                         |f       |1|j                  t        t        d|      j                         |f       | r|S ||j                  t        ||      |f       t        t        d|      j                         }t        t        d|      j                         }|D ](  \  }}|j                  |j                  |      |f       * |D ](  \  }}|j                  |j                  |      |f       * |S g }|3|1|j                  t        t        d|      j                         |f       |
3|1|j                  t        t        d|      j                         |
f       |	1|j                  t        t        d|      j                         |	f       | r||j                  t        ||      |f       |rdt        t        dt        t                    j                         }|D ]2  \  }}|s	||v s|j                  |j                  |      |f        |S  |S |`|j                  t        ||      |f       |rt|r|j                  t        t        dt        t                    j                         |f       n3|1|j                  t        t        d|      j                         |f       t        t        d|      j                         }t        t        d|      j                         }|D ](  \  }}|j                  |j                  |      |f       * |D ](  \  }}|j                  |j                  |      |f       * |S )uO	  라벨드 참조 조립 — (라벨, 이미지 소스[Path|bytes]) 순서 리스트.

    char_refs/prop_refs = [(이름, 소스)]. bgonly 는 플레이트 외 전부 제외.
    prev 샷은 콘티 없음(분류 단계 배제) — prev_sel 이 있으면 conti 는 무시,
    플레이트도 제외(E2E6 ⑦: prev=배경 SOT, refs=[prev+엔티티]).

    geom_authority (2026-08-07, 팩 v15): 좁고 복잡한 실내만 참. prev 샷에도
    콘티를 **함께** 붙여 공간 기하를 준다(상세는 해당 분기 주석). 기본값
    False = 기존 조립 byte-identical.

    handled_by (2026-08-07, 분류 팩 v4): 그 순간 물건을 다루고 있는 사람.
    bgonly 샷에서 그 사람의 참조 **한 장만** 손 전용 라벨로 싣는다. 빈
    문자열 = 기존 조립 byte-identical.

    ★E2E6 ⑦ 스코프 = **non-lane 경로 한정** (Codex 배치 리뷰 CONTRACT-2
    명시): lane 샷은 사용자 설계 v2(2026-07-14)의 명시 결정대로 prev 가
    있어도 스케치(+seed)를 유지한다(아래 lane 분기 — 스케치=배치 SOT,
    prev=연속성 추가 참조). lane 의 장소 권위 단일화(location_ref_mode
    prev_only/seed_only)는 outdoor_frame_mode(피드백 ③) 구현에서 통합
    예정 — 여기서 암묵 제거하지 않는다.

    lane_sketch(Stage D, v2+ 팩 전용): 야외 lane 샷 — 플레이트/photo canon
    참조 0, [마커 스케치(+lane_seed 구조물 룩)] + prev + 엔티티.
    prev 가 있어도 스케치 유지(설계 v2: 레인 스틸 참조에 prev **추가**).
    bgonly lane 샷도 스케치(+seed)는 유지 — 구조물 룩·배치 SOT.

    structure_seed(2026-07-16 복잡 구조물=A/B, v4 팩 전용): 플레이트와
    **공존**하는 STRUCTURE LOOK — A/B 양 브랜치와 bg_only 에 공통 부착
    (관할=structure_look_clause tie-break). prev 샷은 미부착(prev=배경·
    구조물 look SOT — 권위 이중화 금지, Codex R6) — prev_sel 과 동시
    전달 = ValueError.

    seed_bg(2026-07-17 seed-bg 승격, v5 팩 전용): 플레이트 producer 가
    구조적으로 없는 structure_plate 그룹 — seed 가 LOCATION+구조물 단일
    권위로 **정확히 1회** 부착 (Codex 조건 1: STRUCTURE LOOK 이중 첨부
    금지 → structure_seed/plate 와 상호 배타). bg_only=seed_bg 만,
    prev 샷=미부착(prev-only).
    still_recipe vuK    팩에는 seed-bg 단일 권위 계약이 없음 — seed_bg 는 v5 전용u~   seed_bg 는 단일 권위 — plate/structure_seed 와 동시 부착 금지 (Codex 조건 1: 동일 seed 이중 첨부 차단)uD   seed_bg 는 prev 샷에 부착 금지 — prev-only (Codex 조건 4)u(   seed_bg 와 lane 참조는 상호 배타u_    팩에는 플레이트 공존 STRUCTURE LOOK 계약이 없음 — structure_seed 는 v4 전용uw   structure_seed 는 prev 샷에 부착 금지 — prev 가 배경·구조물 look SOT (Codex R6 권위 이중화 금지)uu   structure_seed 와 lane 참조는 상호 배타 — 복잡 구조물 샷은 lane 스케치 경로를 쓰지 않는다uK    팩에는 lane 라벨이 없음 — lane_sketch/direct_seed 는 v2+ 전용sketch_label_lanesketch_labelr   
seed_label
char_label
prop_labelnameplate_labelseed_bg_labelhandled_char_labelconti_label_geomconti_label)r   _SEED_BG_PACKSr   _STRUCTURE_LOOK_PACKS_LANE_LABEL_PACKS LANE_SKETCH_LABEL_PROMPT_VERSIONr   r   r   r   _prev_label_stemformatGEOM_AUTHORITY_PROMPT_VERSION)rf  r  contiprev_sel	char_refs	prop_refsr  r  r  r  r  r  r  r  r  r  resolvedrefs_sk_stem_sk_verr  r  r  r  
hand_labels                            rI   build_still_refsr    s   | &n5H/  0 1= =   :L  V  "i&;GHH!!66  0 1S S  H  "i&;C  #3	8M !22  0 19 9  " ' %'(HIK-;X,F Hg
 KKWh%,..3eg{D  KKWl%-//4uw	C KKK!(,<=xH !\855:UW 	 \855:UW 	"ID#KK***5s;< #"ID#KK***5s;< #D X--BHHJ	

 x//!)++057G=	
 !,AGGI	
 KK!(,<=xH $-.134 5:EG  '	cDJ.KK!2!2!2!=s CD	 ' h(898D	
 e/KK/2578 9>  
	-BHHJ	
 WlHEKKMJWlHEKKMJ	cZ&&D&1378 	cZ&&D&1378 KrK   )r  r  r  c                      t        d       t        d      d fd} ||       |d      fS )u.  A/B 두 브랜치 refs 를 **한 곳에서** 조립 — (refs_a, refs_b).

    Codex R6: 브랜치별 개별 조립은 B 브랜치에서 seed/팩 라벨이 누락되는
    드리프트를 허용한다 — 두 브랜치는 콘티 유무 **만** 다르고 플레이트·
    seed·엔티티·팩(라벨)은 동일해야 비교가 순수하다. A/B 는 eligible
    (person-visible·no-prev) 샷 전용 — bg_only/prev 는 호출 금지.

    배치 리뷰 BLOCKING-2: 복잡 구조물(structure_seed) A/B 는 LOCATION
    플레이트 필수 — plate=None 이면 합의된 A=plate+seed+conti /
    B=plate+seed 가 아니라 seed-only 비교가 된다 (ValueError).
    seed-bg 그룹(v5, Codex 조건 1)은 seed_bg 가 LOCATION 권위 —
    plate/structure_seed 와 상호 배타(가드=build_still_refs).
    NuK   복잡 구조물 A/B 는 LOCATION 플레이트 필수 — plate=None 금지u4   A/B 는 LOCATION 권위(plate 또는 seed_bg) 필수c                ,    t        d| d 	      S )NF)	rf  r  r  r  r  r  r  r  r  )r  )branch_contir  r  r  r  r  r  s    rI   _branchz%build_ab_branch_refs.<locals>._branch  s+    ))

 
	
rK   )r  Optional[Path]r   List[Tuple[str, Any]]rc  )r  r  r  r  r  r  r  r  s   ` ````` rI   build_ab_branch_refsr    sU    . !emY
 	
 }OPP
 
 5>74=((rK   zclose-upzmedium shotz	wide shotzinsert close-up on a detail)closemediumwideinsertz	points tozreaches forzlooks towardzmoves toward)	points_toreaches_forlooks_towardmoves_towardre   c                  t        | t              syddlm}m} g }t        | j                  d      xs d      j                         }|r|s|j                  d|        t        j                  t        | j                  d      xs d            }|r|j                  d|        | j                  d      }t        |t              r{g }	|j                  d	      xs g D ]8  }
t        |
t              st        |
j                  d
      xs d      j                         }|j                  t        |
j                  d      xs d            xs dgd   }|j                  t        |
j                  d      xs d            xs dgd   }|r|r|s| d| d| }t        j                  t        |
j                  d      xs d            }t        |
j                  d      xs d      j                         }|r|r|d| d| z  }|	j                  |       ; |	r&|j                  ddj                  |	      z   dz          g }| j                  d      xs g D ]&  }t        |t              st        |j                  d      xs d      j                         }|sD|}t        |j                  d      xs d      j                         j                  d      }|r	|d| dz  }t        |j                  d      xs d      j                         j                  d      }|r|d| z  }t        |j                  d      xs d      j                         j                  d      }|r|d| z  }|j                  |       ) |r&|j                  d dj                  |      z   dz          |syt        |t        |xr |      !      \  }}t        t         |t#        |      d"j                  |      #      j                         S )$u  shot_staging CP 1샷 엔트리 → CAMERA/FRAME 절 (fix1, 2026-07-19).

    E2E9 S19sh4 실측: staging 엔 camera_direction·frame_spatial_contract·
    framing_scale·key_bg_elements(구도·스케일 SOT)가 실재하는데 스틸 base
    프롬프트에 구조적으로 누락 — 무콘티 브랜치(C/D)·prev 샷·배경 제거
    변형에서 구도 정보가 0 이 되어 원근·스케일 붕괴(CCTV 거대화)가 남.

    구조화 필드의 결정론 렌더링만 수행(zone/depth/gesture enum → NL 구절 —
    글자 의미 판단 없음). staging 부재/유효 필드 0 = "" (보강 절이라
    fail-safe — 스틸 자체를 막지 않는다). 절 헤더·스케일 계약 문구는
    프롬프트 팩 v6 camera_frame_clause 스템 SOT.
    r[   r   )DEPTH_PHRASESZONE_PHRASEScamera_direction
- CAMERA: framing_scale- FRAMING SCALE: frame_spatial_contractconstraintslabelscreen_zoneNdepth_planez in the z of the frame, gesture_actiongesture_target_label,  z- FRAME LAYOUT: r   .key_bg_elementselementstatez (r   orientationu    — 
camera_usez
; used as z- KEY BACKGROUND ELEMENTS: r  
r   body)r   r   app.core.frame_spatial_contractr  r  r   r   r   r   _FRAMING_SCALE_PHRASES_GESTURE_PHRASESr~   rstriprg   rE   r   r   r   )stagingrR   re   r  r  linescamscalefscplacedr   r  zonedepthseggesturegtlbg_lineselr  r  r  r  rX   rf   s                            rI   build_camera_frame_clauser    s     gt$KE
gkk,-3
4
:
:
<C" (z#'("&&GKK(.B/1E(01
++.
/C#t'-2-Aa&g,"-335E $$AEE-(.B/1 ;59FA?D"&&AEE-(.B/1 ;59FA?EduG8D6@C&**AEE*+1r24Gaee239r:@@BC3G9AcU++MM#! ." LL+dii.??#EFHkk+,22"d#266)$*+113 BFF7O)r*00299#>Rwa= C"&&/526<<>EEcJU;-((C-34::<CCCH
Z
|,,C% 3& 2TYYx5HH3NO "5J 6=9<1>?ID# &s+YYu eg	rK   c                N    t        |       xr t        |      xr t        |       S )u  이 샷이 **실제로 마커 스케치로 나가는가** — 한 곳에서만 정한다.

    ★[2026-09-18 컨트리로드] 같은 판단이 두 곳에 따로 적혀 있었다:

        camera_frame_en / framing_scale_en …  `lane_used` 만 봄
        lane_ref_mode                  …  `lane_used and not (lane_chain and bg_only)`

    그래서 **체인이면서 배경 전용**인 lane 샷에서 스케일 줄만 남고 lane 모드는
    꺼져, `build_still_prompt` 의 「framing_scale_en 은 lane 샷 전용」 계약에 걸려
    **scene_image_pipeline 이 통째로 죽었다**(242샷 중 121에서 정지).

    체인+배경 전용 lane 샷은 플레이트(장소 사진)가 되살아나 붙으므로 스케치 잠금
    문안이 거짓이 된다 — 그래서 그 샷은 스케치 샷이 아니다(종전 `lane_ref_mode`
    의 판단 그대로다). 카메라·스케일 절도 같은 판단을 따른다.
    )rE   )ro  
lane_chainrf  s      rI   lane_sketch_moder  s  s$      	?GD$4$FgGGrK   c                    t        | t              syt        j                  t	        | j                  d      xs d            }|syt        t        dt        t              d|       j                         S )u  lane 샷용 — typed `framing_scale` **한 줄만** 절로 싼다 (B-2b).

    `build_camera_frame_clause` 와 같은 값(`_FRAMING_SCALE_PHRASES`)을 쓰되
    **스케일 한 줄만** 낸다. CAMERA 산문·FRAME LAYOUT·KEY BACKGROUND
    ELEMENTS 는 배치라 마커 스케치가 SOT 이고, 여기서 같이 주면 권위가
    이중화된다(이 파일 2083 의 상호 배타 가드가 그 계약이다).

    typed 값이 없으면 "" — 그러면 호출부는 라벨도 옛 판 그대로 둔다.
    새 라벨은 「FRAMING SCALE 줄이 스케일을 갖는다」고 말하는데, 그 줄이
    없는 채 라벨만 바꾸면 **가리킬 값이 없는 지시**가 되기 때문이다.
    r[   r  framing_scale_clauser  r  )
r   r   r  r   r   r   r   r   FRAMING_SCALE_PROMPT_VERSIONr   )r  r  s     rI   build_framing_scale_clauser    sl     gt$"&&GKK(.B/1E'&'CD ( eg	rK   c                    t        | t              syt        | j                  d      xs d      j	                         }|syt        t        dt        t              d|       j	                         S )u.  shot_staging CP 1샷 엔트리 lighting_mood → LIGHTING & MOOD 절
    (E2E10 fix⑤, 2026-07-21).

    E2E10 실측: 붉은 원 계열(bgonly) 샷 프롬프트에 조명·무드 절 부재
    (TIME OF DAY 1줄뿐) + 오브젝트 재질·질감 서술 부재 → 평면 낙서화
    (S12sh18/S18sh10/S25sh5). 조명 SOT=shot_staging.lighting_mood —
    scene_still.lighting_json 은 v4 파이프라인 미저작(E2E10 192행 전부
    '{}' 실측)이라 소비 대상이 아니다.

    결정론 조립만 수행(저작된 NL 문장을 절로 감쌈 — 글자 의미 판단
    없음). 절 헤더·재질 사실감 계약 문구=팩 v8 lighting_mood_clause 스템
    SOT. staging 부재/빈값 = "" (보강 절이라 fail-safe — 스틸 자체를
    막지 않는다, camera_frame 관례 동일).
    r[   lighting_moodlighting_mood_clause- LIGHTING & MOOD: r  )	r   r   r   r   r   r   r   r   LIGHTING_MOOD_PROMPT_VERSION)r  moods     rI   build_lighting_mood_clauser	    sg     gt$w{{?+1r288:D'&'CD"4&) eg	rK   c           
     v    | syt        t        dt        t        |t                          j                         S )u  캐릭터 identity 참조 역할 한정 절 (2026-08-12 차렷/증명사진 대응).

    인물 시트(정면 응시 참조)의 포즈·시선·구도가 스틸로 전이되는 경로를
    역할 한정 문장으로 차단한다 — 참조는 동일성(얼굴·헤어·체격·복장) 전용,
    포즈·시선의 권위는 본 프롬프트의 CAMERA/동작 서술. 절 문구=팩 v16
    identity_ref_role_clause 스템 SOT.

    has_char_refs=False(캐릭터 참조 미첨부 샷)면 "" — 참조가 없는 샷에
    이 절이 나가면 존재하지 않는 이미지를 가리키는 거짓 문장이 된다.
    호출자 계약(unit 테스트 test_still_recipe 가 잠근다): 절은 **본문
    캐릭터 참조(char_label "CHARACTER REFERENCE — …")가 실리는 조건**
    (char_refs 비어 있지 않음 AND not bg_only)에서만 참을 전달한다.
    ★의도된 예외(Codex HIGH-4 명문화) — bg_only+handled_by 샷이 싣는
    "HAND OWNER REFERENCE" 1장은 이 절의 대상이 아니다: 절의 지시 대상
    (CHARACTER REFERENCE images)이 그 샷에 존재하지 않아 절이 나가면
    거짓 문장이고, 손 라벨 스템은 자체 역할 한정문(얼굴·몸·복장 반입
    금지, 손목·전완 너머 금지)을 이미 내장한다.
    r[   identity_ref_role_clauser   )r   r   r   ra   IDENTITY_ROLE_PROMPT_VERSIONr   )has_char_refsrR   s     rI   build_identity_ref_role_clauser    s;    * +&.0LMO eg	rK   c                
    ~ | S )u  마감 절을 roll(t2i) 프롬프트에 붙일까 — **끝 단계에만 준다.**

    2026-08-27 사용자 결정(뒤 결정이 앞 결정을 덮는다):

        앞: "roll 에 언제나 붙이는 쪽으로 가"
        뒤: 표를 보고 「이거 보면 제대로 구현이 안되었는데?」
            → 「끝 단계 한 곳」, 재생성은 안 한다

    ## 무엇을 막고 무엇을 연 것인가

    두 실패를 나란히 적는다 — 한쪽만 보고 고치면 반대편이 되살아난다.

        cine ON 인데 roll 도 마감을 받는다   성공 샷 **100%**
        포기·기각이면 마감이 아무 데도 없다  611 중 **2** (0.3%)

    앞의 것이 이 판의 표적이다. 뒤의 것은 **열어 둔 채 기록으로 드러낸다** —
    사용자가 재생성을 물리셨다("어차피 후보들 중에서 판단할거아냐").
    자산 record 의 `cine_transform.finish_owner` 가 그 자리를 말한다.

    ★일반 실패는 이 갈래가 아니다 — `:5208` 이
    `StillCineTransformIncomplete` 를 올려 스텝을 안 닫고 resume 이 실패한
    변환만 다시 시도한다. 확정 원본 fallback 은 포기·기각 둘뿐이다.

    ## 왜 중복이 해로운가 (앞 판단을 뒤집는 근거)

    앞에서는 「마감 절이 CAMERA·layout 권위를 안 건드리니 두 번 가도
    무해하다」고 봤다. 그런데 **이 절이 다루는 것이 바로 미지정 선택지**고,
    최종 i2i 단계가 하는 일도 그것이다. 같은 선택을 두 단계가 각자 하면
    앞 단계가 이미 고른 것을 뒤 단계가 다시 고른다 — 「끝 단계가 정한다」는
    계약이 서지 않는다. 사용자 지적의 뜻이 이것이다.

    ## 지출

    조건이 바뀌면 조립 바이트가 바뀌고, `compute_input_fingerprint` 가
    프롬프트를 직접 접으므로(`multiroll_select.py:722`) cine ON 으로
    완주한 샷은 롤부터 다시 산다. 각도 권위를 걷은 팩(v33)과 **같은 판에**
    넣어 한 번만 치게 했다.

    ## grok_backend 를 남겨 둔 이유

    호출부 둘(조립·지문)이 같은 축으로 판정하는지 시험이 AST 로 대조한다.
    인자를 지우면 그 대조가 무의미해진다.
    rx   r]   cine_ons     rI   should_attach_cinematic_finishr    s    \ 	;rK   c                d    t        t        dt        | xs t                    j	                         S )u  시네마틱 마감 절 (2026-08-13 사용자 지시) — 전 스틸 프롬프트 공통.

    CAMERA 계약(스테이징 권위)은 불변 — 미지정 선택지의 영화적 실현만
    지시한다.

    ★빈 selector 를 받으면 `CINEMATIC_FINISH_PROMPT_VERSION` 으로 간다.
     종전 기본값은 컴팩트 지도 팩 selector 였고 호출부는 `_guidance` 를
     넘겼는데, nb2 백엔드에서 그 값이 **빈 문자열**이라 `resolve_prompt_
     version("")` 이 ValueError 를 냈다. 그래서 이 절은 grok 백엔드에서만
     불릴 수 있었다 — 마감을 붙일지는 백엔드가 아니라 **최종 i2i 가 그
     마감을 하느냐**로 갈려야 한다.
    cinematic_finishr   )r   r   r   CINEMATIC_FINISH_PROMPT_VERSIONr   rQ   s    rI   build_cinematic_finish_clauser  
  s2     #&? ?A eg	rK   c               @    t        ||      s| S | dz   t               z   S )u  마감 절을 t2i 프롬프트 끝에 붙인다 — **조립까지 여기서 한다.**

    가르는 것과 붙이는 것을 한 자리에 둔 이유는 시험이다. 조건만 함수로
    빼고 조립을 호출부에 남겨 두면, 시험이 조건은 재면서 **나가는 문안은
    한 번도 안 태운다** — 붙이는 줄이 통째로 빠져도 초록이다
    (2026-08-27 Codex 지적).

    붙일 조건은 [[should_attach_cinematic_finish]] 를 보라.
    r  r   )r  r  )r   r]   r  s      rI   attach_cinematic_finishr     s)     *%w8F?:<<<rK   )camera_direction_enlighting_mood_enc                  t        | xs t              }t        t        d|      j	                         }g }|j	                         r"|j                  d|j	                                 |j	                         r"|j                  d|j	                                 |s|S t        t        d|dj                  |            j	                         }| d| S )	u  i2i 시네마틱 변환 문안 (2026-08-13 #108) — 팩 스템 단일 SOT.

    sel 확정 후 최종 스틸을 영화 키프레임으로 재구성하는 짧은 범용 지시
    하나 — 원본 조립 프롬프트·시나리오 고유명사를 쓰지 않는다(파일럿
    grok_cine_batch 계약 승격). 장소·인물·순간은 원본 스틸이 고정하고
    프레이밍·조명만 재구성한다.

    2026-08-25 (사용자 지시 "카메라 앵글이나 기타 시네마틱 관련 프롬프트
    추가하는 것으로 해, 이전에는 하지말고") — 연출 재료를 받으면
    `cine_stage_direction` 절이 붙는다. 종전에는 이 단계가 **문안과 소스
    한 장**만 받았다: camera_direction 도 lighting_mood 도 없이 「동기 있는
    실제광을 쓰라」고 했으니 무엇이 그 빛을 동기 짓는지 모르는 채로 지어낼
    수밖에 없었다.

    ★재료가 하나도 없으면 절을 안 붙인다 — 종전과 **바이트 동일**이다.
    ★`cine_stage_direction` 스템은 팩 v24 에만 있다. 재료를 넘길 때는
     selector 도 v24 여야 한다(`cine_pack_selector`).
    cine_transformr   r  r  cine_stage_directionr  r  r   )r   rH   r   r   r   r   r~   )rR   r  r  verbaser  tails          rI   build_cine_transform_promptr!  2  s    0 !99;Cw 0#>DDFDE  "z"5";";"=!>?@*+;+A+A+C*DEF'YYu eg 	 V4vrK   c                p    t        |       \  }}t        t        |t        |            j	                         S )uM  b 롤 구도 변주 절 (2026-08-13 사용자 결정: 2롤 ab, b=영화적 구도
    변주).

    이 절이 실린 후보만 **카메라를 다시 세울** 수 있다 — 각도와 높이 두 축
    뿐이다. 장소·인물·순간과 CAMERA & FRAME 의 프레임(subject scale ·
    screen placement · key background elements), SHOT TEXT 직설 프레이밍은
    불변이다 (2026-08-28 감사 P0-B).

    Args:
        version_selector: `still_guidance_selector()` 결과. 스템과 팩은
            `broll_variation_stem()` 이 정한다 — 지문과 같은 출처다.
    r   )rY   r   r   r   r   )rR   rX   rf   s      rI   build_broll_variation_clauser#  [  s4     %%56ID#5c:egrK   c                    t        |      dk  r|D ci c]  }||  c}S | dz   t        |      z   }t        |      D ci c]  \  }}||dk(  r| n| c}}S c c}w c c}}w )u  2롤 ab 변주 프롬프트 맵 (2026-08-13 사용자 확정 — 전 백엔드·전 경로).

    첫 롤=기준(base 그대로), 나머지 롤=구도 변주 절 동반 — a 와 b 가 같은
    구도·앵글로 수렴하지 않게 하는 구조 재료. labels 1개면 base 그대로.

    ★스템·팩은 `broll_variation_stem()` 이 정한다 — 지문과 같은 출처다.
     종전에는 여기서 `grok_stem_version()` 으로 직접 골랐고 지문은 딴
     상수를 봤다(2026-08-28 감사 P0-B 에서 합쳤다).r<   r   r   )r   r#  	enumerate)rU  labelsrR   lnr   is         rI   build_ab_roll_prompt_mapr)  n  s     6{Q*01&BK&11

!=" C v&&EAr 	AF[+&  2s
   
AAc                    |syt        t              }| r0t        t        d|dj	                  |             j                         S t        t        d|      j                         S )u  prev 스틸 인물 계승 스코프 절 (#119③) — 렌더 완문을 돌려준다.

    cast_known=False(앵커 인물 명단을 모름) = 빈 문자열 → 레거시 무조건
    잠금 스템 그대로(조립 byte-identical). 겹침이 있으면 그 인물들만
    의상 잠금, 겹침 0 이면 "그 사진의 인물은 이 샷에 없다" 명문 —
    S64sh4 실측(앵커 인물 외모·의상이 다른 인물에게 복제) 대응.r[   prev_people_rule_sharedr  )r   shared_namesprev_people_rule_noner   )r   PREV_CAST_SCOPE_PROMPT_VERSIONr   r   r~   r   )r,  
cast_knownr{   s      rI   build_prev_people_ruler0    s[     =>A.<0227%'	: w 7CIIKKrK   c                   g }| D ]}  }t        |j                  d      xs d      j                         }|s2t        |j                  d      xs d      j                         }|j                  |r	d| d| dnd| d        |syt	        t
        dt        t              	      j                         }|d
z   d
j                  |      z   S )u&  저작 공급된 원어 문안 렌더 (#119②) — 빈 목록=빈 문자열.

    머리말 스템 + 한 줄에 하나. 문안 저작은 저작 모듈 소유 — 여기는
    렌더만.

    ★**면(`surface_native`)을 요구하지 않는다** (2026-08-27 Codex BLOCK).
     저작 팩 v2 부터 그 칸을 안 받는데(면을 정하려니 통칭이 필요했고 샷마다
     갈려 같은 장소가 다른 곳처럼 보였다) 이 함수가 여전히 그것을 **필수로**
     걸러, **v2·v3 산출이 통째로 버려지고 있었다.** 저작이 성공해도 문안이
     한 줄도 안 나가고, 그 샷은 전면 금지 판으로 갔다.

     면이 있으면 옛 모양(`- 면: "문안"`)으로, 없으면 문안만 낸다 — 옛
     records 를 읽는 자리가 남아 있다.
    text_nativer[   surface_native- z: ""z- "signage_sectionr   r  )	r   r   r   r   r   r   r   SIGNAGE_SECTION_PROMPT_VERSIONr~   )inscriptionsr  xr   surfaceheads         rI   build_signage_sectionr<    s     E155'-2.446aee,-34::<r'#dV1-D6mL  "&*,- .3UW 	 $;5)))rK   c                    |r.t        t        dt        t              |      j	                         S t        t        d|       j	                         S )u   prev 스틸 라벨 — people_rule 이 있으면 인물 스코프 명시 v21 스템,
    없으면 레거시 스템 그대로(byte-identical).prev_label_castr   people_rule
prev_labelr   )r   r   r   r.  r   )r  r@  s     rI   r  r    sI     &*+IJ#% &+UW	- wh?EEGGrK   rx   T)world_anchorprev_usage_enpose_clausesmovement_en
figures_en
carried_enr  
char_namesnonhuman_nameschar_names_exactlane_ref_modestructure_seed_attachedseed_bg_modechain_bg_modeplate_attachedcamera_frame_enframing_scale_enr  conduct_versionidentity_role_enr  
signage_enr  guidance_versionc                  &' |r|dvrt        d|      |r|t        vrt        d| d      |r$|t        vrt        d| d      |rt        d      |r&|t        vrt        d| d      |s|rt        d      |r|rt        d	      |r|rt        d
      |r|st        d      |r|s|s|s|rt        d      t	        |xs |      'd?'fd}|rd}n{|r |d|       }nm|r.t        t        dt	        t                    j                         }n=|r	 |d      }n2|s.t        t        dt	        t                    j                         }nd} |d||xs d      d|  d| d|z   g}|r|j                   |d             |r|j                  |       |r|j                  |       |r|j                  |       |r|r>|j                  t        t        dt	        t              |      j                                n|j                   |d             |r=|j                  t        t        dt	        t              |       j                                |s9|r7|j                  d!j                  |             |j                   |d"             |rt        nt        &d?&fd#} |j                   | d$             |xs g D !cg c]5  }!t!        |!      j                         st!        |!      j                         7 }"}!t#        |"      xr t%        |"      t%        |xs g       k\  }#|r|r|#s.|j                   | d%             |j                   |d&             |"rT|j                  t        t        d't	        |rt&        nt(              d(j                  |"      )      j                                |j                   |d*             |rt	        |xs |      }$|sA|j                  t        t        d+t	        t+        |                  j                                |j                  t        t        d,|$      j                                |sF|j                  t        t        d-t	        t-        |t.                          j                                |r|j                  d.|z          |	r|j                  d/|	z          |
r|j                  d0|
z          |rt0        }%|j                  t        t        d1t	        |%      |2      j                                |r>|s<|s:|s8|j                  t        t        d3t	        |%            j                                |r|rt2        j5                  d4|       |r)|j                   |d5d6j                  |      7             n|j                  t        t        d8t	        |xs t6              d6j                  |      9      j                                nZ|rA|j                  t        t        d:t	        |xs t8                    j                                n|j                   |d;             |r|j                  |       |r|j                  |       |j                  t        t        |rd<nd=t	        |rt:        nt<                    j                                d>j                  |      S c c}!w )@u  s39 _build_prompt ①~⑭ 조립 순서 이식.

    char_names = ["이름 (traits...)"] — entity_canon.stable_traits 병기는
    호출자가 수행. world_anchor 예: " — contemporary ..., 2026" (프로젝트
    데이터 파생, 하드코딩 금지).

    lane_ref_mode(Stage D HIGH-3, v3+ 팩 전용): ""=기존(LOCATION
    PHOTOGRAPH), "sketch"=레인1(사진 참조 0 — 스케치=배치 SOT+텍스트
    저작), "seed"=레인2(STRUCTURE LOOK 사진=구조물 SOT). prev_used 샷은
    prev 문장이 그대로 location authority (lane 여부 무관).

    structure_seed_attached(2026-07-16 복잡 구조물=A/B, v4 팩 전용):
    플레이트와 공존하는 STRUCTURE LOOK 관할·tie-break 절 주입
    (structure_look_clause — 구조물=seed 우선, 주변·시간·조명=플레이트
    우선). lane_ref_mode 와 상호 배타.

    framing_scale_en(B-2b, 2026-08-28): lane 샷에만 실리는 **스케일 한 줄**
    (build_framing_scale_clause 렌더 완문). `camera_frame_en` 과 상호 배타이고
    lane 밖에서는 거부한다. 비면 조립 byte-identical.
    ★짝이 되는 라벨 교체(`build_still_refs(framing_scale_owned=True)`)와
     **같이** 켜야 한다 — 옛 라벨은 스케치가 camera framing 권위라고 말한다.

    camera_frame_en(fix1, 2026-07-19): build_camera_frame_clause 렌더 완문 —
    비면 조립 byte-identical(기존 팩 무변경). lane 샷은 마커 스케치가
    배치 SOT 라 상호 배타(이중 권위 금지).

    chain_bg_mode(2026-07-27 리뷰 F1, BGFIRST2 체인 저작 전용): Step2 는
    재투영/채색 배경본을 **첫 참조**로 받고 LOCATION PHOTOGRAPH 는 어디에도
    첨부되지 않는다 — 체인 저작 base 는 prev_used=False·lane_ref_mode=""
    이라 기본 fallback 문구("the attached LOCATION PHOTOGRAPH shows the
    exact spot.")가 그대로 나가 존재하지 않는 이미지를 가리켰다(v3 에서
    lane 샷에 대해 한 번 제거된 모순의 체인 경로 재발). 배경본을 장소
    권위로 명시하는 스템으로 교체한다. 다른 권위 모드와 상호 배타이며,
    False(기본)면 조립 byte-identical.
    )sketchseedzunknown lane_ref_mode r  uN    팩에는 lane location authority 가 없음 — lane_ref_mode 는 v3+ 전용uT    팩에는 structure_look 계약이 없음 — structure_seed_attached 는 v4 전용u;   structure_seed_attached 와 lane_ref_mode 는 상호 배타uP    팩에는 seed-bg 단일 권위 계약이 없음 — seed_bg_mode 는 v5 전용uk   seed_bg_mode 는 단일 권위 — lane_ref_mode/structure_seed_attached 와 상호 배타 (Codex 조건 3)u   camera_frame_en 과 lane_ref_mode 는 상호 배타 — lane 샷은 마커 스케치가 배치 SOT (구도 권위 이중화 금지)un   framing_scale_en 과 camera_frame_en 은 상호 배타 — 스케일 줄은 둘 중 한 곳에서만 나간다ud   framing_scale_en 은 lane 샷 전용 — 그 밖에서는 camera_frame_en 이 스케일을 갖는다u   chain_bg_mode 는 단일 권위(첫 참조=배경본) — prev_used/lane_ref_mode/seed_bg_mode/structure_seed_attached 와 상호 배타c                F    t        t        | fdi|j                         S )Nr   )r   r   r   )r  kwr  s     rI   _pzbuild_still_prompt.<locals>._pT	  s#    7DA(AbAGGIIrK   z8the attached PREVIOUS SHOT STILL shows this exact place.location_lock_lane_location_lock_chain_bgr   location_lock_seed_bglocation_lock_text_onlyz6the attached LOCATION PHOTOGRAPH shows the exact spot.
still_headzas the scene text implies)rB  time_of_dayz#SHOT TEXT (authoritative, Korean): LOCATION (lock): u    The shot takes place here — structure_look_clausecontinues_clause_castr?  continues_clauseprev_usage_clauser  r  rp   c                V    t        t        | t                    j                         S )Nr   r   r   r   r   )r  _realize_sels    rI   _realizez$build_still_prompt.<locals>._realize	  s%    T*<8::?%'	BrK   ro   
human_formrn   nonhuman_castr  )r   namesrl   rm   rk   support_stillness_clausezMOVEMENT (follow exactly): u+   FIGURES — size & depth (follow exactly): uU   CARRIED STATE (persist exactly — must match the neighbouring shots of this scene): handled_object_clause)r   handlerhandled_object_framinguY   still_recipe: bg_only 와 인물 배정이 충돌 — 배정을 따른다 (char_names=%s)people_clauser   )rH  people_clause_scene)r   rH  no_people_clauseno_people_fallbackno_textno_text_noner   )r  r   r   r   )r   _LANE_LOCATION_PACKSr  r  r   r   r    CHAIN_BG_LOCATION_PROMPT_VERSIONr   !TEXT_ONLY_LOCATION_PROMPT_VERSIONr   r.   PREV_USAGE_CLAUSE_PROMPT_VERSIONr~   REALIZE_COMPACT_PROMPT_VERSIONREALIZE_PROMPT_VERSIONr   rE   r   $NONHUMAN_CAST_COMPACT_PROMPT_VERSIONNONHUMAN_CAST_PROMPT_VERSIONrS   ra    SUPPORT_STILLNESS_PROMPT_VERSIONHANDLED_OBJECT_PROMPT_VERSIONloggerinfoPEOPLE_SCENE_PROMPT_VERSIONNO_PEOPLE_PROMPT_VERSIONTEXT_POLICY_GROK_PROMPT_VERSIONTEXT_POLICY_PROMPT_VERSION)(	shot_desc
place_texttime_of_day_enrB  rf  rn  rC  rD  rE  rF  rG  r  rH  rI  rJ  rK  rL  rM  rN  rO  rP  rQ  r  rR  rS  r  rT  r  rU  r[  loc_tailpartsrj  n	_nonhuman_all_nonhuman_cv	_hand_selri  r  s(                                         @@rI   build_still_promptr    s   t .@@1-1BCDD/CC^, -D D
 	
 !66  0 1M M  M  /  0 1B B  3M  =N
 	
 OB
 	
 9
 	
 ]l6MS
 	
 &&6&H.IHJ F 		+M?;<	 -*02
 %'	 	
 
-. .*13
 %'	 	 E 	 	<l%D)D	F
-i[9
J<'FG
		E R/01_% 	%& 	%& LL0.24,	. /4eg	7 LL./0 LL,.46"	$ %*EG	- |TYY|,- 	R*+,6F2/ B 
LL/*+$ +9*>B*>R*>A3q6<<>Q*>IR
 OOI#j>NB:O(OMjLL,/0LL012LL.<L857 ii	*,
 -2EG5 
LL&'(
 %%5%HI LL02'(89;< =BEG	E
 	!4cBHHJ	L 	3.%(8:;<
 =BEG	E 2[@A9JF	
 &(23	
  2	[,*95  !&	)0 J'0@LL1.y9;;@57D  KK"#-/ LLO		*8MNO LL..$C(CE99Z0	2 38%'	;
 
['* <$<>? @Euw	H
 	R,-. 	%& Z $ 
LLjn&/?++-. /4eg	7
 ;;uS Ss   	[([c                    t        |t              s| S |j                  d      }|dk(  ry|dk(  r)|j                  d      }|st        d      t	        |      S | S )uT  배경 공유 계획(B-3, 2026-07-19)의 prev/배경 결정 — 상위 권위.

    계획 없음=classify 판정 유지(fail-safe). ref_plan=="background"=
    prev 미사용(계획이 '이 샷은 배경 기준' 확정). ref_plan=="prev"=계획
    앵커로 override — 앵커 결손=ValueError(조용한 강등 금지, 샷 격리).
    ref_plan
backgroundNprevprev_anchor_tagu-   share_plan prev 앵커 결손 — fail-closed)r   r   r   r   r   )classify_prev_tag	shot_planrW  anchors       rI   apply_share_plan_prevr  m
  sb     i&  	z	"B	\	V|01LMM6{rK   c                B    | xr | xr | xr | xr | xr | xr | S )u  BGFIRST2 체인 대상 판정 — **일반 콘티 샷만** (순수 로직).

    사용자 확정(2026-07-20): 콘티 사용 샷만 교체, 비콘티 샷(prev/bgonly/
    lane)은 기존 유지. 복잡 구조물(structure seed·seed-bg·complex A/B)
    파이프는 2026-07-16 사용자 확정 별도 계약이라 제외 — BGFIRST2 실증
    (S19sh4/S22sh2/S8sh4)도 전부 일반 콘티 샷.
    rx   )conti_presentrf  rn  ro  rm  rL  seed_bg_attacheds          rI   bgfirst_eligibler  
  sR    $ 	 	!K	!M	! M	! N		!
 ('	! ! rK   c                N   t        | t              syg }t        | j                  d      xs d      j	                         }|r|j                  |       t               }| j                  d      xs g D ]  }t        |t              st        |j                  d      xs d      j	                         }t        |j                  d      xs d      j	                         }|r||v rs|j                  |       |j                  |rd| d| nd|         dj                  |      S )	u  장소 사실 — 절제 블록 (2026-07-26 사용자: "너무 디테일하게 넣으면
    안 돼").

    싣는 것: layout_narration_en 1단락 + items 의 kind/name_en.
    빼는 것:
      - placement_en — 배치 권위는 첨부 콘티의 선이다. 텍스트로 또 주면
        이중 권위가 되어 구도가 흔들린다.
      - code/evidence/inferred/zone_labels_en — 감사 필드이거나 narration
        과 중복.
      - excluded_transient_elements — 금지 대상 열거는 오히려 프라이밍
        이다(outdoor_marker_map.build_geometry_text_lines 의 구조 토큰
        비노출과 같은 계열).

    코드는 데이터 직렬화만 한다 — 의미 판단 없음.

    spec 은 `Any` 다 — 실호출은 체크포인트 원본(무타입)이라 dict 가 아닌
    값([] / None)이 실제로 들어온다. Dict 로 좁히면 아래 isinstance 가정이
    항상 참이 되어 정적 검사기가 가드를 죽은 코드로 읽는다(Optional 로는
    None 만 덮여 [] 를 못 담는다).
    r[   layout_narration_enr   kindname_enr4  : r  )	r   r   r   r   r   r   r   r   r~   )specr  	narrationseenitr  r  s          rI   build_place_facts_blockr  
  s   * dD!EDHH239r:@@BIYUDhhw%2%"d#266&>'R(..0266)$*+113tt|Dr$r$(4&kB & 99UrK   )rP  r  place_facts_blockworld_facts_block	lane_fillr  c        	   	        t        |      }	|re|j                         st        d      |j                         st        d      t        t        d|	      j                         d|  d| d| dg}
n.t        t        d	|	      j                         d|  d| d| dg}
|r|
j                  |       |r|
j                  |       |re|
j                  d
|z          |
j                  d|z          |
j                  t        t        dt        t                    j                                n<|
j                  t        t        dt        t                    j                                dj                  |
      S )u  Step1 재투영 프롬프트 — 정본 bg_prompt 조립 순서.

    head(무인+콘티 카메라 권위·플레이트=재질/간판 소스·재투영 계약) →
    SHOT TEXT → LOCATION(lock) → TIME OF DAY(lock) → CAMERA/FRAME 절
    (fix1 렌더 완문, 부재=생략) → LIGHTING & MOOD 절(fix⑤, 부재=생략)
    → tail(16:9 빈 실사 배경).

    lane_fill (2026-07-26 확정 흐름): 참조 이미지 0 으로 콘티 자체를
    편집하는 lane 경로. head/tail 스템이 bg_fill_* 로 바뀌고 장소·world
    사실 블록이 **필수**가 된다 — 둘 중 하나라도 비면 무국적 배경을
    조용히 만들게 되므로 ValueError(fail-closed). 기본값 False 는 기존
    조립과 byte-identical.
    uy   lane_fill 인데 place_facts_block 이 비어 있음 — 장소 사실 없이 배경을 만들지 않는다 (fail-closed)uz   lane_fill 인데 world_facts_block 이 비어 있음 — 지역·시대 없이 배경을 만들지 않는다 (fail-closed)bg_fill_headr   z/SHOT TEXT this background must serve (Korean): rb  TIME OF DAY (lock): r  bg_reproject_headzTHINGS AT THIS PLACE:
u1   WORLD FACTS (creator-confirmed — always true):
bg_fill_tailbg_reproject_tailr   )r   r   r   r   r   r   r  r~   )r  r  r  rP  r  r  r  r  r  r  r  s              rI   build_bgfirst_bg_promptr  
  s   2 &n5H &&(L  !&&(L 
 BHHJ=i[I
|,">"2!4	
 ,h@@E=i[I
|,">"2!4
 _% 	%& 	.1BBC@ 	! 	[^**,- .3UW	6
 	[(**,- .3UW	6 ;;urK   c                    |rdt        |      }}n!|rdt        t              }}ndt        |      }}t        t        ||      j	                         dz   | z   S )u  Step2 인물 삽입 프롬프트 — 정본 final_prompt: stage_head(배경
    EXACTLY 유지·스케치=인물 배치만·선 잔류 금지) + base 스틸 전문.

    mannequin=True (2026-07-26 확정 흐름): 배경본의 회색 마네킹을 실제
    인물로 교체하는 계약 스템(stage_head_mannequin)을 쓴다 — 위치·크기·
    자세·방향 유지, 미러 금지, 마네킹/스케치 선 잔류 0. lane 체인의
    Step1 은 마네킹을 **보존한 채** 배경만 실사화하므로(bg_fill_head
    "do not turn them into people"), 이 교체 계약이 없으면 회색 마네킹이
    최종 스틸까지 살아남는다.

    스템 교체이지 덧붙임이 아닌 이유: v7 stage_head 는 ①"배경을 EXACTLY
    유지"(=마네킹 유지로 읽힌다)와 ②"SECOND attached image (LAYOUT
    SKETCH)"(lane 은 conti=None 이라 그 참조 자체가 없다)를 품고 있어,
    앞에 절을 덧대면 두 문장이 그대로 남아 서로 싸운다.

    geom_authority=True (2026-08-07, 팩 v15): 좁고 복잡한 실내 —
    `stage_head_geom` 으로 교체한다. v7 stage_head 는 "Ignore the sketch's
    background lines" 를 품고 있어 덧대면 두 지시가 싸운다. 교체본은 배경의
    조작 장치·좌석·거울을 세고 같은 수·같은 자리로 유지하게 하며, 스케치의
    배경선을 인물과 구조의 관계를 읽는 데 쓰게 한다. 실물 대조가 근거다 —
    배경본은 핸들 1개인데 인물 삽입 뒤 림이 이중이 됐다.

    mannequin 과 겹치면 mannequin 이 이긴다: 마네킹 교체는 그 자체가 배경을
    고쳐 쓰는 계약이라 "배경을 그대로 두라"와 양립하지 않는다. 마네킹 경로는
    lane 체인 전용이고 lane 은 이 판별 대상이 아니다.

    base 스틸 프롬프트 전문은 세 경로 모두 그대로 유지한다 — 자세 정본·
    조명·표정 사실감·소품 계약을 잃지 않는다. 기본값(False)은 기존
    산출과 byte-identical.
    stage_head_mannequinstage_head_geom
stage_headr   r   )r   r  r   r   r   )rU  r  	mannequinr  rX   r  s         rI   build_bgfirst_final_promptr    sk    H "$:>$J 	"#@A  &'=n'MhGT84::<
	
	rK   )r  entity_label_versionr  c                F   t        |      }t        |      }t        t        d|      j                         | fg}	|K|rdt        t              fnd|f\  }
}|	j                  t        t        |
|      j                         |f       t        t        d|      j                         }t        t        d|      j                         }|D ](  \  }}|	j                  |j                  |      |f       * |D ](  \  }}|	j                  |j                  |      |f       * |	S )u  Step2 참조 조립 — [SHOT BACKGROUND, LAYOUT SKETCH, 엔티티].

    bg/sketch 라벨=팩 v7 정본 스템, 엔티티 라벨=샷의 기본 팩(v1 —
    eligibility 가 일반 콘티 샷만 허용하므로 다른 selector 불가)과 동일
    포맷 — 무콘티 후보(B)와 엔티티 라벨이 정확히 일치해야 비교가 순수.

    conti=None (2026-07-26 확정 흐름): LAYOUT SKETCH 슬롯을 생략한다 —
    lane 체인의 배경본은 이미 마네킹 배치와 장소를 담고 있어 콘티가
    중복이고, 선 그림을 참조로 넣으면 스케치 선 잔류 위험이 있다.
    비-lane 호출(conti 실재)의 참조 순서·라벨은 불변이다.
    bg_labelr   sketch_label_geomr  r  r  r  )r   r   r   r   r  r   r  )bgr  r  r  r  r  r  r  ent_resolvedr  sk_stemsk_verr  r  r  r  s                   rI   build_bgfirst_refsr  Q  s+   * &n5H)*>?L	Wj(	;	A	A	CRH#D   !#$ABD$2H#= 	
 	'6:@@BEJ	
 |55:UW |55:UW 	cZ&&D&1378 	cZ&&D&1378 KrK   c                X    t        |       }t        t        d|      j                         S )u   2택1 중립 판정 헤더 — 기본 헤더('generated from this')는 체인/무콘티
    후보가 서로 다른 참조·프롬프트로 생성되므로 거짓(HIGH-6 계약 연속).judge_headerr   )r   r   r   r   )r  r  s     rI   load_bgfirst_judge_headerr    s&    
 &n5HwAGGIIrK   >   r  bgonlyno_platec                z    t        | t              r| sy| j                  d      ry| j                  d      t        v S )u  콘티 스텝이 구조적 비대상으로 선언한 샷 여부 (E2E10 결함 1호 fix).

    shot_conti_light 모듈 계약: "콘티 부재가 스틸을 막지 않는다 — 플레이트
    없는 샷은 skipped_reason='no_plate'". BGFIRST2 정책 대상은 '콘티 사용
    샷'만(사용자 확정 2026-07-20) — 콘티 스텝이 스스로 대상 외로 선언한 샷
    (no_plate/prev/bgonly)은 fail-closed 가 아니라 기존 경로 유지. E2E10
    실측: 야외 prev_shot_ref 그룹·lane none 의 no_plate 10샷 fail-closed +
    prev 앵커 부재 연쇄 4샷(skipped=prev 인데 prev_sel=None → prev_used=
    False 로 정책 대상 오판정). entry 부재/비정형(non-dict)/생성 error/
    미지 사유는 여전히 정책 대상 → bgfirst_conti_defect fail-closed
    (Codex BLOCKING 계약). error 는 skipped_reason 보다 우선 — 손상·stale
    CP 에서 구조적 skip 에 가려 legacy 로 무음 하강하지 않는다 (Codex
    NARROW 재리뷰).
    Ferrorskipped_reason)r   r   r   BGFIRST_STRUCTURAL_SKIPS)conti_entrys    rI   bgfirst_structural_skipr    s8     k4(w??+,0HHHrK   )chain_lane_prevc                "    |s| sy|ry| xr | S )ut  BGFIRST full 대상 판정 (E2E10 fix④ — 사용자 확정 "콘티=무조건 배경
    우선 전면화", 순수 로직).

    콘티가 실재하는 전 샷 = 체인 대상: complex(structure seed)·seed-bg·
    no_plate(콘티 팩 v3 저작) 전부 포함 — bgfirst_eligible(v2 계약)과
    달리 구조물 파이프도 체인으로 편입한다(Step1 에 STRUCTURE LOOK 3번째
    참조·위치 권위 해석은 서비스 담당). 비콘티 샷(prev/bgonly/lane)은
    확정 레시피대로 기존 유지.

    chain_lane_prev (2026-07-25 사용자 확정 — 야외 케이스1 스펙 E·F):
    lane(마커 스케치 콘티) 샷과 prev 지휘 샷도 체인 편입. 실측 근거:
    lane 샷은 배경 권위가 아예 없어 같은 장소 일반 샷과 다른 장소로
    그려졌고(S15sh1 vs S15sh5), prev 지휘 샷은 구도가 다른 직전 스틸을
    재투영 없이 참조만 해 장소가 재현되지 않았다. 배경 권위 해석(prev
    스틸/groupbg)과 조립 통일은 서비스 담당. bg_only 는 계속 제외
    (콘티 자체가 없다). default False = 기존 경로 byte-identical.
    FTrx   )r  rf  rn  ro  r  s        rI   bgfirst_eligible_fullr    s!    2 m=*]*rK   )rB  r  location_detail_enscene_evidencec           
        t        |      }t        t        d|      j                         d| d|  g}|t        v r|xs dj                         rC|j                  t        t        d|      j                         d|j                          z          |D cg c]'  }|xs dj                         s|j                         ) }	}|	rK|j                  t        t        d|      j                         dz   dj                  d	 |	D              z          |j                  d
| dt        t        dt        t                    j                         g       dj                  |      S c c}w )u  no_plate 그룹의 장소 단위 배경(groupbg) 프롬프트 (fix③④).

    head(무인+콘티=공간 근거·사진 신조) → THE LOCATION(장소 lock+world
    anchor) → [v11+ LOCATION DETAIL(loc 서술/traits) → SCENE EVIDENCE
    (share 그룹 원문 인용 — 장소 사실만)] → TIME OF DAY → tail(16:9 샷
    중립 실사 배경). **샷 특정 정보(SHOT TEXT/카메라) 불포함** — 같은
    장소의 모든 샷이 재사용하는 그룹 공용 배경이라 특정 샷 카메라에
    묶지 않는다 (샷별 카메라는 Step1 재투영이 담당).

    E2E11 ③ 실측: 장소 근거가 place_en 1문장+콘티뿐이라 감식 현장
    groupbg 가 허허벌판 시멘트 바닥으로 렌더 — 가용 근거(loc description/
    visual_traits·share 그룹 evidence 인용)를 코드 조립으로 주입한다.
    빈 입력=절 생략(dangling 금지), v10 이하=kwargs 무시(byte-identical).
    groupbg_headr   zTHE LOCATIONr  r[   groupbg_detail_headr  groupbg_evidence_headc              3  &   K   | ]	  }d |   yw)r4  Nrx   )ry   qs     rI   r|   z'build_groupbg_prompt.<locals>.<genexpr>  s     "<VRs8Vs   r  r  groupbg_tailr   )	r   r   r   r   _GROUPBG_DETAIL_PACKSr   r~   extendr  )
r  r  rB  r  r  r  r  r  r  quotess
             rI   build_groupbg_promptr    s[   . &n5HG^X>DDF
|nBzl3E ..$"++-LL2H%')//1234 &4I^R7H!'')^ILL4h%' "<V"<<= 
LL
~.a0^**,	- .3UW  ;;u Js   E%1E%c                0   ddl }ddl}|j                  | xs dj                         |D cg c]'  }|xs dj                         s|j                         ) c}gdd      }|j	                  |j                  d            j                         dd S c c}w )	u6  groupbg 장소 근거의 그룹 단위 지문 (Codex 리뷰 NARROW-4).

    meta 에 실려 **모든 멤버**(origin 아닌 후속 샷 포함)가 상류 근거
    (loc 서술/traits·share 그룹 evidence) drift 를 감지하게 한다 —
    기존 meta(model/size/pack/contract/group_sig)만으로는 origin 완료
    후 partial resume 시 stale groupbg 가 조용히 재사용되던 창 봉합.
    입력은 그룹 안정 파생값이어야 한다(샷별 값 금지 — 멤버 간 지문
    불일치로 공유 배경이 흔들리는 역결함 차단).
    r   Nr[   FT)r   	sort_keysr      )r  r   r   r   r  r   r  )r  r  _hashlibr   r  payloads         rI   groupbg_context_sigr    s     kk%2,,. .D117//2CQWWYD	
 d  G ??7>>'23==?DD	 Es   B
B
c                    t        | xrE |du xr? t        |t              xr- |j                  d      xr |xr t        |t              xr d|v       S )u&  (era R2 BLOCK-1) 조사 미성립 시 기존 groupbg 보존 여부 — 순수 판정.

    True 조건 전부: era ON · 이번 방문 조사 산출 없음 · outcome 이
    "실패"(비대상 아님) · 기존 파일 정상 · 이전 meta 에 era_research_sha
    존재(=원래 era 로 만든 배경). 보존은 현재 계약 검증을 미루는 것이라
    호출측은 걷기 끝 미봉인(StillEraPreserveIncomplete)과 반드시 짝지어
    쓴다 — 보존만 하고 봉인하면 진짜 drift 가 낡은 배경 아래 영구 동결.
    Nfailedera_research_sha)rE   r   r   r   )era_onera_metaoutcomefile_ok	prev_metas        rI   era_preserve_appliesr    sf       	,8t# 	,w%	,*1++h*?	,	,"9d3	, )+	- -rK   c                j    |r| j                  d      |k(  sy |       }| j                  d      |k7  |fS )u  groupbg 재사용 결정 — (regenerate, fp).

    계약(서비스 인라인 블록을 순수 함수로 추출):
    - 파일 결손 or meta(팩·모델·계약·group_sig·context_sig) 불일치 =
      멤버 무관 재생성
    - meta 일치 = **canonical origin 입력(현재 상류 SOT 기준) 지문 대조**
      — Codex 3차 리뷰: origin 이 already_done 으로 skip 되는 partial
      resume 에서 follower 가 origin 의 현재 place/time/콘티 bytes drift
      를 감지하지 못하던 창 봉합. fingerprint_fn 은 호출측이 canonical
      origin 기준(자기 자신 or record.origin_tag 의 현재 입력)으로
      구성한다 — follower 자기 값 지문 금지(멤버별 진동 차단).
    meta)TNinput_fingerprintr   )prev_recr  r  fingerprint_fnfps        rI   decide_groupbg_reuser  0  s<    & V,4		B<<+,2B66rK   )current_group_sigc                b   t        | j                  d      xs d      xs |}| ro|m| j                  d      xs i j                  d      }|||k7  rt        d| d      |xs i j                  d      xs g }||k7  r|r||vrt        d| d	      ||k(  ri |d|iS  ||      }i |d|iS )
u  canonical origin 입력 해석 (Codex 재리뷰 NARROW-1 + 3·4차 NARROW).

    fresh run 은 그룹의 story-순 최초 샷(origin) 콘티·장소로 생성한다.
    partial resume 에서도 지문 대조·재생성이 전부 **origin 의 현재 상류
    SOT 입력** 기준이어야 실행 형태와 무관하게 픽셀/lineage/감사값이
    동일하다(3차 리뷰: 과거 record 복원만으로는 origin 의 현재
    place/time/콘티 bytes drift 를 follower resume 이 감지 못함). 계약:

    - **group_sig 변경 = fail-closed** (4차 리뷰): share 그룹이 재저작돼
      멤버 구성이 바뀌면(old origin 이탈·더 이른 멤버 추가 포함) 과거
      origin 을 보존해 재생성하는 것도, 트리거 샷을 조용히 승격하는
      것도 fresh-equivalence 를 깨므로 record 리셋 재실행을 명시 요구.
      origin_tag ∉ 현재 그룹 tags(레코드 손상)도 동일.
    - origin_tag = prev_rec.origin_tag(있으면 보존) else 현재 tag —
      follower 를 새 origin 으로 조용히 승격하지 않는다.
    - 자기 자신이 origin = current_inputs 가 canonical.
    - 타 origin = origin_lookup_fn(origin_tag) 이 **현재** authoritative
      maps(classify/콘티)에서 해석 — 해석 실패는 lookup 이 ValueError
      (fail-closed, 원 origin 재실행 요구). follower 자기 값은 절대
      사용하지 않는다.

    반환: {place_text, time_of_day_en, conti_path, conti_asset_id,
    origin_tag}
    
origin_tagr[   r  	group_siguJ   groupbg share 그룹 재구성 감지(group_sig 변경) — 과거 origin(um   ) 보존/승격 모두 fresh-equivalence 위반이라 fail-closed. groupbg record 리셋 후 재실행 필요tagsgroupbg origin(uN   )이 현재 그룹 tags 에 없음 — record 손상/드리프트, fail-closed)r   r   r   )	r  r,  current_inputsorigin_lookup_fnr  r  prev_sigr  r  s	            rI    resolve_groupbg_canonical_originr  I  s    @ X\\,/526=#J%1LL(.B33K@H0A$A$ &WW 
 "'R,,V4:*D*@!* .: :  S4.4,44
+H1h1j11rK   c                   | rt        |       nd}|-|j                         r|j                         j                  dk  rt	        d| d| d      t        |t              r|j                         st	        d| d      |S )u/  canonical 콘티 소스 검증 (Codex 4차 NARROW-2) — 생성 전 fail-closed.

    파일 실재+비어있지 않음+asset UUID(SOT) 필수 — Path.exists() 만으로
    디렉터리/0-byte/asset 결손이 통과해 감사 edge 가 과거 UUID 로 남던
    창 봉합. 반환=검증된 Path.
    Nr   r  u4   ) 콘티 파일 무효(부재/디렉터리/0-byte): u    — fail-closeduA   ) 콘티 asset UUID 결손 — lineage SOT 미해결, fail-closed)r   ru  statst_sizer   r   r   r   )
conti_pathconti_asset_idr  ry  s       rI   validate_groupbg_conti_sourcer  ~  s     'ZDAy		qvvx'7'71'<j\ *"~%57
 	
 ~s+0D0D0Fj\ *1 1
 	
 HrK   c                "    | st        d      | gS )uw   groupbg lineage 입력 검증 — 근거 콘티 UUID 실재 필수 (fail-closed,
    bgfirst_require_input_ids 관례).u5   groupbg 근거 콘티 asset 미해결 — fail-closedrc  )r  s    rI   groupbg_require_input_idsr    s     PQQrK   lane_conti_only)conti_attachedc                    |dddddt         fvrt        d|      |t         k(  r| st        t          d      | r	|rdd	gS d	gS |g}|r|j                  d
       |S )uM  final still 의 직접 첨부(actual_attached) lineage 역할 목록
    (Codex HIGH-3 — false direct edge 방지, 순수 로직).

    체인 승: final 의 실제 refs=[bgfirst_bg, conti, entities] — conti/
    bgfirst_bg 만. 위치 권위·STRUCTURE LOOK 은 Step1 중간 bg 의 input
    edge 가 소유한다(직접 첨부 아님).
    무콘티 승: B 후보 refs=[위치 권위(+STRUCTURE LOOK), entities] —
    권위 종류별 role + seed(실재 시).

    authority_kind="prev" (2026-07-25, 케이스1 스펙 E·F): prev 지휘 샷
    체인. 무콘티 후보가 없어 B=현행 prev 경로 그대로이므로, 무콘티 승
    시 role 은 prev_still(+콘티 실재 시 conti) — 서비스가 현행 조립과
    동일한 첨부를 재현한다.
    authority_kind="canon_master" (동): lane 샷 체인 — 콘티(마커 스케치)
    가 본 장소 실사가 곧 위치 권위다. 무콘티 승 시 그 사진이 직접 참조.

    conti_attached (2026-07-26): 체인 승 final 이 콘티를 **실제로** 참조
    하는지. 확정 흐름의 lane 체인은 Step2 참조에서 콘티를 빼므로
    False — 이때 direct role 은 bgfirst_bg 뿐이고 콘티 UUID 는 중간
    bgfirst_bg 의 input edge 에만 남는다(conti → bgfirst_bg → final).
    True 로 두면 존재하지 않는 direct edge 가 생긴다.
    r  groupbgr  r  canon_masterzunknown authority_kind uj    는 체인 단독 경로 — chain_won=False 는 도달 불가 조합 (거짓 lineage 대신 fail-closed)r  
bgfirst_bgr  )LANE_CONTI_ONLYr   r   )	chain_wonauthority_kindrL  r  r   s        rI   bgfirst_winner_lineager    s    : Iv~  2>2DEFF(  G G
 	
 *8&L|nL
C

#$JrK   c                T    t        t        dt        |             j                         S )un   Step1 3번째 참조(STRUCTURE LOOK) 관할 절 — complex 샷 체인 편입
    (fix④). 팩 v9 스템 SOT.bg_reproject_seed_clauser   rh  )r  s    rI   build_bgfirst_seed_clauser    s(    
 +&~6 egrK   c                    | xs i }|j                  d      rd|d    S |j                  d      r	d|d    dS ||j                         sd|j                  d      dS |j                  d	      sy
y)u  BGFIRST 정책 대상 샷의 콘티 산출 결함 사유 (없으면 None).

    Codex 리뷰 1 (BLOCKING): flag ON 에서 정책 대상(일반 콘티 샷)이 콘티
    실패/파일 결손/asset 결손이면 legacy 무콘티 생성으로 조용히 하강하지
    않는다 — 호출자는 사유로 mark_failed (fail-closed). 비대상(prev/
    bgonly/lane/complex)은 이 판정을 타지 않는다.
    r  u   콘티 생성 실패: r  u   콘티 미생성(skipped=r   Nu   콘티 파일 결손(
image_pathr   u7   콘티 asset_id 결손 — lineage 없이 진행 금지)r   ru  )r  r  entrys      rI   bgfirst_conti_defectr    s     2Eyy'g'788yy!"*51A+B*C1EE!3!3!5&uyy'>&ACC99Z HrK   )seed_asset_idseed_attachedr  c                    | st        d      |t        k(  r|s|t        t         d| d      | g}n|st        d| d      | |g}|r|st        d      |j                  |       |S )u  Step1 lineage 입력 검증 — conti/plate UUID **둘 다** 실재 필수.

    Codex 재리뷰 1 (HIGH): plate UUID 미해결(row 부재·조회 예외로 None)
    상태에서 bg asset·성공 record 를 영속하면 불완전 lineage 가 성공으로
    고착된다 — 등록 **전** ValueError 로 샷 fail-closed (warning-only
    fail-open 금지). 반환=[conti, plate] 고정 순서.

    seed_attached (fix④ full — complex 샷 체인 편입): STRUCTURE LOOK 을
    3번째 참조로 첨부한 경우 seed UUID 도 실재 필수 → [conti, plate,
    seed]. default False=기존 2 UUID byte-identical.

    authority_kind=LANE_CONTI_ONLY (2026-07-26 확정 흐름): 외부 사진이
    아예 없는 lane 체인 — 플레이트 UUID 요구를 **이 모드에서만** 면제
    하고 [conti] 만 반환한다. 콘티 UUID 는 여전히 필수(불완전 lineage 를
    성공 record 로 영속 금지 원칙 유지). 다른 kind 는 기존 fail-closed.
    u.   bgfirst 콘티 asset 미해결 — fail-closedu*    인데 플레이트 입력이 공급됨 (u*   ) — 참조 0 계약 위반 (fail-closed)u%   bgfirst 플레이트 asset 미해결(u3   ) — 불완전 lineage 영속 금지 (fail-closed)uX   bgfirst STRUCTURE LOOK asset 미해결 — 불완전 lineage 영속 금지 (fail-closed))r   r  r   )r  plate_asset_idrt  r  r  r  r   s          rI   bgfirst_require_input_idsr    s    2 IJJ(Z3"# $<IK  7
| D6 6  ~..  	

=!JrK   r  bgfirst_step1)model
asset_typer   chainc                     |t        |             }d|
i} ||      }|/ |||	t        |      |       ||_        |dd |_        d|_        |S  |||||dd       }d|_         |||	t        |      |       |S )u  Step1 재투영 배경을 intermediate ImageAsset 으로 idempotent 등록.

    asset_type/role/chain (fix③④ full): groupbg(장소 단위 공유 배경)도
    동일 upsert 계약으로 등록 — default=기존 bgfirst_bg byte-identical.

    Codex 리뷰 2 (BLOCKING): 캡처 큐는 이 서비스에 generation_context 가
    없어 no-op 이고, 있어도 UUID generated 경로로 등록해 recipe 경로
    조회와 어긋난다 — recipe 경로 기준 **명시 upsert** 가 lineage SOT.
    register_intermediate_assets 와 동일 주입 계약: 기존 row 는 lineage/
    prompt/model 을 현재 값으로 갱신(재생성 후 stale edge 잔존 차단),
    신규는 생성. input_ids=bgfirst_require_input_ids 산출([conti, plate]
    — 호출 전 검증 완료 계약). 재리뷰 2 (NARROW): fresh/existing 모두
    is_intermediate=True 정규화(캔버스 필터/접기 — 이 전용 recipe 경로는
    bgfirst_bg 중간물 단일 용도). 반환=asset.
    r  N)r   r   r  i  T)relr  r  r   )r   r   generation_modelprompt_usedis_intermediate)bg_pathr   r   rel_fnfind_asset_by_rel	new_assetannotate_fnr  r  r   r  r!  r  existingassets                  rI   register_bgfirst_bg_assetr,  *  s    : W
C#U+D %H44	?	
 %*!%fu~#' Jefu~E !EDDO$ LrK   )r   r   )rR   r   r   r   )rR   r   r   Tuple[str, str])r]   rE   r   r   )rR   r   re   rE   r   r-  )rR   r   rj   r   r   r   )r   r   rX   r   r   r   r   r   )
r   r   r   r   r   r   rR   r   r   zTuple[str, List[str]])r   r   r   r   )r   r   rX   r   r   r   )r   r<  )r   r   r   r   r   r<  )r   r   r   r   r   zDict[str, Dict[str, Any]]r   Dict[str, str]r   r<  r   r   )r   Sequence[Dict[str, Any]]r   zTuple[List[str], List[str]])Jr?  r   r@  Sequence[Tuple[str, Any]]r   r   r.  r   r   r   r!  r   rA  r   r0  r   r1  r   r3  r   r4  rE   rB  r.  r:  r<  r  r   r  r   r  r   r  r   r  r   r  rE   r	  rE   r
  r   r  rE   r  r   r  r   r  r   r  r   r  rE   r  r   r  r   r  r   r  rE   r  rE   r  r   r  r   r  r   r  r   r   zTuple[Any, Dict[str, Any]])r9  r<  r   rE   )r9  r<  rU  r   r   r   )r?  r   r9  r<  r   r   )rd  r   re  rE   rf  rE   r   r   )rl  rE   rm  rE   rf  rE   rn  rE   ro  rE   rp  rE   rq  rE   r   r   )rv  Optional[Dict[str, Any]]rw  r   rx  r  r   z/Tuple[Optional[Path], Optional[Dict[str, Any]]])r  r/  r,  r   rd  r   r   zSet[str])r   Dict[Tuple[int, int], List[str]]r   Tuple[int, int]r   	List[str])r  r2  r   r3  r   zTuple[List[str], bool])r  r   r   r   )"rf  rE   r  r  r  r  r  r  r  r0  r  r0  r  r  r  r  r  rE   r  r  r  r  r  r   r  rE   r  r   r  r   r  rE   r   r  )r  r  r  r   r  r0  r  r0  r  r  r  r  r  r   r   z3Tuple[List[Tuple[str, Any]], List[Tuple[str, Any]]])r[   )r  r1  rR   r   re   rE   r   r   )ro  rE   r  rE   rf  rE   r   rE   )r  r1  r   r   )r  rE   rR   r   r   r   )r]   rE   r  rE   r   rE   )r   r   r]   rE   r  rE   r   r   )rR   r   r  r   r  r   r   r   )rU  r   r&  Sequence[str]rR   r   r   r.  )r,  r5  r/  rE   r   r   )r8  r/  r   r   )r  r   r@  r   r   r   )<r  r   r  r   r  r   rB  r   rf  rE   rn  rE   rC  r   rD  r5  rE  r   rF  r   rG  r   r  r   rH  r5  rI  r5  rJ  rE   rK  r   rL  rE   rM  rE   rN  rE   rO  rE   rP  r   rQ  r   r  r   rR  r   rS  r   r  r   rT  r   r  r   rU  r   r   r   )r  r   r  r1  r   r   )r  rE   rf  rE   rn  rE   ro  rE   rm  rE   rL  rE   r  rE   r   rE   )r  r   r   r   )r  r   r  r   r  r   rP  r   r  r   r  r   r  r   r  rE   r  r   r   r   )
rU  r   r  r   r  rE   r  rE   r   r   )r  r   r  r  r  r0  r  r0  r  r   r  r   r  rE   r   r  )r  r   r   r   )r  r1  r   rE   )r  rE   rf  rE   rn  rE   ro  rE   r  rE   r   rE   )r  r   r  r   rB  r   r  r   r  r   r  r5  r   r   )r  r   r  r5  r   r   )r  rE   r  r1  r  r1  r  rE   r  r   r   rE   )r  r<  r  r<  r  rE   r   zTuple[bool, Optional[str]])
r  r<  r,  r   r  r<  r  r1  r   r<  )r  r   r  r   r  r   r   r   )r  r   r   r4  )
r  rE   r  r   rL  rE   r  rE   r   r4  )r  r1  r  r  r   r   )r  r   r  r   rt  r   r  r   r  rE   r  r   r   r4  )r%  r   r   r   r   r5  r&  r   r'  r   r(  r   r)  r   r  r   r  r   r   r   r  r   r   r   )__doc__
__future__r   loggingr  r   typingr   r   r   r	   r
   r   r   r   r   	getLoggerr   r  r   r   LANE_PROMPT_VERSIONCOMPLEX_PROMPT_VERSIONSEED_BG_PROMPT_VERSIONrb   r  r  rd   r{  r  r  r~  r  r  r.  r7  r  r  r}  r|  rP   rO   r  r  COMPLEX_AB_CONTRACT_VERSIONr  rx  r  r  BGFIRST_PROMPT_VERSIONBGFIRST_CONTRACT_VERSIONBGFIRST_BG_IMAGE_MODELBGFIRST_BG_SIZEBGFIRST_FULL_PROMPT_VERSIONBGFIRST_FULL_CONTRACT_VERSIONBGFIRST_LANE_PROMPT_VERSIONBGFIRST_LANE_CONTRACT_VERSIONry  rz  STILL_CONDUCT_PROMPT_VERSIONr  r  r\   r  rH   rG   rJ   ri   rc   rW   rS   rY   r^   rg   ra   r   r   GROK_SHED_POLICY_BASELINEr   r   r   r   r   r   r   r   r   r  rH  rQ  rX  r_  rg  rr  r{  r  r  r  r  r  r  r  r  r  r  r  r	  r  r  r  r  r!  r#  r)  r0  r<  r  r  r  r  r  r  r  r  r  	frozensetr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r,  rx   rK   rI   <module>rJ     s  $ #   B B B 1			8	$
t	t 		t 	t 	t( 	)t4 	5t@ 	AtL 	MtV 	Wtd 	
etr 	
st| 	
}tJ 	
Kt` 	
at| 	
}tL 	
MtV 	
Wtj 	
ktt 	
utJ 	
KtZ 	
[tp 	
qtH 	
It` 	
atl 	
mtp 	
qtt 	
utH 	
ItR 	
Stb 	
ctn 	
otr 	
stB 	
CtJ 	
Kt~ @ 
 
  , 
:  gt n    ! $  $  $(   &* " $(  
     $ '+ $ " "&  "& 
 "&  # "   !%  ! $( ! $  !% 
    #J u c
      2  #  1  #  #  $(   %) !#   $  $(  
  $   #' 
 !%  '+ #)    !=A  "& ,00@&&59&&6D4. 4: .8 J62z 9	000 0 	0
 0f
L	92M;&M;M; M;`	 
 *	
 # ( *!+! !N  "! */##'!! $) % $!% W[[ +[ 	[
 [ [ [ [ [ [ [ [  [ &[ [  ![" #[( )[* +[, -[. /[0 1[2 $(3[4 5[6 7[8 !9[: ;[< =[> ?[@ A[B C[H !I[L M[N O[P Q[T U[V W[X  Y[|0&&),&&000+00(  	
 	2FF F 	F
 F F F F 	FB0-0 $0 "	0
 50f(	  	.1/16E11&/&6E&&&: #' $"%)"   !&-aa a 	a
 a )a )a  a a a #a a a a a$ %a, -a. /aT &*"+)+) +) )	+)
 )+) #+) +) +) 9+)b +	   ""	  @Bi%*i%i9<i"i 	iXH&28 24+.<//$(/	/f ,=="&=15==& &  "	&& & 	&
 	&R,   	.LLL 	L(*>H   "$  " %' "$)      kgg g 	g
 g g g g  g g g g g  !g( ")g. /g0 1g2 "3g4 5g6 7gD EgF GgJ KgL MgN OgT UgZ [g` agb cgj kgl 	mgT$' @  	
   "  
8%Z 0II I 	I
 I I I I I I 	I\ 1 	111 1 	1
 	1t 1 # -- - )	-
 )- - - - -b 1JJJ %%CD I8 "++ + 	+
 + + 
+D   5 $&33 3 	3
 3 3 "3 	3lEE "E 		E6-- '- &	-
 - - 
-.77 7 	7  7> 372222 
22 #	22 022 22j  	
 
4 $  ,, , "	,
 , ,` 6) 8 $(!0!0 "0 	0
 !0 0 0 0x (" 11 1 	1
 1 1 1 1 1 1 1 1 	1rK   