+
    Wj.                    $   R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIHt . RIOt	. RJOt
]P                  P                  R]	4      R 4       t]P                  P                  R]
4      R	 4       tR
 tR tR tRRRRRRRR. RR/RRRRRRR.R. R. R. /R R!^R"^R#R$/R"R%RR&R'R(R)R*R+R,R-/.R.R/R0R1R$R2R3R3R4R5R/./tR6R7R8R9R:R;R</.R=R>/tRKR? ltR@ tRA tRB tRC t]P                  P3                  ]P4                  P7                  RD4      RE8g  RFRG7      RH 4       tR# )LuX  Entity Dossier card tests — the explicit-ask intercept, the mention
auto-show, the /profile fetch + emit contract (voice/entity_show.py), and the
HUD→engine contract (control/entity_action.json, applied by
ControlWatcher._entity_action). The JSON payloads here are EXACTLY what the
Mac HUD's EntityPanel writes, so these tests pin the contract between the
Swift card and the Python engine. No network, no HUD, no audio (fakes).

Run:            .venv/bin/python -m pytest test_entity_card.py -q
Live smoke:     LIVE=1 .venv/bin/python -m pytest test_entity_card.py -q -k live
  (LIVE hits the real memory service /profile AND writes a real
   control/entity_action.json so a RUNNING Jarvis pops the card on screen —
   the eyes-on proof that the whole chain renders. Run it after any deploy
   that touches the card, the endpoint, or the intercept.)
)annotationsN)entity_showAINReval Studiozutterance,expectedc                H  a . oV P                  \        R V3R l4       V P                  \        RR 4       \        P                  P                  4        \        P                  ! V4      pV'       d$   VP                  4       VP                  4       9   g   Q h\        P                  ! 4       R,           pS'       g3   \        P                  ! 4       V8  d   \        P                  ! R4       K:  S'       d+   VP                  4       S^ ,          P                  4       9   g   Q hR# )show_entityc                &   < SP                  V 4      # Nappendnfireds   &test_entity_card.py<lambda>.test_explicit_ask_fires_show.<locals>.<lambda>5       ell1o    mentioned_entitiesc                    . # r	    ts   &r   r   r   6       Rr          @{Gz?N)setattrr   	_COOLDOWNclearexplicit_asklowertimesleep)monkeypatch	utteranceexpectednamedeadliner   s   &&&  @r   test_explicit_ask_fires_showr(   2   s    E]4MN%9<H!##I.DHNN$

444yy{S H		h.

4X^^%q)99999r   r$   c                d  a . oV P                  \        R V3R l4       V P                  \        RR 4       \        P                  P                  4        \        P                  ! V4      p\
        P                  ! R4       V\        ^ ,          \        ^,          39   d   Vf	   S'       d   Q hR# R# )r   c                &   < SP                  V 4      # r	   r
   r   s   &r   r   4test_explicit_ask_ignores_non_asks.<locals>.<lambda>D   r   r   r   c                    . # r	   r   r   s   &r   r   r+   E   r   r   皙?N)r   r   r   r   r   r!   r"   NEGATIVE_CASES)r#   r$   resultr   s   && @r   "test_explicit_ask_ignores_non_asksr0   A   s    E]4MN%9<H!%%i0FJJsO ^A&q(9::~e++e ;r   c                  a . oV P                  \        RV3R l4       V P                  \        RR 4       \        P                  P                  4        \        P                  ! R4      R8X  g   Q h\
        P
                  ! 4       R,           pS'       g3   \
        P
                  ! 4       V8  d   \
        P                  ! R4       K:  SR.8X  g   Q hR	# )
uD   'show me saud' + known 'Saud Altamimi' → fires the canonical name.r   c                &   < SP                  V 4      # r	   r
   r   s   &r   r   8test_explicit_ask_prefers_known_entity.<locals>.<lambda>S   r   r   r   c                
    R R.# )SaudSaud Altamimir   r   s   &r   r   r3   U   s	    6?";r   zshow me saudr6   r   r   N)r   r   r   r   r   r!   r"   )r#   r'   r   s   & @r   &test_explicit_ask_prefers_known_entityr7   P   s    E]4MN%9;=!##N3FFFyy{S H		h.

4_%%%%r   c                r  a . oV P                  \        R V3R l4       V P                  \        RR 4       \        P                  P                  4        \        P                  ! R4      R8X  g   Q h\        P                  ! R4      R8X  g   Q h\
        P                  ! R4       \        S4      ^8X  g   Q hR# )r   c                &   < SP                  V 4      # r	   r
   r   s   &r   r   5test_explicit_ask_double_fire_guard.<locals>.<lambda>`   r   r   r   c                    . # r	   r   r   s   &r   r   r:   a   r   r   who is MohamedMohamedg333333?N)r   r   r   r   r   r!   r"   len)r#   r   s   &@r   #test_explicit_ask_double_fire_guardr?   ^   s    E]4MN%9<H!##$45BBB##$45BBBJJsOu:??r   c                L   V P                  \        RR 4       V P                  \        RR 4       \        P                  P                  4        \        P                  ! R4      R8X  g   Q h\        P                  P                  4        \        P                  ! R4      e   Q hR# )	zmain.py's integration point: handle_utterance returns the fired name on
an explicit ask (so the brain gets the 'don't recite' note), None otherwise.r   c                    R # r	   r   r   s   &r   r   Dtest_handle_utterance_returns_name_on_explicit_ask.<locals>.<lambda>m   s    dr   r   c                    . # r	   r   r   s   &r   r   rC   n   r   r   r<   r=   let's push the code nowN)r   r   r   r   handle_utterance)r#   s   &r   2test_handle_utterance_returns_name_on_explicit_askrG   j   s     ]NC%9<H!''(89YFFF!''(ABJJJr   entitykeyainr&   etypethingaliases
created_atz2026-07-01T00:00:00+00:00summaryzAIN is Reval Studio's client.read quickphones
0544067114emailsurlsamountscountsfacts	relations
last_touchz
2026-07-15otherotypecompanyrtypezmentioned-with	directionoutother_summaryzAhmed's studio.timelineidx1datekindfactu   Ain lead — Mohammad.toneheadlineu   AIN — Reval Studio's clientsectionstitleWhobodyzA lead handed to Reval Studio.cachedFc                    \         P                   ! 4       V,           pV ! 4       '       g5   \         P                   ! 4       V8  d   \         P                  ! R 4       K?  R# R# )r   N)r!   r"   )predtimeoutr'   s   && r   	_wait_forrs      s9    yy{W$Hffx/

4 0fr   c                  a . oV P                  \        RR 4       V P                  \        RR 4       V P                  \        RV3R l4       \        P                  ! R4      pRVP                  4       9   g   R	VP                  4       9   g   Q hS'       d   S^ ,          ^ ,          R
8X  g   Q hS^ ,          ^,          pVR,          RJ d   VR,          e   Q hVR	,          pVR,          R,          R8X  g   Q hVR,          R,          R8X  g   Q h\	        VR,          R,          \
        4      '       g   Q h\	        VR,          \
        4      '       g   Q h\        V3R l4       \        S4      ^8X  d   S^,          ^ ,          R
8X  g   Q hS^,          ^,          pVR,          RJ g   Q hVR,          \        8X  g   Q hVR	,          R,          R,          R8X  g   Q hR# )u   STAGE 1: card pops instantly (composing=True, composed=None). STAGE 2 (on
a daemon thread): the composed dossier swaps in (composing=False,
composed=<dict>). The spoken confirmation returns after STAGE 1 — it never
waits for composition.fetch_profilec                     \        \        4      # r	   dictFAKE_PROFILErB   s   &r   r   1test_show_entity_two_stage_emit.<locals>.<lambda>   	    \@Rr   fetch_composedc                     \        \        4      # r	   )rx   FAKE_COMPOSEDrB   s   &r   r   rz      s	    mATr   emitc                (   < SP                  W34      # r	   r
   rK   kweventss   &,r   r   rz          FMM5+,Fr   r   showingprofileentity_profile	composingTcomposedNrH   r&   rK   rL   rR   rS   rc   c                     < \        S 4      ^8  #    r>   r   s   r   r   rz          c&kQ&r   F)	r   r   r   r    
isinstancelistrs   r>   r~   )r#   msge1r   e2r   s   &    @r   test_show_entity_two_stage_emitr      s   
 F_6RS%57TUVFH

!
!%
(C		#yCIIK'???fQil&6666	1Bk?d"r*~'===mG8V$---8W%000gg&x0$7777gj)40000&'v;!q	!0@ @@@	1Bk?e###j>]***i="6*e333r   c                  a . oV P                  \        RR 4       V P                  \        RR 4       V P                  \        RV3R l4       \        P                  ! R4       S^ ,          ^,          R,          R	J d   S^ ,          ^,          R
,          e   Q h\        V3R l4       \	        S4      ^8X  g   Q hS^,          ^,          pVR
,          f   VR,          RJ g   Q hR# )u   POST /dossier 404s / times out / returns null → STAGE 2 still emits, with
composed=None composing=False, so the HUD drops the spinner and renders the
raw profile. STAGE 1 is unaffected.ru   c                     \        \        4      # r	   rw   rB   s   &r   r   ;test_show_entity_compose_failure_degrades.<locals>.<lambda>   r{   r   r|   c                    R # r	   r   rB   s   &r   r   r      s    r   r   c                (   < SP                  W34      # r	   r
   r   s   &,r   r   r      r   r   r   r   Tr   Nc                     < \        S 4      ^8  # r   r   r   s   r   r   r      r   r   F)r   r   r   rs   r>   )r#   r   r   s   & @r   )test_show_entity_compose_failure_degradesr      s     F_6RS%5~FVFHE"!9Q<$,1j1I1QQQ&'v;!	1Bj>!bo&>>>&>r   c                <  a . oV P                  \        R R 4       V P                  \        RR 4       V P                  \        RV3R l4       \        P                  ! R4      p\        P                  ! R4       S'       d   Q hRVP                  4       9   g   Q hR	# )
ru   c           
         R RRRRRR/ R/ /# )rH   NrO   rQ   rP   rR   rX   r   rB   s   &r   r   4test_show_entity_unknown_is_silent.<locals>.<lambda>   s    8T9b&"#*B"#>r   r|   c                .    \         P                  ! R 4      # )zcomposed unknown entity)pytestfailrB   s   &r   r   r      s    &++.G"Hr   r   c                &   < SP                  V 4      # r	   r
   r   s   &,r   r   r      s    FMM%,@r   zNobody Whatsoeverr-   z
don't haveN)r   r   r   r!   r"   r    )r#   r   r   s   & @r   "test_show_entity_unknown_is_silentr      s    F_>? %5HJV@B

!
!"5
6CJJsO:399;&&&r   c                     ^ RI p ^ RIHp V P                  V4      pRV9   g   Q R4       hRV9   g   Q hR F$  pVP	                  R4      V9   d   K  Q RV R24       h	  R# )	    Nentity_action.jsonz=control.py no longer watches the HUD card's back-channel file_entity_action"zcmd z not dispatched in control.py)z"show_entity"z"entity_note"z"entity_summary")inspectvoice.controlcontrol	getsourcestrip)r   r   srccmds       r   *test_control_watcher_handles_entity_actionr      sp    #


G
$C3& HGH&s"""Eyy~$OSE1N&OO$ Fr   LIVE1zLIVE=1 only)reasonc                    \         P                  ! R 4      p V '       d   V P                  R4      '       g   Q R4       hV R,          R,          ^ 8  g   Q h\        P                  P                  \        P                  P                  \        4      RR4      pVR,           p\        VR4      ;_uu_ 4       p\        P                  ! R	R
RR /V4       RRR4       \        P                  ! W!4       \        P                  ! ^4       \        P                  P                  V4      '       d   Q R4       h\        R4       R#   + '       g   i     Lu; i)r   rH   zlive /profile returned nothingrX   rY   r   r   z.tmpwr   r   r&   Nu@   engine did not consume entity_action.json — is Jarvis running?uA   
LIVE: command consumed — the AIN card should be ON SCREEN now.)r   ru   getospathjoindirname__file__openjsondumpreplacer!   r"   existsprint)r   ctrltmpfs       r   test_live_profile_and_cardr      s    ''.Gw{{8,,N.NN,8W%)))77<<19,.D
-C	c31		5-7; 
JJsJJqMww~~d## KJK#	
NO 
s   3D<<E	))z$pull up everything we know about AINr   )zjarvis pull up AINr   )zshow me Saud Altamimir6   )r<   r=   )zwho's HishamHisham)ztell me about AlrugaibAlrugaib)z%tell me everything about Reval Studior   )zwhat do we know about DinasorDinasor)z"pull up the profile of Wafi EnergyzWafi Energy)u   من هو سعودu   سعود)u    كل شي نعرفه عن عينu   عين)rE   zpull upz6the show me nothing phrase isn't at the end so no tail)r   )__doc__
__future__r   r   r   r!   r   voicer   EXPLICIT_CASESr.   markparametrizer(   r0   r7   r?   rG   ry   r~   rs   r   r   r   r   skipifenvironr   r   r   r   r   <module>r      s   #  	    -~>: ?: n5, 6,&		K  ueVUGW"l,GI.
Bh"fb)RPwK\<H7NGY,k5"$57 8 $flFF2FB@ A /'5&*JKLe4@?"'(	P  BJJNN6*c1-HP IPr   