
    :j;              
          U d Z ddlZddlZddlmZ ddlmZmZ ddlm	Z	  ej                  e      Z ej                  d      ZdZ eh d      Z ed	h      Zd
Z ed       G d d             Z ed       G d d             Z edddd       edddd       edddd      dZeeef   ed<   dedefdZddddededefdZdefdZdddee   d ededee   fd!Z d"ed#ee   defd$Z!ddd%ee   d&ededee   fd'Z"ddd%ee   d(ededee   fd)Z#dee   d ee   dedefd*Z$d+edefd,Z%d+ed-ededefd.Z&y)/uX  Area #1 — subject_reference_policy SOT helper.

Single source of truth for ID/outlook reference policy validation + default.
All consumers (render_prompt_card, visible_entities_validator) must call this
helper; consumer-local default 절대 금지.

Spec: docs/superpowers/specs/2026-05-16-area-1-id-outlook-reference-policy-sot-v1-design.md
    N)	dataclass)IterableOptional)AppErrorz^(C\d{2,3})(?:O\d{2,3})?$
subject_idpolicy_typepolicyreason>   base_id_requiredid_and_outlook_requiredgeneric_descriptor_allowedidentity_referencer   T)frozenc                   6    e Zd ZU eed<   eed<   eed<   eed<   y)SubjectReferencePolicyr   r	   r
   r   N)__name__
__module____qualname__str__annotations__     W/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/subject_reference_policy.pyr   r      s    OKKr   r   c                   :    e Zd ZU eed<   eed<   eed<   dZeed<   y)IdUsageRulebase_requiredoutlook_requiredoutlook_forbiddenFbase_forbiddenN)r   r   r   boolr   r    r   r   r   r   r   '   s     ND r   r   F)r   r   r   r    )r   r   r   _POLICY_TO_ID_USAGEr
   returnc           	      f    | t         vr!t        dd| dt        t               d      t         |    S )NDstep.contract_violation.subject_reference_policy.invalid_enum_policyzinvalid policy z (allowed: )codemessage)r"   r   sortedALLOWED_POLICIES)r
   s    r   policy_to_id_usage_ruler,   ?   sG    ((W!& ,#$456a9
 	
 v&&r    warn_on_outlookwherer/   r0   c                    t        | t              rt        j                  |       st	        dd| d|       | j                  d      d   }|r d| v rt        j                  dd|| |d	
       |S )NzIstep.contract_violation.subject_reference_policy.invalid_subject_id_shapezinvalid subject_id z (expected C## or C##O##) r'   Or   zFsubject_reference_policy: outlook_id form detected, normalized to basez.subject_reference_policy.outlook_id_normalized)eventr0   raw_subject_idnormalized_subject_id)extra)
isinstancer   _CHAR_ID_REmatchr   splitloggerwarning)rawr/   r0   bases       r   normalize_subject_idr?   K   s     c3{'8'8'=\)#0J5'R
 	
 99S>!D3#:TI"%)-	 	 	
 Kr   c                     t               }| xs g D ]K  }t        |t              st        j	                  |      }|s,|j                  |j                  d             M |S )N   )setr7   r   r8   r9   addgroup)visible_entitiesidssidms       r   derive_visible_subject_idsrI   d   sS    
%C%2%c3!!#&A
#	 &
 Jr   )r0   itemsvisible_subject_idsc                d   | t        | t              s| S g }| D ]  }t        |t              r|j                  d      nd}t        |t              rNt
        j                  |      }|r7|j                  d      }||vr"t        j                  d|t        |      |       |j                  |        |S )u  FINDING 6 W4a — consumer-boundary normalization (render_prompt_card path).

    shot_staging 이 emit 한 subject_reference_policy items 중 subject 가
    shot-level visible subject set 에 없는 항목을 deterministic drop 한다.
    `shot_director.visible_entity_ids` 가 SOT — depicted-but-not-physically-
    present subject (사진/CCTV/반사 묘사물) 이 정책을 들고 있어
    `normalize_subject_reference_policy_items` 가 `unknown_subject` 로 raise
    하던 것을 차단한다.

    drop 대상 = well-formed C##(O##) subject 이면서 base 가
    `visible_subject_ids` 에 없는 item 만. malformed / shape-invalid / non-dict
    item 은 **보존** — 직후 `normalize_subject_reference_policy_items` 가
    fail-fast 하도록 (silent fallback 아님 — 알려진 non-visible 만 정규화).

    items 가 None / non-list 면 그대로 pass-through (caller validator 가 처리).
    Nr   rA   zYsubject_reference_policy: dropping policy for non-shot-visible subject %s (visible=%s) %s)r7   listdictgetr   r8   r9   rD   r;   r<   r*   append)rJ   rK   r0   keptitemrG   rH   r>   s           r   *filter_subject_reference_policy_to_visiblerS   n   s    , }Jud3D(24(>dhh|$Dc3!!#&Awwqz22NNFf%895
 D  Kr   textbase_required_basesc                    | r|s| S | }|D ]}  }t        |t              st        j                  |      }|r|j	                  d      |k7  r@t        j                  dt        j                  |       d      }|j                  ||      } |S )u  FINDING 6 W4b — base_id_required subject 의 outlook-form deterministic
    canonicalization.

    `base_id_required` 정책 subject 는 outlook 복합 ID `C##O##` 사용이
    금지된다 (`visible_entities_validator` 의 `outlook_forbidden`). scene_detail
    gemini-pro 가 t2i_prompt 에 outlook form 을 쓰면 위반이므로, code 가
    `base_id_required` base 에 한해 정확히 `C<base>O<digits>` occurrence 를
    base form `C<base>` 로 deterministic 치환한다.

    base 별 exact word-boundary regex (`\bC##O\d{2,3}\b`) — base 가 아닌
    다른 character ID, prose 숫자, `O` 없는 ID (`C0702` 등) 는 무영향.
    `base_id_required` 가 아닌 subject 의 outlook form 은 caller 가
    `base_required_bases` 에 안 넣으므로 보존된다 (전역 strip 아님).

    base 가 순수 `C##` form 이 아니면 (malformed) 해당 base 는 skip (no-op).
    rA   z\bz
O\d{2,3}\b)	r7   r   r8   r9   rD   recompileescapesub)rT   rU   outr>   rH   patterns         r   +canonicalize_base_id_required_outlook_formsr]      s    ( *
C#$$d#AGGAJ$& **299T?"3<@Akk$$ $ Jr   	raw_itemsoffscreen_referencedc                \   t        | t              s| S |s| S | D cg c]  }t        |t              rt        |      n|! }}i }|D ]Z  }t        |t              st        |j                  d      t              s4|j                  |d   j                  d      d   |       \ t        |j                               D ]i  \  }}|j                  d      d   }|j                  |      }	|	|j                  |dd|xs dd       H|	j                  d      d	k(  s]d|	d<   | d
|	d<   k |S c c}w )u'  C10 Phase 1 — screen-presence detector 가 off-screen-referenced 로
    플래그한 subject 의 identity policy 를 `generic_descriptor_allowed` 로
    inject/override.

    Override rule:
      - 기존 entry 없음            → generic_descriptor_allowed inject;
      - id_and_outlook_required    → generic_descriptor_allowed 다운그레이드
                                     (explicit producer 정책 override —
                                     reason 에 provenance 기록);
      - base_id_required           → 보존 (의도적 partial-frame 정책);
      - generic_descriptor_allowed → 보존 (이미 정합).

    `raw_items` 가 list 아니면 그대로 반환 — `normalize_subject_reference_
    policy_items` 가 fail-fast 처리. malformed dict item 도 보존.
    r   r2   r   r   r   screen_presence_reconciliationr   r
   r   z, | overrode explicit id_and_outlook_requiredr   
r7   rM   rN   rO   r   
setdefaultr:   r*   rJ   rP   )
r^   r_   r0   itrJ   by_baserG   r   r>   existings
             r   apply_screen_presence_downgraderg      s;   * i&@IJ	"B-T"X25	EJGb$Jrvvl/CS$Ir,/55c:1=rB  288:;Vyy~a ;;t$LL"36 D$D	  \\(#'@@!=HX(FG X < L) K   $D)text_only_subjectsc                \   t        | t              s| S |s| S | D cg c]  }t        |t              rt        |      n|! }}i }|D ]Z  }t        |t              st        |j                  d      t              s4|j                  |d   j                  d      d   |       \ t        |j                               D ]i  \  }}|j                  d      d   }|j                  |      }	|	|j                  |dd|xs dd       H|	j                  d      d	k(  s]d|	d<   | d
|	d<   k |S c c}w )u<  Phase 2 — episode_reference_policy manifest 가 text_only 로 판정한
    subject 의 identity policy 를 `generic_descriptor_allowed` 로 다운그레이드.

    `apply_screen_presence_downgrade` 와 동일 override 규칙:
      - 기존 entry 없음            → generic_descriptor_allowed inject;
      - id_and_outlook_required    → generic_descriptor_allowed 다운그레이드;
      - base_id_required           → 보존 (의도적 partial-frame 정책 우선 —
                                     이 subject 는 required_ref 를 받아
                                     Phase 1 materialization 보호 대상);
      - generic_descriptor_allowed → 보존.

    text_only_subjects = {base C## subject_id: reason}.
    `raw_items` 가 list 아니면 그대로 반환 (caller validator 가 처리).
    r   r2   r   r   r   z"episode_reference_policy:text_onlyr   r
   r   z> | overrode id_and_outlook_required (episode_reference_policy)r   rb   )
r^   ri   r0   rd   rJ   re   rG   r   r>   rf   s
             r   (apply_episode_reference_policy_downgraderk      s?   ( i&@IJ	"B-T"X25	EJGb$Jrvvl/CS$Ir,/55c:1=rB  06689Vyy~a ;;t$LL"36 H$H	  \\(#'@@!=HX( - . X :  L+ Krh   c                   | i S t        | t              s&t        ddt        |       j                   d|       | si S i }i }t        |       D ]  \  }}t        |t              s)t        dd| dt        |      j                   d|       t        D cg c]	  }||vs| }}|rt        dd| d|d	t         d
|       t        D ]D  }t        ||   t              rt        dd| d| dt        ||         j                   d|        |d   t        vr)t        dd| d|d   dt        t               d
|       |d   t        vr)t        dd| d|d   dt        t               d
|       |d   j                         st        dd| d|       t        |d   d| d| d      }	|j                  |	g       j                  |d          |	|v rt        dd|	d||	   d
|       t!        |	|d   |d   |d         ||	<   ||	|vst        d d!|	d"t        |      d|        |S c c}w )#Nz=step.contract_violation.subject_reference_policy.invalid_typezitems must be list, got  r'   zitem[z] expected dict, got z>step.contract_violation.subject_reference_policy.invalid_shapez] missing keys z (required: z) z].z expected str, got r	   zIstep.contract_violation.subject_reference_policy.invalid_enum_policy_typez].policy_type z invalid (v1 allowed: r
   r%   z	].policy z invalid (allowed: r   z].reason empty string r   Tz.item[]r.   zBstep.contract_violation.subject_reference_policy.duplicate_subjectz duplicate normalized subject_id z (raw forms: r   z@step.contract_violation.subject_reference_policy.unknown_subjectzsubject z not in visible_subject_ids )r7   rM   r   typer   	enumeraterN   REQUIRED_KEYSr   ALLOWED_POLICY_TYPESr*   r+   stripr?   rc   rP   r   )
rJ   rK   r0   resultraw_by_baseidxrR   kmissingr>   s
             r   (normalize_subject_reference_policy_itemsry     s!    }	eT"P.tE{/C/C.DAeWM
 	
 	FKu%	T$%TC5 5Dz**+1UG5  ,=mq}1m=UC5{ ;""/5';  Ad1gs+XuBqc)<Q=112!E7<   &::`C5tM/B.E F,,23G,H+IE7T  >!11[C5	$x.); <!!'(8!9 :"UGE  H~##%Uu$:5'B 
 $ G6#a(

 	tR(//\0BC6>Y6th ?##.t#4"7r%B  .]+>>	
t *t;N/NWth&B125Qug? O &^ MM >s   	H? H?
policy_mapc           	          t        | j                         d       D cg c]4  \  }}|j                  |j                  |j                  |j
                  d6 c}}S c c}}w )Nc                     | d   S )Nr   r   )kvs    r   <lambda>z8serialize_subject_reference_policy_map.<locals>.<lambda>  s    1r   )keyr   )r*   rJ   r   r	   r
   r   )rz   _pols      r   &serialize_subject_reference_policy_mapr     sb     Z--/5EF GFAs ..??jjjj		
 G  s   9Ar   c                V    t        |d|      }|| v r| |   S t        |dt        d      S )NFr.   r   u,   (default — omitted from shot_staging emit)r   )r?   r   DEFAULT_POLICY)rz   r   r0   r>   s       r   'get_subject_reference_policy_or_defaultr     s>      
EODz$!(=	 r   )'__doc__loggingrW   dataclassesr   typingr   r   app.core.errorsr   	getLoggerr   r;   rX   r8   rq   	frozensetr+   rr   r   r   r   r"   rN   r   r   r,   r!   r?   rB   rI   rM   rS   r]   rg   rk   ry   r   r   r   r   r   <module>r      s    	 ! % $			8	$bjj56A   
 !"6!78 * $   $! ! !  +TU  $Ud #.et#/ T#{*+  	'C 	'K 	' "	  	
 	2C  	'D>'' 	'
 d^'T!
!!#! 	!P 	-~-- 	-
 d^-h 	-~-- 	-
 d^-`bD>b "#b 	b
 
bJ	t 	 	 	
 r   