+
    jHàj‘‡  ã            
      óˆ  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIHt ^ RIHtHtHt ^ RIHt ^ RIHtHtHt ^ RIHt ^ RIHt ]P6                  ! ]4      t^tR	tR
t Rt!Rt"Rt#]!]"]#3t$/ t%R]&R&   ]! RR7       ! R R4      4       t'R R lt(R R lt)R R lt*R R lt+R R lt,R R lt-R  R! lt.R" R# lt/RsR$ R% llt0R& R' lt1R(t2R) R* lt3R+ R, lt4R- R. lt5R/ R0 lt6R1 R2 lt7R3 R4 lt8R5 R6 lt9R7] /R8 R9 llt:R:R;/R< R= llt;R> R? lt<R@ RA lt=RB RC lt> ! RD RE]P~                  4      t@RF RG ltARH RI ltBRJ RK ltCRL RM ltDRN RO ltERP RQ ltFRRRSRTRRURtRVRRWRS/RX RY lltGRZ R[ ltHR\ R] ltIR^ R_ ltJR` Ra ltK. tLRb]&Rc&   / tMRd]&Re&   Rf Rg ltNRh Ri ltORj Rk ltPRl Rm ltQRnR/Ro Rp lltRRq Rr ltSR# )uu”  Host-wide singleton rendezvous: one lock + one record per ROLE per OS user.

Multiplex-only (Teknium ruling): exactly ONE ``hermes serve`` and ONE ``hermes gateway run``
per host, each multiplexing every profile. The per-``HERMES_HOME`` gateway lock/PID files
(``gateway.status``) cannot express that â€” N profiles are N homes, so N processes each take
their own flock and none of them ever sees the others. This module adds the missing layer:

* a **host lock** (flock/``msvcrt``) held for the lifetime of the winning process, and
* a **rendezvous record** the winner publishes so a second invocation can find it, prove it
  is the same live process, and ATTACH instead of binding a second port.

Both live in :func:`gateway.status._get_lock_dir` â€” the only cross-profile lock root already
in the tree (``$HERMES_GATEWAY_LOCK_DIR`` else ``$XDG_STATE_HOME/hermes/gateway-locks``),
which scopes to the **OS user**. That is the correct granularity: separate OS users have
separate ``$HOME``s, separate ``~/.hermes`` profile roots, separate ports-by-convention and
separate credentials, so "one per host" means "one per host per OS user".

**Staleness is proved, never assumed.** New records pair the PID with the repository's stable
process-start fingerprint (boot-relative on Linux/WSL); ``createTime`` remains the legacy fallback.
A dead PID or a mismatched process incarnation is STALE and ignored â€” an attaching client must
never dial a recycled PID's port.

**Relationship to ``spawn-ledger.json``** (``hermes_cli/process_identity.py``): the ledger stays
the append-only machine roster of every long-lived Hermes process (Desktop's attach ladder reads
it) and is still written unchanged. It cannot be the host record: it has no lock, no
single-writer semantics, no removal on exit, and no place to publish a protocol version or
an authentication handle. The record here is authoritative for "who owns this host role"; the
ledger remains authoritative for "what is running". Both are written. New host records pair the
ledger's live-PID proof with ``gateway.status``'s canonical process-start fingerprint; legacy
records without that fingerprint keep the old ``(pid, create_time)`` proof.
)ÚannotationsN)Ú	dataclass)ÚdatetimeÚtimezoneÚUTC)ÚPath)ÚAnyÚOptionalÚSequence)Úmake_user_dir)Úatomic_json_writez/api/host/identityg       @ÚgatewayÚservezdesktop-servezdict[tuple[str, str], Any]Ú_lock_handlesT)Úfrozenc                  óÂ   € ] tR t^Tt$ RtR]R&   R]R&   R]R&   R]R&   R	]R
&   R]R&   R]R&   R]R&   R]R&   RtR]R&   RtR	]R&   RtR]R&   R R lt	]
R R l4       tRtR# )Ú
HostRecordzTA published host-role owner. ``profiles`` is the SERVED set, not the launch profile.ÚstrÚroleÚintÚpidúOptional[float]Úcreate_timeÚhostúOptional[int]ÚportÚprotocol_versionÚtoken_fingerprintútuple[str, ...]ÚprofilesÚ
updated_atÚ ÚhomeNÚ
start_timeÚ
boot_epochc               ó   € V ^8„  d   QhRR/# )é   Úreturnzdict[str, Any]© )Úformats   "Ú</home/ubuntu/.hermes/hermes-agent/gateway/host_rendezvous.pyÚ__annotate__ÚHostRecord.__annotate__r   s   € ÷ 
ñ 
˜ñ 
ó    c                	ó8  € R V P                   RV P                  RV P                  RV P                  RV P                  RV P
                  RV P                  RV P                  RV P                  R	V P                  R
\        V P                  4      RV P                  /# )r   r"   r   Ú
createTimeÚ	startTimeÚ	bootEpochr   r   ÚprotocolVersionÚtokenFingerprintr   Ú	updatedAt)r   r"   r   r   r#   r$   r   r   r   r   Úlistr   r    )Úselfs   &r*   Úto_jsonÚHostRecord.to_jsonr   sƒ   € à�D—I‘IØ�D—I‘IØ�4—8‘8Ø˜$×*Ñ*Ø˜Ÿ™Ø˜Ÿ™Ø�D—I‘IØ�D—I‘IØ˜t×4Ñ4Ø × 6Ñ 6Øœ˜TŸ]™]Ó+Ø˜Ÿ™ð
ð 	
r-   c               ó    € V ^8„  d   QhRRRR/# )r&   Úpayloadr   r'   úOptional[HostRecord]r(   )r)   s   "r*   r+   r,   ƒ   s   € ÷ 
ñ 
 ð 
Ð(<ñ 
r-   c                	óÔ  € \        V\        4      '       g   R # VP                  R4      pVP                  R4      p\        V\        4      '       d   V^ 8:  g   V\        9  d   R # VP                  R4      pVP                  R4      pVP                  R4      pVP                  R4      pVP                  R4      pT ! TT\        V\        \
        34      '       d   \        V4      MR \        V\        4      '       d)   \        V\        4      '       g   V^ 8”  d   \        V4      MR \        VP                  R4      ;'       g    R	4      \        VP                  R
4      ;'       g    R	4      \        V\        4      '       d   ^ Tu;8  d   R8:  d   M M\        V4      MR \        V\        4      '       d   TM^ \        VP                  R4      ;'       g    R	4      \        V\        4      '       d-   \        ;QJ d    . R V 4       F  NK  	  5M! R V 4       4      MR\        VP                  R4      ;'       g    R	4      \        VP                  R4      ;'       g    R	4      R7      # )Nr   r   r/   r0   r   r   r2   r1   r!   r   iÿÿ  r3   c              3  óh   "  € T F(  p\        V\        4      '       g   K  \        V4      x € K*  	  R # 5i©N)Ú
isinstancer   ©Ú.0Úps   & r*   Ú	<genexpr>Ú'HostRecord.from_json.<locals>.<genexpr>�   s!   é € ÐJ©8 a´zÀ!ÄS×7Iœ6œ3˜qŸ6˜6«8ùs   ‚2Ÿ2r4   r"   ©r   r   r   r#   r$   r   r   r   r   r   r    r"   r(   )
r?   ÚdictÚgetr   Ú_ROLESÚfloatÚboolr   r5   Útuple)	Úclsr:   r   r   ÚcreateÚstartr   r   Úversions	   &&       r*   Ú	from_jsonÚHostRecord.from_json‚   sÉ  € ä˜'¤4×(Ò(ÙØ�k‰k˜%Ó ˆØ�{‰{˜6Ó"ˆÜ˜#œs×#Ò# s¨a¤x°4¼vÔ3EÙØ—‘˜\Ó*ˆØ—‘˜KÓ(ˆØ�{‰{˜6Ó"ˆØ—;‘;˜zÓ*ˆØ—+‘+Ð/Ó0ˆÙØØÜ)3°F¼SÄ%¸L×)IÒ)Iœ˜fœÈtô ˜e¤S×)Ò)´*¸UÄD×2IÒ2IÈeÐVWÌiô �E”
àä˜7Ÿ;™; {Ó3×9Ð9°rÓ:Ü�W—[‘[ Ó(×.Ð.¨BÓ/Ü(¨¬s×3Ò3¸¸DÖ8IÀE×8I”�T”ÈtÜ(2°7¼C×(@Ò(@™WÀaÜ! '§+¡+Ð.@Ó"A×"GÐ"GÀRÓHÜNXÐYaÔcg×NhÒNh—U”UÑJ©8ÓJ—U‘UÑJ©8ÓJÔJÐnpÜ˜7Ÿ;™; {Ó3×9Ð9°rÓ:Ü�W—[‘[ Ó(×.Ð.¨BÓ/ô!
ð 	
r-   r(   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__annotations__r"   r#   r$   r7   ÚclassmethodrP   Ú__static_attributes__r(   r-   r*   r   r   T   ss   ‡ á^à
ƒIØ	ƒHØ Ó Ø
ƒIØ
ÓØÓØÓØÓØƒOð €Dˆ#ƒNð
 !%€J�Ó$ð
 €J�Óõ
ð  ô
ó ô
r-   r   c               ó   € V ^8„  d   QhRR/# ©r&   r'   r   r(   )r)   s   "r*   r+   r+   £   s   € ÷ ñ ˜ñ r-   c                 ó   € ^ RI Hp  V ! 4       # )zBPer-OS-USER rendezvous dir (shared by every profile of this user).©Ú_get_lock_dir)Úgateway.statusr^   r]   s    r*   Úhost_state_dirr`   £   s   € å,á‹?Ðr-   c               ó   € V ^8„  d   QhRR/# r[   r(   )r)   s   "r*   r+   r+   ª   s   € ÷ ñ ˜tñ r-   c                 óˆ  € \        4       p \        V RRRR7       \        P                  R8w  d€   \        P
                  ! \        4      ;_uu_ 4        \        P                  ! V P                  4       P                  4      ^?,          '       d   \        P                  ! V R4       RRR4       V # V #   + '       g   i     T # ; i)a°  The rendezvous dir, created owner-only (``0o700``) and tightened if it is not.

A bare ``mkdir`` under the common ``umask 002`` leaves the dir group-writable, and the record
inside it is what every lifecycle verb believes: a same-group process could unlink+replace it
and choose this host's ATTACH answers (which home to dial, which profiles are "served").
Ownership of the dir is ours, so widening is repaired rather than refused.
TiÀ  )ÚparentsÚexist_okÚmodeÚwin32N)r`   r   ÚsysÚplatformÚ
contextlibÚsuppressÚOSErrorÚstatÚS_IMODEÚst_modeÚosÚchmod)Ú	directorys    r*   Úensure_host_state_dirrr   ª   s‰   € ô Ó €IÜ�) T°D¸uÕEÜ
‡|�|�wÔÜ× Ò ¤×)Õ)Ü�|Š|˜IŸN™NÓ,×4Ñ4Ó5¸×=Ô=Ü—’˜ EÔ*÷ *ð Ðˆ9Ð÷ *Ö)ð Ðús   ÁAB0Â0C	c               ó    € V ^8„  d   QhRRRR/# )r&   Úpathr   r'   rJ   r(   )r)   s   "r*   r+   r+   »   s   € ÷ ñ ˜ð  $ñ r-   c                ób  € \         P                  R8X  d   R#  V P                  4       pV P                  P                  4       p\
        P                  ! 4       pTP                  T8w  g   TP                  T8w  d$   \        P                  RYP                  T4       R# R#   \         d     R# i ; i)uø  True when ``path`` was written by THIS OS user inside a dir this user owns.

``read_record`` hands its result straight to :mod:`gateway.host_attach`, which dials the home
it names and believes the served set it carries. A hand-written record therefore buys an
attacker the lifecycle verdict for every profile on the host, so the file's ``st_uid`` â€” not
its contents â€” is what makes it a record at all. Windows sets no ACLs from mode bits; there
the dir already lives under the user's own state root.
rf   TFz6ignoring host record %s: owned by uid %s (expected %s))
rg   rh   rl   Úparentrk   ro   ÚgetuidÚst_uidÚloggerÚwarning)rt   Úinforv   Úuids   &   r*   Ú_record_is_ownr}   »   sŒ   € ô ‡|�|�wÔÙðØ�y‰y‹{ˆØ—‘×!Ñ!Ó#ˆô �)Š)‹+€CØ‡{�{�cÔ˜VŸ]™]¨cÔ1Ü�‰ØDÀdÏKÉKÐY\ô	^áÙøô ô Úðús   ™*B ÂB.Â-B.c               ó    € V ^8„  d   QhRRRR/# ©r&   r   r   r'   r(   )r)   s   "r*   r+   r+   Ó   s   € ÷ ñ ˜#ð  #ñ r-   c                ó:   € V \         9  d   \        R V : 24      hV # )zunknown host role: )rH   Ú
ValueError©r   s   &r*   Ú_validated_rolerƒ   Ó   s"   € Ø”6ÔÜÐ.¨t©hÐ7Ó8Ð8Ø€Kr-   c               ó    € V ^8„  d   QhRRRR/# ©r&   r   r   r'   r   r(   )r)   s   "r*   r+   r+   Ù   s   € ÷ Cñ C�cð C˜dñ Cr-   c                ó>   € \        4       R \        V 4       R2,          # )úhost-z.json©r`   rƒ   r‚   s   &r*   Úrecord_pathr‰   Ù   ó   € ÜÓ ¤o°dÓ&;Ð%<¸EÐBÕBÐBr-   c               ó    € V ^8„  d   QhRRRR/# r…   r(   )r)   s   "r*   r+   r+   Ý   s   € ÷ Cñ C�Cð C˜Dñ Cr-   c                ó>   € \        4       R \        V 4       R2,          # )r‡   z.lockrˆ   r‚   s   &r*   Ú	lock_pathr�   Ý   rŠ   r-   c               ó    € V ^8„  d   QhRRRR/# r…   r(   )r)   s   "r*   r+   r+   á   s   € ÷ Dñ D�Sð D˜Tñ Dr-   c                ó>   € \        4       R \        V 4       R2,          # )r‡   z.tokenrˆ   r‚   s   &r*   Ú
token_pathr�   á   s   € ÜÓ ¤o°dÓ&;Ð%<¸FÐCÕCÐCr-   c               ó    € V ^8„  d   QhRRRR/# )r&   Útokenr   r'   r(   )r)   s   "r*   r+   r+   å   s   € ÷ ^ñ ^˜Sð ^ Sñ ^r-   c                óŒ   € V '       d<   \         P                  ! V P                  RR4      4      P                  4       R,          # R# )zQShort, non-reversible handle for a session token (safe to publish in the record).úutf-8Úreplace:Né   Nr!   )ÚhashlibÚsha256ÚencodeÚ	hexdigest)r’   s   &r*   r   r   å   s2   € çPUŒ7�>Š>˜%Ÿ,™, w°	Ó:Ó;×EÑEÓGÈÕLÐ]Ð[]Ð]r-   c               ó    € V ^8„  d   QhRRRR/# )r&   r   r   r'   r   r(   )r)   s   "r*   r+   r+   ê   s   € ÷ %ñ %˜]ð %°oñ %r-   c                ó   € ^ RI Hp V! V 4      # )zKCreation time of ``pid`` (default: this process); ``None`` when unknowable.)Ú_process_create_time)Úhermes_cli.process_identityr�   )r   r�   s   & r*   Úprocess_create_timerŸ   ê   s   € å@á Ó$Ð$r-   c               ó$   € V ^8„  d   QhRRRRRR/# )r&   r   r   r   r   r'   úOptional[bool]r(   )r)   s   "r*   r+   r+   ñ   s!   € ÷ 0ñ 0 #ð 0°Oð 0Èñ 0r-   c                ó   € ^ RI Hp V! W4      # )zVReuse the spawn ledger's proof: True/False when provable, ``None`` when it cannot say.)Ú_pid_alive_matches)rž   r£   )r   r   r£   s   && r*   Ú_pid_incarnation_matchesr¤   ñ   s   € å>á˜cÓ/Ð/r-   g      ^@c               ó   € V ^8„  d   QhRR/# )r&   r'   r   r(   )r)   s   "r*   r+   r+      s   € ÷ ñ �Oñ r-   c                 ó�   €  ^ RI p \        V P                  4       4      #   \         d    \        P                  RRR7        R# i ; i)é    Nz5boot time unreadable; legacy record age check skippedT©Úexc_info)ÚpsutilrI   Ú	boot_timeÚ	Exceptionry   Údebug)rª   s    r*   Ú
_boot_timer®      sB   € ðÛä�V×%Ñ%Ó'Ó(Ð(øÜô Ü�‰ÐLÐW[ˆÔ\Úðús   ‚ Ÿ"AÁAc               ó    € V ^8„  d   QhRRRR/# ©r&   r   r   r'   r¡   r(   )r)   s   "r*   r+   r+   
  s   € ÷ Gñ G˜Sð G ^ñ Gr-   c                óZ  € \        V 4      '       d   R# ^ RIHp  \        P                  ! \        \        V 4      4      \        P                  4      pT! \        P                  ! TRRR	7      4      '       * #   \         d     R# \         d    \        P                  RT RR7        R# i ; i)
ai  True when no process holds the host lock for ``role``; ``None`` when it cannot be probed.

Read-only callers (status, dashboard) reach this, so it never creates the lock dir or file: a
missing lock file means nobody holds the lock. The probe takes the lock for an instant, so a
gateway claiming it in that instant gets HELD_BY_OTHER and its supervisor retries.
F©Ú_probe_lock_fileTz host %s lock could not be probedr¨   Nzr+r”   ©Úencoding)Úowns_host_lockr_   r³   ro   Úopenr   r�   ÚO_RDWRÚFileNotFoundErrorrk   ry   r­   Úfdopen)r   r³   Úfds   &  r*   Ú_host_lock_is_freer¼   
  s†   € ô �d×ÒÙÝ/ðÜ�WŠW”Sœ 4›Ó)¬2¯9©9Ó5ˆñ  ¤§	¢	¨"¨d¸WÔ EÓFÔFÐFøô ô ÚÜô Ü�‰Ð7¸ÈˆÔMÚðús   ›7A6 Á6B*ÂB*ÂB*Â)B*c               ó   € V ^8„  d   QhRR/# )r&   r'   r   r(   )r)   s   "r*   r+   r+     s   € ÷ ñ ˜Sñ r-   c                 ó„   €  ^ RI Hp  V ! 4       ;'       g    R#   \         d    \        P	                  RRR7        R# i ; i)u^   Identity of this boot. Empty when /proc is unreadable â€” a recorded epoch is then unprovable.©Úcurrent_instantiation_epochr!   z4boot epoch unreadable; host liveness will not use itTr¨   )Úgateway.drain_controlrÀ   r¬   ry   r­   r¿   s    r*   Ú_current_boot_epochrÂ     s?   € ðÝEá*Ó,×2Ð2°Ð2øÜô Ü�‰ÐKÐVZˆÔ[Úðús   ‚ – ™"?¾?c               ó    € V ^8„  d   QhRRRR/# )r&   Úrecordr   r'   r¡   r(   )r)   s   "r*   r+   r+   *  s   € ÷ ,ñ ,¨
ð ,°~ñ ,r-   c                ó¦  € V P                   p\        4       pV'       d   V'       g   R# W8w  d   R# V P                  f   V P                  f   R# V P                  f"   \	        V P
                  V P                  4      pMQ\	        V P
                  R4      pVRJd   V# ^ RIHpHp V! V P
                  4      pVf   RMV! V P                  V4      pVRJd   V# V P                  \        8X  df   V'       g^   V P                  eP   \        4       pVeB   V P                  V\        ,
          8  d&   \        V P                  4      p	V	f   R# V	'       d   R# R# )uÉ  Whether the record still names the same live process incarnation.

New records prefer the canonical start fingerprint: on Linux/WSL it comes from /proc start
ticks and is immune to wall-clock shifts that move psutil.create_time(). Those ticks are
NOT unique across reboots â€” they reset, and an early-boot PID reused within the drift
tolerance looks like the dead owner. A published boot epoch that differs from this boot's is a
mismatch; one that cannot be compared (this boot's epoch is unreadable) is unprovable, ``None``.

A gateway record with no epoch (published before ``bootEpoch`` existed, or on any platform
without /proc, where the epoch is always empty) whose create_time predates this boot is a
mismatch only while nobody holds the host lock. A record with neither create_time nor
start_time proves nothing about its pid, so it never vouches for one. Legacy records without
a start fingerprint keep the create-time check. Do not infer identity from argv.
NFT)Úget_process_start_timeÚstart_time_fingerprints_match)r$   rÂ   r#   r   r¤   r   r_   rÆ   rÇ   r   ÚROLE_GATEWAYr®   Ú_LEGACY_BOOT_SLACK_Sr¼   )
rÄ   Úrecorded_epochÚepochÚmatchedÚaliverÆ   rÇ   ÚcurrentÚbootÚ	lock_frees
   &         r*   Ú_record_incarnation_matchesrÑ   *  s  € ð ×&Ñ&€NÜÓ!€EßßÙØÔ"ÙØ×ÑÒ  V×%7Ñ%7Ò%?ÙØ×ÑÒ Ü*¨6¯:©:°v×7IÑ7IÓJ‰ä(¨¯©°TÓ:ˆØ˜ÓØˆLßXá(¨¯©Ó4ˆØ!š/‘$Ñ/LÈV×M^ÑM^Ð`gÓ/hˆØ�dÓØˆØ‡{�{”lÔ"¯>¸f×>PÑ>PÒ>\Ü‹|ˆØÒ × 2Ñ 2°TÔ<PÕ5PÔ PÜ*¨6¯;©;Ó7ˆIØÒ ÙßÙÙr-   c               ó    € V ^8„  d   QhRRRR/# )r&   rÄ   r;   r'   rJ   r(   )r)   s   "r*   r+   r+   Y  s   € ÷ 8ñ 8Ð0ð 8°Tñ 8r-   c                óV   € V f   R# V P                   \        8w  d   R# \        V 4      RJ # )aa  A record nobody may attach to: absent, unknown protocol, dead PID, or PID reuse.

``None`` from the liveness probe (no psutil, permission denied, an unexpected psutil error)
means UNPROVABLE, and an unprovable record is a CANDIDATE, never an owner: it survives this
predicate only to be handed to :func:`probe_owner`, which dials the recorded port and makes
the owner prove itself. Treating unprovable as dead would let a second process bind a second
port; treating it as alive *without the probe* made a record for a long-dead PID a permanent
silent outage (attach exited 0 forever, with nothing listening).
TF)r   ÚHOST_PROTOCOL_VERSIONrÑ   ©rÄ   s   &r*   Úrecord_is_stalerÖ   Y  s.   € ð ‚~ÙØ×ÑÔ"7Ô7ÙÜ& vÓ.°%Ð7Ð7r-   c               ó    € V ^8„  d   QhRRRR/# ©r&   rÄ   r   r'   rJ   r(   )r)   s   "r*   r+   r+   j  s   € ÷ 	7ñ 	7˜zð 	7¨dñ 	7r-   c                óX   € V P                   f   V P                  f   R# \        V 4      RJ # )aZ  True only when the record's process-incarnation probe positively matched (never on ``None``).

A record with neither ``createTime`` nor ``startTime`` cannot prove incarnation:
``_same_incarnation`` treats a missing create_time as a match, so a recycled pid would
otherwise look alive forever. Topology already refused that shape; attach did not.
FT)r   r#   rÑ   rÕ   s   &r*   Úliveness_is_provenrÚ   j  s.   € ð ×ÑÒ! f×&7Ñ&7Ò&?ÙÜ& vÓ.°$Ð6Ð6r-   c               ó    € V ^8„  d   QhRRRR/# ©r&   rÄ   r   r'   r   r(   )r)   s   "r*   r+   r+   v  s   € ÷ Gñ G�jð G Sñ Gr-   c                óD   € V P                   ;'       g    RpVR9   d   R# T# )zIAddress to dial for ``record``: a wildcard bind is reached over loopback.ú	127.0.0.1)z0.0.0.0z::Ú*r!   )r   )rÄ   r   s   & r*   Ú	dial_hostrà   v  s&   € à�;‰;×%Ð%˜+€DØÐ"<Ô<ˆ;ÐFÀ$ÐFr-   Útimeoutc               ó$   € V ^8„  d   QhRRRRRR/# )r&   rÄ   r   rá   rI   r'   zOptional[dict]r(   )r)   s   "r*   r+   r+   |  s!   € ÷ (ñ (˜
ð (°ð (ÈNñ (r-   c          	     ód  € V P                   '       g   R# ^ RIp\        V 4      p VP                  W0P                   3VR7      ;_uu_ 4         RRR4       ^ RIp\        T P                  4      pT'       d
   RTRRT 2/M/ pTP                  P                  RT RT P                    \         2TR	7      p TP                  P                  YqR7      ;_uu_ 4       pTP                  ^È8w  d    RRR4       R# \        P                   ! TP#                  R
4      P%                  RR4      4      p	RRR4       \)        X	\*        4      '       g   R# T	P-                  R4      T P                  8w  g!   T	P-                  R4      T P.                  8w  d   R# T	#   + '       g   i     ELK; i  \         d0    \
        P                  RT P                  Y0P                   4        R# i ; i  + '       g   i     L½; i  \&         d2    \
        P                  RT P                  Y0P                   RR7        R# i ; i)uù  Make the recorded endpoint prove it is this record's owner; ``None`` when it does not.

Two gates, both required: a bounded TCP connect (nothing listening â†’ the owner is gone, even
though its record and PID may still look alive during a graceful-shutdown window) and an
identity GET authenticated with the 0600 token (a foreign listener that inherited the port
answers the connect but cannot answer as PID N of role R).

Returns the owner's identity payload (``pid``/``role``/``servesSpa``) on success.
N)rá   z&host %s owner does not answer at %s:%szX-Hermes-TokenÚAuthorizationzBearer zhttp://Ú:)Úheadersi   r”   r•   z&host %s identity probe failed at %s:%sTr¨   r   r   )r   Úsocketrà   Úcreate_connectionrk   ry   r­   r   Úurllib.requestÚ
read_tokenÚrequestÚRequestÚHOST_IDENTITY_PATHÚurlopenÚstatusÚjsonÚloadsÚreadÚdecoder¬   r?   rF   rG   r   )
rÄ   rá   rç   r   Úurllibr’   ræ   rë   Úresponser:   s
   &$        r*   Úprobe_ownerrö   |  sÅ  € ð �;�;ˆ;ÙÛä�VÓ€DðØ×%Ñ% t¯[©[Ð&9À7Ð%×KÕKØ÷ Ló ä�v—{‘{Ó#€EßOTÐ ¨¸7À5À'Ð9JÑKÐZ\€GØ�n‰n×$Ñ$Ø
�$��q˜Ÿ™˜Ô&8Ð%9Ð:ÀGð %ó M€GðØ�^‰^×#Ñ# GÐ#×=Ô=ÀØ�‰ #Ô%Ø÷ >Ñ=ô —j’j §¡¨uÓ!5×!<Ñ!<¸WÀiÓ!PÓQˆG÷ >ô �gœt×$Ò$ÙØ‡{�{�6Ó˜fŸk™kÔ)¨W¯[©[¸Ó-?À6Ç:Á:Ô-MÙØ€N÷1 L×KÐKûäô Ü�‰Ð=¸v¿{¹{ÈD×R]ÑR]Ô^Úðú÷ >×=ûô ô Ü�‰Ð=¸v¿{¹{ÈD×R]ÑR]ÐhlˆÔmÚðúsk   ¥&F# ÁFÁF# Â5$G3 ÃG Ã,G3 Ã65G Ä+G3 ÆF 	ÆF# Æ F# Æ#6GÇGÇ G0	Ç+G3 Ç0G3 Ç38H/È.H/Úinclude_staleFc               ó$   € V ^8„  d   QhRRRRRR/# )r&   r   r   r÷   rJ   r'   r;   r(   )r)   s   "r*   r+   r+   §  s'   € ÷ Lñ L�cð L¨Tð LÐ>Rñ Lr-   c               ó^  € \        V 4      p\        V4      '       g   R#  VP                  RR7      p \
        P                  \        P                  ! T4      4      pTf   R# T'       g   \        T4      '       g   T# R#   \        \        3 d     R# i ; i  \        \        3 d     R# i ; i)z\Published record for ``role``; ``None`` when absent, foreign, corrupt or (by default) stale.Nú	utf-8-sigr´   )r‰   r}   Ú	read_textrk   ÚUnicodeDecodeErrorr   rP   rð   rñ   r�   Ú	TypeErrorrÖ   )r   r÷   rt   ÚrawrÄ   s   &$   r*   Úread_recordrÿ   §  sŸ   € ä�tÓ€DÜ˜$×ÒÙðØ�n‰n kˆnÓ2ˆðÜ×%Ñ%¤d§j¢j°£oÓ6ˆð ‚~Ùß"¬/¸&×*AÒ*Aˆ6ÐKÀtÐKøô Ô'Ð(ô Úðûô œ	Ð"ô Úðús"    A? ³)B Á?BÂBÂB,Â+B,c               ó    € V ^8„  d   QhRRRR/# r   r(   )r)   s   "r*   r+   r+   ¹  s   € ÷ ñ �Sð ˜Sñ r-   c                ó†   €  \        V 4      P                  RR7      P                  4       #   \        \        3 d     R# i ; i)u  Owner-written session token for ``role`` (``""`` when absent/unreadable).

This is the handle an attaching client uses when the backend is auth-gated and therefore
withholds its token from an unauthenticated ``GET /``. Confidentiality is enforced at WRITE
time by :func:`_write_private_text` (POSIX ``0o600``; an owner+SYSTEM-only protected DACL on
Windows, where mode bits set no ACLs at all). Reading it is therefore evidence of, not proof
of, same-OS-user authority â€” the authority boundary the host lock is scoped to.
rú   r´   r!   )r�   rû   Ústriprk   rü   r‚   s   &r*   rê   rê   ¹  sA   € ðÜ˜$Ó×)Ñ)°;Ð)Ó?×EÑEÓGÐGøÜÔ'Ð(ô Úðús   ‚(+ «A ¿A c               ó    € V ^8„  d   QhRRRR/# rØ   r(   )r)   s   "r*   r+   r+   È  s   € ÷ 	Rñ 	R zð 	R°dñ 	Rr-   c                ó€   € V P                   '       g   R# \        \        V P                  4      4      V P                   8H  # )uJ  Does the on-disk token still hash to the record's fingerprint?

A record published without a token (the gateway) has an empty fingerprint and is consistent
by definition. A mismatch means the record and the token file come from different
incarnations (a torn restart) â€” discovery must not attach with a token the owner rejects.
T)r   rê   r   rÕ   s   &r*   Úrecord_token_is_consistentr  È  s2   € ð ×#×#Ð#ÙÜœZ¨¯©Ó4Ó5¸×9QÑ9QÑQÐQr-   c               ó$   € V ^8„  d   QhRRRRRR/# )r&   rt   r   Útextr   r'   ÚNoner(   )r)   s   "r*   r+   r+   Ô  s!   € ÷ $ñ $˜dð $¨#ð $°$ñ $r-   c           	     ó  € \         P                  R8X  d    ^ RIHp V! WP	                  R4      4       R# V P                  V P                  R,           4      p\        P                  ! \        V4      \        P                  \        P                  ,          \        P                  ,          R4      p \        P                  ! VRRR7      ;_uu_ 4       pVP                  V4       VP                  4        \         P"                  ! \$        4      ;_uu_ 4        \        P&                  ! VP)                  4       4       RRR4       RRR4       \        P.                  ! \        T4      \        T 4      4       R#   + '       g   i     LC; i  + '       g   i     LN; i  \*         d`    \         P"                  ! \$        4      ;_uu_ 4        \        P,                  ! \        T4      4       RRR4       h   + '       g   i     h ; ii ; i)	aà  Create/replace ``path`` with owner-only content.

POSIX: ``0o600`` via tmp + atomic replace. Windows: mode bits set NO ACLs, so the same
``os.open`` would leave a live session token readable by whatever the inherited DACL grants;
the SSH runtime's protected owner+SYSTEM DACL writer is the repo's primitive for exactly this
credential class and is reused here. It also replaces in place, because ``os.replace`` onto a
token file another process still holds open fails on Windows.
rf   )Úwrite_private_filer”   Nz.tmpé€  Úwr´   )rg   rh   Úhermes_cli.windows_ssh_runtimer
  r™   Ú	with_nameÚnamero   r·   r   ÚO_WRONLYÚO_CREATÚO_TRUNCrº   ÚwriteÚflushri   rj   rk   ÚfsyncÚfilenoÚBaseExceptionÚunlinkr•   )rt   r  r
  Útmpr»   Úhandles   &&    r*   Ú_write_private_textr  Ô  s5  € ô ‡|�|�wÔÝEá˜4§¡¨WÓ!5Ô6ÙØ
�.‰.˜Ÿ™ VÕ+Ó
,€CÜ	�Š”�S“œ2Ÿ;™;¬¯©Õ3´b·j±jÕ@À%Ó	H€Bð	Ü�YŠY�r˜3¨×1Õ1°VØ�L‰L˜ÔØ�L‰LŒNÜ×$Ò$¤W×-Õ-Ü—’˜Ÿ™›Ô)÷ .÷ 2ô ‡J‚JŒs�3‹xœ˜T›Ö#÷ .×-ú÷ 2×1ûô
 ô Ü× Ò ¤×)Õ)Ü�IŠI”c˜#“hÔ÷ *à÷ *Ö)àúðús[   Â1!F ÃAF	Ä%E6	Ä;F	ÅF Å6FÆF	Æ	F	ÆF ÆF Æ-HÇ	 G2Ç)	HÇ2HÇ=	Hc                  ó&   € ] tR tRtRtRtRtRtRtR# )ÚHostLockOutcomeiñ  a  Why a host-lock claim ended the way it did.

``COULD_NOT_OPEN`` is NOT contention: a read-only/undeletable lock dir (EROFS, EACCES,
ENOSPC) collapsed into the same "another owner holds it" log and sent operators hunting a
second gateway that never existed.
Úacquiredzheld-by-otherzcould-not-openr(   N)	rR   rS   rT   rU   rV   ÚACQUIREDÚHELD_BY_OTHERÚCOULD_NOT_OPENrY   r(   r-   r*   r  r  ñ  s   † ñð €HØ#€MØ%„Nr-   r  c               ó    € V ^8„  d   QhRRRR/# )r&   r   r   r'   ztuple[str, str]r(   )r)   s   "r*   r+   r+   þ  s   € ÷ ñ �Cð ˜Oñ r-   c                ó  € \        V 4      p\        P                  ! \        4      ;_uu_ 4        V \        P
                  P                  \        V4      4      3uuR R R 4       #   + '       g   i     M; iT \        T4      3# r>   )r�   ri   rj   rk   ro   rt   Úabspathr   )r   rt   s   & r*   Ú	_lock_keyr%  þ  sS   € Ü�T‹?€DÜ	×	Ò	œW×	%Õ	%Ø”b—g‘g—o‘o¤c¨$£iÓ0Ð1÷ 
&×	%×	%Ò	%úà”#�d“)ÐÐs   ®*A#Á#A3	c               ó    € V ^8„  d   QhRRRR/# )r&   r   r   r'   z)tuple[HostLockOutcome, Optional[OSError]]r(   )r)   s   "r*   r+   r+     s   € ÷ ,ñ ,˜#ð ,Ð"Kñ ,r-   c                óx  € \        V 4      p \        V 4      p\        P                  V4      e   \        P
                  R3# ^ RIHp \        V^,          4      p \        4        \        VRRR7      pT! T4      '       gN   \        P                   ! \        4      ;_uu_ 4        TP#                  4        RRR4       \        P$                  R3# T\        T&   \        P
                  R3#   \         d5   p\        P                  RYRR7       \        P                  T3u Rp?# Rp?ii ; i  + '       g   i     L; i)	z˜Take the host-wide lock for ``role``. Idempotent per (role, lock path).

Returns the outcome and, for ``COULD_NOT_OPEN``, the OSError that explains it.
N©Ú_try_acquire_file_lockúa+r”   r´   z&host %s lock could not be opened at %sTr¨   )rƒ   r%  r   rG   r  r  r_   r)  r   rr   r·   rk   ry   r­   r!  ri   rj   Úcloser   )r   Úkeyr)  rt   r  Úexcs   &     r*   Úclaim_host_lockr.    sú   € ô
 ˜4Ó €DÜ
�D‹/€CÜ×Ñ˜ÓÒ)Ü×(Ñ(¨$Ð/Ð/Ý5ä��A•‹<€Dð5ÜÔÜ�d˜D¨7Ô3ˆñ " &×)Ò)Ü× Ò ¤×)Õ)Ø�L‰LŒN÷ *ä×-Ñ-¨tÐ4Ð4Ø„M�#ÑÜ×$Ñ$ dÐ+Ð+øô ô 5Ü�‰Ð=¸tÐTXˆÔYÜ×.Ñ.°Ð4Õ4ûð5ú÷ *×)ús*   ÁC' Â!D)Ã'D&Ã2)D!ÄD&Ä!D&Ä)D9	c               ó    € V ^8„  d   QhRRRR/# ©r&   r   r   r'   r  r(   )r)   s   "r*   r+   r+     s   € ÷ 	ñ 	˜Cð 	 Dñ 	r-   c                ó  € \         P                  \        \        V 4      4      R4      pVf   R# ^ RIHp V! V4       \        P                  ! \        4      ;_uu_ 4        VP                  4        RRR4       R#   + '       g   i     R# ; i)z>Release the host lock for ``role`` when this process holds it.N©Ú_release_file_lock)
r   Úpopr%  rƒ   r_   r3  ri   rj   rk   r+  )r   r  r3  s   &  r*   Úrelease_host_lockr5    sZ   € ä×Ñœy¬¸Ó)>Ó?ÀÓF€FØ‚~ÙÝ1á�vÔÜ	×	Ò	œW×	%Õ	%Ø�‰Œ÷ 
&×	%×	%Ò	%ús   ÁA:Á:B	c               ó    € V ^8„  d   QhRRRR/# ©r&   r   r   r'   rJ   r(   )r)   s   "r*   r+   r+   +  s   € ÷ Kñ K˜ð K ñ Kr-   c                óT   € \         P                  \        \        V 4      4      4      RJ# )zXTrue when THIS process holds the host lock for ``role`` (re-probing our own flock lies).N)r   rG   r%  rƒ   r‚   s   &r*   r¶   r¶   +  s"   € ä×ÑœY¤°tÓ'<Ó=Ó>ÀdÐJÐJr-   c               ó    € V ^8„  d   QhRRRR/# r°   r(   )r)   s   "r*   r+   r+   0  s   € ÷ $ñ $˜Cð $ Nñ $r-   c                óâ   € \        V 4      p \        V 4      '       d   R# \        V 4      pVP                  4       '       g   R#  \	        VRRR7      p^ RIHp T! T4      #   \
         d     R# i ; i)a*  Whether any process currently holds the host lock for ``role``.

``True`` / ``False`` when the flock can be observed; ``None`` when the lock file cannot
be opened. Never retains a lock. This process's handle is authoritative: re-probing our
own flock succeeds on POSIX, so a second open would lie.
TFr*  r”   r´   Nr²   )rƒ   r¶   r�   Úexistsr·   rk   r_   r³   )r   rt   r  r³   s   &   r*   Úhost_lock_is_heldr<  0  sj   € ô ˜4Ó €DÜ�d×ÒÙÜ�T‹?€DØ�;‰;�=Š=ÙðÜ�d˜D¨7Ô3ˆõ 0á˜FÓ#Ð#øô	 ô Úðús   ÁA ÁA.Á-A.c               ó    € V ^8„  d   QhRRRR/# rØ   r(   )r)   s   "r*   r+   r+   F  s   € ÷ ñ  Jð °4ñ r-   c                ó¸   € \        V 4      '       g   R# \        V P                  4      RJ d.   \        P	                  RV P                  V P
                  4       R# R# )un  True when ``record``'s incarnation is proven AND its host lock is not observed free.

The publisher holds the host lock for its whole life and the OS drops it on death, so a free
flock falsifies a record whose pid merely looks alive (recycle, start-tick collision after a
reboot â€” #138343, #133744). Every reader that trusts a record as a running owner uses this.
Fz5host %s record names pid %s but the host lock is freeT)rÚ   r<  r   ry   r­   r   rÕ   s   &r*   Úrecord_names_live_ownerr?  F  sE   € ô ˜f×%Ò%ÙÜ˜Ÿ™Ó%¨Ó.Ü�‰ÐLÈfÏkÉkÐ[a×[eÑ[eÔfÙÙr-   r   r!   r   r   r’   r"   c               ó4   € V ^8„  d   QhRRRRRRRRRR	R
RRR/# )r&   r   r   r   r   r   r   zSequence[str]r’   zOptional[str]r"   r'   r;   r(   )r)   s   "r*   r+   r+   U  sP   € ÷ +ñ +Ø
ð+ð ð+ð ð	+ð
 ð+ð ð+ð ð+ð ñ+r-   c               ó:  € \        V 4      p ^ RIHp \        T \        P
                  ! 4       \        4       V! \        P
                  ! 4       4      \        4       \        T;'       g    R4      \        V\        4      '       d   V^ 8”  d   \        V4      MR\        \        T;'       g    R4      \        ;QJ d    . R V 4       F  NK  	  5M! R V 4       4      \        P                  ! \         4      P#                  4       \        T;'       g    R4      R7      p \%        4        V'       d   \'        \)        V 4      V4       \+        \-        V 4      VP/                  4       RR7       \7        4        T#   \0         d    \2        P5                  RT R	R
7        R# i ; i)u"  Publish this process as the host owner of ``role``. ``None`` when the write failed.

``token`` (serve) is persisted 0600 next to the record and only its fingerprint is published.
``home`` is the launch HERMES_HOME â€” the key an attaching client needs to reach this owner's
control socket.
)rÆ   r!   Nc              3  ó8   "  € T F  p\        V4      x € K  	  R # 5ir>   )r   r@   s   & r*   rC   Ú!publish_record.<locals>.<genexpr>q  s   é € Ð0¡x !”s˜1—v�v£xùs   ‚rE   r  )re   zAhost %s record could not be published; discovery will not find itTr¨   )rƒ   r_   rÆ   r   ro   ÚgetpidrŸ   rÂ   r   r?   r   rÔ   r   rK   r   Únowr   Ú	isoformatrr   r  r�   r   r‰   r7   rk   ry   rz   Ú_invalidate_attach_cache)r   r   r   r   r’   r"   rÆ   rÄ   s   &$$$$$  r*   Úpublish_recordrH  U  s%  € ô ˜4Ó €DÝ5äØÜ�IŠI‹KÜ'Ó)Ù)¬"¯)ª)«+Ó6Ü&Ó(Ü�—�˜‹_Ü$ T¬3×/Ò/°D¸1´HŒS�ŒYÀ$Ü.Ü+¨E¯K¨K°RÓ8ß”Ñ0¡xÓ0—‘Ñ0¡xÓ0Ó0Ü—<’<¤Ó$×.Ñ.Ó0Ü�—�˜‹_ô€Fð	ÜÔßô  ¤
¨4Ó 0°%Ô8Üœ+ dÓ+¨V¯^©^Ó-=ÀEÕJô ÔØ€Møô	 ô Ü�‰ÐZÐ\`ÐkoˆÔpÚðús   ÄAE3 Å3#FÆFc               ó   € V ^8„  d   QhRR/# ©r&   r'   r  r(   )r)   s   "r*   r+   r+   ƒ  s   € ÷ (ñ ( $ñ (r-   c                 óž   € \         P                  ! \        4      ;_uu_ 4        ^ RIHp  V ! 4        RRR4       R#   + '       g   i     R# ; i)zNDrop :mod:`gateway.host_attach`'s memo: the record it summarises just changed.©Úinvalidate_host_gateway_cacheN)ri   rj   r¬   Úgateway.host_attachrM  rL  s    r*   rG  rG  ƒ  s+   € ä	×	Ò	œY×	'Õ	'ÝEá%Ô'÷ 
(×	'×	'Ò	'ús	   £;»A	c               ó    € V ^8„  d   QhRRRR/# r7  r(   )r)   s   "r*   r+   r+   ‹  s   € ÷ "ñ "˜cð " dñ "r-   c           	     ód  € \        V 4      p \        V RR7      pVf   R# RpVP                  \        P                  ! 4       8w  d2   \        V4      RJd#   \        V 4      '       g   \        V 4      ;pf   R#  Veb   \        V RR7      fS    VeL   ^ RIH	p V! V4       \        P                  ! \        4      ;_uu_ 4        VP                  4        RRR4       R# R# \        V 4      \        V 4      3 F@  p\        P                  ! \        4      ;_uu_ 4        VP!                  RR7       RRR4       KB  	  VeK   ^ RIH	p V! V4       \        P                  ! \        4      ;_uu_ 4        VP                  4        RRR4       M \#        4        R#   + '       g   i     R# ; i  + '       g   i     KÄ  ; i  + '       g   i     LD; i  Te^   ^ RIH	p T! T4       \        P                  ! \        4      ;_uu_ 4        TP                  4        RRR4       i   + '       g   i     i ; ii ; i)aH  Retract the record for ``role`` when its owner is provably gone; True when one was removed.

A confirmed stop must retract the record too. Leaving it made ``gateway restart --all`` a
silent no-op: the re-entered ``gateway run`` read the corpse's record, decided ATTACH and
exited 0, so the host ended up with no gateway at all.
T©r÷   NFr2  ©Ú
missing_ok)rƒ   rÿ   r   ro   rD  rÑ   r¶   Ú_hold_lock_brieflyr_   r3  ri   rj   rk   r+  r‰   r�   r  rG  )r   rÄ   Úguardr3  rt   s   &    r*   Údiscard_dead_recordrV  ‹  s¥  € ô ˜4Ó €DÜ˜¨TÔ2€FØ‚~ÙØ€EØ‡z�z”R—Y’Y“[Ô Ô%@ÀÓ%HÐPUÓ%Uô ˜$×ÒÔ-?ÀÓ-EÐ$E EÒ#NÙðØÒ¤¨TÀÔ!FÒ!NØð
 ÒÝ9á˜uÔ%Ü×$Ò$¤W×-Õ-Ø—‘”÷ .Ñ-ñ	 ô	 ! Ó&¬
°4Ó(8Ó9ˆDÜ×$Ò$¤W×-Õ-Ø—‘ t�Ô,÷ .Ñ-ñ :ð ÒÝ9á˜uÔ%Ü×$Ò$¤W×-Õ-Ø—‘”÷ .Ð-ð	 ô ÔÙ÷ .×-Ð-ú÷ .×-Ð-ú÷ .×-ûð	 ÒÝ9á˜uÔ%Ü×$Ò$¤W×-Õ-Ø—‘”÷ .×-×-Ð-úð	 úsZ   Á7G Â?FÃ;G ÄF%Ä*G Å*F9ÆF"	Æ%F6Æ0	G Æ9G		Ç5H/ÈH	È	H/ÈH+È&	H/c               ó   € V ^8„  d   QhRR/# )r&   r   r   r(   )r)   s   "r*   r+   r+   °  s   € ÷ ñ ˜Sñ r-   c                ó(  € ^ RI Hp \        V 4      p \        VRRR7      pT! T4      '       d   T# \
        P                  ! \        4      ;_uu_ 4        TP                  4        RRR4       R#   \         d     R# i ; i  + '       g   i     R# ; i)zROpen and flock ``role``'s host lock without registering it; ``None`` when not won.r(  r*  r”   r´   N)r_   r)  r�   r·   rk   ri   rj   r+  )r   r)  rt   r  s   &   r*   rT  rT  °  su   € å5ä�T‹?€DðÜ�d˜D¨7Ô3ˆñ ˜f×%Ò%ØˆÜ	×	Ò	œW×	%Õ	%Ø�‰Œ÷ 
&áøô ô Úðú÷ 
&Ö	%áús   “A. ÁB Á.A=Á<A=Â B	c               ó    € V ^8„  d   QhRRRR/# r0  r(   )r)   s   "r*   r+   r+   À  s   € ÷ 	ñ 	�sð 	˜tñ 	r-   c                óz  € \        V 4      p \        V RR7      pVe'   VP                  \        P                  ! 4       8w  d   R# \        V 4      \        V 4      3 F@  p\        P                  ! \        4      ;_uu_ 4        VP                  RR7       RRR4       KB  	  \        4        R#   + '       g   i     Ka  ; i)zERemove this process's record + token on exit (never another owner's).TrQ  NrR  )rƒ   rÿ   r   ro   rD  r‰   r�   ri   rj   rk   r  rG  )r   Úexistingrt   s   &  r*   Úclear_recordr\  À  s†   € ä˜4Ó €DÜ˜4¨tÔ4€HØÒ §¡´·	²	³Ô ;ÙÜ˜TÓ"¤J¨tÓ$4Ó5ˆÜ× Ò ¤×)Õ)Ø�K‰K 4ˆKÔ(÷ *Ñ)ñ 6ô Ö÷ *×)Ð)ús   Á>B)Â)B:z	list[str]Ú_cleanup_roleszdict[int, Any]Ú_prev_signal_handlersc               ó    € V ^8„  d   QhRRRR/# r0  r(   )r)   s   "r*   r+   r+   Ñ  s   € ÷  ñ  ˜ð   ñ  r-   c                ó,  € \         P                  ! \        4      ;_uu_ 4        \        V 4       R R R 4       \         P                  ! \        4      ;_uu_ 4        \	        V 4       R R R 4       R #   + '       g   i     LH; i  + '       g   i     R # ; ir>   )ri   rj   r¬   r\  r5  r‚   s   &r*   Ú_cleanup_rolera  Ñ  sT   € Ü	×	Ò	œY×	'Õ	'Ü�TÔ÷ 
(ä	×	Ò	œY×	'Õ	'Ü˜$Ô÷ 
(Ñ	'÷ 
(×	'úç	'×	'Ð	'ús   £A/ÁBÁ/A?	ÂB	c               ó   € V ^8„  d   QhRR/# rJ  r(   )r)   s   "r*   r+   r+   Ø  s   € ÷ (ñ (°ñ (r-   c                ó
  € ^ RI p\        \        4       F  p\        V4       K  	  \        P                  V 4      p\        V4      '       d   V! W4       R# WBP                  J d   R# \        P                  ! \        4      ;_uu_ 4        VP                  WP                  4       \        P                  ! \        P                  ! 4       V 4       RRR4       \        ^€\!        V 4      ,           4      h  + '       g   i     L+; i)zFDrop the record + token, then hand off to the handler we prepended to.N)ÚsignalrK   r]  ra  r^  rG   ÚcallableÚSIG_IGNri   rj   r¬   ÚSIG_DFLro   ÚkillrD  Ú
SystemExitr   )ÚsignumÚframeÚ_signalr   Úprevs   &&   r*   Ú_handle_terminating_signalrn  Ø  s¦   € ãä”nÖ%ˆÜ�dÖñ &ä ×$Ñ$ VÓ,€DÜ�‡~‚~ÙˆVÔÙØ�‰ÓÙÜ	×	Ò	œY×	'Õ	'Ø�‰�vŸ™Ô/Ü
�Š”—	’	“˜VÔ$÷ 
(ô �Sœ3˜v›;Õ&Ó
'Ð'÷ 
(×	'ús   Â	AC2Ã2D	c               ó    € V ^8„  d   QhRRRR/# r0  r(   )r)   s   "r*   r+   r+   ê  s   € ÷ 1ñ 1˜#ð 1 $ñ 1r-   c                ó&  € ^ RI p^ RIp^ RIp\        V 4      p V \        9  d,   \        P                  V 4       VP                  \        V 4       VP                  4       VP                  4       Jd   R# R F…  p\        W$R4      pVe   V\        9   d   K   \        P                  ! \        \        \         4      ;_uu_ 4        VP#                  V4      pVP                  V\$        4       V\        V&   RRR4       K‡  	  R#   + '       g   i     Kœ  ; i)uÆ  Clear ``role``'s record + token and release its lock on exit â€” SIGTERM included.

``atexit`` alone is false advertising for the NORMAL stop: systemd stop, ``docker stop`` and
the update relaunch all send SIGTERM, and every terminating SIGTERM path here ends in the
default disposition (uvicorn's ``capture_signals`` re-raises it after its graceful shutdown),
which kills the process without running ``atexit``. The record then outlived its process and
the 0600 token kept a LIVE session token on disk indefinitely.

The handler only PREPENDS cleanup: whatever handler was installed before (uvicorn's graceful
shutdown, the exit-flush chain, a supervisor's) still runs, so the shutdown sequence is
unchanged.
N)ÚSIGTERMÚSIGBREAK)Úatexitrd  Ú	threadingrƒ   r]  ÚappendÚregisterra  Úcurrent_threadÚmain_threadÚgetattrr^  ri   rj   r�   rk   ÚRuntimeErrorÚ	getsignalrn  )r   rs  rl  rt  r  rj  rm  s   &      r*   Úcleanup_on_exitr|  ê  sÓ   € ó ÛÛä˜4Ó €DØ”>Ô!Ü×Ñ˜dÔ#Ø�‰œ tÔ,Ø×ÑÓ!¨×)>Ñ)>Ó)@Ó@ÙÛ'ˆÜ˜¨Ó-ˆØŠ>˜VÔ'<Ô<ÙÜ× Ò ¤¬W´l×CÕCØ×$Ñ$ VÓ,ˆDØ�N‰N˜6Ô#=Ô>Ø,0Ô! &Ñ)÷ DÑCó	 (÷ D×CÐCús   Ã 1C?Ã?Dc               ó   € V ^8„  d   QhRR/# )r&   r'   rJ   r(   )r)   s   "r*   r+   r+     s   € ÷ 	ñ 	 Tñ 	r-   c                 ó   € R# )u  Will THIS process multiplex? An explicit ``true`` and an unset key both say yes, and an
explicit ``false`` is RETIRED (``hermes_cli.gateway_multiplex_mode``) â€” it is warned about and
ignored at boot, so it must not make the claim-time record advertise a narrower roster than
the process actually serves. Reading it here was the last place the retired flag still decided
topology, and it made CLI/dashboard report "standalone, serving default" while the runtime
multiplexed. The RUNTIME verdict (a boot-time guard refusal) narrows the record afterwards, in
``gateway.run._refresh_host_gateway_record``, which republishes the SETTLED set.
Tr(   r(   r-   r*   Ú_multiplex_profiles_enabledr    s   € ñ r-   Ú	multiplexc               ó    € V ^8„  d   QhRRRR/# )r&   r€  r¡   r'   r   r(   )r)   s   "r*   r+   r+     s   € ÷ ñ  .ð ¸Oñ r-   c                ó
  €  ^ RI Hp V f   \        4       M
\        V 4      p\        ;QJ d    . R V! VR7       4       F  NK  	  5# ! R V! VR7       4       4      #   \
         d    \        P                  RRR7       Ru # i ; i)aU  Profiles this process multiplexes; ``()`` when the roster cannot be read.

``multiplex`` defaults to what this process's own config says. Hard-coding ``True`` here
published a record claiming EVERY profile from a gateway that would only ever serve its own,
and a second profile's supervised unit then stood down against a set nobody serves.
)Úprofiles_to_servec              3  ó*   "  € T F	  w  rVx € K  	  R # 5ir>   r(   )rA   r  Ú_s   &  r*   rC   Ú"served_profiles.<locals>.<genexpr>"  s   é € ÐNÑ)M™g˜d•TÓ)Mùs   ‚)r€  z!served profile roster unavailableTr¨   r(   )Úhermes_cli.profilesrƒ  r  rJ   rK   r¬   ry   r­   )r€  rƒ  Úenableds   $  r*   Úserved_profilesr‰    sq   € ðÝ9à3<Ò3DÔ-Ô/Ì$ÈyË/ˆßŒuÑNÑ):ÀWÕ)MÓN�uÐNˆuÑNÑ):ÀWÕ)MÓNÓNÐNøÜô Ü�‰Ð8À4ˆÔHØŠ	ðús   ‚)A ¬A ÁA Á$BÂBc               ó    € V ^8„  d   QhRRRR/# rÜ   r(   )r)   s   "r*   r+   r+   (  s   € ÷ ?ñ ?�Zð ? Cñ ?r-   c                óþ   € V P                   '       d&   V P                  ;'       g    R RV P                    2MRpV P                  '       d   RP                  V P                  4      MRpRV P                   RV RV R	2# )
zEOne-line human description used by attach messages and conflict logs.rÞ   rå   zno bound portz, ÚunknownzPID z (z; profiles: Ú))r   r   r   Újoinr   )rÄ   Úwherer   s   &  r*   Údescriber�  (  sh   € à=C¿[¿[¸[ˆv�{‰{×)Ð)˜kÐ*¨!¨F¯K©K¨=Ñ9Èo€EØ-3¯_¯_¨_ˆt�y‰y˜Ÿ™Ô)À)€HØ�&—*‘*�˜R ˜w l°8°*¸AÐ>Ð>r-   r>   r(   )TÚ__conditional_annotations__rV   Ú
__future__r   ri   Úenumr—   rð   Úloggingro   rl   rg   Údataclassesr   r   r   r   Úpathlibr   Útypingr   r	   r
   Úhermes_dir_aclr   Úutilsr   Ú	getLoggerrR   ry   rÔ   rí   ÚPROBE_TIMEOUT_SrÈ   Ú
ROLE_SERVEÚROLE_DESKTOP_SERVErH   r   rW   r   r`   rr   r}   rƒ   r‰   r�   r�   r   rŸ   r¤   rÉ   r®   r¼   rÂ   rÑ   rÖ   rÚ   rà   rö   rÿ   rê   r  r  ÚEnumr  r%  r.  r5  r¶   r<  r?  rH  rG  rV  rT  r\  r]  r^  ra  rn  r|  r  r‰  r�  )r‘  s   @r*   Ú<module>rŸ     sþ  øðôõ@ #ã Û Û Û Û Û 	Û Û 
Ý !ß ,Ñ ,Ý ß *Ñ *Ý (å #à	×	Ò	˜8Ó	$€ð Ð ð
 *Ð ð €à€Ø€
ð %Ð Ø
˜
Ð$6Ð	7€ð -/€Ð)Ó .ñ �$Ô÷K
ð K
ó ðK
õ\õõ"õ0õCõCõDõ^÷
%õ0ð Ð õõGõ*õ,õ^8õ"	7õGð(¸÷ (ðVL°E÷ Lõ$õ	Rõ$ô:
&�d—i‘iô 
&õõ,õ4	õKõ
$õ,ð+ð ð+ð ð	+ð
 !ð+ð  ð+ð ÷+õ\(õ"õJõ 	ð €�	Ó Ø(*Ð �~Ó *õ õ(õ$1õB	ð°4÷ ÷"?r-   