
    i%&                    J   U d Z ddlmZ ddlZddlmZmZmZ  eh d      Z	de
d<    G d d	e      Zdd
Z ej                  d      Z ej                  d      ZddZ ej                  d      ZddZ eh d      Zde
d<    eddh      Zde
d<    G d de      ZddddZy)u/  D6 controlled vocab — bg_id regex + state_class enum + format helper +
location space_profile validator.

spec: docs/superpowers/specs/2026-05-09-deterministic-bg-id-and-catalog-lineage.md §4.1, §4.3, §4.4
plan: docs/superpowers/plans/2026-05-09-d6-deterministic-bg-id-and-catalog-lineage-implementation.md T1, T2-fix

LLM 은 bg_id 를 만들 수 없음. master_plan post-processing 에서 코드가 부여.
두 정규식이 다른 step (raw vs assigned) 에 적용:
- BG_ID_RE: code-assigned (`L09B01` uppercase) — strict.
- LLM_INTENT_ID_RE: LLM raw intent (fp_id 등 lowercase snake) — strict.

location.space_profile (entity_canon.metadata_json) 의 controlled vocab + 검증:
- LOCATION_SPACE_KEY_VOCAB: allowed_space_keys 의 enum.
- validate_location_space_profile: shape + enum 검증. fail-fast.
    )annotationsN)AnyDict	FrozenSet>   busyquietnormalarrival	busy_exit	intrusion	ransackedblood_sceneclean_afterevidence_displaydream_or_vision_statezFrozenSet[str]STATE_CLASS_ENUMc                      e Zd ZdZy)StateClassErroru?   state_class 가 enum 밖 — fail-fast (nearest-match 안 함).N__name__
__module____qualname____doc__     M/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/bg_state_vocab.pyr   r   )   s    Ir   r   c                R    | t         vrt        d| dt        t                d      y)u   Raise StateClassError if state_class is not in STATE_CLASS_ENUM.

    LLM prompt 에 enum 강제. 위반 시 master_plan retry. nearest-match 시도하면
    drift 의 silent path 가 열리므로 fail-fast.
    zstate_class z not in enum (size=uR   ). LLM prompt 의 enum 강제 violation 또는 nearest-match 시도 — fail-fast.N)r   r   len)state_classs    r   validate_state_classr    -   sA     **;/)<SAQ=R<S T^ _
 	
 +r   z^L\d{2,3}B\d{2,3}$z^[a-z0-9][a-z0-9_]*$c                f    | dk  rt        d| d      |dk  rt        d|d      d| dd|dS )	u  deterministic bg_id 생성.

    spec §4.5: variant numbering 은 1-based. ``L00B00`` 같은 0-prefix ID 는
    `_max_b_for_loc` history 에서 발생할 수 없는 sentinel — entity_canon 의
    short_id (L01+) 와 monotonic next_b (1+) 모두 ≥1 이므로 0 입력은
    invariant 위반.

    99 까지 2-digit zero-pad, 100+ 자연 확장 (`L100B100`). BG_ID_RE 의 모든
    출력은 정규식 pass — `test_format_bg_id_output_passes_bg_id_regex` 가 invariant.
       zloc_num u]    invalid — must be ≥1 (spec §4.5 1-based numbering, entity_canon short_id starts at L01)zvar_num uV    invalid — must be ≥1 (spec §4.5 1-based numbering, _max_b_for_loc + 1 monotonic)L02dB)
ValueError)loc_numvar_nums     r   format_bg_idr)   L   si     {wk "3 4
 	
 {wk ", -
 	
 wsm1WSM**r   z
^L\d{2,3}$c                    t         j                  |       st        d| d      t        | dd       }|dk  rt        d| d      |S )u   `L09` → 9. invariant: short_id matches `^L\d{2,3}$` AND value ≥1.

    spec §4.5: location short_id 는 entity_extractor 가 1-based 발급. ``L00`` 은
    아무 entity 도 가질 수 없는 sentinel — 입력 시 invariant 위반.
    zlocation short_id u"    invalid — must match ^L\d{2,3}$r"   NuU    invalid — loc_num must be ≥1 (spec §4.5 1-based, entity_canon never issues L00))_LOC_SHORT_ID_REmatchr&   int)short_idnums     r   parse_loc_numr0   g   sl     !!(+ ,QR
 	
 hqrl
C
Qw  -B C
 	
 Jr   >   mainyardofficestairskitchenrooftopexteriorLOCATION_SPACE_KEY_VOCABsingle_spacemulti_spaceLOCATION_SPACE_KIND_VOCABc                      e Zd ZdZy)SpaceProfileErroru>   location.space_profile shape 또는 enum 위반 — fail-fast.Nr   r   r   r   r=   r=      s    Hr   r=    )r.   c                  |rd|nd}t        | t              s#t        | dt        |       j                         | j                  d      }t        |t              st        | d|      |j                  d      }t        |t              st        | d|      |j                  d      }|t        vr t        | d|d	t        t                     |j                  d
      }t        |t              r|st        | d|      |D cg c]  }t        |t              r| }}|r5t        | d|d|D cg c]  }t        |      j                   c} d      |D cg c]  }|t        vs| }	}|	r t        | d|	 dt        t                     |dk(  r|dgk7  rt        | d|       |dk(  r*|j                  d      }
|
|
|vrt        | d|
d|       |S c c}w c c}w c c}w )u  `metadata_json.location.space_profile` shape 검증 + 정규화 dict 반환.

    spec §4.3: D6 의 SOT. master_plan 후처리 의 normalize_space_key 가 본 shape
    에 의존. 누락/invalid 시 모든 bg_id 부여 fail → silent default '{}' 차단.

    invariants:
        - metadata_json: dict
        - metadata_json["location"]["space_profile"]: dict
        - kind: LOCATION_SPACE_KIND_VOCAB
        - allowed_space_keys: non-empty list, subset of LOCATION_SPACE_KEY_VOCAB
        - single_space → allowed_space_keys == ["main"]
        - multi_space → default_space_key in allowed_space_keys

    Returns: 검증 통과한 space_profile dict (호출자가 store/forward 가능).
    z	location locationz metadata_json not a dict: z- metadata_json.location missing or not dict: space_profilez; metadata_json.location.space_profile missing or not dict: kindz space_profile.kind z not in allowed_space_keysz> space_profile.allowed_space_keys must be non-empty list, got zP space_profile.allowed_space_keys must contain only strings, got non-str items: z	 (types: )zJ space_profile.allowed_space_keys contains keys outside controlled vocab: z. vocab=r9   r1   z; single_space requires allowed_space_keys == ['main'], got r:   default_space_keyzU multi_space requires default_space_key in allowed_space_keys, got default_space_key=z
, allowed=)
isinstancedictr=   typer   getr;   sortedliststrr8   )metadata_jsonr.   where	loc_blockprofilerB   allowedknon_str_itemsinvalid_keysdefault_keys              r   validate_location_space_profilerV      s     )1i|$jEmT*g0m1D1M1M0NO
 	
 !!*-Ii&gB9-P
 	
 mmO,Ggt$gPQXP[\
 	
 ;;vD,,g)$&AZ:[9\]
 	
 kk./Ggt$GgST[S^_
 	

 !(B1z!S/AQMBg ""/!2)*78-QQ  -89<
 	

  'Lw!!3K*KAwLLg !!- /4568
 	

 ~'fX"5gPQXPYZ
 	
 }kk"56+W"<#' ))4z'L  N3 C
 9Ls   G(%G(<G-&G24G2)r   rL   returnNone)r'   r-   r(   r-   rW   rL   )r.   rL   rW   r-   )rM   r   r.   rL   rW   zDict[str, Any])r   
__future__r   retypingr   r   r   	frozensetr   __annotations__r&   r   r    compileBG_ID_RELLM_INTENT_ID_REr)   r+   r0   r8   r;   r=   rV   r   r   r   <module>ra      s    # 	 ' ' $- . $ . Jj J

& 2::+,
 2::56 +0 2::m, 2 ,5 6 , .  -6~}6U,V > VI
 I LN Dr   