+
    jHàjè@  ã                   óŠ  € R t ^ RIt^ RIt^ RItR>t. ]ORN5tR R ltRt]RRR3t]P                  ]P                  3tR	 R
 ltR R ltR R ltR R ltR R ltR?tR R ltR@tRAt]! ]RR4      t]P.                  ! R4      tR R lt ! R  R!]4      t ! R" R#]4      t ! R$ R%]4      t ! R& R']4      t ! R( R)]4      t ! R* R+]4      t  ! R, R-]4      t! ! R. R/]4      t" ! R0 R1]4      t# ! R2 R3]#4      t$^Dt%R4t&R5t'R6t(R7]( R82t) ! R9 R:]PT                  4      t+R;t,]!R3]R3]R3]$R3]#R3]+R33t-RBRCRDRERF]R3RGRH3t.R< R= lt/R# )Iz¿Exception types and error-classification predicates for the state store.
Shared by hermes_state and its mixins; string predicates match wrapped RPC
strings as well as live sqlite3 exceptions.Nz database disk image is malformedc                ó0   € V ^8„  d   QhR\         R\        /# ©é   ÚexcÚreturn©ÚBaseExceptionÚbool)Úformats   "Ú8/home/ubuntu/.hermes/hermes-agent/hermes_state_errors.pyÚ__annotate__r      s   € ÷ ñ œ}ð ´ñ ó    c                óÌ   a € \        S \        P                  4      ;'       dC    \        ;QJ d#    V 3R l\         4       F  '       g   K   R# 	  R# ! V 3R l\         4       4      # )u“   Malformed-schema OR generic corrupt-image error. Diagnostics / offline
recovery only â€” runtime repair must use :func:`is_malformed_schema_error`.c              3   óZ   <"  € T F   q\        S4      P                  4       9   x € K"  	  R # 5i©N©ÚstrÚlower©Ú.0Úmarkerr   s   & €r   Ú	<genexpr>Ú(is_malformed_db_error.<locals>.<genexpr>   s#   øé € ð :Ù1F v”#�c“(—.‘.Ó"Ö"Ó1Fùó   ƒ(+TF)Ú
isinstanceÚsqlite3ÚDatabaseErrorÚanyÚ_MALFORMED_DB_MARKERS©r   s   fr   Úis_malformed_db_errorr       sY   ø€ ô �cœ7×0Ñ0Ó1÷ ð ·c³cô :Ý1Fó:·c´cð ²cð °cô :Ý1Fó:ó 7ð r   zdisk i/o errorzdatabase is lockedzdatabase table is lockedÚbusyc                ó2   € V ^8„  d   QhR\         R,          /# )r   r   N)Úint)r
   s   "r   r   r   +   s   € ÷ :ñ :¬¨d­
ñ :r   c                ó^   € \        V RR4      p\        V\        4      '       d
   V^ÿ,          # R# )zPPrimary result code (extended codes keep it in the low byte); None when unknown.Úsqlite_errorcodeN)Úgetattrr   r#   )Ú
exc_or_strÚcodes   & r   Ú_sqlite_primary_coder)   +   s,   € ä�:Ð1°4Ó8€DÜ$ T¬3×/Ò/ˆ4�$�;Ð9°TÐ9r   c                ó$   € V ^8„  d   QhR\         /# ©r   r   ©r	   )r
   s   "r   r   r   1   s   € ÷ .ñ .¬ñ .r   c                óˆ   € \        V 4      pVe
   V\        9   # \        V 4      P                  4       pRV9   ;'       g    RV9   # )z²SQLITE_BUSY / SQLITE_LOCKED: wait and retry, never treat as damage. A known result code
decides; only without one (our own re-raised messages, RPC-wrapped strings) does the text.Úlockedr!   )r)   Ú_SQLITE_LOCK_CODESr   r   ©r'   r(   Útexts   &  r   Úis_sqlite_lock_errorr2   1   sI   € ô   
Ó+€DØÒØÔ)Ñ)Ð)Üˆz‹?× Ñ Ó"€DØ�tÑ×-Ð-˜v¨™~Ð-r   c                óD   € V ^8„  d   QhR\         P                  R\        /# r   )r   ÚErrorr	   )r
   s   "r   r   r   ;   s   € ÷ 8ñ 8œ'Ÿ-™-ð 8¬Dñ 8r   c                ó:   € R\        V 4      P                  4       9   # )zŠTransient engine error on contended WAL appends (retries like locked/busy);
message-scoped because some builds raise it as InterfaceError.zno more rows availabler   r   s   &r   Ú_is_no_more_rowsr6   ;   s   € ð $¤s¨3£x§~¡~Ó'7Ñ7Ð7r   c                ó0   € V ^8„  d   QhR\         R\        /# r   r   )r
   s   "r   r   r   A   ó   € ÷ ñ ¤=ð ´Tñ r   c                óò   a € \        S \        P                  4      ;'       dV    \        S 4      ;'       gC    \        ;QJ d#    V 3R l\
         4       F  '       g   K   R# 	  R# ! V 3R l\
         4       4      # )zi"Busy right now", not "damaged": one predicate so retry and the HTTP
503-vs-500 split cannot drift apart.c              3   óZ   <"  € T F   q\        S4      P                  4       9   x € K"  	  R # 5ir   r   r   s   & €r   r   Ú,is_transient_sqlite_error.<locals>.<genexpr>E   s!   øé € Ð(lÑRkÈ´3°s³8·>±>Ó3CÖ)CÓRkùr   TF)r   r   ÚOperationalErrorr2   r   Ú_TRANSIENT_SQLITE_MARKERSr   s   fr   Úis_transient_sqlite_errorr>   A   s_   ø€ ô �cœ7×3Ñ3Ó4÷ ð Ü˜SÓ!×lÐl§S£SÔ(lÕRkÓ(l§S¤SðÚ%(ðØ%(Ô(lÕRkÓ(lÓ%lðr   c                ó0   € V ^8„  d   QhR\         R\        /# r   r   )r
   s   "r   r   r   I   r8   r   c                óÌ   a € \        S \        P                  4      ;'       dC    \        ;QJ d#    V 3R l\         4       F  '       g   K   R# 	  R# ! V 3R l\         4       4      # )z“Only SQLite's explicit malformed-schema text: a generic "disk image is
malformed" may be any B-tree page, so runtime repair must fail closed on it.c              3   óZ   <"  € T F   q\        S4      P                  4       9   x € K"  	  R # 5ir   r   r   s   & €r   r   Ú,is_malformed_schema_error.<locals>.<genexpr>L   s#   øé € ð :Ù1J v”#�c“(—.‘.Ó"Ö"Ó1Jùr   TF)r   r   r   r   Ú_MALFORMED_SCHEMA_MARKERSr   s   fr   Úis_malformed_schema_errorrD   I   sY   ø€ ô �cœ7×0Ñ0Ó1÷ ð ·c³cô :Ý1Jó:·c´cð ²cð °cô :Ý1Jó:ó 7ð r   c                óT   € V ^8„  d   QhR\         \        ,          R,          R\        /# )r   r   Nr   )r   r   r	   )r
   s   "r   r   r   X   s(   € ÷ Cñ CœM¬CÕ/°$Õ6ð C¼4ñ Cr   c                óf  a€ V f   R# \        V \        4      '       d#   \        V RR4      \        P                  8X  d   R# \        V \
        4      '       d   T M
\        V 4      P                  4       o\        ;QJ d#    V3R l\         4       F  '       g   K   R# 	  R# ! V3R l\         4       4      # )zFDisk-full / ENOSPC: OSError(ENOSPC), SQLITE_FULL, or matching strings.NFÚerrnoTc              3   ó,   <"  € T F	  qS9   x € K  	  R # 5ir   © )r   r   Úlowereds   & €r   r   Ú%is_disk_full_error.<locals>.<genexpr>_   s   øé € ÐBÑ/A V˜Ö Ó/Aùó   ƒ)	r   ÚOSErrorr&   rG   ÚENOSPCr   r   r   Ú_DISK_FULL_MARKERS)r   rJ   s   &@r   Úis_disk_full_errorrP   X   sz   ø€ à
‚{ÙÜ�#”w×Ò¤G¨C°¸$Ó$?Ä5Ç<Á<Ô$OÙÜ  ¤c×*Ò*‰s´°C³×?Ñ?ÓA€Gß‹3ÔBÕ/AÓB�3Œ3ÐBŠ3ÐBˆ3ÔBÕ/AÓBÓBÐBr   ÚcompressionÚcompression_closedÚ
turn_leaseÚcorruptÚreplacedÚdeleted_walÚSQLITE_CORRUPT_VTABi  z\bmessages_fts\w*c                ó$   € V ^8„  d   QhR\         /# r+   r,   )r
   s   "r   r   r   z   s   € ÷ Dñ D´$ñ Dr   c                ó¤  a€ V f   R# \        V RR4      pVe
   V\        8H  # \        V \        4      '       d   T M
\        V 4      P	                  4       o\
        P                  S4      '       g   R# SP                  R4      '       d
   RS9   d   R# \        ;QJ d#    V3R l\         4       F  '       g   K   R# 	  R# ! V3R l\         4       4      # )aà  Corruption SQLite itself attributes to the FTS index layer: the ONE provenance rule
shared by the write-repair gate (``SessionDB._is_fts_write_corruption_error``), the
gateway transcript retry and :func:`classify_persistence_error` (#96038, #97794).

A known result code outranks prose: ``SQLITE_CORRUPT_VTAB`` is FTS-scoped even with
the generic malformed-image text older SQLite builds emit, while bare ``SQLITE_CORRUPT``
/ ``SQLITE_NOTADB`` carry no object scope and any other known code contradicts
FTS-looking prose, so both fail closed. Only without a code (Python < 3.11, RPC-wrapped
strings) does the text decide, and then only an ``fts5:`` corruption report or a
corruption marker that names a ``messages_fts*`` object counts.
NFr%   zfts5:rT   Tc              3   ó,   <"  € T F	  qS9   x € K  	  R # 5ir   rI   ©r   r   r1   s   & €r   r   Ú1is_fts_scoped_corruption_error.<locals>.<genexpr>�   s   øé € ÐCÑ,B &˜Ž~Ó,BùrL   )
r&   rW   r   r   r   Ú_FTS_OBJECT_REÚsearchÚ
startswithr   Ú_DB_CORRUPTION_MARKERSr0   s   & @r   Úis_fts_scoped_corruption_errorra   z   s§   ø€ ð ÒÙÜ�:Ð1°4Ó8€DØÒØÔ*Ñ*Ð*Ü$ Z´×5Ò5‰J¼3¸z»?×QÑQÓS€DÜ× Ñ  ×&Ò&ÙØ‡��w×Ò I°Ô$5Ùß‹3ÔCÕ,BÓC�3Œ3ÐCŠ3ÐCˆ3ÔCÕ,BÓCÓCÐCr   c                   óB   a a€ ] tR t^“t oRtV3R lV 3R lltRtVtV ;t# )ÚCompressionSessionClosedErrorz@A durable write targeted a parent already closed by compression.c                ó    <€ V ^8„  d   QhRS[ /# )r   Ú
session_id©r   )r
   Ú__classdict__s   "€r   r   Ú*CompressionSessionClosedError.__annotate__–   s   ø€ ÷ 
ñ 
¡3ñ 
r   c                ó<   <€ Wn         \        SV `	  R V: R24       R# )zSession zP is closed by compression; adopt its live continuation before appending messagesN)re   ÚsuperÚ__init__)Úselfre   Ú	__class__s   &&€r   rk   Ú&CompressionSessionClosedError.__init__–   s(   ø€ Ø$ŒÜ‰ÑØ�z‘nð %Dð Dö	
r   )re   )	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__rk   Ú__static_attributes__Ú__classdictcell__Ú__classcell__)rm   rg   s   @@r   rc   rc   “   s   ù‡ € ÙJ÷
÷ 
ó 
r   rc   c                   ó   € ] tR t^žtRtRtR# )ÚCompressionSessionBusyErrorz>A non-owner tried to write while compression owns the session.rI   N©ro   rp   rq   rr   rs   rt   rI   r   r   rx   rx   ž   s   † ÝHr   rx   c                   ó   € ] tR t^¢tRtRtR# )Ú!SessionCompressionInProgressErroru  A concurrent writer collided with a *live* compression lock â€” transient
(the compressor publishes in seconds; ``_execute_write`` waits), unlike the
parent class's other case (a compressor whose own lease is gone: permanent,
fail fast). Subclassing keeps every existing handler working.rI   Nry   rI   r   r   r{   r{   ¢   s   † õEr   r{   c                   ó   € ] tR t^©tRtRtR# )ÚPruneRowUnresolvedErrorz©An in-place prune rewrite could not name the exact live row of a changed message. Raised before
any write, so the caller commits through ``archive_and_compact`` instead.rI   Nry   rI   r   r   r}   r}   ©   s   † õQr   r}   c                   ó   € ] tR t^®tRtRtR# )ÚPruneRowStaleErroruË  The named live row no longer holds the body the pruner is pruning: another writer already
rewrote this generation. Unlike plain ambiguity this must NOT fall back to the full writer â€”
that writer archives rows it never compared and would republish the stale held text over the
newer one. Subclassing keeps callers that only know the parent failing closed-enough (they stop
the in-place path); the prune caller tells the two apart and skips the pass entirely.rI   Nry   rI   r   r   r   r   ®   s   † õ]r   r   c                   ó   € ] tR t^¶tRtRtR# )ÚHeldRowRewrittenErroru½  A full-writer commit found one of the held rows it was about to archive already rewritten in
place by another writer. Raised INSIDE the commit's write transaction, after the caller's
precheck but before any row is archived: a write that lands in that window is exactly the lost
race the precheck cannot see (#124102). Callers treat it like ``StaleHeldHistory`` â€” nothing
written, the pass is a no-op and the newer generation keeps the session.rI   Nry   rI   r   r   r�   r�   ¶   s   † õPr   r�   c                   ó   € ] tR t^¾tRtRtR# )ÚSessionTurnLeaseLostErrorzåA transcript write presented a turn-lease holder that no longer owns it.
Fail-fast fencing (no ``_execute_write`` retry): a later writer may already
be persisting a newer turn, and landing this one would interleave a stale reply.rI   Nry   rI   r   r   rƒ   rƒ   ¾   s   † õXr   rƒ   c                   ó   € ] tR t^ÄtRtRtR# )ÚSessionActiveWriteGuardErrorzNRaised when an active turn lease or compression lock rejects session deletion.rI   Nry   rI   r   r   r…   r…   Ä   s   † ÝXr   r…   c                   ó   € ] tR t^ÈtRtRtR# )ÚStateDbReplacedErrorzÃThe state.db path no longer names the file this SessionDB opened
(out-of-band cp/mv/restore). In-place FTS repair and fail-open trigger
dropping cannot fix a generation mismatch; they amplify it.rI   Nry   rI   r   r   r‡   r‡   È   s   † õCr   r‡   c                   ó   € ] tR t^ÎtRtRtR# )ÚDeletedWalGenerationErrorac  A live process holds a deleted state.db-wal / -shm generation. Opening or
writing through this handle would mint a second WAL inode (split-brain ->
intermittent SQLITE_CORRUPT / IOERR). Stop the writers; never unlink the WAL
yourself. Subclasses StateDbReplacedError so every consumer that diverts
transcripts on a replaced store handles this identically.rI   Nry   rI   r   r   r‰   r‰   Î   s   † õAr   r‰   Údb_file_generationzàFATAL: state.db was replaced underneath the gateway; refusing further writes to this file. Divert transcripts to sessions/<id>.jsonl (and the gateway pending_messages spool) and restore or reopen after operator intervention.zNhttps://hermes-agent.nousresearch.com/docs/user-guide/session-storage-recoveryaÉ  FATAL: session storage stopped writing because another Hermes process still holds a deleted state.db-wal or state.db-shm inode (an old copy of the write-ahead log). Nothing is lost: quit every Hermes process on this profile (Desktop app, gateway, dashboard, cron), run `hermes doctor` (it names the processes still holding the log), then start Hermes again. Do not delete the WAL yourself and do not run `hermes doctor --fix` while they are running. Guide: zð Detail: the path names a different (or missing) generation than the one this process holds open; opening or writing through it would mint a second WAL (split-brain). database.journal_mode: delete is operator containment, not a new default.c                   ó   € ] tR t^ñtRtRtR# )ÚStateDbCorruptErrorub  A live SessionDB observed structural (non-FTS, non-replaced) corruption and
is quarantined: sticky for the handle's life â€” writes fail fast, no reopen,
no close-time checkpoint (a handle that kept writing after the first error
checkpointed 15 pages under wrong page numbers and turned a readable file
into "file is not a database"; SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE on 3.12+
also stops SQLite's own). Subclasses sqlite3.DatabaseError so every degrade
path keeps working. Recovery boundary: restart on a repaired/restored file.

Stopping the writes is what prevents that; skipping the explicit checkpoint is the second line of
defence. SQLite still runs its own last-connection checkpoint inside ``close()`` (and deletes the
``-wal`` sidecar) unless ``SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`` is set â€” Python exposes it via
``Connection.setconfig()`` on 3.12+, so quarantine disables the close-time checkpoint there and the WAL
survives on disk for forensics; on 3.11 the internal checkpoint is unavoidable (post-quarantine it can
only carry pre-corruption committed frames, since no further writes are accepted). See #90837.
rI   Nry   rI   r   r   rŒ   rŒ   ñ   s   † õr   rŒ   a¯  FATAL: state.db reported structural corruption (database disk image is malformed outside the FTS shadow tables) on a live handle; refusing further writes, automatic reopen, and the close-time WAL checkpoint on this file. Stop the gateway, then run `hermes sessions recover --source <state.db> --inspect-only` or restore a snapshot. Unwritten transcripts are diverted to sessions/<id>.jsonl (and the gateway pending_messages spool).c                ó$   € V ^8„  d   QhR\         /# r+   rf   )r
   s   "r   r   r   %  s   € ÷ !ñ !¬cñ !r   c                óp  a€ V f   R# \          F  w  r\        W4      '       g   K  Vu # 	  \        V 4      '       d   R# \        V 4      \        9   d   R# \        V RR4      \        P                  8X  d   R# \        V 4      P                  4       o\         FI  w  r2\        ;QJ d    V3R lV 4       F  '       g   K   RM	  R	M! V3R lV 4       4      '       g   KG  Vu # 	  \        V 4      '       gA   \        ;QJ d    V3R
 lR 4       F  '       g   K   RM	  R	M! V3R
 lR 4       4      '       d   R# R# )a�  Coarse cause bucket (PERSISTENCE_ERROR_CAUSES) so the user's guidance
matches: "locked" = busy, retry; "disk" = full/read-only/permissions;
"compression" = a live lease refused the write; "compression_closed" = adopt
the rotated session id; "turn_lease" = fencing, not storage; "corrupt" =
file damage (repair path, not disk space); "fts_index" = SQLite scoped the
corruption to the FTS index (the transcript store is not damaged); "replaced" =
main-file replacement; "deleted_wal" = a retired sidecar generation requiring
capture inspection; "session_row_missing" = the session row was deleted under a
live agent (FK rejection; the flush recreates it).NÚunknownÚ	fts_indexr.   r%   Úsession_row_missingc              3   ó,   <"  € T F	  qS9   x € K  	  R # 5ir   rI   r[   s   & €r   r   Ú-classify_persistence_error.<locals>.<genexpr>B  s   øé € Ð4©G &˜Ž~«GùrL   TFc              3   ó,   <"  € T F	  qS9   x € K  	  R # 5ir   rI   )r   Úmr1   s   & €r   r   r“   D  s   øé € Ð,bÑ@a¸1°$®YÓ@aùrL   Údisk)r–   Úreadonlyz	read-only)Ú_PERSISTENCE_CAUSE_BY_TYPEr   ra   r)   r/   r&   r   ÚSQLITE_CONSTRAINT_FOREIGNKEYr   r   Ú_PERSISTENCE_CAUSE_BY_PHRASEr   rP   )r'   Úexc_typeÚcauseÚmarkersr1   s   &   @r   Úclassify_persistence_errorrž   %  sá   ø€ ð ÒÙ÷
 6‰ˆÜ�j×+Ô+ØŠLñ 6ô
 & j×1Ò1ÙÜ˜JÓ'Ô+=Ô=ÙÜˆzÐ-¨tÓ4¼×8\Ñ8\Ô\Ù$Üˆz‹?× Ñ Ó"€Dß6‰ˆß‹3Ô4©GÓ4�3�3Š3Ô4©GÓ4×4Ô4ØŠLñ 7ô ˜*×%Ò%¯«Ô,bÑ@aÓ,b¯¯ªÔ,bÑ@aÓ,b×)bÒ)bÙÙr   )zmalformed database schema)zno space left on deviceznot enough spacezdatabase or disk is fullz	disk fullz	full diskÚenospc)r.   rQ   rR   rS   rT   r�   rU   rV   r–   r‘   r�   )Ú	malformedzfile is not a databaseznot a databasezdatabase corruption))z
turn leaserS   ))zclosed by compressionrR   ))zbeing compressedzcompression leaserQ   ))zdeleted state.db-walzdeleted state.db-shmrV   ))zwas replaced underneathrU   ))r.   r!   r.   ))zforeign key constraint failedr‘   )0rs   rG   Úrer   rC   r   r    Ú_DISK_IO_ERROR_MARKERr=   ÚSQLITE_BUSYÚSQLITE_LOCKEDr/   r)   r2   r6   r>   rD   rO   rP   ÚPERSISTENCE_ERROR_CAUSESr`   r&   rW   Úcompiler]   ra   ÚRuntimeErrorrc   rx   r{   r}   r   r�   rƒ   r…   r‡   r‰   Ú_STATE_DB_APPLICATION_ID_OFFSETÚ_STATE_DB_GENERATION_KEYÚ_STATE_DB_REPLACED_MSGÚSTORAGE_RECOVERY_DOCS_URLÚ_DELETED_WAL_GENERATION_MSGr   rŒ   Ú_STATE_DB_CORRUPT_MSGr˜   rš   rž   rI   r   r   Ú<module>r®      s  ðñ/ó Û 	Û ð ;Ð ØXÐ3ÐXÐ5WÑXÐ õð )Ð ð
 Ð/Ð1KÈVðÐ ð ×)Ñ)¨7×+@Ñ+@ÐAÐ õ:õ.õ8õõðÐ õCðÐ ðÐ ñ
 ˜gÐ'<¸cÓBÐ ð —’Ð0Ó1€õDô2
 Lô 
ôI ,ô IôEÐ(Cô EôQ˜lô Qô
]Ð0ô ]ôP˜Lô PôX ô XôY <ô YôC˜<ô CôAÐ 4ô Að #%Ð Ø/Ð ðYð ð
 mÐ ðð
 (Ð(ð )PðPð ô˜'×/Ñ/ô ð&Dð ð  Ð-Ø"Ð$8Ð9Ø  -Ð0ð  Ð.Ø˜:Ð&Ø˜)Ð$ð	Ð ð $Ø6Ø>àEØ.Ø˜YÐ'Ø"à?ð Ð ÷!r   