+
    iV9                      a 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RI	H
t
 ^ RIHtHtHtHtHt . ROtR]R&   ] U u. uF  p ]P'                  V ]P(                  4      NK!  	  up tR R	 ltRR
 R lltR R ltR R ltR R ltR R ltR R ltR R ltR R lt]R8X  d   ]P@                  ! ]! 4       4       R# R# u up i )u)  G4.2 canary — STRICT forbidden close-framing wording counter (Rule E lift).

Counts forbidden phrasing in close-framing shots' t2i_prompt strings. v17 must
hit zero — Rule E lift to background_binding.constraints removes the previous
prose; if any forbidden form leaks through, the lift is incomplete.

Patterns (10 total):
  R1-I2: 6 ground-truth forms historically violated by Rule E.
  R2-B3: 4 Rule A close-framing invalidation forms.

Exit criteria (analyzer, OUT-OF-BAND):
  candidate.metrics.rule_e_forbidden_wording_count_close_only == 0  (STRICT)
  baseline.metrics.rule_e_forbidden_wording_count_close_only is informational

Usage:
    python scripts/canary/g4_2_close_forbidden.py \
        --config canary_config.json --role candidate \
        --output results/close_forbidden_candidate.json
)annotationsN)datetime)Path)AnyDictListOptionalTuplez	List[str]CLOSE_FORBIDDEN_PATTERNSc                   V ^8  d   QhRR/# )   returnstr )formats   "&scripts/canary/g4_2_close_forbidden.py__annotate__r   2   s     / /S /    c                 V    \         P                  ! 4       P                  4       R ,           # )Z)r   utcnow	isoformatr   r   r   iso8601_nowr   2   s    ??&&(3..r   c               $    V ^8  d   QhRRRRRR/# )r   msgr   codeintr   Noner   )r   s   "r   r   r   6   s!      s # d r   c                n    \        R V  2\        P                  R7       \        P                  ! V4       R# )z[g4_2_close_forbidden] ERROR: fileN)printsysstderrexit)r   r   s   &&r   _failr%   6   s#    	*3%
0szzBHHTNr   c                    V ^8  d   QhRRRR/# )r   argszargparse.Namespacer   Dict[str, Any]r   )r   s   "r   r   r   ;   s     % %) %n %r   c                   V P                   '       d   \        V P                   4      pVP                  4       '       g   \        R V 24        VP	                  RRR7      ;_uu_ 4       p\
        P                  ! V4      pRRR4       RXP                  R4      RTP                  R4      RTP                  R4      R	TP                  R	4      R
TP                  R
4      RTP                  R4      RTP                  R4      /#  V P                  '       d!   \
        P                  ! V P                  4      MRpV P                  '       d!   \
        P                  ! V P                  4      MRpRV P                  RXRXR	V P                  R
V P                  RV P                  RV P                   /#   + '       g   i     EL5; i  \         d   p\        RT 24        Rp?ELWRp?ii ; i  \         d   p\        RT 24        Rp?LRp?ii ; i)z--config file not found: rutf-8encodingNz--config parse error: pidscene_index_listshot_index_list_per_scenemodel_routingprompt_source_modecard_commit_hashchain_bg_card_snapshot_hashz%failed to parse list/dict JSON args: )configr   existsr%   openjsonload	Exceptiongetr/   loadsr0   r.   r1   r2   r3   r4   )r'   cfg_pathfpcfgexcr/   r0   s   &      r   load_pinningrA   ;   s   {{{$  -hZ89	2sW55iim 6
 3775>(: ;'1L)MSWW_5 #''*>"?(: ;)3773P+Q
 	
	=151F1F1FDJJt,,-D 	
 --- JJt55637 	" 	txx,#%>++d55D11%t'G'G / 655 	2*3%011	2&  =5cU;<<=sZ   F? (F+?F? G& 3G& "G& +F<	6F? <F? ?G#
GG#&H	1HH	c                    V ^8  d   QhRRRR/# )r   pinningr(   r   r   r   )r   s   "r   r   r   c   s     P Pn P Pr   c                :   . ROpV Uu. uF  q P                  V4      R	9   g   K  VNK  	  ppV'       d   \        RV R24       \        V R,          \        4      '       g   \        R4       \        V R,          \        4      '       g   \        R4       R# R# u upi )
r.   r/   r0   Nz#pinning missing required field(s): zF. Provide via --config <json> or individual flags. NO silent defaults.z*pinning.scene_index_list must be list[int]z>pinning.shot_index_list_per_scene must be dict[str, list[int]])r.   r/   r0   r1   r2   r3   r4   )N )r;   r%   
isinstancelistdict)rC   requiredkmissings   &   r   validate_pinningrL   c   s    H
 #C(Qkk!n
&Bqq(GC1' ;S T	
 g01488:;g9:DAANO B Ds
   BBc                    V ^8  d   QhRRRR/# )r   rC   r(   r   zList[Tuple[int, int]]r   )r   s   "r   r   r   u   s      ^ 8M r   c           	        . pV R,          pV R,           F  p\        V4      pVP                  \        V4      4      pVf   VP                  V4      pVf   \        RV R24       \	        V\
        4      '       g&   \        RV R\        V4      P                   R24       V'       g   \        RV R24       V F  pVP                  V\        V4      34       K!  	  K  	  V# )	u  Cartesian (scene_index, shot_index) tuples from pinning block.

Strict — pinning enumerates EXACT (si, shi) tuples. A scene_index without
a corresponding shot_index_list entry (or with a non-list / empty list)
is a contract violation, not a silent default. fail-fast via _fail.
r0   r/   z(pinning.scene_index_list contains scene zF but shot_index_list_per_scene has no entry for it. NO silent default.z"pinning.shot_index_list_per_scene[z] must be list[int], got z. NO silent default.uR   ] is empty — a pinned scene must enumerate at least one shot. NO silent default.)	r   r;   r   r%   rF   rG   type__name__append)rC   outshot_mapsisi_int	raw_shotsshis   &      r   build_pinning_scene_setrX   u   s     "$C23H())RLLV-	 V,I:6( CT U )T**4VH =I//00DF 4VH =V W CJJC)* ) *, Jr   c                    V ^8  d   QhRRRR/# )r   cp_rootr   r   r(   r   )r   s   "r   r   r      s     3 3d 3~ 3r   c                L   V R ,          pVP                  4       '       g   \        RV 24        VP                  RRR7      ;_uu_ 4       p\        P                  ! V4      uuRRR4       #   + '       g   i     R# ; i  \
         d   p\        RT 24        Rp?R# Rp?ii ; i)zmanifest.jsonzmanifest.json not found at r*   r+   r,   Nzmanifest.json parse error: )r6   r%   r7   r8   r9   r:   )rZ   manifest_pathr>   r@   s   &   r   load_cp_manifestr]      s    o-M!!+M?;<3g66"99R= 7666 3+C51223s5   A? 
A+ 
A? +A<	6A? <A? ?B#
BB#c                    V ^8  d   QhRRRR/# )r   scener(   r   zTuple[bool, Optional[str]]r   )r   s   "r   r   r      s      N /I r   c                   V P                  R4      p\        V\        4      '       g   R# VP                  R4      p\        V\        4      '       g   R# VP                  R4      R8X  d   R	# VP                  R4      RJ d   R	# R
# )u  Return (cf_flag, error_message).

Reads from `scene["render_prompt_card"]["background_binding"]` — the actual
CP shape produced by detail_steps.py (scene["background_binding"] does NOT
exist as a flat field). When the scene record is missing the
`render_prompt_card` envelope or its `background_binding` field, returns
(False, reason) so the caller can append to measurement_failures
(NO silent absorb — Codex C1 fix).

error_message is None on success.
render_prompt_cardbackground_bindingmodeskipped_close_framingT"close_framing_skips_background_ref)Fz&render_prompt_card missing or not dict)Fz9render_prompt_card.background_binding missing or not dict)TN)FN)r;   rF   rH   )r_   rpcbbs   &  r   is_close_framingrh      s}     ))(
)Cc4  @@	%	&Bb$SS	vvf~00	vv23t;r   c                    V ^8  d   QhRRRR/# )r   
variationszList[Dict[str, Any]]r   zTuple[int, List[str]]r   )r   s   "r   r   r      s     " "$""r   c                N   ^ p. pV  F  pVP                  R4      ;'       g    Rp\        V\        4      '       g   K6  \        \        \
        4       FK  w  rVVP                  V4      pV'       g   K   V\        V4      ,          pWR9  g   K:  VP                  V4       KM  	  K  	  W3# )u   Return (occurrence_count, list_of_unique_pattern_strs_that_fired).

Counts total occurrences across all variations (not unique shots).
Caller is responsible for validating `variations` is a non-empty list
(Codex iter2 BLOCKING 2 — silent absorb 차단).

t2i_promptrE   )	r;   rF   r   zipr
   _COMPILED_PATTERNSfindalllenrQ   )rj   occurrencesmatchedvarpromptrawcompiledhitss   &       r   scan_scene_forbiddenrx      s     KG&,,"&#&& !9;MNMC##F+Dts4y(%NN3' O	  !!r   c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r      s     ] ]c ]r   c                 ^   \         P                  ! R R7      p V P                  R\        RR7       V P                  R\        RR7       V P                  R\        RR7       V P                  R\        RRR	7       V P                  R
\        RR7       V P                  R\        RRR.R7       V P                  R\        RR7       V P                  R\        RR7       V P                  R\        RR7       V P                  R\        R. RNOR7       V P                  R\        RR7       V P                  R\        RR7       V P                  R\        RR7       V P	                  4       p\        V4      p\        V4       RpVP                  '       d_   \        VP                  4      P                  RRR7      ;_uu_ 4       p\        P                  ! V4      pRRR4       XP                  R4      pV'       g   VP                  pV'       g   \        R4       \        V4      pVP                  4       '       g   \        RV 24       \!        V4      pVP                  R4      p\#        V\$        4      '       g   \        R4       VP                  R 4      p	\#        V	\&        4      '       g   \        R!4       \)        \+        V4      4      p
\)        4       p^ p^ p^ p^ p/ p. p. p. pV	 EFr  p\#        V\$        4      '       g   K  VP                  R"4      pVP                  R#4      pVe   Vf   KH  VV3V
9  d   KS  VP-                  \/        V4      \/        V4      34       V^,          p\1        V4      w  ppV'       d   VP3                  R$V R%V R&V 24       VP3                  R"VR'VR(V/4       V'       g   V^,          pK  V^,          pVP                  R)4      p\#        V\&        4      '       d	   V'       g   VP3                  R$V R%V R*24       EK.  \5        V4      w  ppVV,          pVVV R+V 2&   V F  pVV9  g   K  VP3                  V4       K  	  EKu  	  V
 F)  w  ppVV3V9  g   K  VP3                  R,V R%V R-24       K+  	  R.\7        4       R/VP8                  R0VP:                  R1VR2VR3R4VR5VR6VR7VR8VR9V/R:VR;R<R=//pVP<                  R>8X  dJ   \        P>                  ! V\@        PB                  R?^RR@7       \@        PB                  PE                  RA4       Mq\        VP<                  4      pVPF                  PI                  RRRB7       VP                  RCRR7      ;_uu_ 4       p\        P>                  ! VVR?^RR@7       RRR4       \K        RDVP:                   REVP8                   RFV RGV RHV RIV RJV RK\M        V4       RL2\@        PN                  RM7       V'       d   ^# VP:                  R8X  d
   V^ 8  d   ^# ^ #   + '       g   i     EL;; i  + '       g   i     L; i)Oz<G4.2 canary: STRICT forbidden close-framing wording counter.)descriptionz--configN)rO   defaultz--pidz	--episodez	--cp-rootz/Directory containing scene_detail manifest.json)rO   r|   helpz--prompt-versionT)rO   rI   z--role	candidatebaseline)rO   r|   choicesz--scene-index-listz--shot-index-list-per-scenez--model-routingz--prompt-source-modez--card-commit-hashz--chain-bg-card-snapshot-hashz--outputr*   r+   r,   rZ   z)--cp-root (or config.cp_root) is requiredz--cp-root path does not exist: dataz!cp['data'] is missing or not dictscenesz+cp['data']['scenes'] is missing or not listscene_index_shot_indexs_shz: 
shot_indexrh   t2i_variationsz,: t2i_variations missing, empty, or not list_z
pinning (sz ): not present in CP data.scenes	timestampprompt_versionrolerC   	scene_setmetricstotal_shotsnon_close_framing_shotsclose_framing_shots)rule_e_forbidden_wording_count_close_only!rule_e_forbidden_wording_per_shot!rule_e_forbidden_patterns_matchedmeasurement_failuresmeasurement_scriptsclose_forbiddenz_scripts/canary/g4_2_close_forbidden.py (R1-I2 6-pattern + R2-B3 4-pattern, CF-only scope R2-B4)-F)ensure_asciiindent	sort_keys
)parentsexist_okwz[g4_2_close_forbidden] role=z prompt_version=z
  total_shots=z  ncf=z  cf=z-
  rule_e_forbidden_wording_count_close_only=z
  matched_patterns=z
  measurement_failures: z
Exit criteria (STRICT):
  candidate.metrics.rule_e_forbidden_wording_count_close_only == 0
  baseline value is informational only.r   )Nr    db)(argparseArgumentParseradd_argumentr   
parse_argsrA   rL   r5   r   r7   r8   r9   r;   rZ   r%   r6   r]   rF   rH   rG   setrX   addr   rh   rQ   rx   r   r   r   outputdumpr"   stdoutwriteparentmkdirr!   rp   r#   )parserr'   rC   cp_root_strr>   r?   rZ   cpr   r   pinning_set
seen_in_cptotal	ncf_countcf_countforbidden_totalforbidden_per_shotmatched_patterns_unionscene_set_outr   r_   rT   rW   cfcf_errrj   occrr   prR   out_paths                                  r   mainr      s   $$RF 
d;
c48
#t<
#tN  P
*tD
sK!+[ 9  ;
,3E
5CN
)TB
.S$ 4  6
,3E
7c4P

t<D4 GW"K{{{$++##C'#::b))B-C ;ggi(ll9:;G>>/y9:	'	"B 66&>DdD!!12XXhFfd##;<-g67K'*uJEIHO)+(**,M&(%&&YY}%ii&:9K'BS*+
%e,
F ''B4s3%r&* 	2|S2Db
 	 NIA YY/0
*d++: ''B4s3%KL +J7W3,/bT3%=)A..&--a0 E R !	S9J& ''RDC5(HI ! 	[]$--		7]5%y!87/1C/1G
 	 4   A 
C( {{c		#szza4P

$dT:]]3]11RIIc2E!tL 2 

&tyyk1A$BUBUAV Wvi[hZ @77F6G H45 6##&';#<"= >2		3 ZZ
  yyKOa$7E ;::V 21s   ?X-XX	X,	__main__)
zthe existing \wzfrom the referencezuse the \w+ from the referencez$preserving the same room perspectivez$maintaining the reference'?s framingzdo not generate a new \wzmatching the reference camerazdeviation from referencez(same\s+[^\n]{0,20}angle as the referencezmatch the reference framing)   )!__conditional_annotations____doc__
__future__r   r   r8   rer"   r   pathlibr   typingr   r   r   r   r	   r
   __annotations__compile
IGNORECASErn   r   r%   rA   rL   rX   r]   rh   rx   r   rP   r$   )r   r   s   0@r   <module>r      s   & #   	 
   3 3' )  =UU<TqbjjBMM2<TU /
%PP$D32"0]@ zHHTV I
 Vs   %C