Ë
    âß²j} ã                  ó  — d Z ddlmZ ddlZddlZddlZddlZddlZddl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 ddlmZ ddlmZ ddlmZ dd	lmZmZm Z m!Z!m"Z"  ejF                  d
«      Z$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/dfd„Z0dgd„Z1dhd„Z2did„Z3djd„Z4dkd„Z5ddœdld„Z6dmd„Z7ejp                  dfd„«       Z9dnd „Z:e6Z;dod!„Z<dpd"„Z=dqd#„Z>drd$„Z?dsd%„Z@dsd&„ZAdtd'„ZBdud(„ZCdvd)„ZDdwd*„ZEdwd+„ZFdxd,„ZGdhd-„ZHdyd.„ZIdd/œdzd0„ZJd{d1„ZKe(fd|d2„ZLd}d3„ZMd~d4„ZNd5d6œdd7„ZOd8d9œd€d:„ZPd8d9œd�d;„ZQd‚d<„ZRd‚d=„ZSdod>„ZTdƒd?„ZUejp                  dfd@„«       ZVdddAœ	 	 	 	 	 d„dB„ZWdudC„ZX e
j²                  dD«      ZZ e
j²                  dE«      Z[ e
j²                  dF«      Z\d…dG„Z]dhdH„Z^dsdI„Z_dhdJ„Z`d†d‡dK„ZadLdMœdˆdN„ZbdsdO„ZcddPœd‰dQ„ZdddRœ	 	 	 	 	 	 	 	 	 dŠdS„Zed‹dT„ZfdodU„ZgdodV„ZhdodW„ZidodX„ZjdodY„ZkdZehd[d\fd]eid^d_fd`ejdadbfdcekdddffZldŒde„Zmy)�z½state.db repair, backup and writability preflight (split from hermes_state).

Patchable helpers are looked up as module globals at call time, so tests patch ``hermes_state_repair.<name>``.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)Úget_hermes_home)Úreport_startup_progress)Úread_only_db_uri)Ú_acquire_db_flockÚ_clear_lock_holder_recordÚ_describe_lock_holderÚ_read_lock_holder_recordÚis_advisory_lock_contentionÚhermes_stategš™™™™™¹?i    é   )ú-walú-shmz-journali   ))é   é   )é\   é`   i   g{®Gáz”?)Úmessages_ftsÚmessages_fts_trigramÚmessages_fts_cjkzyFree disk space, then retry (or recover manually with `hermes sessions recover --source {db_path} --inspect-only` first).c                ó"   ‡ — ˆ fd„t         D «       S )z>The three SQLite sidecar paths for *db_path* (present or not).c              3  óZ   •K  — | ]"  }‰j                  ‰j                  |z   «      –— Œ$ y ­w©N©Ú	with_nameÚname)Ú.0ÚsuffixÚdb_paths     €ú8/home/ubuntu/.hermes/hermes-agent/hermes_state_repair.pyú	<genexpr>z_sidecars.<locals>.<genexpr>J   s%   øè ø€ ÒX¸ˆG×Ñ˜gŸl™l¨VÑ3×4ÑXùs   ƒ(+)Ú_DB_SIDECAR_SUFFIXES©r%   s   `r&   Ú	_sidecarsr*   H   s   ø€ ãXÔCWÔXÐXó    c                ó¶   — 	 ddl m}m} 	  || |¬«      5   |«       cddd«       S # t        $ r d„ t        }}Y Œ0w xY w# 1 sw Y   yxY w# |t        f$ r Y yw xY w)aÃ  Run *reader()* under ``hermes_cli.sqlite_safe_read.offline_file_access``.

    ``close()`` on ANY raw descriptor cancels every POSIX advisory lock this process holds on the file,
    including a peer connection's RESERVED lock (``sqlite_safe_read`` rule 1), so a raw read is only safe with
    no live connection; ``None`` when that makes it unsafe or the file is unreadable. Scaffold/embed installs
    without hermes_cli have no tracked connections.r   )ÚLiveConnectionErrorÚoffline_file_accessc                ó*   — t        j                  «       S r   )Ú
contextlibÚnullcontext)Ú_pÚ_ks     r&   ú<lambda>z_read_offline.<locals>.<lambda>W   s   € ÄZ×E[ÑE[ÓE]€ r+   )ÚwhatN)Úhermes_cli.sqlite_safe_readr-   r.   ÚImportErrorÚOSError)r%   r5   Úreaderr-   r.   s        r&   Ú_read_offliner:   M   sn   € ðhßXðÙ  ¨tÔ4ñ 	Ù“8÷	ñ 	øô ò hÙ4]Ô`gÐ0Òðhú÷	ð 	ûà¤Ð)ò Ùðús7   ‚& ‹
A
 •>œ	A
 ¦;º;¾AÁA
 ÁA
 Á
AÁAc                ó¦   — ddl m}m} |5  t        | «      |v r
	 ddd«       y|j	                  t        | «      «       	 ddd«       y# 1 sw Y   yxY w)zÅClaim the one-shot per-process repair attempt for *db_path*: True for the first caller, False
    afterwards (bounds the repair/reopen loop and stops concurrent callers racing surgery on one file).r   )Ú_repair_attempt_lockÚ_repair_attempted_pathsNFT)r   r<   r=   ÚstrÚadd)r%   r<   r=   s      r&   Ú_claim_repair_attemptr@   _   sQ   € ÷ KØ	ñ Üˆw‹<Ð2Ñ2Ø÷ð ð 	 ×#Ñ#¤C¨£LÔ1Ø÷	÷ ñ ús   ‹A£AÁAc                ó  — | j                  | j                  |z   «      }	 |j                  j                  dd¬«       ||j	                  d«      fS # t
        $ r+}t        j                  d|› d|› �||«       |dfcY d}~S d}~ww xY w)zDOpen ``<db>.<suffix>`` for locking; on failure warn and return None.T)ÚparentsÚexist_okza+bzCould not open state.db u    lock %s (%s) â€” N)r!   r"   ÚparentÚmkdirÚopenr8   ÚloggerÚwarning)r%   r$   r5   ÚtailÚ	lock_pathÚexcs         r&   Ú_open_lock_filerL   j   sˆ   € à×!Ñ! '§,¡,°Ñ"7Ó8€IðØ×Ñ×Ñ t°dÐÔ;Ø˜)Ÿ.™.¨Ó/Ð/Ð/øÜò Ü�‰Ð1°$°Ð7IÈ$ÈÐPÐR[Ð]`ÔaØ˜$ˆÕûðús    /A Á	BÁ A?Á9BÁ?Bc                ó„   — dd l }| j                  d«       |j                  | j                  «       t	        ||«      d«       y )Nr   é   )ÚmsvcrtÚseekÚlockingÚfilenoÚgetattr)ÚhandleÚ	flag_namerO   s      r&   Ú_msvcrt_lockrV   u   s-   € ÛØ
‡K�K�„NØ
‡N�N�6—=‘=“?¤G¨F°IÓ$>ÀÕBr+   c                ó¤   — ddl m} |rt        | d«       yddl}|j	                  | j                  «       |j                  |j                  z  «       y)zJTake the advisory lock on *handle* without waiting (raises on contention).r   ©Ú_IS_WINDOWSÚLK_NBLCKN)r   rY   rV   ÚfcntlÚflockrR   ÚLOCK_EXÚLOCK_NB)rT   rY   r[   s      r&   Ú_try_lock_nonblockingr_   {   s6   € å(ÙÜ�V˜ZÕ(ãØ�‰�F—M‘M“O U§]¡]°U·]±]Ñ%BÕCr+   F©Úclear_recordc               óT  — ddl m} t        j                  | «      5  t        j                  t
        «      5  |rt        | d«       n;ddl}|rt        | «       |j                  | j                  «       |j                  «       ddd«       ddd«       y# 1 sw Y   ŒxY w# 1 sw Y   yxY w)z>Drop the advisory lock on *handle* (best effort) and close it.r   rX   ÚLK_UNLCKN)r   rY   r0   ÚclosingÚsuppressr8   rV   r[   r   r\   rR   ÚLOCK_UN)rT   ra   rY   r[   s       r&   Ú_release_lock_handlerg   …   s}   € å(Ü	×	Ñ	˜FÓ	#ñ 8¤Z×%8Ñ%8¼Ó%Añ 8ÙÜ˜ Õ,ãÙÜ)¨&Ô1Ø�K‰K˜Ÿ™›¨¯©Ô7÷8÷ 8÷ 8ñ 8ú÷ 8ð 8ús#   œB¶ABÂBÂB	ÂBÂB'c                ó>  — t        j                  «       |z   }	 	 t        |«       y# t        t        f$ rf}t        |«      st        j                  d| |«       Y d}~yt        j                  «       |k\  rY d}~yt        j                  t        «       Y d}~nd}~ww xY wŒ†)zeWindows counterpart of ``_acquire_db_flock``: True / False (timed out) / None (non-contention error).Tue   Could not acquire state.db repair lock %s (%s) â€” skipping schema surgery on a non-contention error.NF)
ÚtimeÚ	monotonicr_   ÚBlockingIOErrorr8   r   rG   rH   ÚsleepÚ_REPAIR_LOCK_POLL_SECONDS)rJ   rT   ÚtimeoutÚdeadlinerK   s        r&   Ú_acquire_repair_lock_windowsrp   ’   s„   € ä�~‰~Ó 'Ñ)€HØ
ð
	2Ü! &Ô)ØøÜ¤Ð)ò 	2Ü.¨sÔ3Ü—‘ð  7Ø8AÀ3ôHäÜ�~‰~Ó 8Ò+ÜÜ�J‰JÔ0×1Ñ1ûð	2úð	 s    š& ¦Bµ"BÁBÁ8BÂBc           
   #  ó¬  K  — ddl m}m} t        | ddd«      \  }}|€d–— yd}	 |rt	        |||«      }nt        t        |«      ||t        d«      \  }}|€d}n0|s.t        j                  d	||t        |rdn
t        |«      «      «       |–— |rt        |d
¬«       y|j                  «        y# |rt        |d
¬«       w |j                  «        w xY w­w)aÝ  Serialize state.db schema surgery across processes.

    Yields True when this process holds the repair lock, False when the bounded acquire timed out or the lock
    file could not be opened; on False the caller must NOT do surgery (unlocked surgery IS the interleaving
    this prevents). ``flock``: the kernel drops it when the holder dies (a pidfile would wedge every future
    repair); a forked child inheriting the fd is the exception, so the acquire records pid + start time and
    breaks a provably dead holder's lock. Bounded because a live repairer can sit in ``VACUUM`` for minutes.
    An unopenable lock file (no space/inodes/descriptors) fails closed too: a sibling that opened ITS handle
    before the disk filled may be inside surgery.

    The acquire is still bounded because a *live* repairer can legitimately sit in ``VACUUM`` for minutes on
    a large DB, and an unbounded wait would hang the caller's open with no traceback (the failure shape of
    #36644).
    r   )rY   Ú_REPAIR_LOCK_TIMEOUT_SECONDSz.repair.lockÚrepairzOskipping schema surgery rather than running it without cross-process authority.NFzstate.db repair locku¢   state.db repair lock %s held by another process for more than %.0fs â€” skipping schema surgery in this process to avoid racing the repairer. Recorded holder: %s.Tr`   )r   rY   rr   rL   rp   r   r>   rm   rG   rH   r   r   rg   Úclose)r%   rY   rr   rJ   rT   Úacquireds         r&   Ú_cross_process_repair_lockrv   £   sà   è ø€ ÷  GÜ'Ø� Ð+|ó~Ñ€Iˆvà€~ØŠØØ€HðÙÜ3°I¸vÐGcÓd‰Hä0´°Y³ÀÐIeÜ1JÐLbó dÑˆH�fàÐØ‰HÙÜ�N‰Nð hà$Ð&BÜ0¹±ÔJbÐciÓJjÓkômð ŠáÜ  °dÖ;à�L‰L�Nøñ Ü  °dÖ;à�L‰L�Nüs   ‚#C¦A(B/ Â!CÂ/"CÃCc                óŽ   — t        | ddd«      \  }}	 |�t        |«       |S # t        t        f$ r |j	                  «        Y yw xY w)zÕNon-blocking cross-process lock for one auto-maintenance pass (None = skip the pass: otherwise two startups
    both pass the interval check and the second prunes a row the first has only just closed recoverably).z.auto-maintenance.lockzauto-maintenancezskipping automatic maintenance.N)rL   r_   rk   r8   rt   )r%   Ú
_lock_pathrT   s      r&   Ú"_try_acquire_auto_maintenance_lockry   Ï   s[   € ô )¨Ð2JÐL^ð  aBó  CÑ€J�ðØÐÜ! &Ô)ØˆøÜœWÐ%ò Ø�‰ŒÙðús   “" ¢AÁAc                ó  — 	 | j                  d«      j                  «       d   }| j                  dt        |«      dz   dz  › �«       y# t        j                  t
        t        f$ r }t        j                  d|«       Y d}~yd}~ww xY w)am  Increment the schema cookie after direct ``sqlite_master`` surgery.

    Ordinary DDL bumps it and peers compare it before running a prepared statement (how they discard a cached
    schema); ``writable_schema=ON`` edits do NOT, so live connections would keep firing triggers into
    ``messages_fts*`` shadow tables that no longer exist. Best-effort, never raises.zPRAGMA schema_versionr   zPRAGMA schema_version=rN   iÿÿÿz)Could not bump state.db schema cookie: %sN)	ÚexecuteÚfetchoneÚintÚsqlite3ÚDatabaseErrorÚ	TypeErrorÚ
IndexErrorrG   rH   )ÚconnÚcurrentrK   s      r&   Ú_bump_schema_cookier„   ß   sy   € ðIØ—,‘,Ð6Ó7×@Ñ@ÓBÀ1ÑEˆà�‰Ð-¬s°7«|¸aÑ/?À:Ñ.MÐ-NÐOÕPøÜ×!Ñ!¤9¬jÐ9ò IÜ�‰ÐBÀC×HÑHûðIús   ‚AA ÁBÁ&BÂBc                ó€   — t        | «      dk  r| S t        | «      }t        D ]  \  }}d||z
  z  ||| Œ t        |«      S )zJZero the commit-counter fields so ordinary writes don't re-key the ledger.r   ó    )ÚlenÚ	bytearrayÚ#_FINGERPRINT_VOLATILE_HEADER_RANGESÚbytes)ÚheadÚbufÚstartÚends       r&   Ú_mask_volatile_headerr�   í   sL   € ä
ˆ4ƒy�2‚~ØˆÜ
�D‹/€CÜ9ò 1‰
ˆˆsØ  C¨%¡KÑ0ˆˆE�#‰ð1ä�‹:Ðr+   c                óB   — t        t        t        | t        z  «      «      S )zLFree space required *beyond* the copy itself, for a volume of *total_bytes*.)ÚmaxÚ_REPAIR_BACKUP_MIN_FREE_BYTESr}   Ú_REPAIR_BACKUP_FREE_FRACTION)Útotal_bytess    r&   Ú_repair_backup_headroom_bytesr•   ÷   s   € äÔ,¬c°+Ô@\Ñ2\Ó.]Ó^Ð^r+   c                óT  — 	 | j                  «       j                  t        d„ t        | «      D «       «      z   }t	        j
                  | j                  «      }d||j                  t        |j                  «      fS # t        $ r$}d| j                  › d|› d|› d�dddfcY d}~S d}~ww xY w)a+  ``(error, bundle_bytes, free_bytes, headroom_bytes)`` for *db_path*'s volume (main file plus every PRESENT
    sidecar); *error* is set (and the sizes zero) on stat()/disk_usage() failure. Fails CLOSED: the nearly-full
    volume these guards exist for is exactly where they are most likely to fail.c              3  ór   K  — | ]/  }|j                  «       sŒ|j                  «       j                  –— Œ1 y ­wr   )ÚexistsÚstatÚst_size©r#   Úps     r&   r'   z_disk_budget.<locals>.<genexpr>  s(   è ø€ Ò+gÀÐ\]×\dÑ\dÕ\f¨A¯F©F«H×,<Õ,<Ñ+gùs   ‚7˜7z"could not determine free space on z (z); refusing the z$ rather than risk filling the volumer   N)r™   rš   Úsumr*   ÚshutilÚ
disk_usagerD   r8   Úfreer•   Útotal)r%   ÚrefusalÚneedÚusagerK   s        r&   Ú_disk_budgetr¥   ü   sª   € ð/Ø�|‰|‹~×%Ñ%¬Ñ+gÄiÐPWÓFXÔ+gÓ(gÑgˆÜ×!Ñ! '§.¡.Ó1ˆð ��u—z‘zÔ#@ÀÇÁÓ#MÐMÐMøô ò /Ø4°W·^±^Ð4DÀBÀsÀeÐK[Ð\cÐ[dð e%ð %Ø'(¨!¨Qð/õ 	/ûð/ús   ‚AA: Á:	B'ÂB"ÂB'Â"B'c                ó¨   — t        | d«      \  }}}}|�|S ||d|z  z   |z   k\  ryd|dz  d›d| j                  › d|dz  d›d	d|z  dz  d›d
|dz  d›d�S )zEReturn an error unless snapshot, VACUUM and promotion can fit safely.zrepair snapshotNé   úonly ç    eÍÍAú.2fúGB free on z"; the repair snapshot needs up to zGB, VACUUM may need another zGB, and z8GB must remain as headroom. Free disk space, then retry.)r¥   rD   )r%   ÚerrorÚsnapshot_bytesr    Úheadrooms        r&   Ú_repair_scratch_space_errorr¯   	  sœ   € ä,8¸ÐBSÓ,TÑ)€Eˆ>˜4 ØÐØˆð ˆ~  ^Ñ!3Ñ4°xÑ?Ò?ØØ�D˜3‘J˜sÐ# ;¨w¯~©~Ð.>Ð>`Ø Ñ# CÐ(Ð(DÀaÈ.ÑFXÐ\_ÑE_Ð`cÐDdÐdlØ˜#‰~˜cÐ"Ð"Zð\ð ]r+   c           
     óÂ   — t         j                  | ¬«      }t        | d«      \  }}}}|�|› d|› �S ||z
  |k\  ryd|dz  d›d| j                  › d	|dz  d›d
|dz  d›d|› �
S )zÙDisk guard for the forensic copy: reason to refuse, or None. A full raw copy on a nearly-full volume (which a
    preceding repair loop may itself have caused) can finish off the disk and every process on the machine.r)   zforensic copyNz. r¨   r©   rª   r«   z; copying the damaged DB needs zGB and must leave zGB headroom. )Ú_MANUAL_RECOVER_HINTÚformatr¥   rD   )r%   Úhintr¬   r£   r    r®   s         r&   Ú_backup_free_space_errorr´     sŸ   € ô  ×&Ñ&¨wÐ&Ó7€DÜ".¨w¸Ó"HÑ€Eˆ4��xØÐØ�˜˜4˜&Ð!Ð!Øˆd�{�hÒØØ�D˜3‘J˜sÐ# ;¨w¯~©~Ð.>Ð>]Ð^bÐehÑ^hÐilÐ]mð nØ ‘^ CÐ(¨°d°Vð=ð >r+   c                óö   — ddl m} d}| gt        | «      ¢­D ]A  }t        j                  t
        «      5  ||j                  «       j                  z  }ddd«       ŒC t        ||t        z  «      S # 1 sw Y   Œ`xY w)zÁBound one SQLite snapshot by source size incl. sidecars (a WAL can hold committed rows not yet in the
    main file), so a healthy large-database copy is not cut off by the repair-lock timeout.r   )rr   N)
r   rr   r*   r0   re   ÚFileNotFoundErrorr™   rš   r‘   Ú0_REPAIR_SNAPSHOT_MIN_THROUGHPUT_BYTES_PER_SECOND)Úsource_pathrr   Úsource_bytesÚ	candidates       r&   Ú _repair_snapshot_timeout_secondsr»   $  s~   € õ :Ø€LØ!Ð;¤I¨kÓ$:Ñ;ò 5ˆ	Ü× Ñ Ô!2Ó3ñ 	5Ø˜IŸN™NÓ,×4Ñ4Ñ4ˆL÷	5ð 	5ð5ô Ð+¨\Ô<lÑ-lÓmÐm÷	5ð 	5ús   ´A/Á/A8	c                óæ   ‡ — t        ‰ t        j                  «      syt        t        ‰ dd«      x}t        «      r%|dz  t        j
                  t        j                  fv S t        ˆ fd„dD «       «      S )u!  Whether a pre-strategy SQLite failure proves deterministic corruption.

    Lock contention, timeouts, disk-full and I/O failures are environmental â€” a retry may succeed, so they
    must not burn the repair ledger. Only SQLite's corruption/image result codes prove deterministic damage.FÚsqlite_errorcodeNéÿ   c              3  óT   •K  — | ]  }|t        ‰«      j                  «       v –— Œ! y ­wr   ©r>   Úlower©r#   ÚmrK   s     €r&   r'   z3_repair_failure_consumes_attempt.<locals>.<genexpr>;  s!   øè ø€ Òm¨ˆq”C˜“H—N‘NÓ$Ô$Ñmùó   ƒ%()zfile is not a databasez database disk image is malformed)Ú
isinstancer~   r   rS   r}   ÚSQLITE_CORRUPTÚSQLITE_NOTADBÚany)rK   Ú
error_codes   ` r&   Ú _repair_failure_consumes_attemptrÊ   /  sf   ø€ ô
 �cœ7×0Ñ0Ô1ØÜ¤¨Ð-?ÀÓ FÐF�*ÌÔLà˜TÑ!¤w×'=Ñ'=¼w×?TÑ?TÐ&UÐUÐUô ÓmÐ.lÔmÓmÐmr+   c                ó>   — | j                  | j                  dz   «      S )Nz.repair-attempts.jsonr    r)   s    r&   Ú_repair_ledger_pathrÌ   >  s   € Ø×Ñ˜WŸ\™\Ð,CÑCÓDÐDr+   c                ó*   ‡ — dˆ fd„}t        ‰ d|«      S )a  Cheap identity for a damaged DB file: size + a bounded content sample.

    EXCLUDES mtime: a malformed-schema DB still accepts writes, so live writers, checkpoints and the
    strategies move mtime between passes; keyed on mtime every pass looked like a NEW file and the attempt
    counter reset forever. Hashing a multi-GB file per open is the cost this ledger avoids, so sample the
    head/tail slices any real repair, truncation or restore must change.

    ``None`` = identity unavailable (:func:`_read_offline`; a live peer is expected here since this runs
    BEFORE ``_backup_db_file``'s live-connection guard). Callers MUST NOT substitute a differently-shaped key:
    the ledger compares for equality, so alternating shapes never match and the unbounded loop returns.c                 ó¾  •— ‰j                  «       } t        ‰d«      5 }|j                  t        «      }|j	                  t        d| j                  t        z
  «      «       | j                  t        kD  r|j                  t        «      nd}d d d «       | j                  › dt        j                  t        «      z   «      j                  «       d d › �S # 1 sw Y   ŒJxY w)NÚrbr   r+   ú:é    )r™   rF   ÚreadÚ_FINGERPRINT_SAMPLE_BYTESrP   r‘   rš   ÚhashlibÚsha256r�   Ú	hexdigest)ÚstÚfhr‹   rI   r%   s       €r&   Ú_samplez _db_fingerprint.<locals>._sampleM  s»   ø€ Ø�\‰\‹^ˆÜ�'˜4Ó ð 	i BØ—7‘7Ô4Ó5ˆDØ�G‰G”C˜˜2Ÿ:™:Ô(AÑAÓBÔCØ9;¿¹ÔF_Ò9_�2—7‘7Ô4Ô5ÐehˆD÷	ið —*‘*�˜QœwŸ~™~Ô.CÀDÓ.IÈDÑ.PÓQ×[Ñ[Ó]Ð^aÐ_aÐbÐcÐdÐd÷		ið 	iús   žA,CÃCÚfingerprint©Úreturnr>   ©r:   )r%   rÙ   s   ` r&   Ú_db_fingerprintrÞ   B  s   ø€ õeô ˜ -°Ó9Ð9r+   c                ó*   ‡ — dˆ fd„}t        ‰ d|«      S )uâ  Recovery-image identity for forensic-backup dedupe: whole file + sidecars.

    A DIFFERENT relation from :func:`_db_fingerprint` (never conflate them): the fingerprint answers "same
    repair epoch?" from head+tail only, and a live writer can commit into an *interior* page while preserving
    size and both 64 KiB slices â€” reusing a backup on that basis hands the operator a snapshot predating real
    user data. A forensic copy must claim byte identity, so this digests the ENTIRE main file plus every
    present sidecar (the WAL can hold uncheckpointed frames). ``None`` when a live connection makes the read
    unsafe (:func:`_read_offline`) â€” the caller then takes a fresh backup, never a false reuse.

    See #87409.
    c                 ó¶  •‡— t        j                  «       } d‰fgd„ t        t        t	        ‰«      «      D «       ¢}|D ]~  \  }}| j                  d|› d|j                  «       j                  › d�j                  «       «       t        |d«      5 Št        ˆfd„d«      D ]  }| j                  |«       Œ 	 d d d «       Œ€ | j                  «       S # 1 sw Y   ŒšxY w)NÚmainc              3  óL   K  — | ]  \  }}|j                  «       sŒ||f–— Œ y ­wr   )r˜   )r#   Úsfxrœ   s      r&   r'   z<_backup_content_identity.<locals>._digest.<locals>.<genexpr>e  s$   è ø€ Ò'{±V°S¸!Ðpq×pxÑpxÕpz¨¨a¬Ñ'{ùs   ‚$›	$ú rÐ   rÏ   c                 ó&   •— ‰ j                  d«      S )Ni   )rÒ   )rØ   s   €r&   r4   z;_backup_content_identity.<locals>._digest.<locals>.<lambda>k  s   ø€ ¨"¯'©'°+Ó*>€ r+   r+   )rÔ   rÕ   Úzipr(   r*   Úupdater™   rš   ÚencoderF   ÚiterrÖ   )ÚhasherÚmembersÚlabelÚpathÚchunkrØ   r%   s        @€r&   Ú_digestz)_backup_content_identity.<locals>._digestc  sÖ   ù€ Ü—‘Ó!ˆØ˜GÐ$Ð|Ñ'{¼sÔCWÔYbÐcjÓYkÓ?lÔ'{Ð|ˆØ"ò 	)‰KˆE�4ð �M‰M˜B˜u˜g Q t§y¡y£{×':Ñ':Ð&;¸2Ð>×EÑEÓGÔHÜ�d˜DÓ!ð ) RÜ!Ó">ÀÓDò )�EØ—M‘M %Õ(ñ)÷)ð )ð		)ð ×ÑÓ!Ð!÷)ð )ús   Â&CÃC	zbackup-identityrÛ   rÝ   )r%   rï   s   ` r&   Ú_backup_content_identityrð   W  s   ø€ õ
"ô ˜Ð"3°WÓ=Ð=r+   c                óð   — t        j                  t        t        «      5  t	        j
                  t        | «      j                  d¬«      «      }t        |t        «      r|ni cd d d «       S # 1 sw Y   i S xY w)Núutf-8©Úencoding)
r0   re   r8   Ú
ValueErrorÚjsonÚloadsrÌ   Ú	read_textrÅ   Údict)r%   Úraws     r&   Ú_read_repair_ledgerrû   q  s^   € Ü	×	Ñ	œW¤jÓ	1ñ 4Ü�j‰jÔ,¨WÓ5×?Ñ?ÈÐ?ÓQÓRˆÜ  ¤dÔ+‰s°÷4÷ 4ñ 4ð €Iús   ŸAA+Á+A5c                óF  — t        | «      }|j                  d«      }t        | «      }	 |�||k(  n=t        |t        «      xr, |j                  | j                  «       j                  › d�«      }|xr" t        |j                  dd«      «      t        k\  S # t        $ r Y yw xY w)aò  Whether *db_path* has already burned its cross-restart repair budget.

    True only when the ledger records ``_MAX_PERSISTENT_REPAIR_ATTEMPTS`` failures against the CURRENT
    fingerprint. Never raises; a missing/corrupt ledger or unstatable DB reads as "not exhausted" (the
    in-process claim and cross-process lock still bound one run). Fingerprint unavailable (live connection) ->
    compare the SIZE the ledger recorded, otherwise a peer connection hides an exhausted budget on every pass.rÚ   rÐ   FÚfailed_attemptsr   )rû   ÚgetrÞ   rÅ   r>   Ú
startswithr™   rš   r8   r}   Ú_MAX_PERSISTENT_REPAIR_ATTEMPTS)r%   ÚledgerÚrecordedÚfpÚsames        r&   Ú%_persistent_repair_attempts_exhaustedr  x  s¢   € ô ! Ó)€FØ�z‰z˜-Ó(€HÜ	˜Ó	!€BðØ!# ˆx˜2Š~Ü�x¤Ó%Ò[¨(×*=Ñ*=ÀÇÁÃ×AWÑAWÐ@XÐXYÐ>ZÓ*[ð 	ð Ò\”C˜Ÿ
™
Ð#4°aÓ8Ó9Ô=\Ñ\Ð\øô ò Ùðús   ©AB Â	B ÂB c                ót   — ddl m}  |«       }dt        › d|› d| › d|› d| › dt        | «      j                  › d�S )	zµThe stable operator-facing diagnostic for an exhausted repair budget. The ``hermes`` commands
    carry the profile selector: a bare ``hermes`` follows ``active_profile`` (#105887).r   )Úprofile_cli_selectorz$automatic repair has already failed u¹    times on this exact file â€” the corruption is beyond the schema/FTS repair strategies (likely b-tree page damage). Manual recovery required: restore a backup, or salvage with `hermes zsessions recover --source z; --inspect-only`, then (if it reports recoverable) `hermes zÁ --output recovered-state.db` (recovery snapshots the damaged file first, then runs the page-level `.recover` lane on the copy; do NOT point a raw `sqlite3` shell at the live database). Delete z$ to force another automatic attempt.)Úhermes_constantsr  r   rÌ   r"   )r%   r  Úprofile_args      r&   Ú"_persistent_repair_exhausted_errorr
  Š  sj   € õ 6Ù&Ó(€KØ2Ô3RÐ2Sð TCàCNÀ-ÐOiÐjqÐirð sIØITÈÐUoÐpwÐoxð yô *¨'Ó2×7Ñ7Ð8Ð8\ð^ð _r+   )rÚ   c               ó  — t        | «      }	 |r|j                  d¬«       yt        | «      }|j                  d«      }|�|n
t	        | «      }|€t        |t        «      sy|}||k(  rt        |j                  dd«      «      dz   nd}t        j                  j                  «       j                  d¬	«      }|j                  t        j                  |||d
œ«      d¬«       y# t        $ r }	t        j!                  d|	«       Y d}	~	yd}	~	ww xY w)uZ  Update the persistent attempt ledger after a repair pass. Never raises.

    Keys on the post-attempt fingerprint (what the NEXT exhaustion probe sees). If a live connection makes it
    unavailable, keep the recorded key and still increment â€” dropping the pass lets a peer reset the budget
    every time. Never write a differently shaped key.T©Ú
missing_okNrÚ   rý   r   rN   Úseconds)Útimespec)rÚ   rý   Úlast_attemptrò   ró   z+Could not update state.db repair ledger: %s)rÌ   Úunlinkrû   rþ   rÞ   rÅ   r>   r}   ÚdatetimeÚnowÚ	isoformatÚ
write_textrö   ÚdumpsÚ	ExceptionrG   rH   )
r%   ÚrepairedrÚ   Úledger_pathr  r  r  ÚattemptsÚstamprK   s
             r&   Ú_record_repair_outcomer  ˜  s  € ô & gÓ.€KðKÙØ×Ñ¨$ÐÔ/ØÜ$ WÓ-ˆØ—:‘:˜mÓ,ˆØ'Ð3‰[¼ÈÓ9QˆØˆ:Ü˜h¬Ô,ð ØˆBØ@HÈBÂ”3�v—z‘zÐ"3°QÓ7Ó8¸1Ò<ÐTUˆÜ×!Ñ!×%Ñ%Ó'×1Ñ1¸9Ð1ÓEˆØ×ÑœtŸz™z¸"ÐQYÐkpÑ*qÓrð  ~EÐõ  	FøÜò KÜ�‰ÐDÀc×JÑJûðKús#   �C  ¢=C  Á A?C  Ã 	D	Ã)DÄD	c                ó2  — | j                   › d�}	 | j                  j                  «       D �cg c]@  }|j                   j                  |«      sŒ|j                   j	                  t
        «      rŒ?|‘ŒB }}t        |d„ d¬«      S c c}w # t        $ r g cY S w xY w)z8Timestamped forensic backups of *db_path*, newest first.ú.malformed-backup-c                ó   — | j                   S r   )r"   )rœ   s    r&   r4   z-_existing_malformed_backups.<locals>.<lambda>º  s
   €  q§v¡v€ r+   T)ÚkeyÚreverse)r"   rD   Úiterdirrÿ   Úendswithr(   r8   Úsorted)r%   Úprefixrœ   Úfounds       r&   Ú_existing_malformed_backupsr'  ³  s“   € à—‘ˆ~Ð/Ð0€FðØ#ŸN™N×2Ñ2Ó4ö  A�q¸¿¹×8IÑ8IÈ&Õ8QÐZ[×Z`ÑZ`×ZiÑZiÔj~ÕZ’ð  Aˆð  Aô �%Ñ-°tÔ<Ð<ùò AøÜò ØŠ	ðús.   ‘B ­ BÁBÁ.BÁ2B ÂB ÂBÂBc                óÒ   — t        | «      |d D ])  }|gt        |«      ¢­D ]  }	 |j                  d¬«       Œ Œ+ y# t        $ r!}t        j                  d||«       Y d}~Œ@d}~ww xY w)zADelete all but the *keep* newest forensic backups (and sidecars).NTr  z&Could not prune stale DB backup %s: %s)r'  r*   r  r8   rG   rH   )r%   ÚkeepÚstaleÚvictimrK   s        r&   Ú_prune_malformed_backupsr,  ½  sz   € ä,¨WÓ5°d°eÐ<ò VˆØÐ0œy¨Ó/Ñ0ò 	VˆFðVØ—‘¨�Õ.ñ	VñVøô ò VÜ—‘ÐGÈÐQT×UÑUûðVús   ¥<¼	A&ÁA!Á!A&c           
     ó�  — | ||f}t        t        t        | «      «      D ��cg c]R  \  }}|j                  «       r=||j	                  |j
                  |z   «      |j	                  |j
                  |z   «      f‘ŒT }}}g }	 |g|¢­D ]  \  }}	}
t        j                  ||	«       Œ g |¢|‘­D ]-  \  }}	}t        j                  |	|«       |j                  |«       Œ/ yc c}}w # t        $ rZ g d„ g |¢|‘­D «       ¢|¢­D ]B  }t        j                  t        «      5  |j                  d¬«       ddd«       Œ8# 1 sw Y   ŒAxY w ‚ w xY w)uw  Copy DB + sidecars to *staging* names, then rename each into place.

    ORDER MATTERS: the main DB name is the bundle's commit marker (what ``_existing_malformed_backups``
    counts), so sidecars publish FIRST and the main DB LAST â€” a failure partway never leaves a countable
    backup over a missing sidecar. On failure, staging files AND anything promoted are removed.c              3  ó(   K  — | ]
  \  }}}|–— Œ y ­wr   © )r#   Ú_sÚstagedÚ_ds       r&   r'   z)_publish_backup_bundle.<locals>.<genexpr>Ø  s   è ø€ ÒH¡N B¨°œÑHùs   ‚Tr  N)ræ   r(   r*   r˜   r!   r"   rž   Úcopy2ÚosÚreplaceÚappendr  r0   re   r8   r  )r%   ÚstagingÚbackup_pathrá   r$   ÚsidecarÚsidecarsÚ	publishedÚsrcr1  Ú_dstÚ_srcÚdstr+  s                 r&   Ú_publish_backup_bundler@  Ç  si  € ð �W˜kÐ*€Dä'*Ô+?ÄÈ7ÓASÓ'T÷jÙ#�F˜GÐX_×XfÑXfÔXhð ˜'×+Ñ+¨G¯L©L¸6Ñ,AÓBÀK×DYÑDYÐZe×ZjÑZjÐmsÑZsÓDtÒuð j€Hñ jà €Ið
Ø"&Ð!2¨Ñ!2ò 	&ÑˆC�˜Ü�L‰L˜˜fÕ%ð	&à!2 8Ð!2¨TÑ!2ò 	"ÑˆD�&˜#Ü�J‰J�v˜sÔ#Ø×Ñ˜SÕ!ñ	"ùójøô ò ØUÑHÐ6G¸Ð6GÀ$Ñ6GÔHÐUÈ9ÑUò 	/ˆFÜ×$Ñ$¤WÓ-ñ /Ø—‘¨�Ô.÷/÷ /ñ /úð	/ð 	ð	ús+   ¢ACÁ?AC" Ã"8EÄD6Ä-	EÄ6D?Ä;
Ec                óJ  — t        j                  t        «      5  ddlm}  || «      r*d| › d�}t
        j                  d| |«       d|fcddd«       S 	 ddd«       t        j                  j                  «       j                  d«      }| j                  | j                  › d|› �«      }t        j                  d	«      D ]7  }|j                  «       s n%| j                  | j                  › d|› d
|› �«      }Œ9 	 | j                  › d�| j                  › d�fD ]V  }| j                  j!                  |«      D ]6  }t        j                  t"        «      5  |j%                  d¬«       ddd«       Œ8 ŒX t        j                  t"        «      5  t'        | «      dd	 x}rGt)        | «      }	|	�:t)        |d   «      |	k(  r)t
        j+                  d|d   «       |d   dfcddd«       S ddd«       t-        | «      x}�t
        j                  d| |«       d|fS t/        | | j                  | j                  › d|› �«      |«       t1        | «       |dfS # 1 sw Y   �ŒùxY w# 1 sw Y   �Œ-xY w# 1 sw Y   Œ…xY w# t2        $ r(}
t
        j5                  d| |
«       dd|
› �fcY d}
~
S d}
~
ww xY w)u  Raw-copy a (possibly malformed) DB plus sidecars to a timestamped backup.

    Raw bytes on purpose: the DB won't open cleanly, so preserve them exactly for forensics. Returns ``(backup_path,
    None)`` or ``(None, reason)``; repair treats a refused backup as a HARD STOP because the bundle is the recovery
    path when every strategy fails. Refuses while a connection to this DB is live in the process: the raw read would
    ``close()`` a descriptor and cancel that connection's POSIX advisory locks (``hermes_cli.sqlite_safe_read``) â€”
    real case: one SessionDB enters repair while the gateway holds others.

    Dedupe: reuse the newest backup when byte-identical to the current recovery image (``_backup_content_identity``
    â€” NOT mtime, NOT ``_db_fingerprint``); a repair loop once re-copied the same bytes on every restart. Staging
    names live OUTSIDE the ``.malformed-backup-`` prefix: inside it they count as a backup, sort NEWEST (prune kept
    partials, deleted intact copies) and dedupe could return one with no real forensic copy on disk.

    Refusal reasons (``_backup_free_space_error`` / ``_MANUAL_RECOVER_HINT``) point operators at the safe lane,
    `hermes sessions recover --source <db> --inspect-only`, never at a raw sqlite3 shell on the live file.

    See #69603.
    r   )Úhas_live_connectionza connection to z† is still open in this process; raw-copying it would cancel that connection's POSIX advisory locks. Close all SessionDB handles first.z&Refusing to raw-copy %s for backup: %sNz%Y%m%d_%H%M%Sr  rN   Ú_z.backup-staging-*z.malformed-backup-*.incomplete*Tr  zBReusing existing forensic backup %s (identical to the damaged DB).z"Refusing forensic backup of %s: %sz.backup-staging-z%Could not back up malformed DB %s: %szbackup copy failed: )r0   re   r7   r6   rB  rG   r¬   r  r  Ústrftimer!   r"   Ú	itertoolsÚcountr˜   rD   Úglobr8   r  r'  rð   Úinfor´   r@  r,  r  rH   )r%   rB  Úreasonr  r8  ÚseqÚpatternÚoldÚnewestÚsrc_idrK   s              r&   Ú_backup_db_filerO  Þ  s¯  € ô& 
×	Ñ	œ[Ó	)ñ  ÝCÙ˜wÔ'Ø(¨¨	ð 2^ð ^ˆFä�L‰LÐAÀ7ÈFÔSØ˜�<÷ ñ  à'÷ ô ×Ñ×!Ñ!Ó#×,Ñ,¨_Ó=€EØ×#Ñ# w§|¡| nÐ4FÀuÀgÐ$NÓO€KÜ�‰˜qÓ!ò ZˆØ×!Ñ!Ô#ÙØ×'Ñ'¨7¯<©<¨.Ð8JÈ5È'ÐQRÐSVÐRWÐ(XÓY‰ðZð2ð #Ÿ<™<˜.Ð(9Ð:¸w¿|¹|¸nÐLkÐ<lÐmò 	0ˆGØ—~‘~×*Ñ*¨7Ó3ò 0�Ü×(Ñ(¬Ó1ñ 0Ø—J‘J¨$�JÔ/÷0ð 0ñ0ð	0ô × Ñ ¤Ó)ñ 	+ä4°WÓ=¸b¸qÐAÐAˆvÐAÜ1°'Ó:�ØÐ%Ô*BÀ6È!Á9Ó*MÐQWÒ*WÜ—K‘KÐ dÐflÐmnÑfoÔpØ! !™9 d˜?÷	+÷ 	+ñ 	+ô /¨wÓ7Ð7ˆFÐDÜ�L‰LÐ=¸wÈÔOØ˜�<ÐÜ˜w¨×(9Ñ(9¸W¿\¹\¸NÐJZÐ[`ÐZaÐ:bÓ(cÐepÔqä  Ô)Ø˜DÐ Ð ÷E ñ  ú÷&0ñ 0ú÷	+ð 	+ûô ò 2Ü�‰Ð>ÀÈÔMØÐ+¨C¨5Ð1Ð1Õ1ûð2úsg   š/IÃ9AI1 ÅIÅ$%I1 Æ	AI%Ç	I1 Ç!/I1 È9I1 ÉIÉI"ÉI1 É%I.É*I1 É1	J"É:JÊJ"ÊJ"zstate.db©Údb_labelc               ó  — t        | «      dk(  st        | «      j                  d«      ryd}t        j                  t        «      5  t        t        «       «      j                  «       }ddd«       | j                  | j                  dz   «      | j                  | j                  dz   «      f}| j                  dfgd„ | g|¢­D «       ¢D �]¡  \  }}|r|j                  «       r5t        j                  |t        j                  t        j                  z  «      rŒO|rdnd	}d
}t        j                  t         t"        «      5  |duxr |j                  «       j%                  |«      }|ret        j&                  ||j)                  «       j*                  t(        j,                  z  t(        j.                  z  |rt(        j0                  ndz  «       ddd«       |rOt        j                  |t        j                  t        j                  z  «      rt2        j5                  d|||«       �Œ[|j                  j7                  d«      rdnd	}t9        j:                  |› d|rdnd› d|› d|› d|› d|› �«      ‚ y# 1 sw Y   �ŒxY w# 1 sw Y   Œ²xY w)u%  Refuse-or-repair read-only DB files BEFORE the first connection opens.

    A stray read-only ``state.db`` / ``-wal`` / ``-shm`` (sudo run, restored backup, copied dotfiles) otherwise
    surfaces as an opaque "attempt to write a readonly database" inside ``_init_schema``, and the obvious wrong
    "fix" (deleting the ``-wal``) loses committed transactions. ``chmod u+rw`` repair only inside the Hermes home
    tree (Hermes owns those files; ``chmod`` fails on files the user doesn't own, bounding the repair exactly);
    otherwise fail fast naming the file and command. Never deletes/truncates a WAL sidecar â€” once writable, the
    normal open checkpoints it. ``:memory:``/``file:`` skipped. Shared with ``kanban_db``.

    Port of Kilo-Org/kilocode#12508's startup preflight. This preflight:
    z:memory:zfile:Nr   r   Tc              3  óF   K  — | ]  }|j                  «       sŒ|d f–— Œ y­w)FN)Úis_filer›   s     r&   r'   z+preflight_db_writability.<locals>.<genexpr>,  s    è ø€ Ò/h¸qÐ\]×\eÑ\eÕ\g°°E´
Ñ/hùs   ‚!˜	!ÚxÚ Fr   z2%s preflight: repaired read-only %s (chmod u+rw%s)uv    Do NOT delete the -wal file â€” it contains committed data that will be merged into the database once it is writable.z is not writable: Ú	directoryÚfileÚ zf is read-only for this user. Hermes needs read-write access to open the database. Fix with: chmod u+rwz 'z4' (files owned by another user may need sudo/chown).)r>   rÿ   r0   re   r  r   r
   Úresolver!   r"   rD   Úis_dirr4  ÚaccessÚR_OKÚW_OKr8   rõ   Úis_relative_toÚchmodr™   Úst_modeÚS_IRUSRÚS_IWUSRÚS_IXUSRrG   rH  r#  r~   ÚOperationalError)	r%   rQ  Úhomer:  rœ   r[  rU  Úin_scopeÚwal_notes	            r&   Úpreflight_db_writabilityri    s,  € ô ˆ7ƒ|�zÒ!¤S¨£\×%<Ñ%<¸WÔ%EØØ€DÜ	×	Ñ	œYÓ	'ñ 1Ü”OÓ%Ó&×.Ñ.Ó0ˆ÷1ð ×!Ñ! '§,¡,°Ñ"7Ó8¸'×:KÑ:KÈGÏLÉLÐ[aÑLaÓ:bÐc€HØ—~‘~ tÐ,ÐiÑ/hÀWÐDXÈxÑDXÔ/hÐió 5‰	ˆˆ6Ù˜1Ÿ8™8œ:¬"¯)©)°A´r·w±wÄÇÁÑ7HÔ*IØÙ‰C˜rˆØˆÜ× Ñ ¤¬*Ó5ñ 	nØ 4Ð'ÒL¨A¯I©I«K×,FÑ,FÀtÓ,LˆHÙÜ—‘˜˜AŸF™F›H×,Ñ,¬t¯|©|Ñ;¼d¿l¹lÑJÑ^dÌdÏlÊlÐjkÑlÔm÷	nñ œŸ	™	 !¤R§W¡W¬r¯w©wÑ%6Ô7Ü�K‰KÐLÈhÐXYÐ[\Ô]ÙàOPÏvÉvÏÉÐ_eÔOfñLØlnð 	ä×&Ñ&ØˆjÐ*¹&©;ÀfÐ*MÈQÈqÈcð RQØQRÐPSÐSUÐVWÐUXð Y)Ø)1¨
ð4ó5ð 	5ñ5÷	1ñ 1ú÷	nð 	nús   Á"I-Ä<BI:É-I7É:J	g      @©rn   c               ó@   — ddl m}  || | |d¬«      }t        |«       |S )u-  ``sqlite3.connect`` for the repair/probe paths, with macOS write barriers.

    These paths bypass ``SessionDB``/:func:`apply_wal_with_fallback`, so they inherited ``synchronous=NORMAL`` and
    no ``checkpoint_fullfsync`` â€” on Darwin an interrupted ``REINDEX``/``VACUUM``/``writable_schema`` rewrite leaves
    half-written b-tree pages. Autocommit (``isolation_level=None``): DDL and ``VACUUM`` are illegal inside an
    implicit transaction. Barriers are best-effort: on a malformed schema even ``PRAGMA synchronous=FULL`` raises,
    so whole-file rewrites call :func:`_reapply_durability_barriers` once the schema parses again.

    Tracked (:func:`hermes_cli.sqlite_safe_read.connect_tracked`) because repair connections hold the
    strongest locks in the process (``locking_mode=EXCLUSIVE``, ``BEGIN IMMEDIATE``); an untracked fd let
    the byte-level probes ``open()``/``close()`` the live file, which cancels every POSIX advisory lock this
    process holds on it (sqlite.org/howtocorrupt Â§2.2) and lets an external writer commit mid-repair (#63386).
    r   )Úconnect_trackedN)Útracking_pathrn   Úisolation_level)r6   rl  Ú_reapply_durability_barriers)r%   rn   rl  r‚   s       r&   Ú_connect_repair_durablerp  @  s%   € õ <á˜7°'À7Ð\`Ôa€DÜ  Ô&Ø€Kr+   c               óB   — t        j                  t        | |¬«      «      S )zRA :func:`_connect_repair_durable` connection as a context manager, closed on exit.rj  )r0   rd   rp  )r%   rn   s     r&   Ú_repair_connrr  U  s   € ä×ÑÔ5°gÀwÔOÓPÐPr+   c                óT   — ddl m}m} 	  || «        || «       y# t        $ r Y yw xY w)zçBest-effort (re)application of the macOS write barriers; True if accepted.
    Call before ``VACUUM``/``REINDEX`` once the schema parses: a connection opened
    on a malformed schema could not take them at open time. Never raises.r   )Ú_apply_macos_checkpoint_barrierÚ_enforce_macos_synchronous_fullTF)Úhermes_state_walrt  ru  r  )r‚   rt  ru  s      r&   ro  ro  Z  s2   € ÷ bðÙ'¨Ô-Ù'¨Ô-ØøÜò Ùðús   Š ›	'¦'c                óÒ   — ddl m} t        | «      }t        j                  t
        «      5  ddlm}m}  | |«       ddd¬«      x}� || |d¬	«       ddd«       |S # 1 sw Y   |S xY w)
a	  Durability barriers for guest users of ``state.db`` that must inherit its owner's journal mode. Also
    applies the configured ``database.synchronous`` level, a per-connection pragma that otherwise only
    rides on the journal-mode setup path guests must not run.r   )Ú_apply_synchronous_pragma)Úcfg_getÚload_config_readonlyÚdatabaseÚsynchronousN)Údefaultzstate.db (guest)rP  )	rv  rx  ro  r0   re   r  Úhermes_cli.configry  rz  )r‚   rx  Úokry  rz  Úraw_synchronouss         r&   Úapply_durability_barriersr�  g  sk   € õ ;Ü	% dÓ	+€BÜ	×	Ñ	œYÓ	'ñ ZßCÙ&Ñ';Ó'=¸zÈ=ÐbfÔgÐgˆOÐtÙ% d¨OÐFXÕY÷Zð €I÷	Zð €Iús   «'AÁA&c                ó¢   — t        j                  t        «      5  | j                  d«       ddd«       | j	                  «        y# 1 sw Y   ŒxY w)zJLeave EXCLUSIVE locking mode (so the file is never left pinned) and close.zPRAGMA locking_mode=NORMALN)r0   re   r  r{   rt   ©r‚   s    r&   Ú_close_unpinnedr„  t  s;   € ä	×	Ñ	œYÓ	'ñ 3Ø�‰Ð1Ô2÷3à‡J�J…L÷3ð 3ús   šAÁAc                óŒ   — t        | d¬«      }	 d|dfD ]  }|j                  |«       Œ 	 |S # t        $ r t        |«       ‚ w xY w)z›Zero-timeout connection holding ``locking_mode=EXCLUSIVE`` after a rolled-back
    *begin*; closed (unpinned) and re-raised when exclusion cannot be taken.g        rj  zPRAGMA locking_mode=EXCLUSIVEÚROLLBACK)rp  r{   ÚBaseExceptionr„  )r%   Úbeginr‚   Ú	statements       r&   Ú_open_exclusiverŠ  {  sZ   € ô # 7°CÔ8€DðØ9¸5À*ÐMò 	$ˆIØ�L‰L˜Õ#ñ	$ð
 €Køô ò Ü˜ÔØðús	   �- ­Ac              #  óÂ   K  — 	 t        | d«      }	 |df–— t	        |«       y# t        j                  t        f$ r}d|f–— Y d}~yd}~ww xY w# t	        |«       w xY w­w)u  Yield ``(conn, None)`` â€” one live connection that excludes writers for repair surgery â€” or ``(None,
    exc)`` when exclusion could not be taken.

    ``locking_mode=EXCLUSIVE`` retains file-level exclusion after the short ``BEGIN EXCLUSIVE`` is rolled
    back; the rollback is essential because ``Connection.backup`` uses this connection as *source* and later
    as the promotion *destination*, both transaction-free. It stays open across the snapshot -> strategies ->
    promotion window so no writer can commit a change promotion would overwrite. Existing readers make
    acquisition fail rather than being disturbed (fail closed). Timeout 0: the cross-process lock already
    serializes repairers, and a partial repair is less safe than "stop the gateway and retry".zBEGIN EXCLUSIVEN)rŠ  r~   ÚErrorr8   r„  )r%   ÚguardrK   s      r&   Ú_exclusive_repair_db_guardrŽ  ˆ  sb   è ø€ ðÜ Ð):Ó;ˆðØ�TˆkÒô 	˜Õøô �M‰Mœ7Ð#ò Ø�CˆiŠÜûðûô 	˜Õüs;   ‚A„# ‘A —A£A¼AÁAÁAÁAÁAÁA)Úsource_connectionÚdestination_connectionc               óœ  ‡‡	— t        | «      Š	t        j                  «       ‰	z   Šdˆˆ	fd„}t        j                  «       5 }|xs |j                  t        | «      «      }|�!|j                  rt        j                  d«      ‚|xs |j                  t        |«      «      }|j                  |d|t        ¬«       ddd«       y# 1 sw Y   yxY w)a.  Copy one complete SQLite snapshot without replacing either file inode: the online backup API folds
    committed WAL frames into the source snapshot and writes the destination in one transaction (rolled
    back if interrupted), so ``state.db`` is never swapped out from under handles that refer to it.c                óT   •— t        j                  «       ‰k\  rt        d‰d›d�«      ‚y )Nz/timed out copying SQLite repair snapshot after z.0fÚs)ri   rj   ÚTimeoutError)Ú_statusÚ
_remainingÚ_totalro   Údeadline_secondss      €€r&   Ú_check_deadlinez0_copy_database_snapshot.<locals>._check_deadlineª  s2   ø€ Ü�>‰>Ó˜xÒ'ÜÐ!PÐQaÐbeÐPfÐfgÐhÓiÐið (r+   Nz:SQLite repair backup destination has an active transactioné   )ÚpagesÚprogressrl   )r•  r}   r–  r}   r—  r}   rÜ   ÚNone)r»   ri   rj   r0   Ú	ExitStackÚenter_contextrr  Úin_transactionr~   ÚProgrammingErrorÚbackuprm   )
r¸   Údestination_pathr�  r�  r™  ÚownedÚsourceÚdestinationro   r˜  s
           @@r&   Ú_copy_database_snapshotr§     s¼   ù€ ô 8¸ÓDÐÜ�~‰~ÓÐ"2Ñ2€Höjô 
×	Ñ	Ó	ð i 5Ø"ÒT e×&9Ñ&9¼,À{Ó:SÓ&TˆØ!Ð-Ð2H×2WÒ2Wä×*Ñ*Ð+gÓhÐhØ,Òc°×0CÑ0CÄLÐQaÓDbÓ0cˆØ�‰�k¨°ÔNgˆÔh÷i÷ iñ iús   Á A9CÃCc                ó,   ‡ — t        ˆ fd„dD «       «      S )zW``no such table/column``: FTS5 / core tables not created yet (brand new file mid-init).c              3  óT   •K  — | ]  }|t        ‰«      j                  «       v –— Œ! y ­wr   rÀ   rÂ   s     €r&   r'   z$_schema_not_built.<locals>.<genexpr>¹  s!   øè ø€ ÒR¨ˆq”C˜“H—N‘NÓ$Ô$ÑRùrÄ   )zno such tablezno such column)rÈ   ©rK   s   `r&   Ú_schema_not_builtr«  ·  s   ø€ äÓRÐ.QÔRÓRÐRr+   zUmessages_fts(_trigram|_cjk)?(_data|_idx|_content|_docsize|_config|_segdir|_segments)?z\bTree (\d+)\bzmissing from index (\S+)c                óÔ  — |D ���ci c]  \  }}}|sŒ
t        |«      |“Œ }}}}| D ]¸  }t        |«      }|j                  d«      r yt        j	                  |«      }|rC|j                  t        |j                  d«      «      d«      }|rt        j                  |«      s yt        j	                  |«      }	|	sŒ“t        j                  |	j                  d«      «      rŒ¸ y yc c}}}w )aë  True when any damaged object named by ``PRAGMA integrity_check`` output lies outside
    the FTS shadow set: a ``Tree N`` id mapped through ``sqlite_master.rootpage``, an index in
    ``row N missing from index X``, or the file's own freelist. Unparseable lines are not
    counted (the FTS wording stays, which is incomplete rather than wrong). #88587: an FTS
    rebuild cannot repair a canonical b-tree, and the ``.malformed-backup`` it leaves behind
    is a snapshot of the same damage.ÚFreelistTrN   rV  F)
r}   r>   rÿ   Ú_INTEGRITY_TREE_REÚsearchrþ   ÚgroupÚ_FTS_OBJECT_REÚ	fullmatchÚ_INTEGRITY_MISSING_INDEX_RE)
Úintegrity_linesÚmaster_rowsÚrpÚ_typer"   Úname_by_rootpageÚlineÚtextÚtreeÚmissings
             r&   Úintegrity_damage_is_structuralr½  Å  sÏ   € ð >I×OÐO©/¨"¨e°TÊBœ˜B› ™ÐOÐÒOØò ˆÜ�4‹yˆØ�?‰?˜:Ô&ÙÜ!×(Ñ(¨Ó.ˆÙØ#×'Ñ'¬¨D¯J©J°q«MÓ(:¸BÓ?ˆDÙœN×4Ñ4°TÔ:ÙÜ-×4Ñ4°TÓ:ˆÚœ>×3Ñ3°G·M±MÀ!Ó4DÕEÙðð ùô Ps
   ‡C#“C#c                ó®  — 	 t        j                  t        | «      dd¬«      }	 |j	                  d«      j                  «       D �cg c]  }t        |«      ‘Œ }}|j	                  d«      j                  «       D �cg c]  }t        |d   «      ‘Œ }}	 |j                  «        t        t        j                  j                  d„ |D «       «      |«      S # t         j                  $ r Y yw xY wc c}w c c}w # t         j                  $ r Y |j                  «        yt         j                  $ r Y |j                  «        yw xY w# |j                  «        w xY w)	a©  Read-only ``integrity_check`` + ``sqlite_master`` rootpage map on a fresh connection;
    ``integrity_damage_is_structural`` over the result. A check that RAISES instead of
    reporting (a torn page under the walk) is structural too: no FTS-only fixture does that
    while ``messages``/``sessions`` read cleanly, and the FTS rebuild ladder cannot help.
    Cannot-open / locked stays False so the caller keeps the FTS path.Tg      ð?)Úurirn   FzASELECT rootpage, type, name FROM sqlite_master WHERE rootpage > 0úPRAGMA integrity_checkr   c              3  ó<   K  — | ]  }|j                  «       –— Œ y ­wr   )Ú
splitlines)r#   r¹  s     r&   r'   z1state_db_has_structural_damage.<locals>.<genexpr>ñ  s   è ø€ Ò%J¸D d§o¡o×&7Ñ%Jùs   ‚)r~   Úconnectr   rŒ  r{   ÚfetchallÚtupler>   re  rt   r   r½  rE  ÚchainÚfrom_iterable)r%   r‚   Úrrµ  Úliness        r&   Ústate_db_has_structural_damagerÊ  Ü  s'  € ðÜ�‰Ô/°Ó8¸dÈCÔPˆð	Ø)-¯©ØOó*QßQYÑQYÓQ[ö] A”u˜Q•xð ]ˆð ]à$(§L¡LÐ1IÓ$J×$SÑ$SÓ$UÖV˜q”�Q�q‘T•ÐVˆÑVð 	�
‰
ŒÜ)Ü�‰×%Ñ%Ñ%JÀEÔ%JÓJÈKóYð Yøô �=‰=ò Ùðüò]ùâVøÜ×#Ñ#ò Øð 	�
‰
�ô × Ñ ò Øà�
‰
�ðûð 	�
‰
�ús^   ‚!C ¤!C5 ÁC+Á#C5 Á:C0ÂC5 ÃC(Ã'C(Ã+
C5 Ã5D?ÄE ÄD?Ä,E Ä>D?Ä?E ÅEc           	     ó€  — ddl m}m} t        | «      }	 t	        j
                  |«      5   ||«       |j                  d«      j                  «        |j                  d«      j                  «       }|D �cg c]3  }|sŒt        |d   «      j                  «       dk7  sŒ&t        |d   «      ‘Œ5 }}|rdj                  |dd «      cddd«       S |j                  d	«      j                  «        t        D ])  }	 |j                  d
|› d|› d�«      j                  «        Œ+ dt'        j(                  «       › �}
	 |j                  d«       |j                  d|
dt'        j&                  «       f«       |j                  d|
ddt'        j&                  «       f«       t        D ]  }	 |j                  d|› d|› d�«       Œ 	 t	        j*                  t        j,                  «      5  |j                  d«       ddd«       	 ddd«       yc c}w # t        j                  $ r]}|j                  |«      xs t!        |«      }	t#        |t        j$                  «      r|	sd|› d|› �cY d}~c cddd«       S Y d}~�Œ€d}~ww xY w# t        j$                  $ r(}|j                  |«      st!        |«      s‚ Y d}~�Œd}~ww xY w# t        j                  $ rß}t!        |«      sdt        |«      j                  «       v rWY d}~t	        j*                  t        j,                  «      5  |j                  d«       ddd«       n# 1 sw Y   nxY wddd«       yd|› �cY d}~t	        j*                  t        j,                  «      5  |j                  d«       ddd«       n# 1 sw Y   nxY wcddd«       S d}~ww xY w# 1 sw Y   �Œ¿xY w# t	        j*                  t        j,                  «      5  |j                  d«       ddd«       w # 1 sw Y   w xY wxY w# 1 sw Y   yxY w# t        j                  $ r}t        |«      cY d}~S d}~ww xY w)uÆ  Probe a DB on a fresh connection. Returns None if healthy, else a reason.

    Runs the first statement that trips the malformed-schema parse (``PRAGMA journal_mode``),
    ``integrity_check``, a ``sessions`` read, FTS5 MATCH probes and a rolled-back ``messages`` write â€” so FTS5
    index corruption (reads and ``integrity_check`` pass, every ``INSERT INTO messages`` fails through the FTS
    triggers) is reported as unhealthy.

    See #50502.
    r   )Ú	SessionDBÚload_fts5_cjk_extensionzPRAGMA journal_moderÀ  r  ú; Nr   zSELECT COUNT(*) FROM sessionszSELECT 1 FROM z WHERE z MATCH '""' LIMIT 1zfts5 read probe failed on ú: Ú_hermes_fts_health_probe_zBEGIN IMMEDIATEz>INSERT INTO sessions (id, source, started_at) VALUES (?, ?, ?)Ú_health_probezOINSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)ÚuserÚ_fts_health_probeúINSERT INTO ú(z) VALUES('flush')z no such tokenizer: cjk_unicode61r†  zfts5 write probe failed: )r   rÌ  rÍ  rp  r0   rd   r{   r|   rÄ  r>   rÁ   ÚjoinÚ_FTS_TABLESr~   r   Ú_is_fts5_unavailable_errorr«  rÅ   re  ri   Útime_nsre   rŒ  )r%   rÌ  rÍ  r‚   ÚrowsrÈ  ÚproblemsÚ	fts_tablerK   ÚbenignÚprobe_session_ids              r&   Ú_db_opens_cleanlyrß  ô  s¼  € ÷ @ô
 # 7Ó+€Dð<Ü×Ñ Ó%ñ 9	ñ $ DÔ)Ø�L‰LÐ.Ó/×8Ñ8Ô:Ø—<‘<Ð 8Ó9×BÑBÓDˆDØ+/ÖS a²1¼¸Q¸q¹T»¿¹Ó9JÈdÓ9Rœ˜A˜a™D�	ÐSˆHÐSÙØ—y‘y ¨"¨1 Ó.÷9	ñ 9	ð �L‰LÐ8Ó9×BÑBÔDô )ò O�	ðOØ—L‘L >°)°¸GÀIÀ;ÐNcÐ!dÓe×nÑnÕpðOð& ";¼4¿<¹<»>Ð:JÐKÐð-Ø—‘Ð.Ô/Ø—‘Ð]Ø.°ÄÇÁÃÐMôOà—‘ÐnØ.°Ð8KÌTÏYÉYË[ÐYô[ä!,ò "�Ið"ØŸ™ |°I°;¸aÀ	¸{ÐJ[Ð%\Õ]ñ"ô   ×(Ñ(¬¯©Ó7ñ -Ø—L‘L Ô,÷-à÷s9	ð 9	ùò Tøô ×,Ñ,ò 	Oð '×AÑAÀ#ÓFÒ`ÔJ[Ð\_ÓJ`�FÜ& s¬G×,DÑ,DÔEÉ&ð "<¸I¸;ÀbÈÈÐNÖN÷39	ñ 9	õ* KQûð	Oûô4 #×3Ñ3ò "Ø )× DÑ DÀSÔ IÔM^Ð_bÔMcØ!ÿùð"ûô ×(Ñ(ò 9ô % SÔ)Ð-OÔSVÐWZÓS[×SaÑSaÓScÑ-cÛô  ×(Ñ(¬¯©Ó7ñ -Ø—L‘L Ô,÷-÷ -ñ -ú÷o9	ð 9	ðj 3°3°%Ð8Ô8ä×(Ñ(¬¯©Ó7ñ -Ø—L‘L Ô,÷-÷ -ñ -ú÷o9	ñ 9	ûðZ9ú÷-ñ -û”Z×(Ñ(¬¯©Ó7ñ -Ø—L‘L Ô,÷-÷ -ñ -ý÷o9	ð 9	ûôt × Ñ ò Ü�3‹x�ûðús�  •P ªAP	Á5HÁ=HÂHÂ-P	Ã	P Ã(P	Ã7&HÄP	Ä7A)J9Æ!I;Æ9J9Æ<#P	ÇN.Ç1	P	Ç:P ÈP	ÈI8ÈAI3ÉI8ÉP	É#	P É-P	É3I8É8P	É;J6ÊJ1Ê+J9Ê1J6Ê6J9Ê9N+Ë&N&Ë2N;Ë6#P	ÌL4Ì+	P	Ì4L=	Ì9P	Í P Í	N&ÍN+ÍN;Í#P	Í5NÎ	P	ÎNÎP	Î	P Î&N+Î+N;Î.N8	Î3P	Î;$PÏO:	Ï1	PÏ:PÏ?PÐP	Ð	PÐP ÐP ÐP=Ð(
P8Ð2P=Ð8P=c                ó8   — ddl }|j                  | t        ¬«      S )u  True when another process (or a connection outside this call) still holds ``db_path``.

    The foreign-holder scan (``hermes_state_holders``) is the authority: any other process with the DB or a
    WAL sidecar open, a deleted WAL generation, or an unknown/uninspectable holder fails CLOSED. The SQLite
    probe (``locking_mode=EXCLUSIVE`` + ``BEGIN IMMEDIATE``) is only an additional positive signal â€” it cannot
    see a ``journal_mode=DELETE`` reader and cannot run on a malformed file, which is why the scan comes first.r   N)Úconnect_repair_durable)Úhermes_state_holdersÚlive_writer_holds_dbrp  )r%   Ú_state_holderss     r&   Ú_live_writer_holds_dbrå  C  s   € ó 2Ø×.Ñ.¨wÔOfÐ.ÓgÐgr+   c                óh   — |�t        |«      rd| d<   || d<   t        j                  d|› d�|«       | S )zÚRecord *error* on *report* and log it as ``state.db repair <verb>``. An
    *exc* proving deterministic corruption consumes the persistent repair budget
    (private ``_repair_attempted`` marker, popped by the caller).TÚ_repair_attemptedr¬   zstate.db repair z: %s)rÊ   rG   r¬   )ÚreportÚverbr¬   rK   s       r&   Ú_repair_skiprê  N  sB   € ð €Ô;¸CÔ@Ø&*ˆÐ"Ñ#Ø€Fˆ7�OÜ
‡L�LÐ# D 6¨Ð.°Ô6Ø€Mr+   T)r¢  c          	     ó¸  — dddddœ}t        dd¬«       t        | «      } | j                  «       s
| › d�|d<   |S t        | «      rt	        |d	t        | «      «      S t        | «      5 }|s't        | «      €d
\  |d<   |d<   nd|d<   |cddd«       S |}t        | «      rt	        |d	t        | «      «       nIt        | «      rt	        |d	d«       n0t        | «      }t        | |||¬«      }|j                  d«      r||d<   |j                  dd«      s|j                  d«      r%t        | t        |j                  d«      «      ¬«       ddd«       |S # 1 sw Y   S xY w)a  Repair a state.db whose ``sqlite_master`` is malformed or whose FTS indexes reject writes.

    Two corruption classes: malformed schema / "duplicate object definition" (even ``PRAGMA`` fails), and FTS
    write-corruption (reads and ``integrity_check`` pass, writes fail through ``messages_fts*`` triggers).
    ``_REPAIR_STRATEGIES`` run least-destructive first on a complete snapshot; a success is copied back
    transactionally, so canonical rows are never modified by a failed attempt. A raw backup is taken first
    unless ``backup=False``. Serialised across processes (gateway, Desktop backend and CLI open the same file;
    concurrent ``writable_schema`` surgery is itself a corruption source). Returns ``{repaired, strategy,
    backup_path, error}``.

    See #50502.
    FN)r  Ústrategyr8  r¬   g      Œ@Ústate_db_repair)Úphasez does not existr¬   Úskipped)TÚrepaired_by_other_processr  rì  z¢could not obtain the state.db repair lock (held by another process, or the lock file was unopenable); skipped schema surgery to avoid racing a concurrent repairerz¥a live writer still holds state.db; skipped schema surgery to avoid tearing b-tree pages under a concurrent writer. Stop the gateway (hermes gateway stop) and retry.)r¢  rè  Újournal_mode_beforerñ  rç  )r  )r   r   r˜   r  rê  r
  rv   rß  rå  Ú_probe_journal_mode_for_repairÚ_repair_state_db_schema_lockedrþ   Úpopr  Úbool)r%   r¢  rè  Úholding_lockÚresultÚbefore_modes         r&   Úrepair_state_db_schemarù  Y  sƒ  € ð +0¸TÐRVÐaeÑf€Fô ˜EÐ):Õ;Ü�7‹m€GØ�>‰>ÔØ$˜I _Ð5ˆˆw‰Øˆô -¨WÔ5Ü˜F IÔ/QÐRYÓ/ZÓ[Ð[ä	# GÓ	,ð #S°Ùô ! Ó)Ð1Ø9ZÑ6��zÑ" F¨:Ò$6ð$x��w‘à÷#Sñ #Sð ˆô 1°Ô9Ü˜ Ô,NÈwÓ,WÕXô # 7Ô+Ü˜ ð -uõ vô 9¸ÓAˆKÜ3°GÀFÐSYØHSôUˆFà�z‰z˜*Ô%Ø0;�Ð,Ñ-ð �:‰:Ð)¨5Ô1°V·Z±ZÀ
Ô5KÜ" 7´T¸&¿*¹*ÀZÓ:PÓ5QÕR÷G#SðH €M÷I#SðH €Mús   Á& EÂB5EÅEc                ó    — ddl m} 	 t        | «      5 } ||«      cddd«       S # 1 sw Y   yxY w# t        j                  t
        f$ r Y yw xY w)uù   Best-effort journal-mode probe: ``wal``/``delete``, or ``None`` when the file cannot be opened or
    probed (malformed header, concurrent opener's locks â€” both expected on the repair path); callers then
    fall back to ``database.journal_mode``.r   )Ú_on_disk_journal_modeN)rv  rû  rr  r~   rŒ  r8   )r%   rû  r‚   s      r&   rò  rò  Ÿ  sM   € õ 7ðÜ˜'Ó"ð 	/ dÙ(¨Ó.÷	/÷ 	/ò 	/ûä�M‰Mœ7Ð#ò Ùðús#   ˆ1 “%›	1 ¥.ª1 ®1 ±AÁArƒ  c               ód  — ddl m} 	 |€)t        | «      5 } ||| j                  ¬«      }ddd«       n ||| j                  ¬«      }|r|k7  rt        j                  d|||«       yyy# 1 sw Y   Œ+xY w# t        j                  t        f$ r!}t        j                  d| |«       Y d}~yd}~ww xY w)uô  Re-apply the journal mode after schema surgery.

    ``conn`` must be the exclusive repair guard connection when called from the repair path: opening a fresh
    connection AFTER the guard released let a writer still holding the unlinked old ``-wal`` inode coexist with
    a brand-new ``state.db-wal`` â€” two generations of one store. The reopen is the hazard, not the mode.

    A rebuilt file comes back in the default (delete) mode; without this a corruption event silently moves a
    WAL store out of WAL (the open-time WAL-reset gate never sees a flip made inside repair). Routed through
    :func:`apply_wal_with_fallback`, not a direct pragma, so it inherits the WAL-reset gate (a vulnerable
    runtime deliberately keeps DELETE; the journal_mode-changed WARNING is expected there), the macOS-NFS
    silent-refusal handling and the WAL companions. ``before_mode`` is only for the log comparison; the target
    is ``database.journal_mode``. Best-effort: the repair already succeeded, so failures log at WARNING.

    See #89674.
    See #89393.
    The transactional promotion already leaves the destination in its pre-repair mode, so on that path this
    is mostly the WAL-companion re-assertion; the reopen is the hazard, not the mode. See #101064.
    r   )Úapply_wal_with_fallbackNrP  z§state.db repair changed journal_mode %r -> %r (pre-surgery probe %r; restore resolved through apply_wal_with_fallback per database.journal_mode and the WAL-reset gate)zvstate.db repair at %s: post-surgery journal-mode restore failed (%s); verify with PRAGMA journal_mode on the next open)	rv  rý  rr  r"   rG   rH   r~   rŒ  r8   )r%   rø  r‚   rý  r¤  ÚafterrK   s          r&   Ú"_restore_journal_mode_after_repairrÿ  «  sÀ   € õ& 9ðFØˆ<Ü˜gÓ&ð N¨%Ù/°ÀÇÁÔM�÷Nð Nñ ,¨D¸7¿<¹<ÔHˆEÙ˜5 KÒ/Ü�N‰Nð oà&¨¨{õ<ð 0ˆ;÷	Nð Nûô �M‰Mœ7Ð#ò FÜ�‰ð 7Ø8?À÷	Fñ 	FûðFús-   ˆA5 •A)ª<A5 Á)A2Á.A5 Á5B/ÂB*Â*B/)rñ  c          
     ó*  — | j                  | j                  › d�«      }t        |«      x}�t        |dd|› �«      S t	        | «      €d\  |d<   |d<   |S |r2t        | «      \  }}|rt        |«      nd|d<   |€t        |dd	|› �«      S t        | «      5 \  }}	|€t        |d
d|	› �|	¬«      cddd«       S t        | «      x}
�t        |d|
«      cddd«       S 	 t        | ||¬«       	 d|d<   t        ||«       |j                  d«      rC	 t        || |¬«       t         j#                  d|j                  d«      | «       t%        | ||¬«       |j                  d«      st         j+                  d| |d   «       |t        |«      x}�t         j#                  d|«       cddd«       S # t        t        j                  t        f$ r3}t        |«       t        |dd| › d|› �|¬«      cY d}~cddd«       S d}~ww xY w# t        t        j                  t        f$ rF}|j'                  ddt)        |«      ¬«       d|› �|d<   t         j+                  d|«       Y d}~�Œd}~ww xY w# t        |«      x}�t         j#                  d|«       w w xY w# 1 sw Y   yxY w)uQ  Repair strategies for :func:`repair_state_db_schema`; caller holds the cross-process repair lock.

    Strategies run on a SCRATCH COPY, copied back through SQLite's transactional backup API only once proven to open
    cleanly, so a failed repair cannot modify or lose committed data (a WAL checkpoint of committed frames on guard
    release is not a repair mutation). WHY not in place: the final strategy ends in ``VACUUM``, which rebuilds the
    file from the schema SQLite can still parse â€” when the damage IS in the schema b-tree (the ``malformed database
    schema ()`` class) every table hanging off the unreadable part is silently dropped, the probe still reports
    malformed, and repair returned ``repaired=False`` having destroyed what it was asked to save.

    The pre-repair backup (#69603) does not close this: it is a forensic artefact that nothing reads back,
    so recovery still depends on a human noticing a ``.malformed-backup-*`` file and knowing what to do with
    it. Not mutating the original in the first place is the property that holds without a human in the loop.
    z.repair-scratchNÚabortedzBcould not remove a stale repair snapshot before probing state.db: )TÚalready_healthyr  rì  r8  zepre-repair backup refused; aborting schema repair to avoid mutating the only copy of the damaged DB: rï  z”could not acquire exclusive state.db repair ownership; skipped schema surgery to avoid overwriting a concurrent writer. Stop the gateway and retry: rª  )r�  z5could not stage a complete SQLite repair snapshot of rÏ  Trç  )r�  z;state.db repaired via '%s' and promoted transactionally: %srƒ  F)r  rì  rç  z9repaired snapshot could not be promoted transactionally: r¬   z$state.db repair promotion failed: %sz¥state.db schema repair could not recover %s automatically (no committed canonical data was modified or lost; backup: %s); manual restore from backup may be required.z:Could not remove state.db repair snapshot after repair: %s)r!   r"   Ú_unlink_db_triplerê  rß  rO  r>   rŽ  r¯   r§  r8   r~   rŒ  r”  Ú_run_repair_strategiesrþ   rG   rH   rÿ  rç   rÊ   r¬   )r%   r¢  rè  rñ  ÚscratchÚcleanup_errorÚbpathÚbackup_errorÚ
live_guardÚguard_errorÚspace_errorrK   s               r&   ró  ró  Î  s  € ð  ×Ñ 7§<¡< .°Ð @ÓA€GÜ*¨7Ó3Ð3ˆÐ@Ü˜F IÐ1sð  uBð  tCð  0Dó  Eð  	Eô ˜Ó!Ð)Ø1HÑ.ˆˆzÑ˜F :Ñ.ØˆÙÜ-¨gÓ6Ñˆˆ|Ù.3¤ E¤
¸ˆˆ}ÑØˆ=Ü ¨	ð 4MØMYÈNð4\ó ]ð ]ô 
$ GÓ	,ð +lÑ0I°¸[ØÐÜ ¨	ð 4xà#. -ð41à6AôC÷+lñ +lô
 7°wÓ?Ð?ˆKÐLÜ ¨	°;Ó?÷+lñ +lð	Fô $ G¨WÈ
ÕSð	lð +/ˆFÐ&Ñ'Ü" 7¨FÔ3Ø�z‰z˜*Ô%ð
fÜ+¨G°WÐU_Õ`ô —N‘NÐ#`Ø#)§:¡:¨jÓ#9¸7ôDä6°wÐ@SÐZdÕeØ—:‘:˜jÔ)ô —‘ð nà$ f¨]Ñ&;ô=ð ô "3°7Ó!;Ð;�ÐHÜ—‘Ð[Ð]jÔk÷W+lñ +løô œŸ™¬Ð5ò 	FÜ˜gÔ&Ü ¨	Ð5jÐkrÐjsÐsuÐvyÐuzÐ3{ð  BEô  Fô  F÷+lñ +lûð	Fûô  ¤§¡´Ð=ò NØ—M‘M¨5¸4Ü4TÐUXÓ4Yð "ô [à(aÐbeÐafÐ&g�F˜7‘OÜ—L‘LÐ!GÈ×MÒMûð	Nûô$ "3°7Ó!;Ð;�ÐHÜ—‘Ð[Ð]jÕkð Iú÷U+lð +lús   ÂJ	Â4J	ÃF%Ã'"I Ä
G9ÄA I Å8#J	Æ%G6ÇG1Ç"G6Ç#J	Ç1G6Ç6J	Ç9IÈ;IÉI ÉIÉI É &JÊJ	Ê	Jc                óf  — ddl m} g }| gt        | «      ¢­D ]&  }t        d«      D ]  }	 |j	                  d¬«        Œ& Œ( d
j                  |«      xs dS # t
        $ rQ}t        |t        «      r!|r|dk  rt        j                  d«       Y d}~Œj|j                  |› d	|› �«       Y d}~Œod}~ww xY w)zCRemove *path* and every SQLite sidecar; return any cleanup failure.r   rX   é
   Tr  é	   gš™™™™™©?NrÏ  rÎ  )r   rY   r*   Úranger  r8   rÅ   ÚPermissionErrorri   rl   r6  rÖ  )rí   rY   Úfailuresr+  ÚattemptrK   s         r&   r  r    s´   € å(Ø€HØÐ*œ) D›/Ñ*ò 
ˆÜ˜R“yò 		ˆGð4Ø—‘¨�Ô.ñ ñ		ð
ð �9‰9�XÓÒ& $Ð&øô ò 4ä˜c¤?Ô3¹ÈÐRSÊÜ—J‘J˜tÔ$ÜØ—‘ 6 (¨"¨S¨EÐ 2×3Ñ3ûð4ús   ªAÁ	B0Á,B+ÂB+Â+B0c                óŒ   — | j                  d«        |«       rt        | «       | j                  d«       | j                  «        y)z‡Run *edit* under ``writable_schema=ON``; bump the schema cookie when it
    reports a change so live peers discard their cached schema.zPRAGMA writable_schema=ONzPRAGMA writable_schema=OFFN)r{   r„   Úcommit)r‚   Úedits     r&   Ú_edit_sqlite_masterr  0  s4   € ð 	‡L�LÐ,Ô-Ù„vÜ˜DÔ!Ø‡L�LÐ-Ô.Ø‡K�K…Mr+   c           	     óÖ   — ddl m}  || «       t        D ]F  }t        j                  t
        j                  «      5  | j                  d|› d|› d�«       ddd«       ŒH y# 1 sw Y   ŒSxY w)zŒFTS5 'rebuild' rewrites each index from the content table: the least-
    destructive fix for an index that rejects writes while reads work.r   )rÍ  rÔ  rÕ  z) VALUES('rebuild')N)r   rÍ  r×  r0   re   r~   re  r{   )r‚   rÍ  Ú
table_names      r&   Ú_strategy_rebuild_ftsr  :  so   € õ 5á˜DÔ!Ü!ò Vˆ
Ü× Ñ ¤×!9Ñ!9Ó:ñ 	VØ�L‰L˜<¨
 |°1°Z°LÐ@SÐTÔU÷	Vð 	VñV÷	Vð 	Vús   »AÁA(	c                ó\   — t        | «       | j                  d«       | j                  «        y)z’integrity_check reports "wrong # of entries in index" when a B-tree
    index drifts from its base table; REINDEX rewrites it from canonical rows.ÚREINDEXN)ro  r{   r  rƒ  s    r&   Ú_strategy_reindexr  E  s!   € ô ! Ô&Ø‡L�L�ÔØ‡K�K…Mr+   c                ó*   ‡ — dˆ fd„}t        ‰ |«       y)zJDe-duplicate sqlite_master (lowest rowid per type/name wins), keeping FTS.c                 óš   •— ‰j                  d«      j                  «       } | D ]  \  }}}}‰j                  d|||f«       Œ t        | «      S )NzhSELECT type, name, COUNT(*) AS c, MIN(rowid) AS keep FROM sqlite_master GROUP BY type, name HAVING c > 1zFDELETE FROM sqlite_master WHERE type IS ? AND name IS ? AND rowid <> ?)r{   rÄ  rõ  )ÚdupesÚtype_r"   Ú_countr)  r‚   s        €r&   Ú_dedupz&_strategy_dedup_schema.<locals>._dedupP  s]   ø€ Ø—‘Øvó
ç
‰(‹*ð 	ð */ò 	.Ñ%ˆE�4˜ Ø�L‰LÐaØ  tÐ,õ.ð	.ô �E‹{Ðr+   N©rÜ   rõ  )r  )r‚   r"  s   ` r&   Ú_strategy_dedup_schemar$  N  s   ø€ õô ˜˜fÕ%r+   c                ó\   ‡ — t        ‰ ˆ fd„«       t        ‰ «       ‰ j                  d«       y)zæDrop all FTS schema and VACUUM; indexes rebuild on the next open. The
    destructive one, and why strategies run on a scratch copy: on a damaged
    schema b-tree VACUUM silently drops every table hanging off the unreadable part.c                 ó.   •— ‰ j                  d«      xs dS )Nz9DELETE FROM sqlite_master WHERE name LIKE 'messages_fts%'T)r{   rƒ  s   €r&   r4   z+_strategy_drop_fts_vacuum.<locals>.<lambda>`  s   ø€  d§l¡lÐ3nÓ&oÒ&wÐsw€ r+   ÚVACUUMN)r  ro  r{   rƒ  s   `r&   Ú_strategy_drop_fts_vacuumr(  \  s$   ø€ ô ˜ÓwÔxä  Ô&Ø‡L�L�Õr+   Úrebuild_ftsz<state.db FTS indexes rebuilt in place (schema preserved): %sz-state.db FTS in-place rebuild pass failed: %sÚreindex_btreez/state.db B-tree indexes rebuilt via REINDEX: %sz state.db REINDEX pass failed: %sÚdedup_schemazRstate.db schema repaired by de-duplicating sqlite_master (FTS index preserved): %sz%state.db dedup repair pass failed: %sÚdrop_fts_rebuildzdstate.db schema repaired by dropping FTS schema; indexes will rebuild from messages on next open: %sc                ór  — t         D ]^  \  }}}}	 t        | «      5 } ||«       ddd«       t        | «      }|€%d|c|d<   |d<   t        j                  || «       |c S |�ŒZ||d<   Œ` |S # 1 sw Y   ŒGxY w# t        j                  $ r-}t        |«      }|�t        j                  ||«       Y d}~Œzd}~ww xY w)uß   Escalating repair attempts, applied to *db_path* IN PLACE â€” only ever a scratch copy nothing else holds open,
    never the user's database. The "could not recover" log lives in the caller so it names the user's database.NTr  rì  r¬   )Ú_REPAIR_STRATEGIESrr  rß  r~   r   r>   rG   rH   )	r%   rè  r"   ÚbodyÚsuccess_msgÚfailure_msgr‚   rI  rK   s	            r&   r  r  u  sÍ   € ô 1Cò %Ñ,ˆˆd�K ð	1Ü˜gÓ&ð ¨$Ù�T”
÷ä& wÓ/ˆFð
 ˆ>Ø59¸4Ð2ˆF�:Ñ  zÑ 2Ü�N‰N˜;¨Ô0ØŠMØÑØ$ˆF�7ŠOð%ð €M÷ð ûô ×$Ñ$ò 	1Ü˜“XˆFØÐ&Ü—‘˜{¨CÔ0ÿøð	1ús-   �A6›	A*¤A6Á*A3	Á/A6Á6B6Â	#B1Â1B6)r%   r   )r%   r   r5   r>   rÜ   úOptional[str])r%   r   rÜ   rõ  )r%   r   r$   r>   r5   r>   rI   r>   )rU   r>   rÜ   r�  )rÜ   r�  )ra   rõ  rÜ   r�  )rJ   r   rn   Úfloat)r%   r   rÜ   zOptional[Any])r‚   úsqlite3.ConnectionrÜ   r�  )r‹   rŠ   rÜ   rŠ   )r”   r}   rÜ   r}   )r%   r   r¢   r>   rÜ   z%'Tuple[Optional[str], int, int, int]')r%   r   rÜ   r2  )r¸   r   rÜ   r3  )rK   r‡  rÜ   rõ  )r%   r   rÜ   r   )r%   r   rÜ   ú'Optional[str]')r%   r   rÜ   z'Dict[str, Any]')r%   r   rÜ   r>   )r%   r   r  rõ  rÚ   r5  rÜ   r�  )r%   r   rÜ   z'List[Path]')r%   r   r)  r}   rÜ   r�  )r%   r   r7  r   r8  r   rÜ   r�  )r%   r   rÜ   z&'Tuple[Optional[Path], Optional[str]]')r%   r   rQ  r>   rÜ   r�  )r%   r   rn   r3  rÜ   r4  )r%   r   rn   r3  )r‚   r4  rÜ   rõ  )r%   r   rˆ  r>   rÜ   r4  )
r¸   r   r£  r   r�  úOptional[sqlite3.Connection]r�  r6  rÜ   r�  r#  r   )
rè  úDict[str, Any]ré  r>   r¬   r>   rK   zOptional[BaseException]rÜ   r7  )r%   r   r¢  rõ  rÜ   r7  )r%   r   rø  r2  rÜ   r�  )
r%   r   r¢  rõ  rè  r7  rñ  r2  rÜ   r7  )rí   r   rÜ   r2  )r%   r   rè  r7  rÜ   r7  )nÚ__doc__Ú
__future__r   r0   r  rÔ   rE  rö   Úloggingr4  Úrerž   r~   r™   ri   Úpathlibr   Útypingr   r   r   r   r	   r  r
   Úhermes_startup_watchdogr   râ  r   Úhermes_state_commonr   r   r   r   r   Ú	getLoggerrG   rm   r·   r   Ú_MAX_MALFORMED_BACKUPSr(   rÓ   r‰   r’   r“   r×  r±   r*   r:   r@   rL   rV   r_   rg   rp   Úcontextmanagerrv   ry   Ú_release_auto_maintenance_lockr„   r�   r•   r¥   r¯   r´   r»   rÊ   rÌ   rÞ   rð   rû   r  r
  r  r'  r,  r@  rO  ri  rp  rr  ro  r�  r„  rŠ  rŽ  r§  r«  Úcompiler±  r®  r³  r½  rÊ  rß  rå  rê  rù  rò  rÿ  ró  r  r  r  r  r$  r(  r.  r  r/  r+   r&   ú<module>rE     s0  ðñõ
 #ã Û Û Û Û Û Û 	Û 	Û Û Û Û Ý ß 3Õ 3å ,Ý ;Ý 1÷õ ð 
ˆ×	Ñ	˜>Ó	*€àÐ à3CÐ 0ð #$Ð ØÐ ð 4Ð à!Ð ð
 ';Ð #ð !2Ð Ø#Ð ØJ€ð^Ð óYó
ó$óóCóDð :?õ 
8ó2ð" ×Ñò(ó ð(óV
ð "6Ð óIóó_ó

Nó]ó
>ónónóEó:ó*>ó4ó]ó$_ð ]aõ Kó6=ð 9Oô Vóó.82ðv @Jõ $5ðN @Cõ ð* 58õ Qó

ó
óó
ð ×Ñòó ðð0 OSØSWñiØ/Kðià4Pðià\`óió.Sð �—‘Ø\ó€ð  �R—Z‘ZÐ 1Ó2Ð Ø(˜bŸj™jÐ)DÓEÐ óó.Yó0Ló^hôð =Aõ CóL	ð [_õ  FðH bfñKlØðKlØ"ðKlØ,:ðKlØQ^ðKlàóKló\'ó(óVóó&óð Ð)Ð+iØ4ð6àÐ'Ð)ZØ'ð)àÐ+ØYØ,ð.ð Ð2ØkÐmqðsð
Ð ôr+   