
    8^j                    0   U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
mZ ddlmZ  ej                  e      ZdZdd	iZd
ddiddidddgddZded<   e	ege
eef   f   ZddZdddZdddZdZd dZ	 	 d!	 	 	 	 	 d"dZddd	 	 	 	 	 	 	 	 	 	 	 d#dZy)$uP  background_unmanned — 배경 렌더 무인(NO PEOPLE) 계약 (2026-07-20 이식 ①).

E2E9 L09B01 실측: LLM 저작 배경 프롬프트가 씬 인물 서술을 포함하면
플레이트에 인물이 구워진다(무인 계약 위반) — 그 플레이트를 참조하는
하류 스틸 전체가 오염되는 상류 결함. 강제 지점=렌더 경로 단일 seam
(`render_one_background` 서두):

  ① 프롬프트 계약(no_people_clause) 강제 부착 — 단롤 gpt 경로와
     plate_multiroll(nb2) 위임 경로가 같은 계약을 상속.
  ② (opt-in) 무인 VLM 게이트: 렌더 성공 후 Gemini 구조화 판정
     {people_visible, evidence_ko} → 위반 시 재시도 절(no_people_retry)
     을 덧붙여 1회 재렌더 → 재판정. 그래도 위반 = fail-open + 감사
     기록(`people_detected=True`) — 플레이트 실패가 에피소드 전체를
     막지 않는다(후속 라운드 개선 근거·리뷰 갤러리 노출용).

판정은 전부 VLM(gate_judge_sys 팩) — 글자/substring 의미 판단 없음.
flag OFF(default) = 기존 경로 byte-identical.
    )annotationsN)Path)AnyCallableDictOptional)load_promptbackground_unmanned1z1.202607201540objecttypebooleanstring)people_visibleevidence_kor   r   F)r   
propertiesrequiredadditionalPropertiesDict[str, Any]GATE_SCHEMAc           	     n    	 t         |    S # t        $ r  t        d| dt        t                d      w xY w)Nz4unknown background_unmanned prompt version selector z	 (known: ))PROMPT_VERSION_MAPKeyError
ValueErrorsorted)selectors    ^/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/background_unmanned.pyresolve_prompt_versionr   2   sK    
!(++ 
Bl)F+=$>#?qB
 	

s    )4c                T    t        t        dt        |             j                         S )Nno_people_clauseversionr	   _MODULEr   stripprompt_versions    r   build_no_people_clauser)   <   s&    #&~6 eg    c                T    t        t        dt        |             j                         S )Nno_people_retryr"   r$   r'   s    r   build_retry_clauser-   C   s&    "&~6 egr*   )no_people_clause_attachedunmanned_gatepeople_detectedc                F    t         D ci c]  }|| v s|| |    c}S c c}w )uB  render info 의 무인 감사 필드 subset (CP 영속용, E2E10 실측 fix).

    E2E10: step 의 CP entry whitelist 가 감사 3필드를 탈락시켜
    people_detected 분포 acceptance 를 CP 로 검증할 수 없었다. 존재하는
    키만 반환 — OFF/부재=빈 dict 라 기존 CP shape byte-identical.
    )
AUDIT_KEYS)infoks     r   audit_fieldsr5   M   s*     !+8
1a4iAtAwJ
888s   	c                    ddl m ddlm}m di | xs i d|iit        t        dt        |            j                         d	fd}|S )
uV   Gemini 구조화 무인 판정 — multiroll_gemini 판정 어댑터와 동일 배선.r   )call_structured)JUDGE_MODELpng_partbackground_unmanned_gatemodelgate_judge_sysr"   c                B    ddd |       g} |t               S )NtextzBACKGROUND PLATE:)r   r>   )project_configschema_name)r   )png_pathpartsr7   	judge_syspcr9   step_tags     r   judge_fnz(make_unmanned_judge_fn.<locals>.judge_fnf   s8    %89X
 i8
 	
r*   )rA   r   returnr   )	app.modules.llm.llm_clientr7   %app.modules.pipeline.multiroll_geminir8   r9   r	   r%   r   r&   )	r?   r(   r8   rF   r7   rC   rD   r9   rE   s	       @@@@@r   make_unmanned_judge_fnrJ   W   s`    
 ;K)H	E^!r	EHw.D	EB!&~6 eg 

 
 Or*   )rF   r?   c                |   | j                  d      dk7  r| S |t        |      }	  |      }d	|i| d<   |j                  d
      s| S t        j	                  d|j                  dd             j                  j                   dj                         	 t        j                         dfd}	  |       }|j                  d      dk7  sj                         s% |        |j                  d      | d   d<   d| d<   | S d	|i|d<   	  |      }
|
|d   d<   |
j                  d
      rd|d<   |S # t        $ r5}t        j	                  d|       dt        |      dd i| d<   | cY d}~S d}~ww xY w# t        $ r'}t        j	                  d|       d| d<   | cY d}~S d}~ww xY w# t        $ rU}ddlm}	  |        t        ||	      r t        j	                  d|       t        |      dd | d   d<   d| d<   | cY d}~S d}~ww xY w# t        $ r$}t        |      dd |d   d<   d|d<   Y d}~|S d}~ww xY w)u  렌더 결과 무인 게이트 — 위반 시 1회 재렌더 + 재판정.

    계약:
      - status!="ok" = 판정 비대상(no-op).
      - 판정 콜 자체 실패 = 게이트 무효(fail-open, `unmanned_gate.error`)
        — 렌더는 유지(게이트가 렌더를 파괴하지 않는다).
      - 위반 → 원본을 `<stem>.unmanned_v1.png` 로 백업 후 retry_fn()
        (강화 절 재렌더, 같은 out_path). 재렌더 실패·**예외** = 백업 복원
        + `people_detected=True` (원본 유지가 산출 0 보다 낫다 — 감사로
        노출). 예산 예외(ImageCallBudgetExceeded)만 복원 후 재전파
        (Codex 리뷰 4: retry callable 예외가 게이트의 '렌더 비파괴'
        계약을 깨지 않도록 예외 경계 명시).
      - 재렌더 성공 → 재판정. 여전히 위반 = `people_detected=True`
        fail-open (2회 이상 재렌더는 비용 폭주 — 후속 라운드 개선 근거).
    statusokN)r?   u*   background_unmanned gate 판정 실패: %serror   r/   attempt1r   uN   background_unmanned: 무인 계약 위반 감지(%s) — 강화 재렌더 1회r    z.unmanned_v1u;   background_unmanned: 백업 실패(%s) — 재렌더 생략Tr0   c                 R    	 t        j                          y # t        $ r Y y w xY w)N)shutilcopy	Exception)backupout_paths   r   _restorez#run_unmanned_gate.<locals>._restore   s(    	KK) 		s    	&&r   )ImageCallBudgetExceededu;   background_unmanned: 재렌더 예외(%s) — 원본 복원retry_errorretry_statusattempt2retry_judge_error)rG   None)getrJ   rU   loggerwarningstr	with_namestemsuffixrS   rT   app.core.image_call_budgetrY   
isinstanceexists)r3   rW   retry_fnrF   r?   v1excrX   
retry_inforY   v2rV   s    `         @r   run_unmanned_gatern   s   sg   . xxT!)Hh
 (,D66"#
NNX
}b! 8==/hoo=N OPFHf%Z
 ~~h4'x/@
0:x0H_n-"&#-r"2J-h24
?#J/66"#,0J() m  CSI!(#c(4C. 9_  TVYZ"&  	F
c23TVYZ/23x~_m,"&	.  -;>s8DS>
?#$78(,
$%-sr   D9 E: 9F- &H 9	E7*E2,E72E7:	F*F%F*%F*-	H6A
H HH	H;H66H;)r   rb   rG   rb   )r   )r(   rb   rG   rb   )r3   r   rG   r   )Nr   )r?   Optional[Dict[str, Any]]r(   rb   rG   UnmannedJudgeFn)r3   r   rW   r   ri   zCallable[[], Dict[str, Any]]rF   zOptional[UnmannedJudgeFn]r?   ro   rG   r   )__doc__
__future__r   loggingrS   pathlibr   typingr   r   r   r   app.modules.prompt_loaderr	   	getLogger__name__r`   r%   r   r   __annotations__rb   rp   r   r)   r-   r2   r5   rJ   rn    r*   r   <module>r{      s*  $ #    0 0 1			8	$
 	 
 !9-) "=1!^  D64S>12
 O
9 04, B +//3S
S S +	S
 (S -S Sr*   