+
    „WÝj¬u  ã                   ó¨  € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
HtHt ^ RIHt ^ RIHtHtHt ^ RIHt ^ RIHt ^ RIHtHtHtHtHtHt ]P<                  ! ]4      t R R	 lt!R
 R lt"R R lt#R R lt$R R lt%R R lt&]R R l4       t'R R lt(R R lt)R R lt*R R lt+R&R R llt,R  R! lt-R&R" R# llt.R&R$ R% llt/R# )'a“  SQLite-safe restore and archive-member publish plumbing for backups.

Owns the restore side of ``hermes_cli.backup``: the page-copy SQLite restore
(``_safe_restore_db`` plus the foreign-holder scan) and the zip-member publish
helpers used by ``hermes import`` and ``/snapshot restore``.  Backup
*creation* (``run_backup``, full-zip writing) stays in ``hermes_cli.backup``,
which composes these helpers.
N)ÚclosingÚcontextmanager©ÚPath)ÚListÚOptionalÚTuple)Úread_only_db_uri)Ú_safe_copy_db)Ú_preserve_file_modeÚ_preserve_file_ownerÚ_restore_file_modeÚ_restore_file_ownerÚatomic_replaceÚmkstemp_besidec                ó\   € V ^8„  d   QhR\         R\        \        \        ,          ,          /# )é   Údb_pathÚreturn)r   r   ÚlistÚint)Úformats   "ÚMC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_cli\backup_restore.pyÚ__annotate__r       s#   € ÷ +ñ +¤Tð +¬h´t¼CµyÕ.Añ +ó    c                ó®  € \         P                  P                  R4      '       g   R# R R lpV! \        P                  ! V 4      4      pW"R,           VR,           0p. p \        P
                  ! 4       p\        P                  ! R4       F‘  pVP                  4       '       g   K  \        V4      pWu8X  d   K.  RV R	2p \        P                  ! V4      p	T	 F@  p
 \        P                  ! T R
T
 24      pT! T4      T9   g   K.  TP                  T4        K�  	  K“  	  V#   \         d     K¥  i ; i  \         d     Kj  i ; i  \         d     R# i ; i)u¢  PIDs of OTHER processes holding *db_path* or its WAL/SHM open.

Linux-only ``/proc/<pid>/fd`` scan (no psutil dependency), preserving the
kernel's ``(deleted)`` suffix so an already-unlinked sidecar generation â€”
the #90950 split-brain fingerprint â€” still counts as held. Returns
``None`` when the scan is unavailable (non-Linux, or /proc unreadable);
callers must treat ``None`` as "unknown", not as "no holders".
ÚlinuxNc                ó0   € V ^8„  d   QhR\         R\         /# ©r   Úpathr   ©Ústr)r   s   "r   r   Ú-_foreign_db_holder_pids.<locals>.__annotate__,   s   € ÷ 
ñ 
œð 
¤ñ 
r   c                 ó˜   € \         P                  P                  \         P                  P                  V P	                  R 4      4      4      # )z
 (deleted))Úosr   ÚnormcaseÚabspathÚremovesuffix)r   s   &r   Ú
_canonicalÚ+_foreign_db_holder_pids.<locals>._canonical,   s4   € Ü�w‰w×ÑÜ�G‰G�O‰O˜D×-Ñ-¨lÓ;Ó<ó
ð 	
r   ú-walú-shmz/procz/proc/z/fdÚ/)ÚsysÚplatformÚ
startswithr$   ÚfspathÚgetpidÚlistdirÚisdigitr   ÚOSErrorÚreadlinkÚappend)r   r(   Úcanonical_dbÚwatchedÚpidsÚown_pidÚpid_strÚpidÚfd_dirÚfdsÚfdÚtargets   &           r   Ú_foreign_db_holder_pidsrA       s=  € ô �<‰<×"Ñ" 7×+Ò+Ùõ
ñ
 œbŸiši¨Ó0Ó1€LØ¨FÕ2°LÀ6Õ4IÐJ€GØ€DðÜ—)’)“+ˆÜ—z’z 'Ö*ˆGØ—?‘?×$Ò$ÙÜ�g“,ˆCØŒ~ÙØ˜c˜U #Ð&ˆFðÜ—j’j Ó(�ó �ðÜŸ[š[¨F¨8°1°R°DÐ)9Ó:�Fñ ˜fÓ%¨Ö0Ø—K‘K Ô$Úó ñ +ð* €Køô ô Úðûô
 ô Úðûô
 ô Úðúsa   ÁAE Â?D!ÃE ÃD3Ã6
E ÄE Ä!D0Ä,E Ä/D0Ä0E Ä3EÄ>E ÅEÅE ÅEÅEc                óF   € V ^8„  d   QhR\         R\        \         ,          /# ©r   Údstr   )r   r   )r   s   "r   r   r   N   s   € ÷ ñ œdð ¤x´¥~ñ r   c                ób  €  V P                  4       '       d;   V P                  4       P                  pV^8”  d   \        P	                  RV V4       R# V P                  4       '       d   V P                  RR7      # T #   \        \        3 d"   p\        P	                  RY4        Rp?R# Rp?ii ; i)ag  Writable auth-store path whose lock and publish name the same underlying file.

A file symlink is resolved so refresh writers and restore take the same auth.lock. A hard-linked
auth.json cannot be atomically replaced without splitting a deliberately shared store into two
inodes, so that topology fails closed instead of silently breaking credential sharing.
zjRefusing auth.json restore to %s: it has %d hard links; atomic replacement would split a shared auth storeNF)ÚstrictzCRefusing auth.json restore to %s: cannot resolve store identity: %s)	ÚexistsÚstatÚst_nlinkÚloggerÚerrorÚ
is_symlinkÚresolver4   ÚRuntimeError)rD   ÚlinksÚexcs   &  r   Ú_auth_restore_targetrQ   N   s�   € ðØ�:‰:�<Š<Ø—H‘H“J×'Ñ'ˆEØ�qŒyÜ—‘ð6àØô	ñ Ø,/¯N©N×,<Ò,<ˆs�{‰{ %ˆ{Ó(ÐEÀ#ÐEøÜ”\Ð"ô Ü�‰ÐZÐ\_ÔeÝûðús.   ‚A< ˜8A< ÁA< Á(A< Á:A< Á<B.ÂB)Â)B.c                ó<   € V ^8„  d   QhR\         R\         R\        /# ©r   ÚsrcrD   r   ©r   Úbool)r   s   "r   r   r   f   s!   € ÷ 7ñ 7œDð 7¤tð 7´ñ 7r   c                óš  €  \         P                  ! V P                  RR7      4      p\        T\        4      '       g   \        P                  RT 4       R# \        TP                  R4      \        4      '       gc   \        TP                  R4      \        4      '       g>   \        TP                  R	4      \        4      '       g   \        P                  R
T 4       R# \        TP                  R4      \        4      '       gx   \        TP                  R4      \        4      '       gS   \        TP                  R	4      \        4      '       d.   TR	,          pRT9   d   RTR,          /M/ pRTRT'       d   RMR/p ^ RIHpHpHp ^ RIHp	 \#        T4      p
T
f   R# T! T
R7      ;_uu_ 4        T! T
4      pT	! Y+4      pT! YÊR7       RRR4       R#   \        \        \
        3 d"   p\        P                  RY4        Rp?R# Rp?ii ; i  + '       g   i     R# ; i  \$         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)zÕRestore auth.json without rolling back a live single-use OAuth generation.

The live read and final write share the canonical auth-store lock, so a concurrent refresh
cannot land between preservation and publish.
z	utf-8-sig)Úencodingz&Refusing auth.json restore from %s: %sNFz>Refusing auth.json restore from %s: top level is not an objectÚ	providersÚcredential_poolÚsystemszARefusing auth.json restore from %s: unrecognized auth-store shapeÚnous_portalÚnousÚactive_provider)Ú_auth_store_lockÚ_load_auth_storeÚ_save_auth_store)Ú5merge_snapshot_auth_preserving_live_single_use_grants)Útarget_pathTzFailed to restore %s safely: %s)ÚjsonÚloadsÚ	read_textr4   Ú
ValueErrorÚUnicodeErrorrJ   rK   Ú
isinstanceÚdictÚgetÚhermes_cli.authr_   r`   ra   Úhermes_cli.auth_oauth_grantsrb   rQ   Ú	Exception)rT   rD   Úsnapshot_storerP   r[   rY   r_   r`   ra   rb   r@   Ú
live_storeÚrestoreds   &&           r   Ú_restore_auth_jsonrr   f   sÙ  € ðÜŸš C§M¡M¸; MÓ$GÓHˆô �n¤d×+Ò+Ü�‰ÐUÐWZÔ[Ùä�>×%Ñ% kÓ2´D×9Ò9Ü�n×(Ñ(Ð):Ó;¼T×BÒBÜ�n×(Ñ(¨Ó3´T×:Ò:ä�‰ÐXÐZ]Ô^Ùô �~×)Ñ)¨+Ó6¼×=Ò=Ü˜>×-Ñ-Ð.?Ó@Ä$×GÒGÜ�~×)Ñ)¨)Ó4´d×;Ò;à  Õ+ˆØ8EÈÔ8P�V˜W ]Õ3Ñ4ÐVXˆ	ð ˜Ø¯™v¸ð
ˆð
ßXÑXõ	
ô & cÓ*ˆØŠ>ÙÙ¨&×1Ö1Ù)¨&Ó1ˆJÙLØóˆHñ ˜XÕ:÷ 2ñ øôY ”Z¤Ð.ô Ü�‰Ð=¸sÔHÝûðú÷L 2Ö1ñ ûÜô Ü�‰Ð6¸ÔAÝûðúsS   ‚&G Å:H ÆH Æ,H
ÇH ÇHÇ&HÈHÈ
H	ÈH ÈH ÈI
È)IÉI
c                ó<   € V ^8„  d   QhR\         R\         R\        /# rS   rU   )r   s   "r   r   r       s!   € ÷ ñ œ$ð ¤Tð ¬dñ r   c           	     ó\  € ^ RI HpHp ^ RIHp V! V 4      pVR,          '       g    \
        P                  RWR,          4       R#  V! VP                  4       P                  VP                  4       P                  .4      ;_uu_ 4        \        W4      ;_uu_ 4       p\        Wa4      uuRRR4       uuRRR4       #   + '       g   i     M; i RRR4       R#   + '       g   i     R# ; i  T\        \        P                  3 d"   p\
        P                  RT4        Rp?R# Rp?ii ; i)	a  Restore only while holding the destination authority's maintenance reservation.

Returns ``False``, without touching *dst*, when *src* fails the SQLite
integrity check, when the runtime owns the profile, or when a canonical
``state.db`` epoch floor cannot be proven.
©ÚOwnershipConflictÚexclusive_maintenance©Úverify_sqlite_integrityÚvalidz#Refusing SQLite restore from %s: %sÚmessageFNz%s)Úgateway.runtime_ownershiprv   rw   Úhermes_cli.backupry   rJ   rK   ÚabsoluteÚparentrM   Ú_restore_epoch_sourceÚ_restore_db_pagesr4   Úsqlite3ÚError)rT   rD   rv   rw   ry   Úsource_checkÚpreparedrP   s   &&      r   Ú_safe_restore_dbr†       sÓ   € ÷ SÝ9ñ
 +¨3Ó/€LØ˜× Ô Ü�‰Ð:¸CÈiÕAXÔYÙðÙ" C§L¡L£N×$9Ñ$9¸3¿;¹;»=×;OÑ;OÐ#P×QÕQÜ& s×0Ô0°HÜ(¨Ó7÷ 1Ñ0÷ RÒQß0×0úÐ0÷ R×Q×QÒQûð œw¬¯©Ð6ô Ü�‰�T˜3ÔÝûðúsU   ÁAC. ÂCÂB<	Â'
CÂ1
C. Â<CÃCÃC. ÃC+	Ã%C. Ã+C. Ã.D+Ä
D&Ä&D+c                ó0   € V ^8„  d   QhR\         R\        /# r   ©r   r   )r   s   "r   r   r   º   s   € ÷ )ñ )œð )¤#ñ )r   c                 óª  € V P                  4       '       g   ^ # \        \        P                  ! \	        V 4      RR7      4      ;_uu_ 4       pVP                  R4      P                  4       '       g    RRR4       ^ # VP                  R4      P                  4       pV'       d   \        V^ ,          4      M^ uuRRR4       #   + '       g   i     R# ; i)é    T©Úuriz6SELECT 1 FROM sqlite_master WHERE name='runtime_epoch'Nz1SELECT epoch FROM runtime_epoch WHERE singleton=1)rG   r   r‚   Úconnectr	   ÚexecuteÚfetchoner   )r   ÚconnÚrows   &  r   Ú_restore_epochr’   º   sŒ   € Ø�;‰;�=Š=ÙÜ	”—’Ô!1°$Ó!7¸TÔB×	CÔ	CÀtØ�|‰|ÐTÓU×^Ñ^×`Ò`Ø÷ 
DÑ	Cð �l‰lÐNÓO×XÑXÓZˆß!Œs�3�q•6Œ{ q÷	 
D×	C×	CÓ	Cús   Á'CÁ<&CÂ#CÃC	c                ó0   € V ^8„  d   QhR\         R\        /# rC   rˆ   )r   s   "r   r   r   Ä   s   € ÷ ñ ¤Dð ¬Sñ r   c                 óŠ   €  \        V 4      #   \        \        P                  \        3 d   p\        R T  R24      ThRp?ii ; i)z+Cannot read the canonical runtime epoch at z–; refusing in-place restore. Use `hermes sessions recover --source <snapshot> --output <separate-path>` to salvage transcripts into a separate output.N)r’   r4   r‚   rƒ   rg   )rD   rP   s   & r   Ú_restore_destination_epochr•   Ä   sP   € ðÜ˜cÓ"Ð"øÜ”W—]‘]¤JÐ/ô ÜØ9¸#¸ð ?=ð =ó
ð ð		ûðús   ‚
 � A­=½Ac                ó0   € V ^8„  d   QhR\         R\         /# )r   rT   rD   r   )r   s   "r   r   r   Ð   s   € ÷ ñ œtð ¬$ñ r   c           	   #  óî  "  € VP                   R8w  d&   VP                  4       P                   R8w  d   V x € R# \        V4      pV\        V 4      8:  d   V x € R# \        P
                  ! RVP                  R7      ;_uu_ 4       p\        V4      R,          p\        W4      '       g   \        R4      h\        \        P                  ! V4      4      ;_uu_ 4       pVP                  R4       VP                  RV34       VP                  4        RRR4       VP                  V P!                  4       P"                  4       Vx € RRR4       R#   + '       g   i     LG; i  + '       g   i     R# ; i5i)a  Stage an epoch floor into the image BEFORE publishing it atomically.

Updating the destination after backup() would leave a crash window that
reuses an older worker epoch. Never modify the user's source snapshot.
Full restores retain all admissions/worker receipts; normal startup advances
this floor and reconciles started work to unknown, not replayable queued work.
An unreadable destination cannot prove its epoch floor: use transcript salvage
into a separate output rather than silently restoring with a recycled epoch.
ústate.dbNz.restore-epoch-)ÚprefixÚdirz3Unable to stage a restore with a safe runtime epochzÄCREATE TABLE IF NOT EXISTS runtime_epoch (
                singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
                epoch INTEGER NOT NULL CHECK (epoch > 0), instance_id TEXT NOT NULL)z´INSERT INTO runtime_epoch VALUES(1,?, 'offline-restore')
                ON CONFLICT(singleton) DO UPDATE SET epoch=excluded.epoch,
                instance_id=excluded.instance_id)ÚnamerM   r•   r’   ÚtempfileÚTemporaryDirectoryr   r   r
   r4   r   r‚   r�   rŽ   ÚcommitÚchmodrH   Úst_mode)rT   rD   ÚfloorÚworkr…   r�   s   &&    r   r€   r€   Ï   s  é € ð ‡x�x�:Ô #§+¡+£-×"4Ñ"4¸
Ô"BØŠ	ÙÜ& sÓ+€EØ”˜sÓ#Ô#ØŠ	ÙÜ	×	$Ò	$Ð,=À3Ç:Á:×	NÕ	NÐRVÜ˜“: 
Õ*ˆÜ˜S×+Ò+ÜÐOÓPÐPÜ”W—_’_ XÓ.×/Ô/°4Ø�L‰Lð Xô Yð �L‰Lð 4à6;°Xô?ð �K‰KŒM÷ 0ð 	�‰�s—x‘x“z×)Ñ)Ô*ØŠ÷ 
OÑ	N÷ 0×/ú÷	 
O×	NÐ	Nüs7   ‚BE5ÂAE!Ã5E	Ä5E!Å
E5ÅEÅE!Å!E2	Å,	E5c                ó<   € V ^8„  d   QhR\         R\         R\        /# rS   rU   )r   s   "r   r   r   ñ   s&   € ÷ |ñ |œ4ð |¤dð |¬tñ |r   c           	     ó|  € Rp \         P                  ! \        V4      4      p VP                  R4       \         P                  ! \        V 4      RR7      p VP                  V4       VP                  4        VP                  4         V P                  4       P                  pVP                  V4       R#   \         P                   d!   p\
        P                  RY4        Rp?L±Rp?ii ; i  TP                  4        i ; i  \         d"   p\
        P                  RY4        Rp?R# Rp?ii ; i  \         P                  \        \        3 Edé   p\
        P                  RYT4       TeK    TP                  4        M8  \         P                   d!   p\
        P                  RY4        Rp?MRp?ii ; i^ R	IHpHp  \'        T4      p	T	'       d   \
        P)                  R
Y4        Rp?R# T! TRR7      ;_uu_ 4        TP*                  RTP,                   R2,          p
\.        P0                  ! Y
4       TP3                  RR7       R F5  pTP5                  TP,                  T,           4      P3                  RR7       K7  	  \.        P6                  ! \        T
4      \        T4      4       RRR4       M  + '       g   i     M; i Rp?R#   T d&   p\
        P)                  RY4        Rp? Rp?R# Rp?i\        \        3 d'   p\
        P)                  RYT4        Rp? Rp?R# Rp?ii ; iRp?ii ; i)u€  Restore a SQLite database from snapshot *src* into live *dst*.

Uses SQLite's backup() API to write snapshot pages into the live
database file, preserving the file's inode and WAL state so that
any other process still holding the DB open (gateway, dashboard,
another CLI session) sees the restored data on the next read â€”
instead of continuing to serve stale cached pages from a replaced
inode.

The old approach was ``unlink() + move()``, which replaced the file
under any live connection.  SQLite connections cache pages in
per-connection page caches keyed by inode; after an unlink+move the
old inode still existed (the live connection held a reference), so
that connection continued serving the pre-restore data while new
connections saw the restored snapshot â€” a partial/inconsistent
state (issue #65942).

By writing pages through the backup API the file inode is preserved,
the WAL journal is updated correctly, and all connections (old and
new) converge on the restored data.

Falls back to the unlink+move approach on failure ONLY when no other
process or in-process connection holds the file: replacing the inode
under a live holder is the #90950 split-brain, so that branch fails
closed (returns ``False``) and the caller reports the file as skipped.
Callers go through ``_safe_restore_db``, which validates *src* and holds
the maintenance reservation and epoch floor around this publication.
NzPRAGMA wal_checkpoint(TRUNCATE)z,Pre-restore WAL checkpoint of %s skipped: %sTr‹   z<Restored %s but could not apply the snapshot's file mode: %sz+SQLite safe restore failed for %s -> %s: %sz1Closing %s before the fallback restore failed: %s)ÚLiveConnectionErrorÚoffline_file_accesszpRefusing unlink+move restore of %s: process(es) %s still hold the database or its WAL open. Stop them and retry.Fzunlink+move restore of)ÚwhatÚ.z.snap_restore)Ú
missing_okzkRefusing unlink+move restore of %s: %s Close the in-process database handles (or restart Hermes) and retry.z-Fallback restore also failed for %s -> %s: %s)r*   r+   z-journal)r‚   r�   r!   rŽ   rƒ   rJ   Údebugr	   ÚbackupÚcloserH   r    rŸ   r4   Úwarningrg   Úhermes_cli.sqlite_safe_readr¥   r¦   rA   rK   r   r›   ÚshutilÚcopy2ÚunlinkÚ	with_nameÚmove)rT   rD   Údst_connrP   Úsrc_connÚmodeÚ	close_excr¥   r¦   ÚholdersÚtmpÚ_sidecar_suffixÚexc2s   &&           r   r�   r�   ñ   s“  € ð: .2€Hð^Ü—?’?¤3 s£8Ó,ˆð	Sð ×ÑÐ>Ô?ô —?’?Ô#3°CÓ#8¸dÔCˆð	Ø�O‰O˜HÔ%à�N‰NÔØ�‰Ôð	eØ—8‘8“:×%Ñ%ˆDØ�I‰I�dŒOñ øô! �}‰}ô 	Sä�L‰LÐGÈ×RÑRûð	Sûð �N‰NÕûô ô 	eä�N‰NÐYÐ[^×dÐdÙûð	eûô �M‰Mœ7¤JÐ/õ GÜ�‰ÐDÀcÐPSÔTð ÒðbØ—‘Õ øÜ—=‘=ô bÜ—‘ÐPÐRU×aÑaûðbú÷	
ð
6	Ü-¨cÓ2ˆGßô —‘ðNàôõ
 ñ % SÐ/G×HÖHØ—j‘j Q s§x¡x j°Ð#>Õ>�Ü—’˜SÔ&Ø—
‘
 d�
Ô+ó (D�OØ—M‘M #§(¡(¨_Õ"<Ó=×DÑDÐPTÐDÖUñ (Dä—’œC ›H¤c¨#£hÔ/÷! I×H×HÐHúõ" øØ"ô 	Ü�L‰LðBàô÷
 ‘5ûÜœÐ$ô 	Ü�L‰LÐHÈ#ÐTXÔYß‘5ûð	þðKGús   „D2 ¤B6 µ!D2 ÁC. Á( D2 Â	+D Â6C+ÃC&Ã!D2 Ã&C+Ã+D2 Ã.D Ä D2 ÄD/ÄD*Ä$D2 Ä*D/Ä/D2 Ä2!L;ÅL6Å/F Å?L6Æ F5ÆF0Æ+L6Æ0F5Æ5L6Ç)KÇ0KÈB.J9Ê0	KÊ9K	ËKËL3ËK9Ë/L6Ë9L3ÌL3ÌL.Ì$L6Ì.L3Ì3L6Ì6L;c                óf   € V ^8„  d   QhR\         P                  R\        \        \        3,          /# ©r   Úzfr   )ÚzipfileÚZipFileÚtuplerV   r!   )r   s   "r   r   r   p  s'   € ÷ ñ œWŸ_™_ð ´´t¼S°yÕ1Añ r   c                óØ   € V P                  4       pV'       g   R# 0 Rmp\        4       pV F1  p\        V4      P                  pWR9   g   K   VP	                  V4       K3  	  V'       g   R# R# )zDCheck that a zip looks like a Hermes backup.

Returns (ok, reason).
)Fzzip archive is empty>   ú.envr˜   úconfig.yaml)FzZzip does not appear to be a Hermes backup (no config.yaml, .env, or state databases found))TÚ )ÚnamelistÚsetr   r›   Úadd)r¾   ÚnamesÚmarkersÚfoundÚnÚbasenames   &     r   Ú_validate_backup_ziprÎ   p  se   € ð
 �K‰K‹M€EßØ,Ð,ò 2€GÜ‹E€EÛˆä˜“7—<‘<ˆØÖØ�I‰I�hÖñ	 ÷ ð
ð 	
ð
 €Or   c                óD   € V ^8„  d   QhR\         P                  R\        /# r½   )r¿   rÀ   r!   )r   s   "r   r   r   ‹  s   € ÷ ñ ”w—‘ð ¬3ñ r   c                ó¢  € V P                  4        Uu. uF  qP                  R4      '       d   K  VNK  	  ppV'       g   R# V Uu. uF  p\        V4      P                  NK  	  ppV Uu0 uF  p\	        V4      ^8”  g   K  V^ ,          kK   	  pp\	        V4      ^8X  d!   VP                  4       pVR9   d
   VR,           # R# u upi u upi u upi )z½Detect if the zip has a common directory prefix wrapping all entries.

Some tools zip as `.hermes/config.yaml` instead of `config.yaml`.
Returns the prefix to strip (empty string if none).
r,   rÅ   >   Úhermesú.hermes)rÆ   Úendswithr   ÚpartsÚlenÚpop)r¾   rÌ   rÉ   Ú
parts_listÚpÚfirst_partsr™   s   &      r   Ú_detect_prefixrÚ   ‹  s²   € ð Ÿ™œÓ=™�1¯Z©Z¸¯_�QˆQ™€EÐ=ßÙñ */Ó/© A”$�q“'—-”-©€JÐ/ñ ",Ó:¡˜A¬s°1«v¸©z”4�1�Q—4�4¡€KÐ:Ü
ˆ;Ó˜1ÔØ—‘Ó"ˆàÐ*Ô*Ø˜C•<Ðáùò >ùò
 0ùò ;s   “C°CÁCÁ*CÂCc                ó:   € V ^8„  d   QhR\         \        ,          /# )r   r   )r   r   )r   s   "r   r   r   £  s   € ÷ ñ ¤¬¥ñ r   c                 ó”   €  \         P                  ! ^?4      p \         P                  ! V 4       RT ( ,          #   \         d     R# i ; i)u¾  Return the mode ``open(path, "wb")`` gives a file it has to create.

``tempfile.mkstemp`` always creates at 0600, so staging an import through a
temp file would tighten every *newly created* file to owner-only â€” the same
hazard ``utils._restore_file_mode`` documents for Docker/NAS volume mounts
that rely on broader permissions.  The umask can only be read by setting it,
so this is resolved once per import rather than once per member.  The probe
installs a *restrictive* mask rather than 0 so that anything another thread
creates inside the two-syscall window is owner-only, never world-writable.
Returns ``None`` if the umask cannot be read, in which case the caller
leaves mkstemp's mode alone.
Ni¶  )r$   Úumaskr4   )Úcurrents    r   Ú_default_new_file_moderß   £  sC   € ðÜ—(’(˜5“/ˆÜ
�Š�Ôð �G�8ÕÐøô ô Úðús   ‚,8 ¸AÁAc          
      óv   € V ^8„  d   QhR\         P                  R\        R\        R\        \
        ,          RR/# ©r   r¾   Úmemberr@   Únew_file_moder   N©r¿   rÀ   r!   r   r   r   )r   s   "r   r   r   ¸  sI   € ÷ bñ bÜ�‰ðbäðbô ðbô œC•=ð	bð
 
ñbr   c           	     óÊ  € \        V4      p\        V4      pVf   TpM-V\        P                  \        P                  ,          ( ,          p\        VRVP                  R,           R2RR7      w  rg \        P                  ! VR4      ;_uu_ 4       pVeS   \        \        R4      '       d'   \        P                  ! VP                  4       V4       M\        P                  ! Wt4       V P                  V4      ;_uu_ 4       p	\        P                  ! W˜4       RRR4       VP!                  4        \        P"                  ! VP                  4       4       RRR4       \%        \'        Wr4      4      p
\)        W¥4       \+        W¤4       R#   + '       g   i     Lx; i  + '       g   i     LO; i  \,         d+     \        P.                  ! T4       h   \0         d     h i ; ii ; i)u-	  Restore one zip member onto *target* with no truncation window.

``open(target, "wb")`` truncates the user's existing file to zero *before*
any replacement bytes exist.  A Ctrl-C, an ENOSPC, a corrupt member, or a
crash between the truncate and the write therefore leaves that file empty
with nothing behind it â€” during ``hermes import``, which is the
disaster-recovery path a user reaches for *because* they already lost
something.  Staging into the target's own directory and publishing with a
rename means the target only ever moves from its old contents to the
complete new contents.

``atomic_replace`` rather than a bare ``os.replace``: it resolves a
symlinked target first, so a deployment that links ``config.yaml`` into a
dotfiles repo keeps the link instead of having it silently swapped for a
regular file (GitHub #16743), and it falls back to copy/fsync/unlink on
``EXDEV``/``EBUSY`` for cross-device and bind-mount installs.  That
fallback uses ``shutil.copyfile``, which does truncate in place, so on the
cross-device path the guarantee above degrades to today's behaviour rather
than improving on it; closing that belongs in ``utils.atomic_replace``,
where every atomic writer in the repo would benefit, not here.

Permission bits *and* ownership are carried across the replace so routing
through mkstemp does not change the file the caller would otherwise have
produced.  ``os.replace`` swaps in a temp file owned by the *writing* user,
so without the chown a ``sudo hermes import`` would silently re-own every
restored file to root â€” on the disaster-recovery path, and on exactly the
Docker/NAS installs ``utils._restore_file_owner`` documents.  Both concerns
delegate to the shared ``utils`` helpers rather than being re-derived here.
The temp file is removed on any failure so a partial import leaves no
residue.

The one bit of the old file *not* carried across is setuid/setgid.  The
replacement bytes come out of the zip, so preserving those would let an
archive take over the identity an existing privileged file executes as â€”
and unlike the other ``utils`` writers, which re-serialize content this
process produced, the trust boundary here is an untrusted archive.  The
mask is applied once, before the temp file is chmod'd, so neither the
pre-replace ``fchmod`` nor the post-replace restore can re-elevate the
target.
Nr¨   ºNéP   Nz.partial)r™   ÚsuffixÚwbÚfchmod)r   r   rH   ÚS_ISUIDÚS_ISGIDr   r›   r$   ÚfdopenÚhasattrrê   ÚfilenorŸ   Úopenr¯   ÚcopyfileobjÚflushÚfsyncr   r   r   r   ÚBaseExceptionr±   r4   )r¾   râ   r@   rã   r¶   Úownerr?   Útmp_namerD   rT   Ú	real_paths   &&&&       r   Ú_extract_member_atomicallyrø   ¸  sg  € ôb ˜vÓ&€DÜ  Ó(€EØ‚|Ø‰ð 	”$—,‘,¤§¡Õ-Ð.Õ.ˆô " &°1°V·[±[ÀÕ5EÐ4FÀaÐ1HÐQ[Ô\�L€BðÜ�YŠY�r˜4× Ô  CØÒô œ2˜x×(Ò(Ü—I’I˜cŸj™j›l¨DÕ1ä—H’H˜XÔ,ð —‘˜—” CÜ×"Ò" 3Ô,÷ !à�I‰IŒKÜ�HŠH�S—Z‘Z“\Ô"÷! !ô" œ¨Ó9Ó:ˆ	ô
 	˜IÔ-Ü˜9Ö+÷ !—ú÷ !× ûô0 ô ð	Ü�IŠI�hÔð 	øô ô 	ØØð	úðúsa   Á1F- ÂA0FÄ F	Ä<FÅ2F- ÆFÆFÆF*	Æ%F- Æ-G"Æ9GÇG"ÇGÇG"ÇGÇG"c                óh   € V ^8„  d   QhR\         R\        \        \        \        3,          ,          /# r   )r   r   rÁ   r   )r   s   "r   r   r     s'   € ÷ ñ œdð ¤x´´c¼3°hµÕ'@ñ r   c                ó,  € V P                  4       '       g   R#  \        P                  ! \        V 4      RR7      p TP                  R4      P                  4       ^ ,          pTP                  R4      P                  4       ^ ,          p\        T4      \        T4      3TP                  4        #   \        P                   d     R# i ; i  \        P                  \        \        3 d     TP                  4        R# i ; i  TP                  4        i ; i)u‘  Return ``(sessions, messages)`` stored in the session database *path*.

Read-only and best effort.  ``None`` means "unknown" â€” a missing file, a
database that is not a Hermes session store, or one that cannot be read.
Callers must never read ``None`` as "zero rows": acting on an unreadable
database would mask the very loss this count exists to surface.  Same
contract as :func:`_count_cron_jobs`.
NTr‹   zSELECT COUNT(*) FROM sessionszSELECT COUNT(*) FROM messages)Úis_filer‚   r�   r	   rƒ   rŽ   r�   r   r¬   Ú	TypeErrorrg   )r   r�   ÚsessionsÚmessagess   &   r   Ú_count_session_rowsrÿ     sÒ   € ð �<‰<�>Š>ÙðÜ�ŠÔ/°Ó5¸4Ô@ˆðØ—<‘<Ð ?Ó@×IÑIÓKÈAÕNˆØ—<‘<Ð ?Ó@×IÑIÓKÈAÕNˆÜ�8‹}œc (›mÐ+ð 	�
‰
�øô �=‰=ô Úðûô �M‰Mœ9¤jÐ1ô Øà�
‰
Žðûð 	�
‰
�ús5   š!B. ¼A!C
 Â.CÃCÃ
 C>Ã*D Ã=C>Ã>D ÄDc          
      óv   € V ^8„  d   QhR\         P                  R\        R\        R\        \
        ,          RR/# rá   rä   )r   s   "r   r   r   6  sB   € ÷ )ñ )Ü�‰ð)äð)ô ð)ô œC•=ð	)ð
 
ñ)r   c                 ó6  € ^ RI HpHp  V! VP                  4       P                  VP                  4       P                  .4      ;_uu_ 4        \        WW#4       RRR4       R#   + '       g   i     R# ; i  T d   p\        \        T4      4      ThRp?ii ; i)rŠ   ru   N)	r|   rv   rw   r~   r   rM   Ú_import_db_member_exclusiver4   r!   )r¾   râ   r@   rã   rv   rw   rP   s   &&&&   r   Ú_import_db_memberr  6  sq   € ÷ Sð)Ù" F§O¡OÓ$5×$<Ñ$<¸f¿n¹nÓ>N×>UÑ>UÐ#V×WÕWÜ'¨°FÔJ÷ X×W×WÒWûàô )Ü”c˜#“hÓ SÐ(ûð)ús6   ŠAA7 ÁA#ÁA7 Á#A4	Á.A7 Á4A7 Á7BÁ>BÂBc          
      óv   € V ^8„  d   QhR\         P                  R\        R\        R\        \
        ,          RR/# rá   rä   )r   s   "r   r   r   D  sI   € ÷ Nñ NÜ�‰ðNäðNô ðNô œC•=ð	Nð
 
ñNr   c           
     ó’  € VP                  4       '       g\   \        V4      pV'       d;   \        VP                   RRP	                  R \        V4       4       4       R24      h\        WW#4       R# \        V4      p\        V4      p\        P                  ! \        VP                  4      RVP                  R,           R2RR	7      w  rx \        P                  ! VR
4      ;_uu_ 4       p	V P                  V4      ;_uu_ 4       p
\         P"                  ! W©4       RRR4       V	P%                  4        \        P&                  ! V	P)                  4       4       RRR4       \+        \-        V4      V4      '       g;   ^ RIHp V! \-        V4      4      R,          '       g   \        R4      h\        R4      h\3        W&4       \5        W%4        \        P6                  ! V4       R#   + '       g   i     LÐ; i  + '       g   i     L§; i  \         d     R# i ; i   \        P6                  ! T4       i   \         d     i i ; i; i)u�  Publish a SQLite ``.db`` member onto *target* without replacing its inode.

``_extract_member_atomically`` publishes with a rename.  For an ordinary
file that is the safest write available; for a live SQLite database it is
the #65942 / #90950 corruption class.  A gateway, dashboard, or WebUI
process holding the database open keeps its descriptor on the now-unlinked
inode: it goes on serving pre-import pages and writing sessions that no
other process will ever see, and any sidecar WAL left beside the new file
describes the database that was just unlinked.  Nothing fails, so nothing
is reported â€” the sessions simply are not there afterwards (issue #100960).

``hermes import`` is the disaster-recovery path, so that failure mode lands
on users who have already lost something once.  Route the member through
the same ``_safe_restore_db`` page copy that ``/snapshot restore`` has used
since #65942: the live inode is preserved, every open connection converges
on the imported data, and the sidecars are handled there.

Raises ``OSError`` when the database could not be replaced safely, so the
caller reports a skipped file instead of counting a silent success.
z) was deleted but is still open in PID(s) z, c              3   ó8   "  € T F  p\        V4      x € K  	  R # 5i©Nr    )Ú.0r<   s   & r   Ú	<genexpr>Ú._import_db_member_exclusive.<locals>.<genexpr>g  s   é € ÐA±¨#œS ŸX˜X³ùs   ‚zs; publishing a new file would leave them writing an invisible database. Stop those processes and re-run the import.Nr¨   ræ   z	.dbimport)rš   r™   rè   ré   rx   rz   z[the archived database failed its integrity check; the existing database was left untouched.zšlive-safe restore refused or failed; the existing database was left untouched. Stop the gateway/dashboard processes holding it open and re-run the import.)rG   rA   r4   r›   ÚjoinÚsortedrø   r   r   rœ   Úmkstempr!   r   r$   rí   rð   r¯   rñ   rò   ró   rï   r†   r   r}   ry   r   r   r±   )r¾   râ   r@   rã   r¸   r¶   rõ   r?   rö   rD   rT   ry   s   &&&&        r   r  r  D  sÏ  € ð4 �=‰=�?Š?ô
 *¨&Ó1ˆßÜØ—;‘;�-ÐHØ—9‘9ÑA´¸´ÓAÓAÐBð Chðhóð ô
 	# 2¨vÔEÙô ˜vÓ&€DÜ  Ó(€Eä×#Ò#Ü�—‘Ó¨¨6¯;©;°sÕ+;Ð*<¸AÐ'>À{ô�L€BðÜ�YŠY�r˜4× Ô  Cð —‘˜—” CÜ×"Ò" 3Ô,÷ !à�I‰IŒKÜ�HŠH�S—Z‘Z“\Ô"÷ !ô  ¤ X£°×7Ò7ÝAñ +¬4°«>Ó:¸7×CÔCÜð3óð ô ð.óð ô
 	˜FÔ*Ü˜6Ô(ð	Ü�IŠI�hÖ÷/ !—ú÷ !× ûô6 ô 	Úð	ûð	Ü�IŠI�hÕøÜô 	Ùð	ýss   ÃH Ã+G7ÄG$	Ä<G7ÅA3H ÇH
 Ç$G4Ç/G7Ç7H	ÈH È
HÈHÈIÈH5È4IÈ5IÉ IÉIÉIr  )0Ú__doc__rd   Úloggingr$   r¯   r‚   rH   r-   rœ   r¿   Ú
contextlibr   r   Úpathlibr   Útypingr   r   r   Úhermes_state_holdersr	   Úhermes_cli.backup_sqliter
   Úutilsr   r   r   r   r   r   Ú	getLoggerÚ__name__rJ   rA   rQ   rr   r†   r’   r•   r€   r�   rÎ   rÚ   rß   rø   rÿ   r  r  © r   r   Ú<module>r     s½   ðñó Û Û 	Û Û Û Û 
Û Û ß .Ý ß (Ñ (å 1Ý 2÷÷ ð
 
×	Ò	˜8Ó	$€õ+õ\õ07õtõ4)õð ôó ðõB|õ~õ6õ0÷*bõJ÷2)÷Nñ Nr   