
    \jkO                        d Z ddlmZ ddlZddlmZmZmZmZm	Z	m
Z
mZ ddlmZ  ej                  e      Z G d de      ZddZdd	Zdd
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  Reference attachment contract validator — D5 identity-level + Area #5 sidecar edition.

spec: docs/superpowers/specs/2026-05-09-attached-reference-identity-contract-design.md §4.4
      docs/superpowers/specs/2026-05-18-area-5-reference-phrase-phantom-guard-replacement-design.md (Area #5 v1)
plan: docs/superpowers/plans/2026-05-09-attached-reference-identity-contract-implementation.md Task 3
      docs/superpowers/plans/2026-05-18-area-5-reference-phrase-phantom-guard-replacement-implementation.md

required_refs 의 (kind, id) 가 attached_meta 에 exact match 로 존재해야 통과.
substring/text 매칭 폐기. P1 (label 추론 X) + P2 (fallback meta 위조 X).

핵심 contract (D5):
- character_outlook required → attached_meta 에 ("character_outlook", id) exact 존재.
  base "character" / "character_state" 는 만족 X (close_framing 무관).
- character required (FINDING 9 W2 Cat2 — base_id_required subject) → attached_meta
  에 ("character", id) exact 존재. "character_outlook" 은 만족 X. (P8 Inc2-b:
  "character_state" 는 같은 캐릭터의 state-variant ref 이므로 sid prefix 일치 시 만족.)
- background required + NOT close → attached_meta 에 ("background", bg_id) exact
  존재 또는 ("background_prev_shot", chain_bg_lookup(bg_id)) lineage 일치.
  chain_bg_lookup(bg_id)=None 이면 lineage 확인 불가 = missing.
- readiness_policy=block_if_missing + required_refs empty → drift fail-fast.
- meta length mismatch → 즉시 fail-fast (invariant 1).
- sidecar phantom guard (Area #5 v1): reference_phrase_kinds (per-variation, producer
  emit) 의 각 kind 가 attached_meta 의 kind set 과 strict exact compare. legacy
  prompt-prose classifier + window-based heuristic 폐기. step 4 의 close-framing
  background skip 은 보존하지만 step 6 은 strict (axis 분리).

D2 strict extractor 그대로 보존 (사용자 binding G1) — `or {}` silent fallback 금지.
    )annotationsN)AnyCallableDictListOptionalSetTuple)AppErrorc                  $     e Zd ZdZd fdZ xZS )RefContractErroru   Required ref 미첨부 또는 from-the-reference guard 위반 — generate 차단.

    HTTP 422 (validation error). caller 가 retry 1 회 (var_t2i rebuild) 또는 propagate.
    c                *    t         |   d|d       y )Nzref_contract.violationi  )codemessagestatus_code)super__init__)selfdetail	__class__s     U/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/ref_contract_validator.pyr   zRefContractError.__init__.   s    ) 	 	
    )r   str)__name__
__module____qualname____doc__r   __classcell__)r   s   @r   r   r   (   s    

 
r   r   c                b    | D ch c]  \  }}|	 c}}t        fd|D              S c c}}w )uY   attached_meta set 안에 지정 kind 가 하나라도 있는지 (D5 §4.4 phantom guard).c              3  &   K   | ]  }|v  
 y w)N ).0kindattached_kindss     r   	<genexpr>z!_meta_has_kind.<locals>.<genexpr>>   s     8%$t~%%s   )any)attached_setkindsk_r$   s       @r   _meta_has_kindr+   ;   s1    $01LDAqaL1N8%888 2s   +c           
        | i S t        | t              r| si S i }| j                         D ]  \  }}t        |t              s$t	        dt        |      j                   d|      t        |t              s't	        d|dt        |      j                   d|      |D ]9  }t        |t              rt	        d|dt        |      j                   d|       t        |      ||<    |S t        | t              rg| si S i }t        |       D ]P  \  }}t        |t              s't	        d| dt        |      j                   d|      |j                  d	      }|j                  d
      }t        |t              r|s't	        d| dt        |      j                   d|      t        |t              r|s't	        d| dt        |      j                   d|      |j                  d      }	t        |	t              s't	        d| dt        |	      j                   d|	      |	dk7  rt	        d| d|	      |j                  |g       j                  |       S |S t	        dt        |       j                   d|       )u  required_refs canonical normalization — manifest list shape ↔ dict shape.

    Two accepted shapes (D2):
      - manifest canonical (list of dicts):
          [{"kind": "character_outlook", "id": "C01O02", "policy": "required"}, ...]
      - legacy dict:
          {"character_outlook": ["C01O02"], "background": ["L01"]}

    Returns dict[kind, list[id]]. 빈/None → {}.

    Iter1+iter2 BLOCKING (Codex review): malformed input 은 silent drop 금지 —
    explicit []/{}/None 은 허용하되, wrong type / list item non-dict /
    kind|id 누락 / dict value non-list / id non-string / policy drift 는
    RefContractError fail-fast (silent fallback 회로 차단, 사용자 binding G1).
    z:required_refs malformed: dict key must be str (kind), got =z-required_refs malformed: dict value for kind=z must be list, got z2required_refs malformed: dict value list for kind=z must contain str ids, got z#required_refs malformed: list item[z] must be dict, got r#   idz$] 'kind' must be non-empty str, got z"] 'id' must be non-empty str, got policyz9] 'policy' must be str (only 'required' is allowed), got requiredz<] 'policy' must be exactly 'required' (producer drift), got z3required_refs malformed: must be list or dict, got )
isinstancedictitemsr   r   typer   list	enumerateget
setdefaultappend)
raw
normalizedr)   ventryidxitemr#   ridr/   s
             r   _normalize_required_refsrA   A   s     {	#tI+-
IIKDAqa%&7++,AaU4  a&&CA5 I$$(G$4$4#5Qqe=  !%-*  !u %#E{334AeY@   !GJqM%  & #tI
"3ICdD)&9# ?:../q:  88F#D((4.CdC(&9# ?--1$Z-@-@,A4(L  c3's&9# ?--1#Y-?-?,@#J  XXh'Ffc*&9# ?<0016*> 
 #&9# ?CCI*N  !!$+2237= (> 
Cy!!"!C7	, r   )chain_bg_lookupc                  t        |      t        |      k7  r$t        dt        |       dt        |       d      | i }d}nt        | t              s$t        dt	        |       j
                   d|       d| vrt        d      | j                  d      }	|	t        d	      t        |	t              s$t        d
t	        |	      j
                   d|	      d|	vrt        d      |	j                  d      }
|
t        d      t        |
      }|	j                  d      }t        |      }|j                  dg       D ]M  }d|f|v r
|j                  dd      d   t        fd|D              r4t        d|dt        |       d       |j                  dg       D ]#  }d|f|vs
t        d|dt        |       d       |j                  dg       D ]8  df|v r
t        fd|D              rt        ddt        |       d       |sddlm} ddlm} t        d  |D              }t        d! |D              }|j                  d"g       D ]j  }d"|f|v r
|r|r|r ||      nd}|rd#|f|v r%|j!                  |xs d$      r |d%d&d$|gd'|d()      t        d*|d+|d,|d-t        |              |d.k(  r$t        |j#                               st        d/      |t        d0      t        |t$              s$t        d1t	        |      j
                   d|      d2}|D ]  }||vrt        d3|d4| d5      |dk(  r&t'        |d6      st        d7|d8t        |             |d"k(  r&t'        |d9      st        d7|d:t        |             |dk(  sut'        |d;      rt        d7|d<t        |              y)=ui  spec D5 §4.4 + Area #5 v1 — identity-level required_refs vs attached_meta +
    prev_shot lineage + readiness_policy + meta length invariant + sidecar phantom guard.

    위반 시 RefContractError raise (fail-fast). HTTP 422.

    검사 순서:
      1. invariant 1 — len(labeled_refs) == len(attached_meta).
      2. rpc shape strict (D2 보존, 사용자 binding G1 — `or {}` 금지).
      3. character_outlook strict — ("character_outlook", cid) exact match.
         close_framing 분기 없음 (사용자 binding G2).
      3a/3b. prop / character (base_id_required) strict subset — ("prop", id) /
         ("character", id) exact match. close_framing 무관.
      4. background — NOT close framing 시 ("background", bg_id) exact 또는
         chain_bg_lookup(bg_id) 가 loc_id 반환 시 ("background_prev_shot", loc_id)
         lineage 일치. chain_bg_lookup=None / lookup 결과 None 이면 lineage
         확인 불가 = missing (사용자 binding G3).
      5. readiness_policy=block_if_missing consistency (required_refs empty 시 drift).
      6. Sidecar phantom guard (Area #5 v1) — `reference_phrase_kinds` (producer
         emit) 의 각 kind 가 `attached_meta` 에 해당 kind 1+ 있는지 strict exact
         compare. close-framing 무관 (legacy 30/60자 window classifier 폐기,
         strict exact compare). `prompt` arg 보존 — error message context 용,
         step 6 안에서 prompt body read 0.

    `reference_phrase_kinds` keyword-only required (no default). 누락 시 TypeError.
    None / non-list / invalid enum value 는 step 6 안에서 RefContractError fail-fast.
    z#meta length mismatch: labeled_refs=z attached_meta=u    (D5 §2.3 invariant 1)Nz!rpc malformed: must be dict, got r-   asset_requirementsuc   rpc malformed: 'asset_requirements' field missing — RenderPromptCard contract requires this fieldup   rpc malformed: 'asset_requirements' is None — asset_requirements must include explicit required_refs: [] or {}z6rpc malformed: 'asset_requirements' must be dict, got required_refsun   rpc malformed: 'asset_requirements.required_refs' field missing — use explicit [] or {} for no-required-refsuh   rpc malformed: 'asset_requirements.required_refs' is None — use explicit [] or {} for no-required-refsreadiness_policycharacter_outlookO   r   c              3     K   | ]8  \  }}|d k(  xr* t        |t              xr |j                  dd      d   k(   : ywcharacter_state:rI   r   Nr1   r   split)r"   r)   r<   base_cids      r   r%   z)validate_attached_refs.<locals>.<genexpr>   sW      
 %1 "" /1c"/Q"h./ %   >Azrequired character_outlook u    missing — attached=ul    (base 'character' ref 는 충족 X; 같은 캐릭터의 'character_state' 만 immobilized 대체로 충족)propzrequired prop z* (Tier 3 strict subset violation, Patch A)	characterc              3     K   | ]8  \  }}|d k(  xr* t        |t              xr |j                  dd      d   k(   : ywrK   rN   )r"   r)   r<   char_ids      r   r%   z)validate_attached_refs.<locals>.<genexpr>  sW      
 %1 "" .1c".Q"g-. %rQ   zrequired character u<    (base_id_required subject base ref 미첨부, FINDING 9 W2))BG_ID_RE)StaleUpstreamErrorc              3  v   K   | ]1  \  }}|d k(  xr# t        |t              xr |j                  d       3 yw)
backgroundzspace_set_bg:Nr1   r   
startswithr"   r)   r<   s      r   r%   z)validate_attached_refs.<locals>.<genexpr>?  s>      %
$1 V*Q"4Vo9VV$   79c              3  v   K   | ]1  \  }}|d k(  xr# t        |t              xr |j                  d       3 yw)rY   zoutdoor_canon:NrZ   r\   s      r   r%   z)validate_attached_refs.<locals>.<genexpr>G  s?      &
$1 W*Q"4WFV9WW$r]   rY   background_prev_shot background_renderu%   (unknown — validator fallback path)zvalidator detected D6 bg_id u[    missing from attached_meta (preflight bypass) — POST /steps/background_render?mode=force)upstreamexpected_bg_catalog_hashobserved_bg_catalog_hashmissing_in_renderremediationzrequired background z( missing (expected exact ('background', z) or ('background_prev_shot', u   )) — attached=block_if_missingzGrpc drift: readiness_policy=block_if_missing but required_refs is emptyum   reference_phrase_kinds missing — producer (scene_detail v26) emit required, no silent fallback (Area #5 v1)z4reference_phrase_kinds malformed: must be list, got )rS   rY   rR   z+reference_phrase_kinds invalid enum value: z (allowed: ))rS   rG   rL   z%reference_phrase_kinds declares kind=u-    but no character meta attached — attached=)rY   r_   u%    but no background meta — attached=)rR   u    but no prop meta — attached=)lenr   r1   r2   r4   r   r7   rA   setrO   r&   sortedapp.core.bg_state_vocabrV   app.core.errorsrW   matchvaluesr5   r+   )rpclabeled_refsattached_metapromptis_close_framingrB   reference_phrase_kindsr0   	readiness	asset_reqraw_required_refsr'   cidprop_idrV   rW   _space_set_bg_attached_outdoor_canon_attachedbg_idrequired_loc_ALLOWED_KINDSr#   rP   rU   s                         @@r   validate_attached_refsr      sc   J <C..1#l2C1D E /00GI
 	
 {)+#'	#t$"3DI4F4F3GqP   s*"@  GG01	"S  )T*"I//0)@  )+"I  &MM/:$"=  ,,=>MM"45	),]);L ||/4%599S!$Q' 
 %	
 
 )# 1|,- .,-
 	
 5, <<+GL0"  ,"<01 2<=  ,( <<R0!\1 
 %	
 
 !' -|,- .JK
 	
 1"  	56 "% %
$%
 "
 #& &
$&
 #

 \\,3Ee$4%& 6E?51$L!7 F, V~~ekr*(0-T-/',g6ui @7 8
 
 #&ui 0227 ;,,8+; <"<013 5 4D &&s8??3D/EU
 	
 %8
 	
 ,d3./889;Q:TV
 	
 9N&~%"=dX F+,A/  ;~O(
 #7x @..4\.B-CE  <@)
 #7x @%%+L%9$:<  6>.y"I"7x @"<013 + 'r   )r'   zSet[Tuple[str, str]]r(   zTuple[str, ...]returnbool)r:   r   r   zDict[str, List[str]])rp   zOptional[Dict[str, Any]]rq   zList[Tuple[str, bytes]]rr   zList[Tuple[str, str]]rs   r   rt   r   rB   z(Optional[Callable[[str], Optional[str]]]ru   z	List[str]r   None)r   
__future__r   loggingtypingr   r   r   r   r   r	   r
   rm   r   	getLoggerr   loggerr   r+   rA   r   r!   r   r   <module>r      s   8 #  B B B $ 
		8	$
x 
&9Qv AEE	!E)E )E 	E
 E >E &E 
Er   