Ë
    âß²jT  ã                  óÀ  — U d Z ddlm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
mZ ddlmZ ddlmZmZmZ  ej$                  e«      ZdZdZd	Zd
ZdZdZ eh d£«      ZdZdZdZdZ dZ!dZ"dZ#dZ$dZ% ejL                  «       Z'da(de)d<   d)d„Z*d*d+d„Z+d,d„Z,d-d„Z-d.d„Z.d/d„Z/d0d„Z0d1d„Z1 G d „ d!«      Z2	 d*ed"œ	 	 	 	 	 d2d#„Z3d3d$„Z4d4d%„Z5d5d6d&„Z6d7d8d'„Z7d3d(„Z8y)9u•  Startup-liveness watchdog â€” respawn a gateway that wedges before its loop runs (OOF-298).

Every other liveness backstop (loop-liveness watchdog, shutdown watchdog, heartbeat file)
assumes startup succeeded; none can fire if the process deadlocks before the event loop
is alive (OOF-298: ~30h with every thread in ``futex_wait_queue``, zero logs, s6 saw a
live PID). A daemon thread armed at process entry and disarmed once the loop is confirmed
live dumps all-thread stacks (``faulthandler``), records the exit in the lifecycle ledger
(NS-608) and ``os._exit``\ s with the service-restart code so s6/systemd respawn.

Slow-but-alive startups are not killed. Order of authority: (1) phase-owned progress
leases (:func:`report_startup_progress`) â€” authoritative, prove the *startup path itself*
is alive, work for I/O-bound phases (schema migration, corruption repair in
``SessionDB.__init__``) with ~zero CPU; (2) process-wide CPU progress, capped at
``_MAX_CPU_EXTENSIONS`` since an unrelated thread burning CPU must not hide a parked
startup thread forever. Known limitation: a *spinning* startup deadlock earns the capped
extensions before firing; the observed class is parked-thread deadlocks. Idle-by-design
waits call :func:`kick_startup_watchdog` (respawn-storm backoff, up to 300s); MCP
discovery's 120s wait sits inside the 300s default.

IMPORT-LIGHTNESS IS A CORRECTNESS PROPERTY: top-level module, stdlib only. Arming must
precede importing ``gateway`` (hundreds of modules; an import-time deadlock is in scope),
and at fire time the wedged main thread may hold the import lock â€” so the fire path does
no imports on its own thread; the ledger write runs on a helper thread joined with a
timeout. Config is env-only (``HERMES_STARTUP_WATCHDOG=0``,
``HERMES_STARTUP_WATCHDOG_TIMEOUT_S``) because config.yaml parsing is itself in scope.
Everything is best-effort: a watchdog failure must never affect the startup it observes.
é    )ÚannotationsN)ÚdatetimeÚtimezone)ÚPath)ÚAnyÚDictÚOptionalg     Àr@g      >@éK   ÚHERMES_STARTUP_WATCHDOGÚ!HERMES_STARTUP_WATCHDOG_TIMEOUT_S)Úlogszgateway-startup-watchdog.log>   Ú0ÚnoÚoffÚfalseg      @g      ð?é   g      Œ@g      $@ÚarmedÚdisarmedÚfiringz!Optional['StartupWatchdogHandle']Ú_handlec                 ó€  — t         j                  j                  dd«      j                  «       } | rt	        | «      S t
        j                  dk(  rZt         j                  j                  dd«      j                  «       }|rt	        |«      nt	        j                  «       dz  dz  }|dz  S t	        j                  «       dz  S )	uY   HERMES_HOME for diagnostic files â€” stdlib-only replica of the hermes_constants default.ÚHERMES_HOMEÚ Úwin32ÚLOCALAPPDATAÚAppDataÚLocalÚhermesz.hermes)ÚosÚenvironÚgetÚstripr   ÚsysÚplatformÚhome)ÚvalÚlocal_appdataÚbases      ú</home/ubuntu/.hermes/hermes-agent/hermes_startup_watchdog.pyÚ_process_hermes_homer*   e   s�   € ä
�*‰*�.‰.˜¨Ó
+×
1Ñ
1Ó
3€CÙ
Ü�C‹yÐÜ
‡|�|�wÒÜŸ
™
Ÿ™ ~°rÓ:×@Ñ@ÓBˆÙ&3Œt�MÔ"¼¿¹»ÀyÑ9PÐSZÑ9ZˆØ�h‰ÐÜ�9‰9‹;˜Ñ"Ð"ó    c                óD   — | �| n	t        «       } |j                  t        Ž S )z;Return ``<HERMES_HOME>/logs/gateway-startup-watchdog.log``.)r*   ÚjoinpathÚ_DUMP_RELATIVE)r%   r(   s     r)   Úget_startup_watchdog_dump_pathr/   q   s#   € àÐ#‰4Ô)=Ó)?€DØˆ4�=‰=œ.Ð)Ð)r+   c                 óŽ   — t         j                  j                  t        d«      j	                  «       j                  «       t        v S )z:True when ``HERMES_STARTUP_WATCHDOG`` opts out explicitly.r   )r   r    r!   ÚENV_STARTUP_WATCHDOGr"   ÚlowerÚ_FALSEY© r+   r)   Ústartup_watchdog_disabledr5   w   s/   € ä�:‰:�>‰>Ô.°Ó3×9Ñ9Ó;×AÑAÓCÄwÐNÐNr+   c                 ó.  — t         j                  j                  t        d«      j	                  «       } | st
        S 	 t        | «      }|dk  rt
        S t        |t        «      S # t        $ r) t        j                  dt        | t
        «       t
        cY S w xY w)zEDeadline in seconds; env override, floor-clamped, default on garbage.r   z/Ignoring non-numeric %s=%r; using default %.0fsr   )r   r    r!   ÚENV_STARTUP_WATCHDOG_TIMEOUT_Sr"   Ú"DEFAULT_STARTUP_WATCHDOG_TIMEOUT_SÚfloatÚ
ValueErrorÚloggerÚwarningÚmaxÚ_MIN_TIMEOUT_S)ÚrawÚvalues     r)   Ú resolve_startup_watchdog_timeoutrA   |   sƒ   € ä
�*‰*�.‰.Ô7¸Ó
<×
BÑ
BÓ
D€CÙÜ1Ð1ð2Ü�c“
ˆð �‚zÜ1Ð1Üˆu”nÓ%Ð%øô ò 2Ü�‰Ø=Ü*¨CÔ1Sô	
ô 2Ò1ð2ús   ¼A" Á"/BÂBc                óö   — 	 t        «       }|j                  j                  dd¬«       t        |dd¬«      5 } | |«       ddd«       y# 1 sw Y   yxY w# t        $ r t
        j                  |d¬«       Y yw xY w)zQOpen the dump file for append and hand it to *write*; failures only log at DEBUG.T)ÚparentsÚexist_okÚazutf-8)ÚencodingN©Úexc_info)r/   ÚparentÚmkdirÚopenÚ	Exceptionr;   Údebug)ÚwriteÚfailure_msgÚpathÚfhs       r)   Ú_append_dumprR   Ž   sn   € ð1Ü-Ó/ˆØ�‰×Ñ $°ÐÔ6Ü�$˜ gÔ.ð 	°"Ù�"ŒI÷	÷ 	ñ 	ûäò 1Ü�‰�[¨4ˆÖ0ð1ús.   ‚5A ·	A	Á A Á	AÁA ÁA Á A8Á7A8c                ó$   ‡ — t        ˆ fd„d«       y)zDAppend a one-line JSON metadata record beside the faulthandler dump.c                ó^   •— | j                  t        j                  ‰t        ¬«      dz   «      S )N)ÚdefaultÚ
)rN   ÚjsonÚdumpsÚstr)rQ   Úrecords    €r)   ú<lambda>z$_write_dump_record.<locals>.<lambda>œ   s    ø€ �2—8‘8œDŸJ™J v´sÔ;¸dÑBÓC€ r+   z,Failed to write startup watchdog dump recordN)rR   )rZ   s   `r)   Ú_write_dump_recordr\   ™   s   ø€ äÛCØ6õr+   c                óR   — 	  t        t        | «      |g|¢­Ž  y# t        $ r Y yw xY w)zPLog at *level*; never raises (the wedged main thread may hold the logging lock).N)Úgetattrr;   rL   )ÚlevelÚmsgÚargss      r)   Ú_log_quietlyrb   ¡   s/   € ðØŒ”˜Ó˜sÐ* TÔ*øÜò Ùðús   ‚ š	&¥&c                óD   — 	 ddl m}  || d¬«       y# t        $ r Y yw xY w)zÜRecord the watchdog exit in the NS-608 lifecycle sentinel.

    Runs on a helper thread (see ``_fire``): the import can block on the
    interpreter import lock, and the fire path must reach ``os._exit`` regardless.
    r   )Úmark_exitedÚstartup_liveness_watchdog)ÚreasonN)Úgateway.lifecycle_ledgerrd   rL   )Ú	exit_coderd   s     r)   Ú_mark_lifecycle_exitri   ©   s'   € ðÝ8á�IÐ&AÖBøÜò Ùðús   ‚ “	žc                  ó¶   — e Zd ZdZdd„Zdd„Zddd„Zddd„Zedd„«       Z	dd„Z
ddd	„Zedd
„«       Zdd„Zdd„Zedd„«       Zedd„«       Zdd„Zdd„Zdd„Zy)ÚStartupWatchdogHandlez<Disarm/inspect handle for the armed startup watchdog thread.c                ól  — || _         || _        t        j                  «       | _        t
        | _        t        j                  «       | _	        | j                  |z   | _
        t        j                  «       | _        d | _        d| _        d| _        d | _        d| _        t        j                  «       | _        y )Nr   ç        )Ú	timeout_srh   ÚtimeÚ	monotonicÚarmed_atÚ_ARMEDÚ_stateÚ	threadingÚLockÚ_state_lockÚ	_deadlineÚEventÚ_disarmed_eventÚ_threadÚ_extensionsÚ_lease_untilÚ_lease_phaseÚ_lease_countÚ
_fire_done)Úselfrn   rh   s      r)   Ú__init__zStartupWatchdogHandle.__init__º   sŠ   € Ø"ˆŒØ"ˆŒÜŸ™Ó(ˆŒÜˆŒÜ$Ÿ>™>Ó+ˆÔØŸ™¨Ñ2ˆŒÜ(Ÿ™Ó0ˆÔØ37ˆŒØˆÔð  ˆÔØ+/ˆÔØˆÔä#Ÿ/™/Ó+ˆ�r+   c                ó¶   — | j                   5  | j                  t        k(  rt        | _        ddd«       | j                  j                  «        y# 1 sw Y   Œ$xY w)uì   Startup reached a live event loop â€” stand down. Idempotent.

        Atomic with respect to firing: whichever of disarm/fire takes the state
        lock first wins, so a disarm landing before the fire sequence is never lost.
        N)rv   rs   rr   Ú	_DISARMEDry   Úset©r€   s    r)   ÚdisarmzStartupWatchdogHandle.disarmÌ   sI   € ð ×Ññ 	(Ø�{‰{œfÒ$Ü'�”÷	(ð 	×Ñ× Ñ Õ"÷	(ð 	(ús   �AÁAc                óò   — 	 t        dt        |«      «      }| j                  5  t        j                  «       | j                  z   |z   | _        ddd«       y# t        t        f$ r d}Y ŒRw xY w# 1 sw Y   yxY w)záPush the deadline out to ``now + timeout + extra_s``.

        For call sites about to block intentionally with ~zero CPU (the
        respawn-storm backoff sleep), otherwise indistinguishable from a parked deadlock.
        rm   N)	r=   r9   Ú	TypeErrorr:   rv   ro   rp   rn   rw   )r€   Úextra_sÚextras      r)   ÚkickzStartupWatchdogHandle.kick×   sx   € ð	Ü˜œU 7›^Ó,ˆEð ×Ññ 	GÜ!Ÿ^™^Ó-°·±Ñ>ÀÑFˆDŒN÷	Gð 	Gøô œ:Ð&ò 	ØŠEð	ú÷	Gð 	Gús   ‚A £*A-ÁA*Á)A*Á-A6c                ób  — 	 t        |«      }|dk  ryt        |t        «      }| j
                  5  t        | j                  t        j                  «       |z   «      | _        |rt        |«      | _        | xj                  dz  c_        ddd«       y# t        t        f$ r Y yw xY w# 1 sw Y   yxY w)u¦  Claim a progress lease: this phase expects up to ``expected_s`` more seconds of work.

        The authoritative "still making progress" signal â€” owned by the startup path,
        so it works for I/O-bound phases and cannot be counterfeited by unrelated
        threads. Clamped to ``_MAX_LEASE_S`` per call so one buggy caller cannot
        silence the watchdog indefinitely; long phases renew. Never raises.
        Nr   é   )r9   rˆ   r:   ÚminÚ_MAX_LEASE_Srv   r=   r|   ro   rp   rY   r}   r~   )r€   Ú
expected_sÚphaseÚexpecteds       r)   ÚleasezStartupWatchdogHandle.leaseä   s¦   € ð	Ü˜ZÓ(ˆHð �qŠ=ØÜ�x¤Ó.ˆØ×Ññ 	#Ü # D×$5Ñ$5´t·~±~Ó7GÈ(Ñ7RÓ SˆDÔÙÜ$'¨£J�Ô!Ø×Ò Ñ"Õ÷		#ð 	#øô œ:Ð&ò 	Ùð	ú÷
	#ð 	#ús   ‚B ¯AB%ÂB"Â!B"Â%B.c                ó(   — | j                   t        k(  S ©N)rs   rƒ   r…   s    r)   r   zStartupWatchdogHandle.disarmedù   s   € à�{‰{œiÑ'Ð'r+   c                óV   — | j                   d uxr | j                   j                  «       S r•   )rz   Úis_aliver…   s    r)   r—   zStartupWatchdogHandle.is_aliveý   s#   € Ø�|‰| 4Ð'ÒC¨D¯L©L×,AÑ,AÓ,CÐCr+   Nc                óV   — | j                   �| j                   j                  |¬«       y y )N©Útimeout)rz   Újoin)r€   rš   s     r)   r›   zStartupWatchdogHandle.join   s&   € Ø�<‰<Ð#Ø�L‰L×Ñ gÐÕ.ð $r+   c                 óJ   — 	 t        j                  «       S # t        $ r Y yw xY w)zBProcess-wide CPU time (user+system, all threads); None on failure.N)ro   Úprocess_timerL   r4   r+   r)   Ú_process_cpu_secondsz*StartupWatchdogHandle._process_cpu_seconds  s(   € ð	Ü×$Ñ$Ó&Ð&øÜò 	Ùð	ús   ‚ –	"¡"c                ó  — 	 t        j                  | j                  dd¬«      j                  «        t        j                  «       | j                  z
  }t        dd| j                  || j                  | j                  «       t        t        j                  t        j                   «      j#                  «       dt%        j&                  «       | j                  t)        |d«      | j                  | j*                  | j,                  | j                  dœ	«       t/        d	„ d
«       	 t1        j2                  d¬«       	 t        j                  t8        | j                  fdd¬«      }|j                  «        |j;                  t<        ¬«       | j>                  jA                  «        | jC                  | j                  «       y# t        $ r Y �Œ–w xY w# t        $ r t4        j7                  dd¬«       Y Œ·w xY w# t        $ r Y Œww xY w)u–  Forensics, then exit â€” with the exit itself independently bounded.

        Everything producing forensics (logging, dump record, faulthandler, ledger)
        can block: the wedged main thread may hold the logging handler lock, the disk
        may be hung. None of that may stop the respawn, so the escort thread starts
        FIRST. ``os._exit`` is async-signal-safe and lock-free by design.
        Tz$gateway-startup-watchdog-exit-escort©ÚtargetÚdaemonÚnameÚcriticalzøGateway startup did not reach a live event loop within %.0fs (elapsed %.0fs, %d extension(s)), holds no progress lease and shows no CPU progress; dumping all thread stacks and exiting with code %d so the service supervisor can restart it (OOF-298).zstartup_watchdog.firedr   )	ÚtsÚtagÚpidrn   Ú	elapsed_sÚ
extensionsÚlease_countÚlast_lease_phaserh   c                ó0   — t        j                  | d¬«      S )NT)ÚfileÚall_threads)ÚfaulthandlerÚdump_traceback)rQ   s    r)   r[   z-StartupWatchdogHandle._fire.<locals>.<lambda>6  s   € ”|×2Ñ2¸ÈÔM€ r+   z4Startup watchdog file-based faulthandler dump failed)r®   z)Startup watchdog faulthandler dump failedrG   zgateway-startup-watchdog-ledger)r¡   ra   r¢   r£   r™   N)"rt   ÚThreadÚ_exit_escortÚstartrL   ro   rp   rq   rb   rn   r{   rh   r\   r   Únowr   ÚutcÚ	isoformatr   ÚgetpidÚroundr~   r}   rR   r¯   r°   r;   rM   ri   r›   Ú_LEDGER_JOIN_TIMEOUT_Sr   r„   Ú_exit)r€   ÚelapsedÚledger_threads      r)   Ú_firezStartupWatchdogHandle._fire  s©  € ð	Ü×ÑØ×(Ñ(°Ð<bôç‰eŒgô —.‘.Ó" T§]¡]Ñ2ˆÜØðð
 �N‰N˜G T×%5Ñ%5°t·~±~ô	
ô 	ä—l‘l¤8§<¡<Ó0×:Ñ:Ó<Ø/Ü—y‘y“{Ø!Ÿ^™^Ü" 7¨AÓ.Ø"×.Ñ.Ø#×0Ñ0Ø$(×$5Ñ$5Ø!Ÿ^™^ñ
ô	
ô 	ÙMØBô	
ð	UÜ×'Ñ'°DÕ9ð	Ü%×,Ñ,Ü+°4·>±>Ð2CÈDØ6ôˆMð ×ÑÔ!Ø×ÑÔ'=ÐÔ>ð 	�‰×ÑÔØ�
‰
�4—>‘>Õ"øôa ò 	Úð	ûôB ò 	UÜ�L‰LÐDÈtˆLÖTð	Uûô ò 	Ùð	ús6   ‚0F< Ä!G Ä8AG2 Æ<	G	ÇG	Ç G/Ç.G/Ç2	G>Ç=G>c                óš   — | j                  t        «       | j                  j                  «       ry| j	                  | j
                  «       y)z÷Hard-exit if the forensic fire path wedges (bounded-exit seam).

        Deliberately free of log handlers, filesystem access, module loads and any
        lock shared with application code: only a sleep, an Event check and the exit seam.
        N)Ú_sleepÚ_FIRE_EXIT_BOUND_Sr   Úis_setrº   rh   r…   s    r)   r²   z"StartupWatchdogHandle._exit_escortL  s4   € ð 	�‰Ô&Ô'Ø�?‰?×!Ñ!Ô#ØØ�
‰
�4—>‘>Õ"r+   c                ó.   — t        j                  | «       y)z4Seam for tests; production is a bare ``time.sleep``.N)ro   Úsleep)Úsecondss    r)   r¿   zStartupWatchdogHandle._sleepW  s   € ô 	�
‰
�7Õr+   c                ó.   — t        j                  | «       y)z2Seam for tests; production is a bare ``os._exit``.N)r   rº   )Úcodes    r)   rº   zStartupWatchdogHandle._exit\  s   € ô 	�‰��r+   c                ó�   — | j                   5  | j                  t        k7  r
	 ddd«       y|| _        	 ddd«       y# 1 sw Y   yxY w)zDSet a new deadline under the state lock; False when no longer armed.NFT)rv   rs   rr   rw   )r€   Údeadlines     r)   Ú_extend_if_armedz&StartupWatchdogHandle._extend_if_armeda  sE   € à×Ññ 	Ø�{‰{œfÒ$Ø÷	ð 	ð &ˆDŒNØ÷		÷ 	ñ 	ús   �<«<¼Ac           
     óÌ  — | j                  «       }	 | j                  5  | j                  t        k7  r
	 d d d «       y | j                  }d d d «       t        j                  «       z
  }|dkD  r,| j                  j                  t        |t        «      ¬«      ry Œ‡t        j                  «       }| j                  5  | j                  }| j                  }d d d «       |kD  ri| j                  t        ||t        t        | j                  «      z   «      «      sy t!        dd| j                  xs d||z
  «       | j                  «       }�Œ6| j                  «       }|�|�||z
  nd }|�Ž|t"        k\  r…| j$                  t&        k  rr|}| xj$                  dz  c_        | j                  t        j                  «       | j                  z   «      sy t!        dd| j                  || j$                  t&        «       �Œá| j                  5  | j                  t        k7  r
	 d d d «       y t(        | _        d d d «       | j+                  «        y # 1 sw Y   �ŒúxY w# 1 sw Y   �Œ~xY w# 1 sw Y   Œ4xY w)Nr   r™   r<   ue   Gateway startup exceeded %.0fs but phase %r holds a progress lease for another %.0fs â€” honoring it.Úunknownr�   uå   Gateway startup exceeded %.0fs but is consuming CPU (%.1fs this window); extending the startup watchdog deadline (CPU-fallback extension %d of %d â€” phases doing long legitimate work should call report_startup_progress instead).)rž   rv   rs   rr   rw   ro   rp   ry   ÚwaitrŽ   Ú_POLL_SLICE_Sr|   r}   rÉ   r=   rn   rb   Ú_CPU_PROGRESS_MIN_Sr{   Ú_MAX_CPU_EXTENSIONSÚ_FIRINGr½   )	r€   Úlast_cpurÈ   Ú	remainingr´   Úlease_untilÚlease_phaseÚcpuÚwindow_deltas	            r)   Ú_runzStartupWatchdogHandle._runi  s7  € Ø×,Ñ,Ó.ˆØØ×!Ñ!ñ *Ø—;‘;¤&Ò(Ø÷*ð *ð  Ÿ>™>�÷*ð !¤4§>¡>Ó#3Ñ3ˆIØ˜1Š}Ø×'Ñ'×,Ñ,´S¸ÄMÓ5RÐ,ÔSØØô —.‘.Ó"ˆCØ×!Ñ!ñ 0Ø"×/Ñ/�Ø"×/Ñ/�÷0ð ˜SÒ Ø×,Ñ,¬S°¸cÄCÌÐW[×WeÑWeÓDfÑ>fÓ-gÔhØÜØðHà—N‘N KÒ$<°9¸kÈCÑ>Oô	ð  ×4Ñ4Ó6�ÙØ×+Ñ+Ó-ˆCØ-0¨_ÀÐAU˜3 š>Ð[_ˆLØÐ'¨LÔ<OÒ,OÐTX×TdÑTdÔgzÒTzØ�Ø× Ò  AÑ%Õ Ø×,Ñ,¬T¯^©^Ó-=ÀÇÁÑ-NÔOØÜØð8ð
 —N‘N L°$×2BÑ2BÔDWôñ ð ×!Ñ!ñ &Ø—;‘;¤&Ò(Ø÷&ð &ô &�”÷&ð �J‰JŒLØ÷k*ñ *ú÷0ñ 0ú÷F&ð &ús.   žI ¼I Â8IÇ>IÈIÉ I
ÉIÉI#c                óÄ   — t        j                  | j                  dd¬«      }	 |j                  «        || _        y# t        $ r t
        j                  dd¬«       Y yw xY w)NTzgateway-startup-watchdogr    z(Failed to start gateway startup watchdogrG   F)rt   r±   r×   r³   rL   r;   rM   rz   )r€   Úthreads     r)   Ú_startzStartupWatchdogHandle._start£  sZ   € Ü×!Ñ!¨¯©¸4ÐF`Ôaˆð	Ø�L‰LŒNð ˆŒØøô	 ò 	Ü�L‰LÐCÈdˆLÔSÙð	ús   ¤< ¼ AÁA)rn   r9   rh   Úint©ÚreturnÚNone©rm   ©r‰   r9   rÝ   rÞ   ©r   ©r�   r9   r‘   rY   rÝ   rÞ   ©rÝ   Úboolr•   )rš   úOptional[float]rÝ   rÞ   )rÝ   rå   )rÄ   r9   rÝ   rÞ   )rÆ   rÛ   rÝ   rÞ   )rÈ   r9   rÝ   rä   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r�   r†   r‹   r“   Úpropertyr   r—   r›   Ústaticmethodrž   r½   r²   r¿   rº   rÉ   r×   rÚ   r4   r+   r)   rk   rk   ·   s�   „ ÙFó,ó$	#ôGô#ð* ò(ó ð(óDô/ð òó ðó<#ó|	#ð òó ðð òó ðóó8ôtr+   rk   )rh   c               óŽ  — 	 t        «       ryt        5  t        �#t        j                  «       rt        cddd«       S | �t	        | «      dkD  rt	        | «      n	t        «       }t        ||«      }|j                  «       s
	 ddd«       y|a|cddd«       S # 1 sw Y   yxY w# t        $ r t        j                  dd¬«       Y yw xY w)zßArm the process-wide startup watchdog. Idempotent; never raises.

    Returns the (possibly pre-existing) handle, or ``None`` when disabled via
    ``HERMES_STARTUP_WATCHDOG=0`` or when the thread could not be started.
    Nr   z&Failed to arm gateway startup watchdogTrG   )r5   Ú_handle_lockr   r—   r9   rA   rk   rÚ   rL   r;   rM   )rn   rh   ÚresolvedÚhandles       r)   Úarm_startup_watchdogrð   ®  sÁ   € ðÜ$Ô&ØÜñ 	ÜÐ"¤w×'7Ñ'7Ô'9Ü÷	ñ 	ð
 Ð(¬U°9Ó-=ÀÒ-Aô �iÔ ä5Ó7ð ô
 +¨8°YÓ?ˆFØ—=‘=”?Ø÷	ð 	ð ˆGØ÷	÷ 	ò 	ûô ò Ü�‰Ð=ÈˆÔMÙðúsJ   ‚
B! �B! “ B³	B! ½ABÁ?B! ÂBÂ	B! ÂBÂB! ÂB! Â! CÃCc                 ó¼   — 	 t         5  t        } da| �| j                  «        ddd«       y# 1 sw Y   yxY w# t        $ r t        j                  dd¬«       Y yw xY w)u  Disarm the process-wide startup watchdog, if armed. Never raises.

    ``disarm()`` runs while still holding the singleton lock â€” it is non-blocking,
    and this closes the window where a concurrent re-arm could swap in a new
    handle that the disarm then misses.
    Nz)Failed to disarm gateway startup watchdogTrG   )rí   r   r†   rL   r;   rM   ©rï   s    r)   Údisarm_startup_watchdogró   Î  s\   € ðQÜñ 	 ÜˆFØˆGØÐ!Ø—‘”÷		 ÷ 	 ñ 	 ûô
 ò QÜ�‰Ð@È4ˆÖPðQús#   ‚8 ˆ,£8 ¬5±8 µ8 ¸ AÁAc                ó¸   — 	 t         5  t        }ddd«       � t        || «      |Ž  yy# 1 sw Y   ŒxY w# t        $ r t        j                  |d¬«       Y yw xY w)zVCall ``handle.<method>(*args)`` on the armed handle; no-op when unarmed, never raises.NTrG   )rí   r   r^   rL   r;   rM   )ÚmethodrO   ra   rï   s       r)   Ú_with_armed_handlerö   à  s`   € ð1Üñ 	ÜˆF÷	àÐØ#ŒG�F˜FÓ# TÒ*ð ÷	ð 	ûô ò 1Ü�‰�[¨4ˆÖ0ð1ús   ‚6 ˆ*�6 ª3¯6 ¶ AÁAc                ó   — t        dd| «       y)zñExtend the armed watchdog's deadline. No-op when not armed; never raises.

    Call before intentionally blocking with ~zero CPU activity (e.g. the
    respawn-storm backoff sleep) so the idle wait is not mistaken for a parked deadlock.
    r‹   z'Failed to kick gateway startup watchdogN©rö   )r‰   s    r)   Úkick_startup_watchdogrù   ë  s   € ô �vÐHÈ'ÕRr+   c                ó    — t        dd| |«       y)uà  Declare a phase-owned progress lease on the armed startup watchdog.

    Call from startup phases about to do legitimately long synchronous work â€” most
    importantly ``state.db`` schema migrations and corruption repair/backup inside
    ``SessionDB.__init__`` â€” with an honest worst case, renewing for multi-step phases.
    Per-call duration is clamped to ``_MAX_LEASE_S``. No-op when not armed; never
    raises â€” safe to call unconditionally from application code.
    r“   z!Failed to report startup progressNrø   )r�   r‘   s     r)   Úreport_startup_progressrû   ô  s   € ô �wÐ CÀZÐQVÕWr+   c                 óp   — t         5  t        } daddd«        �| j                  «        yy# 1 sw Y   ŒxY w)z0Drop the module singleton (test isolation only).N)rí   r   r†   rò   s    r)   Ú_reset_for_testsrý      s<   € ô 
ñ ÜˆØˆ÷ð ÐØ�‰�ð ÷ð ús   ‡	,¬5)rÝ   r   r•   )r%   zOptional[Path]rÝ   r   rã   )rÝ   r9   )rO   rY   rÝ   rÞ   )rZ   zDict[str, Any]rÝ   rÞ   )r_   rY   r`   rY   rÝ   rÞ   )rh   rÛ   rÝ   rÞ   )rn   rå   rh   rÛ   rÝ   zOptional[StartupWatchdogHandle]rÜ   )rõ   rY   rO   rY   rÝ   rÞ   rß   rà   rá   râ   )9ré   Ú
__future__r   r¯   rW   Úloggingr   r#   rt   ro   r   r   Úpathlibr   Útypingr   r   r	   Ú	getLoggerræ   r;   r8   r>   ÚSERVICE_RESTART_EXIT_CODEr1   r7   r.   Ú	frozensetr3   rÍ   rÎ   rÏ   r�   r¹   rÀ   rr   rƒ   rÐ   ru   rí   r   Ú__annotations__r*   r/   r5   rA   rR   r\   rb   ri   rk   rð   ró   rö   rù   rû   rý   r4   r+   r)   ú<module>r     sN  ðòõ8 #ã Û Û Û 	Û 
Û Û ß 'Ý ß &Ñ &à	ˆ×	Ñ	˜8Ó	$€à%*Ð "Ø€ð Ð à0Ð Ø!DÐ à9€á
Ò/Ó
0€ð €ð
 Ð ð
 Ð ð
 €ð Ð ð Ð ð
 
€Ø€	Ø
€ð
 ˆy�~‰~Ó€Ø-1€Ð	*Ó 1ó	#ô*óOó
&ó$1óóó÷tñ tðp "&ðð /ñØðð ðð %ó	ó@Qó$1ôSô	Xôr+   