Ë
    âß²jÏ\  ã                  ó  — 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m	Z	 ddl
mZmZmZmZmZmZ 	 ddlZd(d„Z ej(                  e«      Zej.                  dk(  Z eh d£«      Z ed	d
h«      Z eddh«      Z eh d£«      Z eddh«      Zd)d„Zd*d„Z d+d„Z!d,d„Z"d-d„Z#d.d„Z$dZ%dZ&dZ'd/d„Z(d0d„Z)d1d„Z*d2d„Z+d3d„Z,	 	 	 	 	 	 	 	 	 	 d4d„Z-d5d„Z.d6d „Z/d7d!„Z0dd"œ	 	 	 d7d#„Z1d$d%œd8d&„Z2	 	 	 	 	 	 d9d'„Z3y# e$ r dZY ŒÈw xY w):a  Process and descriptor authority for state.db structural maintenance.

This module owns the proof that no foreign process still holds the active or
an unlinked SQLite DB/WAL/SHM generation.  ``hermes_state`` supplies only the
SQLite connection factory needed by the final lock probe.
é    )ÚannotationsN)ÚPath)ÚCallableÚListÚOptionalÚSequenceÚSetÚTuplec                óV   — t        | «      j                  «       j                  «       dz   S )z»``file:`` URI for a ``mode=ro`` open. ``as_uri()`` percent-encodes ``?``/``#`` in the home
    path; a raw ``f"file:{path}?mode=ro"`` truncates there and opens the wrong (empty) database.z?mode=ro)r   ÚresolveÚas_uri)Údb_paths    ú9/home/ubuntu/.hermes/hermes-agent/hermes_state_holders.pyÚread_only_db_urir      s%   € ô �‹=× Ñ Ó"×)Ñ)Ó+¨jÑ8Ð8ó    Úwin32>   ú
hermes-acpúhermes-agentÚhermesÚacp_adapterzhermes_cli.mainzhermes_cli/main.pyzrun_agent.py>   ÚQÚWÚXz--check-hash-based-pycsz--jitc                ó  — 	 t        d| › d�d«      5 }|j                  «       }ddd«       sy|j                  dd«      j                  d«      }|d   d	k(  r|j	                  «        |xs dS # 1 sw Y   ŒKxY w# t
        $ r Y yw xY w)
z8Read /proc/<pid>/cmdline without losing argv boundaries.ú/proc/z/cmdlineÚrbNzutf-8Úreplaceú éÿÿÿÿÚ )ÚopenÚreadÚdecodeÚsplitÚpopÚOSError)ÚpidÚhandleÚrawÚargvs       r   Ú_read_proc_argvr+   *   sŽ   € ð
Ü�F˜3˜%˜xÐ(¨$Ó/ð 	 °6Ø—+‘+“-ˆC÷	 áØØ�z‰z˜' 9Ó-×3Ñ3°FÓ;ˆØ�‰8�rŠ>Ø�H‰HŒJØŠ|�tÐ÷	 ð 	 ûô ò Ùðús,   ‚A9 ’A-£
A9 ®>A9 Á-A6Á2A9 Á9	BÂBc                ób  — t        | «      }|€.t        �(	 t        j                  | «      j                  «       xs d}|rWdj                  dj                  t        j                  j                  |d   «      g|dd ¢«      j                  «       «      dd nd}d| › d|› d	�S # t        $ r d}Y Œqw xY w)
zf``PID 123 (hermes gateway run)`` for operator-facing holder lists; /proc argv first, psutil elsewhere.NÚ r   é   éP   zcommand line unavailablezPID z (ú))
r+   ÚpsutilÚProcessÚcmdlineÚ	ExceptionÚjoinÚosÚpathÚbasenamer$   )r'   r*   Úwhos      r   Údescribe_holder_pidr:   9   s³   € ä˜3Ó€DØ€|œÐ*ð	Ü—>‘> #Ó&×.Ñ.Ó0Ò8°DˆDñ VZˆ#�(‰(�3—8‘8œRŸW™W×-Ñ-¨d°1©gÓ6ÐB¸¸a¸b¸ÐBÓC×IÑIÓKÓ
LÈSÈbÑ
QÐ_y€CØ�#��b˜˜˜QÐÐøô ò 	ØŠDð	ús   •'B  Â B.Â-B.c                óú   — t         j                  j                  | «      j                  «       j	                  d«      }dD ]:  }|j                  |«      sŒ|t        |«      d  }| xs t        d„ |D «       «      c S  y)Nú.exe)ÚpythonÚpypyc              3  óJ   K  — | ]  }|j                  «       xs |d k(  –— Œ y­w)ú.N)Úisdigit)Ú.0Úchars     r   ú	<genexpr>z0_looks_like_python_executable.<locals>.<genexpr>J   s"   è ø€ Ò$VÀt T§\¡\£^Ò%B°t¸s±{Ó%BÑ$Vùs   ‚!#F)r6   r7   r8   ÚlowerÚremovesuffixÚ
startswithÚlenÚall)ÚprogramÚnameÚprefixÚsuffixs       r   Ú_looks_like_python_executablerN   E   st   € Ü�7‰7×Ñ˜GÓ$×*Ñ*Ó,×9Ñ9¸&ÓA€DØ$ò WˆØ�?‰?˜6Õ"Øœ#˜f›+˜-Ð(ˆFØ�:ÒV¤Ñ$VÈvÔ$VÓ!VÒVðWð r   c                ó8  — d}|t        | «      k  �r	| |   }|dk(  r|dz  }|t        | «      k  rd| |   fS dS |t        v r|dz  }ŒC|j                  d«      s|j                  d«      r|dz  }Œk|j                  d«      r|dz  }Œ‚|j                  d«      r�|dk7  r||dd }d	}d
}|t        |«      k  rZ||   }||dz   d }|dk(  ry|dk(  r"|rd|fS |dz  }|t        | «      k  rd| |   fS dS |t        v r| }n|dz  }|t        |«      k  rŒZ||rdndz  }�Œd|fS y)zCReturn the Python module or script selected by interpreter options.r.   z--ÚscriptNé   z--check-hash-based-pycs=z--jit=Ú-r   FÚcÚmÚmodule)rH   Ú"_PYTHON_LONG_OPTIONS_WITH_OPERANDSrG   Ú#_PYTHON_SHORT_OPTIONS_WITH_OPERANDS)r*   ÚindexÚargÚoptionsÚoption_indexÚconsumed_nextÚoptionÚattacheds           r   Ú_python_execution_targetr_   N   s}  € à€EØ
”#�d“)Ó
Ø�5‰kˆØ�$Š;Ø�Q‰JˆEØ.3´c¸$³iÒ.?�H˜d 5™kÐ*ÐIÀTÐIØÔ4Ñ4Ø�Q‰JˆEØØ�>‰>Ð4Ô5¸¿¹ÈÔ9QØ�Q‰JˆEØØ�>‰>˜$ÔØ�Q‰JˆEØØ�>‰>˜#Ô 3¨#¢:Ø˜!˜"�gˆGØˆLØ!ˆMØ¤ W£Ò-Ø  Ñ.�Ø" <°!Ñ#3Ð#5Ð6�Ø˜S’=ØØ˜S’=ÙØ'¨Ð1Ð1Ø˜Q‘J�EØ6;¼cÀ$»iÒ6G˜H d¨5¡kÐ2ÐQÈTÐQØÔ@Ñ@Ø(0 L�MØØ Ñ!�ð ¤ W£Ó-ð ™-‘Q¨QÑ.ˆEÙØ˜ˆ}ÐØr   c                ób  ‡— | syt         j                  j                  | d   «      j                  «       j	                  d«      }|t
        v ryt        |«      syt        | «      }|€y|\  }}|j                  «       j                  dd«      Š|dk(  r‰t        v S t        ˆfd„t        D «       «      S )	zCReturn whether argv identifies a supported Hermes execution target.Fr   r<   Tú\ú/rU   c              3  óT   •K  — | ]  }‰|k(  xs ‰j                  d |› �«      –— Œ! y­w)rb   N)Úendswith)rB   rP   Ú
normalizeds     €r   rD   z%_looks_like_hermes.<locals>.<genexpr>‡   s7   øè ø€ ò àð 	�fÑÒA 
× 3Ñ 3°a¸°x°LÓ AÓAñùs   ƒ%()r6   r7   r8   rE   rF   Ú_HERMES_EXECUTABLESrN   r_   r   Ú_HERMES_PYTHON_MODULESÚanyÚ_HERMES_PYTHON_SCRIPTS)r*   rJ   ÚtargetÚkindÚvaluere   s        @r   Ú_looks_like_hermesrm   w   s­   ø€ áØÜ�g‰g×Ñ˜t A™wÓ'×-Ñ-Ó/×<Ñ<¸VÓD€GØÔ%Ñ%ØÜ(¨Ô1ØÜ% dÓ+€FØ€~ØØ�K€Dˆ%Ø—‘“×&Ñ& t¨SÓ1€JØˆxÒØÔ3Ð3Ð3Üó ä,ôó ð r   c                ó˜   — t         j                  j                  t         j                  j                  | j	                  d«      «      «      S )zGNormalize a /proc fd target, stripping the Linux `` (deleted)`` suffix.ú
 (deleted))r6   r7   ÚnormcaseÚabspathrF   )r7   s    r   Úcanonical_sqlite_pathrr   �   s/   € ä�7‰7×ÑœBŸG™GŸO™O¨D×,=Ñ,=¸lÓ,KÓLÓMÐMr   )z--hermes-home)z	--profilez-p)zstate.dbzstate.db-walzstate.db-shmc                óz   — t         j                  j                  t         j                  j                  | «      «      S ©N)r6   r7   rp   Únormpath)rl   s    r   Ú
_norm_pathrv   —   s&   € Ü�7‰7×ÑœBŸG™G×,Ñ,¨UÓ3Ó4Ð4r   c                ó*  — d}dt        | «      }}||k  r~| |   }t        |t        «      r^||v r,|dz   |k  r$t        | |dz      t        «      r| |dz      }|dz  }ŒJ|D ])  }|j                  |dz   «      sŒ|t        |«      dz   d } n |dz  }||k  rŒ~|S )z[Last ``--flag X`` / ``--flag=X`` value, token-exact (``--profile timothy`` is not ``tim``).Nr   r.   rQ   Ú=)rH   Ú
isinstanceÚstrrG   )r*   Úflagsrl   rX   ÚcountÚtokenÚflags          r   Ú_argv_flag_valuer   ›   s¿   € à€EØ”c˜$“iˆ5€EØ
�%Š-Ø�U‘ˆÜ�eœSÔ!Ø˜‰~ %¨!¡)¨eÒ"3¼
À4ÈÐPQÉ	Á?ÔTWÔ8XØ˜U Q™Y™�Ø˜‘
�ØØò �Ø×#Ñ# D¨3¡JÕ/Ø!¤# d£)¨a¡- .Ð1�EÙðð 	�‰
ˆð �%‹-ð €Lr   c                óš   — t        t        | «      «      D ]4  }t        |t        «      sŒ|j	                  d«      sŒ&|t        d«      d c S  y)u]   ``HERMES_HOME=<path>`` env-style assignment on the argv (``env HERMES_HOME=â€¦ hermes â€¦``).zHERMES_HOME=N)ÚreversedÚlistry   rz   rG   rH   )r*   r}   s     r   Ú_argv_env_homerƒ   ®   sI   € äœ$˜t›*Ó%ò /ˆÜ�eœSÕ! e×&6Ñ&6°~Õ&FØœ˜^Ó,Ð-Ð.Ò.ð/ð r   c                óŒ  — 	 ddl m}  || «      }|�Ht        j                  j	                  t        |j                  j                  «      «      |j                  fS t        j                  j                  | «      dk(  r!t        j                  j	                  | «      dfS 	 y# t        $ r t        j                  d| d¬«       Y yw xY w)	u!  ``(<install root>, <our profile name>)`` for the home holding the store, else ``(None, None)``.

    Derived with the canonical ``named_profile_home`` predicate, never a ``basename == "profiles"``
    string test: an arbitrary ``<X>/profiles/<n>/`` tree is not a Hermes install, and promoting
    ``<X>`` to "ours" swallows an unrelated instance living under it â€” the literal two-instance
    shape of #92401. The root store (``~/.hermes/state.db``) is its own root with no profile name,
    so ANY named-profile selection contradicts it.
    r   )Únamed_profile_homeNz.hermesz+Could not classify the install layout of %sT)Úexc_info)NN)Úhermes_constantsr…   r6   r7   rq   rz   ÚparentrK   r8   r4   ÚloggerÚdebug)Ú	this_homer…   Úprofile_homes      r   Ú_store_install_layoutr�   ¶   s­   € ð	^Ý7á)¨)Ó4ˆØÐ#Ü—7‘7—?‘?¤3 |×':Ñ':×'AÑ'AÓ#BÓCÀ\×EVÑEVÐVÐVÜ�7‰7×Ñ˜IÓ&¨)Ò3Ü—7‘7—?‘? 9Ó-¨tÐ3Ð3ð 4ð øô ò ^Ü�‰ÐBÀIÐX\ˆÕ]Øð^ús   ‚AB ÁAB Â!CÃCc                ód  — |€yt        t        j                  j                  |d«      «      t        j                  z   }| j                  |«      sy| t        |«      d j                  t        j                  d«      d   }t        |«      xr& ||rt        j                  j                  |«      ndk7  S )z\True when the token is under ``<install root>/profiles/<name>`` for a name that is not ours.NFÚprofilesr.   r   )
rv   r6   r7   r5   ÚseprG   rH   r$   Úboolrp   )re   Úinstall_rootÚour_profilerL   rK   s        r   Ú_names_other_profiler”   Ì   sŒ   € àÐØÜœŸ™Ÿ™ \°:Ó>Ó?Ä"Ç&Á&ÑH€FØ× Ñ  Ô(ØØ”c˜&“k�lÐ#×)Ñ)¬"¯&©&°!Ó4°QÑ7€DÜ�‹:ÒZ˜$ÁK¤2§7¡7×#3Ñ#3°KÔ#@ÐUYÑZÐZr   c                óÈ   — t        | t        «      xs t        | «      }|rt        |«      t        |«      k(  rdS dS t        | t        «      }|r|�	||k(  rdS dS |�|dk(  rdS dS y)aZ  ``"ours"``/``"other"``/``None`` from the process's OWN profile/home selection.

    Under one process per host the shared binary path proves nothing about which home a process
    serves; its ``--hermes-home``/``HERMES_HOME=``/``--profile``/``-p`` selection does. Same
    token-exact parsers ``gateway/run.py::_argv_contradicts_home`` uses.
    ÚoursÚotherNÚdefault)r   Ú_HOME_FLAGSrƒ   rv   Ú_PROFILE_FLAGS)r*   r‹   r’   r“   Ú
home_valueÚprofile_values         r   Ú_argv_home_selectionr�   ×   s{   € ô " $¬Ó4ÒL¼ÀtÓ8L€JÙÜ# JÓ/´:¸iÓ3HÒHˆvÐUÈgÐUÜ$ T¬>Ó:€MÙØÐ"Ø*¨kÒ9�6ÐF¸wÐFà&Ð2°}È	Ò7QˆvÐ_ÐX_Ð_Ør   c                ó<  — g }t        | «      D ]‹  \  }}t        |t        «      sŒ|j                  d«      r|}nB|j                  d«      r/d|v r+|j	                  dd«      d   }|j                  d«      r|nd}nd}|€Œp|j                  |t        |«      f«       Œ� |S )zH``(argv index, normalized absolute path)`` for every path-bearing token.rb   rR   rx   r.   N)Ú	enumeratery   rz   rG   r$   Úappendrv   )r*   ÚtokensrX   r}   Ú
path_tokenrl   s         r   Ú_argv_path_tokensr£   ì   s¥   € à$&€FÜ! $›ò ;‰ˆˆuÜ˜%¤Ô%ØØ×Ñ˜CÔ Ø‰JØ×Ñ˜cÔ" s¨e¡|ð —K‘K  QÓ'¨Ñ*ˆEØ"'×"2Ñ"2°3Ô"7™¸T‰JàˆJØÑ!Ø�M‰M˜5¤*¨ZÓ"8Ð9Õ:ð;ð €Mr   c                ó¤  ‡— t         j                  j                  t        j                  |«      «      }t         j                  j	                  |«      }t        |«      \  }}||dz   |dz   fD �ch c]!  }t         j                  j                  |«      ’Œ# c}Št         j                  j                  |«      }|rt         j                  j                  |«      nd}t        | «      }	t        ˆfd„|	D «       «      ryt        | |||«      }
|
dk(  ry|du}|
dk(  }|	D ]°  \  }}t        |||«      rd}Œ||k(  s"|j                  |t         j                  z   «      r y|�(||k(  s"|j                  |t         j                  z   «      rŒi|d	k(  r|sŒqd
|v s|j                  d
«      rd}Œ‰t         j                  j                  |«      t        v sŒ¯d}Œ² |S c c}w )u­  Return whether argv proves the process belongs to a DIFFERENT instance.

    ``state.db`` lives at the HERMES_HOME root, so an absolute-path token
    containing a ``/.hermes`` segment (or naming a ``state.db``/WAL/SHM under
    some other parent) identifies that token's own Hermes home.  When at least
    one such token exists AND no token references this instance's state.db,
    its sidecars, or its home directory, the process provably works on a
    different generation and must not be counted as an uninspectable holder
    of ours (issue #92401: a second gateway under /home/demo/.hermes deferred
    this instance's stale-FTS rebuild forever despite lsof proving zero open
    handles).  Ambiguous argv without absolute-path tokens returns False and
    keeps the fail-closed suspicion.

    Evidence is ranked, because one host now runs ONE process for every profile:

    1. A token naming our state.db or a sidecar exactly â€” definitive, ours.
    2. The process's own ``--hermes-home``/``HERMES_HOME=``/``--profile``/``-p``
       selection â€” that is what decides which home a multiplexer serves.
    3. Path tokens. A token under ``<install root>/profiles/<other>`` is another
       profile's store even though it sits under our root; a token that names only
       the SHARED install root is NEUTRAL (it is the same binary for every profile,
       so it can neither prove nor disprove a hold); ``argv[0]`` locates the INSTALL,
       not the home, so it is not other-home evidence for a store whose home is not
       part of an install layout (a custom ``HERMES_HOME`` is served BY the binary
       under ``~/.hermes`` â€” dismissing on it admits maintenance under a live writer).
    ú-walú-shmNc              3  ó,   •K  — | ]  \  }}|‰v –— Œ y ­wrt   © )rB   Ú_re   Úsidecarss      €r   rD   z-_argv_scoped_to_other_home.<locals>.<genexpr>&  s   øè ø€ Ò
C¡m a¨ˆ:˜Ô!Ñ
Cùs   ƒFr–   r—   Tr   z/.hermes)r6   r7   rq   ÚfspathÚdirnamer�   rp   r£   rh   r�   r”   rG   r�   rd   r8   Ú_STATE_DB_NAMES)r*   r   Údb_path_strr‹   r’   r“   Ú	candidateÚthis_home_normÚ	root_normÚpath_tokensÚ	selectionÚargv0_locates_homeÚother_home_seenrX   re   rª   s                  @r   Ú_argv_scoped_to_other_homer¶      sº  ø€ ô6 —'‘'—/‘/¤"§)¡)¨GÓ"4Ó5€KÜ—‘—‘ Ó,€IÜ 5°iÓ @Ñ€L�+ð & {°VÑ';¸[È6Ñ=QÐRöàô 	�‰×Ñ˜Õ#ò€Hô —W‘W×%Ñ% iÓ0€NÙ2>”—‘× Ñ  Ô.ÀD€IÜ# DÓ)€Kä
Ó
C°{Ô
CÔCØÜ$ T¨9°lÀKÓP€IØ�FÒØð &¨TÐ1ÐØ 7Ñ*€OØ(ò #ÑˆˆzÜ 
¨L¸+ÔFØ"ˆOØØ˜Ò'¨:×+@Ñ+@ÀÔRT×RXÑRXÑAXÔ+YÙØÐ Ø˜)Ò# z×'<Ñ'<¸YÌÏÉÑ=OÔ'PàØ�AŠ:Ñ0ØØ˜Ñ# z×':Ñ':¸:Ô'FØ"‰OÜ�W‰W×Ñ˜jÓ)¬_Ò<Ø"‰Oð#ð  ÐùòCs   Á.&Gc           
     óJ
  — t         rg S t        j                  j                  t        j                  | «      «      }t        |«      t        |dz   «      t        |dz   «      h}g }t        «       }d}||dz   |dz   fD ]Q  }	 t        j                  |«      }|j                  |j                  |j                  f«       ||k(  sŒF|j                  }ŒS t         j"                  j%                  d«      �r	 t        j&                  «       }	t        j(                  d«      D ]Ü  }
|
j+                  «       sŒt-        |
«      }||	k(  rŒ%d	|› d
�}	 t        j(                  |«      }|D ]—  }|› d|› �}	 t        j6                  |«      }t        |«      |v }	 t        j                  |«      }|j                  |j                  f|v s(|sŒ`|j9                  d«      sŒr|€Œu|j                  |k(  sŒ…|j                  ||f«       Œ™ ŒÞ 	 |S t@        €dgS 	 tA        jB                  ddg«      D ]œ  }|jD                  }t-        |d   «      }|t        j&                  «       k(  rŒ5|jG                  d«      xs dD ]P  }tI        |dd«      }|sŒt        t        j                  j                  |«      «      |v sŒ>|j                  ||f«       ŒR Œž 	 |S # t        $ rP}|j                  t        j                  t        j                  fvr|j                  dd|› d|› �f«       Y d}~�Œ—d}~ww xY w# t        $ rR t/        |«      }|�At1        |«      r6t3        || «      s*dj5                  |«      }|j                  |d|dd › �f«       Y �Œ[w xY w# t        $ rz}|j                  t        j                  t        j                  fv rY d}~�ŒWt/        |«      }|�0t1        |«      r%t3        || «      s|j                  |d|› d|› �f«       Y d}~�Œšd}~ww xY w# t        $ r–}|j                  t        j                  t        j                  fv rY d}~�ŒÝ|r|j                  |d|› d|› �f«       n=t/        |«      }|�0t1        |«      r%t3        || «      s|j                  |d|› d|› �f«       Y d}~�Œ<d}~ww xY w# t:        $ r7}t<        j?                  d|«       |j                  dd|› �f«       Y d}~|S d}~ww xY w# t:        $ r7}t<        j?                  d|«       |j                  dd|› �f«       Y d}~|S d}~ww xY w)a  Return foreign holders of the DB or one of its WAL sidecars.

    A scan failure is represented as an unknown holder. Structural maintenance
    must not assume quiescence when an old, unlinked SQLite generation may
    still be open by another process.
    r¥   r¦   Nr   zwatched-file stat failed: ú: Úlinuxz/procr   z/fdr-   zuninspectable holder: r/   rb   zuninspectable descriptor: ro   zUCould not prove state.db has no foreign holders; deferring structural maintenance: %szopen-file scan failed: )r   zopen-file scan unavailabler'   Ú
open_filesr¨   r7   r    )%Ú_IS_WINDOWSr6   r7   Úrealpathr«   rr   ÚsetÚstatr&   ÚerrnoÚENOENTÚESRCHr    ÚaddÚst_devÚst_inoÚsysÚplatformrG   ÚgetpidÚlistdirrA   Úintr+   rm   r¶   r5   Úreadlinkrd   r4   r‰   Úwarningr1   Úprocess_iterÚinfoÚgetÚgetattr)r   r®   ÚwatchedÚholdersÚwatched_idsÚdb_devr¯   Ústat_resultÚexcÚown_pidÚpid_strr'   Úfd_dirÚfdsr*   r3   ÚfdÚfd_pathrj   Útarget_is_watchedÚfd_statÚprocessrÍ   Úopenedr7   s                            r   Úforeign_state_db_holdersrà   B  s  € õ Øˆ	ô —'‘'×"Ñ"¤2§9¡9¨WÓ#5Ó6€Kä˜kÓ*Ü˜k¨FÑ2Ó3Ü˜k¨FÑ2Ó3ð€Gð
 &(€GÜ(+«€KØ €FØ! ;°Ñ#7¸ÀvÑ9MÐNò (ˆ	ð	ÜŸ'™' )Ó,ˆKð 	�‰˜×+Ñ+¨[×-?Ñ-?Ð@ÔAØ˜Ó#Ø ×'Ñ'‰Fð(ô ‡|�|×Ñ˜wÕ'ðO	BÜ—i‘i“kˆGÜŸ:™: gÓ.ò F6�Ø—‘Ô(ØÜ˜'“l�Ø˜'’>ØØ! #  cÐ*�ðÜŸ*™* VÓ,�Cð ò 36�BØ!' ¨¨"¨Ð.�Gð!Ü!#§¡¨WÓ!5˜ô" )>¸fÓ(EÈÐ(PÐ%ð!Ü"$§'¡'¨'Ó"2˜ð.  Ÿ™¨¯©Ð7¸;ÑFÚ)Ø"ŸO™O¨LÕ9Ø"Ñ.Ø#ŸN™N¨fÓ4àŸ™¨¨V }Õ5ñg36ñ'F6ð\ ˆä€~Ø2Ð3Ð3ð>Ü×*Ñ*¨E°<Ð+@ÓAò 	0ˆGØ—<‘<ˆDÜ�d˜5‘kÓ"ˆCØ”b—i‘i“kÒ!ØØŸ(™( <Ó0Ò6°Bò 0�Ü˜v v¨rÓ2�ÚÔ1´"·'±'×2BÑ2BÀ4Ó2HÓIÈWÒTØ—N‘N C¨ ;Õ/ñ0ñ	0ð  €Nøôa ò 	Ø�y‰y¤§¡¬u¯{©{Ð ;Ñ;Ø—‘ØÐ5°i°[ÀÀ3À%ÐHÐIôõ ûð	ûô, ò 	Ü*¨3Ó/�DàÐ(Ü.¨tÔ4Ü :¸4ÀÔ Ià"%§(¡(¨4£.˜ØŸ™¨Ð/EÀgÈcÈrÀlÀ^Ð-TÐ'UÔVÚð	ûô #ò !ØŸ9™9¬¯©´u·{±{Ð(CÑCÝ$Ü.¨sÓ3˜à Ð,Ü 2°4Ô 8Ü$>¸tÀWÔ$Mà#ŸN™Nà$'Ø&@ÀÀ	ÈÈCÈ5Ð$Qð!"ôõ !ûð!ûô& #ò !ØŸ9™9¬¯©´u·{±{Ð(CÑCÝ$Ù,Ø#ŸN™NØ!$Ð(BÀ6À(È"ÈSÈEÐ&RÐ Sõô $3°3Ó#7˜Dà $Ð 0Ü$6°tÔ$<Ü(BÀ4ÈÔ(Qà '§¡à(+Ø(DØ+1¨(°"°S°Eð);ð%&ô!"õ !ûð+!ûô: ò 	BÜ�N‰Nð7àôð
 �N‰N˜BÐ"9¸#¸Ð ?Ð@×AÐAØˆûð	Bûô* ò >Ü�‰ð3àô	
ð
 	�‰˜Ð5°c°UÐ;Ð<×=Ð=Ø€Nûð>ús  Á>J=Ã/AR ÅLÅR Å&M7Å;R Æ	O=ÆR Æ;R ÇR ÇR Ç R ÈA3S" É8*S" Ê#S" Ê=	LËALÌLÌAM4Í0R Í3M4Í4R Í7	O:Î ,O5Î,R Î2=O5Ï/R Ï5O:Ï:R Ï=	RÐ,RÐ2R Ð8ARÒR ÒRÒR Ò	SÒ(,SÓSÓ"	T"Ó+,TÔT"©Úexcludec               óp   — ddl m}  || |¬«      D �cg c]  }t        j                  «       |f‘Œ c}S c c}w )a¤  Return holders of ``db_path`` inside THIS process, other than *exclude*.

    :func:`foreign_state_db_holders` skips ``os.getpid()`` by design, so it answers a
    cross-PROCESS question only. Consumers that read "no holders" as "the store is quiet"
    (auto-VACUUM admission) need this arm too: a VACUUM plus its TRUNCATE checkpoint retires
    the generation a sibling SessionDB in this very process still holds.
    r   )Úother_generations_for_pathrá   )Úhermes_state_registryrä   r6   rÇ   )r   râ   rä   Údescriptions       r   Úin_process_state_db_holdersrç   Í  s<   € õ Añ 6°gÀwÔOöàô 
�‰‹�kÒ"òð ùò s   “3z--force)Ú
force_hintc               ó¸  — t        | «      }|syddlm} ddlm} i }g }|D ]x  \  }}	|dk  s|	j                  d«      r|j                  |	«       Œ.|j                  |t        «       «      j                  t        |	j                  d«      «      j                  «       Œz d|› d| › d	�g}
|
t        |«      D �cg c].  }d
t        |«      › ddj                  t        ||   «      «      › �‘Œ0 c}z  }
|r|
j                  d|d   dd › d�«        |«       }|
dd|› d�gz  }
|r|
j                  d|› d�«       |
j                  d|› �«       dj                  |
«      S c c}w )aç  Operator-facing refusal for structural maintenance (VACUUM, index rebuild, bulk delete) while another
    process holds ``db_path`` or a WAL sidecar; ``None`` when the store is provably quiet.

    Running ``hermes sessions optimize-storage`` underneath a fleet of live gateways put every agent into
    the retired-WAL refusal until all writers were stopped (#110054). Same fail-closed scan doctor and
    repair use: an incomplete scan refuses too, it never reads as an all-clear.
    Nr   )Úprofile_cli_selector)ÚSTORAGE_RECOVERY_DOCS_URLÚuninspectablero   zRefusing `hermes sessions z`: another process is using r@   z  r¸   z, z>  cannot prove the database is quiet (holder scan incomplete: éx   r0   zŠRewriting the database under a live writer is how every agent ends up refusing turns with the retired state.db-wal error. Nothing is lost.zStop them first (`hermes z>gateway stop`, quit the Desktop app, pause cron), then re-run.zOverride with z if you accept the risk.zRecovery guide: Ú
)rà   r‡   rê   Úhermes_state_errorsrë   rG   r    Ú
setdefaultr½   rÂ   r   rF   rK   Úsortedr:   r5   )r   Úcommandrè   rÑ   rê   rë   Úby_pidÚunknownr'   rj   ÚlinesÚprofile_args               r   Úheld_store_refusalr÷   ß  s…  € ô ' wÓ/€GÙØÝ5Ý=à"$€FØ€GØò \‰ˆˆVØ�!Š8�v×(Ñ(¨Ô9Ø�N‰N˜6Õ"à×Ñ˜c¤3£5Ó)×-Ñ-¬d°6×3FÑ3FÀ|Ó3TÓ.U×.ZÑ.ZÕ[ð	\ð
 *¨'¨Ð2NÈwÈiÐWXÐYÐZ€EØ	Ô[aÐbhÓ[iÖjÐTW�Ô& sÓ+Ð,¨B¨t¯y©y¼ÀÀsÁÓ9LÓ/MÐ.NÒOÒjÑj€EÙØ�‰ÐUÐV]Ð^_ÑV`ÐaeÐbeÐVfÐUgÐghÐiÔjÙ&Ó(€KØ	ð	7à
# K =Ð0nÐoðñ €Eñ
 Ø�‰�~ j \Ð1IÐJÔKØ	‡L�LÐ#Ð$=Ð#>Ð?Ô@Ø�9‰9�UÓÐùò ks   Â43Ec               ó|  — t        | «      ryd}	  || d¬«      }|j                  d«       |j                  d«       |j                  d«       	 |�$	 |j                  d«       	 |j                  «        y	y	# t        $ r Y Œw xY w# t        $ r Y y	w xY w# t        j
                  $ rr}t        |«      j                  «       }d
|v xs d|v cY d}~|�C	 |j                  d«       n# t        $ r Y nw xY w	 |j                  «        S # t        $ r Y S w xY wS d}~wt        j                  $ rH Y |�C	 |j                  d«       n# t        $ r Y nw xY w	 |j                  «        y	# t        $ r Y y	w xY wy	w xY w# |�C	 |j                  d«       n# t        $ r Y nw xY w	 |j                  «        w # t        $ r Y w w xY ww xY w)aû  Return whether repair lacks proven exclusive ownership of ``db_path``.

    ANY foreign process holding the DB or a sidecar is a live holder (#103339): the lock probe below
    cannot see a DELETE-mode reader (SHARED only) and cannot run at all on a malformed file, and those
    are exactly the states repair/VACUUM/checkpoint get invoked in. The holder scan is the authority and
    fails closed on its own failures (unknown/uninspectable sentinels); the probe only adds a positive
    lock signal on top.TNg        )ÚtimeoutzPRAGMA locking_mode=EXCLUSIVEzBEGIN IMMEDIATEÚROLLBACKzPRAGMA locking_mode=NORMALFÚlockedÚbusy)	rà   Úexecuter4   ÚcloseÚsqlite3ÚOperationalErrorrz   rE   ÚDatabaseError)r   Úconnect_repair_durableÚproberÕ   Úlowereds        r   Úlive_writer_holds_dbr    sã  € ô   Ô(Øà€EðÙ& w¸Ô<ˆØ�‰Ð5Ô6Ø�‰Ð'Ô(Ø�‰�jÔ!Øð ÐðØ—‘Ð:Ô;ðØ—‘•ð øô ò Ùðûô ò Ùðûô ×#Ñ#ò 8Ü�c“(—.‘.Ó"ˆØ˜7Ð"Ò7 f°Ð&7Ô7ð
 ÐðØ—‘Ð:Õ;øÜò ÙðúðØ—‘•øÜò Ùðúð ûô	 × Ñ ò ààÐðØ—‘Ð:Õ;øÜò ÙðúðØ—‘•øÜò Ùðúð ð	ûð ÐðØ—‘Ð:Õ;øÜò ÙðúðØ—‘•øÜò Ùðúð úsï   �=B ÁA5 Á#B Á5	BÂ BÂ	BÂBÂE1Â&"DÃE1Ã	E4 ÃC"Ã"	C.Ã-C.Ã2DÄ	DÄDÄE1Ä)E4 Ä-D? Ä?	EÅ
EÅE  Å 	E,Å+E,Å0E1Å1E4 Å4F;Å8F
Æ	F;Æ
	FÆF;ÆFÆF;ÆF+Æ*F;Æ+	F7Æ4F;Æ6F7Æ7F;)Úreturnrz   )r'   rÉ   r  zOptional[List[str]])r'   rÉ   r  rz   )rJ   rz   r  r‘   )r*   úSequence[str]r  zOptional[Tuple[str, str]])r*   r  r  r‘   )r7   rz   r  rz   )rl   rz   r  rz   )r*   r  r{   r  r  úOptional[str])r*   r  r  r  )r‹   rz   r  z#Tuple[Optional[str], Optional[str]])re   rz   r’   r  r“   r  r  r‘   )
r*   r  r‹   rz   r’   r  r“   r  r  r  )r*   r  r  úList[Tuple[int, str]])r*   r  r   r   r  r‘   )r   r   r  r	  )r   r   rò   rz   rè   r  r  r  )r   r   r  z!Callable[..., sqlite3.Connection]r  r‘   )4Ú__doc__Ú
__future__r   r¿   Úloggingr6   rÿ   rÅ   Úpathlibr   Útypingr   r   r   r   r	   r
   r1   ÚImportErrorr   Ú	getLoggerÚ__name__r‰   rÆ   r»   Ú	frozensetrf   rg   ri   rW   rV   r+   r:   rN   r_   rm   rr   r™   rš   r­   rv   r   rƒ   r�   r”   r�   r£   r¶   rà   rç   r÷   r  r¨   r   r   ú<module>r     s‹  ðñõ #ã Û Û 	Û Û 
Ý ß A× AðÛó
9ð 
ˆ×	Ñ	˜8Ó	$€à�l‰l˜gÑ%€ÙÒ HÓIÐ Ù" MÐ3DÐ#EÓFÐ Ù"Ð$8¸.Ð#IÓJÐ Ù&/²Ó&@Ð #Ù%.Ø Ð(ó&Ð "ó
ó	 óó&óRó,Nð
 !€Ø$€Ø>€ó5óó&óó,[ðØ
ðØ$'ðØ7DðØS`ðàóó*ó(?óDHðX #ñØðàóð$ T]õ "ðJ%Øð%ð >ð%ð 
ô	%øða ò Ø‚Fðús   ´C7 Ã7DÄ D