
    :i%-                    H   d Z ddlmZ ddlZddlZddlmZ ddlmZ  ee	      j                         j                  d   dz  Z ee      ej                  vr"ej                  j                  d ee             ddZdd	Z	 	 	 	 	 	 	 	 	 	 dd
ZddZddZddZddZddZy)u  Shared preflight helpers for G4.3 canary scripts (R3R4-B2 carry).

All 4 metric scripts (`g4_3_body_part_focus.py`,
`g4_3_close_framing_face_forbidden.py`, `g4_3_reproduction_surface.py`,
`g4_3_demographic_descriptor_present.py`) import the helpers from this module
and apply them identically. The token-count script (`g4_3_token_count.py`)
does NOT use these helpers — it reads prompt files, not CP.

NO silent absorb (Override R3R4-B2 / O-3 / O-4 / O-5):
- `or [] / or {}` 패턴 금지.
- `data.scenes` / `t2i_variations` / `t2i_prompt` / `render_prompt_card` /
  `id_policy` 누락은 measurement_failures 로 적재 후 caller 가 exit 1.
- `compute_id_policy_snapshot_hash()` 호출 실패도 measurement_failures 로 적재
  (silent skip 금지).
    )annotationsN)Path)Any   backendc                 4    ddl m} m}m}m}m}m} | |||||dS )zReturn the 6 module symbols Wave 1-A exposes.

    Centralised to give every canary script a single import boundary; if Wave
    1-A renames a constant, only this module needs adjustment.
    r   _ID_AGE_BANDS_ID_BODY_PART_TRIGGERS _ID_CLOSE_FACE_FORBIDDEN_PHRASES_ID_ETHNICITY_COMPONENTS_ID_REPRODUCTION_SURFACEScompute_id_policy_snapshot_hash)!app.core.steps.render_prompt_cardr
   r   r   r   r   r   r	   s         I/Users/manta/Documents/Projects/TheRoad-I1/scripts/canary/_g4_3_common.pyrender_prompt_card_importsr   $   s*      '"8,L$<%>+J     c                    | j                  d      }t        |t              s|j                  d       y|j                  d      }t        |t              s|j                  d       y|S )u   Validate CP envelope shape and return scenes list (or None on failure).

    Caller must check None and fail-fast (sys.exit(1)) — measurement_failures
    is appended in-place. NO silent absorb.
    datazdata missing or not dictNsceneszdata.scenes missing or not list)get
isinstancedictappendlist)cpmeasurement_failuresr   r   s       r   _load_scenes_or_failr   =   s\     66&>DdD!##$>?XXhFfd###$EFMr   c           	        | j                  d      }t        |t              s|j                  d| d| d       y|j                  d      }t        |t              s|j                  d| d| d       y	 dd	lm}  ||       | j                  d      }t        |t              s|j                  d| d| d       y|S # t        $ r$}|j                  d| d| d
|        Y d}~yd}~ww xY w)u  Validate per-scene shape — render_prompt_card / id_policy / variations.

    Returns variations list when valid; None otherwise (caller continues loop).

    Per spec §5.3 + Override R3R4-B2:
      - render_prompt_card must be dict.
      - render_prompt_card.id_policy must be dict.
      - compute_id_policy_snapshot_hash(rpc) must succeed (no silent skip).
      - t2i_variations must be list (may be empty — empty list is valid CP
        shape; per-variation validation happens in caller loop).
    render_prompt_cards_shz(: render_prompt_card missing or not dictN	id_policyz2: render_prompt_card.id_policy missing or not dictr   )r   z: id_policy hash failed: t2i_variationsz$: t2i_variations missing or not list)r   r   r   r   r   r   	Exceptionr   )	scenesishir   rpcr#   r   exc
variationss	            r   _validate_scene_preflightr,   N   s   " ))(
)Cc4 ##t3seCD	
 $Ii&##t3seMN	
 	
 	(, +,Jj$'##t3se?@	
   ##t3se4SE:	
 	s   6C   	C-	C((C-c                   t        | dd      rt        | j                        }|j                         s3t	        d| t
        j                         t        j                  d       	 |j                  dd      5 }t        j                  |      }ddd       j                  d
      |j                  d      |j                  d      |j                  d      |j                  d      |j                  d      |j                  d      dS 	 t        | dd      rt        j                  | j                        nd}t        | dd      rt        j                  | j                        nd}t        | d
d      t        | dd      t        | dd      t        | dd      t        | dd      dS # 1 sw Y   xY w# t        $ r>}t	        d	| t
        j                         t        j                  d       Y d}~`d}~ww xY w# t        $ r=}t	        d| t
        j                         t        j                  d       Y d}~d}~ww xY w)u\  Construct pinning dict from --config JSON OR individual CLI flags.

    Returns dict with 7 keys (G4.3 pinning block):
      pid / scene_index_list / shot_index_list_per_scene / model_routing /
      prompt_source_mode / card_commit_hash / id_policy_card_snapshot_hash

    NO silent defaults — caller must run validate_pinning() afterwards.
    configNz.[g4_3_common] ERROR: --config file not found: file   rutf-8encodingz+[g4_3_common] ERROR: --config parse error: pidscene_index_listshot_index_list_per_scenemodel_routingprompt_source_modecard_commit_hashid_policy_card_snapshot_hashr6   r7   r8   r9   r:   r;   r<   z:[g4_3_common] ERROR: failed to parse list/dict JSON args: )getattrr   r.   existsprintsysstderrexitopenjsonloadr%   r   loadsr7   r8   )argscfg_pathfpcfgr*   r7   r8   s          r   load_pinning_from_argsrL      s    tXt$$ @
KZZ HHQK	sW5iim 6 775> #(: ;),1L)M WW_5"%''*>"? #(: ;,/GG4R,S
 	
 t/6 JJt,,- 	 t8$? JJt556 	" tUD),%> =%d,@$G#D*<dC(/6TVZ([ ? 65 	?uECJJWHHQKK	.  HN	
 	sI   'F6 :F)F6 AH  )F3.F6 6	G=?3G88G= 	I	3IIc                   d}|D cg c]  }| j                  |      dv s| }}|r4t        d| dt        j                         t        j                  d       t        | d   t              s0t        dt        j                         t        j                  d       t        | d	   t              s1t        d
t        j                         t        j                  d       yyc c}w )z8Fail-fast on missing pinning fields. NO silent defaults.r=   )N z8[g4_3_common] ERROR: pinning missing required field(s): zF. Provide via --config <json> or individual flags. NO silent defaults.r/   r1   r7   z?[g4_3_common] ERROR: pinning.scene_index_list must be list[int]r8   zC[g4_3_common] ERROR: pinning.shot_index_list_per_scene must be dictN)r   r@   rA   rB   rC   r   r   r   )pinningrequiredkmissings       r   validate_pinningrS      s    H #C(Qgkk!n
&Bq(GCFwi PS T	

 	g0148M	
 	g9:DAQ	
 	 B Ds
   C$C$c           	     ~   t               }| d   }| d   D ]$  }t        |      }|j                  t        |            }||j                  |      }|4t	        d| dt
        j                         t        j                  d       t        |t              sJt	        d| dt        |      j                   d	t
        j                         t        j                  d       |s4t	        d| d
t
        j                         t        j                  d       |D ]  }|j                  |t        |      f         ' |S )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 non-list / empty list) is a
    contract violation, not a silent default.
    r8   r7   z=[g4_3_common] ERROR: pinning.scene_index_list contains scene z? but shot_index_list_per_scene has no entry. NO silent default.r/   r1   z7[g4_3_common] ERROR: pinning.shot_index_list_per_scene[z] must be list[int], got .u4   ] is empty — pinned scene must enumerate >=1 shot.)setintr   strr@   rA   rB   rC   r   r   type__name__add)rO   outshot_mapr'   si_int	raw_shotsr(   s          r   build_pinning_scene_setr`      s-    !$C23H()RLLV-	 V,IO( %& ZZ	 HHQK)T*84T)_5M5M4NaQZZ
 HHQK8OQZZ
 HHQKCGGVSX&' 7 *: Jr   c                   | dz  }|j                         s3t        d| t        j                         t        j                  d       	 |j                  dd      5 }t        j                  |      cddd       S # 1 sw Y   yxY w# t        $ r=}t        d	| t        j                         t        j                  d       Y d}~yd}~ww xY w)
zELoad `manifest.json` under cp_root. fail-fast on missing/parse error.zmanifest.jsonz0[g4_3_common] ERROR: manifest.json not found at r/   r1   r2   r3   r4   Nz0[g4_3_common] ERROR: manifest.json parse error: )	r?   r@   rA   rB   rC   rD   rE   rF   r%   )cp_rootmanifest_pathrJ   r*   s       r   load_cp_manifestrd     s    o-M!>}oN	
 	g6"99R= 766 >seD	
 	s6   
B A<2	B <BB B 	C3C		Cc                    | j                  d      }t        |t              sy|j                  d      }t        |t              sy|j                  d      dk(  ry|j                  d      d	u ryy
)u  Return (cf_flag, error_message).

    Reads `scene["render_prompt_card"]["background_binding"]`. The G4.2 lift
    stores close-framing flag as either:
      - bb["mode"] == "skipped_close_framing", OR
      - bb["close_framing_skips_background_ref"] is True

    error_message is None on success; a human-readable string when CP shape is
    malformed (caller appends to measurement_failures — NO silent absorb).
    r    )Fz&render_prompt_card missing or not dictbackground_binding)Fz9render_prompt_card.background_binding missing or not dictmodeskipped_close_framing)TN"close_framing_skips_background_refT)FN)r   r   r   )r&   r)   bbs      r   is_close_framingrk     sh     ))(
)Cc4 @	%	&Bb$S	vvf~00	vv23t;r   )returnr   )r   r   r   r   rl   list | None)
r&   r   r'   
int | Noner(   rn   r   r   rl   rm   )rH   r   rl   r   )rO   r   rl   None)rO   r   rl   zset[tuple[int, int]])rb   r   rl   r   )r&   r   rl   ztuple[bool, str | None])__doc__
__future__r   rE   rA   pathlibr   typingr   __file__resolveparents_BACKEND_PATHrX   pathinsertr   r   r,   rL   rS   r`   rd   rk    r   r   <module>r{      s    #  
   X&&(003i?}SXX%HHOOAs=)*2"/// 
/ 	/
 /d9xB&R(r   