Ë
    âß²jH| ã            0       óh  — U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddl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 ddlmZmZmZmZ ddlmZmZmZ ddlm Z  ejB                  d	k(  rddl"Z"nddl#Z#d
Z$dZ%dZ&ejB                  d	k(  Z' e(«       Z)dZ*da+dZ,dZ- ej\                  «       Z/i Z0e1e2e3e4e4f   e2e5e2ee6   f   f   e7d<    ejp                  e9«      Z: G d„ d«      Z; ejx                  «       Z=da>ee   e7d<   da?ee1e3ef      e7d<   dede1e3ef   de1e3ef   fd„Z@daAee;   e7d<   de;fd„ZB�dde5de4fd„ZC�dde5de4fd„ZD G d„ de«      ZE	 �dd d!œd"e6d#e5d$e5deeE   fd%„ZFdefd&„ZGdee3z  defd'„ZHd(ee3z  d)ee3z  de4fd*„ZIdd+œd,ee1e3ef      d-eee3z     de4fd.„ZJ e	j–                  d/«      ZLd0ee3z  dee3   fd1„ZMd,ee1e3ef      dee3   fd2„ZNdefd3„ZO�dd4ee   defd5„ZPdefd6„ZQdefd7„ZRde3fd8„ZSd9ZTd:edee3   fd;„ZU eVd<d=h«      ZWd>ede3fd?„ZXd@ddAœdBe6dCe4dDee5   ddfdE„ZYdFedGede4fdH„ZZdIZ[e[fdGedFedJe6de4fdK„Z\dLe3de3fdM„Z]dNe3dLe3defdO„Z^dBe6dee6   fdP„Z_dBe6dee6   fdQ„Z`dBe6dee3   fdR„ZadSe3dz  de3dz  fdT„ZbdSe3dz  de4fdU„ZcdSe3dz  de4fdV„ZddBe6de4fdW„Zed,e1e3ef   de4fdX„ZfdYedee3   fdZ„ZgdSe3dee3   fd[„Zh e	j–                  d\«      ZidSe3deje3   fd]„Zkd^e3d_e3de4fd`„ZldSe3dYede4fda„ZmdBe6dYede4fdb„Zndd+œd,e1e3ef   dBe6d-ee   de4fdc„Zode1fdd„Zpde1e3ef   fde„Zqd,ee1e3ef      de4fdf„Zrde1e3ef   fdg„Zsd@dhœdedie4dee1e3ef      fdj„Ztdede1e3ef   ddfdk„Zudeddfdl„Zv�dd4ee   dee1   fdm„Zw�ddnee   dee1e3ef      fdo„Zx�dd,ee1e3ef      dpe3dee6   fdq„Zydredsede4fdt„Zzd,ee1e3ef      dee6   fdu„Z{�d	dv„Z|dede2e4ee6   ee6   f   fdw„Z}d4edxe4ddfdy„Z~de4fdz„ZdBe6de4fd{„Z€dBe6de4fd|„Z�dBe6de4fd}„Z‚�d	d~„Zƒde4fd„Z„�d	d€„Z…de4fd�„Z†de4fd‚„Z‡�ddnee   de4fdƒ„Zˆded„e3de4fd…„Z‰dnede4fd†„ZŠ�d	d‡„Z‹ded,e1e3ef   ddfdˆ„ZŒd‰e1e3ef   ddfdŠ„Z�d‹ede1e3e3f   fdŒ„ZŽe)e)e)e)e)e)e)e)e)e)e)e)e)e)e)e)d@dd�d@dŽœd�ed�ed‘ed’ed“ed”ed•ed–ed—ed˜ed™edšed‹ed›edœed�edže4dŸee3   d e4d¡e4de2ee1e3ef   e1e3ef   f   f*d¢„Z�d£e1e3ef   de1e3ef   ddfd¤„Z�d@dd¥œd¡e4d¦ee5   d§ede4fd¨„Z‘d§ede6fd©„Z’�ddee   dee1e3ef      fdª„Z“d«Z”e”fd,ee1e3ef      d¬e6de4fd­„Z•d,ee1e3ef      dee6   fd®„Z–d,ee1e3ef      de4fd¯„Z—d°ede6fd±„Z˜ eVd²d³h«      Z™d´e4d�ed’ede4fdµ„Zšd´e4d�ede4fd¶„Z› ed�¬·«       G d¸„ d¹«      «       ZœdYedee3   fdº„Z�d»edee2e6e1e3ef   f      fd¼„Zžd>ee1e3ef      d½e3de1e3ef   fd¾„ZŸd>ee1e3ef      d½e3de1e3ef   fd¿„Z de)dd�ddddÀœd»ee   d>edÁeeg e2e4ee1e3ef      f   f      dÂe4dÃeedÄee6   f      dÅeedÄee1e3ef      f      dÆeedÄee6   f      deœfdÇ„Z¡	 �ddd+œd>ee1e3ef      d-ee   dee6   fdÈ„Z¢d0edee6   fdÉ„Z£�d	dÊ„Z¤dËe1e3ef   dÌee6   de4fdÍ„Z¥dBe6de4fdÎ„Z¦	 �ddNe3dLe3dÏee1e3ef      de2e4ee1e3ef      f   fdÐ„Z§dNe3dLe3ddfdÑ„Z¨dddÒœdÓee6   dÔee6   de6fdÕ„Z©dÖZªd×Z«dØZ¬d×Z­�ddÙee   defdÚ„Z®defdÛ„Z¯dÜe3d¬e6de4fdÝ„Z°ded¬e6dee2e1e3ef   e6ef      fdÞ„Z±dße6dàede4fdá„Z²ded¬e6de4fdâ„Z³de)dãœdße6däee   dàede4fdå„Z´ded,e1e3ef   de4fdæ„Zµde4fdç„Z¶�ddäee   ddfdè„Z·d,e1e3ef   dee2e6e6ef      fdé„Z¸dÓe6dÔe6de3fdê„Z¹dÓe6dÔe6dëe6dìe5de2e4e4f   f
dí„ZºdBe6dejfdî„Z»dïdðœdñejdòe6de5de6fdó„Z¼dôdôdõœd,e1e3ef   döe6d÷e6dee6   fdø„Z½dÓe6dÔe6döe6d÷e6dee6   f
dù„Z¾dße6de4fdú„Z¿de4fdû„ZÀde4fdü„ZÁ	 �dd�dýœd4ee   dxe4dee6   fdþ„ZÂd4edee2e6e5f      fdÿ„ZÃ	 �dd�e-�d œd4ee   dxe4�de5dee6   f�d„ZÄ�d	�d„ZÅ	 �dd�dýœd4ee   dxe4de4f�d„ZÆy(
  z™Gateway runtime status helpers: PID/lock/marker files under ``{HERMES_HOME}`` (one set per
home/profile) that tell whether the gateway daemon is running.é    N)Ú	dataclass)ÚdatetimeÚtimezone)ÚPath)ÚAnyÚCallableÚ
NamedTupleÚOptional)Ú!_get_platform_default_hermes_homeÚget_hermes_homeÚget_process_hermes_home©Úatomic_json_writeÚwin32úhermes-gatewayúgateway_state.jsonzgateway-lockszgateway.locké   g      ð?Ú_gateway_running_pid_cachec                   ó¦   — e Zd ZdZddeeeeee	f   gdf      fd„Z
dedeee	f   defd„Zdd	ed
ee   defd„Zdd
edefd„Zd	edee   fd„Zdd„Zy)Ú_RuntimeStatusWritera.  Persist the latest complete status snapshot on one daemon thread.

    Runtime status is diagnostic state, not an event log. While one write is
    blocked in filesystem I/O, newer submissions replace the single pending
    snapshot. This bounds memory and keeps every status producer off asyncio.
    NÚwrite_fnc                 ó´   — || _         t        j                  «       | _        d | _        d| _        d| _        d| _        d| _        d | _	        d| _
        d | _        y )Nr   F)Ú	_write_fnÚ	threadingÚ	ConditionÚ
_conditionÚ_pendingÚ_writing_generationÚ_submitted_generationÚ_completed_generationÚ_successful_generationÚ_last_errorÚ_failure_loggedÚ_thread)Úselfr   s     ú3/home/ubuntu/.hermes/hermes-agent/gateway/status.pyÚ__init__z_RuntimeStatusWriter.__init__:   sW   € Ø!ˆŒÜ#×-Ñ-Ó/ˆŒØDHˆŒØ#$ˆÔ Ø%&ˆÔ"Ø%&ˆÔ"Ø&'ˆÔ#Ø48ˆÔØ$ˆÔØ37ˆ�ó    ÚpathÚpayloadÚreturnc                 óÈ  — | j                   5  | xj                  dz  c_        | j                  }||t        j                  |«      f| _        | j
                  �| j
                  j                  «       sAt        j                  | j                  dd¬«      | _        | j
                  j                  «        | j                   j                  «        |cd d d «       S # 1 sw Y   y xY w)Né   Tzgateway-runtime-status-writer)ÚtargetÚdaemonÚname)r   r   ÚcopyÚdeepcopyr   r$   Úis_aliver   ÚThreadÚ_runÚstartÚ
notify_all)r%   r)   r*   Ú
generations       r&   Úsubmitz_RuntimeStatusWriter.submitF   s¬   € Ø�_‰_ñ 		Ø×&Ò&¨!Ñ+Õ&Ø×3Ñ3ˆJØ'¨¬t¯}©}¸WÓ/EÐFˆDŒMØ�|‰|Ð#¨4¯<©<×+@Ñ+@Ô+BÜ(×/Ñ/ØŸ9™9¨TÐ8Wô Y�”à—‘×"Ñ"Ô$Ø�O‰O×&Ñ&Ô(Ø÷		÷ 		ò 		ús   �CCÃC!r8   Útimeoutc                 óØ  — |€dn t        j                  «       t        |d«      z   }| j                  5  | j	                  |«      x}€s|€| j                  j                  «        Œ0|t        j                  «       z
  }|dk  r
	 ddd«       y| j                  j                  |¬«       | j	                  |«      x}€Œs|s|€| j                  �| j                  ‚|cddd«       S # 1 sw Y   yxY w)z\Block until ``generation`` (or a later snapshot) is persisted; ``False`` on timeout/failure.Nç        r   F©r:   )ÚtimeÚ	monotonicÚmaxr   ÚsettledÚwaitr"   )r%   r8   r:   ÚdeadlineÚstateÚ	remainings         r&   rB   z_RuntimeStatusWriter.waitR   sß   € à"˜?‘4´·±Ó0@Ä3ÀwÐPSÓCTÑ0TˆØ�_‰_ñ 	Ø ŸL™L¨Ó4Ð4�5Ð=ØÐ#Ø—O‘O×(Ñ(Ô*ØØ$¤t§~¡~Ó'7Ñ7�	Ø ’>Ø ÷	ð 	ð —‘×$Ñ$¨YÐ$Ô7ð !ŸL™L¨Ó4Ð4�5Ñ=ñ ˜W˜_°×1AÑ1AÐ1MØ×&Ñ&Ð&Ø÷	÷ 	ò 	ús   ²AC Â	/C Â9C Ã C)c                 ó�   — | j                   5  | j                  }d d d «       dk(  xs | j                  ||¬«      S # 1 sw Y   Œ#xY w)Nr   r=   )r   r   rB   )r%   r:   r8   s      r&   Úflushz_RuntimeStatusWriter.flushb   sE   € Ø�_‰_ñ 	4Ø×3Ñ3ˆJ÷	4à˜Q‰ÒH $§)¡)¨JÀ )Ó"HÐH÷	4ð 	4ús	   �<¼Ac                 óä   — | j                   5  | j                  |k\  r
	 ddd«       y| j                  |k\  xr | j                  dk(  xr | j                  du }|rdndcddd«       S # 1 sw Y   yxY w)zW``True`` once ``generation`` persisted, ``False`` once it can no longer, else ``None``.NTr   F)r   r!   r    r   r   )r%   r8   Úno_more_works      r&   rA   z_RuntimeStatusWriter.settledg   s{   € à�_‰_ñ 	3Ø×*Ñ*¨jÒ8Ø÷	3ð 	3ð ×*Ñ*¨jÑ8ò *Ø×,Ñ,°Ñ1ò*à—M‘M TÐ)ð ñ
 )‘5¨d÷	3÷ 	3ò 	3ús   �A&§5A&Á&A/c                 óX  — 	 | j                   5  | j                  €'| j                   j                  «        | j                  €Œ'| j                  \  }}}d | _        || _        d d d «       d }	  | j                  xs t
        t        |«      «       | j                   5  d| _        t        | j                  «      | _	        |€#t        | j                  |«      | _
        d | _        n|| _        | j                   j                  «        d d d «       |€)| j                  r]t        j                  d«       d| _        n@| j                  st        j!                  d|«       d| _        nt        j#                  d|«       �Œx# 1 sw Y   �ŒxY w# t        $ r}|}Y d }~�Œd }~ww xY w# 1 sw Y   ŒœxY w)NTr   z,Gateway runtime-status persistence recoveredFzFFailed to persist gateway runtime status; later updates will retry: %sz,Failed to persist gateway runtime status: %s)r   r   rB   r   r   Ú_write_json_fileÚ_merge_over_on_diskÚBaseExceptionr@   r    r!   r"   r7   r#   ÚloggerÚinfoÚwarningÚdebug)r%   r8   r)   r*   ÚerrorÚexcs         r&   r5   z_RuntimeStatusWriter._runs   sz  € ØØ—‘ñ 6Ø—m‘mÐ+Ø—O‘O×(Ñ(Ô*ð —m‘mÑ+à,0¯M©MÑ)�
˜D 'Ø $�”Ø+5�Ô(÷6ð .2ˆEðØ3�—‘Ò3Ô#3°TÔ;NÈtÐU\Ó;]Ô^ð —‘ñ -Ø+,�Ô(Ü-0°×1KÑ1KÈZÓ-X�Ô*Ø�=Ü25°d×6QÑ6QÐS]Ó2^�DÔ/Ø'+�DÕ$à',�DÔ$Ø—‘×*Ñ*Ô,÷-ð ˆ}Ø×'Ò'Ü—K‘KÐ NÔOØ+0�DÕ(Ø×)Ò)Ü—‘Ø\Ð^côeà'+�Õ$ä—‘ÐKÈUÔSñ= ÷6ñ 6ûô !ò Ø–ûðú÷-ð -ús6   Ž3E:ÁE:Á+%F ÂA)F Å:FÆ	FÆFÆFÆ F)©N©g       @©r+   N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r
   r   r   ÚdictÚstrr   r'   Úintr9   ÚfloatÚboolrB   rG   rA   r5   © r(   r&   r   r   2   s¢   „ ññ
8 ¨(°D¸$¸sÀC¸x¹.Ð3IÈ4Ð3OÑ*PÑ!Qó 
8ð
˜4ð 
¨$¨s°C¨x©.ð 
¸Só 
ñ˜sð ¨X°e©_ð Èó ñ I˜Uð I¨Tó Ið

3 #ð 
3¨(°4©.ó 
3ôTr(   r   Ú_runtime_status_state_pathÚ_runtime_status_stater)   r*   r+   c                 óH   — t        | «      }t        |t        «      ri |¥|¥S |S )a  Lay the canonical snapshot over whatever is on disk right before writing. Out-of-process
    writers (the migration's compensator clearing multiplex-owned status, container_boot
    seeding ``desired_state``) stamp this file directly; the gateway's fields win, theirs survive.)Ú_read_json_fileÚ
isinstancer[   )r)   r*   Úexistings      r&   rL   rL   š   s-   € ô ˜tÓ$€HÜ&0°¼4Ô&@Ð"ˆhÐ"˜'Ð"ÐMÀgÐMr(   Ú_runtime_status_writerc                  óf   — t         5  t        €
t        «       at        cddd«       S # 1 sw Y   yxY w)zULazily create the single writer; callers serialise on ``_runtime_status_state_lock``.N)Ú_runtime_status_state_lockrg   r   r`   r(   r&   Ú_get_runtime_status_writerrj   ¥   s.   € ô 
$ñ &Ü!Ð)Ü%9Ó%;Ð"Ü%÷&÷ &ò &ús   ‡'§0r:   c                 ó:   — t         }|€dS |j                  | ¬«      S )z?Wait boundedly for all runtime-status updates submitted so far.Tr=   )rg   rG   ©r:   Úwriters     r&   Úflush_runtime_statusrn   ®   s!   € ä#€FØ�>ˆ4ÐD v§|¡|¸G |Ó'DÐDr(   c              ƒ   ó˜   K  — t         }|€yt        j                  |j                  t	        t        | «      d«      ¬«      ƒ d{  –—† S 7 Œ­w)zDAwait the current writer generation without blocking the event loop.NTr<   r=   )rg   ÚasyncioÚ	to_threadrG   r@   r^   rl   s     r&   Úflush_runtime_status_asyncrr   ´   s;   è ø€ ä#€FØ€~Øä×"Ñ" 6§<¡<¼¼UÀ7»^ÈSÓ9QÔR×RÐRÐRús   ‚AA
ÁAÁA
c                   ó0   — e Zd ZU dZeed<   eed<   eed<   y)Ú	StormInfozFRespawn-storm check result: start count, window, and backoff to sleep.ÚcountÚwindow_sÚ	backoff_sN)rW   rX   rY   rZ   r]   Ú__annotations__r^   r`   r(   r&   rt   rt   ½   s   … ÙPàƒJØƒOØÔr(   rt   g     Àr@)Úbackoff_cap_sÚ
max_startsrv   ry   c          
      óÖ  — 	 t        «       dz  }|j                  j                  dd¬«       t        j                  t
        j                  «      j                  «       }g }|j                  «       ra|j                  d¬«      j                  «       D ]>  }t        j                  t        «      5  |j                  t        |«      «       ddd«       Œ@ |j                  |«       |D �cg c]  }||z
  |k  sŒ|‘Œ }}|t!        | dz  d«       d }	|j#                  d	«      }
|
j%                  d
j'                  d„ |	D «       «      d
z   d¬«       t)        j*                  |
|«       t-        |«      | k  ryt/        |ddt/        t-        |«      | z
  d«      z  z  «      }t1        t-        |«      ||¬«      S # 1 sw Y   �Œ(xY wc c}w # t2        $ r }t4        j7                  d|«       Y d}~yd}~ww xY w)z±Record this start; :class:`StormInfo` when > ``max_starts`` landed in ``window_s``.
    Best-effort: a broken ``gateway-starts.log`` ledger is logged and swallowed, never fatal.zgateway-starts.logT©ÚparentsÚexist_okúutf-8©ÚencodingNé   é(   z.tmpÚ
c              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrT   )Úrepr)Ú.0Útss     r&   ú	<genexpr>z/record_start_and_check_storm.<locals>.<genexpr>Ø   s   è ø€ Ò =¨b¤ b§Ñ =ùó   ‚ç      @é   é   )ru   rv   rw   z8respawn-storm breaker bookkeeping failed (non-fatal): %s)r   ÚparentÚmkdirr   Únowr   ÚutcÚ	timestampÚexistsÚ	read_textÚ
splitlinesÚ
contextlibÚsuppressÚ
ValueErrorÚappendr^   r@   Úwith_suffixÚ
write_textÚjoinÚosÚreplaceÚlenÚminrt   Ú	ExceptionrN   rQ   )rz   rv   ry   r)   r�   rf   Úlinerˆ   ÚrecentÚto_writeÚtmpÚbackoffÚ_es                r&   Úrecord_start_and_check_stormr¨   Å   s«  € ð
ÜÓ Ð#7Ñ7ˆØ�‰×Ñ $°ÐÔ6Ü�l‰lœ8Ÿ<™<Ó(×2Ñ2Ó4ˆØ "ˆØ�;‰;Œ=ØŸ™°˜Ó8×CÑCÓEò 1�Ü×(Ñ(¬Ó4ñ 1Ø—O‘O¤E¨$£KÔ0÷1ð 1ð1ð 	�‰˜ÔØ'Ö@˜¨3°©8°xÓ+?’"Ð@ˆÐ@àœS ¨a¡°Ó4Ð4Ð5Ð6ˆØ×Ñ˜vÓ&ˆØ�‰�t—y‘yÑ =°HÔ =Ó=ÀÑDÈwˆÔWÜ
�
‰
�3˜ÔÜˆv‹;˜*Ò$ØÜ�m S¨A´´S¸³[À:Ñ5MÈqÓ1QÑ,QÑ%RÓSˆÜœs 6›{°XÈÔQÐQ÷1ñ 1üò Aøô ò Ü�‰ÐOÐQSÔTÜûðúsI   ‚B)F? Â+F-ÃF? Ã%F:Ã3F:Ã7A6F? Å.>F? Æ-F7	Æ2F? Æ?	G(ÇG#Ç#G(c                  ó   — t        «       S )z°Launch-home HERMES_HOME for identity files (PID, lock, status, markers):
    ``get_hermes_home()`` honors the per-session ``_HERMES_HOME_OVERRIDE`` and would misroute
    them.)r   r`   r(   r&   Ú_get_process_hermes_homerª   ã   s   € ô #Ó$Ð$r(   c                 óT   — t        | «      j                  «       j                  d¬«      S )z=Stable absolute HERMES_HOME path for persisted identity data.F)Ústrict)r   Ú
expanduserÚresolve©r)   s    r&   Ú_canonical_hermes_homer°   ê   s#   € ä�‹:× Ñ Ó"×*Ñ*°%Ð*Ó8Ð8r(   ÚleftÚrightc                 óÌ   — t         j                  j                  t        t	        | «      «      «      }|t         j                  j                  t        t	        |«      «      «      k(  S )zBCompare HERMES_HOME paths with the host platform's case semantics.)r�   r)   Únormcaser\   r°   )r±   r²   Úleft_cs      r&   Ú_same_hermes_homer¶   ï   sF   € ä�W‰W×ÑœcÔ"8¸Ó">Ó?Ó@€FØ”R—W‘W×%Ñ%¤cÔ*@ÀÓ*GÓ&HÓIÑIÐIr(   ©Úexpected_homeÚrecordr¸   c                óà   — t        | t        «      r| j                  d«      nd}t        |t        «      r|j	                  «       sy	 |�|n	t        «       }t        ||«       S # t        $ r Y yw xY w)a4  True when a persisted gateway record names a DIFFERENT HERMES_HOME (cross-profile kill guard:
    profile B's stop must never SIGTERM profile A). ``expected_home`` overrides the comparison base.
    Legacy records without ``hermes_home`` prove nothing -> False; a comparison failure fails
    closed -> True.Úhermes_homeNFT)re   r[   Úgetr\   Ústriprª   r¶   r¡   )r¹   r¸   Úrecorded_homeÚbases       r&   Úrecorded_gateway_home_conflictsrÀ   õ   sl   € ô 2<¸FÄDÔ1I�F—J‘J˜}Ô-Èt€MÜ�m¤SÔ)°×1DÑ1DÔ1FØðØ -Ð 9‰}Ô?WÓ?YˆÜ$ ]°DÓ9Ð9Ð9øÜò Ùðús   ÁA! Á!	A-Á,A-z^[a-z0-9][a-z0-9_-]{0,63}$Úhomec                 ó’  — 	 t        | «      }|j                  j                  dk(  r+t        j                  |j                  «      r|j                  S ddl}|j                  t        f}|D ]A  }t        j                  t        «      5  t        | |«       «      r	 ddd«        y	 ddd«       ŒC y# t        $ r Y yw xY w# 1 sw Y   Œ]xY w)zYBest-effort label: ``<root>/profiles/<name>`` -> name, root home -> "default", else None.NÚprofilesr   Údefault)r°   r¡   rŽ   r0   Ú_PROFILE_LABEL_REÚmatchÚhermes_constantsÚget_default_hermes_rootr   r–   r—   r¶   )rÁ   Ú	canonicalrÇ   Údefault_homesÚdefault_homes        r&   Ú_profile_label_for_homerÌ     sÃ   € ðÜ*¨4Ó0ˆ	ð ×Ñ×Ñ 
Ò*Ô/@×/FÑ/FÀyÇ~Á~Ô/VØ�~‰~ÐÛØ%×=Ñ=Ô?`Ða€MØ%ò !ˆÜ× Ñ ¤Ó+ñ 	!Ü  ©L«NÔ;Ø ÷	!ñ 	!Ø;÷	!ð 	!ð!ð øô ò Ùðú÷	!ð 	!ús   ‚B. ÂB=Â.	B:Â9B:Â=C	c                 óH  — t        | t        «      sy| j                  d«      }t        |t        «      r3t        j                  |j                  «       «      r|j                  «       S | j                  d«      }t        |t        «      r|j                  «       rt        |«      S dS )zºProfile label of a scoped-lock owner (None: PID-only wording): the validated ``profile``
    field stamped by :func:`acquire_scoped_lock`, else inferred from ``hermes_home`` (old locks).NÚprofiler»   )re   r[   r¼   r\   rÅ   rÆ   r½   rÌ   )r¹   rÎ   rÁ   s      r&   Úscoped_lock_owner_labelrÏ     sz   € ô �fœdÔ#ØØ�j‰j˜Ó#€GÜ�'œ3ÔÔ$5×$;Ñ$;¸G¿M¹M»OÔ$LØ�}‰}‹ÐØ�:‰:�mÓ$€DÜ,6°t¼SÔ,AÀdÇjÁjÄlÔ" 4Ó(Ð\ÐX\Ð\r(   c                  ó   — t        «       dz  S )Núgateway.pid)rª   r`   r(   r&   Ú_get_pid_pathrÒ   (  s   € Ü#Ó%¨Ñ5Ð5r(   Úpid_pathc                 óD   — | xs
 t        «       j                  t        «      S rT   )rÒ   Ú	with_nameÚ_GATEWAY_LOCK_FILENAME©rÓ   s    r&   Ú_get_gateway_lock_pathrØ   ,  s   € ØÒ'œ›×2Ñ2Ô3IÓJÐJr(   c                  ó$   — t        «       t        z  S rT   )rª   Ú_RUNTIME_STATUS_FILEr`   r(   r&   Ú_get_runtime_status_pathrÛ   0  s   € Ü#Ó%Ô(<Ñ<Ð<r(   c                  ó  — t        j                  d«      } | rt        | «      S t        j                  d«      xs d}t         j                  j	                  |«      rt        |«      nt        j
                  «       dz  dz  }|dz  t        z  S )a9  Cross-profile rendezvous dir for machine-local locks; ``HERMES_GATEWAY_LOCK_DIR`` overrides.

    Scope is the **OS user**, not the kernel host: separate users have separate ``$HOME``s,
    separate ``~/.hermes`` profile roots and separate credentials, so "one gateway per host"
    means "one per host per OS user". Holds the token-scoped locks (:func:`acquire_scoped_lock`)
    and the host-role lock + rendezvous record (``gateway/host_rendezvous.py``); the per-home
    ``gateway.pid``/``gateway.lock`` above deliberately stay under each profile's HERMES_HOME.
    ÚHERMES_GATEWAY_LOCK_DIRÚXDG_STATE_HOMEÚ z.localrD   Úhermes)r�   Úgetenvr   r)   ÚisabsrÁ   Ú_LOCKS_DIRNAME)ÚoverrideÚstate_home_envÚ
state_homes      r&   Ú_get_lock_dirrç   4  ss   € ô �y‰yÐ2Ó3€HÙÜ�H‹~Ðô —Y‘YÐ/Ó0Ò6°B€NÜ)+¯©¯©°~Ô)F”�nÔ%ÌDÏIÉIËKÐZbÑLbÐelÑLl€JØ˜Ñ ¤>Ñ1Ð1r(   c                  ód   — t        j                  t        j                  «      j	                  «       S rT   )r   r�   r   r‘   Ú	isoformatr`   r(   r&   Ú_utc_now_isorê   G  s   € Ü�<‰<œŸ™Ó%×/Ñ/Ó1Ð1r(   g   À¡6ÌAÚvaluec                 ó  — t        | t        «      r‰| j                  «       }|j                  d«      r|dd dz   }	 t	        j
                  |«      }|j                  r|j                  «       S |j                  t        j                  ¬«      j                  «       S t        | t        t        f«      r§t        | t        «      s—t        | «      }t	        j                  t        j                  «      j!                  «       }t#        j$                  |«      r|t&        k  s||dz   kD  ry	 t	        j(                  |t        j                  ¬«      j                  «       S y# t        $ r Y yw xY w# t*        t,        t        f$ r Y yw xY w)a?  Coerce a persisted ``updated_at`` (ISO string, legacy epoch, hand edit, garbage) to the
    RFC3339 ``string | null`` that ``/api/status`` promises. ``str``: iff fromisoformat parses
    (trailing ``Z`` tolerated; naive -> UTC). Epoch: before 2000-01-01, > 1 day ahead or
    non-finite -> None. ``bool``/other -> None.)ÚZÚzNéÿÿÿÿz+00:00)Útzinfoi€Q )Útz)re   r\   r½   Úendswithr   Úfromisoformatr˜   rð   rž   r   r‘   ré   r]   r^   r_   r�   r’   ÚmathÚisfiniteÚ_EPOCH_MIN_PLAUSIBLEÚfromtimestampÚOverflowErrorÚOSError)rë   ÚrawÚparsedÚsecondsr�   s        r&   Únormalize_updated_atrý   O  s6  € ô
 �%œÔØ�k‰k‹mˆà�<‰<˜
Ô#Ø�c�r�(˜XÑ%ˆCð	Ü×+Ñ+¨CÓ0ˆFð !Ÿ-š-�×[Ñ[Ó]Ð]¨V¯^©^Ä8Ç<Á<¨^Ó-P×[Ñ[Ó]Ð]Ü�%œ#œu˜Ô&¬z¸%ÄÔ/FÜ˜“,ˆÜ�l‰lœ8Ÿ<™<Ó(×2Ñ2Ó4ˆÜ�}‰}˜WÔ%¨Ô3GÒ)GÈ7ÐUXÐ[`ÑU`ÒK`Øð	Ü×)Ñ)¨'´h·l±lÔC×MÑMÓOÐOð øô ò 	Ùð	ûô œw¬
Ð3ò 	Ùð	ús#   »E Ä$2E' Å	E$Å#E$Å'E>Å=E>Úloop_liveness_watchdogÚshutdown_watchdogÚruntimec                 óÖ   — t        | t        «      r| ni }|j                  d«      dk7  rA|j                  d«      dk(  ry|j                  d«      dk(  r|j                  d«      t        v ryy)a”  What a NOT-running gateway's retained ``gateway_state.json`` says about it now:
    ``"startup_failed"`` (or a watchdog-stamped ``"degraded"``) only while the operator still
    wants it running, else ``"stopped"``.

    ``hermes gateway stop`` keeps the last ``startup_failed`` + ``exit_reason`` on disk for
    diagnostics and records the durable stop intent as ``desired_state``; a profile the operator
    stopped is "stopped", not a current failure. A watchdog exit (``degraded`` + an exit_reason in
    ``WATCHDOG_EXIT_REASONS``) is the same kind of current failure as ``startup_failed`` and is kept
    under the same rule, so the dashboard agrees with ``hermes gateway status``. Any other retained
    state of a dead process (``running``, ``starting``, missing) is just "stopped". Shared by
    ``/api/status`` and ``/api/messaging/platforms`` so the sidebar strip and the Channels page
    cannot disagree.Údesired_stateÚstoppedÚgateway_stateÚstartup_failedÚdegradedÚexit_reason)re   r[   r¼   ÚWATCHDOG_EXIT_REASONS)r   Úrts     r&   Úretained_gateway_stater
  o  sb   € ô ˜w¬Ô-‰°2€BØ	‡v�vˆoÓ )Ò+Ø�6‰6�/Ó"Ð&6Ò6Ø#Ø�6‰6�/Ó" jÒ0°R·V±V¸MÓ5JÔNcÑ5cØØr(   F©ÚforceÚexpected_start_timeÚpidr  r  c          
      ó  — |rRt         s|�J|€t        d| › d�«      ‚t        | «      }|€t        d| › d�«      ‚	 t        ||«      st        d| › d�«      ‚	 |rt         sKt        j                  | |st        j                  «       yt        t        dt        j                  «      «       ydd	lm} 	 t        j                  d
dt        | «      ddgddddd |«       ¬«      }|j"                  dk7  r>|j$                  xs |j&                  xs dj)                  «       }t        |xs d| › �«      ‚y# t        t
        f$ r}t        d| › d�«      |‚d}~ww xY w# t         $ r' t        j                  | t        j                  «       Y yw xY w)u  Terminate a PID; POSIX SIGTERM/SIGKILL, Windows taskkill /T /F for force. Identity guard:
    Windows ``force`` REQUIRES a matching ``expected_start_time`` (taskkill on a recycled PID has
    killed svchost.exe); POSIX optional, but a provided mismatch refuses the kill everywhere.

    On POSIX an expectation is optional, but when the caller provides one and it no longer matches the live
    process, the kill is refused on every platform â€” a mismatched fingerprint always means the PID was
    recycled. See #89614.
    Nzrefusing to force-kill PID z# without a process start-time guardz#; process start time is unavailablez; process identity changedz; malformed start timeÚSIGKILLr   )Úwindows_hide_flagsÚtaskkillz/PIDz/Tz/FTr   rž   é
   )Úcapture_outputÚtextr�   Úerrorsr:   Úcreationflagsrß   ztaskkill failed for PID )Ú_IS_WINDOWSrù   Ú_get_process_start_timeÚ_start_times_agreeÚ	TypeErrorr˜   r�   ÚkillÚsignalÚSIGTERMÚgetattrÚhermes_cli._subprocess_compatr  Ú
subprocessÚrunr\   ÚFileNotFoundErrorÚ
returncodeÚstderrÚstdoutr½   )r  r  r  Úcurrent_start_timerS   r  ÚresultÚdetailss           r&   Úterminate_pidr*  …  s—  € ñ •+Ð!4Ð!@ØÐ&ÜÐ7¸°uÐ<_Ð`ÓaÐaÜ4°SÓ9ÐØÐ%ÜÐ7¸°uÐ<_Ð`ÓaÐað	^Ü%Ð&8Ð:MÔNÜÐ ;¸C¸5Ð@ZÐ[Ó\Ð\ð Oñ •kÜ
�‰�©5”V—^‘^ÔaØô 7>¼fÀiÔQW×Q_ÑQ_Ó6`ÔaØå@ðÜ—‘Ø˜¤ S£¨4°Ð6ÀtÐRVØ Y¸ÑJ\ÓJ^ô
ˆð ×Ñ˜AÒØ—=‘=Ò7 F§M¡MÒ7°R×>Ñ>Ó@ˆÜ�gÒAÐ#;¸C¸5Ð!AÓBÐBð øô! œ:Ð&ò 	^ÜÐ7¸°uÐ<RÐSÓTÐZ]Ð]ûð	^ûô ò Ü
�‰�”V—^‘^Ô$Ùðús)   ¹D, Â//E Ä,EÄ;EÅEÅ-FÆFÚcurrentÚrecordedc                 ól   ‡— t        | «      Š‰dkD  xr" t        ˆfd„t        t         |«      D «       «      S )zXSame process object: all fingerprints > 0 and within 1ms of ``current``; raises on junk.r   c              3   óN   •K  — | ]  }|d kD  xr t        |‰z
  «      dk  –— Œ y­w)r   gü©ñÒMbP?N)Úabs)r‡   ÚrÚcurs     €r&   r‰   z%_start_times_agree.<locals>.<genexpr>±  s*   øè ø€ ÒY¸q˜1˜q™5Ò:¤S¨¨S©£\°UÑ%:Ó:ÑYùs   ƒ"%)r^   ÚallÚmap)r+  r,  r1  s     @r&   r  r  ®  s-   ø€ ä
�‹.€CØ�‰7ÒY”sÓYÄCÌÈxÓDXÔYÓYÐYr(   éÈ   Ú	tolerancec                 óH   — t        t        |«      t        | «      z
  «      |k  S )a  Liveness-reconciliation comparator for :func:`get_process_start_time` fingerprints: the
    recorded owner and the current reading are the same incarnation when they agree within
    ``tolerance``. Raises on junk; callers decide what an unreadable (``None``) side means.)r/  r]   )r,  r+  r5  s      r&   Ústart_time_fingerprints_matchr7  »  s!   € ô Œs�7‹|œc (›mÑ+Ó,°	Ñ9Ð9r(   Úidentityc                 ól   — t        j                  | j                  d«      «      j                  «       d d S )Nr   é   )ÚhashlibÚsha256ÚencodeÚ	hexdigest)r8  s    r&   Ú_scope_hashr?  Â  s*   € Ü�>‰>˜(Ÿ/™/¨'Ó2Ó3×=Ñ=Ó?ÀÀÐDÐDr(   Úscopec                 ó:   — t        «       | › dt        |«      › d�z  S )NÚ-z.lock)rç   r?  )r@  r8  s     r&   Ú_get_scope_lock_pathrC  Æ  s"   € Ü‹? ˜w a¬°HÓ(=Ð'>¸eÐDÑDÐDr(   c                 óv  — t        j                  t        t        t        «      5  t        t        d| › d�«      j                  d¬«      j                  «       d   «      cddd«       S # 1 sw Y   nxY w	 ddl	}t        t        |j                  | «      j                  «       dz  «      «      S # t        $ r Y yw xY w)	zâPer-process start-time fingerprint (PID-reuse guard), or None: ``/proc/<pid>/stat`` field 22
    on Linux, else psutil ``create_time()`` in centiseconds. Units differ per platform; the guard
    only compares same-host values.ú/proc/ú/statr   r€   é   Nr   éd   )r–   r—   Ú
IndexErrorr˜   rù   r]   r   r”   ÚsplitÚpsutilÚroundÚProcessÚcreate_timer¡   ©r  rK  s     r&   r  r  Ê  s£   € ô 
×	Ñ	œZ¬´WÓ	=ñ VÜ”4˜&   UÐ+Ó,×6Ñ6ÀÐ6ÓH×NÑNÓPÐQSÑTÓU÷V÷ Vò VúðÛÜ”5˜Ÿ™¨Ó,×8Ñ8Ó:¸SÑ@ÓAÓBÐBøÜò Ùðús   ¤9A'Á'A0Á47B, Â,	B8Â7B8c                 ó   — t        | «      S )zBPublic wrapper for retrieving a process start time when available.)r  ©r  s    r&   Úget_process_start_timerR  ×  s   € ä" 3Ó'Ð'r(   c           	      ód  — t        j                  t        «      5  t        d| › d�«      j	                  «       }|r:|j                  dd«      j                  dd¬«      j                  «       cddd«       S 	 ddd«       t        j                  t        «      5  d	dl	}|j                  | «      j                  «       }|rd
j                  |«      cddd«       S 	 ddd«       t        s§t        j                  t        t        j                  «      5  t        j                   ddt#        | «      ddgddddd¬«      }|j$                  d	k(  r=|j&                  j                  «       r#|j&                  j                  «       cddd«       S ddd«       yy# 1 sw Y   �ŒxY w# 1 sw Y   ŒÄxY w# 1 sw Y   yxY w)u  Process command line as one string: /proc, then psutil, then ``ps``.

    Order is by cost, and this runs per live gateway on every roster/status poll. ``psutil`` reads
    the process table in-process (a ``sysctl`` on macOS) where ``ps`` costs a fork+exec â€” measured
    0.02ms against 4.2ms on macOS for the same string. It cannot always answer: on macOS it raises
    ``AccessDenied`` for a process owned by another user, which ``ps`` still reports, so ``ps``
    stays as the fallback rather than being replaced.rE  z/cmdlineó    ó    r   Úignore)r  Nr   Ú Úpsú-pú-ozcommand=Trž   é   ©r  r  r�   r  r:   )r–   r—   rù   r   Ú
read_bytesrž   Údecoder½   r¡   rK  rM  Úcmdlinerœ   r  r!  ÚTimeoutExpiredr"  r\   r$  r&  )r  rú   rK  Úcmdline_partsr(  s        r&   Ú_read_process_cmdlinerb  Ü  sw  € ô 
×	Ñ	œWÓ	%ñ WÜ�V˜C˜5 Ð)Ó*×5Ñ5Ó7ˆÙØ—;‘;˜w¨Ó-×4Ñ4°WÀXÐ4ÓN×TÑTÓV÷Wñ Wà÷Wô 
×	Ñ	œYÓ	'ñ +ÛØŸ™ sÓ+×3Ñ3Ó5ˆÙØ—8‘8˜MÓ*÷	+ñ +ð ÷+õ
 Ü× Ñ ¤¬*×*CÑ*CÓDñ 	-Ü—^‘^Ø�tœS ›X t¨ZÐ8Ø#¨$¸ÈÐ\]ôˆFð × Ñ  AÒ%¨&¯-©-×*=Ñ*=Ô*?Ø—}‘}×*Ñ*Ó,÷	-÷ 	-ñ 	-ñ ÷#Wñ Wú÷+ð +ú÷	-ð ús%   šAFÂ6FÄA,F&ÆFÆF#Æ&F/Úcommandc                 óè  — | sy	 t        j                  | d¬«      }|D �cg c]1  }|j                  d«      j	                  dd«      j                  «       ‘Œ3 }}|sy|D �cg c]  }|j                  dd«      d   ‘Œ }}|d	   d
k(  ryt        d„ |D «       «      ryt        d„ |D «       «      rydj                  |«      }d|vrd|vrt        d„ |D «       «      syg }d}|D ]1  }|rd}Œ|dv rd}Œ|j                  d«      rŒ!|j                  |«       Œ3 t        |«      D ])  \  }	}|dk(  sŒ|	dz   t        |«      k  r
||	dz      c S dc S  y# t        $ r | j                  «       }Y �ŒJw xY wc c}w c c}w )aÒ  Hermes gateway lifecycle subcommand from a command line, or None. No loose substring matches
    (``"gateway" in cmdline`` also matched ``gateway status`` / ``python -m tui_gateway``): needs a
    Hermes entrypoint plus the ``gateway`` subcommand, or a gateway-dedicated entrypoint. Tokenizes
    quote-aware (Windows paths with spaces); ``--profile``/``-p`` selectors are stripped anywhere in
    argv since ``_apply_profile_override`` removes them before argparse.NF)Úposixz"'ú\ú/r-   rï   r   Ú	osascriptc              3   óL   K  — | ]  }|d k(  xs |j                  d«      –— Œ y­w)zgateway/run.pyz/gateway/run.pyN)rò   )r‡   Úts     r&   r‰   z._gateway_command_subcommand.<locals>.<genexpr>  s(   è ø€ Ò
RÀaˆ1Ð Ñ ÒA A§J¡JÐ/@Ó$AÓAÑ
Rùs   ‚"$r"  c              3   ó$   K  — | ]  }|d v –— Œ
 y­w))r   zhermes-gateway.exeNr`   ©r‡   Úbs     r&   r‰   z._gateway_command_subcommand.<locals>.<genexpr>  s   è ø€ Ò
L¸Qˆ1Ð8Ô8Ñ
Lùó   ‚rW  zhermes_cli.mainzhermes_cli/main.pyc              3   ó$   K  — | ]  }|d v –— Œ
 y­w))rà   z
hermes.exeNr`   rl  s     r&   r‰   z._gateway_command_subcommand.<locals>.<genexpr>  s   è ø€ ò ZØ*+ˆÐ%Ô%ñZùrn  )ú	--profilerY  T)ú
--profile=z-p=Úgateway)ÚshlexrJ  r˜   r½   rž   ÚlowerÚrsplitÚanyrœ   Ú
startswithr™   Ú	enumeraterŸ   )
rc  Ú
raw_tokensrj  ÚtokensÚ	basenamesÚjoinedÚfilteredÚ	skip_nextÚtokenÚis
             r&   Ú_gateway_command_subcommandr�  ø  s§  € ñ Øð%Ü—[‘[ °Ô6ˆ
ð BLÖL¸Aˆa�g‰g�e‹n×$Ñ$ T¨3Ó/×5Ñ5Õ7ÐL€FÐLÙØØ/5Ö6¨!�—‘˜#˜qÓ! "Ó%Ð6€IÐ6ð ��|�{Ò"Øä
Ñ
RÈ6Ô
RÔRØÜ
Ñ
LÀ)Ô
LÔLØØ�X‰X�fÓ€FØ Ñ&Ð+?ÀvÑ+MÔVYñ ZØ/8ôZô Wð à€HØ€IØò #ˆÙØ‰IØÐ)Ñ)Ø‰IØ×!Ñ!Ð"7Õ8Ø�O‰O˜EÕ"ð#ô ˜hÓ'ò G‰ˆˆ5Ø�IÓà&'¨!¡e¬c°(«mÒ&;�8˜A ™E‘?ÒFÀÒFðGð øôG ò %Ø—]‘]“_‹
ð%üò Mùò 7s   …E
  6E*ÁE/Å
E'Å&E'c                 ó   — t        | «      dk(  S )z:True only for a real ``gateway run`` process command line.r"  ©r�  ©rc  s    r&   Úlooks_like_gateway_command_liner…  (  s   € ä& wÓ/°5Ñ8Ð8r(   c                 ó   — t        | «      dv S )a  True for command lines that can host the runtime (``run`` or ``restart``: without a service
    manager the manual restart fallback runs ``run_gateway()`` in-process). For validating
    Hermes-owned records / cleanup scans only; ``looks_like_gateway_command_line`` stays strict.>   r"  Úrestartrƒ  r„  s    r&   Ú'looks_like_gateway_runtime_command_linerˆ  -  s   € ô ' wÓ/Ð3EÐEÐEr(   c                 óH   — t        | «      }t        |«      xr t        |«      S )z;True when the live PID still looks like the Hermes gateway.)rb  r_   r…  )r  r_  s     r&   Ú_looks_like_gateway_processrŠ  4  s!   € ä# CÓ(€GÜ�‹=ÒEÔ<¸WÓEÐEr(   c                 ó¼   — | j                  d«      }| j                  d«      t        k7  st        |t        «      r|syt	        dj                  d„ |D «       «      «      S )zMValidate gateway identity from PID-file metadata when cmdline is unavailable.ÚargvÚkindFrW  c              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrT   ©r\   )r‡   Úparts     r&   r‰   z-_record_looks_like_gateway.<locals>.<genexpr>?  s   è ø€ Ò;WÈ$¼CÀ¿IÑ;WùrŠ   )r¼   Ú_GATEWAY_KINDre   Úlistrˆ  rœ   )r¹   rŒ  s     r&   Ú_record_looks_like_gatewayr“  :  sK   € à�:‰:�fÓ€DØ‡z�z�&Óœ]Ò*´*¸TÄ4Ô2HÑPTØÜ2°3·8±8Ñ;WÐRVÔ;WÓ3WÓXÐXr(   Úprofile_homec                 óP   — | j                   j                  dk(  r| j                  S dS )zYProfile id for ``<root>/profiles/<name>``; None for the root/default home (bare gateway).rÃ   N)rŽ   r0   )r”  s    r&   Ú_profile_name_for_homer–  B  s&   € à ,× 3Ñ 3× 8Ñ 8¸JÒ Fˆ<×ÑÐPÈDÐPr(   c                 óØ   — | j                  «       }t        |«      D ]L  \  }}|j                  d«      r|j                  d«      d   c S |dv sŒ2|dz   t	        |«      k  sŒD||dz      c S  y)zÄThe ``-p``/``--profile`` argument of a command line, or None. Token equality is the only safe
    profile match: a substring test lets ``-p ops`` claim (and ``gateway stop`` SIGTERM) ``-p ops-2``.rq  Ú=rŒ   )rY  rp  r-   N)rJ  rx  rw  Ú	partitionrŸ   )rc  rz  r€  Útoks       r&   Úprofile_flag_valuer›  G  sr   € ð �]‰]‹_€FÜ˜FÓ#ò !‰ˆˆ3Ø�>‰>˜,Ô'Ø—=‘= Ó% aÑ(Ò(ØÐ%Ò%¨!¨a©%´#°f³+Ó*=Ø˜!˜a™%‘=Ò ð	!ð
 r(   z4(?:^|\s)hermes_home=(?:\"([^\"]*)\"|'([^']*)'|(\S+))c                 ó¬   — t         j                  | «      D �cg c]1  }t        d„ |j                  «       D «       «      j	                  d«      ‘Œ3 c}S c c}w )aT  Values of every ``HERMES_HOME=<value>`` assignment in ``command`` (the caller lowercases
    and normalizes separators). Values are token-bounded, quotes stripped: the substring test
    this replaces let ``HERMES_HOME=/root/profiles/ops`` claim a ``/root/profiles/ops2`` gateway.
    The name is token-bounded too (``FOO=hermes_home=/x`` is not an assignment), and a trailing
    separator on the value is stripped -- ``HERMES_HOME=/root/.hermes/`` (systemd ``Environment=``
    or a shell wrapper spelling) is the same home as ``/root/.hermes``; callers strip the profile
    home the same way.c              3   ó&   K  — | ]	  }|€Œ|–— Œ y ­wrT   r`   )r‡   Úgs     r&   r‰   z*hermes_home_assignments.<locals>.<genexpr>_  s   è ø€ Ò4�1 a¡mŒQÑ4ùs   ‚Šrg  )Ú_HERMES_HOME_ASSIGNMENT_REÚfinditerÚnextÚgroupsÚrstrip)rc  Úms     r&   Úhermes_home_assignmentsr¥  V  sK   € ô ,×4Ñ4°WÓ=öàô 	Ñ4˜Ÿ™›
Ô4Ó4×;Ñ;¸CÕ@òð ùò s   ˜6AÚ
command_lcÚhome_lcc                 ó|   — |t        | «      v ryt        j                  dt        j                  |«      › d�| «      duS )ad  True when ``command_lc`` carries ``HERMES_HOME=<home_lc>`` (both lowercased, ``/``-separated,
    no trailing separator). Argv reaches us space-joined, so an unquoted value with a space in it
    (``HERMES_HOME=C:/Users/John Doe/.hermes``) is cut at the space by the token parser; a
    token-bounded literal match of the whole home recovers that spelling.Tz(?:^|\s)hermes_home=z
/?(?=\s|$)N)r¥  ÚreÚsearchÚescape)r¦  r§  s     r&   Úcommand_line_names_hermes_homer¬  d  s@   € ð
 Ô)¨*Ó5Ñ5ØÜ�9‰9Ð,¬R¯Y©Y°wÓ-?Ð,@À
ÐKÈZÓXÐ`dÐdÐdr(   c                 ót  — | j                  «       j                  dd«      }t        |«      }t        |«      j                  «       j                  dd«      j	                  d«      }|�.|dk7  r)t        |«      |j                  «       k(  ryt        ||«      S t        |«      �yt        |«       xs t        ||«      S )aY  True when a gateway command line belongs to ``profile_home`` (mirrors
    ``hermes_cli.gateway._matches_current_profile``): a stale state file can record a PID recycled
    onto ANOTHER profile's live gateway. Named profiles carry ``-p``/``--profile <name>`` or
    ``HERMES_HOME=`` on argv; the default gateway runs bare. Separators normalized.rf  rg  rÄ   TF)rt  rž   r–  r\   r£  r›  r¬  r¥  )rc  r”  r¦  Úprofile_namer§  s        r&   Ú _command_line_belongs_to_profiler¯  n  s±   € ð
 —‘“×(Ñ(¨¨sÓ3€JÜ)¨,Ó7€LÜ�,Ó×%Ñ%Ó'×/Ñ/°°cÓ:×AÑAÀ#ÓF€GØÐ L°IÒ$=Ü˜jÓ)¨\×-?Ñ-?Ó-AÒAØÜ-¨j¸'ÓBÐBô
 ˜*Ó%Ð1ØÜ& zÓ2Ð2ÒiÔ6TÐU_ÐahÓ6iÐir(   c                 óœ   — 	 ddl m}m}  |«       }|duxr( |j                  | k(  xr |j                   ||«      «      S # t        $ r Y yw xY w)uÙ  Does the ONE host gateway â€” PID ``pid`` â€” serve ``profile_home``'s profile?

    Argv cannot answer this: the host singleton runs ONE home's (usually bare/default) command line
    while multiplexing every profile, so :func:`_command_line_belongs_to_profile` rejects every
    secondary and the profile reads as "not running" while its messages are being served. The live
    served set is the only proof; the argv rule stays as the fallback when no record exists.
    r   )Úhost_gatewayÚprofile_name_for_homeFN)Úgateway.host_attachr±  r²  r¡   r  Úserves)r  r”  r±  r²  Úowners        r&   Ú_host_gateway_serves_homer¶  ƒ  sU   € ðßKá“ˆð ˜ÐÒg §¡¨cÑ!1Òg°e·l±lÑCXÐYeÓCfÓ6gÐgøô ò Ùðús   ‚? ¿	AÁ
Ac                óŒ   — t        |«      }|st        | «      S t        |«      sy|�t        ||«      ry|du xs t	        ||«      S )uJ  True when a live PID still identifies as this gateway record. The live command line wins (a
    stale record's argv must not make a recycled PID count as a gateway; with ``expected_home`` it
    must also belong to that profile â€” or serve it as the host multiplexer); unreadable cmdline
    (Windows/EACCES) -> persisted record.FNT)rb  r“  rˆ  r¶  r¯  )r¹   r  r¸   Úlive_cmdlines       r&   Ú _record_matches_live_gateway_pidr¹  ”  sS   € ô )¨Ó-€LÙÜ)¨&Ó1Ð1Ü2°<Ô@ØØÐ Ô%>¸sÀMÔ%RØØ˜DÐ ÒaÔ$DÀ\ÐS`Ó$aÐar(   c            
      óÖ   — t        j                  «       t        t        t        j
                  «      t        t        j                  «       «      t        t        t        «       «      «      dœS )N)r  r�  rŒ  Ú
start_timer»   )
r�   Úgetpidr‘  r’  ÚsysrŒ  r  r\   r°   rª   r`   r(   r&   Ú_build_pid_recordr¾  ¥  sC   € ä�y‰y‹{¤M¼4ÄÇÁ»>Ü-¬b¯i©i«kÓ:ô Ô1Ô2JÓ2LÓMÓNñð r(   c                  ó†   — 	 ddl m}   | «       }|j                  d«      |j                  d«      dœS # t        $ r i cY S w xY w)u  Code identity of THIS process for ``gateway_state.json`` (restart picked up new code?).
    Lazy import keeps ``gateway.status`` free of ``hermes_cli`` at import time. Never raises.

    A gateway keeps serving the module versions it imported at startup, so stamping the identity into
    ``gateway_state.json`` lets `hermes update` (and the dashboard) prove whether a running gateway actually
    picked up new code after the restart phase â€” instead of assuming it did (#88654, #69754). Never raises;
    degrades to absent fields.
    r   )Úget_code_identityÚshaÚversion)Úcode_shaÚcode_version)Úhermes_cli.build_inforÀ  r¼   r¡   )rÀ  r8  s     r&   Ú_get_code_identity_fieldsrÆ  ¯  sC   € ðÝ;Ù$Ó&ˆØ$ŸL™L¨Ó/ÀÇÁÈiÓAXÑYÐYøÜò ØŠ	ðús   ‚/2 ²A ¿A c                 óx   — t        | t        «      sy| j                  d«      }| xs t        |t	        «       «      S )z¸True when the record's ``hermes_home`` matches the current process (legacy records: True);
    another HERMES_HOME's record must be ignored or the default gateway assumes its identity.Fr»   )re   r[   r¼   r¶   rª   )r¹   Úrecord_homes     r&   Ú&_pid_record_belongs_to_current_profilerÉ  À  s8   € ô �fœdÔ#ØØ—*‘*˜]Ó+€KØˆ?ÒXÔ/°Ô=UÓ=WÓXÐXr(   c            	      óV   — i t        «       ¥dd ddi ddit        «       dœ¥t        «       ¥S )NÚstartingFr   ÚstatusÚunknown)r  r  Úrestart_requestedÚactive_agentsÚ	platformsÚsession_storeÚ
updated_at)r¾  rê   rÆ  r`   r(   r&   Ú_build_runtime_status_recordrÓ  É  sA   € ðÜ
Ó
ðØ0:È4Ø"°QÀRØ" IÐ.¼l»nòô $Ó
%ð	ð r(   ©Úbare_pid_okrÕ  c                óÆ  — 	 | j                  «       r | j                  d¬«      j                  «       nd}|sy	 t        j                  |«      }|rt        |t        «      rd|iS t        |t        «      r|S dS # t        t        f$ r Y yw xY w# t
        j                  $ rA d}|r:t        j                  t        «      5  t        |«      }ddd«       n# 1 sw Y   nxY wY Œ’w xY w)z˜JSON object at ``path``, or None when absent/empty/unreadable/invalid. ``bare_pid_ok`` also
    accepts legacy bare-integer PID files as ``{"pid": N}``.r   r€   rß   Nr  )r“   r”   r½   rù   ÚUnicodeDecodeErrorÚjsonÚloadsÚJSONDecodeErrorr–   r—   r˜   r]   re   r[   )r)   rÕ  rú   r*   s       r&   rd   rd   Ò  sÕ   € ðØ:>¿+¹+¼-ˆd�n‰n gˆnÓ.×4Ñ4Ô6ÈRˆñ Øð#Ü—*‘*˜S“/ˆñ ”z '¬3Ô/Ø�wÐÐÜ  ¬$Ô/ˆ7Ð9°TÐ9øô Ô'Ð(ò Ùðûô ×Ñò #ØˆÙÜ×$Ñ$¤ZÓ0ñ #Ü˜c›(�÷#÷ #ñ #üð#ús:   ‚2A7 ¸B Á7B	ÂB	Â0C Â<CÃ	C ÃC	ÃC ÃC c                 ó"   — t        | |d d¬«       y )N)ú,ú:)ÚindentÚ
separatorsr   )r)   r*   s     r&   rK   rK   ç  s   € Ü�d˜G¨D¸ZÖHr(   c                 ó„   — t        j                  t        «      5  | j                  d¬«       d d d «       y # 1 sw Y   y xY w)NT©Ú
missing_ok)r–   r—   rù   Úunlinkr¯   s    r&   Ú_unlink_quietlyrä  ë  s3   € Ü	×	Ñ	œWÓ	%ñ %Ø�‰˜tˆÔ$÷%÷ %ñ %ús   š6¶?c                 ó4   — t        | xs
 t        «       d¬«      S ©NTrÔ  )rd   rÒ   r×   s    r&   Ú_read_pid_recordrç  ð  s   € Ü˜8Ò6¤}£ÀDÔIÐIr(   Ú	lock_pathc                 ó4   — t        | xs
 t        «       d¬«      S ræ  )rd   rØ   )rè  s    r&   Ú_read_gateway_lock_recordrê  ô  s   € Ü˜9Ò@Ô(>Ó(@ÈdÔSÐSr(   Úkeyc                 óT   — 	 t        | |   «      S # t        t        t        f$ r Y y w xY wrT   )r]   ÚKeyErrorr  r˜   )r¹   rë  s     r&   Ú_pid_from_recordrî  ø  s/   € ðÜ�6˜#‘;ÓÐøÜ”i¤Ð,ò Ùðús   ‚ �'¦'Úrecorded_startÚcurrent_startc                 ó   — d| |fvxr || k7  S )zFPID-reuse guard: True only when BOTH start times are known and differ.Nr`   )rï  rð  s     r&   Ú_start_times_conflictrò  ÿ  s   € à˜¨Ð6Ð6ÒZ¸=ÈNÑ;ZÐZr(   c                 ó‚   — t        | «      }|�t        |«      syt        | j                  d«      t	        |«      «      ry|S )zSRecord's PID when it is alive and passes the start-time PID-reuse guard, else None.Nr»  )rî  Ú_pid_existsrò  r¼   r  )r¹   r  s     r&   Ú_live_pid_from_recordrõ    s<   € ä
˜6Ó
"€CØ
€{œ+ cÔ*ØÜ˜VŸZ™Z¨Ó5Ô7NÈsÓ7SÔTØØ€Jr(   c                  ób   — t         5  t        j                  «        d d d «       y # 1 sw Y   y xY wrT   )Ú_gateway_running_pid_cache_lockr   Úclearr`   r(   r&   Ú_clear_running_pid_cacherù    s&   € Ü	(ñ +Ü"×(Ñ(Ô*÷+÷ +ñ +ús   ‡%¥.c                 ót   — 	 | j                  «       }d|j                  |j                  fS # t        $ r Y yw xY w)N)FNNT)Ústatrù   Úst_mtime_nsÚst_size)r)   Ústs     r&   Ú_file_cache_signaturerÿ    s=   € ð#Ø�Y‰Y‹[ˆð �"—.‘. "§*¡*Ð-Ð-øô ò #Ù"ð#ús   ‚+ «	7¶7Úcleanup_stalec                óÂ   — |syt        «        | t        | «      fD ]6  }t        j                  t        «      5  |j                  d¬«       ddd«       Œ8 y# 1 sw Y   ŒCxY w)zXForce-unlink a stale PID file + sibling lock (lock confirmed inactive, so no pid check).NTrá  )rù  rØ   r–   r—   r¡   rã  )rÓ   r   r)   s      r&   Ú_cleanup_invalid_pid_pathr    s^   € áØÜÔØÔ1°(Ó;Ð<ò )ˆÜ× Ñ ¤Ó+ñ 	)Ø�K‰K 4ˆKÔ(÷	)ð 	)ñ)÷	)ð 	)ús   ·AÁA	c                 óü  — 	 t         r�| j                  dt        j                  «       | j	                  «       dk(  r!| j                  d«       | j                  «        | j                  t        «       t        j                  | j                  «       t        j                  d«       yt        j                  | j                  «       t        j                  t        j                  z  «       y# t         t"        f$ r Y yw xY w)Nr   r„   r-   TF)r  Úseekr�   ÚSEEK_ENDÚtellÚwriterG   Ú_WINDOWS_LOCK_OFFSETÚmsvcrtÚlockingÚfilenoÚLK_NBLCKÚfcntlÚflockÚLOCK_EXÚLOCK_NBÚBlockingIOErrorrù   ©Úhandles    r&   Ú_try_acquire_file_lockr  %  s¨   € ðÝØ�K‰K˜œ2Ÿ;™;Ô'Ø�{‰{‹} Ò!Ø—‘˜TÔ"Ø—‘”Ø�K‰KÔ,Ô-Ü�N‰N˜6Ÿ=™=›?¬F¯O©O¸QÔ?ð ô �K‰K˜Ÿ™›¬¯©¼¿¹Ñ)FÔGØøÜœWÐ%ò Ùðús   ‚B"C) Â%AC) Ã)C;Ã:C;c                 óÖ  — t        | «      } 	 ddl}t        j                  dk7  }	 |r-|j	                  | «      j                  «       |j                  k(  ryt        |j                  | «      «      S # t        |dd«      $ r Y yt        $ r Y Œ6w xY w# t        $ r Y nw xY wt        rt        | «      S t        | «      ry	 t        j                  | d«       y# t         $ r Y yt"        $ r Y yw xY w)ax  Cross-platform "is this PID alive" check that does NOT kill the target. CRITICAL on Windows:
    ``os.kill(pid, 0)`` sends ``CTRL_C_EVENT`` to the whole console group (bpo-14484), so prefer
    psutil, then ctypes ``OpenProcess`` (Windows) / ``os.kill(pid, 0)`` (POSIX). Zombies report
    dead: treating one as alive makes --replace wait forever under systemd Restart=always.r   NÚntFÚNoSuchProcessr`   T)r]   rK  r�   r0   rM  rÌ  ÚSTATUS_ZOMBIEr  r¡   r_   Ú
pid_existsÚImportErrorr  Ú_pid_exists_win32_ctypesÚ_posix_is_zombier  ÚPermissionErrorrù   )r  rK  Úprobe_zombies      r&   rô  rô  5  sò   € ô
 ˆc‹(€CðÛô
 —w‘w $‘ˆð	ñ  §¡¨sÓ 3× :Ñ :Ó <À×@TÑ@TÒ TØô
 �F×%Ñ% cÓ*Ó+Ð+øô	 �v˜°Ó3ò 	ÙÜò 	Ùð	ûô ò ÙðúåÜ'¨Ó,Ð,Ü˜ÔØðÜ
�‰��QŒð
 øô	 ò ÙÜò ÙðúsX   �B ¥.A. ÁB Á.BÁ>B Â BÂB Â
BÂB Â	BÂBÂ;C Ã	C(ÃC(Ã'C(c                 óü  — 	 t        d| › d�«      j                  d¬«      j                  «       }t        |«      dkD  xr |d   dk(  S # t        $ r– t        j                  t        «      5  t        j                  ddd	d
t        | «      gddddd¬«      }|j                  dk(  xr) |j                  j                  «       j                  d«      cddd«       cY S # 1 sw Y   Y yxY wt        t         t"        f$ r Y yw xY w)zWZombie via ``/proc/<pid>/stat`` field 3, or ``ps -o state=`` without /proc (macOS/BSD).rE  rF  r   r€   rŒ   rí   rX  rZ  zstate=rY  Trž   r[  r\  r   NF)r   r”   rJ  rŸ   r#  r–   r—   r¡   r!  r"  r\   r$  r&  r½   rw  rI  r  rù   )r  Ústat_fieldsr0  s      r&   r  r  a  sù   € ðÜ˜V C 5¨Ð.Ó/×9Ñ9À7Ð9ÓK×QÑQÓSˆÜ�;Ó !Ñ#Ò=¨°A©¸#Ñ(=Ð=øÜò JÜ× Ñ ¤Ó+ñ 	Jô —‘Ø�t˜X t¬S°«XÐ6Ø#¨$¸ÈÐ\]ôˆAð —<‘< 1Ñ$ÒI¨¯©¯©Ó)9×)DÑ)DÀSÓ)I÷	J÷ 	Jô 	Jð úô œ¬Ð1ò ØØðús1   ‚AA Á"C;Á)A#CÃ
C;ÃC"	ÃC;Ã"C;Ã:C;c                 óè  — 	 ddl }|j                  j                  }|j                  |j                  _        |j                  |j                  _        |j                  |j                  _        d\  }}d\  }}|j	                  ||z  d| «      }|s|j                  «       |k(  S 	 |j                  |d«      |k(  |j                  |«       S # |j                  |«       w xY w# t        t        f$ r Y yw xY w)zGpsutil-free Windows liveness probe via OpenProcess/WaitForSingleObject.r   N)i   r   )i  r[  F)ÚctypesÚwindllÚkernel32Úc_void_pÚOpenProcessÚrestypeÚc_uintÚWaitForSingleObjectÚGetLastErrorÚCloseHandlerù   ÚAttributeError)r  r"  r$  Ú!PROCESS_QUERY_LIMITED_INFORMATIONÚSYNCHRONIZEÚWAIT_TIMEOUTÚERROR_ACCESS_DENIEDr  s           r&   r  r  y  sè   € ðÛØ—=‘=×)Ñ)ˆà'-§¡ˆ×ÑÔ$Ø/5¯}©}ˆ×$Ñ$Ô,Ø(.¯©ˆ×ÑÔ%Ø9IÑ6Ð)¨;Ø,9Ñ)ˆÐ)Ø×%Ñ%Ð&GÈ+Ñ&UÐW\Ð^aÓbˆÙð ×(Ñ(Ó*Ð.AÑAÐAð	)à×/Ñ/°¸Ó:¸lÑJà× Ñ  Õ(øˆH× Ñ  Õ(ûÜ”^Ð$ò Ùðús*   ‚BC Â#C	 Â7C Ã	CÃC ÃC1Ã0C1c                 ób  — t        j                  t        «      5  t        rI| j	                  t
        «       t        j                  | j                  «       t        j                  d«       n2t        j                  | j                  «       t        j                  «       d d d «       y # 1 sw Y   y xY w)Nr-   )r–   r—   rù   r  r  r  r	  r
  r  ÚLK_UNLCKr  r  ÚLOCK_UNr  s    r&   Ú_release_file_lockr4  ’  sg   € Ü	×	Ñ	œWÓ	%ñ 8ÝØ�K‰KÔ,Ô-Ü�N‰N˜6Ÿ=™=›?¬F¯O©O¸QÕ?ä�K‰K˜Ÿ™›¬¯©Ô7÷8÷ 8ñ 8ús   šBB%Â%B.c                  óŒ  — t         �yt        «       } | j                  j                  dd¬«       	 t	        | dd¬«      }t        |«      s|j                  «        y|j                  d«       |j                  «        t        j                  t        «       |«       |j                  «        t!        j"                  t        «      5  t%        j&                  |j)                  «       «       ddd«       |a t+        «        y# t
        $ r3 	 | j                  «        t	        | dd¬«      }n# t        $ r Y Y yw xY wY Œøw xY w# 1 sw Y   ŒUxY w)	zMClaim the cross-process runtime lock; the OS releases it if the process dies.NTr|   úa+r   r€   Fr   )Ú_gateway_lock_handlerØ   rŽ   r�   Úopenr  rã  rù   r  Úcloser  ÚtruncaterØ  Údumpr¾  rG   r–   r—   r�   Úfsyncr  rù  )r)   r  s     r&   Úacquire_gateway_runtime_lockr=  ›  s  € ô Ð'ØÜ!Ó#€DØ‡K�K×Ñ˜d¨TÐÔ2ð	Ü�d˜D¨7Ô3ˆô " &Ô)Ø�‰ŒØØ
‡K�K�„NØ
‡O�OÔÜ‡I�IÔÓ! 6Ô*Ø
‡L�L„NÜ	×	Ñ	œWÓ	%ñ "Ü
�‰�—‘“Ô!÷"à!ÐÜÔØøô' ò ð	Ø�K‰KŒMÜ˜$ ¨wÔ7‰FøÜò 	Úð	úñ ðú÷"ð "úsA   °C; Ã$D:Ã;	D7ÄD$Ä#D7Ä$	D1Ä-D7Ä0D1Ä1D7Ä6D7Ä:Ec                  óÖ   — t         dc} a | €yt        | «       t        j                  t        «      5  | j                  «        ddd«       t        «        y# 1 sw Y   t        «        yxY w)z<Release the gateway runtime lock when owned by this process.N)r7  r4  r–   r—   rù   r9  rù  r  s    r&   Úrelease_gateway_runtime_lockr?  º  sT   € ô $8¸Ð €FÐ Ø€~ØÜ�vÔÜ	×	Ñ	œWÓ	%ñ Ø�‰Œ÷äÕ÷äÕús   ±AÁA(c                  ó   — t         duS )z»True when THIS process holds the runtime lock. ``is_gateway_runtime_lock_active`` answers
    "does anyone?"; re-probing our own flock succeeds on POSIX, so only the handle discriminates.N)r7  r`   r(   r&   Úowns_gateway_runtime_lockrA  Æ  s   € ô   tÐ+Ð+r(   c                 ó<  — 	 t        | «       }|st        | «       |t        j                  t        «      5  | j                  «        ddd«       S # 1 sw Y   S xY w# t        j                  t        «      5  | j                  «        ddd«       w # 1 sw Y   w xY wxY w)zVTrue when another process holds the lock (a won probe is released); closes ``handle``.N)r  r4  r–   r—   rù   r9  )r  Úhelds     r&   Ú_probe_lock_filerD  Ì  sy   € ðÜ)¨&Ó1Ð1ˆÙÜ˜vÔ&Øä× Ñ ¤Ó)ñ 	Ø�L‰LŒN÷	÷ 	ñ 	ûŒZ× Ñ ¤Ó)ñ 	Ø�L‰LŒN÷	÷ 	ñ 	ýs.   ‚A µAÁA	ÁBÁ5BÂ	BÂBÂBc                 óÐ   — | xs
 t        «       }t        �|t        «       k(  ry|j                  «       sy	 t        |dd¬«      }t        |«      S # t        $ r t        |«       Y yw xY w)z?True when some process currently owns the gateway runtime lock.TFr6  r   r€   )rØ   r7  r“   r8  r  rä  rD  )rè  Úresolved_lock_pathr  s      r&   Úis_gateway_runtime_lock_activerG  Ø  ss   € à"Ò>Ô&<Ó&>ÐÜÐ'Ð,>ÔBXÓBZÒ,ZØØ×$Ñ$Ô&ØðÜÐ(¨$¸ÔAˆô
 ˜FÓ#Ð#øô	 ò äÐ*Ô+Ùðús   µA ÁA%Á$A%Úlabelc                 ó~   — 	 | j                  «        y# t        $ r Y yt        $ r}t        |› d|› �«      |‚d}~ww xY w)zWLike ``path.exists()`` but raises RuntimeError instead of False on EACCES-style errors.TFz metadata is not inspectable: N)rû  r#  rù   ÚRuntimeError)r)   rH  rS   s      r&   Ú_strict_path_existsrK  è  sJ   € ðSØ�	‰	ŒØøÜò ÙÜò SÜ˜e˜WÐ$BÀ3À%ÐHÓIÈsÐRûðSús   ‚ “	<ž<¦7·<c                 óÌ   — 	 t        | dd¬«      }	 t	        |«      S # t        $ r Y yt        $ r}t        d|› �«      |‚d}~ww xY w# t        $ r}t        d|› �«      |‚d}~ww xY w)z<Probe ownership without treating access failures as absence.zr+r   r€   Fz)gateway runtime lock is not inspectable: Nz#gateway runtime lock probe failed: )r8  r#  rù   rJ  rD  )rè  r  rS   s      r&   Ú&_is_gateway_runtime_lock_active_strictrM  ó  s„   € ðWÜ�i °Ô8ˆð
QÜ Ó'Ð'øô ò ÙÜò WÜÐFÀsÀeÐLÓMÐSVÐVûðWûô ò QÜÐ@ÀÀÐFÓGÈSÐPûðQús.   ‚ ‘
A œ	A§A¯>¾AÁ	A#ÁAÁA#c                  óŽ   — t        «       } | j                  j                  dd¬«       t        | t	        «       «       t        «        y)zWrite this process's PID record via O_CREAT|O_EXCL; a racing gateway's FileExistsError
    propagates for the caller to decide.Tr|   N)rÒ   rŽ   r�   Ú_write_json_exclr¾  rù  r¯   s    r&   Úwrite_pid_filerP    s6   € ô ‹?€DØ‡K�K×Ñ˜d¨TÐÔ2Ü�TÔ,Ó.Ô/ÜÕr(   c                 óJ  — t        j                  | t         j                  t         j                  z  t         j                  z  «      }	 t        j
                  |dd¬«      5 }t        j                  ||«       ddd«       y# 1 sw Y   yxY w# t        $ r t        | «       ‚ w xY w)zSCreate ``path`` with O_CREAT|O_EXCL and dump ``record``; unlinks on a failed write.Úwr   r€   N)
r�   r8  ÚO_CREATÚO_EXCLÚO_WRONLYÚfdopenrØ  r;  r¡   rä  )r)   r¹   Úfdr  s       r&   rO  rO  
  s{   € ä	�‰�”r—z‘z¤B§I¡IÑ-´·±Ñ;Ó	<€BðÜ�Y‰Y�r˜3¨Ô1ð 	&°VÜ�I‰I�f˜fÔ%÷	&÷ 	&ñ 	&ûäò Ü˜ÔØðús*   ÁB Á B Á7B Â B	ÂB Â	B ÂB"r.   c                 óJ   — |D ]  \  }}}|t         usŒ|� ||«      n|| |<   Œ  y)zXAssign each ``(key, value, coerce)`` whose value was explicitly passed (not ``_UNSET``).N)Ú_UNSET)r.   Úfieldsrë  rë   Úcoerces        r&   Ú_apply_set_fieldsr\    s6   € à$ò IÑˆˆU�FØœÒØ+1Ð+=™& œ-À5ˆF�3ŠKñIr(   rÑ  c                 óx   — t        | t        «      rt        | j                  d«      xs d«      nd}d|dv r|iS diS )NrÌ  rß   >   ÚokÚretryingÚunavailablerÍ  )re   r[   r\   r¼   )rÑ  rD   s     r&   Ú_coerce_session_storera    sD   € Ü6@ÀÔPTÔ6UŒC�×!Ñ! (Ó+Ò1¨rÔ2Ð[]€EØ˜uÐ(IÑI�eÐYÐYÈyÐYÐYr(   T)r  r  rÎ  rÏ  Úactive_workÚplatformÚplatform_stateÚ
error_codeÚerror_messageÚneeds_attentionÚretrying_sinceÚserved_profilesrÑ  Úmultiplex_standalone_reasonÚingress_urlÚlistener_baseÚclear_profile_platformsÚdrop_profile_platformsÚload_existingÚreload_existingr  r  rÎ  rÏ  rb  rc  rd  re  rf  rg  rh  ri  rj  rk  rl  rm  rn  ro  rp  c                 óš  — t        «       }t        5  |st        |k7  st        €|a|rt	        |«      ndxs
 t        «       at        }t        j                  |«      }t        «       }|j                  di «       t        |d   t        «      si |d<   |s|rZ|r|› d�nd}|d   j                  «       D ��ci c]/  \  }}t        |t        «      rd|vs|�|j                  |«      s||“Œ1 c}}|d<   |j                  dD �ci c]  }|||   “Œ
 c}«       t!        «       |d<   |j                  t#        «       «       t%        |d| dfd|dfd|t&        fd	|t(        fd
|d„ fd|d„ fd|d„ fd|t*        ff«       |t,        ur­t        j                  |d   j/                  |i «      «      }t        |t        «      si }|dk(  r|	t,        u rdn|	}	|
t,        u rdn|
}
t%        |d|dfd|dfd|dfd|	t&        fd|
dfd|dfd|dff«       |j                  t!        «       |d   |d   ¬«       ||d   |<   |a|||fcddd«       S c c}}w c c}w # 1 sw Y   yxY w)zAMerge one update into the process-wide canonical status snapshot.NrÐ  rÝ  )r�  r  rŒ  r»  rÒ  r  r  rÎ  rÏ  rb  c                 ó    — | rt        | «      S d S rT   ©r’  ©Úvs    r&   ú<lambda>z0_prepare_runtime_status_update.<locals>.<lambda>I  s   € ¹a´4¸³7€ ÀT€ r(   ri  c                 ó    — t        | xs g «      S rT   rs  rt  s    r&   rv  z0_prepare_runtime_status_update.<locals>.<lambda>J  s   € ¼4ÀÂÀR»=€ r(   rj  c                 ó    — | rt        | «      S d S rT   r�  rt  s    r&   rv  z0_prepare_runtime_status_update.<locals>.<lambda>K  s   € Ñ]^ÔSVÐWXÓSY€ Ðdh€ r(   rÑ  Ú	connectedFrD   re  rf  rg  rh  rk  rl  r  r»  )rÒ  Ú
writer_pidÚwriter_start_time)rÛ   ri   ra   rb   rd   rÓ  r1   r2   r¾  Ú
setdefaultre   r[   Úitemsr\   rw  Úupdaterê   rÆ  r\  r_   Úparse_active_agentsra  rY  r¼   )r  r  rÎ  rÏ  rb  rc  rd  re  rf  rg  rh  ri  rÑ  rj  rk  rl  rm  rn  ro  rp  r)   Úprevious_payloadr*   Úcurrent_recordÚdrop_prefixÚkru  rë  Úplatform_payloads                                r&   Ú_prepare_runtime_status_updater…  !  sÃ  € ô $Ó%€DÜ	#ñ 4/ÙÔ8¸DÒ@ÔDYÐDaØ)-Ð&á*7” Ô&¸TÒdÔFbÓFdð "ô 1ÐÜ—-‘-Ð 0Ó1ˆÜ*Ó,ˆØ×Ñ˜;¨Ô+Ü˜' +Ñ.´Ô5Ø#%ˆG�KÑ Ù"Ñ&<Ù:PÐ3Ð4°AÑ6ÐVZˆKà!(¨Ñ!5×!;Ñ!;Ó!=÷$Ù˜˜AÜ! !¤SÔ)¨S¸©\ØÐ+°A·L±LÀÔ4Mð �1‘ó$ˆG�KÑ ð
 	�‰Ð<aÖb°S˜˜^¨CÑ0Ñ0ÒbÔcÜ ,£ˆ�ÑØ�‰Ô0Ó2Ô3Ü˜'Ø˜m¨TÐ2°]ÀKÐQUÐ4VØ Ð"3´TÐ:Ø˜mÔ-@ÐAØ˜KÑ)IÐJØ Ñ1HÐIØ*Ð,GÑIhÐiØ˜mÔ-BÐCð$
ô 	ð œ6Ñ!Ü#Ÿ}™}¨W°[Ñ-A×-EÑ-EÀhÐPRÓ-SÓTÐÜÐ.´Ô5Ø#%Ð Ø Ò,Ø+:¼fÑ+D¡%È/�Ø)7¼6Ñ)A¡À~�ÜÐ.Ø˜.¨$Ð/°,À
ÈDÐ1QØ  -°Ð6Ø" O´TÐ:Ø! >°4Ð8Ø ¨TÐ2Ø  -°Ð6ð1ô ð ×#Ñ#Ü'›>°nÀUÑ6KØ"0°Ñ">ð $ô @ð .>ˆG�KÑ  Ñ*Ø 'ÐØ�WÐ.Ð.÷i4/ñ 4/ùó$ùò
 c÷)4/ð 4/ús+   ‘B!IÂ24H6Ã&IÃ<H<
Ä	D#IÈ6IÉI
r€  c                 ó~   — t        j                  t        «      5  ddlm}  || |«       d d d «       y # 1 sw Y   y xY w)Nr   )Úemit_runtime_status_transition)r–   r—   r¡   Úagent.monitoring.gateway_healthr‡  )r€  r*   r‡  s      r&   Ú_emit_runtime_status_transitionr‰  e  s9   € ô 
×	Ñ	œYÓ	'ñ BÝRÙ&Ð'7¸ÔA÷B÷ Bñ Bús   š3³<)rp  Úwait_timeoutrŠ  rZ  c                 óÐ   — t         5  t        dd| i|¤Ž\  }}}t        «       }|j                  ||«      }ddd«       t	        «       j                  |¬«      S # 1 sw Y   Œ(xY w)a'  Synchronously persist status for CLI callers and off-loop startup.

    ``wait_timeout`` bounds how long the caller waits for durable persistence.
    A timed-out update remains queued for the single background writer.
    Keyword ``fields`` are those of ``_prepare_runtime_status_update``.
    rp  Nr=   r`   )ri   r…  rj   r9   r‰  rB   )rp  rŠ  rZ  r)   r*   r€  rm   r8   s           r&   Úwrite_runtime_statusrŒ  m  sv   € ô 
$ñ 2Ü*Hñ +7Ø+ð+7Ø/5ñ+7Ñ'ˆˆgÐ'ä+Ó-ˆØ—]‘] 4¨Ó1ˆ
÷	2ô $Ð$4°gÔ>Ø�;‰;�z¨<ˆ;Ó8Ð8÷2ð 2ús   ‡.AÁA%c                  óª   — t         5  t        dddi| ¤Ž\  }}}t        «       j                  ||«      }ddd«       t	        «       S # 1 sw Y   ŒxY w)z’Merge and enqueue status without waiting for filesystem persistence.

    Keyword ``fields`` are those of ``_prepare_runtime_status_update``.
    ro  FNr`   )ri   r…  rj   r9   r‰  )rZ  r)   r*   r€  r8   s        r&   Úpublish_runtime_statusrŽ  �  sj   € ô
 
$ñ HÜ*Hñ ++Øð++Ø#)ñ++Ñ'ˆˆgÐ'ä/Ó1×8Ñ8¸¸wÓGˆ
÷Hô $Ð$4°gÔ>ØÐ÷Hð Hús   ‡,A	Á	Ac                 ó0   — t        | xs
 t        «       «      S )zRRead ``gateway_state.json``; ``path`` lets callers inspect another profile's file.)rd   rÛ   r¯   s    r&   Úread_runtime_statusr�  Ž  s   € ä˜4Ò=Ô#;Ó#=Ó>Ð>r(   éx   Úttl_sc                 óf   — t        | t        «       xs t        | j                  d«      xs d|«      S )zYTrue when the snapshot's ``updated_at`` is older than ``ttl_s`` (or missing/unparseable).rÒ  rß   )re   r[   Ú_marker_is_staler¼   )r¹   r’  s     r&   Úruntime_status_is_staler•  š  s1   € ô ˜&¤$Ó'Ð'ÒbÔ+;¸F¿J¹JÀ|Ó<TÒ<ZÐXZÐ\aÓ+bÐbr(   c           	      ó  — t        | t        «      rt        | j                  d«      «      nd}|syt	        dt        t        j                  t        j                  «      t        j                  |«      z
  j                  «       «      «      S )z–Whole seconds since the snapshot's ``updated_at``; None when missing/unparseable (an
    unparseable stamp is a stale *file*, not a wedged heartbeat).rÒ  Nr   )re   r[   rý   r¼   r@   r]   r   r�   r   r‘   ró   Útotal_seconds)r¹   rÒ  s     r&   Úruntime_status_heartbeat_age_sr˜  ¡  se   € ô DNÈfÔVZÔC[Ô% f§j¡j°Ó&>Ô?Ðae€JÙØÜˆq”#”x—|‘|¤H§L¡LÓ1´H×4JÑ4JÈ:Ó4VÑV×eÑeÓgÓhÓiÐir(   c                 ó   — t        | «      duS )zPTrue when the snapshot's PID is alive and passes the start-time PID-reuse guard.N)rõ  )r¹   s    r&   Úruntime_status_pid_is_liverš  ª  s   € ä  Ó(°Ð4Ð4r(   rú   c                 óX   — 	 t        dt        | «      «      S # t        t        f$ r Y yw xY w)zWCoerce ``active_agents`` to a non-negative int; shared by writer and both HTTP readers.r   )r@   r]   r  r˜   )rú   s    r&   r  r  ¯  s/   € ðÜ�1”c˜#“hÓÐøÜ”zÐ"ò Ùðús   ‚ —)¨)Úrunningr  Úgateway_runningc                 ó:   — t        | |¬«      syt        |«      dkD  S )zÞBusy iff live, serving (``running``/``degraded``), and ``active_agents > 0`` -- the contract NAS gates on. Liveness
    keys off ``gateway_running``, NEVER ``updated_at`` (a stale heartbeat is a health warning, not death).©r�  r  Fr   )Úderive_gateway_drainabler  )r�  r  rÏ  s      r&   Úderive_gateway_busyr¡  ¼  s"   € ô $°OÐS`ÕaØÜ˜}Ó-°Ñ1Ð1r(   c                 ó,   — t        | «      xr |t        v S )zVDrainable iff live and serving; independent of ``active_agents`` (idle drains finish).)r_   Ú_DRAINABLE_GATEWAY_STATESrŸ  s     r&   r   r   Ä  s   € ä�Ó ÒO ]Ô6OÐ%OÐOr(   )Úfrozenc                   ó€   — e Zd ZU dZeed<   ee   ed<   eed<   dZ	ee
eef      ed<   dZeed<   dZee
eef      ed	<   y)
ÚGatewayLivenesszóResolved gateway liveness for one dashboard surface. ``source``: which ladder rung answered
    (logging/tests only -- never branch product behavior on it). ``probe_error``: a rung raised;
    lets fail-open callers tell "down" from "unknown".rœ  r  ÚsourceNÚhealth_bodyFÚprobe_errorr   )rW   rX   rY   rZ   r_   rx   r
   r]   r\   r¨  r[   r   r©  r   r`   r(   r&   r¦  r¦  É  sW   … ñ:ð ƒMØ	�#‰ÓØƒKØ,0€K�˜$˜s C˜x™.Ñ)Ó0Ø€K�Óð )-€GˆX�d˜3 ˜8‘nÑ%Ô,r(   r¦  c                 óÆ   — t        | «      }t        |«      }|r|S 	 ddlm} |j	                  «       t         |«       «      j	                  «       k(  ry	 y# t
        $ r Y yw xY w)uø   Profile id of any Hermes home: ``<root>/profiles/<name>`` â†’ ``<name>``, the default root â†’
    ``"default"``, anything else â†’ None. Multiplex-only makes ``default`` an ordinary served
    profile, so reporting surfaces need a name for it too.r   ©rÈ   rÄ   N)r   r–  rÇ   rÈ   r®   r¡   )r”  rÁ   ÚnamedrÈ   s       r&   r²  r²  Ú  sj   € ô �Ó€DÜ" 4Ó(€EÙØˆðÝ<Ø�<‰<‹>œTÑ"9Ó";Ó<×DÑDÓFÒFØð Gð øô ò Ùðús   œ5A Á	A ÁA Úprofile_dirc                 ó  — t        t        | «      «      }|syddlm} ddlm} ddlm} ddlm	}  |«       }|�|j                  |«      r|j                  }n|dk7  r ||«      r |«       }ny|€y|t         |«       dz  «      xs i fS )	u–  ``(pid, host gateway_state.json)`` when the ONE live host gateway serves the profile whose home
    is ``profile_dir``; None for a home it does not serve or when no gateway owns the host role.

    Multiplex-only: ``default`` is just another served profile, not the owner of a private topology â€”
    resolving from the host rendezvous record (``gateway/host_topology.py``) is what lets it be
    reported as SERVED rather than only as owner. A served profile owns no
    ``gateway.pid``/``gateway_state.json`` (#97120), so every PID-file rung of the dashboard ladder
    reports it stopped while ``hermes -p X status`` says running â€” the two must agree.
    Nr   )Úhost_gateway_topology)Ú+named_profile_served_by_running_multiplexer)Úlive_default_gateway_pidr«  rÄ   r   )r²  r   Úgateway.host_topologyr¯  Úhermes_cli.gatewayr°  Ú#hermes_cli.gateway_multiplex_servedr±  rÇ   rÈ   r´  r  r�  )r­  r0   r¯  r°  r±  rÈ   Útopologyr  s           r&   Ú multiplexer_liveness_for_profiler¶  ë  s‹   € ô !¤ kÓ!2Ó3€DÙØÝ;ÝNÝLÝ8Ù$Ó&€HØÐ §¡°Ô 5Ø%Ÿ\™\‰Ø	�Ò	ÑJÈ4ÔPá&Ó(‰àØ
€{ØØÔ#Ñ$;Ó$=Ð@TÑ$TÓUÒ[ÐY[Ð[Ð[r(   rÎ   c                 ó  — ddl m}m} | xs i j                  d«      }|r|dk(  st	        |t
        «      si S i }t        |«      D ]¸  }|j                  |«      }t	        |t
        «      r|j                  d«      dvrŒ8|j                  d«      }t	        |t        «      r|r|› d|› |j                  |d	«      › �nd
}	|j                  «       D �
�ci c]  \  }
}|
dk7  sŒ|
|“Œ c}}
||<   ||   j                  |	d¬«       Œº |S c c}}
w )u7  Entries for the api_server/webhook mirrors a served ``profile`` gets from the DEFAULT's
    listener. The multiplexer never builds those adapters for a secondary (``gateway.run_adapters``
    skips them: ``SHARED_LISTENER_MIRROR_PLATFORMS``), so the record has no ``<profile>:api_server``
    entry and every reader fell through to ``pending_restart`` â€” "Restart needed" forever while
    ``/p/<profile>/v1/...`` answered. Only a live default entry is mirrored; its state is the profile's
    state, plus the ``/p/<profile>`` URL the client must actually call.
    r   )ÚSHARED_LISTENER_MIRROR_PATHSÚ SHARED_LISTENER_MIRROR_PLATFORMSrÐ  rÄ   rD   >   r_  ry  Ú
connectingrl  z/p/rß   N)rk  Úmirrored_from)
Úgateway.configr¸  r¹  r¼   re   r[   Úsortedr\   r}  r~  )r   rÎ   r¸  r¹  ÚplatsÚmirroredr0   Úentryr¿   Úurlrƒ  ru  s               r&   Ú shared_listener_mirror_platformsrÂ  	  s  € ÷ ^ØŠ]˜×Ñ Ó,€EÙ�g Ò*´*¸UÄDÔ2IØˆ	Ø!€HÜÐ7Ó8ò 	HˆØ—	‘	˜$“ˆÜ˜%¤Ô&¨%¯)©)°GÓ*<ÐDkÑ*kØð �y‰y˜Ó)ˆÜS]Ð^bÔdgÔShÑmq���c˜'˜Ð#?×#CÑ#CÀDÈ"Ó#MÐ"NÑOÐw{ˆØ+0¯;©;«=×Q¡4 1 a¸AÀÓ<P˜!˜Q™$ÓQˆ�‰Ø�‰×Ñ¨#¸YÐÕGð	Hð €Oùó Rs   ÃDÃDc                 óF  — | xs i j                  d«      }t        |t        «      si S |› d�}|j                  «       D ��ci c]E  \  }}t        |t        «      r0|j                  |«      rt        |t        «      r|t        |«      d |“ŒG }}}i t        | |«      ¥|¥S c c}}w )u8  The ``<profile>:<platform>`` entries of a multiplexer record, re-keyed to bare platform names â€” the
    same shape a standalone gateway for ``profile`` writes into its own ``gateway_state.json`` â€” plus the
    default listener's api_server/webhook mirrors the profile is served through (``ingress_url`` set).rÐ  rÝ  N)r¼   re   r[   r}  r\   rw  rŸ   rÂ  )r   rÎ   r¾  Úprefixrë  rë   Úowns          r&   Ú"profile_platforms_from_multiplexerrÆ  #  s¨   € ð Š]˜×Ñ Ó,€EÜ�eœTÔ"Øˆ	Øˆy˜ˆ]€FØ6;·k±k³m÷ [©
¨¨UÜ˜œcÔ" s§~¡~°fÔ'=Ä*ÈUÔTXÔBYð Œs�6‹{ˆ|Ð˜eÑ#ð [€Cñ [àHÔ.¨w¸Ó@ÐHÀCÐHÐHùó[s   Á A
B)r­  r   Úhealth_probeÚ	use_cacheÚ	pid_probeÚruntime_readerÚruntime_pid_proberÇ  rÈ  rÉ  .rÊ  rË  c                 ó(  ‡— |xs |rt         nt        }|xs t        }|xs t        }	dŠ| du}
ddœˆfd„
}|
r ||| dz  «      n ||«      }|�t	        d|d¬«      S d}|�3 ||d	¬«      \  }}|r$|r|j                  d«      nd}t	        d|d
|¬«      S |t        u r|
rd| dz  ini } ||fi |¤Ž}|
rd| ini } ||	|fi |¤Ž}|�t	        d|d|¬«      S |
r| n	t        «       } |t        |«      }|�|\  }}t	        d|d||¬«      S t	        ddd|‰¬«      S )uÄ  Single source of truth for "is the gateway up?" across dashboard surfaces. Ladder, most to
    least authoritative: (1) PID file + runtime lock (scoped to ``profile_dir``; cached by default
    so polling does not re-flock ``gateway.lock``); (2) caller-supplied HTTP health probe (gateway
    in another container); (3) LOCAL runtime status PID validated against the live process table
    with ``expected_home`` (a recycled PID of another profile never counts; pass ``runtime`` if
    already read); (4) for a named ``profile_dir`` only, the live default multiplexer that records the
    profile in ``served_profiles`` (a served profile writes no identity files of its own). ``*_probe``/
    ``runtime_reader`` are the dashboard's injection/test seam. A rung
    that raises degrades to the next (never 500 a status endpoint) and sets ``probe_error``.

    Before this existed, ``/api/status`` and ``/api/messaging/platforms`` each open-coded their own ladder
    and disagreed on the same page load â€” the sidebar read "running" while the Channels page rendered "The
    gateway is not running."  Three deployments hit it: a cross-container gateway (only ``/api/status`` ran
    the HTTP health probe), a profile-scoped dashboard (only ``/api/status`` passed the profile's paths, so
    messaging borrowed another profile's runtime state â€” issue #71211), and a launch-service-managed gateway
    with no PID file (only some callers used the runtime-status fallback).
    FN)Úfallbackc                ó<   •— 	  | |i |¤ŽS # t         $ r dŠ|cY S w xY w©NT)r¡   )ÚfnrÍ  ÚargsÚkwargsr©  s       €r&   Úguardedz)resolve_gateway_liveness.<locals>.guardedN  s1   ø€ ð	Ù�tÐ&˜vÑ&Ð&øÜò 	ØˆKØŠOð	ús   ƒ ‹šrÑ   Tr  )rœ  r  r§  )FNÚhealth)rœ  r  r§  r¨  r)   r   r¸   Úruntime_statusÚmultiplexer)rœ  r  r§  r¨  r   Únone)rœ  r  r§  r¨  r©  )	Úget_running_pid_cachedÚget_running_pidr�  Úget_runtime_status_running_pidr¦  r¼   rY  rª   r¶  )r­  r   rÇ  rÈ  rÉ  rÊ  rË  Ú
_pid_probeÚ_runtime_readerÚ_runtime_pid_probeÚscopedrÓ  r  r¨  ÚaliveÚ
remote_pidÚreader_kwargsÚprobe_kwargsÚruntime_pidÚown_homeÚservedÚmux_pidÚmux_runtimer©  s                          @r&   Úresolve_gateway_livenessrè  0  s‡  ø€ ð0 ÒX¹Õ5Ì€JØ$Ò;Ô(;€OØ*ÒLÔ.LÐØ€KØ Ð$€Fà$(ö ñ ?E‰'�*˜k¨MÑ9Ô
:É'ÐR\ÓJ]€CØ
€Ü t°¸UÔCÐCØ,0€KØÐÙ$ \¸MÔJÑˆˆ{Ùá3>˜Ÿ™¨Ô/ÀDˆJÜ"Ø *°XÈ;ôð ð ”&ÑÙHN˜ Ð/CÑ!CÑDÐTVˆÙ˜/Ñ;¨]Ñ;ˆÙ5;�O [Ñ1À€LÙÐ,¨gÑF¸ÑF€KØÐÜØ˜kÐ2BÐP[ô
ð 	
ñ %‰{Ô*BÓ*D€HÙÔ5°xÓ@€FØÐØ%Ñˆ�ÜØ˜g¨mÈÐ^iô
ð 	
ô Ø˜4¨¸KÐU`ôð r(   c                óÂ   — | �| n	t        «       }t        |t        «      sy|j                  d«      dv ryt	        |«      }|€y|€t        |«      syt        |||¬«      sy|S )ap  Live gateway PID from the runtime status record, or None: the ``get_running_pid()`` fallback
    for launch-service-managed gateways with a fresh ``gateway_state.json`` but no ``gateway.pid``.
    ``expected_home`` scopes the OS-identity check to another profile's home so a PID recycled onto
    a different profile's gateway is not reported running for the dead one.Nr  >   Nr  r  r·   )r�  re   r[   r¼   rõ  rÉ  r¹  )r   r¸   r*   r  s       r&   rÚ  rÚ  ~  so   € ð !Ð,‰gÔ2EÓ2G€GÜ�gœtÔ$ØØ‡{�{�?Ó#Ð'JÑJØÜ
 Ó
(€CØ
€{Øð ÐÔ%KÈGÔ%TØÜ+¨G°SÈÕVØØ€Jr(   c                 ór   — t        | «      } t        | dz  d¬«      }|�|S t        t        | dz  «      | ¬«      S )aÁ  Verified PID of the gateway owned by ``home`` (pid file + runtime lock first, then the runtime
    status record), or None. Every reader of another home's gateway identity goes through this so
    they all prove the same thing: the PID passes the start-time reuse guard, its live command line is
    a gateway's belonging to ``home``, and the record is not ``stopped``. Bare PID existence is not
    identity -- a stale record whose PID was recycled by an unrelated process lent it ``served_profiles``
    and put phantom gateways into the update inventory (#109680) -- while a launch-service gateway whose
    ``gateway.pid`` was unlinked is still live (#110166). Never unlinks ``home``'s identity files.rÑ   F©r   r   r·   )r   rØ  rÚ  r�  )rÁ   r  s     r&   Úlive_gateway_pid_for_homerì  –  sD   € ô �‹:€Dô ! ¨Ñ!5ÀUÔ
K€CØ
€Øˆ
Ü)Ô*=¸dÐEYÑ>YÓ*ZÐjnÔoÐor(   c                  ó  — t        j                  t        «      5  t        «       } t	        t        | «      «      }|�!|t        j                  «       k7  r
	 ddd«       y| j                  d¬«       t        «        ddd«       y# 1 sw Y   yxY w)zšRemove the PID file only if it belongs to this process: during --replace the old process's
    atexit can fire AFTER the new process wrote its own record.NTrá  )
r–   r—   r¡   rÒ   rî  rd   r�   r¼  rã  rù  )r)   Úfile_pids     r&   Úremove_pid_filerï  §  ss   € ô 
×	Ñ	œYÓ	'ñ #Ü‹ˆÜ#¤O°DÓ$9Ó:ˆØÐ H´·	±	³Ò$;Ø÷	#ð #ð
 	�‰˜tˆÔ$Ü Ô"÷#÷ #ñ #ús   š9BÁBÂB
rf   Úexisting_pidc                 óØ   — |�t        |«      sy| j                  d«      }t        |«      }t        ||«      ryt	        |«      st        |«      �yd||fv rt        | «      syt        |«      S )aæ  True when a foreign scoped-lock record no longer names a live gateway: PID missing/dead,
    start time changed (PID reuse), or the live process is not a gateway -- a readable cmdline says
    so (also catches boot-time PID+start_time collisions; systemd spawns deterministically);
    cmdline unreadable AND start_time unknown on either side => the lock record's own argv is the
    only signal left. Stopped (SIGTSTP) processes look alive to _pid_exists; stale so --replace
    works.NTr»  )rô  r¼   r  rò  rŠ  rb  r“  Ú_process_is_stopped)rf   rð  rï  rð  s       r&   Ú_scoped_lock_record_is_staleró  ³  ss   € ð Ð¤;¨|Ô#<ØØ—\‘\ ,Ó/€NÜ+¨LÓ9€MÜ˜^¨]Ô;ØÜ& |Ô4Ü  Ó.Ð:ØØ�N MÐ2Ñ2Ô;UÐV^Ô;_ØÜ˜|Ó,Ð,r(   c                 ó(  — t        j                  t        «      5  t        d| › d�«      j	                  d¬«      j                  «       D ]3  }|j                  d«      sŒ|j                  «       d   dv c cddd«       S  	 ddd«       y	# 1 sw Y   y	xY w)
zHTrue for a stopped / tracing-stop state (T/t) in ``/proc/<pid>/status``.rE  z/statusr   r€   zState:r-   >   ÚTrj  NF)r–   r—   rù   r   r”   r•   rw  rJ  )r  r¢   s     r&   rò  rò  È  s‰   € ä	×	Ñ	œWÓ	%ñ 5Ü˜6 #  gÐ.Ó/×9Ñ9À7Ð9ÓK×VÑVÓXò 	5ˆDØ�‰˜xÕ(Ø—z‘z“| A‘¨*Ð4Ñ4÷5ñ 5ñ	5÷5ð ÷	5ð ús   šABÁBÁ=BÂBÚmetadatac                 óÞ  — t        | |«      }|j                  j                  dd¬«       i t        «       ¥| t	        |«      |xs i t        «       dœ¥}t        t        «       «      }|r||d<   t        |«      }|€|j                  «       rt        |«       |r£t        |«      }|t        j                  «       k(  rt        ||«       d|fS t        ||«      sd|fS t!        j"                  t$        «      5  |j'                  |j(                  dz   «      }t        j*                  ||«       t        |«       ddd«       	 t-        ||«       y# 1 sw Y   ŒxY w# t.        $ r dt        |«      fcY S w xY w)	zYAcquire a machine-local lock keyed by scope + identity (one Telegram token across homes).Tr|   )r@  Úidentity_hashrö  rÒ  rÎ   NFz.stale)TN)rC  rŽ   r�   r¾  r?  rê   rÌ   rª   rd   r“   rä  rî  r�   r¼  rK   ró  r–   r—   rù   rÕ   r0   rž   rO  ÚFileExistsError)	r@  r8  rö  rè  r¹   rÎ   rf   rð  Ú	tombstones	            r&   Úacquire_scoped_lockrû  Ñ  sb  € ô % U¨HÓ5€IØ×Ñ×Ñ 4°$ÐÔ7ðÜ
Ó
ðØ(-ÄÈHÓ@UØ’N ´,³.ò€Fô &Ô&>Ó&@ÓA€GÙØ#ˆˆyÑÜ˜yÓ)€HØÐ˜I×,Ñ,Ô.ä˜	Ô"ÙÜ'¨Ó1ˆð œ2Ÿ9™9›;Ò&Ü˜Y¨Ô/Ø˜�>Ð!Ü+¨H°lÔCØ˜(�?Ð"ô × Ñ ¤Ó)ñ 	'Ø!×+Ñ+¨I¯N©N¸XÑ,EÓFˆIÜ�J‰J�y )Ô,Ü˜IÔ&÷	'ð1Ü˜ FÔ+ð ÷	'ð 	'ûô ò 1Ø”o iÓ0Ð0Ò0ð1ús   Ã1A EÄ:E ÅEÅE,Å+E,c                 óš   — t        | |«      }t        |«      xs i j                  d«      t        j                  «       k(  rt        |«       yy)z„Release a scope lock owned by this PID. No start_time equality check: on-disk null vs a live
    fingerprint would wedge reconnects.r  N)rC  rd   r¼   r�   r¼  rä  )r@  r8  rè  s      r&   Úrelease_scoped_lockrý    sA   € ô % U¨HÓ5€IÜ˜	Ó"Ò( b×-Ñ-¨eÓ4¼¿	¹	»ÒCÜ˜	Õ"ð Dr(   )Ú	owner_pidÚowner_start_timerþ  rÿ  c                 ób  — t        «       }|j                  «       syd}|j                  d«      D ]q  }| �4t        |«      xs i }t	        |«      | k7  s|�|j                  d«      |k7  rŒ9t        j                  t        «      5  |j                  d¬«       |dz  }ddd«       Œs |S # 1 sw Y   ŒxY w)z°Remove scoped lock files (--replace cleanup); returns the count removed. With ``owner_pid``
    only that gateway's records go (``owner_start_time`` narrows against PID reuse).r   z*.lockNr»  Trá  r-   )
rç   r“   Úglobrd   rî  r¼   r–   r—   rù   rã  )rþ  rÿ  Úlock_dirÚremovedÚ	lock_filer¹   s         r&   Úrelease_all_scoped_locksr  	  sº   € ô
 ‹€HØ�?‰?ÔØØ€GØ—]‘] 8Ó,ò 	ˆ	ØÐ Ü$ YÓ/Ò5°2ˆFÜ Ó'¨9Ò4Ø Ð,°·±¸LÓ1IÐM]Ò1]àÜ× Ñ ¤Ó)ñ 	Ø×Ñ¨ÐÔ-Ø�q‰LˆG÷	ð 	ð	ð €N÷	ð 	ús   ÂB%Â%B.	z.gateway-takeover.jsoné<   z.gateway-planned-stop.jsonr»   c                 óB   — t        | xs
 t        «       «      }|t        z  S )zVTakeover marker path; ``hermes_home`` is given only for a verified cross-home handoff.)r°   rª   Ú_TAKEOVER_MARKER_FILENAME)r»   rÁ   s     r&   Ú_get_takeover_marker_pathr	  .  s!   € ä! +Ò"KÔ1IÓ1KÓL€DØÔ+Ñ+Ð+r(   c                  ó$   — t        «       t        z  S rT   )rª   Ú_PLANNED_STOP_MARKER_FILENAMEr`   r(   r&   Ú_get_planned_stop_marker_pathr  4  s   € Ü#Ó%Ô(EÑEÐEr(   Ú
written_atc                 óÆ   — 	 t        j                  t        j                  «      t        j                  | «      z
  }|j                  «       |kD  S # t        t        f$ r Y yw xY wrÏ  )r   r�   r   r‘   ró   r—  r  r˜   )r  r’  Úages      r&   r”  r”  8  sT   € ðÜ�l‰lœ8Ÿ<™<Ó(¬8×+AÑ+AÀ*Ó+MÑMˆØ× Ñ Ó" UÑ*Ð*øÜ”zÐ"ò Ùðús   ‚AA ÁA ÁA c                 ó¸   — t        | «      }|syt        |d«      }|�t        |j                  d«      xs d|«      rt	        | «       y|||j                  d«      fS )z¼``(record, target_pid, target_start_time)`` for a usable marker, else None. Malformed/expired
    markers can never match anyone, so they are unlinked here (must not wedge a new instance).NÚ
target_pidr  rß   Útarget_start_time)rd   rî  r”  r¼   rä  )r)   r’  r¹   r  s       r&   Ú_read_live_pid_markerr  @  s`   € ô ˜TÓ"€FÙØÜ! &¨,Ó7€JØÐÔ-¨f¯j©j¸Ó.FÒ.LÈ"ÈeÔTÜ˜ÔØØ�:˜vŸz™zÐ*=Ó>Ð>Ð>r(   r  r  c                 ób   — | t        j                  «       k7  ryt        | «      }d||fv xs ||k(  S )aÌ  PID match with an optional start-time PID-reuse guard (watcher probe + consume). Both start
    times known -> must match; either unknown -> PID equality decides (bounded by the marker TTL):
    ``_get_process_start_time`` is None without /proc (macOS, native Windows -- where the
    planned-stop watcher matters most) and requiring a match there would misclassify a legitimate
    ``hermes gateway stop`` as an unexpected exit revived by the service manager.FN)r�   r¼  r  )r  r  Úour_start_times      r&   Ú_pid_marker_names_selfr  M  s;   € ð ”R—Y‘Y“[Ò ØÜ,¨ZÓ8€NØÐ% ~Ð6Ð6Ò]Ð:KÈ~Ñ:]Ð]r(   c                ó  — t        | |«      }|€y|\  }}}t        «       }|j                  d«      }|�t        |t        «      rt        ||«      s!y|j                  d«      }|�t        ||«      syt        ||«      }	t        | «       |	S )NFÚtarget_hermes_homeÚreplacer_hermes_home)r  rª   r¼   re   r\   r¶   r  rä  )
r)   r’  rû   r¹   r  r  Úour_homeÚtarget_homeÚreplacer_homeÚmatchess
             r&   Ú_consume_pid_marker_for_selfr  Y  s–   € Ü" 4¨Ó/€FØ€~ØØ,2Ñ)€FˆJÐ)ô
 (Ó)€HØ—*‘*Ð1Ó2€KØÐÜ˜+¤sÔ+Ô3DÀ[ÐRZÔ3[ØàŸ
™
Ð#9Ó:ˆØÐ$Ô->¸}ÈhÔ-WØÜ$ ZÐ1BÓC€GÜ�DÔØ€Nr(   ©r  r  r  c                ó(  — 	 t        |xs
 t        «       «      }|t        u rt        | «      }t	        t        |«      | |t        |«      t        j                  «       t        t        t        «       «      «      t        «       dœ«      S # t        $ r Y yw xY w)a{  Record that ``target_pid`` is being replaced by this process; True on success. Captures the
    target's ``start_time`` (PID-reuse guard) + a timestamp for TTL. A verified cross-home handoff
    passes ``target_home`` + validated ``target_start_time`` so the marker lands in the target's
    home; such callers must fail closed on False (the target's supervisor could revive it).)r  r  r  Úreplacer_pidr  r  F)r°   rª   rY  r  Ú_write_markerr	  r\   r�   r¼  rê   rù   )r  r  r  Úmarker_homes       r&   Úwrite_takeover_markerr$  p  s‰   € ðÜ,¨[Ò-VÔ<TÓ<VÓWˆØ¤Ñ&Ü 7¸
Ó CÐÜÔ6°{ÓCØ$Ð;LÜ"% kÓ"2ÄBÇIÁIÃKÜ$'Ô(>Ô?WÓ?YÓ(ZÓ$[Ü&›.ñ	F
ó ð 	øô ò Ùðús   ‚BB Â	BÂBc                 ó<   — 	 t        | |«       y# t        $ r Y yw xY w)zDAtomically write a marker record; False (never raise) on OS failure.TF)rK   rù   )r)   r¹   s     r&   r"  r"  …  s&   € ðÜ˜˜vÔ&ØøÜò Ùðús   ‚ �	šc                  ó4   — t        t        «       t        ¬«      S )zXConsume the takeover marker; True => planned takeover (exit 0); unlinked on match/stale.©r’  )r  r	  Ú_TAKEOVER_MARKER_TTL_Sr`   r(   r&   Ú consume_takeover_marker_for_selfr)  Ž  s   € ä'Ô(AÓ(CÔKaÔbÐbr(   c                 ó,   — t        t        | «      «       y)zDRemove the takeover marker unconditionally. Safe to call repeatedly.N)rä  r	  )r  s    r&   Úclear_takeover_markerr+  “  s   € äÔ-¨kÓ:Õ;r(   c                 óì  — t        | t        «      rt        | «      syt        | «      }| j	                  d«      }| j	                  d«      }|�ƒ|dk  s~|t        j                  «       k(  sgt        |t        «      rWt        |t        «      sGt        |t        «      r7|j                  «       r't        |«      j                  «       j                  «       syt        |«      }t        ||«      dk7  ryt!        |«      }|�t#        |«      syt%        |dz  «      xs i }|j	                  d«      }t        |«      r>t        |«      |k7  s0|j	                  d«      |k7  st        |t        «      rt'        ||«      sy|||fS )a  Resolve a live scoped-lock owner to a verified ``(pid, start_time, home)``. A lock file is
    only a claim: the record, the target home's PID record, and the live process must agree on
    PID, start-time, gateway identity, and home. Missing legacy metadata fails closed.Nr»  r»   r   ÚsamerÑ   )re   r[   r“  rî  r¼   r�   r¼  r]   r_   r\   r½   r   r­   Úis_absoluter°   Ú_scoped_lock_owner_staterb  rˆ  rd   r¶   )r¹   rþ  rÿ  Úraw_homer  r¸  Ú
pid_recordÚpid_record_homes           r&   Ú$_validated_scoped_lock_gateway_ownerr3  ˜  sE  € ô �fœdÔ#Ô+EÀfÔ+MØÜ  Ó(€IØ—z‘z ,Ó/ÐØ�z‰z˜-Ó(€HàÐ˜Y¨!š^¨y¼B¿I¹I»KÒ/GÜÐ*¬CÔ0´JÐ?OÔQUÔ4VÜ˜(¤CÔ(°·±Ô0@Ü�H‹~×(Ñ(Ó*×6Ñ6Ô8àÜ(¨Ó2€KÜ 	Ð+;Ó<ÀÒFØÜ(¨Ó3€LØÐÔ(OÐP\Ô(]Øä  ¨}Ñ!<Ó=ÒCÀ€JØ —n‘n ]Ó3€Oä& zÔ2Ü˜JÓ'¨9Ò4Ø�>‰>˜,Ó'Ð+;Ò;Ü˜/¬3Ô/Ü  °+Ô>àØÐ&¨Ð3Ð3r(   c                 óH   — t        | «      syt        | «      }|€y||k(  rdS dS )zBReturn ``same``, ``exited``, or ``unknown`` for a validated owner.ÚexitedrÍ  r-  )rô  r  )rþ  rÿ  Úlive_start_times      r&   r/  r/  ¼  s3   € ä�yÔ!ØÜ-¨iÓ8€OàÐØØ$Ð(8Ò8ˆ6ÐF¸hÐFr(   ÚattemptsÚdelayc                óÊ   — t        t        d|«      «      D ];  }t        | |«      }|dk(  r y|dk(  r yt        j                  t        d|«      «       Œ= dt        | |«      dk(  fS )	zFReturn ``(exited, safe_to_force)`` after bounded identity-aware waits.r   r5  )TFrÍ  )FFr<   Fr-  )Úranger@   r/  r>   Úsleep)rþ  rÿ  r7  r8  Ú_rD   s         r&   Ú _wait_for_scoped_lock_owner_exitr=  Ç  so   € ô ”3�q˜(Ó#Ó$ò $ˆÜ(¨Ð4DÓEˆØ�HÒÙØ�IÒÙÜ�
‰
”3�s˜E“?Õ#ð$ð Ô*¨9Ð6FÓGÈ6ÑQÐQÐQr(   c                 óÂ   — t         rg S 	 ddl}|j                  t        | «      «      j	                  d¬«      S # t
        $ r t        j                  d| d¬«       g cY S w xY w)zåBest-effort snapshot of ``pid``'s live descendants (POSIX only; never raises). Take it while
    the parent is alive -- once it exits the children are reparented and undiscoverable. ``[]`` on
    Windows (taskkill /T tree-kills).r   NT)Ú	recursivez-Could not snapshot children of gateway PID %d©Úexc_info)r  rK  rM  r]   Úchildrenr¡   rN   rQ   rO  s     r&   Ú_snapshot_gateway_childrenrC  Õ  s]   € õ Øˆ	ðÛØ�~‰~œc #›hÓ'×0Ñ0¸4Ð0Ó@Ð@øÜò Ü�‰ÐDÀcÐTXˆÔYØŠ	ðús   Š-8 ¸#AÁAr‹   r=   rB  Ú
parent_pidc          
      óJ  — t         s| syd}	 ddl}g }| D ]‡  }	 |j                  «       r|j                  «       |j                  k(  rŒ2|j                  «       |k(  r"t        j                  d|j                  |«       Œg|j                  «        |j                  |«       Œ‰ |sy|j                  |t        d	|«      ¬
«      \  }}t!        |«      }|D ]  }	 |j#                  «        |dz  }Œ |rt        j%                  d||«       |S # |j                  $ r Y �Œt        $ r( t        j                  dt        |dd«      |d¬«       Y �Œ1w xY w# t        $ r' t        j                  dt        |dd«      |d¬«       Y Œªw xY w# t        $ r t        j                  d|d¬«       Y |S w xY w)aì  Best-effort reap of a dead gateway's orphaned descendants (POSIX; surviving adapter
    subprocesses keep holding token locks); returns count signalled. Call only AFTER the parent is
    confirmed dead, with a :func:`_snapshot_gateway_children` snapshot. ``is_running()`` is
    identity-aware so a recycled child PID is never signalled; a child whose ppid still equals
    ``parent_pid`` is skipped (parent alive => not an orphan). SIGTERM, bounded wait, SIGKILL
    survivors. Never raises.r   NzCSkipping child PID %d of old gateway %d: parent still appears alivez2Could not terminate child PID %s of old gateway %dr  ú?Tr@  r<   r=   r-   z3Could not force-kill child PID %s of old gateway %dz@Reaped %d orphaned child process(es) of replaced gateway PID %d.z-Child reap for replaced gateway PID %d failed)r  rK  Ú
is_runningrÌ  r  ÚppidrN   rQ   r  Ú	terminater™   r  r¡   r  Ú
wait_procsr@   rŸ   r  rO   )	rB  rD  r:   ÚreapedrK  ÚliveÚchildÚgonerß  s	            r&   Úreap_gateway_childrenrO  ã  s¿  € õ ™(ØØ€Fð!aÛØˆØò 	TˆEðTØ×'Ñ'Ô)¨U¯\©\«^¸v×?SÑ?SÒ-SØØ—:‘:“< :Ò-Ü—L‘Lð ")Ø*/¯)©)°ZôAàØ—‘Ô!Ø—‘˜EÕ"ð	Tñ ØØ×'Ñ'¨´c¸#¸wÓ6GÐ'ÓH‰ˆˆeÜ�T“ˆØò 	TˆEðTØ—
‘
”Ø˜!‘‘ð	Tñ Ü�K‰KÐZØ 
ô,ð €Møð+ ×'Ñ'ò ÚÜò TÜ—‘ÐQÜ$ U¨E°3Ó7¸Èdð ÷ TðTûô ò TÜ—‘ÐRÜ$ U¨E°3Ó7¸Èdð ö TðTûô ò aÜ�‰ÐDÀjÐ[_ˆÕ`Ø€Mðaús‚   �E= ™-DÁE= Á4DÁ;E= Á<!DÂE= Â"0E= ÃE
Ã(E= ÄEÄE= Ä,EÅE= ÅEÅE= Å
-E:Å7E= Å9E:Å:E= Å=!F"Æ!F"é   ©Úgraceful_attemptsÚforce_attemptsrR  rS  c                óÖ   — t        | «      }|€y|\  }}}t        |«      }t        |||¬«      sy	 t        ||||¬«      }t	        |«       |�t        ||¬«       |S # t	        |«       w xY w)a‚  Terminate one verified scoped-lock holder for explicit ``--replace``. Returns the owner PID
    only after that exact PID/start-time identity exited; validation or marker-write failure returns
    None without signalling (a cross-home handoff must place a consumable marker in the target's
    home or its supervisor revives it: flap loop). On POSIX the snapshotted children are reaped.Nr  rQ  )rD  )r3  rC  r$  Ú_terminate_verified_ownerr+  rO  )	r¹   rR  rS  rµ  rþ  rÿ  r  Úowner_childrenÚreplaceds	            r&   Útake_over_scoped_lock_holderrX    sˆ   € ô 1°Ó8€EØ€}ØØ/4Ñ,€IÐ ä/°	Ó:€NÜ Ø˜{Ð>Nõð ð+Ü,ØÐ'Ð;LØ)ô
ˆô 	˜kÔ*ØÐÜ˜n¸ÕCØ€Oøô 	˜kÕ*ús   °A ÁA(c                óô   — t        | |«      }|dk(  r| S |dk7  ry|dddif|dd|d	œffD ]/  \  }}}	 t        | fi |¤Ž t	        | |||¬
«      \  }}	|r| c S |	rŒ/ y y# t        $ r | cY c S t        $ r Y  yw xY w)z½Bounded identity-aware SIGTERM-then-SIGKILL of a verified owner; the PID once it exited, else
    None. Per signal step: ``ProcessLookupError`` => already gone; other ``OSError`` => refuse.r5  r-  Ng      à?r  Fg      Ð?Tr  )r7  r8  )r/  r*  ÚProcessLookupErrorrù   r=  )
rþ  rÿ  rR  rS  rD   r7  r8  rÒ  r5  Úsafe_to_forces
             r&   rU  rU  0  sÆ   € ô
 % YÐ0@ÓA€EØ�ÒØÐØ�‚Øà	˜C '¨5Ð!1Ð2Ø	˜¨ÐFVÑWÐXð$ò Ñˆ�%˜ð	Ü˜)Ñ. vÒ.ô
 !AØÐ'°(À%ô!
Ñˆ�ñ ØÒÚÙð!ð" øô "ò 	ØÔÜò 	Úð	ús   ±AÁA7Á+A7Á6A7c                 óz   — t        t        «       | t        | «      t        j                  «       t        «       dœ«      S )zÊRecord that ``target_pid`` is being stopped intentionally: unexpected SIGTERM exits non-zero
    so service managers revive the gateway; the CLI writes this first so a deliberate stop exits
    cleanly.)r  r  Ústopper_pidr  )r"  r  r  r�   r¼  rê   )r  s    r&   Úwrite_planned_stop_markerr^  N  s4   € ô Ô6Ó8Ø Ô7NÈzÓ7ZÜ—y‘y“{´,³.ñ;ó ð r(   c                  ó4   — t        t        «       t        ¬«      S )zDReturn True when the current process is being intentionally stopped.r'  )r  r  Ú_PLANNED_STOP_MARKER_TTL_Sr`   r(   r&   Ú$consume_planned_stop_marker_for_selfra  X  s   € ä'Ü%Ó'Ô/Iôð r(   c                  ób   — t        t        «       t        «      } | duxr t        | d   | d   «      S )a  Non-destructive watcher probe: True when a live planned-stop marker names us. Never unlinks a
    matching marker (the shutdown handler does the authoritative consume); malformed/expired ones
    are still cleaned up; markers naming another PID are left alone.Nr-   rŒ   )r  r  r`  r  )rû   s    r&   Ú planned_stop_marker_targets_selfrc  _  s5   € ô #Ô#@Ó#BÔD^Ó_€FØ˜ÐÒNÔ"8¸À¹ÀFÈ1ÁIÓ"NÐNr(   rë  c                ó¢  — | xs
 t        «       }t        |«      }t        |«      r�t        |«      t	        |«      f}| �| j
                  nd}d}|D ]A  }t        |«      }|€Œ|€t        |«      nt        ||¬«       }	|	rt        |||¬«      r|c S d}ŒC |�|st        ||¬«       | €
t        «       S dS | €
t        «       nd}
|
€t        ||¬«       |
S )a  PID of a running gateway (lock + PID file verified against the live process), or None.
    An explicit ``pid_path`` is a scoped query into that home's identity files: records are
    validated against the probed home (not the serve process's), and a live record is never
    cleanup-unlinked, so polling another profile must not delete its gateway.pid/gateway.lock
    (#106406). The unscoped path keeps main's poison-file housekeeping: a live record owned by
    another home inside this home's gateway.pid is unlinked on refusal (#89315).NFr·   Trë  )rÒ   rØ   rG  rç  rê  rŽ   rõ  rÉ  rÀ   r¹  r  rÚ  )rÓ   r   Úresolved_pid_pathrF  Úrecordsr¸   Úsaw_live_pidr¹   r  Úhome_okrã  s              r&   rÙ  rÙ  g  s	  € ð !Ò3¤M£OÐÜ/Ð0AÓBÐÜ%Ð&8Ô9äÐ.Ó/Ô1JÐK]Ó1^ð
ˆð ,4Ð+?˜ŸšÀTˆØˆØò 	 ˆFÜ'¨Ó/ˆCØˆ{ØàBOÐBWÔ6°vÔ>Ü8¸È}Ô]Ð]ð ñ Ô;Ø˜¨=õð ’
ð  ‰Lð	 ð  Ð ©Ü%Ð&7À}ÕUØ3;Ð3CÔ-Ó/ÐMÈÐMà6>Ð6FÔ0Ô2ÈD€KØÐÜ!Ð"3À=ÕQØÐr(   c                 óÊ  ‡— t        | «      }t        |«      }t        |d«      }t        |d«      syt        |«      sy|st	        d«      ‚t        |«      t        |«      f}t        |«      st	        d«      ‚t        |d   «      Š‰�‰dk  st        |d   «      ‰k7  rt	        d«      ‚t        ‰«      st	        d	«      ‚t        ‰«      }t        d
„ |D «       «      }|�t        d„ |D «       «      rt	        d«      ‚	 t        |g|¢­Ž st	        d«      ‚	 t        ˆfd„|D «       «      st	        d«      ‚t!        |«      }t"        s‰|fS 	 ddl}	t!        |	j'                  ‰«      j)                  «       «      }
t-        t/        |
dz  «      «      t-        |«      k7  rt	        d«      ‚‰|
fS # t        t        f$ r}t	        d«      |‚d}~ww xY w# t*        $ r}t	        d«      |‚d}~ww xY w)zFReturn a verified process identity or fail on ambiguous runtime state.zgateway PIDzgateway lockNz'active gateway lock has no PID metadataz)gateway PID or lock metadata is malformedr   r-   z(gateway PID and lock identities disagreezgateway identity is not livec              3   ó>   K  — | ]  }|j                  d «      –— Œ y­w)r»  N)r¼   )r‡   r¹   s     r&   r‰   z2get_running_pid_identity_strict.<locals>.<genexpr>¨  s   è ø€ ÒB°�6—:‘:˜l×+ÑBùs   ‚c              3   ó$   K  — | ]  }|d u –— Œ
 y ­wrT   r`   )r‡   r6   s     r&   r‰   z2get_running_pid_identity_strict.<locals>.<genexpr>©  s   è ø€ Ò#F°e E¨T¤MÑ#Fùrn  z$gateway creation time is unavailablez gateway process identity changedz"gateway creation time is malformedc              3   ó6   •K  — | ]  }t        |‰«      –— Œ y ­wrT   )r¹  )r‡   r¹   r  s     €r&   r‰   z2get_running_pid_identity_strict.<locals>.<genexpr>°  s   øè ø€ ÒSÀÔ/°¸×<ÑSùs   ƒz1runtime metadata does not identify a live gatewayz*exact gateway creation time is unavailablerH  )r   rØ   rK  rM  rJ  rç  rê  r2  rî  rô  r  Útuplerv  r  r  r˜   r^   r  rK  rM  rN  r¡   r]   rL  )rÓ   re  rF  r  rf  rð  ÚstartsrS   r+  rK  Úexact_create_timer  s              @r&   Úget_running_pid_identity_strictrp  ’  sù  ø€ ä˜X›ÐÜ/Ð0AÓBÐÜ$Ð%6¸ÓF€Jô Ð1°>ÔBØÜ1Ð2DÔEØÙÜÐDÓEÐEÜÐ 1Ó2Ô4MÐN`Ó4aÐb€GÜˆwŒ<ÜÐFÓGÐGÜ
˜7 1™:Ó
&€CØ
€{�c˜Q’hÔ"2°7¸1±:Ó">À#Ò"EÜÐEÓFÐFÜ�sÔÜÐ9Ó:Ð:Ü+¨CÓ0€MÜÑB¸'ÔBÓB€FØÐ¤Ñ#F¸vÔ#FÔ FÜÐAÓBÐBðJÜ! -Ð9°&Ó9ÜÐAÓBÐBð :ô ÓSÈ7ÔSÔSÜÐNÓOÐOÜ�MÓ"€GÝØ�Gˆ|ÐðRÛä! &§.¡.°Ó"5×"AÑ"AÓ"CÓDÐô Œ5Ð" SÑ(Ó)Ó*¬c°'«lÒ:ÜÐ=Ó>Ð>ØÐ!Ð!Ð!øô# ”zÐ"ò JÜÐ?Ó@ÀcÐIûðJûô ò RÜÐGÓHÈcÐQûðRús0   Ã:F% Å,G Æ%GÆ4G Ç GÇ	G"ÇGÇG")r   Úttl_secondsrq  c                óp  — |dk  rt        | |¬«      S | xs
 t        «       }| du }|t        |«      g}|r|j                  t	        «       «       t        d„ |D «       «      }t        |«      t        |«      |f}t        j                  «       }t        5  t        j                  |«      }	|	�!||	d   z
  |k  r|	d   |k(  r|	d   cddd«       S ddd«       t        | |¬«      }
t        d„ |D «       «      }t        5  t        j                  «       ||
ft        |<   ddd«       |
S # 1 sw Y   ŒWxY w# 1 sw Y   |
S xY w)z¹Cached ``get_running_pid()`` for dashboard polling: short TTL, invalidated on PID/lock/
    runtime-status file changes, so status endpoints do not re-flock ``gateway.lock`` constantly.r   rë  Nc              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrT   ©rÿ  ©r‡   Úps     r&   r‰   z)get_running_pid_cached.<locals>.<genexpr>Ð  s   è ø€ Ò@°1Ô+¨A×.Ñ@ùrŠ   r-   rŒ   c              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrT   rt  ru  s     r&   r‰   z)get_running_pid_cached.<locals>.<genexpr>Ø  s   è ø€ ÒJ¸QÔ 5°a× 8ÑJùrŠ   )rÙ  rÒ   rØ   r™   rÛ   rm  r\   r_   r>   r?   r÷  r   r¼   )rÓ   r   rq  re  Úinclude_runtime_statusÚwatchedÚ	signaturerë  r�   Úcachedr  Úrefreshed_signatures               r&   rØ  rØ  Â  s=  € ð �aÒÜ˜x°}ÔEÐEØ Ò3¤M£OÐØ%¨Ð-Ðà Ô"8Ð9JÓ"KÐL€GÙØ�‰Ô/Ó1Ô2ÜÑ@¸Ô@Ó@€IÜÐ Ó!¤4¨Ó#6Ð8NÐ
O€CÜ
�.‰.Ó
€CÜ	(ñ Ü+×/Ñ/°Ó4ˆØÐ #¨¨q©	¡/°[Ò"@ÀVÈAÁYÐR[ÒE[Ø˜!‘9÷÷ ñ ô ˜(°-Ô
@€CÜÑJÀ'ÔJÓJÐÜ	(ñ WÜ+/¯>©>Ó+;Ð=PÐRUÐ*VÔ" 3Ñ'÷Wà€J÷ð ú÷Wà€Jús   Â/DÃ6D+ÄD(Ä+D5c                  óX   — 	 t        «       j                  d¬«       y# t        $ r Y yw xY w)z/Remove the planned-stop marker unconditionally.Trá  N)r  rã  rù   r`   r(   r&   Úclear_planned_stop_markerr~  ã  s-   € ðÜ%Ó'×.Ñ.¸$Ð.Õ?øÜò Ùðús   ‚ �	)¨)c                ó    — t        | |¬«      duS )z1Check if the gateway daemon is currently running.rë  N)rÙ  )rÓ   r   s     r&   Úis_gateway_runningr€  ê  s   € ô ˜8°=ÔAÈÐMÐMr(   rU   )r[  g      ^@rT   rQ  rV   )ÇrZ   rp   r–   r1   r;  rØ  Úloggingrô   r�   r©  rs  r  r!  r½  r   r>   Údataclassesr   r   r   Úpathlibr   Útypingr   r   r	   r
   rÇ   r   r   r   Úutilsr   rc  r	  r  r‘  rÚ   rã   r  ÚobjectrY  rÖ   r7  r  Ú&_GATEWAY_RUNNING_PID_CACHE_TTL_SECONDSÚLockr÷  r   r[   rm  r\   r_   r^   r]   rx   Ú	getLoggerrW   rN   r   ÚRLockri   ra   rb   rL   rg   rj   rn   rr   rt   r¨   rª   r°   r¶   rÀ   ÚcompilerÅ   rÌ   rÏ   rÒ   rØ   rÛ   rç   rê   rö   rý   Ú	frozensetr  r
  r*  r  ÚSTART_TIME_DRIFT_TOLERANCEr7  r?  rC  r  rR  rb  r�  r…  rˆ  rŠ  r“  r–  r›  rŸ  r’  r¥  r¬  r¯  r¶  r¹  r¾  rÆ  rÉ  rÓ  rd   rK   rä  rç  rê  rî  rò  rõ  rù  rÿ  r  r  rô  r  r  r4  r=  r?  rA  rD  rG  rK  rM  rP  rO  r\  ra  r…  r‰  rŒ  rŽ  r�  Ú_RUNTIME_STATUS_STALE_TTL_Sr•  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+  r3  r/  r=  rC  rO  rX  rU  r^  ra  rc  rÙ  rp  rØ  r~  r€  r`   r(   r&   ú<module>r�     s¼  ðòBó Û Û Û Û Û Û Û 	Û 	Û Û Û Û 
Û Û Ý !ß 'Ý ß 6Ó 6ç hÑ hÝ #à‡<�<�7ÒÜãà €Ø+Ð Ø €Ø�l‰l˜gÑ%€Ù	‹€Ø'Ð ØÐ ð #Ð Ø),Ð &Ø"0 )§.¡.Ó"2Ð à_aÐ ˜D  s¨D°$ Ñ!7¸¸uÀeÈXÐVYÉ]Ð?ZÑ9[Ð![Ñ\Ó aà	ˆ×	Ñ	˜8Ó	$€÷`Tñ `TðF -˜YŸ_™_Ó.Ð Ø-1Ð ˜H T™NÓ 1Ø26Ð �x  S¨# X¡Ñ/Ó 6ðN˜dð N¨T°#°s°(©^ð NÀÀSÈ#ÀXÁó Nð :>Ð ˜Ð!5Ñ6Ó =ð&Ð$8ó &òE %ð E°$ó EòS¨eð S¸dó Sô�
ô ð ,1ñØLQòØðØ#(ðØDIðàˆiÑóð<% $ó %ð9 ¨¡ð 9°ó 9ð
J˜D 3™Jð J¨t°c©zð J¸dó Jð PTòØ�T˜#˜s˜(‘^Ñ$ðØ8@ÀÈÁÑ8Lðà	óð& �B—J‘JÐ<Ó=Ð ð $¨¡*ð °¸#±ó ð"	] H¨T°#°s°(©^Ñ$<ð 	]ÀÈ#Áó 	]ð6�tó 6òK X¨d¡^ð K¸tó Kð= $ó =ð2�tó 2ð&2�có 2ð
 #Ð ð ð ¨°©ó ñ: "Ð#;Ð=PÐ"QÓRÐ ð Cð ¨Có ð.  %ÈTò&CØ	ð&CØð&CØ;CÀE¹?ð&Cà	ó&CðRZ ð Z°ð Z¸ó Zð !Ð ð Qkñ :¨Cð :¸#ð :È#ð :Ðosó :ðE˜#ð E #ó EðE ð E¨sð E°tó Eð
 ð 
¨°#©ó 
ð( ð (¨°©ó (ð
˜sð  x°¡}ó ð8-¨¨t©ð -¸¸d¹
ó -ð`9¨S°4©Zð 9¸Dó 9ð
F°S¸4±Zð FÀDó FðF Sð F¨Tó FðY t¨C°¨H¡~ð Y¸$ó YðQ¨ð Q°(¸3±-ó Qð
	 ð 	¨°©ó 	ð (˜RŸZ™ZÐ(_Ó`Ð ð Sð ¨T°#©Yó ðe¨sð e¸Sð eÀTó eðj¨cð jÀð jÈ$ó jð*h 3ð h°dð h¸tó hð$ JNòbØ��c�‰NðbØ!$ðbØ8@À¹ðbà	óbð"˜4ó ð 4¨¨S¨¡>ó ð"Y°8¸DÀÀcÀ¹NÑ3Kð YÐPTó Yð d¨3°¨8¡nó ð 8=ò :˜$ð :°ð :ÀÈ$ÈsÐTWÈxÉ.ÑAYó :ð*I˜4ð I¨$¨s°C¨x©.ð I¸Tó Ið%˜$ð % 4ó %ò
J˜x¨™~ð J¸À$¹ó JòT¨°$©ð TÀ8ÈDÐQTÐVYÐQYÉNÑC[ó Tò˜X d¨3°¨8¡nÑ5ð ¸Cð ÈHÐUXÉMó ð[¨#ð [¸cð [Àdó [ð
 (¨4°°S°©>Ñ":ð ¸xÈ¹}ó ô+ð
. ð .¨¨t°X¸c±]ÀHÈSÁMÐ/QÑ)Ró .ð)¨ð )Àð )Èó )ð dó ð )�Sð )˜Tó )ðX˜#ð  $ó ð0 #ð ¨$ó ô28ð dó ô>	ð, 4ó ,ð	 ó 	ò$¨h°t©nð $Èó $ð S˜dð S¨3ð S°4ó SðQ°dð Q¸tó Qôð˜4ð ¨¨c°3¨h©ð ¸Dó ðI˜d 3¨ 8™nð I¸ó IðZ¨ð Z°°c¸3°h±ó Zð #°vÐX^Ø°FÈFÐjpØ°6ÐRXØ ¸ÐV\Ø'-Ø°FÐ\aØ,0Ø¸òA/ØðA/Ø14ðA/ØRUðA/àðA/à.1ðA/àFIðA/àdgðA/ð ðA/ð .1ðA/ð MPðA/ð ð	A/ð 47ð	A/ð QTð	A/ð
 "%ðA/ð ðA/ð /2ðA/ð VZðA/ð % S™MðA/ð ðA/ð 26ðA/ð ˆ4��c˜3�h‘  c¨3 h¡Ð/Ñ0óA/ðHBØ˜3 ˜8‘nðBØ/3°C¸°H©~ðBà	óBð  %Àdò9Øð9Ø4<¸U±Oð9ØVYð9à	ó9ð(
 Sð 
¨Só 
ò?˜h t™nð ?¸ÀÀcÈ3ÀhÁÑ8Pó ?ð "Ð ð 4OñcØ�T˜#˜s˜(‘^Ñ$ðcØ-0ðcà	ócðj¨8°D¸¸c¸±NÑ+Cð jÈÐQTÉó jð5 x°°S¸#°X±Ñ'?ð 5ÀDó 5ð
˜Sð  Só ñ & y°*Ð&=Ó>Ð ð2¨Dð 2Àð 2ÐUXð 2Ð]aó 2ðP°ð PÀcð PÈdó Pñ
 �$Ô÷-ð -ó ð-ð ¨ð °¸#±ó ð"\°$ð \¸8ÀEÈ#ÈtÐTWÐY\ÐT\É~ÐJ]ÑD^Ñ;_ó \ð<¨h°t¸CÀ¸H±~Ñ.Fð ÐQTð ÐY]Ð^aÐcfÐ^fÑYgó ð4
I°¸¸cÀ3¸h¹Ñ0Hð 
IÐSVð 
IÐ[_Ð`cÐehÐ`hÑ[ió 
Ið &*¸&ØRVØÐPTØHLØ@DòKØ˜T‘NðKØ47ðKà˜8 B¨¨d°H¸TÀ#ÀsÀ(¹^Ñ4LÐ.LÑ(MÐ$MÑNÑOðKð ðKð (0°¸¸hÀs¹mÐ9KÑ0LÑ'MðKð ˜X c¨8°D¸¸c¸±NÑ+CÐ&CÑDÑEð	Kð
   ¨¨h°s©mÐ);Ñ <Ñ=ðKð óKð^ )-ñØQUòØ�d˜3 ˜8‘nÑ%ðØ@HÈÁðàˆc�]óð0p Dð p¨X°c©]ó pô"	#ð-¨4°°S°©>ð -ÈÐRUÉð -Ð[_ó -ð*˜Sð  Tó ð EIò-Øð-Øð-Ø)1°$°s¸C°x±.Ñ)Að-à
ˆ4�˜$˜s C˜x™.Ñ)Ð)Ñ*ó-ð`#˜sð #¨cð #°dó #ð #'È$òØ˜3‘-ðØ:BÀ3¹-ðàóð> 5Ð ØÐ Ø <Ð ØÐ ò,¨8°D©>ð ,ÀTó ,ðF tó Fð ð ¨Sð °Tó ð
? ð 
?¨Sð 
?°X¸eÀDÈÈcÈÁNÐTWÐY\ÐD\Ñ>]Ñ5^ó 
?ð	^ sð 	^¸sð 	^Àtó 	^ð tð °sð ¸tó ð0 7;ÐU[òØðØ%-¨d¡^ðØORðà	óð*˜ð  d¨3°¨8¡nð ¸ó ðc¨$ó cò
< x°¡~ð <Àó <ð
!4°°c¸3°h±ð !4ÀHÈUÐSVÐX[Ð]aÐSaÑMbÑDcó !4ðHG¨ð G¸sð GÀsó GðRØðRØ&)ðRØ8;ðRØDIðRà
ˆ4�ˆ:ÑóRð Cð ¨Dó ð PSò , Dð ,¸ð ,Àuð ,ÐWZó ,ð` 9;ÐRTòØ��c�‰NðØ25ðØLOðàˆc�]óð<ØðØ&)ðØADðØVYðàˆc�]óð<¨#ð °$ó ð¨dó ðO¨$ó Oð  $ñ(Ø>Bò(Ø�t‰nð(Ø7;ð(àˆc�]ó(ðV-"¨dð -"°xÀÀcÈ5ÀjÑ@QÑ7Ró -"ðb  $ñØ>BØ?óØ�t‰nðØ7;ñàðð ˆc�]ôõBð  $ñNð òNØ�t‰nðNð ðNð 
õ	Nr(   