
    j                     L   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mZ ddlmZmZ ddlmZ ddlmZ ddlmZ ddlmZmZ dd	lmZ ej6                  d
k(  rddlZnddlZdZdZdZ ej6                  d
k(  Z! e"       Z#dZ$da%dZ&h dZ' G d de(e      Z)de*de*fdZ+de*de*ddfdZ,e G d d             Z-defdZ.ddee   defdZ/defdZ0defdZ1de(fd Z2d!ede3fd"Z4d!ede*fd#Z5d!edejl                  dz  fd$Z7d!ed%e*de*fd&Z8d!ed'e9e(ef   ddfd(Z:d!eddfd)Z;d*d+de*d,e3ddfd-Z<d.e(de(fd/Z=d0e(d.e(defd1Z>de*dee*   fd2Z?dee(   fd3Z@d!e(ez  dz  dee(   fd4ZAde*dee   fd5ZBde*dee*   fd6ZCde*dee(   fd7ZDd8e(dz  de3fd9ZEde*de3fd:ZFd;e9e(ef   de3fd<ZGd;e9e(ef   de*d=ede)fd>ZH	 ddd?dee   d=ee   dee-   fd@ZIde9fdAZJde9e(ef   fdBZKd!edee9e(ef      fdCZLd!ed'e9e(ef   ddfdDZMddee   dee9   fdEZNddFee   dee9e(ef      fdGZOd;ee9e(ef      dee*   fdHZPdedIe3ddfdJZQddKZRde3fdLZSde*de3fdMZTddNZUde3fdOZVddPZWddFee   de3fdQZXddRZYe#e#e#e#e#e#e#e#e#dS	dTedUedVedWedXedYedZed[ed\eddfd]ZZd'e9e(ef   dede9e(ef   fd^Z[dedefd_Z\dee9e(ef      fd`Z]	 ddaee9e(ef      dee*   fdbZ^ddcZ_dd0e(d.e(ddee9e(ef      de`e3ee9e(ef      f   fdeZad0e(d.e(ddfdfZbdddgdhee*   diee*   de*fdjZcdkZddlZedmZfdlZgdefdnZhdefdoZidpe(dqe*de3fdrZjd!edse(dte(due(dqe*de3fdvZkdwe*de3fdxZlde3fdyZmddzZndwe*de3fd{Zode3fd|Zpde3fd}Zqdd~Zr	 ddddee   dIe3dee*   fdZs	 ddddee   dIe3de3fdZty)u  
Gateway runtime status helpers.

Provides PID-file based detection of whether the gateway daemon is running,
used by send_message's check_fn to gate availability in the CLI.

The PID file lives at ``{HERMES_HOME}/gateway.pid``.  HERMES_HOME defaults to
``~/.hermes`` but can be overridden via the environment variable.  This means
separate HERMES_HOME directories naturally get separate PID files — a property
that will be useful when we add named profiles (multiple agents running
concurrently under distinct configurations).
    N)	dataclass)datetimetimezone)Enum)Pathget_hermes_home)AnyOptional)atomic_json_writewin32hermes-gatewaygateway_state.jsonzgateway-locksgateway.lock   >   gateway.pidr   r   .gateway-takeover.json.gateway-planned-stop.jsonc                       e Zd ZdZdZdZdZy)ProcessIdentityz?Result of validating persisted metadata against a live process.matchmismatchunknownN)__name__
__module____qualname____doc__MATCHMISMATCHUNKNOWN     /home/cube/projects/richard/traning coach/.omo/evidence/nutricoach-v150-combined/st_01a0560c-r63-installed-wheel-first-claim-qa/venv/lib/python3.12/site-packages/gateway/status.pyr   r   ;   s    IEHGr"   r   pidreturnc                 $   t        t        dd       }|	 || d      S t        j                  j	                  d      st        d      dd l}|j                  d d      }|j                  d| d      }|dk  rt        |j                         d      |S )	N
pidfd_openr   linuxzpidfds are unavailableT	use_errnoi  zpidfd_open failed)
getattrossysplatform
startswithOSErrorctypesCDLLsyscall	get_errno)r$   native_openr1   libcfds        r#   _pidfd_openr8   C   s    "lD1K3""<<""7+.//;;tt;,D	c3	"B	Avf&&(*=>>Ir"   r7   sigc                 (   t        t        dd       }|
 || |       y t        j                  j	                  d      st        d      dd l}|j                  d d      }|j                  d| |d d      }|dk  rt        |j                         d      y )	Npidfd_send_signalr(   zpidfd signalling is unavailabler   Tr)   i  zpidfd_send_signal failed)
r+   signalr-   r.   r/   r0   r1   r2   r3   r4   )r7   r9   native_sendr1   r6   results         r#   _pidfd_send_signalr?   R   s    &"5t<KB<<""7+788;;tt;,D\\#r3a0Fzf&&(*DEE r"   c                       e Zd ZU dZeed<   dZedz  ed<   dZej                  dz  ed<   dZ
edz  ed<   dZedz  ed<   dZedz  ed<   d	efd
Zded	dfdZded	efdZddZy)GatewayProcessHandlez>A stable, verified process authority for one gateway instance.r$   Nr7   processprocess_create_timerecordexpected_homer%   c                 0   | j                   | j                  | j                  duxs | j                  duS | j                  ;t	        | j                   | j
                  | j                        t        j                  u S | j                  | j                  y	 | j                  j                         sy| j                  j                         | j                  k7  ry| j                   j                  d      t        k7  ry| j                   j                  d      t               k7  ryt        | j                   j                  d            }t        | j                        }|||k7  ryt        | j                  dd      }t!        |      sy |       }t        |j                  d            |k7  ryt#        dj%                  | j                  j'                                     S # t(        j*                  t(        j,                  t(        j.                  f$ r Y yw xY w)	zERevalidate the same process instance before every destructive action.NFkindboot_idhomeenvironHERMES_HOME )rD   rE   r7   rB   _record_matches_live_gatewayr$   r   r   rC   
is_runningcreate_timeget_GATEWAY_KIND_get_boot_identity_canonical_homer+   callablelooks_like_gateway_command_linejoincmdlinepsutilAccessDeniedNoSuchProcessZombieProcess)selfrecord_homerE   rJ   environments        r#   is_validzGatewayProcessHandle.is_validl   s   ;;$"4"4"<77$&B$,,d*BB77,KK4+=+= #(() <<4#;#;#C	<<**,||'')T-E-EE{{v&-7{{y)-?-AA)$++//&*ABK+D,>,>?M"k]&BdllIt<GG$!)K{}=>-O2388DLL<P<P<R3STT##V%9%96;O;OP 		s=   G  3'G  "G  >&G  %A G  &"G  	$G  .1G   2HHr9   c                    | j                         st        d      | j                  t        | j                  |       y | j                  t        d      |t
        j                  k(  r| j                  j                          y |t        t
        dd       k(  r| j                  j                          y | j                  j                  |       y )Nz gateway process identity changedz%gateway process handle is unavailableSIGKILL)r_   r0   r7   r?   rB   r<   SIGTERM	terminater+   killsend_signal)r\   r9   s     r#   re   z GatewayProcessHandle.send_signal   s    }}<==77tww,<<ABB&.. LL""$GFIt44LLLL$$S)r"   timeoutc                 N   | j                   2t        j                  | j                   gg g |      \  }}}t        |      S | j                  y	 | j                  j	                  |       y# t
        j                  $ r Y yt
        j                  t
        j                  f$ r Y yw xY w)NF)rf   T)	r7   selectboolrB   waitrX   TimeoutExpiredrZ   r[   )r\   rf   ready_s       r#   wait_for_exitz"GatewayProcessHandle.wait_for_exit   s    77 --	2r7CKE1a;<<	LLg.$$ 	$$f&:&:; 		s   A* *B$?"B$#B$c                 |    | j                   y 	 t        j                  | j                          y # t        $ r Y y w xY wN)r7   r,   closer0   )r\   s    r#   rq   zGatewayProcessHandle.close   s5    77?	HHTWW 		s   / 	;;r%   N)r   r   r   r   int__annotations__r7   rB   rX   ProcessrC   floatrD   dictrE   r   ri   r_   re   rn   rq   r!   r"   r#   rA   rA   a   s    H	HBd
%)GV^^d")(,,FD4K!%M4$;%"$ "H*s *t *U t r"   rA   c                       t               } | dz  S )z@Return the path to the gateway PID file, respecting HERMES_HOME.r   r   rI   s    r#   _get_pid_pathrz      s    D-r"   pid_pathc                 V    | | j                  t              S t               }|t        z  S )z1Return the path to the runtime gateway lock file.)	with_name_GATEWAY_LOCK_FILENAMEr	   )r{   rI   s     r#   _get_gateway_lock_pathr      s.    !!"899D(((r"   c                  <    t               j                  t              S )z5Return the persisted runtime health/status file path.)rz   r}   _RUNTIME_STATUS_FILEr!   r"   r#   _get_runtime_status_pathr      s    ?$$%9::r"   c                      t        j                  d      } | rt        |       S t        t        j                  dt        j                         dz  dz              }|dz  t        z  S )zBReturn the machine-local directory for token-scoped gateway locks.HERMES_GATEWAY_LOCK_DIRXDG_STATE_HOMEz.localstatehermes)r,   getenvr   rI   _LOCKS_DIRNAME)override
state_homes     r#   _get_lock_dirr      sT    yy23HH~bii 0$))+2H72RSTJ >11r"   c                  d    t        j                  t        j                        j	                         S rp   )r   nowr   utc	isoformatr!   r"   r#   _utc_now_isor      s    <<%//11r"   pathc                 &    | j                   t        v S rp   )name_AUTHORITY_FILENAMESr   s    r#   _is_authority_pathr      s    99,,,r"   c           	      R   t        j                  | j                        }t        j                  |j
                        st        j                  |j
                        st        d      t        j                  | j                  t         j                  t        t         dd      z        S )zDOpen the configured authority directory without accepting a symlink.z"unsafe gateway authority directoryO_DIRECTORYr   )r,   lstatparentstatS_ISLNKst_modeS_ISDIRr0   openO_RDONLYr+   )r   infos     r#   _authority_parent_fdr      sf    88DKK D||DLL!dll)C:;;774;;gb-.K KLLr"   c                 V    t        t        dd              }	 t        j                  |       }t        j                  |j                        rB||j                  |k7  s1|j                  dk7  s"t        j                  |j                        dk7  ry|S # t        $ r Y yt        $ r Y yw xY w)zCReturn a safe authority-file stat without following its final link.getuidc                       y rp   r!   r!   r"   r#   <lambda>z!_authority_stat.<locals>.<lambda>       r"   N     )r+   r,   r   FileNotFoundErrorr0   r   S_ISREGr   st_uidst_nlinkS_IMODE)r   current_uidr   s      r#   _authority_statr      s    5'"h57Kxx~ LL&#{(B==A<<%.K   s   B 	B(B('B(flagsc                    t        |       }	 |t        t        dd      z  }|t        j                  z  r$t        j                  | j
                  |d|      }n"t        j                  | j
                  ||      }t        j                  |       t        j                  |      } t        t        dd              }t        j                  |j                        rB||j                  |k7  s1|j                  dk7  s"t        j                  |j                        dk7  r t        j                  |       t        d      |S # t        j                  |       w xY w)	z@Open one private regular authority file without following links.
O_NOFOLLOWr   r   dir_fdr   c                       y rp   r!   r!   r"   r#   r   z!_authority_open.<locals>.<lambda>  r   r"   r   unsafe gateway authority file)r   r+   r,   O_CREATr   r   rq   fstatr   r   r   r   r   r   r0   )r   r   	parent_fd
open_flagsr7   r   r   s          r#   _authority_openr      s    $T*IWRq99
2::JiHBJyAB
88B<D5'"h57KLL&#{(B==A<<%.
566I 	s   A-D> >Epayloadc           	      b   | j                   j                  dd       t        |       }| j                         r|t	        d      t        |       }d| j                   dt        j                          dt        j                  d       d}d}	 t        j                  |t        j                  t        j                  z  t        j                  z  t        t        dd	      z  d
|      }t        j                   |dd      5 }d}t#        j$                  ||dd       |j'                          t        j(                  |j+                                ddd       t        j,                  || j                  ||       t        j(                  |       	 |t        j2                  |       t        j2                  |       y# 1 sw Y   oxY w# t.        $ r) 	 t        j0                  ||        # t        $ r Y  w xY ww xY w# |t        j2                  |       t        j2                  |       w xY w)zCAtomically replace a private authority file without path following.Tparentsexist_okNr   .   z.tmpr   r   r   r   wutf-8encoding,:)indent
separators)
src_dir_fd
dst_dir_fd)r   mkdirr   existsr0   r   r   r,   getpidsecrets	token_hexr   r   O_EXCLO_WRONLYr+   fdopenjsondumpflushfsyncfilenoreplaceBaseExceptionunlinkrq   )r   r   existingr   temporary_namer7   handles          r#   _write_authority_jsonr     s   KKdT2t$H{{})566$T*I1RYY[M73D3DQ3G2HMN	BWWJJ"R[[072|Q3OO	
 YYr31VBIIgvdzJLLNHHV]]_%	 2
 	

II  		
 	 >HHRL
+ 21  	IInY7 	  		 >HHRL
sW   A3G AF?A G ?GG 	G=G-,G=-	G96G=8G99G==H   .H.c                     t        |       yt        |       }	 t        j                  | j                  |       t        j
                  |       t        j                  |       y# t        j                  |       w xY w)z7Remove only a validated private regular authority file.Nr   )r   r   r,   r   r   r   rq   )r   r   s     r#   _unlink_authority_filer   6  sV    t$$T*I
		$))I.

s   6A% %A<F)forcer   c                   |r|t         rv	 t        j                  ddt        |       ddgddd      }|j                  d	k7  r>|j                  xs |j                  xs d
j                         }t        |xs d|        y|st        j                  nt        t        dt        j                        }t        j                  | |       y# t        $ r' t        j                  | t        j                         Y yw xY w)zTerminate a PID with platform-appropriate force semantics.

    POSIX uses SIGTERM/SIGKILL. Windows uses taskkill /T /F for true force-kill
    because os.kill(..., SIGTERM) is not equivalent to a tree-killing hard stop.
    taskkillz/PIDz/Tz/FT
   capture_outputtextrf   Nr    ztaskkill failed for PID ra   )_IS_WINDOWS
subprocessrunstrr   r,   rd   r<   rb   
returncodestderrstdoutstripr0   r+   )r$   r   r>   detailsr9   s        r#   terminate_pidr   B  s     		^^VSXtT:#	F !}};;BBDG'E'?u%EFF %&..769fnn+UCGGC ! 	GGC(	s   'C -C76C7identityc                 l    t        j                  | j                  d            j                         d d S )Nr      )hashlibsha256encode	hexdigest)r   s    r#   _scope_hashr   ]  s*    >>(//'23==?DDr"   scopec                 :    t               |  dt        |       dz  S )N-z.lock)r   r   )r   r   s     r#   _get_scope_lock_pathr  a  s"    ?waH(='>eDDDr"   c                 B   t        d|  d      }	 t        |j                  d      j                         d         S # t        t
        t        t        t        f$ r Y nw xY w	 ddl	}t         |j                  |       j                         dz        S # t        $ r Y yw xY w)	z:Return the kernel start time for a process when available./proc/z/statr   r      r   Ni@B )r   rs   	read_textsplitr   
IndexErrorPermissionError
ValueErrorr0   rX   ru   rO   	Exception)r$   	stat_pathrX   s      r#   _get_process_start_timer  e  s    vcU%()I9&&&8>>@DEEz?JP >6>>#&224y@AA s!   += AA"/B 	BBc                      	 t        d      j                  d      j                         } | r| S 	 	 ddl}d |j                         dS # t        t        t
        t        f$ r Y 5w xY w# t        $ r Y yw xY w)z5Return a host-boot identity for PID-reuse validation.z/proc/sys/kernel/random/boot_idr   r   r   Nz
boot-time:z.6f)
r   r  r   r   r
  r0   UnicodeDecodeErrorrX   	boot_timer  )rH   rX   s     r#   rR   rR   v  s    89CC D 

%' 	 N 
,F,,.s344 9KL   s"   ,A
 A) 
A&%A&)	A54A5c                     | y 	 t        t        |       j                         j                               S # t        t
        t        t        f$ r Y y w xY wrp   )r   r   
expanduserresolver0   RuntimeError	TypeErrorr  r   s    r#   rS   rS     sK    |4:((*22455\9j9 s   /5 AAc                 f   	 t        d|  d      j                         }|j                  d      D ]a  }|j                  d      s|j	                  d      d   j                  dd	      }t        |      j                         j                         c S  	 	 d
dl}t         |j                  |       dd      }t        |      r |       j!                  d      nd}|r't        |      j                         j                         S dS # t        t        t        t        f$ r Y w xY w# t"        $ r Y yw xY w)zHReturn a live process's HERMES_HOME, or None when it cannot be verified.r  z/environ    s   HERMES_HOME=   =   r   stricterrorsr   NrJ   rK   )r   
read_bytesr  r/   	partitiondecoder  r  r   r
  r0   r  rX   r+   ru   rT   rP   r  )r$   rawentryvaluerX   rJ   s         r#   _read_process_homer$    s   VC5)*557YYw'E0-a0777QE{--/7799 (.&..-y$?080A	m,t5:tE{%%'//1DD 9KL   s8   AD AD D A,D$ D$ D! D!$	D0/D0c                     t        |       S )zBPublic wrapper for retrieving a process start time when available.)r  )r$   s    r#   get_process_start_timer&    s    "3''r"   c                    t        d|  d      }	 |j                         }|r1|j                  dd      j                  dd      j	                         S 	 t        j                  dd	t        |       d
dgddd      }|j                  dk(  r4|j                  j	                         r|j                  j	                         S 	 ddl} |j                  |       }|j!                         }|rdj#                  |      S 	 y# t
        t        t        f$ r Y w xY w# t        t        j                  f$ r Y qw xY w# t$        $ r Y yw xY w)zReturn the process command line as a space-separated string.

    On Linux, reads /proc/<pid>/cmdline directly.  On macOS and other
    platforms without /proc, falls back to ``ps -p <pid> -o command=``.
    On Windows (no /proc, no ps), uses psutil.
    r  z/cmdliner      r   ignorer  ps-pz-ozcommand=T   r   r   NrL   )r   r  r   r   r   r   r
  r0   r   r   r   r   r   rk   rX   ru   rW   rV   r  )r$   cmdline_pathr!  r>   rX   proccmdline_partss          r#   _read_process_cmdliner0    sH    &X./LW%%' ;;w-44WX4NTTVV
4S44	
 !fmm&9&9&;==&&((
v~~c"88M** 
 9 8  Z../   s6   C; A)D  8D4 ;DDD10D14	E ?E commandc                    | sy	 t        j                  | d      }|D cg c]1  }|j                  d      j	                  dd      j                         3 }}|sy|D ]5  }|dk(  s|j                  d      r y|j                  dd	      d
   }|dv s5 y dj                  |      }d|v xs d|v xs t        d |D              }|syg }d}	|D ]B  }|	rd}	|dv rd}	|j                  d      s|j                  d      r2|j                  |       D t        |      D ]*  \  }
}|dk7  r|
d	z   t        |      k\  r y||
d	z      dk(  c S  y# t        $ r | j                         }Y Kw xY wc c}w )am  Return True only for a real ``gateway run`` process command line.

    Lifecycle decisions (is the gateway up? did restart relaunch it?) must not
    fire on loose substring matches.  The previous ``"... gateway" in cmdline``
    test also matched ``hermes_cli.main gateway status`` and even unrelated
    processes like ``python -m tui_gateway`` -- which made ``restart()`` race
    against a still-draining old process and ``status``/``start`` report false
    positives.  This requires the actual ``gateway`` subcommand followed by
    ``run`` (or one of the gateway-dedicated entrypoints), excluding the other
    ``gateway`` management subcommands and any process that merely contains the
    word "gateway".

    Tokenizes quote-aware (``shlex``) so quoted Windows paths with spaces
    (``"C:\Program Files\...\hermes-gateway.exe"``) survive, and strips
    ``--profile``/``-p`` selectors from anywhere in argv -- Hermes's
    ``_apply_profile_override`` removes them before argparse, so the profile
    flag (and a profile literally named ``gateway``) can legally appear on
    either side of the ``gateway`` subcommand.
    F)posixz"'\/zgateway/run.pyz/gateway/run.pyTr   )r   zhermes-gateway.exerL   zhermes_cli.mainzhermes_cli/main.pyc              3   J   K   | ]  }|j                  d d      d   dv   yw)r5  r   r6  )r   z
hermes.exeN)rsplit).0ts     r#   	<genexpr>z2looks_like_gateway_command_line.<locals>.<genexpr>  s'     PAqxxQ#'??s   !#)z	--profiler+  z
--profile=z-p=gatewayr   )shlexr  r  r   r   lowerendswithr8  rV   anyr/   append	enumeratelen)r1  
raw_tokensr:  tokenstokenbasenamejoinedhas_gateway_entryfiltered	skip_nextis              r#   rU   rU     s   ( %[[6
 BLLAaggen$$T3/557FL $$7H(I<<Q'+??  XXfFV# 	Q6)	QPPP 
 
 HII''IL)U-=-=e-D  h'5Iq5CM!A%'' ( ]  %]]_
% Ms   E 6E+E('E(c                 4    t        |       }|syt        |      S )zBReturn True when the live PID still looks like the Hermes gateway.F)r0  rU   )r$   rW   s     r#   _looks_like_gateway_processrN    s    #C(G*733r"   rD   c                     | j                  d      t        k7  ry| j                  d      }t        |t              r|sydj	                  d |D              }t        |      S )zMValidate gateway identity from PID-file metadata when cmdline is unavailable.rG   FargvrL   c              3   2   K   | ]  }t        |        y wrp   )r   )r9  parts     r#   r;  z-_record_looks_like_gateway.<locals>.<genexpr>0  s     2TTs4yTs   )rP   rQ   
isinstancelistrV   rU   )rD   rP  rW   s      r#   _record_looks_like_gatewayrU  '  sR    zz&]*::fDdD!hh2T22G*733r"   rE   c                 |   | j                  d      t        k7  rt        j                  S t	        |      st        j                  S | j                  d      }t        |      }|t        j                  S |t        j                  S ||k7  rt        j                  S | j                  d      }t               }t        |t              r|st        j                  S |t        j                  S ||k7  rt        j                  S t        | j                  d            }t        |      }t        t        |            }	||t        j                  S |	t        j                  S ||k7  s|	|k7  rt        j                  S t        |      }
|
t        j                  S t        |
      rt        j                  S t        j                  S )aU  Return whether *record* names this exact live gateway process.

    PID liveness alone is not process identity: PIDs are reused, a generic
    gateway command can belong to another profile, and kernel start counters
    reset at reboot. All four identity fields must agree before lifecycle
    commands may report or signal the process.
    rG   
start_timerH   rI   )rP   rQ   r   r   _pid_existsr  r    rR   rS  r   rS   r$  r0  rU   r   )rD   r$   rE   recorded_startcurrent_startrecorded_bootcurrent_bootr]   expected_home_value	live_homelive_commands              r#   rM   rM   4  s    zz&]*'''s'''ZZ-N+C0M'''&&&&'''JJy)M%'LmS)'''&&&$'''!&**V"45K)-8 23 78I19'''&&&))Y:M-M'''(-L&&& +<8 	 %%r"   )rE   c                   | xs
 t               }|xs |j                  }t        |j                        t        |      k7  ryt        |      }t	        |      syt        |      t        |      fD ]  }t        |t              st        |      }|"t        j                  j                  d      r@	 t        |t        |      ||      }|%|j!                         r|c S |j#                          	 t%        j&                  |      }t        |||j)                         ||      }|j!                         s|c S  y# t        $ r d}Y w xY w# t$        j*                  t$        j,                  t$        j.                  f$ r Y w xY w)a*  Open a stable handle for this profile's verified gateway, if supported.

    A pidfd binds signalling to the process instance opened before the final
    metadata check. Platforms without an equivalent stable handle fail closed
    rather than falling back to a validate-then-``os.kill`` race.
    Nr(   )r$   r7   rD   rE   )r$   rB   rC   rD   rE   )rz   r   rS   r   is_gateway_runtime_lock_active_read_pid_record_read_gateway_lock_recordrS  rw   _pid_from_recordr-   r.   r/   rA   r8   r0   r_   rq   rX   ru   rO   rY   rZ   r[   )	r{   rE   resolved_pid_pathresolved_home	lock_pathrD   r$   r   rB   s	            r#   open_running_gateway_processrh  k  sx    !3MO!=%6%=%=M(//0OM4RR&'89I))4 	*+!), &$'v&;<<""7+-"3'!"/	 !??$!M
	nnS)G)$+$7$7$9+F ??MIJ )    ##V%9%96;O;OP 		s$   0D:03E:EE2F Fc            	          t        j                         t        t        t        j
                        t        t        j                               t               t        t                     dS )N)r$   rG   rP  rW  rH   rI   )
r,   r   rQ   rT  r-   rP  r  rR   rS   r	   r!   r"   r#   _build_pid_recordrj    sB    yy{SXX-biik:%' 12 r"   c            	      Z    t               } | j                  dd ddi t               d       | S )NstartingFr   )gateway_stateexit_reasonrestart_requestedactive_agents	platforms
updated_at)rj  updater   )r   s    r#   _build_runtime_status_recordrt    s6    !GNN#""n  Nr"   c                    	 t        |       r[t        | t        j                        }	 t        j                  |dd      5 }|j                         j                         }d d d        n | j                  d      j                         }sy 	 t        j                  |      }t        |t               r|S d S # 1 sw Y   WxY w# t        $ r  w xY w# t        t        t        f$ r Y y w xY w# t        j                  $ r Y y w xY w)Nrr   r   )r   r   r,   r   r   readr   r  r  r   r0   r  r   loadsJSONDecodeErrorrS  rw   )r   r7   r   r!  r   s        r#   _read_json_filerz    s    d# r{{3BYYr39V ++---/C :
 ..'.288:C
 **S/ !$/79T9! :9  w(:;    sR   %C C  B5C '!C C) 5B>:C CC C&%C&)C?>C?c                 T    t        |       rt        | |       y t        | |ddd       y )Nr   r   r   )r   r   mode)r   r   r   )r   r   s     r#   _write_json_filer}    s.    $dG,r"   c                 0    t        | xs
 t                     S )zNRead only validated JSON authority records; legacy numeric PIDs lack identity.)rz  rz   r{   s    r#   rb  rb    s    86}77r"   rg  c                 0    t        | xs
 t                     S rp   )rb  r   )rg  s    r#   rc  rc    s    IA)?)ABBr"   c                 Z    | sy 	 t        | d         S # t        t        t        f$ r Y y w xY w)Nr$   )rs   KeyErrorr  r  )rD   s    r#   rd  rd    s5    6%=!!i, s    **cleanup_stalec                    |sy	 t        |        	 t        t        |              y# t        $ r Y !w xY w# t        $ r Y yw xY w)a  Delete a stale gateway PID file (and its sibling lock metadata).

    Called from ``get_running_pid()`` after the runtime lock has already been
    confirmed inactive, so the on-disk metadata is known to belong to a dead
    process.  Unlike ``remove_pid_file()`` (which defensively refuses to delete
    a PID file whose ``pid`` field differs from ``os.getpid()`` to protect
    ``--replace`` handoffs), this path force-unlinks both files so the next
    startup sees a clean slate.
    N)r   r  r   r{   r  s     r#   _cleanup_invalid_pid_pathr    sQ     x(5h?@    s   & 5 	22	A Ac                    | j                  d       | j                          t        j                  t	               |        | j                          	 t        j                  | j                                y # t        $ r Y y w xY w)Nr   )
seektruncater   r   rj  r   r,   r   r   r0   r   s    r#   _write_gateway_lock_recordr    s[    
KKN
OOII!6*
LLN
! s   #A5 5	B B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   TF)r   r  r,   SEEK_ENDtellwriter   _WINDOWS_LOCK_OFFSETmsvcrtlockingr   LK_NBLCKfcntlflockLOCK_EXLOCK_NBBlockingIOErrorr0   r  s    r#   _try_acquire_file_lockr    s    KK2;;'{{}!T"KK,-NN6==?FOOQ?  KK)FGW% s   B"C) %AC) )C;:C;c                    	 ddl }t         |j                  t        |                   S # t        $ r Y nw xY wt
        r	 ddl}|j                  j                  }|j                  |j                  _        |j                  |j                  _        |j                  |j                  _        d}d}d}d}d}|j                  ||z  dt        |             }	|	s|j                         }
|
|k(  ry|
|k(  ry	y	 |j                  |	d      }||k(  |j                  |	       S # |j                  |	       w xY w# t         t"        f$ r Y yw xY w	 t%        j&                  t        |       d       y	# t(        $ r Y yt*        $ r Y y	t         $ r Y yw xY w)
u  Cross-platform "is this PID alive" check that does NOT kill the target.

    CRITICAL on Windows: Python's ``os.kill(pid, 0)`` is NOT a no-op like it
    is on POSIX. CPython's Windows implementation
    (``Modules/posixmodule.c::os_kill_impl``) treats ``sig=0`` as
    ``CTRL_C_EVENT`` because the two values collide at the C level, and
    routes it through ``GenerateConsoleCtrlEvent(0, pid)`` — which sends
    a Ctrl+C to the entire console process group containing the target
    PID, not just the PID itself. Any caller that wanted to "check if
    this PID is alive" via ``os.kill(pid, 0)`` on Windows was silently
    killing that process (and often unrelated processes in the same
    console group). Long-standing Python quirk; see bpo-14484.

    Implementation: prefer :mod:`psutil` (hard dependency — the canonical
    cross-platform answer, maintained by Giampaolo Rodolà, uses
    ``OpenProcess + GetExitCodeProcess`` on Windows internally). Fall back
    to a hand-rolled ctypes ``OpenProcess`` / ``WaitForSingleObject`` pair
    on Windows + ``os.kill(pid, 0)`` on POSIX if psutil is somehow
    unavailable — e.g. stripped-down install or import error during the
    scaffold phase before ``psutil`` is pip-installed.
    r   Ni   r   i  W   r,  FT)rX   ri   
pid_existsrs   ImportErrorr   r1   windllkernel32c_void_pOpenProcessrestypec_uintWaitForSingleObjectGetLastErrorCloseHandler0   AttributeErrorr,   rd   ProcessLookupErrorr
  )r$   rX   r1   r  !PROCESS_QUERY_LIMITED_INFORMATIONSYNCHRONIZEWAIT_TIMEOUTERROR_INVALID_PARAMETERERROR_ACCESS_DENIEDr   errwait_results               r#   rX  rX  &  s   ,%F%%c#h/00  	}}--H ,2??H  (39==H((0,2MMH!!)06-"K%L&(#"#))1K?CF ++-11 ---&::61E #l2$$V,$$V,( 				GGCHa ! 	 	 		s[   '* 	66B+D3 -D3 5D D3 D00D3 3EE	E) )	F	4F	>F	F	c                 &   	 t         rI| j                  t               t        j                  | j                         t        j                  d       y t        j                  | j                         t        j                         y # t        $ r Y y w xY w)Nr   )r   r  r  r  r  r   LK_UNLCKr  r  LOCK_UNr0   r  s    r#   _release_file_lockr  q  s[    KK,-NN6==?FOOQ?KK7 s   AB 2B 	BBc                  T   t         yt               } | j                  j                  dd       	 t	        j
                  t        | t        j                  t        j                  z        dd      }t        |      s|j                          yt        |       |a y# t        $ r Y yw xY w)zClaim the cross-process runtime lock for the gateway.

    Unlike the PID file, the lock is owned by the live process itself. If the
    process dies abruptly, the OS releases the lock automatically.
    Tr   r+r   r   F)_gateway_lock_handler   r   r   r,   r   r   O_RDWRr   r0   r  rq   r  )r   r   s     r#   acquire_gateway_runtime_lockr  |  s     '!#DKKdT2?4RZZ1GH$Y`a "&)v&!  s   AB 	B'&B'c                  p    t         } | yda t        |        	 | j                          y# t        $ r Y yw xY w)z<Release the gateway runtime lock when owned by this process.N)r  r  rq   r0   r  s    r#   release_gateway_runtime_lockr    s?     "F~v s   ) 	55c                     | xs
 t               }t        |t               k(  ryt        |      "|j                         xs |j	                         S 	 t        j                  t        |t
        j                        dd      }	 t        |      rt        |       	 	 |j                          y	 	 |j                          y# t        $ r Y yw xY w# t        $ r Y yw xY w# t        $ r Y yw xY w# 	 |j                          w # t        $ r Y w w xY wxY w)zFReturn True when some process currently owns the gateway runtime lock.Tr  r   r   F)r   r  r   r   
is_symlinkr,   r   r   r  r0   r  r  rq   )rg  resolved_lock_pathr   s      r#   ra  ra    s    #>&<&>',>BXBZ,Z)*2 "((*M.@.K.K.MM?+=ryyI4Zab	!&)v&	LLN 	LLN    		w 			LLN 		sf   0B> C+ C -C >	C
	C
	CC	C('C(+D-C>=D>	D
D	D

Dc            	         t               } | j                  j                  dd       t        j                  t                     }t        |       }	 t        j                  | j                  t        j                  t        j                  z  t        j                  z  t        t        dd      z  d|      }	 t        j                   |dd	      5 }|j#                  |       |j%                          t        j&                  |j)                                d
d
d
       t        j&                  |       	 t        j                  |       y
# t        $ r t        j                  |        w xY w# 1 sw Y   XxY w# t*        $ r3 	 t        j,                  | j                  |        # t.        $ r Y  w xY ww xY w# t        j                  |       w xY w)zWrite the current process PID and metadata to the gateway PID file.

    Uses atomic O_CREAT | O_EXCL creation so that concurrent --replace
    invocations race: exactly one process wins and the rest get
    FileExistsError.
    Tr   r   r   r   r   r   r   r   N)rz   r   r   r   dumpsrj  r   r,   r   r   r   r   r   r+   FileExistsErrorrq   r   r  r   r   r   r  r   r0   )r   rD   r   r7   fs        r#   write_pid_filer    s^    ?DKKdT2ZZ)+,F$T*I	WWIIJJ"R[[072|Q3OO	
YYr31QGGFOGGIHHQXXZ  2 	 	!  
 21
  	IIdii	2 	  		 	sb   A%E 7E7 AE+E7  E(+E40E7 7	F3!F#"F3#	F/,F3.F//F33F6 6G)	rm  rn  ro  rp  r.   platform_state
error_codeerror_messageserved_profilesrm  rn  ro  rp  r.   r  r  r  r  c        	            t               }	t        |	      xs
 t               }
t               }|
j	                  di        |d   |
d<   |d   |
d<   |d   |
d<   |d   |
d<   |d   |
d<   |d   |
d<   t               |
d<   | t        ur| |
d	<   |t        ur||
d
<   |t        urt        |      |
d<   |t        urt        dt        |            |
d<   |t        urt        |xs g       |
d<   |t        urQ|
d   j                  |i       }|t        ur||d<   |t        ur||d<   |t        ur||d<   t               |d<   ||
d   |<   t        |	|
       y)zBPersist gateway runtime health information for diagnostics/status.rq  rG   r$   rP  rW  rH   rI   rr  rm  rn  ro  r   rp  r  r   r  r  N)r   rz  rt  rj  
setdefaultr   _UNSETri   maxrs   rT  rP   r}  )rm  rn  ro  rp  r.   r  r  r  r  r   r   current_recordplatform_payloads                r#   write_runtime_statusr    s    $%Dd#E'C'EG&(N{B'$V,GFO#E*GEN$V,GFO*<8GL'	2GI$V,GFO(NGLF"#0 & !,&'+,='>#$F"#&q#m*<#= f$ &*/*?R%@!"v";/33HbA'(6W%V#-7\*&0=_-)5&)9X&T7#r"   c                   | j                  d      }|dv r| S |j                         s| S t        |       }|t        |       }d|d<   |S t	        |      }t        |      r| S t        |      sGt        |       }d|d<   d|d<   t               |d<   	 t        t        |      |       t        |d
       |S t        | ||j                        }|t        j                  urt        |       }d|d<   |S | S # t        $ r}t        d	      |d}~ww xY w)az  Reconcile a conclusively dead gateway without trusting its finalizer.

    SIGKILL and a host crash prevent the gateway from writing its own terminal
    record. The first observer, while the runtime lock is inactive, is the
    authority that writes ``stopped``. An unreadable or live-but-mismatched
    identity is deliberately preserved and surfaced as degraded instead.
    rm  >   Nstoppedstartup_failedNdegradedr  unexpected_exitrn  rr  z.unable to reconcile dead gateway runtime stateTr  )rP   r   rd  rw   r   ra  rX  r   r}  %_get_runtime_status_path_for_pid_pathr0   r  rM   r   r   r   )	r   r{   rm  r$   observedrg  
reconciledexcr   s	            r#   _observe_runtime_statusr    s(    KK0M;; ??
7
#C
{=$.!&x0I%i0s']
&/
?#$5
=!#/>
< 	UB8LjY 	"($?+GS(//JH,,,=$.!N  	UJKQTT	Us   C0 0	D
9DD
c                 ,    | j                  t              S rp   )r}   r   r  s    r#   r  r  G  s    233r"   c                  ^    t               } t        t        |             }|yt        ||       S )zCRead runtime status and reconcile only a conclusively dead gateway.Nr  )rz   rz  r  r  )r{   r   s     r#   read_runtime_statusr  K  s.    HCHMNG"7X>>r"   runtimec                    | | n	t               }t        |t              sy|j                  d      }|dv ry|dk(  rI|j                  d      xs i j                  d      }t        |t              r|j                  d      dk7  ryt	        |      }|yt        ||t               j                        t        j                  u r|S y)	a  Return a live gateway PID from the runtime status record, if valid.

    ``get_running_pid()`` is the primary liveness source because it verifies the
    runtime lock and PID file.  Launch-service managers can still leave us with
    a live process and a fresh ``gateway_state.json`` but no ``gateway.pid``; use
    this as a conservative fallback only after validating the full process and
    profile identity tuple.
    Nrm  >   Nr  r  runningrq  telegramr   	connected)
r  rS  rw   rP   rd  rM   rz   r   r   r   )r  r   rm  r  r$   s        r#   get_runtime_status_running_pidr  T  s     !,g2E2GGgt$KK0M;;	!KK,277
Ch%(,,w*?;*N
7
#C
{#GS-/2H2HI_MbMbb
r"   c                      	 t               } t        |       }|)	 t        |d         }||t        j                         k7  ry| j                  d       y# t        t        t
        f$ r d}Y Ew xY w# t        $ r Y yw xY w)ah  Remove the gateway PID file, but only if it belongs to this process.

    During --replace handoffs, the old process's atexit handler can fire AFTER
    the new process has written its own PID file.  Blindly removing the file
    would delete the new process's record, leaving the gateway running with no
    PID file (invisible to ``get_running_pid()``).
    Nr$   T
missing_ok)
rz   rz  rs   r  r  r  r,   r   r   r  )r   rD   file_pids      r#   remove_pid_filer  r  s     & ve}- #BIIK(?t$ i4     s9   A1 A A1 A1 A.+A1 -A..A1 1	A=<A=metadatac                    t        | |      }|j                  j                  dd       i t               | t	        |      |xs i t               d}t        |      }|#|j                         r	 |j                  d       |r	 t        |d         }|t        j                         k(  r3|j!                  d      |j!                  d      k(  rt#        ||       d|fS |du }|t%        |      sd}nt'        |      }|j!                  d      |||j!                  d      k7  rd}|s8|j!                  d      '|%t)        |      st+        |      }	|	t-        |      sd}|s-|j!                  d      |t)        |      st+        |      }	|	d}|sq	 t/        d| d	      }
|
j                         rQ|
j1                  d
      j3                         D ].  }|j5                  d      s|j7                         d   }|dv rd} n |r	 |j                  d       nd|fS 	 t        j:                  |t        j<                  t        j>                  z  t        j@                  z  d      }	 t        jD                  |dd
      5 }tG        jH                  ||       ddd       y# t        $ r Y )w xY w# t        t        t        f$ r d}Y 4w xY w# t        t8        f$ r Y w xY w# t        $ r Y w xY w# tB        $ r dt        |      fcY S w xY w# 1 sw Y   yxY w# tJ        $ r$ 	 |j                  d        # t        $ r Y  w xY ww xY w)zAcquire a machine-local lock keyed by scope + identity.

    Used to prevent multiple local gateways from using the same external identity
    at once (e.g. the same Telegram bot token across different HERMES_HOME dirs).
    Tr   )r   identity_hashr  rr  Nr  r$   rW  r  z/statusr   r   zState:r   >   Tr:  Fr   r   )TN)&r  r   r   rj  r   r   rz  r   r   r0   rs   r  r  r  r,   r   rP   r}  rX  r  rN  r0  rU  r   r  
splitlinesr/   r  r
  r   r   r   r   r  r   r   r   r  )r   r   r  rg  rD   r   existing_pidstalerZ  live_cmdline_proc_status_line_stater7   r   s                  r#   acquire_scoped_lockr    so    %UH5I4$7

$X.N"nF y)HI,,.
	- 	 x/L 299;&8<<+ET`Ia+aY/>!$#|, 7 ELL.:%1%l)CC E  \2:%-7E#8#FL#/7QRZ7[ $  \2>%17E#8#FL#/ $ 
'+f\N',J'K'..0)5)?)?)?)Q)\)\)^#(#3#3H#=-2[[]1-=F'-';04$) *_   D 1 (?"1WWY

RYY 6 DeLYYr31VIIff% 2 w  		
 )Z0 	 L	 ~ $_5 
    1oi0001 2   	- 	  		s   0J J/ AK (K K! AK0 'L ?LL 	J,+J,/K	K	KK!	K-,K-0L	L	LL L 	M"L54M5	M>M MMc                 *   t        | |      }t        |      }|sy|j                  d      t        j                         k7  ry|j                  d      t        t        j                               k7  ry	 |j                  d       y# t        $ r Y yw xY w)zDRelease a previously-acquired scope lock when owned by this process.Nr$   rW  Tr  )r  rz  rP   r,   r   r  r   r0   )r   r   rg  r   s       r#   release_scoped_lockr     s    $UH5Iy)H||Ebiik)||L!%<RYY[%IID) s   3B 	BB)	owner_pidowner_start_timer  r  c                    t               }d}|j                         r|j                  d      D ]u  }| Yt        |      }t	        |t
              s!|j                  d      }|5	 t        |      }|| k7  rG||j                  d      |k7  r^	 |j                  d       |dz  }w |S # t        t        f$ r Y w xY w# t        $ r Y w xY w)a&  Remove scoped lock files in the lock directory.

    Called during --replace to clean up stale locks left by stopped/killed
    gateway processes that did not release their locks gracefully. When an
    ``owner_pid`` is provided, only lock records belonging to that gateway
    process are removed. ``owner_start_time`` further narrows the match to
    protect against PID reuse.

    When no owner is provided, preserves the legacy behavior and removes every
    scoped lock file in the directory.

    Returns the number of lock files removed.
    r   z*.lockr$   rW  Tr  r   )r   r   globrz  rS  rw   rP   rs   r  r  r   r0   )r  r  lock_dirremoved	lock_filerD   record_pid_raw
record_pids           r#   release_all_scoped_locksr    s    $ HG!x0I$(3!&$/!'E!2!)!$^!4J *$0

<04DD  D 11+ 10 N ":.   s$   $B(B=(B:9B:=	C	C	r   <   r   c                  (    t               } | t        z  S )z6Return the path to the --replace takeover marker file.)r	   _TAKEOVER_MARKER_FILENAMEry   s    r#   _get_takeover_marker_pathr  Y  s    D+++r"   c                  (    t               } | t        z  S )z<Return the path to the intentional gateway stop marker file.)r	   _PLANNED_STOP_MARKER_FILENAMEry   s    r#   _get_planned_stop_marker_pathr  _  s    D///r"   
written_atttl_sc                     	 t        j                  |       }t        j                  t        j                        |z
  j                         }||kD  S # t        t        f$ r Y yw xY w)NT)r   fromisoformatr   r   r   total_secondsr  r  )r  r  
written_dtages       r#   _marker_is_staler  e  sX    ++J7
||HLL)J6EEGU{z" s   AA A"!A"	pid_fieldstart_time_fieldboot_id_fieldc                z   t        |       }|sy	 t        ||         }|j                  |      }|j                  |      }|j                  d      xs d}	t        |	|      r	 | j                  d       yt        j                         }
t        |
      }t               }||
k7  rd}n&t        |t              r|r||k7  rd}n||||k(  }nd}	 | j                  d       |S # t        t        t
        f$ r& 	 | j                  d       Y y# t        $ r Y Y yw xY ww xY w# t        $ r Y yw xY w# t        $ r Y |S w xY w)NFr  r   Tr  )rz  rs   rP   r  r  r  r   r0   r  r,   r   r  rR   rS  r   )r   r  r  r  r  rD   
target_pidtarget_start_timetarget_boot_idr  our_pidour_start_timeour_boot_idmatchess                 r#   _consume_pid_marker_for_selfr  n  sl    T"F
	*+
"JJ'78M2ZZ-3
 
E*	KK4K( iikG,W5N$&K W,NnXcFc		&>+E#~5t$ NW i, 	KK4K(   		  		<  NsN   AC! "D D- !D6D

	DDDD	D*)D*-	D:9D:r  c                     	 t        |       }| |t               t        j                         t	               d}t        t               |       y# t        t        f$ r Y yw xY w)a  Record that ``target_pid`` is being replaced by the current process.

    Captures the target's ``start_time`` so that PID reuse after the
    target exits cannot later match the marker. Also records the
    replacer's PID and a UTC timestamp for TTL-based staleness checks.

    Returns True on successful write, False on any failure. The caller
    should proceed with the SIGTERM even if the write fails (the marker
    is a best-effort signal, not a correctness requirement).
    )r  r  r  replacer_pidr  TF)	r  rR   r,   r   r   r}  r  r0   r
  r  r  rD   s      r#   write_takeover_markerr    s]    3J?$!202IIK&.
 	24f=_%    A	A AAc                  :    t        t               dddt              S )a  Check & unlink the takeover marker if it names the current process.

    Returns True only when a valid (non-stale) marker names this PID +
    start_time. A returning True indicates the current SIGTERM is a
    planned --replace takeover; the caller should exit 0 instead of
    signalling ``_signal_initiated_shutdown``.

    Always unlinks the marker on match (and on detected staleness) so
    subsequent unrelated signals don't re-trigger.
    r  r  r  r  r  r  r  )r  r  _TAKEOVER_MARKER_TTL_Sr!   r"   r#    consume_takeover_marker_for_selfr#    s#     (!#,&$ r"   c                  X    	 t               j                  d       y# t        $ r Y yw xY w)zDRemove the takeover marker unconditionally. Safe to call repeatedly.Tr  N)r  r   r0   r!   r"   r#   clear_takeover_markerr%    s-    !#**d*;     	))c                     	 t        |       }| |t               t        j                         t	               d}t        t               |       y# t        t        f$ r Y yw xY w)a&  Record that ``target_pid`` is being stopped intentionally.

    The gateway exits non-zero for unexpected SIGTERM so service managers can
    revive it. Service stop commands send the same SIGTERM, so the CLI writes
    this short-lived marker first to let the target process exit cleanly.
    )r  r  r  stopper_pidr  TF)	r  rR   r,   r   r   r}  r  r0   r
  r  s      r#   write_planned_stop_markerr)    s]    3J?$!20299;&.
 	68&A_% r  c                  :    t        t               dddt              S )zDReturn True when the current process is being intentionally stopped.r  r  r  r!  )r  r  _PLANNED_STOP_MARKER_TTL_Sr!   r"   r#   $consume_planned_stop_marker_for_selfr,    s!    '%',&( r"   c                     t               } t        |       }|sy	 t        |d         }|j                  d      }|j                  d      }|j                  d      xs d}t        |t              r	 | j                  d       yt        j                         }||k7  ryt        |      }t               }t        |t               r|r||k7  r	 | j                  d       y||||k(  S y# t        t
        t        f$ r& 	 | j                  d       Y y# t        $ r Y Y yw xY ww xY w# t        $ r Y yw xY w# t        $ r Y yw xY w)	ue  Return True only when a live planned-stop marker names the current process.

    This is a **non-destructive** probe used by the watcher thread
    (``gateway/run.py:_run_planned_stop_watcher``) to decide whether to
    trigger shutdown. Unlike :func:`consume_planned_stop_marker_for_self`,
    it never unlinks a marker that matches us — the shutdown handler does
    the authoritative consume on its own thread.

    It *does* clean up markers that can never apply to this process:
    malformed markers and markers older than the TTL are unlinked so a
    stale file left behind by a previous gateway instance cannot wedge
    the new one. Markers naming a different PID/start_time are left in
    place (they may still be consumed legitimately by the process they
    name) but report False here.

    Returns False (without raising) on any read/parse error.
    Fr  r  r  r  r   Tr  )r  rz  rs   rP   r  r  r  r   r0   r  r+  r,   r   r  rR   rS  r   )	r   rD   r  r  r  r  r  r  r  s	            r#    planned_stop_marker_targets_selfr.    sk   $ )*DT"F-.
"JJ':;$45ZZ-3
 
$>?	KK4K( iikGW -W5N$&K~s+[(	KK4K( $)C N22W i, 	KK4K(   			  		2  		sN   AC' 0D$ 
D3 'D!<D	DD!DD!$	D0/D03	D?>D?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   r0   r!   r"   r#   clear_planned_stop_markerr0  N  s-    %'..$.? r&  Tr  c                   | xs
 t               }t        |      }t        |      }t        |      }t	        |      }d}||fD ]e  }t        |t              st        |      }	|	"t        ||	|j                        }
|r|
t        j                  u r|	c S |
t        j                  u sdd}g |s#t        t        |            }|t        ||       |s|s|t!        ||       y)zReturn the PID of a running gateway instance, or ``None``.

    Checks the PID file and verifies the process is actually alive.
    Cleans up stale PID files automatically.
    FNTr  r  )rz   r   ra  rb  rc  rS  rw   rd  rM   r   r   r   r    rz  r  r  r  )r{   r  re  r  lock_activeprimary_recordfallback_recordidentity_unknownrD   r$   r   r  s               r#   get_running_pidr6  V  s     !3MO/0AB01CDK%&78N/0BCO!?3&$'v&;/=N=U=UV8'<'<<J...# 4 !"GHY"Z[#G6GH /N4N!"3=Qr"   c                     t        | |      duS )z1Check if the gateway daemon is currently running.r  N)r6  r  s     r#   is_gateway_runningr8    s     8=AMMr"   rp   rr   )ur   r   r   r,   r=  r<   rh   r   r   r   rX   r-   dataclassesr   r   r   enumr   pathlibr   hermes_constantsr	   typingr
   r   utilsr   r.   r  r  rQ   r   r   r   objectr  r~   r  r  r   r   r   rs   r8   r?   rA   rz   r   r   r   r   ri   r   r   stat_resultr   r   rw   r   r   r   r   r  r  rR   rS   r$  r&  r0  rU   rN  rU  rM   rh  rj  rt  rz  r}  rb  rc  rd  r  r  r  rX  r  r  r  ra  r  r  r  r  r  r  r  tupler  r  r  r  r"  r  r+  r  r  r  r  r  r#  r%  r)  r,  r.  r0  r6  r8  r!   r"   r#   <module>rB     s     	        
 ! '   ,   #<<7 +  llg%	'   #  c4 S S F3 FS FT F Q Q Qh t  )Xd^ )t );$ ;
2t 22c 2-T -d -Mt M M$ 2>>D#8 &$ s s 0& &tCH~ &$ &R	 	$ 	 .3 s d t 6E# E# EE Es Et E # "HSM &#*t+  C HTN *( ( (
&s &x} &RGS4Z GD GT4S 4T 4
4tCH~ 
4$ 
44cN4	4 4 	4p  $8 %)8tn8 D>8 "#	8v4 
d38n 
:$ :8DcN#; :0
4 
$sCx. 
T 
8x~ 8$ 8
C$ C8DQTVYQYNC[ CXd38n5 (3-    ,d  GS GT GVd 0htn  8"N  # !2$2$ 2$ 	2$
 2$ 2$ 2$ 2$ 2$ 2$ 
2$j,#s(^, , 
#s(^	,^4D 4T 4?Xd38n5 ? )-d38n%c]<0ss sc sXd3PS8n=U safgkmuvz{~  AD  |D  wE  nF  hF  bG sls c d $  $&*-}- sm- 		-F 5   <  ,4 ,0t 0 S T <
< < 	<
 < < 
<~c d 4$ (# $ ,d G$ GT  $( (tn( ( c]	(X  $N NtnN N 
	Nr"   