+
    jHàj;,  ã                  ó  a € 0 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 ]P                  ! ]4      t]P                   ! 4       t/ tR]R&    ! R R]4      t ! R	 R
]4      tR R ltR R ltR R ltR4R R lltR R ltR R ltR R lt ! R R4      t ! R R]]P>                  4      t / t!R]R&   R R  lt"R!RR"R/R# R$ llt#R% R& lt$R' R( lt%R) R* lt&R+^dR,R-/R. R/ llt']PP                  R0R1/R2 R3 ll4       t)R# )5a˜  Lock-safe inspection of SQLite database files.

POSIX advisory locks are cancelled **process-wide** by ``close()`` on *any* fd for that file, so a
bare ``open(db_path, "rb") ... close()`` on a live database drops every lock SQLite holds from this
process (a VACUUM's EXCLUSIVE lock, an in-flight BEGIN IMMEDIATE's RESERVED lock). This module
tracks live connections so raw reads happen only when none exist.
)ÚannotationsN)ÚPath)ÚOptionalzdict[str, int]Ú_live_connectionsc                  ó   € ] tR t^tRtRtR# )ÚUntrackableConnectionErrorz®A connection to a probe-able database could not be tracked. Raised rather than returning an
untracked connection: on these paths tracking is part of the correctness contract.© N©Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r   ó    Ú@/home/ubuntu/.hermes/hermes-agent/hermes_cli/sqlite_safe_read.pyr   r      s   † õZr   r   c                  ó   € ] tR t^!tRtRtR# )ÚLiveConnectionErrorzGA raw file operation was attempted on a database with live connections.r   Nr	   r   r   r   r   r   !   s   † ÝQr   r   c               ó    € V ^8„  d   QhRRRR/# )é   Úpathú
Path | strÚreturnÚstrr   )Úformats   "r   Ú__annotate__r   %   s   € ÷ ñ ˆzð ˜cñ r   c                ó€   €  \        \        V 4      P                  4       4      #   \         d    \        T 4      u # i ; i)z;Canonicalise a *filesystem* path for use as a registry key.)r   r   ÚresolveÚOSError©r   s   &r   Ú_keyr    %   s8   € ðÜ”4˜“:×%Ñ%Ó'Ó(Ð(øÜô Ü�4‹yÒðús   ‚!$ ¤=¼=c               ó    € V ^8„  d   QhRRRR/# )r   Úconnúsqlite3.Connectionr   úOptional[str]r   )r   s   "r   r   r   -   s   € ÷ 	ñ 	Ð/ð 	°Mñ 	r   c                óð   €  V P                  R4      P                  4       pT'       d    \	        T4      ^8  g   T^,          '       g   R# \        T^,          4      #   \        P                   d     R# i ; i)z®The on-disk path of ``main`` as SQLite reports it (immune to ``file:`` URIs, relative paths,
symlinks). ``None`` for in-memory/unnamed databases, which cannot be byte-probed.zPRAGMA database_listN)ÚexecuteÚfetchoneÚsqlite3ÚErrorÚlenr    )r"   Úrows   & r   Ú_canonical_db_pathr,   -   s_   € ðØ�l‰lÐ1Ó2×;Ñ;Ó=ˆ÷ ”#�c“(˜Q”, c¨!§f¤fÙÜ��A•‹<Ðøô	 �=‰=ô Úðús   ‚A ÁA5Á4A5c               ó    € V ^8„  d   QhRRRR/# ©r   r   r   r   ÚNoner   )r   s   "r   r   r   9   s   € ÷ ñ ˜:ð ¨$ñ r   c                ó„   € \         ;_uu_ 4        \        \        V 4      4       RRR4       R#   + '       g   i     R# ; i)z¡Record that this process holds a connection to *path* (prefer :func:`connect_tracked`; this
is for callers managing their own connection objects, and for tests).N©Ú
_live_lockÚ
_track_keyr    r   s   &r   Útrack_connectionr4   9   s!   € ÷ 
ŽÜ”4˜“:Ô÷ 
��Šús   �.®?	c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úkeyr   ÚdeltaÚintr   r/   r   )r   s   "r   r   r   @   s!   € ÷ )ñ )�Cð ) ð )¨Dñ )r   c                ó�   € \         P                  V ^ 4      V,           pV^ 8”  d   V\         V &   R# \         P                  V R4       R# )zQAdjust the live count for an already-canonical key (caller holds ``_live_lock``).N)r   ÚgetÚpop)r6   r7   Ú	remainings   && r   r3   r3   @   s;   € ä!×%Ñ% c¨1Ó-°Õ5€IØ�1„}Ø!*Ô˜#Óä×Ñ˜c 4Ö(r   c               ó    € V ^8„  d   QhRRRR/# r.   r   )r   s   "r   r   r   I   s   € ÷ #ñ #˜Zð #¨Dñ #r   c                ó†   € \         ;_uu_ 4        \        \        V 4      R4       RRR4       R#   + '       g   i     R# ; i)z5Record that one connection to *path* has been closed.Néÿÿÿÿr1   r   s   &r   Úuntrack_connectionr@   I   s!   € ç	ŽÜ”4˜“:˜rÔ"÷ 
��Šús	   �/¯A 	c               ó    € V ^8„  d   QhRRRR/# )r   r6   r   r   r$   r   )r   s   "r   r   r   O   s   € ÷ 
ñ 
˜ð 
 ñ 
r   c                ó¶   € V \         9   d   V # R FE  pV P                  V4      '       g   K  V R\        V4      )  \         9   g   K6  V R\        V4      )  u # 	  R# )a
  The tracked main-database key that makes *key* live, or ``None`` (caller holds ``_live_lock``).

SQLite locks the main file and its WAL sidecars; a raw ``close()`` of any of those
inodes cancels this process's POSIX locks, but the registry is keyed by the main path.N)z-walz-shm)r   Úendswithr*   )r6   Úsuffixs   & r   Ú_live_main_keyrE   O   sX   € ð
 ÔÔØˆ
Û"ˆØ�<‰<˜×Ô C¨¬#¨f«+¨Ð$6Ô:KÖ$KØ�}œ˜V›˜Ð%Ò%ñ #ñ r   c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   Úboolr   )r   s   "r   r   r   \   s   € ÷ 6ñ 6˜jð 6¨Tñ 6r   c                óˆ   € \         ;_uu_ 4        \        \        V 4      4      RJuuRRR4       #   + '       g   i     R# ; i)a  Whether this process holds a connection to *path* (or to the database it is a sidecar of).

Point-in-time answer: a raw open/close right after it returns ``False`` can still race a
new connection. Hold :func:`offline_file_access` across the I/O whenever possible.N)r2   rE   r    r   s   &r   Úhas_live_connectionrI   \   s&   € ÷
 
ŽÜœd 4›jÓ)°Ð5÷ 
��‹ús	   �0°A	c                  óB   a € ] tR t^et$ RtRtR]R&   R V 3R lltRtV ;t	# )Ú_TrackingMixinaµ  Untrack-on-close behaviour, mixable into any Connection subclass.

The real ``close()`` and the unregister happen together under ``_live_lock`` so a concurrent
probe can never observe "no live connection" while this descriptor is still open. Unregister
runs only after ``close()`` succeeds: untracking before a failing close (e.g. cross-thread
ProgrammingError) would leave the FD open while the byte-probe guard thinks nothing is live.
Nz
str | NoneÚ_hermes_tracked_pathc               ó   € V ^8„  d   QhRR/# )r   r   r/   r   )r   s   "r   r   Ú_TrackingMixin.__annotate__p   s   € ÷ 	)ñ 	)�tñ 	)r   c                	óÀ   <€ \         ;_uu_ 4        \        V R R4      p\        SV `  4        Ve   RV n        \        V4       RRR4       R#   + '       g   i     R# ; i)rL   N)r2   ÚgetattrÚsuperÚcloserL   r@   )Úselfr   Ú	__class__s   & €r   rR   Ú_TrackingMixin.closep   sD   ø€ ßŽZÜ˜4Ð!7¸Ó>ˆDô ‰G‰MŒOØÒØ,0�Ô)Ü" 4Ô(÷ �Z�ZŠZús   �2AÁA	)rL   )
r
   r   r   r   r   rL   Ú__annotations__rR   r   Ú__classcell__)rT   s   @r   rK   rK   e   s   ø‡ ñð (,Ð˜*Ó+÷	)ö 	)r   rK   c                  ó   € ] tR t^|tRtRtR# )ÚTrackedConnectionz°A ``sqlite3.Connection`` that untracks its path exactly once on close (callers close in many
places, some via ``contextlib.closing``, so counting closes by hand is unreliable).r   Nr	   r   r   r   rY   rY   |   s   † õ[r   rY   zdict[type, type]Ú_tracked_factory_cachec               ó    € V ^8„  d   QhRRRR/# )r   ÚfactoryÚtyper   r   )r   s   "r   r   r   „   s   € ÷ ñ ˜tð ¨ñ r   c                óî   € V \         P                  J d   \        # \        V \        4      '       d   V # \
        P                  V 4      pVf*   \        RV P                   2\        V 3/ 4      pV\
        V &   V# )a
  Return *factory* augmented with untrack-on-close. Callers legitimately pass their own
``Connection`` subclasses (tests simulate FTS5-less or pragma-failing runtimes); leaving them
untracked would quietly unguard the database, so the tracking ``close()`` is mixed in.ÚTracked)	r(   Ú
ConnectionrY   Ú
issubclassrK   rZ   r:   r]   r
   )r\   Úcacheds   & r   Ú_tracking_factoryrc   „   sp   € ð ”'×$Ñ$Ó$Ü Ð Ü�'œ>×*Ò*ØˆÜ#×'Ñ'¨Ó0€FØ‚~Ü˜ × 0Ñ 0Ð1Ð2´^ÀWÐ4MÈrÓRˆØ*0Ô˜wÑ'Ø€Mr   Útracking_pathÚ
connect_fnc               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   rd   zPath | str | Noner   r#   r   )r   s   "r   r   r   “   s$   € ÷ ñ Ø
ðØ(9ðàñr   c               óD  € Ve   TM\         P                  p\        VP                  R\         P                  4      4      VR&   \
        ;_uu_ 4        V! \        V 4      3/ VB p Ve   \        V4      M
\        V4      pVf   VuuRRR4       # \        V\        4      '       g   \        WV4      pWen        \        V4       VuuRRR4       #   \         d4     \         P                  P                  T4       h   \         d     h i ; ii ; i  + '       g   i     R# ; i)a_  ``sqlite3.connect`` that registers the connection for the lifetime of the fd (released on
``close()``). Use for any database that might be byte-probed (``state.db``, ``kanban.db``).
Open and registration happen together under ``_live_lock`` so a concurrent
:func:`read_header_bytes_preopen` cannot slip between them and cancel this connection's locks.Nr\   )r(   Úconnectrc   r:   r`   r2   r   r    r,   Ú
isinstancerK   Ú_retrofit_trackingrL   r3   Ú	ExceptionrR   )r   rd   re   ÚkwargsÚopenerr"   Úresolveds   &$$,   r   Úconnect_trackedro   “   sþ   € ð &Ò1‰Z´w·±€FÜ)¨&¯*©*°YÄ×@RÑ@RÓ*SÓT€Fˆ9Ñç	ŽÙ”c˜$“iÑ* 6Ñ*ˆð	Ø.;Ò.G”t˜MÔ*ÔM_Ð`dÓMeˆHØÒØ÷ 
Šô ˜d¤N×3Ò3ô *¨$Ó9�Ø(0Ô%Ü�xÔ Ø÷ 
Šøô ô 	ðô ×"Ñ"×(Ñ(¨Ô.ð øô ô ØØðúð	ú÷ 
�ˆúsH   ÁDÁ%CÂ3CÃDÃC9Ã8DÃ9D	ÄDÄD	ÄDÄDÄD	c               ó$   € V ^8„  d   QhRRRRRR/# )r   r"   r#   rn   r   r   r   )r   s   "r   r   r   µ   s#   € ÷ ñ Ð/ð ¸3ð ÐCUñ r   c                óž   € \        V 4      p \        V4      V n        V #   \         d#   p\	        RT RTP
                   R24      ThRp?ii ; i)z·Give an already-open connection untrack-on-close semantics by swapping ``__class__`` for
one mixing in the tracking ``close()`` (used when an opener ignored the factory we asked for).zconnection to z uses factory zn, which cannot release its tracking entry on close; byte-probe safety for this database would be silently lostN)r]   rc   rT   Ú	TypeErrorr   r
   )r"   rn   ÚclsÚexcs   &&  r   rj   rj   µ   sa   € ô ˆt‹*€CðÜ*¨3Ó/ˆŒØˆøÜô Ü(Ø˜X˜J n°S·\±\°Nð C7ð 7ó
ð ð		ûðús   � ŸAªAÁAc               ó    € V ^8„  d   QhRRRR/# )r   r"   r#   r   zOptional[int]r   )r   s   "r   r   r   Ä   s   € ÷ 	ñ 	Ð-ð 	°-ñ 	r   c                ód  €  V P                  R4      P                  4       ^ ,          pV P                  R4      P                  4       ^ ,          p\        V4      \        V4      ,          #   \        P                  \
        \        \        3 d"   p\        P                  RT4        Rp?R# Rp?ii ; i)z¸Logical database size in bytes (``page_count * page_size``, the header field at offset 28)
read via PRAGMA over *conn* so no new fd is opened. ``None`` when the pragmas cannot be read.zPRAGMA page_countzPRAGMA page_sizez$page_count/page_size unavailable: %sN)
r&   r'   r8   r(   r)   rr   Ú
IndexErrorÚ
ValueErrorÚloggerÚdebug)r"   Ú
page_countÚ	page_sizert   s   &   r   Úpage_count_bytesr}   Ä   sƒ   € ðØ—\‘\Ð"5Ó6×?Ñ?ÓAÀ!ÕDˆ
Ø—L‘LÐ!3Ó4×=Ñ=Ó?ÀÕBˆ	Ü�:‹¤ Y£Õ/Ð/øÜ�M‰Mœ9¤j´*Ð=ô Ü�‰Ð;¸SÔAÝûðús   ‚A&A) Á)%B/ÂB*Â*B/c               ó    € V ^8„  d   QhRRRR/# )r   r"   r#   r   zOptional[bool]r   )r   s   "r   r   r   Ð   s   € ÷ ñ Ð%7ð ¸Nñ r   c                ó¸   € \        V 4      pVf   R# \        V 4      pV'       g   R#  \        P                  P	                  V4      V8¬  #   \
         d     R# i ; i)a  Whether the file on disk is at least as long as the header claims ("torn extend" check),
without opening the file (PRAGMA over *conn* + ``stat()``). Advisory in WAL mode: a freshly
committed page may still live in ``-wal`` so the main file legitimately lags.N)r,   r}   Úosr   Úgetsizer   )r"   Úpath_strÚlogicals   &  r   Úfile_length_matches_headerr„   Ð   sV   € ô " $Ó'€HØÒÙÜ˜tÓ$€GßÙðÜ�w‰w�‰˜xÓ(¨GÑ3Ð3øÜô Úðús   ¨!A
 Á
AÁAÚlengthÚforceFc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r   r   r…   r8   r†   rG   r   zOptional[bytes]r   )r   s   "r   r   r   à   s)   € ÷ ñ  Jð ¸3ð ÈTð Ð^mñ r   c          	     ó¬  € \         ;_uu_ 4        V'       g8   \        \        V 4      4      e"   \        P	                  RV 4        RRR4       R#  \        V R4      ;_uu_ 4       pVP                  V4      uuRRR4       uuRRR4       #   + '       g   i     M; i  \         d     RRR4       R# i ; i RRR4       R#   + '       g   i     R# ; i)a_  Read the first *length* bytes of *path* -- only when no connection is live. The ONLY
sanctioned byte-level read of a database file, for first-open validation (real SQLite? zeroed?
overwritten?). Check and open/read/close run together under ``_live_lock`` so a connection
cannot be opened between deciding "nothing is live" and closing this descriptor.Nzqrefusing byte-level read of %s: a live connection exists in this process and close() would cancel its POSIX locksÚrb)r2   rE   r    ry   rz   ÚopenÚreadr   )r   r…   r†   Úhandles   &$$ r   Úread_header_bytes_preopenr�   à   sœ   € ÷
 
Žßœ¬¨T«
Ó3Ò?Ü�L‰LðHàôð ÷ 
‰ð	Ü�d˜D×!Ô! VØ—{‘{ 6Ó*÷ "Ñ!÷ 
Š÷ "×!ûäô 	Ø÷ 
‰ð	úð "÷ 
��ŠúsM   �6CÁBÁ$B
	Á5
BÂ
BÂBÂCÂBÂB4Â(CÂ3B4Â4CÃC	Úwhatr‹   c               ó    € V ^8„  d   QhRRRR/# )r   r   r   rŽ   r   r   )r   s   "r   r   r   ô   s   € ÷ ñ ˜jð °3ñ r   c             #  óô   "  € \         ;_uu_ 4        \        \        V 4      4      pVe-   V\        V 4      8X  d   RMRV 2p\        RV RV  RV R24      hRx € RRR4       R#   + '       g   i     R# ; i5i)a  Hold the connection-lifecycle lock across a raw read of a database file: checking
:func:`has_live_connection` and *then* doing raw I/O is a check/use race (a connection opened
in between loses its POSIX locks to the raw ``close()``). Held only for the raw I/O.NÚitzits main database zRefusing to Ú z: a connection to z« is still open in this process, and raw file access would cancel that connection's POSIX advisory locks. Close all database handles (stop the gateway/dashboard) and retry.)r2   rE   r    r   )r   rŽ   ÚmainÚsubjects   &$  r   Úoffline_file_accessr•   ó   sz   é € ÷
 
ŽÜœd 4›jÓ)ˆØÒØ"¤d¨4£jÔ0‘dÐ8JÈ4È&Ð6QˆGÜ%Ø˜t˜f A d VÐ+=¸g¸Yð GBð BóCð Có
 	÷ 
��Šüs   ‚A8‘A	A$Á
A8Á$A5	Á/	A8)é   )*Ú__conditional_annotations__r   Ú
__future__r   Ú
contextlibÚloggingr€   r(   Ú	threadingÚpathlibr   Útypingr   Ú	getLoggerr
   ry   ÚRLockr2   r   rV   ÚRuntimeErrorr   r   r    r,   r4   r3   r@   rE   rI   rK   r`   rY   rZ   rc   ro   rj   r}   r„   r�   Úcontextmanagerr•   )r—   s   @r   Ú<module>r¢      s  øðôõ #ã Û Û 	Û Û Ý Ý à	×	Ò	˜8Ó	$€ð �_Š_Ó€
à$&Ð �>Ó &ôZ ô Zô
R˜,ô Rõõ	õ÷)õ#õ
õ6÷)ñ )ô.[˜¨×(:Ñ(:ô [ð
 ,.Ð Ð(Ó -õðØ<@ðØMQ÷õDõ	õð Àð ÐTY÷ ð& ×Ñð¸ö ó òr   