+
    jHàj.T  ã                  óæ  € R t ^ RIH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 ]P&                  ! ]4      tR R ltR	 R
 ltR:R R l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R R lt R:R R llt!Rt"R t#R!t$R"t%R#t&R$R/R% R& llt'R:R'R/R( R) lllt(R* R+ lt)R,R-/R. R/ llt*R:R,R0/R1 R2 lllt+R3RR']&/R4 R5 llt,R;R6 R7 llt-R8 R9 lt.R# )<uL  Gateway lifecycle ledger â€” durable termination-reason evidence.

Graceful shutdowns leave forensics (``gateway-exit-diag.log``); an unclean
death (SIGKILL, kernel OOM, VM death) runs no handler.  A sentinel at
``<HERMES_HOME>/state/gateway.lifecycle.json`` closes the gap:
:func:`record_startup` finds ``phase == "running"`` from the previous life â†’
unclean death, appended to the exit-diag log as ``gateway.previous_unclean_exit``
and logged at WARNING; :func:`mark_exited` rewrites ``phase=exited`` on every
clean exit path.  Best-effort: forensics must never affect the lifecycle.
)ÚannotationsN)Úclosing)ÚdatetimeÚtimezoneÚUTC)ÚPath)ÚAnyÚDictÚOptionalc               ó   € V ^8„  d   QhRR/# )é   Úreturnr   © )Úformats   "Ú=/home/ubuntu/.hermes/hermes-agent/gateway/lifecycle_ledger.pyÚ__annotate__r      s   € ÷ iñ i˜dñ ió    c                 ó–   € ^ RI Hp Hp \        P                  P                  RR4      P                  4       '       d   V! 4       # V ! 4       # )zEHERMES_HOME for process-level identity files (ignore task overrides).©Úget_hermes_homeÚget_process_hermes_homeÚHERMES_HOMEÚ )Úhermes_constantsr   r   ÚosÚenvironÚgetÚstripr   s     r   Ú_process_hermes_homer      s7   € çIô )+¯
©
¯©°}ÀbÓ(I×(OÑ(O×(QÒ(QÑ"Ó$ÐhÑWfÓWhÐhr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚhomeúOptional[Path]ÚrelativeÚstrr   r   r   )r   s   "r   r   r   #   s&   € ÷ Rñ R�^ð R°ð R¸ñ Rr   c                ó>   € V f   \        4       MT P                  ! V!  # ©N)r   Újoinpath)r    r"   s   &*r   Ú
_home_pathr'   #   s   € Ø&*¢lÔ Ô"¸×FÒFÈÑQÐQr   c               ó    € V ^8„  d   QhRRRR/# )r   r    r!   r   r   r   )r   s   "r   r   r   '   s   € ÷ ?ñ ? nð ?Àñ ?r   c                ó   € \        V RR4      # )z6Return ``<HERMES_HOME>/state/gateway.lifecycle.json``.Ústatezgateway.lifecycle.json)r'   ©r    s   &r   Úget_lifecycle_sentinel_pathr,   '   s   € ä�d˜GÐ%=Ó>Ð>r   c               ó   € V ^8„  d   QhRR/# )r   r   r#   r   )r   s   "r   r   r   ,   s   € ÷ )ñ )�#ñ )r   c                 óR   € \         P                  ! \        4      P                  4       # r%   )r   Únowr   Ú	isoformatr   r   r   Ú_now_isor1   ,   s   € Ü�<Š<œÓ×&Ñ&Ó(Ð(r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úpathr#   Úwantedzdict[str, str]r   zdict[str, int]r   )r   s   "r   r   r   0   s!   € ÷ ñ �sð  Nð °~ñ r   c                óˆ  € / p \        V RR7      ;_uu_ 4       pV Fh  pVP                  R^4      ^ ,          pWQ9   g   K$  \        VP                  4       ^,          4      W!V,          &   \        V4      \        V4      8X  g   Kh   M	  RRR4       V#   + '       g   i     T# ; i  \        \
        \        3 d    / u # i ; i)zN``{dst: int}`` for each ``src: dst`` key found in a ``Key: value`` /proc file.ú	utf-8-sig©ÚencodingÚ:N)ÚopenÚsplitÚintÚlenÚOSErrorÚ
ValueErrorÚ
IndexError)r3   r4   ÚfoundÚfhÚlineÚkeys   &&    r   Ú_proc_fieldsrE   0   s¥   € à€Eð	Ü�$ ×-Õ-°Û�Ø—j‘j  aÓ(¨Õ+�Ø–=Ü),¨T¯Z©Z«\¸!­_Ó)=�E �+Ñ&Ü˜5“z¤S¨£[Ö0Ùñ ÷ .ð €L÷ .Ö-ð €Lûô ”Z¤Ð,ô ØŠ	ðús:   „B& ™"BÁ ?BÂBÂB& ÂB#	ÂB& Â#B& Â&CÃ Cc               ó   € V ^8„  d   QhRR/# )r   r   údict[str, Any]r   )r   s   "r   r   r   @   s   € ÷ 
ñ 
�~ñ 
r   c                 óØ   € \        RRR/4      p \        RRRRRR	R	R
R
/4      pVP                  R	R4      VP                  R
R4      r2V P                  V4       Ve   Ve   W#,
          V R&   V # )z²Cheap /proc snapshot (KiB): own RSS + MemTotal/MemAvailable + swap used.  Linux-only
(``{}`` elsewhere), never raises; the 30s heartbeat embeds it so OOM cycles are classifiable.z/proc/self/statusÚVmRSSÚrss_kibz/proc/meminfoÚMemTotalÚmem_total_kibÚMemAvailableÚmem_available_kibÚ	SwapTotalÚSwapFreeNÚswap_used_kib)rE   ÚpopÚupdate)ÚsampleÚmemÚ
swap_totalÚ	swap_frees       r   Úsample_memoryrX   @   s�   € ô Ð-°¸Ð/CÓD€FÜ
�¨°_ÀnÐViØ)4°kÀ:Èzð)[ó \€CàŸG™G K°Ó6¸¿¹À
ÈDÓ8Q�	Ø
‡M�M�#ÔØÒ )Ò"7Ø",Õ"8ˆˆÑØ€Mr   c               ó    € V ^8„  d   QhRRRR/# )r   r3   r   r   úOptional[dict[str, Any]]r   )r   s   "r   r   r   M   s   € ÷ 4ñ 4�Tð 4Ð6ñ 4r   c                ó´   €  \         P                  ! V P                  R R7      4      p\        T\        4      '       d   T# R#   \        \        3 d     R# i ; i)r6   r7   N)ÚjsonÚloadsÚ	read_textr>   r?   Ú
isinstanceÚdict)r3   Údatas   & r   Ú
_read_jsonrb   M   sR   € ðÜ�zŠz˜$Ÿ.™.°+˜.Ó>Ó?ˆô ˜d¤D×)Ò)ˆ4Ð3¨tÐ3øô ”ZÐ ô Úðús   ‚&A ÁAÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚpayloadrG   r    r!   r   ÚNoner   )r   s   "r   r   r   U   s&   € ÷ 
Jñ 
J˜^ð 
J°>ð 
JÀdñ 
Jr   c                óÀ   €  ^ RI Hp \        V4      p^ RIHp V! VP
                  4       V! W0RR7       R#   \         d    \        P                  RRR7        R# i ; i)é    )Úatomic_json_write©Úmkdir_under_hermes_homeN)Úindentz"Failed to write lifecycle sentinelT©Úexc_info)	Úutilsrh   r,   r   rj   ÚparentÚ	ExceptionÚloggerÚdebug)rd   r    rh   r3   rj   s   &&   r   Ú_write_sentinelrs   U   sP   € ð	JÝ+ä*¨4Ó0ˆÝ<á §¡Ô,Ù˜$°×5øÜô JÜ�‰Ð9ÀDˆ×IðJús   ‚37 ·"AÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚrecordrG   r    r!   r   re   r   )r   s   "r   r   r   b   s&   € ÷ 
Lñ 
L˜nð 
L°Nð 
LÀtñ 
Lr   c           	     óx  €  \        VRR4      p^ RIHp V! VP                  4       VP	                  RRR7      ;_uu_ 4       pVP                  \        P                  ! V \        R7      R,           4       R	R	R	4       R	#   + '       g   i     R	# ; i  \         d    \        P                  R
RR7        R	# i ; i)zVAppend a JSON line to gateway-exit-diag.log (same format as the CLI's ``_exit_diag``).Úlogszgateway-exit-diag.logri   Úazutf-8r7   )ÚdefaultÚ
Nz$Failed to append unclean-exit recordTrl   )r'   r   rj   ro   r:   Úwriter\   Údumpsr#   r>   rq   rr   )ru   r    r3   rj   rB   s   &&   r   Ú_append_exit_diagr}   b   s‡   € ðLÜ˜$ Ð(?Ó@ˆÝ<á §¡Ô,Ø�Y‰Y�s WˆY×-Ô-°Ø�H‰H”T—Z’Z ´Ô4°tÕ;Ô<÷ .×-×-Ò-ûäô LÜ�‰Ð;Àdˆ×KðLús0   ‚A B Á3A?Á5B Á?B	Â
B ÂB Â"B9Â8B9c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Úpidr   Ú
start_timeÚcreate_timer   Úboolr   )r   s   "r   r   r   o   s(   € ÷ ñ  ð °ð À3ð È4ñ r   c                ót  €  \        V 4      p^ RIHp V^ 8:  g   V! V4      '       g   R#  ^ RIHp T! T4      pTf   R# \        T4      \         \        39   d   \        T\        T4      ,
          4      R8*  # \        T4      \         \        39   d   T\        T4      R,           8*  # R#   \         d     R# i ; i)uð  True when ``pid`` is a live process that is the sentinel's incarnation â€” guards the
``--replace`` race: a live matching owner mid-teardown is a handover, not a death.

Identity is the psutil ``create_time`` the sentinel stamps at claim (epoch seconds, same
producer on both sides). The sentinel's ``start_time`` is the ledger claim time and is NOT
comparable with ``gateway.status.get_process_start_time`` (proc ticks on Linux, centiseconds
elsewhere) â€” the old comparison never matched, so every ``--replace`` handover read as an
unclean death. A pre-stamp sentinel (no ``create_time``) still has ``start_time`` in epoch
seconds: the owner was born BEFORE it claimed, a PID reuser AFTER the owner died, so a birth
later than the claim is a reuser.)Ú_pid_existsF©Ú_process_create_timeTg       @)	r<   Úgateway.statusr„   rp   Úhermes_cli.process_identityr†   ÚtypeÚfloatÚabs)r   r€   r�   Úpid_intr„   r†   Úactuals   &&&    r   Ú_pid_is_sentinel_ownerrŽ   o   s¬   € ðÜ�c“(ˆå.à�aŒ<™{¨7×3Ò3Ùð  4õ Aá! 'Ó*€FØ‚~ÙÜˆKÓœS¤%˜LÔ(Ü�6œE +Ó.Õ.Ó/°3Ñ6Ð6ÜˆJÓœC¤˜<Ô'Øœ˜zÓ*¨SÕ0Ñ0Ð0Ùøô ô Úðús   ‚%B( Â(B7Â6B7c               ó    € V ^8„  d   QhRRRR/# )r   rU   rG   r   r‚   r   )r   s   "r   r   r   �   s   € ÷ 
ñ 
˜ð 
¨4ñ 
r   c                óü   € ^ RI HpHp V P                  R4      V P                  R4      rC\	        V\
        4      '       g   R# WB8  ;'       g1    \	        V\
        4      ;'       d    V^ 8„  ;'       d    WC,          V8  # )z—Heuristic only (classification stays with the reader); thresholds are
memory_status' "critical" tier so a live warning and a post-mortem verdict agree.)Ú_CRITICAL_AVAILABLE_FRACTIONÚ_CRITICAL_AVAILABLE_KIBrL   rN   F)Úgateway.memory_statusr‘   r’   r   r_   r<   )rU   r‘   r’   ÚtotalÚavails   &    r   Ú_suspected_oomr–   �   sm   € ÷ \à—7‘7˜?Ó+¨S¯W©WÐ5HÓ-Iˆ5Ü�eœS×!Ò!ÙØÑ*÷ ð Ü�5œ#Ó×]Ð] 5¨1¡9×]Ð]°µÐA]Ñ1]ðr   c               ó    € V ^8„  d   QhRRRR/# )r   r    r!   r   rZ   r   )r   s   "r   r   r   œ   s   € ÷ ñ ˜nð Ð8Pñ r   c                ó€  € \        \        V 4      4      pV'       d   VP                  R4      R8w  d   R# \        VP                  R4      VP                  R4      VP                  R4      4      '       d   R# RVP                  R4      RVP                  R	4      R
VP                  R4      /p ^ RIHp \        V! V 4      4      ;'       g    / pV'       d   VP                  R4      VR&   VP                  R4      p\        V\        4      '       d   WRR&   \        V4      '       d   RVR&   V#   \         d    / p Lni ; i)zNEvidence dict when the previous life died uncleanly, else ``None``. Read-only.ÚphaseÚrunningNr   r€   r�   Ú	prior_pidÚprior_started_atÚ
started_atÚprior_start_time)Úget_loop_heartbeat_pathÚ
updated_atÚlast_heartbeat_atrU   Úlast_heartbeat_memTÚsuspected_oom)
rb   r,   r   rŽ   Úgateway.shutdown_watchdogrŸ   rp   r_   r`   r–   )r    ÚsentinelÚevidencerŸ   ÚhbrU   s   &     r   Údetect_unclean_exitr¨   œ   s  € äÔ5°dÓ;Ó<€Hß�x—|‘| GÓ,°	Ô9ÙÜ˜hŸl™l¨5Ó1°8·<±<ÀÓ3MÈxÏ|É|Ð\iÓOj×kÒkÙà�X—\‘\ %Ó(Ð*<¸h¿l¹lÈ<Ó>XØ˜HŸL™L¨Ó6ð €Hð
ÝEäÑ/°Ó5Ó6×<Ð<¸"ˆ÷ 
Ø(*¯©¨|Ó(<ˆÐ$Ñ%Ø
�&‰&�‹-€CÜ�#”t×ÒØ),Ð%Ñ&Ü˜#×ÒØ(,ˆH�_Ñ%Ø€Oøô ô ØŠðús   Â-D- ÃD- Ä-D=Ä<D=Ú state_db_unclean_integrity_checkg      Œ@i † g      N@g      œ@Údeadline_atc               ó$   € V ^8„  d   QhRRRRRR/# )r   Úconnzsqlite3.Connectionrª   úOptional[float]r   re   r   )r   s   "r   r   r   Ì   s(   € ÷ Kñ KÐ);ð KÈ_ð KÐhlñ Kr   c               ó¨   aaa€ ^ RI Ho S! \        \        R7       \        P
                  ! 4       oR VVV3R llpV P                  V\        4       R# )a  Claim the integrity-check lease and renew it from a SQLite progress handler.

``report_startup_progress`` is a no-op when no watchdog is armed and never raises.
Without ``deadline_at`` the handler always returns 0 -- it must never abort the verdict
PRAGMA.  With ``deadline_at`` (a ``time.monotonic`` timestamp) it returns non-zero once
that instant is reached, so SQLite raises ``OperationalError: interrupted`` and the
caller can bound the scan.  Synchronous by design: no checker worker thread can outlive
the check.
)Úreport_startup_progress©r™   c               ó   € V ^8„  d   QhRR/# )r   r   r<   r   )r   s   "r   r   Ú4_install_integrity_check_lease.<locals>.__annotate__Û   s   € ÷ ñ ˜#ñ r   c                 ó˜   <€ \         P                  ! 4       p V S,
          \        8¼  d   V oS! \        \        R 7       Se
   V S8¼  d   ^# ^ # )r°   )ÚtimeÚ	monotonicÚ_INTEGRITY_CHECK_LEASE_RENEW_SÚ_INTEGRITY_CHECK_LEASE_SÚ_INTEGRITY_CHECK_LEASE_PHASE)r/   rª   Ú
last_renewr¯   s    €€€r   Ú_on_progressÚ4_install_integrity_check_lease.<locals>._on_progressÛ   sA   ø€ ä�nŠnÓˆØ�ÕÔ=Ô=ØˆJÙ#Ô$<ÔD`ÕaØÒ" s¨kÔ'9ÙÙr   N)Úhermes_startup_watchdogr¯   r·   r¸   r´   rµ   Úset_progress_handlerÚ_INTEGRITY_CHECK_PROGRESS_OPS)r¬   rª   rº   r¹   r¯   s   &d @@r   Ú_install_integrity_check_leaser¿   Ì   s>   ú€ õ @áÔ4Ô<XÕYÜ—’Ó!€J÷ò ð 	×Ñ˜lÔ,IÖJr   Ú
deadline_sc               ó$   € V ^8„  d   QhRRRRRR/# )r   r    r!   rÀ   r­   r   r#   r   )r   s   "r   r   r   è   s'   € ÷ #Sñ #S >ð #SÈð #SÐdgñ #Sr   c               ó2  € \        V R4      pVP                  4       '       g   R# RpVe1    \        P                  ! 4       \	        R\        V4      4      ,           p \        \        P                  ! \        V4      4      4      ;_uu_ 4       p\        WCR7       VP                  R4      P                  4       pRRR4       X'       d   T^ ,          f   R# \        T^ ,          4      #   \        \        3 d    Rp L¡i ; i  + '       g   i     LP; i  \         dy   pTef   \!        T\        P"                  4      '       dF   R\        T4      P%                  4       9   d(   Te   \	        R\        T4      4      MRpRTR	 R
2u Rp?# RT 2u Rp?# Rp?ii ; i)uI  ``"ok"``, ``"absent"``, or the first ``quick_check`` complaint.  Never raises.

Only after an unclean death â€” SIGKILL mid-WAL-checkpoint can leave half-written
b-tree pages.  ``quick_check(1)`` stops at the first problem (~2s on a healthy
500MB store): cheap once per unclean boot, too costly every boot.  Opened
normally: a WAL store needs its -shm sidecar for read-only, and the PRAGMA writes nothing.

Holds a phase-owned startup-watchdog lease for the check duration
(renewed from a SQLite progress handler, #115542) so a long healthy check
on a huge store is not mistaken for a parked deadlock.

``deadline_s`` (optional): abort the scan once that many seconds elapse and
return ``"check-incomplete: exceeded <N>s"``.  ``deadline_s=None`` (default) is the
historic unbounded behaviour -- the progress handler never aborts the PRAGMA.
ústate.dbÚabsentNg        )rª   zPRAGMA quick_check(1)Ú	interruptzcheck-incomplete: exceeded ÚgÚszcheck-failed: zcheck-failed: no result)r'   Úexistsr´   rµ   ÚmaxrŠ   Ú	TypeErrorr?   r   Úsqlite3Úconnectr#   r¿   ÚexecuteÚfetchonerp   r_   ÚOperationalErrorÚlower)r    rÀ   r3   rª   r¬   ÚrowÚexcÚlimits   &$      r   Úcheck_state_db_integrityrÔ   è   sE  € ô  �d˜JÓ'€DØ�;‰;�=Š=ÙØ#'€KØÒð	ÜŸ.š.Ó*¬S°´e¸JÓ6GÓ-HÕHˆKð	&Ü”W—_’_¤S¨£YÓ/×0Ô0°DÜ*¨4ÕIØ—,‘,Ð6Ó7×@Ñ@ÓBˆC÷ 1÷ -0°3°qµ6²>Ð$ÐRÄsÈ3ÈqÍ6Ã{ÐRøô œ:Ð&ô 	ØŠKð	ú÷ 1×0ûô ô &ØÒ#¬
°3¼×8PÑ8P×(QÒ(QØ¤3 s£8§>¡>Ó#3Ô3Ø3=Ò3I”C˜œU :Ó.Ô/ÈsˆEØ0°°q°	¸Ð;Õ;Ø ˜uÐ%Õ%ûð&úsZ   ¬/C' Á0D Â,D Â8D Ã'C=Ã<C=Ä D	ÄD ÄD ÄFÄA#FÆFÆFÆFÆFc               ó    € V ^8„  d   QhRRRR/# )r   r3   r   r   zOptional[str]r   )r   s   "r   r   r     s   € ÷ 
ñ 
 ð 
¨-ñ 
r   c                ó(  €  ^ RI Hp \        \        P                  ! \        V 4      RR7      4      ;_uu_ 4       pV! V4      ;'       g    RuuRRR4       #   + '       g   i     R# ; i  \         d    \        P                  RRR7        R# i ; i)z¥On-disk journal mode of ``path`` via one ``PRAGMA journal_mode`` read on a short-lived
connection (no lock outlives it); ``None`` when undeterminable.  Never raises.)Ú_on_disk_journal_modeg      ð?)ÚtimeoutNz$Could not read state.db journal modeTrl   )	Úhermes_state_walr×   r   rË   rÌ   r#   rp   rq   rr   )r3   r×   r¬   s   &  r   Ú_state_db_journal_moderÚ     sg   € ðÝ:ä”W—_’_¤S¨£Y¸Ô<×=Ô=ÀÙ(¨Ó.×6Ð6°$÷ >×=×=Ó=ûäô Ü�‰Ð;ÀdˆÔKÚðús.   ‚8A+ ºAÁ
A+ ÁA(	Á"A+ Á(A+ Á+"BÂBÚdefer_integrity_checkFc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r¦   rG   r    r!   rÛ   r‚   r   re   r   )r   s   "r   r   r     s*   € ÷ ñ  >ð ¸ð Ðcgð Ðtxñ r   c               ó¦  € V'       d   RV R&   M-\        VR7      ;q0R&   VR9  d   \        P                  RV4       \        R\	        4       RRR\
        P                  ! 4       /V CV4       \        P                  R	V P                  R
4      V P                  R4      V P                  R4      V P                  R4      V P                  RR4      4       R# )a  Persist the exit-diag record and log at WARNING.

When ``defer_integrity_check`` is set, the forensic store scan is NOT run here (it would
gate platform startup); the record carries ``state_db_integrity: "deferred"`` and
:func:`run_deferred_integrity_check` runs the scan post-connect.
ÚdeferredÚstate_db_integrityr+   õ�   state.db FAILED integrity check after an unclean gateway exit: %s â€” sessions may read as missing until it is repaired. Run `hermes doctor`.ÚtsÚtagzgateway.previous_unclean_exitr   u$  Previous gateway life (pid=%s, started_at=%s) exited UNCLEANLY (no exit path ran â€” SIGKILL / OOM / VM death, or a process kill issued by the agent or one of its descendants, e.g. a pkill/taskkill of the host interpreter image; see #113667). last_heartbeat_at=%s last_mem=%s suspected_oom=%sr›   rœ   r¡   r¢   r£   FN©ÚokrÄ   )	rÔ   rq   Úerrorr}   r1   r   ÚgetpidÚwarningr   )r¦   r    rÛ   Úverdicts   &&$ r   Ú_report_unclean_exitré     sÇ   € ÷ Ø)3ˆÐ%Ò&ô 4LÐQUÔ3VÐVˆÐ/Ñ0ØÐ*Ô*Ü�L‰LðEàôô
 �tœX›Z¨Ð0OÐQVÔXZ×XaÒXaÓXcÐpÐgoÐpÐrvÔwÜ
‡N�Nð	fð 	�‰�[Ó! 8§<¡<Ð0BÓ#CÀXÇ\Á\ÐReÓEfØ�‰Ð)Ó*¨H¯L©L¸È%Ó,Pör   Tc               ó$   € V ^8„  d   QhRRRRRR/# )r   r    r!   rÛ   r‚   r   rZ   r   )r   s   "r   r   r   7  s"   € ÷ $ñ $˜ð $È$ð $ÐZrñ $r   c          	     ó
  € Rp \        V 4      pVe   \        W VR7        RRR\
        P                  ! 4       R	\        P                  ! 4       R
\        4       /p^ RI	H
p T! \
        P                  ! 4       4      pTe   YSR&   Te"   RTR&   TP                  R4      '       d   RTR&   \        Y04       T#   \         d    \        P	                  RRR7        L¸i ; i  \         d    \        P	                  RRR7        T# i ; i)a\  Boot entry point: report any unclean previous exit (evidence dict, also persisted
to ``gateway-exit-diag.log`` and logged at WARNING) then claim the sentinel.  Never raises.

``defer_integrity_check`` defaults to True: the state.db integrity scan is forensic and
must NOT gate platform startup (a large store's ``quick_check`` blocked the ``:8644`` fleet
peer-DM transport for minutes).  In deferred mode the exit-diag record is written
immediately with ``state_db_integrity: "deferred"`` and the scan runs post-connect via
:func:`run_deferred_integrity_check`.  Pass False for the historic INLINE scan.
N)rÛ   zUnclean-exit detection failedTrl   r™   rš   r   r€   r�   r…   r�   Úprior_unclean_exitr£   Úprior_suspected_oomz"Failed to claim lifecycle sentinel)r¨   ré   rp   rq   rr   r   ræ   r´   r1   rˆ   r†   r   rs   )r    rÛ   r¦   Úclaimr†   r�   s   &$    r   Úrecord_startuprï   7  s  € ð *.€HðEÜ& tÓ,ˆØÒÜ  ÐG\Õ]ðJØ!(¨)°U¼B¿IºI»KÈÔW[×W`ÒW`ÓWbÐdpÔrzÓr|Ð }ˆõ 	Eá*¬2¯9ª9«;Ó7ˆØÒ"Ø#.�-Ñ ð ÒØ*.ˆEÐ&Ñ'Ø�|‰|˜O×,Ò,Ø/3�Ð+Ñ,Ü˜Ô$ð €Oøô+ ô EÜ�‰Ð4¸tˆÖDðEûô& ô JÜ�‰Ð9ÀDˆÕIØ€OðJús)   „B4 ¡B C Â"C Â4"CÃCÃ"DÄDr    c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r¦   rZ   r    r!   rÀ   rŠ   r   r#   r   )r   s   "r   r   r   ^  s.   € ÷ 84ñ 84Ø&ð84Ø1?ð84àð84ð 	ñ84r   c               ó’  €  \        VR4      pVP                  4       '       d   \        V4      MRpVR8w  d3   RT;'       g    R R2p\        P	                  RY4;'       g    RV4       M\        WR7      pVR9  d.   VP                  R4      '       g   \        P                  R	V4       R
\        4       RRRRRV/p\        V \        4      '       d(   V P                  R4      e   V P                  R4      VR&   \        Wa4       \        V \        4      '       d   WPR&   V#   \         d    \        P                  RRR7        R# i ; i)uƒ  Run the deferred post-connect state.db scan and append a second diag record.

Companion to :func:`record_startup` with ``defer_integrity_check=True``: the forensic scan
is moved off the startup path (where, on a multi-GB store, it gated every platform adapter
â€” including the ``:8644`` fleet peer-DM transport â€” for minutes) into a daemon thread
started only after platforms connected.  ``deadline_s`` bounds a pathological store so the
thread cannot be pinned forever.

Appends ``{"tag": "gateway.state_db_integrity", "deferred": true, "state_db_integrity":
<verdict>, "prior_pid": ...}`` and logs the existing ERROR text on a bad verdict.  NEVER
raises -- a forensic scan must not disturb a running gateway (the alerting path is the
bounded per-poll readiness probe ``gateway/readiness.py::_probe_state_db``).

Only a WAL store is scanned: in rollback-journal mode (``database.journal_mode: delete``,
or a silent WAL fallback) the scan's SHARED lock would block every live commit for its
whole run, so the scan is skipped with a ``skipped: ...`` verdict and one WARNING.  The
on-disk mode decides, not config; an undeterminable mode is skipped too.
rÃ   Úwalz.skipped: rollback-journal store (journal_mode=Úunknownz), scan would block live writesz÷Previous gateway exit was unclean, but the state.db integrity check was skipped: %s is a rollback-journal store (journal_mode=%s) and a background scan would block live writes. To check it, stop the gateway and run: sqlite3 %s 'PRAGMA quick_check')r    rÀ   zskipped:rà   rá   râ   zgateway.state_db_integrityrÞ   Trß   r›   z(Deferred state.db integrity check failedrl   z"check-failed: deferred scan raisedrã   )r'   rÈ   rÚ   rq   rç   rÔ   Ú
startswithrå   r1   r_   r`   r   r}   rp   rr   )r¦   r    rÀ   r3   Úmoderè   ru   s   &$$    r   Úrun_deferred_integrity_checkrö   ^  s@  € ð,"4Ü˜$ 
Ó+ˆØ/3¯{©{¯}ª}Ô% dÔ+À%ˆØ�5Œ=ØGÈ×HYÐHYÐPYÐGZð [6ð 6ˆGä�N‰NðYð ×'Ð'˜i¨õ	ô /°DÔPˆGØÐ*Ô*°7×3EÑ3EÀj×3QÒ3QÜ�L‰LðEàôð ”(“*ØÐ/Ø˜Ø  'ð	"
ˆô �h¤×%Ò%¨(¯,©,°{Ó*CÒ*OØ"*§,¡,¨{Ó";ˆF�;ÑÜ˜&Ô'Ü�h¤×%Ò%Ø-4Ð)Ñ*ØˆøÜô 4Ü�‰Ð?È$ˆÔOÚ3ð4ús$   ‚?D  ÁD  Á!2D  ÂBD  Ä "EÅEc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   Ú	exit_codezOptional[int]Úreasonr#   r    r!   r   re   r   )r   s   "r   r   r   ™  s0   € ÷ Pñ P˜=ð P¸ð PÐZhð PÐtxñ Pr   c                ó   €  \        \        V4      4      pVe,   VP                  R4      \        P                  ! 4       8w  d   R# RRR\        P                  ! 4       RV RVR\        4       /pR F(  pVf   K	  VP                  V4      f   K  W5,          WE&   K*  	  \        WB4       R#   \         d    \        P                  RR	R
7        R# i ; i)u<  Mark the current life as cleanly exited.  Idempotent, never raises.

Only rewrites a sentinel provably owned by this process: during ``--replace``
the replacement claims it before the old process finishes teardown and must not
be clobbered.  ``pid=None`` / malformed sentinels have unknown ownership â†’ left alone.
Nr   r™   Úexitedrø   Úexit_reasonÚ	exited_atz(Failed to mark lifecycle sentinel exitedTrl   )r€   r�   )
rb   r,   r   r   ræ   r1   rs   rp   rq   rr   )rø   rù   r    r¥   rû   rD   s   &&&   r   Úmark_exitedrþ   ™  s´   € ðPÜÔ9¸$Ó?Ó@ˆØÒ H§L¡L°Ó$7¼2¿9º9»;Ô$FÙØ")¨8°U¼B¿IºI»KÈÐV_ÐanÐpvØ"-¬x«zð";ˆó 1ˆCØÔ#¨¯©°SÓ(9Ô(EØ&�m�“ñ 1ô 	˜Ö%øÜô PÜ�‰Ð?È$ˆ×OðPús$   ‚AB' Á-B' Á7B' ÂB' Â'"CÃCc               ó    € V ^8„  d   QhRRRR/# )r   Úprofile_homer   r   r#   r   )r   s   "r   r   r   °  s   € ÷ ñ ¨ð °ñ r   c                ó®   €  \        \        V 4      4      ;'       g    / P                  R4      pRRRR/P                  VR4      #   \         d     R# i ; i)z²``clean``/``unclean``/``unknown`` for the profile's last gateway life; annotates
``container-boot.log``.  ``running`` is unclean: the old PID namespace is gone at container boot.r™   rû   Úcleanrš   Úuncleanró   )rb   r,   r   rp   )r   r™   s   & r   Úread_prior_exit_labelr  °  sY   € ðÜÔ7¸ÓEÓF×LÐLÈ"×QÑQÐRYÓZˆØ˜' 9¨iÐ8×<Ñ<¸UÀIÓNÐNøÜô Úðús   ‚A �'A ÁAÁAr%   )NÚgraceful_shutdownN)/Ú__doc__Ú
__future__r   r\   Úloggingr   rË   r´   Ú
contextlibr   r   r   r   Úpathlibr   Útypingr   r	   r
   Ú	getLoggerÚ__name__rq   r   r'   r,   r1   rE   rX   rb   rs   r}   rŽ   r–   r¨   r¸   r·   r¾   r¶   ÚSTATE_DB_INTEGRITY_DEADLINE_Sr¿   rÔ   rÚ   ré   rï   rö   rþ   r  r   r   r   Ú<module>r     s  ðñ	õ #ã Û Û 	Û Û Ý ß ,Ñ ,Ý ß &Ñ &à	×	Ò	˜8Ó	$€õiõR÷?õ
)õõ 
õ4õ
Jõ
Lõõ@
÷ðL  BÐ Ø Ð ð !(Ð Ø!%Ð à &Ð ðKÐ_c÷ Kñ8#SÐ[_÷ #Sð #SõL
ðÐjo÷ ñ8$ÐQU÷ $ð $ðN84ØBFð84à5÷84÷vP÷.r   