Ë
    âß²jA¢  ã                  óÀ  — 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
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mZmZ ddlmZmZ  ej>                  d«      Z d	„ Z! ejD                  «       Z#i Z$d
e%d<   g Z&de%d<   dZ'd?d„Z(d@d„Z)dAd„Z*dBd„Z+dCd„Z,dDd„Z-dEd„Z.d„ Z/dZ0dZ1dZ2dZ3dZ4dZ5dZ6dZ7da8d„ Z9dFd„Z:d „ Z;dGd!„Z<dGd"„Z=dHd#„Z>d$Z?d%Z@dZAd&ZBd'ZCd(ZD G d)„ d*eE«      ZFdId+„ZGdJd,„ZHdKd-„ZIdLd.„ZJdMd/„ZKdNd0„ZL	 	 	 	 	 	 dOd1„ZMdPd2„ZNdQd3„ZOd(d4d5œdRd6„ZPd(d4d5œdRd7„ZQej¤                  dSdTd8„«       ZSd4d9œdUd:„ZTdVd;„ZUdWd<„ZV	 	 	 	 	 	 dXd=„ZWdYd>„ZXy)ZaÐ  state.db file-level health helpers, split out of ``hermes_state.py``.

Header probes (application_id / zeroed-file detection), deleted-WAL-sidecar
holder scans, quarantine of zeroed databases, ``collect_state_db_stats`` and
holder-process classification.  Helpers that hermes_state itself imports and
calls (``_connect_tracked_db`` & co) are looked up lazily from ``hermes_state`` at
call time, so tests that monkeypatch ``hermes_state.<name>`` keep intercepting.
é    )ÚannotationsN)ÚPath)ÚAnyÚCallableÚDictÚListÚOptionalÚTuple)Úcanonical_sqlite_pathÚread_only_db_uri)ÚFTS_REBUILD_DEFERRAL_KEYÚstat_db_file_identityÚhermes_statec                 ó  — d} t         j                  j                  dk7  rt        | «      ‚	 ddl}|j
                  d   }|j                  f|_        d|_        |S # t        t        t        f$ r}t        | «      |‚d}~ww xY w)aÙ  Bind a non-finalizing reference before opening a writable SQLite handle.

    A Python container is cleared during interpreter shutdown. An unmatched
    CPython C reference keeps the exact connection alive through that cleanup,
    so SQLite cannot checkpoint its lost WAL generation from a finalizer. This
    deliberately retains the connection and its descriptors until process exit;
    it does not make already-unlinked WAL data durable after the last fd closes.
    z®Writable SessionDB on a Python without sqlite3 setconfig (< 3.12) requires CPython with ctypes support to retain a quarantined SQLite connection through interpreter shutdown.Úcpythonr   NÚ	Py_IncRef)ÚsysÚimplementationÚnameÚRuntimeErrorÚctypesÚ	pythonapiÚ	py_objectÚargtypesÚrestypeÚImportErrorÚAttributeErrorÚOSError)Úmessager   ÚretainÚexcs       ú8/home/ubuntu/.hermes/hermes-agent/hermes_state_dbfile.pyÚ_prepare_connection_retirementr#   $   sŒ   € ð	að ô ×Ñ×Ñ )Ò+Ü˜7Ó#Ð#ð-Ûð ×!Ñ! +Ñ.ˆØ!×+Ñ+Ð-ˆŒØˆŒð €Møô œ¬Ð1ò -Ü˜7Ó#¨Ð,ûð-ús   ¬,A ÁA?Á.A:Á:A?z!'dict[str, tuple[int, int, int]]'Ú_HEADER_PROBE_FDSz'list[int]'Ú_RETIRED_HEADER_PROBE_FDS)Úmessages_ftsÚmessages_fts_trigramÚmessages_fts_cjkc                ód  — ddl m} |r`t        j                  t        «      5  | j                  d«      5 }|j                  |«       |j                  |«      cddd«       cddd«       S t        | «      }	 t        j                  | «      }t        5  t        j                  |«      }|�P|d   |d   f|j                  |j                  fk7  r-t         j#                  t        j%                  |«      d   «       d}|€]	 t        j
                  | t        j&                  «      }	 t        j(                  |«      }	||	j                  |	j                  fx}t        |<   t        j                  t        «      5  t        j*                  |d   ||«      cddd«       cddd«       S # 1 sw Y   nxY wddd«       y# 1 sw Y   yxY w# t        $ r Y yw xY w# t        $ r Y ddd«       yw xY w# t        $ r  t         j#                  |«       Y ddd«       yw xY w# 1 sw Y   nxY w	 ddd«       y# 1 sw Y   yxY w)zåLock-safe raw read of a possibly-live SQLite database: POSIX preads from a cached,
    never-closed fd (rebound when the path names a new inode); Windows reads plainly, since
    advisory-lock cancellation is a POSIX-only hazard.r   ©Ú_IS_WINDOWSÚrbNé   é   )r   r+   Ú
contextlibÚsuppressr   ÚopenÚseekÚreadÚstrÚosÚstatÚ_HEADER_PROBE_LOCKr$   ÚgetÚst_devÚst_inor%   ÚappendÚpopÚO_RDONLYÚfstatÚpread)
Údb_pathÚoffsetÚlengthr+   ÚhandleÚkeyÚstÚcachedÚfdÚfsts
             r"   Ú_pread_db_rangerI   M   s  € õ )ÙÜ× Ñ ¤Ó)ñ 	'¨7¯<©<¸Ó+=ð 	'ÀØ�K‰K˜ÔØ—;‘;˜vÓ&÷	'÷ 	'ò 	'ô ˆg‹,€CðÜ�W‰W�WÓˆô 
ñ 7Ü"×&Ñ& sÓ+ˆØÐ 6¨!¡9¨f°Q©iÐ"8¸R¿Y¹YÈÏ	É	Ð<RÒ"Rä%×,Ñ,Ô->×-BÑ-BÀ3Ó-GÈÑ-JÔKØˆFØˆ>ðÜ—W‘W˜W¤b§k¡kÓ2�ðÜ—h‘h˜r“l�ð 02°3·:±:¸s¿z¹zÐ.JÐJˆFÔ& sÑ+Ü× Ñ ¤Ó)ñ 	7Ü—8‘8˜F 1™I v¨vÓ6÷	7ð 	7÷#7ñ 7÷	'ð 	'ú÷ 	'ð ÷	'ð ûô ò Ùðûô ò Ø÷7ð 7ðûô ò Ü)×0Ñ0°Ô4Ø÷7ð 7ðú÷	7ð 	7úð 	7÷#7ð& ÷'7ð& ús›   ¢F2´"FÁ	F2Á5F> ÂA*H&Ã;$GÄ G$Ä5;H&Å0HÆ
	H&ÆF&	Æ"F2Æ2F;Æ>	G
Ç	G
Ç	G!ÇH&Ç G!Ç!H&Ç$HÈH&ÈHÈH&ÈH	ÈH&È&H/c                ó   — t        | d|«      S )z[Lock-safe raw header read of a possibly-live SQLite database (see :func:`_pread_db_range`).r   ©rI   )r@   rB   s     r"   Ú_pread_db_headerrL   r   s   € ä˜7 A vÓ.Ð.ó    c                ó¬   — ddl m} |dz   }t        | |«      }|�t        |«      |k  s|dd dk7  ryt	        t        j                  d||| «      d   «      S )zRapplication_id from the SQLite header, via the lock-safe :func:`_pread_db_header`.r   ©Ú_STATE_DB_APPLICATION_ID_OFFSETé   Né   ó   SQLite format 3 ú>I)Úhermes_state_errorsrP   rL   ÚlenÚintÚstructÚunpack)r@   rP   ÚendÚheaders       r"   Ú_read_sqlite_application_idr\   w   s`   € åCØ
)¨AÑ
-€CÜ˜g sÓ+€FØ€~œ˜V› sÒ*¨f°S°b¨kÐ=SÒ.SØÜŒv�}‰}˜T 6Ð*IÈ#Ð#NÓOÐPQÑRÓSÐSrM   c           
     óÚ   — t        j                  | «      }dD �ci c]  }|t        t        ||z   «      «      “Œ }}|j	                  «       D ��ci c]  \  }}|€Œ	||“Œ c}}S c c}w c c}}w )z<Snapshot ``(st_dev, st_ino)`` for existing WAL/SHM sidecars.©ú-walú-shm)r5   ÚfspathÚ_stat_db_file_identityr   Úitems)r@   ÚbaseÚsuffixÚidentsÚidents        r"   Ú_stat_sqlite_sidecar_identityrh   �   sd   € ä�9‰9�WÓ€DØP`ÖaÀfˆfÔ,¬T°$¸±-Ó-@ÓAÑAÐa€FÐaØ/5¯|©|«~×S™m˜f eÀÑARˆF�E‰MÓSÐSùò bùÛSs   šA"Á
A'ÁA'c                óD  — t         j                  j                  t        j                  | «      «      }|dz   |dz   f}|D �ci c]  }t	        |«      |“Œ }}t         j                  j                  t         j                  j                  t         j                  j                  |«      «      t         j                  j                  |«      «      t         j                  j                  |«      f}|D ]*  }dD ]#  }|j                  t	        ||z   «      ||z   «       Œ% Œ, |S c c}w )z®Map each sidecar's canonical (/proc-comparable) form to its literal, still-named path,
    so a canonical match can be re-``stat``'d for identity rather than trusted as text.r_   r`   r^   )
r5   ÚpathÚabspathra   r   ÚjoinÚrealpathÚdirnameÚbasenameÚ
setdefault)r@   Úliteral_baseÚliteralrj   ÚwatchedÚresolved_basesrd   re   s           r"   Ú_watched_sqlite_sidecar_pathsru   ˆ   sû   € ô —7‘7—?‘?¤2§9¡9¨WÓ#5Ó6€LØ˜fÑ$ l°VÑ&;Ð<€GØ=DÖE°TÔ$ TÓ*¨DÑ0ÐE€GÐEô 	�‰�‰”R—W‘W×%Ñ%¤b§g¡g§o¡o°lÓ&CÓDÜ—W‘W×%Ñ% lÓ3ó	5ä
�‰×Ñ˜Ó&ð€Nð
 ò TˆØ&ò 	TˆFØ×ÑÔ4°T¸F±]ÓCÀTÈFÁ]ÕSñ	TðTð €Nùò Fs   ÁDc                ó‚   — 	 t        j                  |«      }| |j                  |j                  fk7  S # t        $ r Y yw xY w)z»The shared verdict: does ``(st_dev, st_ino)`` name a generation the watched path no longer
    holds?  See :func:`_fd_is_truly_unlinked` for why the test is identity and never link count.T)r5   r6   r   r9   r:   )ÚidentityÚwatched_pathÚwatched_stats      r"   Ú_identity_is_truly_unlinkedrz   Ÿ   sG   € ðÜ—w‘w˜|Ó,ˆð ˜×+Ñ+¨\×-@Ñ-@ÐAÑAÐAøô ò Ùðús   ‚2 ²	>½>c                ó   — 	 t        j                  | «      }t        |j                  |j                  f|«      S # t        $ r;}|j                  t        j                  t        j
                  fv rY d}~yY d}~yd}~ww xY w)u�  Confirm a `` (deleted)`` /proc fd target really names an orphaned generation, not the
    CURRENT watched sidecar.

    The suffix alone is not proof: on OpenZFS a live, still-linked file whose dentry was
    unhashed is reported as deleted while it is still the very same inode the watched path
    names. Conversely ``st_nlink == 0`` is not proof of the opposite â€” a stale generation can
    keep a surviving hard link (a backup, an operator copy) after the watched path itself is
    removed or replaced, leaving ``st_nlink >= 1`` on a truly orphaned inode. So compare
    identity, not link count: only an exact ``(st_dev, st_ino)`` match between the fd and the
    CURRENT watched path proves they are the same live file. A mismatch, or a watched path
    that cannot be stat'd at all, means the fd holds a generation the watched path no longer
    names â€” the guard keeps failing closed.NFT)	r5   r6   r   ÚerrnoÚENOENTÚESRCHrz   r9   r:   )Úfd_pathrx   Úfd_statr!   s       r"   Ú_fd_is_truly_unlinkedr�   ©   sd   € ðÜ—'‘'˜'Ó"ˆô '¨¯©¸¿¹Ð'GÈÓVÐVøô ò ð �9‰9œŸ™¤u§{¡{Ð3Ñ3ÜÜûðús   ‚9 ¹	A=Á,A8Á8A=c               #  óv  K  — t        j                  d«      D ]‚  } | j                  «       sŒd| › d�}	 t        j                  |«      }|D ]N  }t	        j
                  t        «      5  |› d|› �}t        | «      t        j                  |«      |f–— ddd«       ŒP Œ„ y# t        $ r Y Œ‘w xY w# 1 sw Y   ŒlxY w­w)zVYield ``(pid, readlink target, fd path)`` for every readable ``/proc/<pid>/fd`` entry.z/procz/proc/z/fdú/N)r5   ÚlistdirÚisdigitr   r/   r0   rW   Úreadlink)Úpid_strÚfd_dirÚfdsrG   r   s        r"   Ú_iter_proc_fd_targetsrŠ   Â   sË   è ø€ ä—:‘:˜gÓ&ò BˆØ�‰Ô ØØ˜'˜ #Ð&ˆð	Ü—*‘*˜VÓ$ˆCð ò 	BˆBÜ×$Ñ$¤WÓ-ñ BØ#˜H A b TÐ*�Ü˜'“l¤B§K¡K°Ó$8¸'ÐAÒA÷Bð Bñ	BñBøô ò 	Ùð	ú÷Bð Büs@   ‚0B9³BÁB9Á&+B-ÂB9Â	B*Â'B9Â)B*Â*B9Â-B6Â2B9r-   r.   é   i°  é   é    é°   c                 óˆ  — t         } | �| S ddl}|j                  dd¬«      } |j                  | j                  _        |j                  |j                  |j                  |j                  f| j                  _        |j                  | j                  _        |j                  |j                  |j                  |j                  |j                  f| j                  _        |j                  | j                  _        |j                  |j                  |j                  |j                  |j                  f| j                  _        | a | S )zölibproc's fd-enumeration entry points, loaded once per process.

    macOS exports these from libSystem, so this process's own handle resolves them; a failure
    here propagates to the caller's ``except Exception`` and keeps the guard fail-open.Nr   T)Ú	use_errno)Ú_DARWIN_LIBPROCr   ÚCDLLÚc_intÚproc_listpidsr   Úc_uint32Úc_void_pr   Úproc_pidinfoÚc_uint64Úproc_pidfdinfo)Úlibr   s     r"   Ú_darwin_libprocr›   ì   sò   € ô €CØ
€Øˆ
Ûà
�+‰+�d dˆ+Ó
+€CØ &§¡€C×ÑÔØ"(§/¡/°6·?±?ÀFÇOÁOÐU[×UdÑUdÐ!e€C×ÑÔØ%Ÿ|™|€C×ÑÔØ!'§¡¨v¯|©|¸V¿_¹_ÈfÏoÉoÐ_e×_kÑ_kÐ l€C×ÑÔØ!'§¡€C×ÑÔØ#)§<¡<°·±¸v¿|¹|ÈVÏ_É_Ð^d×^jÑ^jÐ"k€C×ÑÔØ€OØ€JrM   c                ó  — ddl }d}	 |j                  |«      }| j                  t        d||«      }|dk  rg S ||k  r;t	        j
                  d|dz  › d�|j                  «      D �cg c]
  }|dkD  sŒ	|‘Œ c}S |dz  }Œvc c}w )zZEvery pid ``proc_listpids`` will name (the kernel silently omits ones we may not inspect).r   Ni €  Ú<rQ   Úir.   )r   Úcreate_string_bufferr”   Ú_DARWIN_ALL_PIDSrX   Úunpack_fromÚraw)rš   r   ÚsizeÚbufferÚusedÚpids         r"   Ú_darwin_all_pidsr§     s“   € ãà€DØ
Ø×,Ñ,¨TÓ2ˆØ× Ñ Ô!1°1°f¸dÓCˆØ�1Š9ØˆIØ�$Š;Ü#)×#5Ñ#5¸¸$À!¹)¸ÀAÐ6FÈÏ
É
Ó#SÖ_˜CÐWZÐ]^ÓW^’CÒ_Ð_Ø�‰	ˆð ùò `s   Á&
A>Á1A>c            	   #  ó¶  K  — ddl } t        «       }t        |«      D �]8  }d}	 | j                  |«      }|j	                  |t
        d||«      }|dk  rn||k  rn|dz  }Œ<t        d|t        z
  dz   t        «      D ]Ø  }t        j                  d|j                  |«      d   }| j                  t        «      }|j                  ||t        |t        «      dk  rŒ]|j                  }	t        j                  d|	t        «      d   t        j                  d|	t        «      d   f}
|	t         d j#                  d	d«      d   j%                  d
d«      }||||
f–— ŒÚ �Œ; y­w)al  Yield ``(pid, fd, last pathname, (st_dev, st_ino))`` for every vnode fd libproc reports.

    The pathname and the identity both stay readable after the path is unlinked, which is what
    makes an orphaned WAL generation visible at all on macOS.  Processes that cannot be inspected
    (gone, or not ours) and descriptors that are not vnodes are skipped silently.r   Ni   r.   r-   z<iz<Iz<Qó    zutf-8Úreplace)r   r›   r§   rŸ   r—   Ú_DARWIN_PIDLISTFDSÚrangeÚ_DARWIN_PROC_FD_INFO_SIZErX   r¡   r¢   Ú_DARWIN_FD_RECORD_SIZEr™   Ú_DARWIN_PIDFDVNODEPATHINFOÚ_DARWIN_FD_DEV_OFFSETÚ_DARWIN_FD_INO_OFFSETÚ_DARWIN_FD_PATH_OFFSETÚsplitÚdecode)r   rš   r¦   r£   Úlistingr¥   rA   rG   Úrecordr¢   rw   Útargets               r"   Ú_iter_darwin_fd_targetsr¸     sq  è ø€ ó ä
Ó
€CÜ Ó$ó ,ˆØˆØØ×1Ñ1°$Ó7ˆGØ×#Ñ# CÔ);¸QÀÈÓNˆDØ�qŠyØØ�dŠ{ØØ�A‰IˆDð ô ˜A˜tÔ&?Ñ?À!ÑCÔE^Ó_ò 
	,ˆFÜ×#Ñ# D¨'¯+©+°vÓ>¸qÑAˆBØ×0Ñ0Ô1GÓHˆFØ×!Ñ! # rÔ+EÀvÜ"8ó:Ø=>ò?àØ—*‘*ˆCÜ×*Ñ*¨4°Ô6KÓLÈQÑOÜ×*Ñ*¨4°Ô6KÓLÈQÑOðQˆHàÔ/Ð0Ð1×7Ñ7¸ÀÓCÀAÑF×MÑMÈgÐW`ÓaˆFØ�r˜6 8Ð+Ó+ò
	,ñ,ùs   ‚EEc                óª  — t         j                  j                  t         j                  j                  t        j                  | «      «      «      }|dz   |dz   fD �ci c]  }|j                  «       |“Œ }}g }t        «       D ]I  \  }}}}|j                  |j                  «       «      }	|	€Œ*t        ||	«      sŒ7|j                  ||f«       ŒK |S c c}w )a¸  The macOS leg of :func:`iter_deleted_sqlite_sidecar_holders`: libproc enumeration matched
    against the watched sidecar paths, judged by identity.

    The watched side is resolved with ``os.path.realpath`` because libproc reports the kernel's
    path for the vnode, while ``os.path.abspath`` does not resolve symlinks -- a textual compare
    of the two silently misses every sidecar under a symlinked prefix (on macOS ``/var`` itself).r_   r`   )
r5   rj   rm   rk   ra   Úcasefoldr¸   r8   rz   r;   )
r@   rd   rj   rs   Úholdersr¦   Ú_fdr·   rw   rr   s
             r"   Ú_iter_darwin_sidecar_holdersr½   3  s½   € ô �7‰7×ÑœBŸG™GŸO™O¬B¯I©I°gÓ,>Ó?Ó@€Dð 37¸±-ÀÈÁÐ1OÖP¨ˆt�}‰}‹ Ñ$ÐP€GÐPØ%'€GÜ&=Ó&?ò *Ñ"ˆˆS�&˜(Ø—+‘+˜fŸo™oÓ/Ó0ˆØÑÔ#>¸xÈÕ#QØ�N‰N˜C ˜=Õ)ð*ð €Nùò Qs   ÁCc                ó¼  — t         j                  dk(  rg S g }	 t         j                  dk(  rt        | «      }|S t         j                  j                  d«      rVt	        | «      }t        «       D ]>  \  }}}t        |«      }d|v sŒ||v sŒt        |||   «      sŒ,|j                  ||f«       Œ@ |S # t        $ r"}t        j                  d| |«       Y d}~|S d}~ww xY w)a@  Return processes holding an unlinked ``state.db-wal`` / ``-shm`` sidecar for *db_path*.

    Linux enumerates ``/proc/<pid>/fd`` (using the `` (deleted)`` suffix as a cheap pre-filter);
    macOS enumerates descriptors through libproc, because a retired generation there keeps its
    last pathname and no suffix marks it.  Either way the verdict is the same identity comparison:
    the fd must name a watched sidecar path while its ``(st_dev, st_ino)`` no longer matches what
    that path holds.  Windows returns ``[]`` -- it cannot unlink a held sidecar, so no retired
    generation can exist; any other platform returns ``[]`` as before.

    Includes this process: on the open/write refuse path the in-process writer holding the orphan
    inode must not mint a replacement WAL (``_foreign_state_db_holders`` skips this PID).Úwin32ÚdarwinÚlinuxz
 (deleted)z)deleted-WAL holder scan failed for %s: %sN)r   Úplatformr½   Ú
startswithru   rŠ   r   r�   r;   Ú	ExceptionÚloggerÚdebug)r@   r»   rs   r¦   r·   r   Ú	canonicalr!   s           r"   Ú#iter_deleted_sqlite_sidecar_holdersrÈ   F  sà   € ô ‡|�|�wÒØˆ	Ø%'€GðPÜ�<‰<˜8Ò#Ü2°7Ó;ˆGð €Nô �\‰\×$Ñ$ WÔ-Ü3°GÓ<ˆGÜ(=Ó(?ò 2Ñ$��V˜WÜ1°&Ó9�	Ø  FÒ*¨y¸GÒ/CÜ1°'¸7À9Ñ;MÕNØ—N‘N C¨ =Õ1ð	2ð €Nøô ò PÜ�‰Ð@À'È3×OÐOØ€NûðPús/   ™B0 ¹A
B0 ÂB0 Â	B0 ÂB0 Â0	CÂ9CÃCc                ól   — ddl m} ddlm} t	        | «      syt
        j                  |«        ||«      ‚)z¦Raise if any process holds a deleted WAL/SHM generation for *db_path*; called
    *before* ``sqlite3.connect`` so a second opener cannot mint a replacement WAL inode.r   )ÚDeletedWalGenerationError)Ú_DELETED_WAL_GENERATION_MSGN)r   rÊ   rU   rË   rÈ   rÅ   Úerror)r@   rÊ   rË   s      r"   Úrefuse_deleted_wal_generationrÍ   d  s.   € õ 7Ý?Ü.¨wÔ7ØÜ
‡L�LÐ,Ô-Ù
#Ð$?Ó
@Ð@rM   z.retired-wal-zmanifest.jsoni    i  € éd   c                  ó   — e Zd ZdZy)ÚRetiredGenerationCaptureErrorzNThe retired WAL generation could not be captured durably; nothing was mutated.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© rM   r"   rÐ   rÐ   …  s   „ ÚXrM   rÐ   c                óè   — ddl m} |ryt        j                  | t        j                  «      }	 t        j
                  |«       t        j                  |«       y# t        j                  |«       w xY w)zµfsync a file or directory we own. Never used on the live database: opening and closing a
    descriptor on a file SQLite has locked would cancel this process's POSIX advisory locks.r   r*   N)r   r+   r5   r1   r=   ÚfsyncÚclose)rj   r+   rG   s      r"   Ú_fsync_pathrÙ   ‰  sH   € õ )ÙØÜ	�‰�”r—{‘{Ó	#€BðÜ
�‰�Œä
�‰��øŒ�‰��ús   ¯A ÁA1c                óZ  — | syt        | «      }dD ]z  }	 t        j                  |«      }|D ]\  }|j	                  «       sŒ	 t        j
                  t        |«      «      }|j                  |j                  f|k(  sŒOt        |«      c c S   y y# t        $ r Y Œ‰w xY w# t        $ r Y Œ}w xY w)zÑThis process's open descriptor for the inode ``(st_dev, st_ino)``, or None.

    Exact-generation qualified: pathnames are never consulted. The descriptor stays owned by
    SQLite; callers only pread from it.N)z/proc/self/fdz/dev/fd)	Útupler5   r„   r   r…   r>   rW   r9   r:   )rw   Úwantedrˆ   Únamesr   rE   s         r"   Ú_own_descriptor_for_identityrÞ   –  s»   € ñ
 ØÜ�8‹_€FØ.ò ˆð	Ü—J‘J˜vÓ&ˆEð ò 	!ˆDØ—<‘<”>ØðÜ—X‘Xœc $›iÓ(�ð —	‘	˜2Ÿ9™9Ð%¨Ó/Ü˜4“yÔ ð	!ñ ðð øô ò 	Ùð	ûô ò Ùðús#   •BÁBÂ	BÂBÂ	B*Â)B*c          	     óØ  — t        j                  «       }|j                  |j                  dz   «      }d}	 t	        |d«      5 }||k  rS | |t        t        ||z
  «      «      }|sn6|j                  |«       |j                  |«       |t        |«      z  }||k  rŒS|j                  «        t        j                  |j                  «       «       ddd«       ||k7  r1|j                  d¬«       t!        d|j                  › d|› d	|› d
�«      ‚t        j"                  ||«       |j                  ||j%                  «       dœS # 1 sw Y   ŒsxY w# t        $ r |j                  d¬«       ‚ w xY w)a  Stream ``size`` bytes via ``read(offset, length)`` into ``dest`` (temp file, fsync, rename).

    A short read raises ``RetiredGenerationCaptureError``: a truncated copy with a valid-looking
    manifest would let the caller settle the handle while the unlinked inode still dies at exit.z.partr   ÚwbNT)Ú
missing_okzshort read copying ú: ú of uZ    bytes â€” the retired generation is NOT fully captured; the handle stays open for a retry)ÚfileÚbytesÚsha256)Úhashlibræ   Ú	with_namer   r1   ÚminÚ_CAPTURE_CHUNK_BYTESÚwriteÚupdaterV   Úflushr5   r×   ÚfilenorÄ   ÚunlinkrÐ   rª   Ú	hexdigest)r3   Údestr£   ÚdigestÚpartrA   ÚoutÚchunks           r"   Ú_copy_rangerö   °  sV  € ô
 �^‰^Ó€FØ�>‰>˜$Ÿ)™) gÑ-Ó.€DØ€FðÜ�$˜Óð 		# Ø˜4’-Ù˜V¤SÔ)=¸tÀf¹}Ó%MÓN�ÙØØ—	‘	˜%Ô Ø—‘˜eÔ$Øœ#˜e›*Ñ$�ð ˜4“-ð �I‰IŒKÜ�H‰H�S—Z‘Z“\Ô"÷		#ð �‚~Ø�‰˜tˆÔ$Ü+Ø! $§)¡) ¨B¨v¨h°d¸4¸&ð AGð Gó
ð 	
ô ‡J�Jˆt�TÔØ—I‘I¨¸&×:JÑ:JÓ:LÑMÐM÷)		#ð 		#ûô ò Ø�‰˜tˆÔ$Øðús*   ¶E ÁAE Â3E ÃE Å E	ÅE ÅE)c               ó&   ‡ — t        ˆ fd„||¬«      S )zIpread ``fd`` (a descriptor SQLite owns; never closed here) into ``dest``.c                ó2   •— t        j                  ‰|| «      S ©N)r5   r?   )rA   rB   rG   s     €r"   ú<lambda>z"_copy_descriptor.<locals>.<lambda>Ò  s   ø€ ¬b¯h©h°r¸6À6Ó.J€ rM   ©r£   ©rö   )rG   rñ   r£   s   `  r"   Ú_copy_descriptorrý   Ð  s   ø€ äÓJÈDÐW[Ô\Ð\rM   c               ó&   ‡ — t        ˆ fd„||¬«      S )zZCopy the live main file through the lock-safe cached descriptor (see ``_pread_db_range``).c                ó   •— t        ‰| |«      S rù   rK   )rA   rB   r@   s     €r"   rú   z"_copy_main_image.<locals>.<lambda>×  s   ø€ ¬o¸gÀvÈvÓ.V€ rM   rû   rü   )r@   rñ   r£   s   `  r"   Ú_copy_main_imager   Õ  s   ø€ äÓVÐX\ÐcgÔhÐhrM   c                ó:  — t        | «      t        k  s| d d dk7  rddiS t        j                  d| dd «      d   }ddlm} d	d
d|ddœ}|j                  «       D ��ci c]%  \  }}|t        j                  d| ||dz    «      d   “Œ' }}}d|dk(  rdn|dœ|¥S c c}}w )NrR   rS   ÚvalidFz>Hé   r   rO   rŒ   é   é<   é\   )Úchange_counterÚ
page_countÚuser_versionÚapplication_idÚversion_valid_forrT   rQ   Tr-   i   )r  Ú	page_size)rV   Ú_SQLITE_HEADER_BYTESrX   rY   rU   rP   rc   )r[   Úraw_page_sizerP   Úfieldsr   ÚoffÚparseds          r"   Ú_parse_sqlite_headerr  Ú  s»   € Ü
ˆ6ƒ{Ô)Ò)¨V°C°R¨[Ð<RÒ-RØ˜ÐÐÜ—M‘M $¨¨r°"¨Ó6°qÑ9€MÝCØ "°"ÀbØ ?ÐVXñZ€FàQW×Q]ÑQ]ÓQ_×`ÁIÀDÈ#ˆd”F—M‘M $¨¨s°3¸±7Ð(;Ó<¸QÑ?Ñ?Ð`€FÑ`Ø°À!Ò1C©ÈÑaÐZ`ÐaÐaùó as   Á*Bc               óô	  — t        | «      } t        |j                  d«      xs d«      }|st        d| › d�«      ‚t	        |«      }|€t        d|› d| › �«      ‚	 t        j                  |«      j                  }| j                  › t        › t        j                  d
t        j                  «       «      › dt        j                  «       › �}| j                  |«      }d}	|j!                  «       s,|j                  |j                  dz   «      j!                  «       rY|	dz  }	| j                  |› d|	› �«      }|j!                  «       rŒ,|j                  |j                  dz   «      j!                  «       rŒY|j                  |j                  dz   «      }
	 |
j#                  dd¬«       t$        t'        | «      |t        j                  «       t        j                  dt        j                  «       «      t(        j*                  j-                  «       d   t.        j0                  dt3        |«      it5        ||
| j                  dz   z  |¬«      ¥dt7        | «      j9                  «       D ��ci c]  \  }}|t3        |«      “Œ c}}ddœ}t        |j                  d«      xs d«      }|rt	        |«      nd}|�mt;        j<                  t        «      5  dt3        |«      it5        ||
| j                  dz   z  t        j                  |«      j                  ¬«      ¥|d<   ddd«       t?        | dt@        «      }|€t        d| › �«      ‚t        jB                  | «      j                  }t3        tE        | «      xs d«      xs d|tG        |«      dœ}|tH        k  r. |jJ                  ddditM        | |
| j                  z  |¬«      ¤Ž nU|
| j                  dz   z  }|jO                  |«       tQ        |«       |jK                  d|j                  tS        |«      ¬«       ||d <   dd!l*m+}  ||
tX        z  |d¬"«       tQ        |
«       t        jZ                  |
|«       tQ        |j\                  «       |S # t        $ r}t        d| › d	|› �«      |‚d}~ww xY wc c}}w # 1 sw Y   �Œ}xY w# t        $ r t_        j`                  |
d¬#«       ‚ t        $ r1}t_        j`                  |
d¬#«       t        d$| › d%|
› d	|› �«      |‚d}~ww xY w)&a³  Durably capture the lost WAL generation this process still holds open; return the artifact dir.

    The artifact sits next to the database as ``<name>.retired-wal-<utc ts>-<pid>/`` and holds the
    retired ``<name>-wal`` (and ``-shm`` when still ours), the main image (or its header past the size
    cap) and ``manifest.json`` with identities, sizes, digests and the sidecar generation found at the
    path at capture time. Nothing is merged: whether those frames belong on top of the main file is
    the operator's decision. Raises :class:`RetiredGenerationCaptureError` when the exact generation
    cannot be located or written; no descriptor is ever closed, moved or truncated.
    r_   rÕ   z(no recorded WAL generation identity for z"; refusing to guess it by pathnameNz3this process no longer holds the retired WAL inode rã   zcannot stat the retired WAL of râ   ú%Y%m%d-%H%M%SÚ-r   z.partialr-   TF©ÚparentsÚexist_okz%Y-%m-%dT%H:%M:%SZrw   rû   zçFrames in the captured WAL were committed by the retired generation. Whether they belong on top of the main file now at the path is an operator decision; inspect the copied image with `hermes sessions recover --inspect-only` first.)ÚversionÚdatabaseÚtriggerr¦   Úcaptured_atÚpythonÚsqliteÚwalÚshmÚpath_generation_at_captureÚnoter`   r   z%cannot read the main image header of )rw   r£   r[   ÚmodeÚcopiedz.headerÚheader_only)r#  rä   rå   Úmain)Úatomic_json_write)Ú	sort_keys)Úignore_errorsz.could not write the retired WAL generation of z under )1r   rÛ   r8   rÐ   rÞ   r5   r>   Úst_sizer   r   ÚRETIRED_GENERATION_DIR_SUFFIXÚtimeÚstrftimeÚgmtimeÚgetpidrè   ÚexistsÚmkdirÚ#RETIRED_GENERATION_MANIFEST_VERSIONr4   r   r  r³   Úsqlite3Úsqlite_versionÚlistrý   rh   rc   r/   r0   rI   r  r6   rb   r  Ú'RETIRED_GENERATION_MAIN_IMAGE_MAX_BYTESrì   r   Úwrite_bytesrÙ   rV   Úutilsr'  ÚRETIRED_GENERATION_MANIFESTrª   ÚparentÚshutilÚrmtree)r@   Úsidecar_identityr  Úwal_identityÚwal_fdÚwal_sizer!   ÚstemÚfinalÚnÚstagingre   rg   ÚmanifestÚshm_identityÚshm_fdr[   Ú	main_sizer&  Úheader_filer'  s                        r"   Úcapture_retired_wal_generationrJ  å  s¶  € ô �7‹m€GÜÐ)×-Ñ-¨fÓ5Ò;¸Ó<€LÙÜ+Ø6°w°iÐ?aÐbódð 	dä)¨,Ó7€FØ€~Ü+ØAÀ,ÀÈtÐT[ÐS\Ð]ó_ð 	_ðiÜ—8‘8˜FÓ#×+Ñ+ˆð �l‰lˆ^Ô9Ð:¼4¿=¹=ÈÔZ^×ZeÑZeÓZgÓ;hÐ:iÐijÔkm×ktÑktÓkvÐjwÐx€DØ×Ñ˜dÓ#€EØ	€AØ
�,‰,Œ.˜EŸO™O¨E¯J©J¸Ñ,CÓD×KÑKÔMØ	ˆQ‰ˆØ×!Ñ! T F¨!¨A¨3 -Ó0ˆð �,‰,�.˜EŸO™O¨E¯J©J¸Ñ,CÓD×KÑKÕMð �o‰o˜eŸj™j¨:Ñ5Ó6€Gð4hØ�‰˜d¨UˆÔ3ä:Ü˜G›ØÜ—9‘9“;ÜŸ=™=Ð)=¼t¿{¹{»}ÓMÜ—k‘k×'Ñ'Ó)¨!Ñ,Ü×,Ñ,Ø¤ \Ó 2ð bÜ& v¨w¸'¿,¹,ÈÑ:OÑ/PÐW_Ô`ðbàô :WÐW^Ó9_×9eÑ9eÓ9g÷+iÙ(5¨°�œ˜U›Ñ#ó+ið]ñ$
ˆô$ Ð-×1Ñ1°&Ó9Ò?¸RÓ@ˆÙ?KÔ-¨lÔ;ÐQUˆØÐÜ×$Ñ$¤WÓ-ñ _Ø#-¬t°LÓ/Að #_ÔEUØ˜G w§|¡|°fÑ'<Ñ=ÄBÇHÁHÈVÓDT×D\ÑD\ôF^ð #_�˜‘÷_ô ! ¨!Ô-AÓBˆØˆ>Ü/Ð2WÐX_ÐW`Ð0aÓbÐbÜ—G‘G˜GÓ$×,Ñ,ˆ	Ü,0Ô1GÈÓ1PÒ1VÐTVÓ,WÒ,_Ð[_Ø(1Ô=QÐRXÓ=Yñ [ˆàÔ?Ò?ØˆD�K‰KÑk˜XÐkÔ)9¸'À7ÈWÏ\É\ÑCYÐ`iÔ)jÓkà! W§\¡\°IÑ%=Ñ>ˆKØ×#Ñ# FÔ+Ü˜Ô$Ø�K‰K˜]°×1AÑ1AÌÈVËˆKÔUØˆ�Ñå+Ù˜'Ô$?Ñ?ÀÐUYÕZÜ�GÔÜ
�
‰
�7˜EÔ"Ü�E—L‘LÔ!ð €Løô ò iÜ+Ð.MÈgÈYÐVXÐY\ÐX]Ð,^Ó_ÐehÐhûðiüó0+i÷_ñ _ûô. )ò Ü�‰�g¨TÕ2ØÜò hÜ�‰�g¨TÕ2Ü+Ø<¸W¸IÀWÈWÈIÐUWÐX[ÐW\Ð]ó_Ødgð	hûðhús^   ÁQ' ÆCR É$R
É:AR Ë	ARÌER Ñ'	RÑ0RÒRÒ
R ÒRÒR Ò)S7Ó,S2Ó2S7c                óÌ   — 	 ddl m}  || f|t
        j                  dœ|¤ŽS # t        $ r8 t        j	                  d| «       t        j                  t        | «      fi |¤ŽcY S w xY w)u¥  ``sqlite3.connect`` that registers the open fd so byte-level probes of a live file are
    refused (an ``open()``/``close()`` would cancel every POSIX lock, even a running VACUUM's
    EXCLUSIVE).  The ONLY tolerated fallback is the helper being absent (scaffold/embed installs
    without hermes_cli); a real connection failure must propagate â€” a silent untracked retry
    would disable the guard for that connection.r   )Úconnect_trackedzihermes_cli.sqlite_safe_read unavailable; opening %s untracked (byte-probe guard inactive in this install))Útracking_pathÚ
connect_fn)Úhermes_cli.sqlite_safe_readrL  r   rÅ   rÆ   r3  Úconnectr4   )rj   rM  ÚkwargsrL  s       r"   Ú_connect_tracked_dbrR  >  sg   € ð4Ý?ñ ˜4Ðc¨}ÌÏÉÑcÐ\bÑcÐcøô ò 4Ü�‰ð CØDHô	Jä�‰œs 4›yÑ3¨FÑ3Ò3ð4ús   ‚" ¢>A#Á"A#c                ó´   — 	 | j                  «       sy| j                  «        ddlm}m} |s	 || «      ry || t        d|«      |¬«      S # t        $ r Y yw xY w)u›  First ``probe_bytes`` of *path* read WITHOUT opening a SQLite connection, or None when the probe
    must not run: special files (a FIFO would block), unreadable paths, or â€” unless ``force`` â€” a path
    this process already has a connection to (``close()`` cancels every POSIX lock, even a running
    VACUUM's EXCLUSIVE; see #97568).  ``force=True`` only for offline files (quarantined copies, snapshots).Nr   )Úhas_live_connectionÚread_header_bytes_preopenrR   )rB   Úforce)Úis_filer6   rO  rT  rU  ÚmaxrÄ   )rj   Úprobe_bytesrV  rT  rU  s        r"   Ú_preopen_headerrZ  N  sV   € ð
	Ø�|‰|Œ~ØØ�	‰	Œß^ÙÑ,¨TÔ2ØÙ(¨´c¸"¸kÓ6JÐRWÔXÐXøÜò Ùðús   ‚A “"A ¶A Á	AÁAF©rY  rV  c               óô   — t        j                  t        «      5  ddlm}  || ||¬«      cddd«       S # 1 sw Y   nxY wt        | ||«      }|duxr& |j                  d«       xr t        d„ |D «       «      S )zÑDetect the zeroed state.db signature (0-byte or NUL header).  Prefers
    ``hermes_cli.backup.is_zeroed_sqlite_file``; this copy keeps SessionDB openable without the CLI
    package in constrained embed paths.r   )Úis_zeroed_sqlite_filer[  Nó   SQLite format 3c              3  ó&   K  — | ]	  }|d k(  –— Œ y­w©r   NrÕ   )Ú.0Úbs     r"   ú	<genexpr>z%is_zeroed_state_db.<locals>.<genexpr>h  s   è ø€ ÒOeÐ[\ÐPQÐUVÕPVÑOeùs   ‚)r/   r0   rÄ   Úhermes_cli.backupr]  rZ  rÃ   Úall)rj   rY  rV  r]  Úheads        r"   Úis_zeroed_state_dbrg  _  sx   € ô 
×	Ñ	œYÓ	'ñ QÝ;Ù$ T°{È%ÔP÷Q÷ Qò Qúô ˜4 ¨eÓ4€Dà�tÐÒe D§O¡OÐ4FÓ$GÐ GÒeÌCÑOeÐ`dÔOeÓLeÐes   š5µ>c               óL   — t        | ||«      }|duxr |j                  d«       S )z¦A pre-existing state.db whose first page is not SQLite: 0-byte, NUL, or clobbered page 0
    (#102198).  Zeroed files are the subset :func:`is_zeroed_state_db` names.Nr^  )rZ  rÃ   )rj   rY  rV  rf  s       r"   Ú!has_invalid_sqlite_header_preopenri  k  s/   € ô ˜4 ¨eÓ4€DØ�tÐÒG D§O¡OÐ4FÓ$GÐ GÐGrM   c              #  ó  ‡‡	‡
‡K  — ddl }| j                  | j                  dz   «      }|j                  j	                  dd¬«       |j                  d«      Š
d}	 |j                  «       dk(  rddlŠˆ
ˆfd	„Šˆˆfd
„}ˆˆfd„}nddlŠ	ˆ	ˆ
fd„}ˆ	ˆ
fd„}t        j                  «       |z   }|s	  |«        d}|sŒ|–— 	 |r |«        ‰
j                  «        y# t        $ r1 t        j                  «       |k\  rY ŒAt        j                  d«       Y Œ[w xY w# t        t        f$ r Y Œ_w xY w# ‰
j                  «        w xY w# 	 |r «        n# t        t        f$ r Y nw xY w‰
j                  «        w # ‰
j                  «        w xY wxY w­w)z8Acquire the cross-process lock for path.quarantine.lock.r   Nz.quarantine.lockTr  za+bFÚWindowsc                ój   •— ‰j                  d«       ‰j                  ‰j                  «       | d«       y )Nr   r-   )r2   Úlockingrî   )r#  rC   Úmsvcrts    €€r"   Ú_lockz,quarantine_cross_process_lock.<locals>._lock}  s$   ø€ Ø—‘˜A”Ø—‘˜vŸ}™}›°°aÕ8rM   c                 ó(   •—  ‰ ‰j                   «      S rù   )ÚLK_NBLCK©ro  rn  s   €€r"   rú   z/quarantine_cross_process_lock.<locals>.<lambda>�  s   ø€ ¡ f§o¡oÓ 6€ rM   c                 ó(   •—  ‰ ‰j                   «      S rù   )ÚLK_UNLCKrr  s   €€r"   rú   z/quarantine_cross_process_lock.<locals>.<lambda>‚  s   ø€ ™e F§O¡OÓ4€ rM   c                 ór   •— ‰ j                  ‰j                  «       ‰ j                  ‰ j                  z  «      S rù   )Úflockrî   ÚLOCK_EXÚLOCK_NB©ÚfcntlrC   s   €€r"   rú   z/quarantine_cross_process_lock.<locals>.<lambda>…  s%   ø€  §¡¨F¯M©M«O¸U¿]¹]ÈUÏ]É]Ñ=ZÓ [€ rM   c                 óX   •— ‰ j                  ‰j                  «       ‰ j                  «      S rù   )rv  rî   ÚLOCK_UNry  s   €€r"   rú   z/quarantine_cross_process_lock.<locals>.<lambda>†  s   ø€ ˜eŸk™k¨&¯-©-«/¸5¿=¹=ÓI€ rM   g{®Gáz”?)rÂ   rè   r   r:  r1  r1   Úsystemrn  rz  r,  Ú	monotonicr   Úsleepr   rØ   )rj   ÚtimeoutrÂ   Ú	lock_pathÚacquiredÚ	_try_lockÚ_unlockÚdeadlinero  rz  rC   rn  s           @@@@r"   Úquarantine_cross_process_lockr†  r  sW  ûè ø€ ó Ø—‘˜tŸy™yÐ+=Ñ=Ó>€IØ×Ñ×Ñ 4°$ÐÔ7Ø�^‰^˜EÓ"€FØ€HðØ�?‰?Ó 	Ò)Ûõ9ô 7ˆIÜ4‰GãÜ[ˆIÜIˆGÜ—>‘>Ó# gÑ-ˆÙð"Ù”Ø�ò ð Šð	ÙÙ”	ð �L‰L�Nøô ò "Ü—>‘>Ó# xÒ/ÙÜ—
‘
˜5Ö!ð"ûô œÐ(ò 	Ùð	ûð �L‰L�Nûð	ÙÙ”	ùÜœÐ(ò 	Ùð	úð �L‰L�NøˆF�L‰L�Nÿsµ   †AF
ÁAE  Â.	C Â7E  Â:E  Â?	D ÃF
Ã DÃ9E  Ã;DÄE  ÄDÄE  ÄD(Ä%D+ Ä'D(Ä(D+ Ä+D=Ä=F
Å FÅ	EÅE2ÅEÅE2ÅEÅE2Å!FÅ2FÆFÆF
)Úalready_lockedc               ó´   ‡ — ˆ fd„}|r |«       S t        ‰ «      5 }|s t        j                  d‰ «       	 ddd«       y |«       cddd«       S # 1 sw Y   yxY w)a  Move a non-SQLite state.db (zeroed or clobbered page 0) aside with its -wal/-shm sidecars,
    preserving bytes; return the quarantine path.  A cross-process lock stops two concurrent
    startups racing: the second re-checks under the lock and finds the file gone (or fresh).c            	     ó„  •— ‰j                  «       st        j                  d‰«       y t        ‰«      st        j                  d‰«       y t	        ‰«      rdnd} 	 t        j                  d«      }‰j                  › d| › d|› dt        j                  «       › �}‰j                  |› d	�«      }d
}|j                  «       r-|dz  }‰j                  |› d|› d	�«      }|j                  «       rŒ-	 ‰j                  |«       dD ]r  }t        t!        ‰«      |z   «      }|j                  «       sŒ+t#        j$                  t        «      5  |j                  t        t!        |«      |z   «      «       d d d «       Œt |S # t        $ r d}Y �Œw xY w# t        $ r!}t        j                  d‰|«       Y d }~y d }~ww xY w# 1 sw Y   Œ¿xY w)Nz@quarantine_invalid_state_db: %s already moved by another processzpquarantine_invalid_state_db: %s is no longer invalid (another process quarantined it and a fresh DB was created)ÚzeroedÚnotadbr  Úunknownú.r  z.bakr   r-   z#Failed to quarantine invalid %s: %sr^   )r0  rÅ   Úinfori  rg  r,  r-  rÄ   r   r5   r/  rè   Úrenamer   rÌ   r   r4   r/   r0   )	ÚkindÚtsrA  rñ   rC  r!   re   Úsiderj   s	           €r"   Ú_do_quarantinez3quarantine_invalid_state_db.<locals>._do_quarantineŸ  sœ  ø€ Ø�{‰{Œ}Ü�K‰KÐZÐ\`ÔaØÜ0°Ô6Ü�K‰Kð MØNRôTàÜ-¨dÔ3‰x¸ˆð	Ü—‘˜Ó/ˆBð —)‘)�˜A˜d˜V 1 R D¨¬"¯)©)«+¨Ð7ˆØ�~‰~   d˜mÓ,ˆØˆØ�k‰kŒmØ�‰FˆAØ—>‘> T F¨!¨A¨3¨dÐ"3Ó4ˆDð �k‰k�mð	Ø�K‰K˜Ôð 'ò 	:ˆFÜœ˜D›	 FÑ*Ó+ˆDØ�{‰{�}Ü×(Ñ(¬Ó1ñ :Ø—K‘K¤¤S¨£Y°Ñ%7Ó 8Ô9÷:ð :ð	:ð
 ˆøô% ò 	Ø‹Bð	ûô ò 	Ü�L‰LÐ>ÀÀcÔJÜûð	ú÷:ð :ús6   ÁE7 Ã-F	 Å'F6Å7FÆFÆ		F3ÆF.Æ.F3Æ6F?	uM  quarantine lock for %s not acquired within 5s â€” refusing to quarantine without the cross-process lock. The invalid file is left in place. If sessions fail to load, run `hermes sessions recover --source <state.db> --inspect-only`, or restore a snapshot with `/snapshot list` / `/snapshot restore <id>` (terminal `hermes` chat only).N)r†  rÅ   rÌ   )rj   r‡  r“  r‚  s   `   r"   Úquarantine_invalid_state_dbr”  ›  sf   ø€ ôñ> ÙÓÐÜ	& tÓ	,ð 	 °ÙÜ�L‰Lð fð
 ôð ÷	 ð 	 ñ Ó÷	 ÷ 	 ò 	 ús   ›A¾AÁAc                óH  ‡‡‡— ddl m} t        j                  d«      }t	        j
                  t        «      5  t        t        | «      dz   «      }|j                  «       r|j                  «       j                  nd|d<   ddd«       	  |t        | «      t        | «      dd¬	«      Šd$d%ˆfd„Šd$d&ˆfd„Šd'ˆfd„}	  ‰d«      |d<    ‰d«      |d<   |d   �|d   �|d   |d   z  |d<    ‰d«      |d<    ‰d«      }|�t        |«      nd|d<    ‰d«      |d<    ‰d«      |d<   t	        j
                  t        «      5  ‰j!                  dt"        «      j%                  «       D �ch c]  }|d   ’Œ	 }}t"        D �	ci c]  }	|	|	|v “Œ	 c}	|d<   ddd«        |d«      |d<    |d«      x|d<   }
 |d«      x|d<   }|
€d n|xs d|
k  |d!<   t	        j
                  t        «      5  ‰j!                  d"t&        f«      j)                  «       }|rt+        j,                  |d   «      nd}t/        |t        «      r||d#<   ddd«       t	        j
                  t        «      5  ‰j1                  «        ddd«       |S # 1 sw Y   �ŒøxY w# t        $ r#}t        j                  d
| |«       |cY d}~S d}~ww xY wc c}w c c}	w # 1 sw Y   �Œ1xY w# 1 sw Y   Œ�xY w# 1 sw Y   |S xY w# t	        j
                  t        «      5  ‰j1                  «        ddd«       w # 1 sw Y   w xY wxY w)(u*  Best-effort, strictly read-only stats snapshot of a state.db file: ``mode=ro`` with a short
    timeout so it can run against a *live* database without taking a write lock.  Every field is
    collected independently (a failed pragma/SELECT yields ``None`` for it); never raises.
    Deliberately does NOT instantiate :class:`SessionDB` â€” its constructor runs DDL.
    ``wal_size_bytes`` is 0 when the sidecar is absent; ``fts_storage_version`` None means the
    legacy inline layout; ``fts_rebuild_deferral`` is the durable blocked-repair diagnostic.r   )rR  )r  r  Úfreelist_countÚlogical_size_bytesÚwal_size_bytesÚjournal_modeÚmessagesÚsessionsÚ
fts_tablesÚfts_storage_versionÚfts_rebuild_pendingÚfts_rebuild_high_waterÚfts_rebuild_progressÚfts_rebuild_deferralr_   r˜  NTg       @)rM  Úurir€  z4collect_state_db_stats: cannot open %s read-only: %sc                óv   •— 	 ‰j                  | |«      j                  «       }|r|d   S d S # t        $ r Y y w xY w)Nr   )ÚexecuteÚfetchonerÄ   )ÚsqlÚparamsÚrowÚconns      €r"   Ú_scalarz'collect_state_db_stats.<locals>._scalarä  sD   ø€ ð	Ø—,‘,˜s FÓ+×4Ñ4Ó6ˆCÙ �3�q‘6Ð* dÐ*øÜò 	Ùð	ús   ƒ&, ª, ¬	8·8c                ó4   •—  ‰| |«      }|�t        |«      S d S rù   )rW   )r¦  r§  Úvaluerª  s      €r"   Ú_intz$collect_state_db_stats.<locals>._intê  s#   ø€ Ù˜˜VÓ$ˆØ"Ð.Œs�5‹zÐ8°DÐ8rM   c                ó8   •— 	  ‰d| f«      S # t         $ r Y y w xY w)Nz*SELECT value FROM state_meta WHERE key = ?)rÄ   )rD   r­  s    €r"   Ú	_meta_intz)collect_state_db_stats.<locals>._meta_intí  s*   ø€ ð	ÙÐDÀsÀfÓMÐMøÜò 	Ùð	ús   ƒ	 �	˜zPRAGMA page_countr  zPRAGMA page_sizer  r—  zPRAGMA freelist_countr–  zPRAGMA journal_moder™  zSELECT COUNT(*) FROM messagesrš  zSELECT COUNT(*) FROM sessionsr›  zISELECT name FROM sqlite_master WHERE type = 'table' AND name IN (?, ?, ?)rœ  r�  rŸ  r   Frž  z2SELECT value FROM state_meta WHERE key = ? LIMIT 1r¡  )rÕ   )r¦  r4   Úreturnr   )r¦  r4   r°  úOptional[int])rD   r4   r°  r±  )r   rR  ÚdictÚfromkeysr/   r0   r   r   r4   r0  r6   r*  r   rÄ   rÅ   rÆ   r¤  Ú_FTS_TABLE_NAMESÚfetchallr   r¥  ÚjsonÚloadsÚ
isinstancerØ   )r@   rR  ÚstatsÚwal_pathr!   r¯  Újmr¨  rÝ   ÚtÚ
high_waterÚprogressr  r­  rª  r©  s                @@@r"   Úcollect_state_db_statsr¿  Ì  s/  ú€ õ 1Ü ŸM™Mð +Ró S€Eô
 
×	Ñ	œWÓ	%ñ VÜœ˜G› vÑ-Ó.ˆØ=E¿_¹_Ô=N (§-¡-£/×"9Ò"9ÐTUˆÐÑ÷Vðñ #Ô#3°GÓ#<ÌDÐQXËMØ'+°Sô:ˆö
ö9õðÙ"Ð#6Ó7ˆˆlÑÙ!Ð"4Ó5ˆˆkÑØ�ÑÐ*¨u°[Ñ/AÐ/MØ*/°Ñ*=ÀÀkÑ@RÑ*RˆEÐ&Ñ'Ù"&Ð'>Ó"?ˆÐÑÙÐ*Ó+ˆØ+-¨>¤ B¤¸tˆˆnÑÙ Ð!@ÓAˆˆjÑÙ Ð!@ÓAˆˆjÑô × Ñ ¤Ó+ñ 	NØ'+§|¡|Ø[Ü ó("ç"*¡(£*ö. �S˜“Vð .ˆEð .ô =MÖ"M°q 1 q¨E z¡>Ò"MˆE�,Ñ÷		Nñ (1Ð1FÓ'GˆÐ#Ñ$Ù7@ÐAYÓ7ZÐZˆÐ&Ñ'¨*Ù3<Ð=SÓ3TÐTˆÐ$Ñ%¨Ø0:Ð0B¡uÈÊÐUVÐZdÑHdˆÐ#Ñ$Ü× Ñ ¤Ó+ñ 	7Ø—,‘,ÐSÜ 8Ð:ó<ß<D¹H»Jð á+.”T—Z‘Z  A¡Ô'°DˆFÜ˜&¤$Ô'Ø06�Ð,Ñ-÷	7ô × Ñ ¤Ó+ñ 	Ø�J‰JŒL÷	à€L÷sVñ Vûô ò Ü�‰ÐKÈWÐVYÔZØ�ûðüò>.ùò #N÷		Nñ 	Nú÷	7ð 	7ú÷	à€Lûô × Ñ ¤Ó+ñ 	Ø�J‰JŒL÷	÷ 	ñ 	ýs«   ¸AI5ÂJ Â;BK! Ä='J;Å$J1Å0
J;Å:J6ÆJ;ÆAK! Ç"AKÈ9K! ÉKÉ5I?Ê	J.ÊJ)Ê#J.Ê)J.Ê1
J;Ê;KË K! ËKËK! ËKË!L!Ë;LÌ	L!ÌLÌL!c                ó  — 	 t         j                  j                  t        | «      «      }t        j
                  dk(  r_t        j                  |«      }|j                  |j                  f}t        t        «       D ����ch c]  \  }}}}||k(  sŒ|’Œ c}}}}«      S t        j
                  j                  d«      syt        t        «       D ���	ch c]  \  }}}	||k(  sŒ|’Œ c}	}}«      S c c}}}}w c c}	}}w # t        $ r Y yw xY w)zÿBest-effort count of distinct PIDs holding ``db_path`` open (``/proc/*/fd`` on Linux, libproc
    on macOS); ``None`` on any error or other host, never raises.  Uninspectable processes (other
    users' without root) are skipped, so this is a lower bound.rÀ   rÁ   N)r5   rj   rm   r4   r   rÂ   r6   r9   r:   rV   r¸   rÃ   rŠ   rÄ   )
r@   r·   rE   rw   r¦   r¼   Ú_pathrg   ÚlinkÚ_fd_paths
             r"   Úcount_db_holdersrÄ    sÙ   € ðÜ—‘×!Ñ!¤# g£,Ó/ˆÜ�<‰<˜8Ò#ô —‘˜“ˆBØŸ	™	 2§9¡9Ð-ˆHÜÔ:QÓ:S×iÑiÑ 6  S¨%°ÐW\Ð`hÓWhšÕiÓjÐjÜ�|‰|×&Ñ& wÔ/ØÜÔ3HÓ3J×]Ð]Ñ/˜C  xÈdÐV\Ën’CÔ]Ó^Ð^ùõ jùô ^øÜò ÙðúsH   ‚A<C: Á>C+ÂC+Â	C: ÂC: Â<C: ÃC3ÃC3Ã"C: Ã+C: Ã:	DÄDc                ó,   — | dv xr ||k\  xr |xr d|vS )z9Pure safety predicate for the narrow Desktop holder reap.)r   r-   ÚESTABLISHEDrÕ   )ÚppidÚage_secondsÚmin_age_secondsÚephemeral_backendÚconnection_statusess        r"   Ú"_is_inactive_orphan_desktop_holderrÌ  (  s2   € ð �FˆNò 9˜{¨oÑ=ò 9ÐBSò 9ØÐ%8Ð8ð:rM   c                ó  ‡— t         j                  j                  t         j                  j                  t        j                  | «      «      «      }||dz   |dz   hŠt        t        j                  ˆfd„|D «       «      «      S )zAReturn unique PIDs proven to hold this DB or one of its sidecars.r_   r`   c              3  óP   •K  — | ]  \  }}|d kD  sŒt        |«      ‰v sŒ|–— Œ y­wr`  )r   )ra  r¦   rj   rs   s      €r"   rc  z1_concrete_state_db_holder_pids.<locals>.<genexpr>3  s0   øè ø€ ò \Ù��T¨¨a«Ô4IÈ$Ó4OÐSZÒ4ZŒñ\ùs   ƒ&‘&Ÿ&)r5   rj   Únormcaserk   ra   r5  r²  r³  )r@   r»   Úcanonical_dbrs   s      @r"   Ú_concrete_state_db_holder_pidsrÑ  /  sm   ø€ ä—7‘7×#Ñ#¤B§G¡G§O¡O´B·I±I¸gÓ4FÓ$GÓH€LØ˜\¨FÑ2°LÀ6Ñ4IÐJ€GÜ”—‘ó \Ø$ô\ó \ó ]ð ]rM   )r@   r   rA   rW   rB   rW   r°  ú'Optional[bytes]')r@   r   rB   rW   r°  rÒ  )r@   r   r°  ú'Optional[int]')r@   r   r°  úDict[str, tuple])r°  zDict[str, str])rw   z'Tuple[int, int]'rx   r4   r°  Úbool)r   r4   rx   r4   r°  rÕ  )r°  ú	List[int])r°  úList[Tuple[int, str]])r°  ÚNone)rj   r   r°  rØ  )r°  rÓ  )r3   z%Callable[[int, int], Optional[bytes]]rñ   r   r£   rW   r°  úDict[str, Any])rG   rW   rñ   r   r£   rW   r°  rÙ  )r@   r   rñ   r   r£   rW   r°  rÙ  )r[   rå   r°  rÙ  )r=  rÔ  r  r4   r°  r   rù   )rj   r   rY  rW   rV  rÕ  r°  zOptional[bytes])rj   r   rY  rW   rV  rÕ  r°  rÕ  )g      @)rj   r   r€  Úfloat)rj   r   r‡  rÕ  r°  zOptional[Path])r@   r   r°  rÙ  )r@   r   r°  r±  )rÇ  rW   rÈ  rÚ  rÉ  rÚ  rÊ  rÕ  rË  z	List[str]r°  rÕ  )r@   r   r»   r×  r°  rÖ  )YrÔ   Ú
__future__r   r/   r|   rç   r¶  Úloggingr5   r;  r3  rX   r   Ú	threadingr,  Úpathlibr   Útypingr   r   r   r   r	   r
   Úhermes_state_holdersr   r   Úhermes_state_commonr   r   rb   Ú	getLoggerrÅ   r#   ÚLockr7   r$   Ú__annotations__r%   r´  rI   rL   r\   rh   ru   rz   r�   rŠ   r    r«   r¯   r­   r®   r°   r±   r²   r‘   r›   r§   r¸   r½   rÈ   rÍ   r+  r9  r2  r6  rê   r  r   rÐ   rÙ   rÞ   rö   rý   r   r  rJ  rR  rZ  rg  ri  Úcontextmanagerr†  r”  r¿  rÄ  rÌ  rÑ  rÕ   rM   r"   ú<module>ræ     s  ðòõ #ã Û Û Û Û Û 	Û Û Û Û 
Û Û Ý ß =× =ç H÷ð
 
ˆ×	Ñ	˜>Ó	*€òðF $�Y—^‘^Ó%Ð Ø79Ð Ð4Ó 9Ø)+Ð ˜;Ó +ØOÐ ó"óJ/ó
TóTóó.BóWò2Bð4 Ð ØÐ ØÐ ØÐ ØÐ ð Ð ØÐ ØÐ Ø€òó,ò,óDó&ó<Að* !0Ð Ø-Ð Ø&'Ð #ð
 +<Ð 'Ø&Ð ØÐ ôY Lô Yó
óó4Nó@]ó
ió
bðVØ"2ðVØ=@ðVà	óVórdó ð" :=ÈEõ 	fð ILÐ[`õ Hð ×Ñó%ó ð%ðP GLõ . óbFóRð&:Ø:>ð:ØU^ð:Øcgó:ô]rM   