Ë
    áß²jÄ$  ã                   óþ   — d Z ddlZddlZddlmZmZ  ej                  e«      ZdZ	dZ
dededee   fd	„Z	 dd
edededee   def
d„Zdedededefd„Zd
edee   fd„Zd
edefd„Zd
edee   fd„Zd
edee   fd„Zy)uÈ  Rotation-stable logical cache scope for prompt_cache_key derivation.

Legacy compression rotation mints a new physical ``session_id`` mid-conversation, moving it
into a fresh cache bucket. ``resolve_prompt_cache_scope()`` maps the physical id to the ROOT
of its compression lineage â€” NOT ``get_conversation_root`` (the Portal-attribution walk),
which would collapse /branch children and delegate trees into one id. ``/new`` starts a
fresh scope; fork children (branch, delegate, tool-tagged) are isolated. Hosts minting one id
per RESPONSE declare the conversation via ``gateway_session_key``, which wins over the lineage
walk and is hashed to ``gwk_<sha256[:24]>`` (it embeds platform/chat/user identifiers).
é    N)ÚAnyÚOptionalÚ_prompt_cache_scope_memoÚgwk_Ú
session_idÚ
session_dbÚreturnc                 óÜ   — |€y	 |j                  | «      }t	        |t
        t        f«      r|r|d   }t	        |t        «      r|r|S y# t        $ r t        j                  dd¬«       Y yw xY w)zRCompression-lineage root of *session_id*, or None (tolerates test-double results).Nz&prompt-cache scope lineage walk failedT©Úexc_infor   )Úget_compression_lineageÚ	ExceptionÚloggerÚdebugÚ
isinstanceÚlistÚtupleÚstr)r   r   ÚlineageÚroots       ú=/home/ubuntu/.hermes/hermes-agent/agent/prompt_cache_scope.pyÚ_lineage_rootr      st   € àÐØðØ×4Ñ4°ZÓ@ˆô �'œD¤%˜=Ô)©gØ�q‰zˆÜ�dœCÔ ¡TØˆKØøô ò Ü�‰Ð=ÈˆÔMÙðús   …A Á A+Á*A+ÚagentÚ
row_sourcec                 óè  — |€F|rD|�B	 |j                  |«      }|r,t	        |j                  d«      xs d«      j                  «       nd}|r|S t        | dd«      }	 dd	lm	} t	         ||«      xs d«      j                  «       }|r|S 	 t	        |xs d«      j                  «       S # t        $ r t        j                  dd¬«       d}Y Œ±w xY w# t        $ r t        j                  d
d¬«       Y Œgw xY w)a¨  The ``sessions.source`` this agent's conversation is recorded under.

    ``row_source``: the row's value if already read (``""`` = read, none; ``None`` = look it
    up). Before the row lands, use the SAME resolver persistence uses, not ``agent.platform``:
    they diverge under ``HERMES_SESSION_SOURCE`` and the declared scope is memoized at once,
    so both sides of a ``/new`` would otherwise hash the same scope.
    Nz#declared-scope source lookup failedTr   ÚsourceÚ Úplatformr   )Ú_session_source_for_agentz+declared-scope source authority unavailable)
Úget_sessionr   r   r   r   ÚgetÚstripÚgetattrÚ	run_agentr   )r   r   r   r   Úrowr   r   r   s           r   Ú_agent_sourcer&   &   sý   € ð Ð™j¨ZÐ-Cð	Ø×(Ñ(¨Ó4ˆCñ >A”S˜Ÿ™ Ó*Ò0¨bÓ1×7Ñ7Ô9Àbˆ
ÙØÐÜ�u˜j¨$Ó/€HðSå7äÑ.¨xÓ8Ò>¸BÓ?×EÑEÓGˆÙØˆMð ô ˆxŠ~˜2Ó×$Ñ$Ó&Ð&øô! ò 	Ü�L‰LÐ>ÈˆLÔNØŠCð	ûô ò SÜ�‰ÐBÈTˆÖRðSús#   ˆB& Á,C Â&"CÃ
CÃ C1Ã0C1Úsession_keyr   c                 óv   — t        |dd«      }t        |«      sy || |«      }|€dS t        t        |«      «      S )a«  Durable generation for *session_key*'s current conversation (``""`` if none).

    The declared key survives ``/new``, so hashing it alone would reuse one scope across
    conversations. The counter advances with each reset boundary, independent of prunable
    rows and wall-clock; compression does not advance it.

    The declared key names a chat and deliberately survives `/new` and policy resets. See #79017, #86733.
    Úlatest_conversation_boundaryNr   )r#   Úcallabler   Úint)r'   r   r   ÚreaderÚ
generations        r   Ú_conversation_generationr.   F   sC   € ô �ZÐ!?ÀÓF€FÜ�FÔØÙ˜ VÓ,€JØÐ#ˆ2Ð=¬¬S°«_Ó)=Ð=ó    c                 ó  — t        | dd«      }t        |t        «      r|j                  t        «      r|S t        t        | dd«      xs d«      j                  «       }|rt        | dd«      ryt        t        | dd«      xs d«      }t        | dd«      }d}d}|r<|�:	 t        |d	d«      }t        |«      r ||«      \  }}n|j                  |«      }|ry	 t        | |||«      }	|�	 t        ||	|«      }|	› d|› d|› �}
t        j                  |
j                  dd¬«      «      j!                  «       dd }t        › |› �S # t        $ r t        j                  d
d¬«       Y yw xY w# t        $ r t        j                  dd¬«       Y yw xY w)uû  Host-declared logical conversation scope (``gwk_<sha256[:24]>``), or None.

    Hashes ``(source, gateway_session_key, generation)``. None (fall back to the physical id)
    when no key is declared, for a background-review fork (``_persist_disabled``), for an
    explicit fork child, and on any DB error (fail closed rather than merge a fork onto its
    parent's key).

    The one sanctioned exception is a same-model cache-parity fork (#109964): its whole purpose
    is prefix parity with the parent, yet ``_persist_disabled`` + ``_session_db=None`` made both
    resolvers key it into a different bucket (one cold ~full-context request per review).
    ``build_cache_parity_fork`` stamps the parent's ALREADY-RESOLVED scope as
    ``_inherited_cache_scope`` (no DB access from the fork). Only a ``gwk_`` value is a declared
    scope; a physical lineage root stays out of the affinity header so the fork publishes
    exactly what its parent publishes (None â†’ consumers fall back to the conversation root).
    Ú_inherited_cache_scopeNÚ_gateway_session_keyr   Ú_persist_disabledFr   Ú_session_dbÚdeclared_scope_identityz declared-scope fork check failedTr   z%declared-scope generation read failedÚ|zutf-8Úreplace)Úerrorsé   )r#   r   r   Ú
startswithÚ_DECLARED_SCOPE_PREFIXr"   r*   Úis_explicit_fork_childr   r   r   r&   r.   ÚhashlibÚsha256ÚencodeÚ	hexdigest)r   Ú	inheritedÚkeyÚsidÚdbr-   r   ÚidentityÚis_forkr   ÚcarrierÚdigests               r   Údeclared_conversation_scoperI   V   s«  € ô  ˜Ð7¸Ó>€IÜ�)œSÔ! i×&:Ñ&:Ô;QÔ&RØÐÜ
Œg�eÐ3°RÓ8Ò>¸BÓ
?×
EÑ
EÓ
G€CÙ”'˜%Ð!4°eÔ<ØÜ
Œg�e˜\¨4Ó0Ò6°BÓ
7€CÜ	�˜ tÓ	,€BØ€JØ $€JÙ
ˆrˆ~ð	ô ˜rÐ#<¸dÓCˆHÜ˜Ô!Ù&.¨s£mÑ#�™à×3Ñ3°CÓ8�ÙØð ô
 ˜5 # r¨:Ó6€FØ	€~ð	Ü1°#°v¸rÓBˆJð
 �˜˜#˜˜a 
˜|Ð,€GÜ�^‰^˜GŸN™N¨7¸9˜NÓEÓF×PÑPÓRÐSVÐTVÐW€FÜ$Ð% f XÐ.Ð.øô ò 	Ü�L‰LÐ;ÀdˆLÔKÙð	ûô ò 	Ü�L‰LÐ@È4ˆLÔPÙð	ús$   Â7D? Ã'E% Ä? E"Å!E"Å% FÆFc                 óÌ  — t        | dd«      }t        |t        «      r|r|S t        t        | dd«      xs d«      }|syt        | dd«      }||duf}t        | t        d«      }t        |t        «      rt        |«      dk(  r|d   |k(  r|d   S t        | «      xs t        ||«      }|xs |}|€|�t        | d	d
«      r	 t        | t        ||f«       |S |S # t        $ r Y |S w xY w)zöRotation-stable cache-scope id: the inherited parent scope of a same-model cache-parity
    fork, else the declared scope, else the compression-lineage root of ``agent.session_id``
    (the physical id without ancestry/DB). Memoized on the agent.r1   Nr   r   r4   é   r   é   r3   F)
r#   r   r   Ú
_MEMO_ATTRr   ÚlenrI   r   Úsetattrr   )r   rA   rC   rD   rB   Úmemor   Úscopes           r   Úresolve_prompt_cache_scoperR   �   s  € ô ˜Ð7¸Ó>€IÜ�)œSÔ!¡iØÐÜ
Œg�e˜\¨4Ó0Ò6°BÓ
7€CÙØÜ	�˜ tÓ	,€Bà�˜$�Ð
€CÜ�5œ* dÓ+€DÜ�$œÔ¤3 t£9°¢>°d¸1±gÀ²nØ�A‰wˆÜ& uÓ-ÒG´¸sÀBÓ1G€DØŠK�C€Eð Ð˜2˜:¬°Ð8KÈUÔ)Sð	Ü�Eœ:¨¨U |Ô4ð €Lˆ5€Løô ò 	ØØ€Lð	ús   Â?C Ã	C#Ã"C#c                 óf   — 	 t        | «      S # t        $ r t        j                  dd¬«       Y yw xY w)z=Never-raising variant of :func:`declared_conversation_scope`.z-declared conversation scope resolution failedTr   N)rI   r   r   r   ©r   s    r   Ú declared_conversation_scope_saferU   ¬   s5   € ðÜ*¨5Ó1Ð1øÜò Ü�‰ÐDÈtˆÔTÙðús   ‚
 � 0¯0c                 ón   — 	 t        | «      xs dS # t        $ r t        j                  dd¬«       Y yw xY w)uk  Never-raising variant of :func:`resolve_prompt_cache_scope` (None = use the physical id).
    At turn_context an exception inside ``set_runtime_main(...)`` would skip the whole binding.

    Returns None on any failure (or when there is no scope). Consumers treat None/empty as "fall back to the
    physical session_id", so a resolution failure degrades to pre-#79017 behavior instead of blocking the
    caller â€” important at turn_context's call site, where an exception raised inside the
    ``set_runtime_main(...)`` argument list would otherwise skip the whole runtime binding, not just the
    cache scope.
    Nz$prompt-cache scope resolution failedTr   )rR   r   r   r   rT   s    r   Úresolve_prompt_cache_scope_saferW   µ   s:   € ðÜ)¨%Ó0Ò8°DÐ8øÜò Ü�‰Ð;ÀdˆÔKÙðús   ‚ ‘ 4³4)N)Ú__doc__r=   ÚloggingÚtypingr   r   Ú	getLoggerÚ__name__r   rM   r;   r   r   r&   r.   rI   rR   rU   rW   © r/   r   ú<module>r^      s  ðñ	ó Û ß  à	ˆ×	Ñ	˜8Ó	$€à'€
ØÐ ð˜cð ¨sð °xÀ±}ó ð" OSñ'Øð'Øð'Ø-0ð'Ø>FÀs¹mð'àó'ð@>¨#ð >°sð >Èð >ÐPSó >ð 6/ sð 6/¨x¸©}ó 6/ðr cð ¨có ð:¨Cð °H¸S±Mó ð¨3ð °8¸C±=ô r/   