
    j%             (         U d Z ddlmZ ddl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mZmZmZmZ  ej"                  e      Z	 dL	 	 	 	 	 dMdZg dZded	<   e
eeeeeef      egef   Ze
eeeeeef      ee   ee   geee	f   f   Ze
eeeeeef      egeee	f   f   ZdNd
Z	 	 dO	 	 	 	 	 dPdZ	 dQ	 	 	 	 	 dRdZdSdZdTdZdZ dZ!dZ"dZ#dZ$e#e$fZ%dZ&dZ'dZ(e'e(fZ)dZ*dd	 	 	 	 	 	 	 	 	 dUdZ+dVdZ,dWdZ-	 	 	 	 	 	 	 	 dXdZ.	 	 	 	 	 	 	 	 	 	 dYd Z/	 	 	 	 dZd!Z0	 	 	 	 	 	 d[d"Z1	 	 	 d\	 	 	 	 	 	 	 	 	 	 	 	 	 d]d$Z2d%Z3d&Z4d'Z5	 	 	 d^	 	 	 	 	 	 	 	 	 	 	 d_d(Z6dd)	 	 	 	 	 	 	 	 	 	 	 d`d*Z7dad+Z8	 	 	 	 	 	 dbd,Z9dcd-Z:ddd.Z;ded/Z<d#dd#d#dddd#d#d#d#e'e#d0	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dfd1Z=	 dL	 	 	 	 	 	 	 	 	 dgd2Z>d3Z?d4Z@d5ZAd6ZBd7ZCd8ZDd9ZEd:ZF	 	 	 	 	 	 dhd;ZG	 di	 	 	 	 	 	 	 djd<ZHd=ZId>ZJd?ZKd@ZL	 	 	 	 	 	 	 	 dkdAZM	 dL	 	 	 	 	 dldBZN	 	 	 	 	 	 	 	 dmdCZO	 	 	 	 	 	 dndDZPd#dE	 	 	 	 	 	 	 	 	 dodFZQd#dE	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dpdGZRdqdHZSdddId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#e'e#dddddddJ'	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 drdKZTy)su  multiroll_select — s40 공통 생성 파이프 이식 (2026-07-13).

N롤(동일 프롬프트·동일 참조) → Gemini VLM 단독 판정(0-10 + 랭킹, 동점=랭킹
우선) → 선정본 복사(`{stem}_sel.png`) → 결함 검사(CRITIQUE) → 결함 시 수정
프롬프트 저작 → i2i(선정 원본 **단독** 참조) → 수정본이 최종 `_sel`(후속 체인
앵커).

이미지 엔진·VLM 판정은 전부 callable 주입 — 이 모듈은 결정론 로직(롤 파일
재개, 선정/동점 해소, critique 소급 재개, 수정 프롬프트 조립)만 소유한다.
실험 정본: scratchpad/forest_exp/s40_full_multiref.py(_roll3/_judge_gemini/
_critfix/_select/_resume) + s39_threeroll_vlm.py:207-216(_gemini_select).

재개 계약(크래시/예산 소진 후 재실행 안전):
  - 롤 파일(`{stem}_{a..}.png`) 존재 → 해당 롤 skip.
  - `_sel` 존재 + record 에 critique 흔적 있음(critique/critique_skipped)
    → 전부 skip.
  - `_sel` 존재 + critique 미실행 + 게이트 on → 결함 검사·수정만 소급.
  - `_sel` 존재 + record 부재(선정 라벨 불명) → 재지출 없이 sel 유지(경고).
    )annotationsN)Path)AnyCallableDictListMappingOptionalSequenceTuplec           	     z   t        |xs g       }g }t        |       D ]  \  }\  }}|t        |      k  rt        ||   t              r||   ni }|j                  t        |      t        |t        t        f      rt        |      ndt        |       d|j                  d      |j                  d      d        |S )u  records.json 에 적을 참조 목록 — 자산 id 를 함께 나른다.

    ★`labeled_refs` 튜플을 넓히지 않는다. 그것은 코드 전체 243곳에 나오고
    3곳에서 제자리 치환된다(`outdoor_site_layout_step.py:357,464` ·
    `visual_continuity_anchor_step.py:490`) — 넓히면 2-튜플로 푸는 자리가
    전부 깨진다.

    씬 경로에 이미 있는 관례를 따른다: `ref_role_metadata[i]` 는
    `labeled_refs[i]` 와 **index 로 정렬된** 병렬 목록이다
    (`scene_generation_coordinator.collect_actual_attached_refs` 와 동형).
    라벨로 맞추지 않는 이유 — 같은 라벨이 둘일 수 있다.

    ★`path` 칸은 그대로 둔다. 지금 그것을 읽는 것들이 있고, 빼면 그 자리가
    조용히 빈다.
    z<bytes:>asset_idpipeline_role)labelpathr   role)	list	enumeratelen
isinstancedictappendstrr   get)labeled_refsref_role_metadatametasoutir   srcmetas           [/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/multiroll_select.pybuild_ref_recordsr$   !   s    & "(b)E "C$\2<E3s5z>jq4.HuQxb

Z ",C#t!=SX#CH:Q/,HH_-
 	 3 J    )ABCDE	List[str]ROLL_LABELSc                `    | dk  s| t        t              kD  rt        d|  d      t        d|  S )u-   롤 수 → 후보 라벨 목록 (A..E, 1~5).   zroll count out of range: z (must be 1~5)N)r   r,   
ValueError)counts    r#   roll_labelsr1   R   s7    qyEC,,4UG>JKKvr%   TFc           
     H   t        |       }d|dddiddid}g d}ddd|ddd|dd	ddd||d
d	dg dd
}|s|S dddd|dddiddiddidddid	dg dd
d	|d   d<   |r*|d   d   d   }ddi|d   d<   t        |d         dgz   |d<   ddi|d   d<   g d|d<   |S )u  판정 JSON 스키마 — 실험 정본(s29 JUDGE_SCHEMA)의 라벨 가변판.

    ``with_readings`` (팩 v6, 2026-08-06): 후보마다 방향·복잡 구조물 내부
    공간·중요 엔티티 정체를 **문장으로** 쓰게 하는 필드를 요구한다. 점수만
    받으면 안 본 축을 안 본 채로 넘어간다는 것이 실측이다 — 총구가 상대를
    전혀 겨누지 않은 후보가 최고점을 받았고 판정문에 조준 이야기가 한 줄도
    없었다. 서술을 강제하면 그 축을 실제로 보게 된다.

    False 로 부르면 v5 이하와 byte 동일한 스키마다(회귀 안전판).

    ``with_physics`` (팩 v7, 2026-08-07): 공중에 뜬 몸·물체를 **무엇이
    받치는가**를 쓰게 하는 네 번째 축. 심판 4종 대조가 근거다 — 걷는 자세를
    가로로 눕혀 띄운 후보에 Opus·Sol·Qwen 셋이 최고점을 줬고 **셋 다 부유를
    한 줄도 쓰지 않았다.** 같은 이미지에서 Gemini 만 "물리적으로 불가능한
    연출"로 걸었다. v6 는 이것을 `hard_violations` 예시 목록 안에만 적어
    두었는데, 목록에 있다는 것과 보게 만드는 것은 다르다.

    ★이 인자와 팩 selector 는 함께 움직인다. 팩 v7 은 PHYSICS 서술을
    요구하므로 스키마에 자리가 없으면 모델이 쓸 곳을 잃는다 — 되돌릴 때도
    둘을 같이 내린다.

    ★기본값은 **False** 다 (2026-08-07 Codex 2차 리뷰 수용). v7 은 스틸
    전용이라 구조물 씨드·배경 플레이트는 v6 프롬프트를 그대로 쓰는데,
    기본을 True 로 두면 그쪽 판정에 **v6 system("three axes")과 physics
    필수 스키마가 모순으로** 들어간다 — 없는 축을 지어내거나 구조화 호출이
    재시도로 샌다. selector 를 갈랐으면 스키마도 갈라야 한다. v7 을 쓰는
    호출만 명시로 True 를 준다.
    stringtypeenumr5   integer)r   score
verdict_koobjectFarrayr5   itemsr5   additionalProperties
propertiesrequiredwinnerrankingverdicts)r   	directionbuilt_spaceentitieshard_violationsr@   readingsr=   physicsrA   booleanall_candidates_fail)rC   rD   rE   rJ   rM   r   )labelswith_readingswith_physicslabsverdict_propsrA   schemaitems           r#   build_judge_schemarV   Y   sO   > <D"D1)$x(%M
 0H%'6 '*2D!AC  $e"/ (	
 6F" e"*D9$h/ &1#X.,3.4h-?$A
,
(F<$&  l#J/8)/(:\9%Z 01YK?Z3992EF<./1F:Mr%   c                L   ddddddddiddiddiddd	d
ddgddidgd}| r8|d   d   d   }dg dd|d   d<   ddi|d   d<   t        |d         ddgz   |d<   |rE|d   d   d   }dddidd|d   d<   ddd	|d   d<   ddd	|d   d<   t        |d         g dz   |d<   |S )u  결함 검사 JSON 스키마 — 실험 정본(s29 CRITIQUE_SCHEMA).

    with_severity (2026-08-13 사용자 지시 #106): 취합 이슈에 관찰자의
    심각도를 **구조 필드로** 실어 보내는 계약 — v9 까지는 산문 규칙
    ("critical 만 fix_en")뿐이라 취합자가 어겨도 코드가 못 막았다
    (S39sh4: major 소파 지적이 fix 를 발동시켜 인물이 바뀜). True 는
    QK v10 취합 전용 — 기존 경로(v7 critique·GQ v8)는 False 로
    byte-identical 스키마(지문 불변).

    with_ref_gate (2026-08-19 사용자 지시 — 수정본 자세 변질): 지적마다
    **어느 참조를 봐야 하는지** 말하는 칸. 종전에는 이 칸이 없어서 코드가
    고를 방법 자체가 없었고, 그래서 편집 호출에 참조를 전부 붙였다
    (`_critique_and_fix` 의 조건 없는 붙이기). 붙는 것 다수가 인물 정본
    이고 그 라벨은 얼굴·머리·체형을 맞추라고 하므로 사람을 다시 그리라는
    지시가 된다 — 실측 139장면 중 82장면(59%)이 개악으로 버려졌다.
    True 는 참조 선별 ON 경로 전용, False 는 byte-identical.r:   Fissuesr;   r5   r3   rL   u   true when the fault is in the structure's overall form — its storey count, massing, footprint or how many units it reads as holding — so that editing the finished photograph cannot repair it and the image must be generated again.)r5   description)issue_kofix_en	unfixableneeds_regenerationrZ   r[   r>   r<   r@   r=   criticalmajorminorr4   severityr7   observation_indexrA   u  Numbers, from REFERENCE INDEX, of the reference photographs that must be seen to carry out this correction. Attaching a reference is not free: a reference the correction does not need still tells the editing model to look at it, and it then redraws parts nobody asked it to touch — people get restaged, poses reset, wardrobes swapped. Leave this empty when the correction is self-contained (erase something, move something, blur something, change an angle). A reference is needed only when the correction says to match, restore or copy something whose appearance is knowable only from that reference. Be strict; when in doubt, list nothing.)r5   r=   rY   needs_ref_indiceszttrue when the correction requires putting into the photograph a person or object that is NOT currently there at all.adds_missing_entityzPWhat must be added, when adds_missing_entity is true; an empty string otherwise.missing_entity_name)rd   re   rf   rN   )with_severitywith_ref_gaterT   rU   s       r#   build_critique_schemari      sr   ( %$e%+X$6#)8"4 '-i%8 %.!;	/#. ",X 63
@ JE#FH l#H-g6&D*F\:& 4:92E\./Z 01+5- -Zl#H-g6
 i('	3
\./(  5
\01 -5
\01  Z 01 5# #Z Mr%   c            
     >    ddddddddidg ddd	d
dgddidgdS )u  G+Q 수정 흐름의 Qwen **관찰** 스키마 (2026-08-10).

    관찰자는 "무엇이 잘못됐나"만 쓴다 — 수정문(fix_en)·수정 가능성
    (unfixable/needs_regeneration)은 취합자(Gemini) 몫이라 여기 없다.
    severity 는 취합 참고 입력일 뿐 최종 issues 계약에는 실리지 않는다.
    결함이 없으면 빈 배열 — 억지 지적은 수정 자체가 손실인 파이프라인
    (critique 계약 "editing is not free")과 어긋난다.
    r:   Fobservationsr;   r5   r3   r^   r4   )rZ   rb   rZ   rb   r>   r<    rl   r%   r#   build_gq_observe_schemarm      sX     %$e%+X$6$,$B%D# ",Z 8	
 $$# r%   c                  
 | j                  d      xs g }|D ci c]  }|d   |d    }}|s"| j                  d      }|st        d      i |fS t        |j                               }|j	                         D cg c]  \  }}||k(  s| }}}| j                  d      xs g 
t        |
fd      }	||	fS c c}w c c}}w )	u   verdicts 점수 최고 후보 선정, 동점 = ranking 앞선 것 (s39 _gemini_select).

    verdicts 가 비면 winner 필드로 fallback (실험엔 없던 방어 — 판정 스키마가
    required 라 정상 경로에선 도달하지 않는다).
    rE   r   r8   rC   z,judge result has neither verdicts nor winnerrD   c                2    | v rj                  |       S dS )Nc   index)labranks    r#   <lambda>zgemini_select.<locals>.<lambda>N  s    C4KC)OR)Or%   key)r   r/   maxvaluesr=   min)judgerE   vtotalsrC   bestrs   ttiedselectedrt   s             @r#   gemini_selectr   >  s     yy$*H>FGhaj!G*4hFG8$KLL6zv}}D$lln:nFCT	CnD:ii	*0bD4OPH8 H ;s   B;=C C r.   fix_rejudge_v2_orig_on_tiegpt_composition_v1_mergeedit
regeneraterepair_method_v1_regeneratelegacy_editdeferregen_issue_v1_deferrl   )brief_sourcesc           	         ddl m} | t        vr% |dd| ddj                  t               dd	      |y
| |ddd	      |D cg c]  }| }}|rt	        d |D              s |ddd	      y
c c}w )u$  재생성 계약 선행 검증 — **첫 유료 호출 전에** 부른다.

    재생성을 켰다면 ①원본 vs 재생성본 판정 계약과 ②다시 줄 브리프가 함께
    있어야 한다. 재생성이 개선을 보장하지 않는다는 것은 실측이고(같은
    개정절로 다시 그렸더니 한 그룹이 더 나빠졌다), 판정 없이 재생성본을
    확정하면 그 악화가 그대로 굳는다 — v13 이 없애려던 "무판정 확정"이
    방향만 바꿔 되살아난다. 그래서 기존 편집으로 **하강시키지 않고** 막는다.

    brief_sources: 재생성 브리프가 조립될 원천(롤 프롬프트들, 없으면 base
    프롬프트). 실제 브리프는 판정으로 라벨이 뽑힌 뒤에야 정해지므로, 어느
    라벨이 뽑히든 비지 않는지를 **진입 시점에** 원천으로 확인한다. 이 검사가
    없으면 결손이 critique 뒤에야 드러나 롤 생성·판정·검사가 모두 유료로
    나간 뒤에 막힌다(실측: gen 2 · judge 1 · critique 1).
    r   AppErrorregen_issue_policy_unknownu(   알 수 없는 재생성 이슈 정책: u
    (가능: z, )  codemessagestatus_codeNregen_without_rejudgeu   재생성이 켜져 있는데 원본 vs 재생성본 판정 계약이 없다 — 무판정 확정을 막기 위해 실행하지 않는다c              3  D   K   | ]  }|xs d j                           yw) Nstrip).0ss     r#   	<genexpr>z1validate_regeneration_contract.<locals>.<genexpr>  s     AAqwBoo/s    regen_brief_missinguo   재생성이 켜져 있는데 다시 줄 브리프의 원천이 비었다 — 롤을 그리기 전에 막는다)app.core.errorsr   REGEN_ISSUE_POLICIESjoinall)regeneration_issue_policyregen_gen_fnfix_rejudge_fnr   r   r   sourcess          r#   validate_regeneration_contractr     s    * ) (<<-:;T:W X II&:;<A? 	 (K 	 ((-Qq-G(#AAA&4 	 B )s   	A3c                    t        |       }t        t        |            D ci c]  }||   |t        |      dz
  |z
      c}S c c}w )u<   역순 제시의 display 라벨 → canonical 라벨 매핑.r.   )r   ranger   )rO   rR   r    s      r#   flip_display_to_canonicalr     sI    <D6;CI6FG6FDGT#d)a-!+,,6FGGGs   Ac                "   t        |      }t        | t              s!t        dt	        |       j
                         | j                  d      }t        |t               s!t        dt	        |      j
                         g }|D ]  }t        |t              s!t        dt	        |      j
                         |j                  d      }t        |t              st        d|      |j                  d      }t        |t              rt        |t              rt        d|      |j                  |        t        |      t        |      k7  st        |      t        |      k7  rt        d	| d
|       | j                  d      }t        |t               rt        d |D              st        d|      t        |      t        |      k7  rt        d| d|       | j                  d      |vrt        d| j                  d            y)u  판정 완전성 fail-closed — verdict N개·중복 0, ranking=permutation,
    winner 유효. 위반=ValueError (호출자가 샷 단위 격리).

    재리뷰 NARROW-1: durable record 는 임의 JSON 손상이 가능 — root/
    verdicts/ranking/각 항목의 **타입까지** 전부 ValueError 로 귀결하는
    total validator 여야 재사용 경로(except ValueError)가 회복 가능하다.
    AttributeError/TypeError 누수=resume 영구 차단.
    zjudge result not an object: rE   zjudge verdicts not a list: z"judge verdict item not an object: r   z"judge verdict label not a string: r8   z judge verdict score not an int: u$   judge verdicts malformed — labels  != rD   c              3  <   K   | ]  }t        |t                y wN)r   r   )r   rs     r#   r   z(_validate_judge_shape.<locals>.<genexpr>  s      0$+q
1cG   z!judge ranking not a string list: u$   judge ranking not a permutation — z vs rC   u   judge winner invalid — N)r   r   r   r/   r5   __name__r   r   intboolr   r   setr   sorted)	r{   rO   rR   rE   seenr|   rs   r8   rD   s	            r#   _validate_judge_shaper     s     <DeT"*4;+?+?*@A
 	
 yy$Hh%)$x.*A*A)BC
 	
 D!T"4T!W5E5E4FG  eeGn#s#A#IJJg%%E4)@?yIJJC  4yCITc$i!724&TFC
 	
 ii	"Ggt$C 0$+0 - <WKHIIg&,&27)4vF
 	
 yy$&4UYYx5H4KLMM 'r%   c                @   t        | |       t        |      }t        |      t        |      k7  s%t        |j                               t        |      k7  rt	        d|       || d      | d   D cg c]  }||   	 c}| d   D cg c]  }i |d||d      i c}d}d| v rt        | d         |d<   | j                  d      rJ| d   D cg c]8  }i |d|j                  |j                  d      |j                  d            i: c}|d<   |S c c}w c c}w c c}w )	u  display 판정을 canonical 라벨로 역매핑 (winner·ranking·verdicts 전부).

    입력 검증 fail-closed — malformed 판정은 결합 전에 reject.

    ★`all_candidates_fail` 과 `readings` 도 함께 옮긴다 (2026-08-07, Codex
    리뷰 수용). 종전에는 세 키만 남기고 버렸는데, 팩은 "전 후보가 한 축을
    실패하면 그렇게 선언하라, 호출측이 재촬영 여부를 그 값으로 정한다"고
    적어 두었고 스키마도 필수로 요구한다. 여기서 버리면 심판이 자동차
    기하 붕괴나 공중 부유를 **전 후보에서** 잡아내도 그 사실이 기록에 남지
    않는다. readings(방향·공간·엔티티·물리 서술)도 같은 이유로 남긴다.
    u&   display→canonical 매핑 불완전: rC   rD   rE   r   rB   rM   rJ   )r   r   r   ry   r/   r   r   )	judge_rawdisplay_to_canonicalrO   remaprs   r|   r   r   s           r#   normalize_flip_verdictr     sM     )V,%&E
5zS[ C$73v;$FA%IJJ	(+,*3I*>?*>3E#J*>? z*
* .q-'57,-*
C 	)%))4I*J%K!"}}Z  z*
* FqE'599QUU7^QUU7^DE*
J J @

s   0DD=Dc                ^   t        | |       t        ||       t        |      t              t        t        |            k7  rt        d       | d   D ci c]  }|d   |d    }}|d   D ci c]  }|d   |d    }}|D ci c]  }|||   ||   z    c}| d   |d   k(  }|r| d   }	nHt	        j                               }
|D cg c]  }|   |
k(  s| }}t        |j                        }	t        |fd      }||t        d}|	|fS c c}w c c}w c c}w c c}w )	u   정순+역순(canonical 정규화 완료) 결합 — (selected, combined).

    합의=그 승자 / 불일치=양회 점수 합산 최대 / 동점=priority 앞선 라벨.
    normalized 입력만 소비한다(raw display 판정 전달 금지).
    u+   priority 는 labels permutation 이어야: rE   r   r8   rC   rv   c                2    |     j                  |       fS r   rq   )rs   prior}   s    r#   ru   z'combine_flip_verdicts.<locals>.<lambda>6  s    vc{lDJJsO-Lr%   )r}   rD   	agreementpolicy)	r   r   r   r/   rx   ry   rz   rr   FLIP_POLICY_VERSION)	judge_fwdjudge_rev_normalizedrO   priorityr|   
fwd_scores
rev_scoresrs   r   r   r~   r   rD   combinedr   r}   s                 @@r#   combine_flip_verdictsr     sp    )V,.7>Dd|vd6l++FtfMNN2;J2GH2GQ!G*aj(2GJH(<Z(H(H1'
AgJ(H   AGGc:c?Z_44GF(#';H'EEIX&6==?#%=v)<v=t,V!LMG%	H X' I H >s   D4D 
D%D*D*c                    g }| D ]i  }t        |t              r|j                  d      s%t        |j                  d      xs d      j	                         }|sT||vsY|j                  |       k |S )uq  "사진에 아예 없어 새로 넣어야 하는 것" 이름 목록 (2026-08-19).

    참조 선별의 짝이다 — 없는 것을 넣으라는 지적은 그 참조를 봐야 하고,
    수정 지시문도 "여기 있는 것만 고치고 나머지는 그대로"에서 벗어난다는
    사실을 알아야 한다(사용자 지시). 순서 보존 중복 제거.
    re   rf   r   )r   r   r   r   r   r   )rX   r   issuenames       r#   collect_missing_entitiesr   @  sj     C%&eii8M.N599239r:@@BDOJJt  Jr%   c                   t        |      }| D cg c]  }t        |t              s| }}|D cg c]	  }d|vs| }}|r$|dt        |      t        |      t        |      dfS t	        d |D              }t               }g }|D ]u  }	|	j                  d      xs g D ][  }
t        |
t              s&t        |
t              rd|
cxk  rt        |      k  sn |j                  |
       K|j                  |
       ] w t        |      }|D 
cg c]
  }
||
dz
      }}
dt        |      t        |      |d}|r|dd	 |d
<   |r||d<   ||fS c c}w c c}w c c}
w )u  지적이 **요구한 참조만** 고른다 (2026-08-19 사용자 지시).

    반환 (고른 참조, 감사 기록). 번호는 `ref_index_part` 가 모델에게 보인
    1-based 순서이며 그 순서가 계약이다.

    ★칸이 없는 지적이 하나라도 섞이면 **거르지 않는다.** 그 지적에게는
    무엇을 봐야 하는지 물은 적이 없으므로, 여기서 빼면 참조 없이 글자로만
    지시하던 2026-08-07 이전 상태로 되돌아간다(그때 실측 4건이 실패했다).
    답은 "참조를 빼라"가 아니라 "요구한 것만 붙여라"다 — 물은 적 없는
    지적에게는 종전대로 전부 붙이고, 그 사실을 기록에 남긴다.

    ★범위 밖·정수 아님은 fail-closed 로 버린다(발명한 번호로 엉뚱한
    참조가 붙는 것이 안 붙는 것보다 나쁘다). 버린 값은 기록에 남는다.
    rd   all_ungated)mode	availablekeptungated_issue_countc              3  d   K   | ](  }|j                  d       r|j                  d      sd * yw)re   rd   r.   N)r   r   r    s     r#   r   z"select_fix_refs.<locals>.<genexpr>t  s1      Na55&'7J1K 	
:s   .0r.   gated)r   r   r   requested_indicesN   invalid_indicesmissing_without_ref)r   r   r   r   sumr   r   r   r   r   addr   )rX   r   refsr    
issue_listungatedmissing_no_refwantedinvalidr   norderr   recs                 r#   select_fix_refsr   S  s   $ D#;Vz!T':!VJ;$E*Q(;1(Dq*GE!TI#&w<	
 
 	
  NN NN %FG))/06B6A!T"*Q*</c$i/q!JJqM 7  6NE!&'ADQKD'YD	"	C !("%3!"9M <E4 (s   EE	EEEr   c                   ddj                  d | D              z   }||g}|D cg c]  }t        |      j                         s|  }	}|	rs|j                         rc|j                         dz   dj                  d |	D              z   }
|j                         r|
d|j                         z   z  }
|j                  |
       |j                  |       dj                  |      S c c}w )u  FIX_HEAD + CORRECTIONS 목록 + FIX_TAIL (s40 _critfix 조립).

    missing_* (2026-08-19): 지적이 "지금 사진에 없는 것을 새로 넣어라"고
    할 때만 그 절이 CORRECTIONS 뒤에 낀다. 기본값(빈 목록·빈 문안)은
    기존 조립과 byte-identical.
    zCORRECTIONS:

c              3  ,   K   | ]  }d |d      yw)- r[   Nrl   r   s     r#   r   z#build_fix_prompt.<locals>.<genexpr>  s      /$*q"Qx[MFs   c              3  &   K   | ]	  }d |   yw)r   Nrl   )r   r   s     r#   r   z#build_fix_prompt.<locals>.<genexpr>  s      8%#bHes   

)r   r   r   r   )rX   fix_headfix_tailmissing_namesmissing_headmissing_tailcorrectionspartsr   namesblocks              r#   build_fix_promptr     s     #TYY /$*/ & K {#E%81QQE8##%""$t+dii 8%#8% /% %TL..000EU	LL;;u 9s   C C u  Generate this location photograph again from the brief below.

An earlier attempt from this same brief was rejected for the structural faults listed at the end. Those faults are in the building's overall form — its storey count, massing or footprint — which no amount of retouching a finished photograph can repair. So this is a fresh photograph, not an edit of the previous one.u  Build the structure so that none of those faults occurs this time, and keep everything else the brief requires. Where the brief states a storey count, count the storeys you are drawing and make them match it exactly — a level used for parking or storage still counts as a storey.z*STRUCTURAL FAULTS OF THE REJECTED ATTEMPT:c                   |xs t         j                         dz   dj                  d | D              z   }dj                  |xs t        j	                         |j	                         ||xs t
        j	                         g      S )uq  원 브리프 + 결함 목록 → 재생성 프롬프트.

    base_prompt 는 선정된 롤이 실제로 쓴 프롬프트다. 같은 브리프를 다시
    주되 무엇이 틀렸는지를 덧붙인다 — 프롬프트를 바꿔 버리면 무엇이
    효과를 냈는지 알 수 없고, 브리프 준수 여부도 판정할 수 없다.

    faults_header (2026-08-29): 결함 목록의 머리말. 비우면 구조물용 기본값
    이라 **기존 소비자는 byte-identical** 이다. 스틸은 자기 팩의 말을
    넘긴다 — 구조물 문안을 스틸에 재사용하면 지적의 대상이 뒤바뀐다.
    r   c              3     K   | ]=  }d |j                  d      xs |j                  d      xs d j                          ? yw)r   r[   rZ   r   N)r   rstripr   s     r#   r   z%build_regen_prompt.<locals>.<genexpr>  sE      PA QUU8_7j 17R89@@Bs   AAr   )REGEN_FAULTS_HEADERr   r   
REGEN_HEADr   
REGEN_TAIL)rX   base_prompt
regen_head
regen_tailfaults_headerfaultss         r#   build_regen_promptr    s    " 22::<tCdii PP G F ;;		!z((*		!z((*	  r%   )extrac                   ddl }ddl}|j                         }|j                  | j	                  d             |D ]  \  }}	|j                  d       |j                  t        |      j	                  d             t        |	t
        t        f      rt        |	      j                         n|	}
|j                  |j                  |
      j                                 |j                  d| d| j	                  d             |r1|j                  |j                  |d      j	                  d             |j                         dd S )	u%  생성 입력 지문 — 프롬프트+참조(라벨·내용)+롤/게이트+모델·팩(extra).

    Codex 1차 리뷰 BLOCKING-3: 파일 존재만으로 skip 하면 입력이 바뀌어도
    옛 산출을 재사용한다 — record 의 이 지문과 대조해 mismatch 시 산출 정리.
    r   Nutf-8    |T)	sort_keys   )hashlibjsonsha256updateencoder   r   r   
read_bytesdigestdumps	hexdigest)promptr   
roll_countcritique_enabledr  r
  _jsonhr   r!   datas              r#   compute_input_fingerprintr    s    AHHV]]7#$"
s		U""7+,&0sDk&BDI  " 	 	
%,,./ # HHqA./077@A	Ud3::7CD;;="r%   c                    ddl }	 |j                  | j                               j                         S # t        $ r!}t
        j                  d| |       Y d}~yd}~ww xY w)u   산출 파일의 sha — **무엇을 샀는지**의 신원.

    재롤은 유료라, 「이 파일이 그 시도의 산출이 맞나」를 나중에 되짚을 수
    있어야 한다. 경로만 남기면 덮어쓰기와 구분되지 않는다.
    r   Nu*   multiroll_select: %s sha 계산 실패: %sr   )r
  r  r  r  OSErrorloggerwarning)r   r
  excs      r#   _sha256_filer    sO     ~~doo/0::<< CT3Os   ,3 	AAAc                .   | j                  d      }t        |t              sg S |D ]g  }t        |t              s|j                  d      |k(  s)|j                  d      }t        |t              r|D cg c]  }t	        |       c}c S g c S  g S c c}w )ub  그 후보에 달린 **실격 문장 그대로** — 재롤 처방의 재료.

    ★판정기가 쓴 말을 **그대로 되돌려 준다**. 글자에서 뜻을 캐내 분류하지
     않는다([[feedback-no-literal-substring-meaning]]) — 여기서 하는 일은
     「무엇이 틀렸다고 했는지」를 다시 보여 주는 것뿐이다.
    rJ   r   rI   )r   r   r   r   r   )judge_resultr   rowsr   rawxs         r#   gate_winner_violationsr%    s     J'DdD!	a155>U#:%%)*C,6sD,AC(CqCFC(IrI  I )s   4Bc                   t        d      D ch c]"  }| j                   d|j                          d$ }}|| j                   d| j                   d| j                   dhz  }| j                  j	                  | j                   d      D ]"  }|j                  |vr	 |j                          $ y	c c}w # t        $ r!}t        j                  d||       Y d	}~Pd	}~ww xY w)
ub  이 stem 의 롤/선정/수정/변환 산출 제거 — force·지문 mismatch·무기록 sel.

    ★**자기 산출 allowlist 로만 지운다** — 광역 `{stem}_*` glob 은 같은
    접두를 쓰는 **타 단계 입력**까지 지웠다. 실측 두 번:
    · 2026-08-08 S1sh3 `__bgfirst_bg`(직전에 만든 배경판) — 밑줄 둘
      네임스페이스 제외로 막았다.
    · 2026-08-14 S18sh5/S79sh1/S84sh2 `_confinedfp`(confined 도면) —
      밑줄 하나라 그 가드 밖. 정리가 3분 전 만든 도면을 지워 직후 롤
      생성이 입력 결손 FileNotFoundError 로 죽고, resume 마다 도면
      재생성→정리 삭제→실패가 **결정론으로 재발**해 autodrive 표적
      회수 상한 3회를 소진시켰다(1차 E2E 212/215 정지 원인).
    제외 목록을 늘리는 대신 이 단계가 실제로 만드는 파일만 지운다:
    롤(`_a`~`_e`)·`_sel`·`_fix`(재생성 후보도 이 이름)·`_cine`(변환
    스테이지 산출 — 낡은 sel 의 파생물이라 함께 치운다, 종전 glob 도
    지우던 대상이라 동작 불변).
       _.png_sel.png_fix.pngz	_cine.pngz_*.pngu&   multiroll_select: %s 제거 실패: %sN)	r1   r   lowerparentglobunlinkr  r  r  )out_stemrs   r   pr  s        r#   _clear_outputsr2  %  s    " >I^L^ca		}D1^EL	x(X]]O8*Dy)+ +E__!!X]]O6":;66	MHHJ	 < M  	MNNCQLL	Ms   'B4!B99	C#CC#c                \    | j                   | j                   d|j                          dz  S )Nr(  r)  )r-  r   r,  )r0  r   s     r#   
_roll_pathr4  B  s(    ??adCCCr%   c                :    | j                   | j                   dz  S )Nr*  )r-  r   )r0  s    r#   	_sel_pathr6  F  s    ??h777r%   )fix_ref_labelfix_ref_gatefix_missing_headfix_missing_tailr   composition_critique_fnr   regen_promptr   r   regen_faults_headerr   repair_methodc                  ; t        |      ;t        ||      } || |||      }||
d<   t        |j                  d      xs g       }|4 ||  d|||      }||
d<   |t        |j                  d      xs g       z  }dJ;fd}|s ||       d|
d<   ;S |t        k(  }|t
        k(  }g }g } g }!|D ]_  }"|"j                  d      r|s|!j                  |"       (|r%|"j                  d	      r|s| j                  |"       O|j                  |"       a | r3t        |       |
d
<   | |
d<   t        j                  d| t        |              |s( ||       d|
d<   | r	|!rd|
d<   ;S | rd|
d<   ;S d|
d<   ;S t        d |D              rs|D #cg c]  }#|#j                  d      dk7  s|# }$}#|D #cg c]  }#|#j                  d      dk(  s|# }}#|$rt        |$      |
d<   |$|
d<   |s ||       d|
d<   d|
d<   ;S |j                  |j                   dz  }%|D #cg c]  }#|#j                  d	      s|# }&}#|r&t        d |||fD              sddlm}'  |'ddd      |r|ddlm}'  |'dd d      |r|r&n|&r#|!|j!                         sddlm}'  |'d!d"d      |r|n|&}(|(r||z|j!                         rjt#        |(||||#      })|)|
d$<   t        |(      |
d%<   d&|
d'<   ||
d(<   t        j                  d)| t        |(      |rd*nd+        ||  d,|)t        |      |%       n|rt%        |      ng }*t'        ||||*||-      }+|*r|*|
d.<   d/|
d'<   ||
d(<   |	|fg},|r|rgt)        ||      \  }-}.|-D /0cg c]  \  }/}0|/	 c}0}/|.d0<   |.|
d1<   |.j                  d2      d3k(  r2t        j+                  d4| |.j                  d5             nt        |      }-|,|-D /1cg c]  \  }/}1| d6|/ |1f c}1}/z  },t        |,      |
d7<    ||  d8|+|,|%       |+|
d9<   |
 ||%       ;S d:d;g}2||%d<}3t-        |d=d>      r ||  d?|||3d:   |3d;   g|2      }4t/        |4|2       |4j                  d@      |4j                  dA      |4j                  dB      |4j                  dC      |4j                  dA      d;k(  t1        |4j                  dD            dE|
dF<    ||3t3        |4j                  dA      xs d:                ;S  ||  d?|||3d:   |3d;   g|2      }5t5        |5|2D /ci c]  }/|/|/ c}/|2      }6 ||  dG|||3d;   |3d:   g|2      }7t5        |7t7        |2      |2      }8t9        |6|8|2d:d;g      \  }9}:|5|6|7|8|:|9|9d;k(  t1        |6j                  dD      xr |8j                  dD            t:        dH	|
dF<   |9d:k(  rt        j+                  dI| |        ||3|9          ;S c c}#w c c}#w c c}#w c c}0}/w c c}1}/w c c}/w )Ku	  선정 원본 결함 검사 → 결함 시 수리(편집 또는 재생성) → _sel 확정.

    fix_ref_label (2026-08-07): 비어 있지 않으면 편집 호출에 **critique 가 본
    참조를 그대로 동봉**한다(편집 대상이 첫 장, 참조는 이 라벨을 앞에 달고
    뒤). 빈 문자열=기존 동작(원본 단독 참조) byte-identical.

    fix_ref_gate (2026-08-19 사용자 지시): True 면 동봉을 **지적이 요구한
    참조로 좁힌다** — 판단 재료는 critique 가 구조 필드로 내놓은
    `needs_ref_indices`(`build_critique_schema(with_ref_gate=True)`).
    아무도 요구하지 않으면 편집 대상 원본 한 장만 간다. 없는 것을 새로
    넣으라는 지적이 있으면 `fix_missing_head`/`fix_missing_tail` 절이
    지시문에 끼고 그 참조도 함께 붙는다. False(기본)=byte-identical.

    regen_gen_fn / regen_prompt (2026-07-31): critique 가 어떤 결함을
    `needs_regeneration` 으로 표시하면 그 결함은 i2i 편집으로 못 고친다 —
    건물의 층수·매스·footprint 가 그렇다. 이때는 편집 대신 **원 브리프로
    다시 생성**하고, 나온 후보는 기존 fix 재판정 경로를 그대로 탄다(원본
    vs 재생성본 블라인드 비교).

    regeneration_issue_policy (2026-08-01): 재생성 수단이 없을 때 그
    결함을 어떻게 다룰지 — `legacy_edit`(기본, 편집에 실어 보냄) 또는
    `defer`(손대지 않고 기록만). 기본값을 바꾸지 않는 이유는 이 파이프를
    공유하는 다른 소비자의 계약을 조용히 뒤집지 않기 위해서다. 상세는
    모듈 상단 REGEN_ISSUE_POLICY_* 주석.

    composition_critique_fn (E2E11 fix③, 사용자 확정): 제공 시 GPT 구도
    전담 검사(구도·배치·스케일·시선축만 — sys 계약이 그 외 봉인)를 Gemini
    critique 와 병행 실행, 두 issues 를 **합산한 하나의 수정 프롬프트**로
    i2i. 구도 위반만 있어도 수정 진행. None(default)=기존 동작.

    fix_rejudge_fn (E2E10 fix②): 제공 시 수정본을 무판정 확정하지 않고
    [원본, 수정본] 2후보 블라인드 재판정(정순+역순 flip — 판정자에게 어느
    쪽이 수정본인지 미노출, 순서 편향은 flip 결합으로 상쇄)으로 최종 _sel
    을 결정. 동점 priority=[원본, 수정본] — 2026-08-07 반전, 근거는 호출부
    주석. None(default)=기존 동작.
    critiquerX   _compcomposition_critiquec                    j                  j                  dz         }t        j                  | |       |j	                         y )N.tmp)	with_namer   shutilcopyreplace)r!   tmpsels     r#   _atomic_placez(_critique_and_fix.<locals>._atomic_place  s4    mmCHHv-.CCr%   Tfix_skippedr\   r]   regen_deferred_issue_countregen_deferred_issuesuO   multiroll_select[%s]: 재생성 몫 결함 %d건 — 편집하지 않고 보류all_issues_non_editablefix_skip_reasonall_issues_need_regenerationall_issues_unfixablec              3  $   K   | ]  }d |v  
 yw)rb   Nrl   )r   r   s     r#   r   z$_critique_and_fix.<locals>.<genexpr>  s     
4G5:Gs   rb   r_   fix_severity_skipped_countfix_severity_skippedno_critical_issuer+  c              3  <   K   | ]  }|j                           y wr   r   )r   r$  s     r#   r   z$_critique_and_fix.<locals>.<genexpr>  s       NL!AGGILr   r   r   !repair_regenerate_wording_missingu   수리 수단이 regenerate 인데 재생성 문안(head/tail/머리말)이 비었다 — 구조물 기본 문안으로 내려가지 않고 막는다r   r    repair_regenerate_without_gen_fnul   수리 수단이 regenerate 인데 재생성 함수가 없다 — 편집으로 내려가지 않고 막는다r   un   재생성이 켜져 있는데 다시 줄 브리프가 비었다 — 편집으로 내려가지 않고 막는다)r   r<  regen_issue_countr   repair_moder>  uG   multiroll_select[%s]: 결함 %d건 — i2i 편집 대신 재생성 (%s)u   명시 수단u   구조 결함_regen)r   r   r   fix_missing_entitiesr   kept_labelsr8  r   r   ux   multiroll_select[%s]: 참조 선별 ON 인데 칸 없는 지적 %d건 — 이번 편집은 종전대로 전부 붙인다r    fix_ref_count_fix
fix_promptr&   r'   )r&   r'   
owns_orderF	_fixjudgecross_model_orderrC   rD   rE   rM   )re  rC   rD   rE   fix_wonrM   fix_rejudge_fixjudge_rev)	forward_rawforward_normalizedreverse_rawreverse_normalizedr   rC   rf  rM   r   uW   multiroll_select[%s]: fix-rejudge — 수정본 개악 판정, 선정 원본(%s) 유지)r!   r   returnNone)r6  r4  r   r   REGEN_ISSUE_POLICY_DEFERREPAIR_METHOD_REGENERATEr   r   r  infoanyr-  r   r   r   r   r   r  r   r   r   r  getattrr   r   r   r   r   r   FIX_REJUDGE_POLICY_VERSION)<tagr  r   r0  r   critique_fn
fix_gen_fnr   r   	fix_labelrecordr7  r8  r9  r:  r   r;  r   r<  r   r   r=  r   r>  origcritrX   comprK  	deferringregeneratingfixabledeferredr\   r   r    below_criticalfixedregen_issuesr   repair_issuesregenr   rb  fix_refs
gated_refsgate_recrs   r(  r!   labels2candjudgedfwd_rawfwdrev_rawrevrC   r   rJ  s<                                                              @r#   _critique_and_fixr  J  sw   ~ H
Ch)DsFL$7DF:$((8$*+F*&e5M6<7)-%&$txx)/R00
 d $}
 *-EEI !$<<L$&G%'H&(I99[!,U#599%9:<OOE"NN5!  /28}+,*2&']X	  d $} 	(AF$%
 
	 (FF$% 
 )?F$%
 
4G
44C!!%%
"3z"AAw 	 C C!!%%
"3z"AAw 	 C36~3FF/0-;F)*$$(F=!(;F$%JOOx88E&Fw!!%%0D*EAwLFC  N *J8KL N N 	-4\ 	 , 	-3= 	  \$\-?-?-A 	-&= 	  ,GM1l6H6H6J"<Z-/ "'~&)-&8"# ,}"/U]#+O	B 	uF^UD,>F
 2>$W-2 	%Xx')8H


 -:F)* &}"/0 -6t+<*='6w'M$
H >H*HZ633Z*H')1~&<<'=8NNZX\\*?@B
 ",/
)35)3XS# +O1SE2C8)35 5H"%h-cU$<Xu=)|e
CjGE"D ~|U3e9v|#YS	"G
 	fg.!',?!@jj*zz),

:.zz(+s2 $(

3H(I#J)!
}, 	d3vzz(3:s;<=
%y6<	cDIG !g.gs#s(g.9C%}v|	cDIG !*73W>C -S#wc
KFH!!S=  $GG)* /-. 0 -F= } !$h	
 $v,J{CC GZ +I5f /s6   WW)W
W
W+W*WW	
W 
c                6    |r||   }|| n|}|r| d| S |S | S )u+  변형 롤 모드의 critique 프롬프트 = 선정 변형 전문 + 공유 브리프.

    critique 계약은 '프롬프트 위반만' — 생성 계약(변형 전문)과 공유 불변
    사실(브리프, 진단 ② 검출 축) 둘 다 대조 대상이어야 한다.

    ``shared_prompt`` (2026-08-01 Codex 재리뷰 HIGH 5): 공유 쪽에 쓸 문안을
    호출측이 따로 줄 수 있다. 기본값 None 이면 ``prompt`` 를 그대로 써서
    기존 동작과 byte 동일하다. 필요해진 이유는 생성 계약이 롤 프롬프트에
    직접 실리게 되면서(관할절) **같은 절이 롤과 base 양쪽에 들어가 critique
    입력에만 2회 중복**되기 때문이다. 판정마다 같은 요구가 두 번 보이면
    무게가 왜곡된다 — 각 경로에 정확히 1회만 실리게 한다.

    ★[2026-08-01 Codex 2차 재리뷰 NARROW-3] ``or`` 는 빈 문자열을 falsy 로
    흘려 원 ``prompt`` 를 쓴다. 그런데 지문 쪽은 ``is not None`` 으로 갈라
    빈 문자열도 **별개 계약**으로 기록한다 — 같은 입력이 기록에서는 다르고
    실행에서는 같아진다. 기록이 실행을 대변하지 못하면 감사가 무의미하다.
    "공유 문안을 비운다"는 호출측의 유효한 의사이므로 그대로 존중한다.
    r   rl   )r  roll_promptsr   shared_promptheadshareds         r#   _compose_critique_promptr    s:    0 H%(0m(.$tF8$8D8Mr%   )winner_violation_gate_v1_admissible_firstclean
reselected
unresolvedresolvednot_applicable
incompleteblocked_dependencyc                   | j                  d      }t        |t              sg t        |      fS i }|D ]  }t        |t              st	        |j                  d      xs d      }||vr7|j                  d      }t        |t              s|j                  |      du rdnd||<   s|D cg c]  }t	        |      j                         s|  }}|j                  |      }	|	du r| }
|
sdn	|	||v rdn|
||<    |D cg c]  }|j                  |      du s| }}|D cg c],  }|j                  |d      dus|j                  |      dur|. }}||fS c c}w c c}w c c}w )	u  (허용 후보, **미검사** 후보) — 실격이 빈 후보만 허용한다.

    ## 왜 미검사를 따로 내는가

    `_validate_judge_shape` 는 `readings` 를 **아예 안 본다**. 그래서 어떤
    후보의 행이 통째로 없을 수 있는데, 그것은 「실격이 없다」가 아니라
    **「아무도 안 봤다」**다. 둘을 같게 다루면 안 본 것을 통과로 읽는다
    (2026-09-20 Codex).

    ★판정 팩이 `hard_violations` 를 「그 컷을 못 쓰게 만드는 실격 결함만」
     이라고 규정하므로 **배열이 비었는지**만 본다 — 글자로 종류를 가르지
     않는다.
    rJ   r   r   rI   FNTu   미)r   r   r   r   r   r   )r!  rO   r"  r   r   rs   r#  r$  hvprevokl
admissible	uncheckeds                 r#   gate_admissible_labelsr    sz     J'DdD!4<D!T"!%%.&B'f ee%&#t$!%#%!7TDI/AAa/xx}5=V!#E$,3$;$/1 	S	' * $;Vtxx{d':!VJ;" .Fqdhhq%&8&EXXa[, FI .y   0 <.s$   *E	E?EE"E#<E#c                B   ddl }ddl}	 |j                  | j                  d      dd      }t        |xs d      t        |      |j                  |j                  d            j                         dd	 d
S # t        $ r t        | j                  d            }Y pw xY w)u"  이 판정이 **무엇을 보고** 내려졌는지의 신원 (2026-09-20 Codex ③).

    게이트 정책을 생성 지문에서 뺐으므로, 「어느 근거를 어느 정책으로
    봤는가」는 `gate` 가 스스로 적어야 한다. 안 적으면 나중에 같은
    `clean` 을 보고도 **어느 판정문에서 나온 허용인지** 알 수 없다.

    근거 digest 는 판정문 전체를 통째로 싣지 않으려는 것이지, 판정문을
    **자르는 것이 아니다** — 원문은 `readings` 에 그대로 있다.
    r   NrJ   FT)ensure_asciir  r   r  r	  )input_fingerprintrO   readings_digest)r
  r  r  r   	Exceptionreprr   r   r  r  r  )r!  rO   r  _hl_jsblobs         r#   gate_evidence_idr    s     2yy))*5&+t  =
 !!2!8b9v,::dkk'&:;EEGL   2L$$Z012s   #A8 8#BBgate_reroll_v1_one_per_shotsentproducedr  c               z    t        | t              xr* | j                  d      |k(  xr | j                  d      |k(  S )u(  이 기록의 재롤 시도가 **지금 조건의 것**인가.

    ★「샷당 1회」는 함수 호출당이 아니라 **같은 입력·정책의 canonical 샷당**
     이다 — 재개까지 합산한다. 입력이나 정책이 바뀌면 다른 물음이므로 새
     1회를 준다.
    r   for_fingerprint)r   r   r   )priorr   fingerprints      r#   reroll_attempt_matchesr  ;  s@     ud# <		(#v-<		+,;=r%   c                X   | yt        | dd      syt        | dd      sy|xs i }t        |j                  d      xs d      j                         rXt        |j                  d	      xs d      j                         r,t        |j                  d
      xs d      j                         syy)uh  **이미지를 사기 전에** 재판정 배선을 확인한다 (2026-09-20 Codex).

    돈을 쓰고 나서 「판정할 것이 없다」를 알면 그 돈은 버린 것이다.
    두 가지를 본다:
      · 콜백이 실제로 있는가
      · 그 판정이 **`readings` 를 남기는가** — 게이트는 그 칸으로만 판단
        하므로, 안 남기는 판정기로 재롤하면 결과를 읽을 길이 없다

    `readings` 보존은 호출부가 `emits_readings` 로 **선언**한다 — 판정기를
    만들어 주는 쪽이 자기 스키마를 안다(`owns_order` 와 같은 관례).
    )Fu   재판정 콜백이 없다emits_readingsF)Fu@   재판정기가 readings 를 남긴다고 선언하지 않았다rc  )Fu;   재판정기가 cross-model(owns_order) 갈래가 아니다r   r   r   r=  )FuF   스틸 재생성 문안이 없다(구조물 기본값으로 나간다))Tr   )rs  r   r   r   )judge_fnregen_texts_ts      r#   gate_reroll_readyr  I  s     28-u5X8\51S
 
	B|$*+113BFF<(.B/557BFF017R8>>@^r%   c                    t        | |      \  }sd|fS t        fd|D        d      }|t        fd|D        d      }||fS )u  **허용 집합 안에서만** 순위로 고른다 — 허용이 점수보다 먼저다.

    재롤 후보와 초기 후보를 **한 집합**으로 놓고 본다. 하나도 허용되지
    않으면 `None` — least-bad 를 정상본으로 내보내지 않는다.
    Nc              3  ,   K   | ]  }|v s|  y wr   rl   r   r  r  s     r#   r   z.gate_pick_within_admissible.<locals>.<genexpr>v  s     7GqqJG   	c              3  ,   K   | ]  }|v s|  y wr   rl   r  s     r#   r   z.gate_pick_within_admissible.<locals>.<genexpr>x  s     :1!z/Qr  )r  next)r!  rO   rD   r  pickr  s        @r#   gate_pick_within_admissibler  i  s\     3<HJ	Z**7G7>D|::DAY&&r%   c                    | j                  d      }t        |t              sy|j                  d      t        k(  ry|j                  d      ry|j                  d      t        k7  xs |j                  d      |k7  S )u  이 기록에 정책을 **다시 적어야 하는가** (2026-09-20 Codex (e)).

    ★같은 정책·같은 근거로 이미 본 기록은 **다시 쓰지 않는다** — 캐시로
     끝나는 방문마다 메타를 새로 적으면 「지출 흔적」이 매번 움직여
     재생성 계수가 거짓으로 오른다(두 번째 메타 재기록 0).
    ★의존 대기는 **걷기가 소유**한다 — 여기서 건드리지 않는다.
    gateToutcomeFrerollr   evidence)r   r   r   GATE_DEPENDENCY_OUTCOMEGATE_POLICY_VERSION)ry  r  gs      r#   gate_needs_reapplyr  |  sp     	

6AauuY22
 	uuXEE(O22 -uuZ H,.r%   )r  c           
     b   |s#i }t        | |      rt        t        d|dd| d<   yyt        | j	                  d      t
              r| j	                  d      nd}t        | j	                  d      xs d      }d|i}t        |||      }t        | |      syt        | j	                  d	            xr | j	                  d
       }	|	s|r|st        t        d|d|	rdndd| d<   y| j	                  d      }
|}t        | |||t        |
t
              rt        |
      ng d|      }d| d   d<   ||k7  r|S y)u  **호출 0** 으로 캐시 기록에 정책을 적용한다 — 돌려주는 것은 새 라벨.

    이미 산 후보·이미 받은 판정문만 본다. 생성도 새 VLM 호출도 **없다**.

    ## 왜 필요한가 (2026-09-20 Codex (e))

    정책을 생성 지문에서 뗐으므로(③), 지문이 맞는 기록은 캐시 갈래로
    **판정을 안 거치고** 그대로 반환된다. 그러면 정책을 켜도 옛 기록에는
    영영 판정이 안 붙고, verify 는 그 샷들을 계속 **미판정**으로 붙잡는다.

    ## 무엇을 안 하는가

    ★**수정본이 최종인 기록은 손대지 않는다.** 저장된 `readings` 는
     초기 롤을 본 판정문이라, 수정본의 판정으로 읽으면 **다른 그림의
     판정**을 이 그림에 붙이게 된다. 그런 기록은 `incomplete` 로 남긴다.
    ★근거(`readings`)나 선택이 없으면 **미검증으로 남긴다** — 무료 소급을
     명분으로 생성이나 새 판정 호출을 하지 않는다.
    ★라벨이 바뀌면(재선택) 호출부가 **선정 파일까지 그 후보로 맞춰야**
     한다. 기록만 B 이고 파일은 A 인 채 나가면 안 된다.

    돌려주는 것: 바뀐 라벨(재선택) 또는 None(바뀐 것 없음).
    FT)r  r   
applicabler  retroactiver  r   NrJ   r   r@  critique_skippedu   검사·수리 이력이 있는 캐시라 보수적으로 제외한다 — 저장된 판정문이 이 그림의 것이라는 보장이 없다u'   판정문이나 선택 기록이 없다)r  r   r  r  r  reasonrD   ry  r!  rO   r   rD   r  r  r  )r  GATE_NOT_APPLICABLEr  r   r   r   r   r  r   GATE_INCOMPLETEapply_winner_violation_gate)ry  rO   r  r  ev_narJ   r   r!  r  fixed_finalrD   beforeafters                r#   reapply_gate_to_cached_recordr    se   :  "fe,)<(;,1*/-1	3F6N
  fjj4d; 

:&AE 6::j)/R0H)Lf6GHHfh/ vzz*-. vzz8 4K((&)  &a A

v jj#GF'L!+GT!:W+<	>E
 %)F6N=! r%   c                  
 |st         t        dd| d<   |S t        ||      \  
}t        dt        |||      
||d}|
v rt        |d<   || d<   |S t        
fd|D        d      }	|	t        
fd	|D        d      }	|	,t        |d<   |	|d
<   || d<   t        j                  d||	       |	S |r't        |d<   || d<   t        j                  d||       |S t        |d<   || d<   t        j                  d|       |S )u  선정 결과를 **허용 여부**로 한 번 더 거른다 — 최종 `selected` 를 낸다.

    ## 계약 (2026-09-20, Codex 설계 합의)

        ① 허용 후보 집합을 먼저 만든다   (실격이 빈 후보)
        ② 그 안에서 **기존 순위**로 고른다  ← 있으면 **무료 재선택**
        ③ 하나도 없으면 `unresolved`       ← 정상 확정으로 안 내보낸다

    재롤(유료)은 이 함수 밖이다 — 여기서는 **이미 산 후보**만 다룬다.

    ## 왜 필요한가

    선정은 「둘 중 나은 것」을 성실히 낸다. **둘 다 못 쓸 때 멈추는 자리가
    없다**(실측: 이 화 239샷 중 이긴 후보에 실격이 달린 것 81). 뿌리는
    점수 가중치가 아니라 **「상대 순위」와 「최종 사용 가능」이 분리되지
    않은 것**이다.

    ★비대상 갈래는 `not_applicable` 로 **기록**한다 — 「검사 통과」로
     표시하지 않는다.
    ★미검사 후보는 허용으로 세지 않는다.
    F)r  r   r  r  T)r   r  r  r  r  initial_selectedr  c              3  ,   K   | ]  }|v s|  y wr   rl   r  s     r#   r   z.apply_winner_violation_gate.<locals>.<genexpr>&  s     67aa:o7r  Nc              3  ,   K   | ]  }|v s|  y wr   rl   r  s     r#   r   z.apply_winner_violation_gate.<locals>.<genexpr>(  s     9v!jAvr  r   u   winner_violation_gate: 이긴 후보(%s)에 실격이 달렸다 — 허용되는 기존 후보 %s 로 바꾼다 (이미지 구매 0)u   winner_violation_gate: 허용 후보가 없고 **미검사가 남았다**(%s) — incomplete (초기 선정 %s). 품질 unresolved 와 다르고, 필요한 것은 **판정 복구**이지 새 그림이 아니다u   winner_violation_gate: **허용 후보가 없다** — 이 샷은 unresolved (초기 선정 %s). ★이 단계는 **기록만** 한다 — 확정을 막는 것은 소비자 쪽 일이다)r  r  r  r  
GATE_CLEANr  GATE_RESELECTEDr  r  r  GATE_UNRESOLVED)ry  r!  rO   r   rD   r  r  r  rq  altr  s             @r#   r  r    sM   > %8$7 ).	/v
 2<HJ	% %\6%68 $D :$Yv 676
=C
{9v94@
)YZvBCKS	R 
 )YvEFOQY	[ %DOF6N
NN	"#+- Or%   c                    | j                  d      }t        |t              rd|v rt        |j                  d            }nt        | j                  d            }|rd| d<   yy)u  최종 `_sel` 기준 재촬영 필요 여부 — **수정이 끝난 뒤에** 계산한다.

    단계별 선언을 그대로 두면 두 방향으로 오독된다(Codex 2차 리뷰):
      · 초기 롤이 전부 실패했어도 수정본이 정상 승리하면 재촬영은 불필요한데
        초기 플래그만 보면 True 로 읽힌다.
      · 초기 롤은 통과했는데 [원본 vs 수정본] 재판정에서 둘 다 실패하면
        초기 플래그가 없어 실패한 최종본을 놓친다.

    그래서 **최종 산출을 정한 단계의 선언**을 본다. 재판정이 돌았으면 그것이
    최종을 정했으므로 그 선언이 기준이고, 안 돌았으면 초기 롤 선정본이 그대로
    최종이므로 초기 선언이 기준이다. 값이 참일 때만 키를 남긴다 — 기록에
    False 를 흩뿌리면 없는 것과 구분되지 않는다.
    rg  rM   initial_roll_all_failTneeds_reshootNr   r   r   r   )ry  rj
final_fails      r#   _set_needs_reshootr  K  s^     
M	"B"d 5 ;"&&!678
&**%<=>
"& r%      )'rv  rw  r  r  r   r   rx  r7  r8  r9  r:  ry  extra_fingerprintpersist_record_fnforcer  critique_shared_prompt	roll_refsr   roll_ref_metadataparallel_rolls
judge_flipflip_prioritycritique_selected_prompt_onlyjudge_prompt_headerr   r;  r   r   r   r=  r   r>  winner_gate_applicablegate_reroll_enabledgate_rejudge_fngate_reroll_allow_fngate_rejudge_identitygate_regen_textsc        -           	'()*+,YZ[\]^_`abc 	r||t        d      Ct              t        t                    k7  r#t        dt               dt                     Ct              t        t                    k7  r#t        dt               dt                     rt        d      djfd}-'rt	        |dd	      st        d
      t        |%|!|t              D .cg c]
  }. |-|.       c}.       rt              ni j                  dd       j                  j                  dd       t              cdkfd^dld}/t               [dmdadn[afd}0d	`dk^`fd\|}1|&t        k7  rWddl}2i |1xs i |&t        |2j                  dj                  |"|#|$g      j!                  d            j#                         dd d}1?ddl}3i |1xs i d|3j                  j!                  d            j#                         dd i}1'i |1xs i dt              D 4ci c]  }4|4|4   
 c}4i}17ddlZdoZfd}5i |1xs i dt              D .ci c]  }.|. |5|.          c}.i}1|r)i |1xs i dt$        t'        |xs t                    di}1ri |1xs i ddi}1|i |1xs i d |i}1|i |1xs i d!t(        i}1| i |1xs i d"t*        i}1|%t,        k7  ri |1xs i d#|%t.        d$i}1dpfd%}6t1        	|1&      bcj3                         xs t5        fd't              D              }7|xs! xr j7                  d(      bk7  xs  xr |7}8|8r%|st8        j;                  d)        t=               i dqfd*_drfd+Yd	d,	 	 	 	 	 	 	 ds^c fd-]	 	 	 	 	 	 	 	 dtY\]^_a	b,)+*(c 'fd.}9dk^bc 'fd/}:cj3                         rj7                  d0      s(t8        j;                  d1        cj?                          nd2v sj7                  d3      rn |:         |9tA        j7                  d0      xs d4      d5j7                  d5      ij7                  d6      xs g       }; |/ |0tA        |;            7       cfS 	sdd3<    |:         |9tA        j7                  d0      xs d4      d5j7                  d5      ij7                  d6      xs g       }<tC                |/ |0tA        |<            7        ^        cfS  \        tE        dui d8 d9 |-d0         d: |6d0         d;d0d0   d<|d=|d>|
d?|d@|dAdB|dC|dD|dE|dF|dG| dH|!dI |-d0         dJ|"dK|#dL|$dM|%dN|&ctC               ddOl#m$}=  |/ |=      7        ^        cfS bd(<   d9<   t              d<   Ht        |xs i       }>t              D .ci c]!  }.|.tK        |.   |>j7                  |.            # c}.d<    ^        t              }?|?D .cg c]  }.tM        |.      j3                         r|.! }@}.|@r \        |rtO        @      dPkD  rddQl(m)}A ddRl*m+}B ddSl,m-}C ddTl.m/}D ddUl0m1}E  |B |C |E |D                        }F |AtO        @      V      5 }G@D .ci c]A  }.|.Gje                  F  dW|.jg                           _|.       Y|.      tM        |.            C }H}.ddd       @D ]'  }.H|.   ji                         [jk                  |.       ) @D ]  }.H|.   ji                         }I|II nL@D ]G  }.   dW|.jg                           _|.       Y|.      tM        |.             [jk                  |.       I |?D .cg c]  }.tM        |.       }J}. \        |rt	        |dd	      rd	}dXdY<   |rYj7                  d      }Ktm        |Kt              si }KKj7                  dZ      }Ld}M|L	 to        L|?D .ci c]  }.|.|. c}.|?      }MM( | J|?      }Lto        |L|?D .ci c]  }.|.|. c}.|?      }MLMd\d<    ^         | t'        tq        J            |?      }Nto        |Nts        |?      |?      }Otu        |M|O|?t'        |xs |?            \  }P}Qd   jw                  NO|Qd]       t        |Qd^         }Rt'        |Qd6         }SMd_   }Tty        |Mj7                  d`      xr Oj7                  d`            Mj7                  d5      da}UnF | J|?      }Vt{        |V      \  }R}P|Vj7                  d6      xs g }SVj7                  d_      xs g }TV}Uty        Uj7                  d`            }W|Wrddb<   t8        j;                  dc P       Uj7                  d5      rUd5   d5<   Uj7                  dd      rUdd   dd<   Uj7                  de      rUde   de<   Uj7                  df      rUdf   df<   t}        Ut              PS'bg      }Pjw                  R|P|STtK        |      dh        ^        cj                  cj                  diz         }Xt        j                  tM        |P      |X       |Xj                  c        |9|P|U|S      }P	s+dd3<   tC                |/ |0P      7        ^        cfS  \        tE        dui d8 d9 |-P      d: |6|P      d;d0|Pd<|d=|d>|
d?|d@|dAdB|dC|dD|dE|dF|dG| dH|!dI |-|P      dJ|"dK|#dL|$dM|%dN|&ctC                |/d7        ^        cfS c c}.w c c}4w c c}.w c c}.w c c}.w c c}.w # 1 sw Y   xY wc c}.w c c}.w # t         $ r t8        j;                  d[        d}LY w xY wc c}.w )vu  공통 생성 파이프 1회 실행. 반환 = (최종 `_sel` 경로, record).

    record 는 체크포인트에 그대로 직렬화 가능한 dict:
      {input_fingerprint, prompt, refs, totals, selected, ranking, verdicts,
       critique{issues} | critique_skipped, fix_prompt | fix_skipped}

    재개 계약 (Codex 1차 리뷰 BLOCKING-3 반영):
      - record.input_fingerprint 가 현재 입력 지문과 일치할 때만 산출 재사용.
        mismatch / force=True / sel 존재+record 부재(무기록 — 출처 불명) →
        이 stem 산출 전부 정리 후 처음부터.
      - persist_record_fn 이 있으면 롤 시작 전(지문)·선정 직후·결함 처리
        직후 각 phase 에서 durable 저장 — 크래시 창에서 critique 영구 생략
        결함 차단.

    roll_prompts (seed 품질 2R, 2026-07-16): 제공 시 라벨별 상이 프롬프트로
    각 1롤 생성(변형 롤). 이때 `prompt`=judge/critique 공유 브리프,
    critique 는 선정 변형 전문+브리프 합성, 지문·record 에 변형 전문 포함.
    미제공=기존 동작·지문 byte-identical.

    still-variants 확장 (2026-07-17, Codex 설계 리뷰 반영):
      - roll_refs: 라벨별 상이 참조(A/B 4택1의 콘티 유무). gen=라벨 refs,
        judge=공유 labeled_refs(블라인드 — 어느 후보가 어떤 참조인지
        미노출), critique=선정 라벨 refs. per-label 참조 내용은 지문 포함.
      - parallel_rolls: 미존재 롤 ThreadPool 병렬 생성 — budget 과
        generation_context 를 worker 에 명시 전파(BLOCKING-3), 예외는 전
        future 완료 후 canonical 라벨 순 첫 예외(NARROW-1).
      - judge_flip: 정순·역순 2회 판정(canonical 역매핑 후 결합,
        BLOCKING-2). 정순 직후 중간 persist(NARROW-2).
      - critique_selected_prompt_only: roll_prompts 가 base 전문을 포함할
        때 브리프 재병합으로 base 가 중복되는 경로 차단(BLOCKING-1).
      - judge_prompt_header: 판정 header 는 어댑터 closure 소유 — 지문
        기여용 병기(HIGH-4). 신규 kwargs 전부 default=기존 지문·record
        byte-identical.
    Nz9critique_enabled=True requires critique_fn and fix_gen_fnu&   roll_prompts 라벨 집합 불일치: r   u#   roll_refs 라벨 집합 불일치: z8critique_selected_prompt_only=True requires roll_promptsc                0    r|    S t        |       S )N)r  )r  )selected_labelr  r  r  r  s    r#   _crit_promptz*run_multiroll_select.<locals>._crit_prompt  s(    (//'L.02 	2r%   rc  Fu   winner_violation_gate 는 이 판정 갈래를 아직 지원하지 않는다 — plain judge_flip 은 정순 관찰만 싣는다(역순 실격이 빠진다). cross-model `owns_order` 경로에서만 켠다.)r   r   r   r   gate_select_action_this_runT)parentsexist_okc                      	         y y r   rl   )r  ry  s   r#   _persistz&run_multiroll_select.<locals>._persist  s    (f% )r%   c                  t        |      | d<   	 ddlm}m}m}  |       }|r|| d<   n| j                  dd        ||       }t        |      |d<    ||       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u+  이 방문의 uid 를 record 에 적고 영향 요약을 trace 에 싣는다.

        ★**모든 반환이 이 함수를 지나야 한다.** 한 곳에만 적으면 먼저 나가는
        문(지문 일치 완결·critique 끔)에서 uid 가 빠져 세 곳이 갈린다.

        produced: 이번 방문이 **최종 산출의 저자인가**. False(산출 재사용)면
            record·Opik 에만 남기고 `image_asset` 의 생성 계보는 **건드리지
            않는다** — 그 자산을 만든 것은 이전 주행이다.

        ★유료 구간 진입은 여기서 안 센다 — `_mark_spend_attempt_once` 가
        호출 **전에** 못박는다. 반환 직전에 세면 과금 후 실패한 시도를
        놓친다(예외가 JIT 전후 비교를 통째로 건너뛴다).
        shot_run_producedr   )build_influence_summarycurrent_shot_uidupdate_trace_outputshot_run_uidNr  u&   _stamp_shot_run 실패 (non-fatal): %s)	r   app.modules.llm.opik_tracer
  r  r  popr  r  debug)r   r  r
  r  r  uidsummaryr  s           r#   _stamp_shot_runz-run_multiroll_select.<locals>._stamp_shot_run  s    $ $(> 	HP P #$C&)N# --c2G"&x.GJ( 	HLLA3GG	Hs   A	A 	B#A>>Bc                 D    	 ddl m}   |        xs dS # t        $ r Y yw xY w)u  **지금** 방문의 신원 — `record` 에 남은 옛 uid 를 읽지 않는다.

        ★`record["shot_run_uid"]` 는 반환 직전 `_stamp_shot_run` 에서야
         쓰인다. 첫 생성이면 아직 없고(None), 캐시 방문이면 **지난 방문의**
         uid 다 — 그걸 「이번에 만들었다」의 근거로 쓰면 계보가 뒤집힌다
         (2026-09-20 Codex BLOCK 2).
        r   r  N)r  r  r  r  s    r#   _this_visit_uidz-run_multiroll_select.<locals>._this_visit_uid7  s*    	C#%-- 		s    	c                    j                  d      }t        |t              rB| |j                  d      k(  r.|j                  d      }        }t        |xr	 |xr ||k(        S | v S )u  **최종 bytes 를 이번 방문이 만들었는가** — 라벨이 바뀐 것과 다르다.

        ★「선택이 달라졌다」로 저자를 정하면 두 방향으로 다 틀린다:
         · 옛 초기 후보 B 로 바뀐 것뿐인데 이번 방문을 저자로 찍는다
         · 이번에 **실제로 산** C 인데 캐시 갈래라고 False 로 찍는다
        ★출처를 모르면 **이번 uid 로 메우지 않는다**.
        gate_rerollr   produced_by_visitr  )final_labelrrmadehere
_made_herer  ry  s       r#   _final_author_is_this_visitz9run_multiroll_select.<locals>._final_author_is_this_visitF  sg     ZZ&b$K266'?$B66-.D"$D66$$,77j((r%   c                     ryd	 t        j                  d      xs d      } | dz   d<            y# t        t        f$ r d} Y #w xY w)u  이번 방문이 유료 호출을 **하기 직전에** 한 번 못박는다.

        ★반환 직전이 아니라 **호출 전**이다. 과금 후 실패하면 예외가
        `still_recipe_service` 의 except 로 빠져 JIT 전후 비교를 통째로
        건너뛴다 — 그 시도가 기록에 안 남고 다음 resume 이 같은 자리를
        다시 탄다.

        ★이름이 곧 계약이다: 이 값은 **「유료 호출을 할 수 있는 구간에 몇 번
        들어갔나」**다. 과금 횟수가 아니다 — 콜백이 바깥으로 안 나가고 끝나는
        경로도 센다(후보 하나뿐인 판정 등). 치우침은 안전한 쪽(과다 계상)이다.

        방문당 한 번만 올린다.
        NTshot_run_spend_attempt_countr   r.   )r   r   	TypeErrorr/   )_nr  _spend_markedry  s    r#   _mark_spend_attempt_oncez6run_multiroll_select.<locals>._mark_spend_attempt_onceX  s_     	VZZ >?D1EB 24a-.
 :& 	B	s   6 A
	A
r   r   r  r	  )r>  repair_method_policyrepair_regen_pack_shacritique_shared_prompt_shar  c                    g }| D ]o  \  }}t        |t        t        f      rt        |      j                         n|}|j	                  t        |      j                  |      j                         g       q |S r   )r   r   r   r  r   r  r  )r   r   r   r!   r  _hashlibs        r#   _ref_sigz&run_multiroll_select.<locals>._ref_sig  ss    #%C"
s "#T{3 I((*9<  

Z!6!@!@!BC # Jr%   r  r  )r   r   r  r  rg  gpt_compositionregen_issue_policy)r   versionc                (    t        |          S S r   rN   )r  r   r  s    r#   
_crit_refsz(run_multiroll_select.<locals>._crit_refs  s     	.122r%   )r  r   r  r  r  c              3  R   K   | ]  }t        |      j                            y wr   )r4  exists)r   rs   r0  s     r#   r   z'run_multiroll_select.<locals>.<genexpr>  s)      &*C 	8S!((**s   $'r  uW   multiroll_select[%s]: 입력 지문 불일치/무기록 산출 — 정리 후 재생성c                    r|    S S r   rl   )rs   r  r  s    r#   _roll_promptz*run_multiroll_select.<locals>._roll_prompt  s    $0|C <f<r%   c                (    t        |          S S r   rN   )rs   r   r  s    r#   	_gen_refsz'run_multiroll_select.<locals>._gen_refs  s    '0'<tIcN#N,Nr%   resumedc               X   t        	j                  d      xs d      }|r$	j                  d      xs i j                  d      nd}t        |       }|j                         set        	d   d<   | 	d   d<   	d   j                  d	d       t        j                  d
|                 t        	j                  d      xs d      S 
j                  
j                  dz         }t        j                  ||       |j                  
       d|| t        |      dd	d<   |t        |      	d   d<   	d   j                  dd       | 	d   d<   | 	d   d<   | |k(  rt        nt        	d   d<   | |k(  rdnd	d   d<   | 	d<            t        j                  d| 	d   d          | S )u^  **선정본을 먼저 바꾸고, 성공한 뒤에 확정한다**.

        ★기록을 먼저 확정하면 복사가 실패했을 때 「기록은 C · 그림은 A」가
         남고, 다음 방문은 `gate.reroll` 때문에 소급도 이음도 건너뛰어
         **실격 A 를 그대로 내보낸다**(2026-09-20 Codex BLOCK 3).
        ★재개는 이 함수만 다시 탄다 — 이미지도 판정도 **안 산다**.

        resumed: **지난 방문이 예고한 결정**을 이어받는 길인가. 이 길은
            이번 방문에서 아무것도 안 산다 — 그 사실을 기록에 남겨야
            상한 계수가 이 방문을 유료로 세지 않는다. 같은 방문에서
            사고 곧바로 확정하는 길은 False 다(그쪽은 지출 계수가 이미
            올라 있어 되돌릴 것이 없다).
        r   r   r  pending_pickNr  r  reroll_file_missingr  ug   multiroll_select[%s]: 재롤 결정(%s)의 파일이 없다 — 결정은 남기고 확정만 미룬다rD  reselect_cachedTactionfromtoto_shamaterializedr  resumed_pending_pickadoptedlostr  u8   multiroll_select[%s]: 재롤 뒤 %s 채택 — 종착 %s)r   r   r4  r2  r  r  r  r  rE  r   rF  rG  rH  r  GATE_RESOLVEDr  )r  rr_labelr8  _before_pick_resumed_pendr!   _tmpr  r0  ry  rJ  ru  s          r#   _materialize_reroll_pickz6run_multiroll_select.<locals>._materialize_reroll_pick  s   " 6::j17R8  ZZ&,"11.A  	 4(zz| )8F6N9%48F6N016Nz40NN:;>F Jvzz*-344}}SXX./CS ( "3' 1
,- $M" 012HI}!!.$7+/}i(%)vz"!X-M? 	vy! )Iv 	vx !z
Fvf~i0	2 r%   c                
	   ,r#s| S (j                  d      }t        |t              s| S |j                  d      t        k(  r#|j                  d      s|j                  d      rn|j                  d      t        k7  r| S t        )dz         d   }t        &|      }t        )dz         }(j                  d      }t        |t              }|j                  d	      xs | }	|rt        |t              r|j                  d
      }
|
r,|j                  d      t        k(  r t        |
      |d      S |j                  d      t        k(  r:|j                  d      s)|j                  d      t        j                  d+       | S d}|r|j                         sd}n)|s't               \  }}|sd| }nrd}n"	 "       sd}|r%||d<   t        j                  d+|                | S |s_t!        t#        ||	      D cg c]  }dt        |      i c} |	      xs i j                  dd      xs i j                  dd      xs i j                  dd            }t        t%        j&                         j(                  t*        ||	|t        !xs i       d(d<   t        (j                  d      xs i       }|st        )      D ci c]  }| |       }}|||<   |(d<                      $+ d|j-                          | |	      |       (d   j/                  t0        t3        |              d                            +'%|D cg c]  }t        &|       c}|      }t5        |||j                  d!      xs g       \  }}}(d   j/                  t        |||j                  d"      |j                  d!      xs g |d#       |T|rRt        (d   d<   t7        |      (d   d<   (d   j9                  d$d       t        j                  d%+|                | S |'d&(d   d'<   t        j                  d(+                | S |(d   d
<             ||      S c c}w c c}w c c}w ))u  게이트가 **미해결**이면 후보를 하나 더 사서 다시 판정한다.

        ★**새 판정 직후와 캐시 반환 둘 다** 여기를 지난다 (2026-09-20
         Codex BLOCK 2). 새 판정 자리에만 걸면, 주행 상한에 걸렸거나
         배선이 안 됐던 샷이 다음 방문에 **영영 못 온다** — `_sel` 과
         `critique_skipped` 가 저장돼 캐시 갈래로 먼저 나가기 때문이다.

        돌려주는 것: 최종 라벨(바뀌었으면 호출부가 `_sel` 을 맞춘다).
        r  r  reroll_uncheckedr;  r.   r  )r   r  r  r:  stateTr7  r  rD  Nu~   multiroll_select[%s]: 재롤 결과가 **이미 확정**됐다(전부 관찰·전부 실격) — 다시 판정하지 않는다r   u   앞선 재롤 시도의 결과가 불명이다 — 새 1회를 주지 않는다 (사람이 확인하고 기록을 지워야 다시 산다)u)   재롤 배선이 준비되지 않았다: uq   critique master 가 켜진 조합은 아직 지원하지 않는다 (재롤 라벨의 수정 문안 배선 없음)u(   주행 단위 재롤 상한에 닿았다reroll_skippeduA   multiroll_select[%s]: 게이트 재롤을 하지 않는다 — %srZ   r   r   r=  )r   r  
attempt_idrO  r   from_selectedr  identityr  r(  )rO  r  r  rD   rJ   )rO  r  r  rJ   rD   rD  r  u   multiroll_select[%s]: 재판정에 **안 본 후보**가 있다(%s) — 실격으로 확정하지 않고 미검사로 남긴다no_admissibler  u{   multiroll_select[%s]: 재롤 뒤에도 허용 후보가 없다 — **unresolved** 유지 (이미지 1 + 재판정 1 지출))r   r   r   r  r  r1   r4  r  GATE_REROLL_POLICY_VERSIONREROLL_JUDGEDr   r  rq  r2  r  r  r  r%  uuiduuid4hexREROLL_SENTr,  r  REROLL_PRODUCEDr  r  r   r  )-selected_nowjudge_result_nowranking_nowr  rG  rr_path
labels_allr  same
base_label_pendstopr  whyr|   	rr_prompt_rprs   r  rr_judger  admr  r6  r%  rK  r  r4  r  r  r  r  r  r  r  r  gen_fnr   r0  r  ry  r  rJ  ru  r  s-                          r#   _gate_reroll_continuez3run_multiroll_select.<locals>._gate_reroll_continueM  s    '+>JJv!T"
 55.()QUU3H-I UU90zA~.r2Xx0 a0


=)%4#% UU-.>,
Jud+ IIn-E7+}</J$8 8 		'"m3!IIk2		),4WX[] $#([D'9IJGB C3%H!S&202 B"&ANNST J
 +/E$j02 3 02!*c!f% 02 3Z(!'R,,\2>!'R,,\2>!'R,,-BBGI 5#."jjl..$!!+ $ !6!<"=%F=! vzz.17R8C9DZ9PQ9P#sL--9PQ%CM%(F>"J$&cU!HNN,-.	Z('3=!(((&w/
 &5%6*  J 	!"".89jZ!$j9:G  ;j(,,y"9"?R Ac9}$$"" Z0||I.4"&
 	 <I
 )8F6N9%15iF6N-. 6Nz40NNJKNPY[ J<'6F6N8$NNKLOQ J 15}n-
'h77w30 R. :s   Q6Q;R c                 
   t        t                    } | y| rt        |       }|j                         s/t        t
        ddd|  ddd<   t        j                  d
|        n	j                  	j                  d	z         }t        j                  ||       |j                  	       t        j                  d
      xs d      }| d
<   d|| t        |      ddd<   t        j                  d
|                 y)u  **호출 0** 으로 캐시 기록에 정책을 적용한다 (2026-09-20 Codex (e)).

        정책을 생성 지문에서 뗀 뒤로, 지문이 맞는 기록은 판정을 안 거치고
        캐시로 그대로 나간다 — 그러면 정책을 켜도 옛 기록에는 영영 판정이
        안 붙고 verify 가 그 샷들을 계속 **미판정**으로 붙잡는다.

        ★**무호출 반환이 둘**이라 두 곳 다 여기를 지난다. 한 곳만 걸면
         끄기/켜기 조합에 따라 절반이 조용히 빠진다.
        ★재선택이 나면 **선정 파일까지** 그 후보로 맞춘다 — 기록만 B 이고
         파일은 A 인 채 나가면, 중간에 끊겼다 재개해도 그 어긋남이 남는다.
        )ry  rO   r  r  NTu   재선택 후보 u    의 파일이 없다)r  r   r  r  r  r  u   multiroll_select[%s]: 소급 게이트가 %s 로 바꾸려 했으나 그 후보 파일이 없다 — 선택·선정본을 그대로 두고 **미검증**으로 남긴다rD  r   r   r<  r=  r  uk   multiroll_select[%s]: 소급 게이트 — 허용되는 기존 후보 %s 로 바꿨다 (이미지 구매 0))r  r1   r4  r2  r  r  r  r  rE  r   rF  rG  rH  r   r   r  )changedr!   rJ  _fromr  r  r0  ry  r  rJ  ru  r  s       r#   _retro_gatez)run_multiroll_select.<locals>._retro_gate	  s    0+j"9-)+ ?Xw/C::<  /1"&#' 1':OP"v 458'C
 }}SXX%67C&S! FJJz28b9%,z"
 0!!*3/$(945 CDGR 	
r%   r   ub   multiroll_select[%s]: sel 존재 + 선정 기록 없음 — sel 폐기 후 기존 롤로 재판정r@  r  r   rJ   rD   )r  ru  r  r   r0  rv  rw  r   r   rx  ry  r7  r8  r9  r:  r   r;  r   r<  r   r   r=  r   r>  )fix_stage_wonr.   )ThreadPoolExecutor)bind_current_budget)bind_current_ledger)bind_current_trace)bind_current_generation_context)max_workersr(  judge_fn_owns_orderjudge_flip_skippedri  uK   multiroll_select[%s]: durable forward 판정 malformed — 정순 재판정)ri  rj  )rk  rl  r   r}   rE   rM   )rM   rJ   r  ue   multiroll_select[%s]: 심판이 초기 롤 전부 실패를 선언 — 선정본(%s)은 least-bad 다gqre  dualr  )r}   r   rD   rE   r   rD  )r  r   rm  r   )rm  rn  )r   Dict[str, Any]r  r   rm  rn  )rm  Optional[str])r  r   rm  r   )r   Sequence[Tuple[str, Any]]rm  zList[List[str]])r  r   rm  r}  )rs   r   rm  r   )rs   r   rm  r}  )r  r   rG  r   r8  r   rm  r   )r\  r   r]  r{  r^  Sequence[str]rm  r   rl   )Dr/   r   r1   r   rs  r   r   r  r-  mkdirr6  REPAIR_METHOD_EDITr
  REPAIR_METHOD_POLICY_VERSIONr  r   r  r  r   r   rt  GPT_COMPOSITION_POLICY_VERSIONREGEN_ISSUE_POLICY_LEGACY_EDITREGEN_ISSUE_POLICY_VERSIONr  r2  rr  r   r  r  r2  r/  r   r  r  !app.modules.pipeline.still_reciperp  r$   r4  r   concurrent.futuresrq  app.core.image_call_budgetrr  app.core.send_ledgerrs  r  rt  "app.services.image_capture.contextru  submitr,  	exceptionr   r   r   reversedr   r   r  r   r   r  rE  r   rF  rG  rH  )dru  r  r   r0  rj  r  rv  rw  r  r  r   r   rx  r7  r8  r9  r:  ry  r  r  r  r  r  r  r   r  r  r  r  r  r  r   r;  r   r   r   r=  r   r>  r  r  r  r  r  r  r  rs   r  r  	_fp_extra_hl3_hl2kr+  r0  has_outputsstalerk  ro  	_rr_after
_rr_after2rp  
_roll_metarO   missingrq  rr  rs  rt  ru  boundpoolfuturesr  
cand_paths	_prior_jfjudge_fwd_rawr   judge_rev_raw	judge_revr   r   r}   rD   rE   r!  r{   _acf_tmp_selr6  r*  r  r%  rK  r  r4  r$  r  r  rJ  sd   `````   ``       ` ` ```     `         ``````                                            @@@@@@@@@@@r#   run_multiroll_selectr  b  sk   P [0J4FG
 	
 LSZ!8994VL5I4J$:&')
 	
 I#k*5661&2C1DD:&')
 	
 %)=F
 	
2 2 glE'+?@ 	@ #";!%)4Z)@B)@#L)@B	 $T&\F JJ,d3OO$6
H
C&#HT eJ)  M2 "I **	
B	
*$@ &*[[J(;< 2vg&  )	CR&1	
	 )
B
($++&--g6+88A	CR+I
	
 
B
9MN9MAQQ/9MN
	
 "
	
B
9?	9J9J#Xin--9J
	 
B
- !I+j2IJ
	 %<	R <4d<	&A	R A*,?A	!@	R @"$>@	*H	R H&(FH	$ !$BBA	R A)#<$>,@A	 ,L0@K
 **, # &z*& #K
 	 	(Evzz"56+E	(J&; 

 NN'(+ 	x =O 6;GG G.2G	G GR~8~8(~8 #~8 
	~8 ~8 ~8 ~8@; ;| zz|zz*% NN569 JJL6!VZZ0B%CM .FJJz*0b1VZZ
34

9%+-I !<S^!LN;!)-F%&M.FJJz*0b1VZZ
34

9%+-J v&
 !<S_!MOJ;
 %&# #F:$67 (z(:; "	 -3:,>	
 (
 5? " -5 AJ  .; * "2 "2  . )@ * *&*<= & 3=  %8!" +D#$ ,%C( v& HF]6-BCJ; #.FF8!%l!3~+1r2
 i(
( "9S>:>>#3FGG(
{ J $F Z#%>%E%E%Gv    "#g,* 	:B<A	
 $/0B60JKMN  CL9T #
 #C	 T[[cU!CIIK=1<3DcNJx$=  #   : Cs|%%'/s#  C#,((*C	 
 C%q&S(9#
8S 9
 NN3  DJJ6Cj376JJ
  ghe<
'<#$ JJ|,	)T*I!m4	$	%2!#?CH#?	 $V\:vM /F;FSSF;VI
 )"+ 
| 	
 tHZ,@'A6
 +4V<f
	 3y&$}/F*G
( 	|##("+ %
 	
 hx()x	*+Z(
 $(34 9MM"78$: "j1	(
 flJG(/))I&,"99Z(.B   !678D +/&'-.18	= 
#)*5z #D)t +,&23F&G"# %f-v
 +L:&$:%	'H MM  %l4EF	
 J}}SXX./H
KK
8X.9S %X|WEH %)!"6" 	8B	D
F{
 H%  ) 	 %-	
  
 -7  %- 9B  &3 " * * & !8 " "(+  +5  0!" #<#$ $%C( vFT*J;]Bt O(~
6 :92 K@ $@ %,-0 !%%  <ss   n(n-9n2*&n7,n<n<5o:Ao o%o
o 
o
'o 
p
ooo !ppr   )r   r}  r   ,Optional[Sequence[Optional[Dict[str, Any]]]]rm  zList[Dict[str, Any]])r0   r   rm  r+   )TF)rO   r~  rP   r   rQ   r   rm  r{  )FF)rg   r   rh   r   rm  r{  )rm  r{  )r{   r{  rm  zTuple[Dict[str, int], str])
r   r   r   Optional[GenFn]r   Optional[JudgeFn]r   r~  rm  rn  )rO   r~  rm  zDict[str, str])r{   r   rO   r~  rm  rn  )r   r{  r   zMapping[str, str]rO   r~  rm  r{  )
r   r{  r   r{  rO   r~  r   r~  rm  zTuple[str, Dict[str, Any]])rX   Sequence[Dict[str, Any]]rm  r+   )rX   r  r   r}  rm  z,Tuple[List[Tuple[str, Any]], Dict[str, Any]])rl   r   r   )rX   Sequence[Dict[str, str]]r   r   r   r   r   r~  r   r   r   r   rm  r   )r   r   r   )rX   r  r   r   r   r   r   r   r   r   rm  r   )r  r   r   r}  r  r   r  r   r  Optional[Dict[str, Any]]rm  r   )r   r   rm  r   )r!  r{  r   r   rm  r+   )r0  r   rm  rn  )r0  r   r   r   rm  r   )r0  r   rm  r   )2ru  r   r  r   r   Sequence[Tuple[str, Path]]r0  r   r   r   rv  
CritiqueFnrw  GenFnr   r   r   r   rx  r   ry  r{  r7  r   r8  r   r9  r   r:  r   r   r  r;  Optional[CritiqueFn]r   r  r<  r   r   r   r   r   r=  r   r   r   r>  r   rm  r   )
r  r   r  Optional[Mapping[str, str]]r   r   r  r|  rm  r   )r!  r{  rO   r~  rm  zTuple[List[str], List[str]])r   )r!  r{  rO   r~  r  r   rm  r{  )r  r   r   r   r  r   rm  r   )r  r   r  r  rm  zTuple[bool, str])r!  r{  rO   r~  rD   r~  rm  z*Tuple[Optional[str], List[str], List[str]])ry  r{  r  r{  rm  r   )
ry  r{  rO   r~  r  r   r  r   rm  r|  )ry  r{  r!  r{  rO   r~  r   r   rD   r~  r  r   r  r   rm  r   )ry  r{  rm  rn  )\ru  r   r  r   r   r  r0  r   rj  r  r  JudgeFnrv  r  rw  r  r  r   r  r   r   r   r   r   rx  r   r7  r   r8  r   r9  r   r:  r   ry  r  r  r  r  z*Optional[Callable[[Dict[str, Any]], None]]r  r   r  r  r  r|  r  z1Optional[Mapping[str, Sequence[Tuple[str, Any]]]]r   r  r  z:Optional[Mapping[str, Sequence[Optional[Dict[str, Any]]]]]r  r   r  r   r  zOptional[Sequence[str]]r  r   r  r|  r   r  r;  r  r   r  r   r   r   r   r=  r   r   r   r>  r   r  r   r  r   r  r   r  zOptional[Callable[[], bool]]r  zOptional[Mapping[str, Any]]r  r  rm  zTuple[Path, Dict[str, Any]])U__doc__
__future__r   loggingrF  rW  pathlibr   typingr   r   r   r   r	   r
   r   r   	getLoggerr   r  r$   r,   __annotations__r   r  r  r  r1   rV   ri   rm   r   r   rt  r  r  rp  REPAIR_METHODSr  r  ro  r   r  r   r   r   r   r   r   r   r   r   r   r   r  r  r  r%  r2  r4  r6  r  r  r  r  r  r  rF  r  r  r  r  r  rU  rZ  r[  rV  r  r  r  r  r  r  r  r  rl   r%   r#   <module>r     s	  & #       
		8	$
 GK + C   H 3Y 2 	#sHU39%56=tCD
#xc4i()8D>8C=IcN
 sC%T	*:!;TBDcNRS
 .2,1U&*U%)U6DUr 8=ll04ll^<0   :  "< <  ' $&>?< !. " "$<>  4  $&.". ". &	.
 !. 
.bH.Nb##+# # 	#L##(# # 	#
  #L$&9$9+9 29@ $&$  !	
   	BC   C  $  	
  	F '+ , 	
  $ 	> ),&M:D8" (,48$(!%C+3h	h h -	h
 h h h h h h h h h h h  !h" &#h$ 2%h& "'h( )h* +h, -h. /h0  #1h2 3h4 
5h^ $(	-  !	
 	> B  
&  / ,! ,!*7,! ,!d     	> ;  ===-0=	= ?C ;@' '' ' 0	'&..&4.	.<  TT T 	T
 T T~  \\ !\ 	\
 \ \ \ \ 	\~'> )-"&!'+26DH04,0CG GK<@ -1*/)-(,48$(!%C+ $) !& :> :> 59IE	E E -	E
 E E E &E  E E E E E E E" #E$ %E& 'E( %)E* 0+E, B-E. /E0 .1E4 *5E6 A7E> D?E@:AED EEF GEH +IEJ $(KEL 'MEN &OEP 2QER "SET UEV WEX YEZ  #[E\ ]Ed !eEp qEt uEz 7{E@ 7AEH 2IEJ !KEr%   