+
    jHàj5æ  ã                  ól  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIt^ RIHt ^ RIHt ^ RIHt ^ RIHt ^ RIHt ^ RIHt ]! 4       tR	]R
&   ]P6                  ! 4       tRtRt^
tRt Rt!R R lt"R R lt#R R lt$R R lt%]PL                  R R l4       t']PL                  R R l4       t(Rt)/ t*R]R&   ]PV                  ! 4       t,R R lt-R  R! lt.R" R# lt/ ! R$ R%]04      t1R& R' lt2RnR( R) llt3R* R+ lt4]Pj                  ! R,4      ]Pj                  ! R-4      3t6R. R/ lt7R0 R1 lt8R2 R3 lt9R4 R5 lt:R6 R7 lt;R8 R9 lt<R: R; lt=R< R= lt>] ! R> R?4      4       t?RnR@R/RA RB lllt@RC RD ltARE RF ltBRG RH ltCRnR@R/RI RJ llltD]PL                  RnR@R/RK RL lll4       tERnR@R/RM RN llltFRotGRptHRqtIRrtJRstKRttLRO RP ltMRQ RR ltNRS RT ltORU RV ltPRWRuRXRvRYRwRZRx/tQR[ R\ ltRR] R^ ltSR_ R` ltT^tURatVRbtWRc Rd ltXRe Rf ltYRg Rh ltZ]PL                  RiRj/Rk Rl ll4       t[^ RmI\H]t^ R# )ya›  SQLite connection lifecycle for the Kanban DB: open/configure, cross-process init and dispatch-tick locks, WAL checkpoints, corruption detection + quarantine + repair, additive migrations and the busy-retrying ``write_txn`` boundary.

Split out of ``hermes_cli.kanban_db``; origin-resident helpers are reached
late-bound via ``_kb`` (import-cycle breaking) so monkeypatching
``kanban_db.<name>`` keeps working.
)ÚannotationsN)Ú	dataclass)Úfield)Úadd_column_if_missing)ÚPath)ÚAny)ÚOptionalúset[str]Ú_INITIALIZED_PATHSs   SQLite format 3 iÀÔ g      $@gš™™™™™©?c               ó   € V ^8„  d   QhRR/# )é   ÚreturnÚint© )Úformats   "ÚA/home/ubuntu/.hermes/hermes-agent/hermes_cli/kanban_db_connect.pyÚ__annotate__r   1   s   € ÷ ]ñ ] #ñ ]ó    c                 ó<   € \         P                  ! R\        ^R7      # )zóReturn the SQLite busy timeout for Kanban connections. Kanban is the
shared cross-profile dispatch bus, so worker stampedes are expected; a
long timeout lets WAL serialize writers instead of surfacing transient
``database is locked`` failures.ÚHERMES_KANBAN_BUSY_TIMEOUT_MS)Úminimum)Ú_kbÚ_env_intÚDEFAULT_BUSY_TIMEOUT_MSr   r   r   Ú_resolve_busy_timeout_msr   1   s   € ô
 �<Š<Ð7Ô9PÐZ[Ô\Ð\r   c               ó    € V ^8„  d   QhRRRR/# )r   Úpathr   r   úsqlite3.Connectionr   )r   s   "r   r   r   9   s   € ÷ ñ ˜$ð Ð#5ñ r   c                óN  € ^ RI Hp \        4       pV! V \        P                  RVR,          R7      p VP                  RV 24       V#   \         dQ    \        P                  ! \        4      ;_uu_ 4        TP                  4        RRR4       h   + '       g   i     h ; ii ; i)zãOpen a Kanban SQLite connection via ``connect_tracked``: while registered,
byte-level probes of the file are refused because an ``open()``/``close()``
would cancel this process's POSIX advisory locks (see ``sqlite_safe_read``).)Úconnect_trackedNg     @�@)Ú
connect_fnÚisolation_levelÚtimeoutzPRAGMA busy_timeout=)Úhermes_cli.sqlite_safe_readr   r   Úsqlite3ÚconnectÚexecuteÚBaseExceptionÚ
contextlibÚsuppressÚ	ExceptionÚclose)r   r   Úbusy_timeout_msÚconns   &   r   Ú_sqlite_connectr.   9   s’   € õ <ä.Ó0€OÙØÜ—?‘?ØØ &Õ(ô	€Dð
ð 	�‰Ð+¨OÐ+<Ð=Ô>ð €Køô ô ô × Ò ¤×+Õ+Ø�J‰JŒL÷ ,à÷ ,Ö+àúðús#   ³A	 Á	-B$Á6BÂ	B$ÂB Â	B$c               ó   € V ^8„  d   QhRR/# )r   r   Úboolr   )r   s   "r   r   r   T   s   € ÷ ñ ˜Dñ r   c                óZ  € \         P                  '       dC   ^ RIpV P                  ^ 4       VP	                  V P                  4       VP                  ^4       R# ^ RIp VP                  V P                  4       VP                  VP                  ,          4       R#   \         d     R# i ; i)z™One non-blocking exclusive lock attempt on ``handle``; False when held elsewhere.
Windows: 1-byte ``msvcrt.locking`` range at offset 0; POSIX: ``flock``.NFT)r   Ú_IS_WINDOWSÚmsvcrtÚseekÚlockingÚfilenoÚLK_NBLCKÚfcntlÚflockÚLOCK_EXÚLOCK_NBÚBlockingIOError©Úhandler3   r8   s   &  r   Ú_try_lock_nbr?   T   s~   € ô ‡‡€Ûà�‰�AŒØ�‰�v—}‘}“¨¯©¸Ô;ñ ó 	ð	Ø�K‰K˜Ÿ™›¨¯©¸¿¹Õ)FÔGñ øô ô 	Úð	ús   Á;B ÂB*Â)B*c               ó   € V ^8„  d   QhRR/# )r   r   ÚNoner   )r   s   "r   r   r   f   s   € ÷ 
4ñ 
4�tñ 
4r   c                ó  € \         P                  '       dC   ^ RIpV P                  ^ 4       VP	                  V P                  4       VP                  ^4       R# ^ RIpVP                  V P                  4       VP                  4       R# )zGRelease a lock taken by :func:`_try_lock_nb` (same byte range / flock).N)
r   r2   r3   r4   r5   r6   ÚLK_UNLCKr8   r9   ÚLOCK_UNr=   s   &  r   Ú_unlockrE   f   sN   € ä
‡‡€Ûà�‰�AŒØ�‰�v—}‘}“¨¯©¸Ö;ãà�‰�F—M‘M“O U§]¡]Ö3r   c               ó   € V ^8„  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   t   s   € ÷ )ñ ) 4ñ )r   c              #  ó  "  € V P                   P                  RRR7       V P                  V P                  R,           4      pVP	                  R4      pRp \
        P                  ! 4       \        ,           p  \        V4      pV'       g   \
        P                  ! 4       V8¼  d   M\
        P                  ! \        4       KL  V'       g&   \        P                  P                  RV\        4       Rx €  V'       d   \        V4       VP!                  4        R#   \         d    Rp L¥i ; i  TP!                  4        i ; i   T'       d   \        T4       TP!                  4        i   TP!                  4        i ; i; i5i)uˆ  Serialize first-connect WAL/schema/integrity setup across processes.

``_INIT_LOCK`` only covers one process's threads; a dispatcher burst has
many worker processes hit a fresh/legacy board with empty
``_INITIALIZED_PATHS`` caches. Post-init usage stays concurrent under WAL.

**Bounded** acquire: a blocking ``flock`` let one stalled/stale holder hang
every ``connect()`` (the gateway dispatcher's next tick included) with no
traceback. After the deadline we WARN and proceed WITHOUT the lock â€” safe
because ``_INIT_LOCK`` still serializes same-process threads and init is
idempotent: two racing first-inits mean redundant work, not corruption.
T©ÚparentsÚexist_okz
.init.lockúa+bFuï   kanban init lock for %s not acquired within %.0fs â€” proceeding without the cross-process lock (in-process lock + idempotent init are the correctness backstop). A stuck holder is no longer able to block this connect indefinitely (#36644).N)ÚparentÚmkdirÚ	with_nameÚnameÚopenÚtimeÚ	monotonicÚ_INIT_LOCK_TIMEOUT_SECONDSr?   ÚOSErrorÚsleepÚ_INIT_LOCK_POLL_SECONDSr   Ú_logÚwarningrE   r+   )r   Ú	lock_pathr>   ÚacquiredÚdeadlines   &    r   Ú_cross_process_init_lockr\   s   s  é € ð 	‡K�K×Ñ˜d¨TÐÔ2Ø—‘˜tŸy™y¨<Õ7Ó8€IØ�^‰^˜EÓ"€FØ€HðÜ—>’>Ó#Ô&@Õ@ˆØð!Ü'¨Ó/�÷ œ4Ÿ>š>Ó+¨xÔ7ØÜ�JŠJÔ.Ö/ßÜ�H‰H×ÑðDð Ô5ôó 	ð	ßÜ˜”à�L‰LŽNøô' ô !Ø ’ð!ûð& �L‰L�Nûð		ßÜ˜”à�L‰L�NøˆF�L‰L�Nÿsx   ‚AE?Á!E Á8D ÂE Â>E Ã
)E Ã4D, ÄE?ÄD)Ä&E Ä(D)Ä)E Ä,D>Ä>E?ÅE<ÅE'ÅE<Å'E9Å9E<Å<E?c               ó   € V ^8„  d   QhRR/# )r   Údb_pathr   r   )r   s   "r   r   r   ¡   s   € ÷ ,ñ , ñ ,r   c              #  ó˜  "  € V P                  V P                  R,           4      pRpRp VP                  P                  RRR7       VP	                  R4      p \        V4      p Tx € Te'    T'       d   \        T4       TP                  4        R# R#   \        \        3 d    Rp LFi ; i  \         d    RpRp L[i ; i  \        \        3 d     LUi ; i  TP                  4        i ; i  TeS    T'       d   \        T4       M  \        \        3 d     Mi ; iTP                  4        i   TP                  4        i ; ii ; i5i)uo  Non-blocking single-writer guard around one dispatcher tick; yields
``True`` if this process holds the board's ``.dispatch.lock``, else
``False`` (caller skips the tick).

Two dispatchers (e.g. an orphan gateway escaping its service cgroup) both
pass ``busy_timeout`` and race on WAL frames â€” the root cause of
multi-writer corruption; this is defense-in-depth behind
``_guard_supervised_gateway_conflict``. Non-blocking on purpose: the
gateway's async watcher must never stall; the loser retries next interval.
Without ``fcntl``/``msvcrt`` it degrades to a no-op (yields ``True``).

Motivation (issue #35240): a ``hermes gateway run --replace`` / ``gateway restart`` invoked from a shell
on a systemd/launchd host can leave an orphan gateway whose dispatcher escapes the service cgroup,
survives ``systemctl restart``, and becomes a *second* long-lived writer on the same ``kanban.db``. The
startup guard (``_guard_supervised_gateway_conflict``) blocks the common way an orphan is born, but this
lock is the defense-in-depth that prevents two dispatchers from ever writing concurrently *regardless of
how the second one got there*.
z.dispatch.lockNFTrH   rK   )
rN   rO   rL   rM   rP   r?   rT   ÚAttributeErrorrE   r+   )r^   rY   r>   rZ   s   &   r   Ú_dispatch_tick_lockra       s6  é € ð( ×!Ñ! '§,¡,Ð1AÕ"AÓB€IØ€FØ€HðØ×Ñ×Ñ t°dÐÔ;Ø—‘ Ó&ˆð	Ü# FÓ+ˆHð
ØŠàÒðßÜ˜F”Oð —‘–ñ øô œÐ(ô 	ØŠHð	ûäô ð ˆØŠð	ûô œ^Ð,ô Ùðûð —‘•ûð ÒðßÜ˜F”OùÜœ^Ð,ô Ùðúð —‘•ø�—‘•úð üsÄ   ‚'E
ª.B. ÁB Á%C/ Á)E
Á.C ÂE
ÂB+Â(B. Â)E
Â*B+Â+B. Â.C Â=E
Â?C Ã E
ÃCÃC ÃCÃC ÃC,Ã,E
Ã/EÃ5D	ÄD1Ä	DÄD1ÄDÄD1Ä EÄ1EÅEÅE
g     Àr@zdict[str, float]Ú_LAST_WAL_CHECKPOINTc               ó$   € V ^8„  d   QhRRRRRR/# )r   r-   r   r^   r   r   rA   r   )r   s   "r   r   r   à   s'   € ÷ Lñ LÐ 2ð L¸Tð LÀdñ Lr   c                ó�  €  \        VP                  4       4      p\        P                  ! 4       p\
        ;_uu_ 4        \        P                  V4      pVe   W4,
          \        8  d    RRR4       R# V\        V&   RRR4        V P                  R4      P                  4       p\        P                  P                  RY%e   \        V4      MR4       R#   \         d    \        T4      p LÓi ; i  + '       g   i     L}; i  \        P                    d,   p\        P                  P                  RY&4        Rp?R# Rp?ii ; i)z«``PRAGMA wal_checkpoint(PASSIVE)`` at most once per interval per board,
from the dispatcher tick under the dispatch lock. Never raises: pure
hygiene, must not fail a tick.NzPRAGMA wal_checkpoint(PASSIVE)zSkanban WAL checkpoint (PASSIVE) on %s -> %s (busy, wal_frames, checkpointed_frames)z'kanban WAL checkpoint on %s skipped: %s)ÚstrÚresolverT   rQ   rR   Ú_WAL_CHECKPOINT_LOCKrb   ÚgetÚ _WAL_CHECKPOINT_INTERVAL_SECONDSr&   Úfetchoner   rW   ÚdebugÚtupler$   ÚError)r-   r^   ÚkeyÚnowÚlastÚrowÚexcs   &&     r   Ú_maybe_checkpoint_walrs   à   sù   € ðÜ�'—/‘/Ó#Ó$ˆô �.Š.Ó
€Cß	Ö	Ü#×'Ñ'¨Ó,ˆØÒ ¥Ô/OÔ OØ÷ 
Ñ	ð %(Ô˜SÑ!÷ 
ðLØ�l‰lÐ;Ó<×EÑEÓGˆÜ�‰�‰ð6àš”�s”°Dö	
øô ô Ü�'‹lŠðú÷ 
×	ûô �=‰=ô LÜ�‰�‰Ð@À#×KÒKûðLús;   ‚C ¾,C2Á4	C2ÂAD ÃC/Ã.C/Ã2D	ÄEÄ E Å Ec               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚdataÚbytesÚoffsetr   r   r0   r   )r   s   "r   r   r   û   s!   € ÷ ñ  Eð °3ð ¸4ñ r   c                óD  € \        V 4      V^,           8  d   R# W,          W^,           ,          W^,           ,          rCp\        P                  W^,           V^,            R4      pVR9   ;'       d1    V^8H  ;'       d$    VR9   ;'       d    ^ Tu;8  ;'       d    R8*  # u # )z9Return True for a TLS record header at ``data[offset:]``.FÚbigi H  >   é   é   é   é   >   é    é   r   é   é   )Úlenr   Ú
from_bytes)ru   rw   Úcontent_typeÚmajorÚminorÚlengths   &&    r   Ú_looks_like_tls_record_atrˆ   û   s¥   € ä
ˆ4ƒy�6˜A•:ÔÙØ!%¥¨t¸QµJÕ/?ÀÈqÅjÕAQ˜€LÜ�^‰^˜D¨!¥¨F°Q­JÐ7¸Ó?€FàÐ0Ñ0÷ 	 ð 	 Ø�T‰M÷	 ð 	 àÐ3Ñ3÷	 ð 	 ð �×Ô˜%Ñð	ñ  ð	r   c               ó    € V ^8„  d   QhRRRR/# ©r   r   r   r   rA   r   )r   s   "r   r   r   	  s   € ÷ ñ  $ð ¨4ñ r   c           	     óº  €  V P                  4       P                  ^ 8X  d   R#  ^ RIHp T! T ^@R7      pTe   TP                  \        4      '       d   R# RpTP                  R4      '       d   \        T^4      '       d   RpM\        T^ 4      '       d   Rp\        P                  ! RT  T R	TR
,          P                  R4       24      h  \         d     R# i ; i)aK  Fail early with an actionable error for non-SQLite Kanban DB files.
``sqlite3.connect()`` creates missing and zero-byte files, so those pass;
non-empty files must carry the SQLite header, so a corrupt page 0 isn't
collapsed into a generic PRAGMA error and the gateway's corrupt-board
handling can identify the board by fingerprint.N)Úread_header_bytes_preopen)r‡   Ú s   SQLitz. (TLS record header detected at byte offset 5)z. (TLS record header detected at byte offset 0)z2file is not a database: invalid SQLite header for z; first_32=:Né    NÚ )ÚstatÚst_sizerT   r#   rŒ   Ú
startswithÚ_SQLITE_HEADERrˆ   r$   ÚDatabaseErrorÚhex)r   rŒ   ÚheadÚ	signatures   &   r   Ú_validate_sqlite_headerr˜   	  sÏ   € ðØ�9‰9‹;×Ñ !Ô#Ùð $õ Fá$ T°"Ô5€DØ‚|�t—‘¤~×6Ò6ÙØ€IØ‡��x× Ò Ô%>¸tÀQ×%GÒ%GØD‰	Ü	" 4¨×	+Ò	+ØDˆ	Ü
×
Ò
Ø<Øˆ&��˜; t¨C¥y§}¡}°SÓ'9Ð&:ð	<óð øô ô Úðús   ‚C ÃCÃCc                  ó2   a € ] tR tRtRtR V 3R lltRtV ;t# )ÚKanbanDbCorruptErrori'  uÕ   Raised when an existing kanban DB file fails integrity checks â€” a
fail-closed guard against silently recreating a corrupt board (which would
destroy the user's tasks). Carries the path and the backup made first.c               ó$   € V ^8„  d   QhRRRRRR/# )r   r^   r   Úbackup_pathúOptional[Path]Úreasonre   r   )r   s   "r   r   Ú!KanbanDbCorruptError.__annotate__,  s!   € ÷ 
ñ 
 ð 
°>ð 
È3ñ 
r   c           
     	óp   <€ Wn         W n        W0n        \        SV `  R V RV R\        V4       R24       R# )z&Refusing to open corrupt kanban DB at z: z . Original preserved; backup at Ú.N)r^   rœ   rž   ÚsuperÚ__init__Ú_backup_label)Úselfr^   rœ   rž   Ú	__class__s   &&&&€r   r£   ÚKanbanDbCorruptError.__init__,  sF   ø€ ØŒØ&ÔØŒÜ‰ÑØ4°W°I¸RÀ¸xð H-Ü-:¸;Ó-GÐ,HÈðKö	
r   )rœ   r^   rž   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r£   Ú__static_attributes__Ú__classcell__)r¦   s   @r   rš   rš   '  s   ø† ñN÷
ö 
r   rš   c               ó    € V ^8„  d   QhRRRR/# )r   rœ   r�   r   re   r   )r   s   "r   r   r   6  s   € ÷ Nñ N˜~ð N°#ñ Nr   c                ó$   € V e   \        V 4      # R# )Nz<backup failed>)re   )rœ   s   &r   r¤   r¤   6  s   € Ø*Ò6Œ3ˆ{ÓÐMÐ<MÐMr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rL   r   Ú	base_namere   Úkeepr�   r   rA   r   )r   s   "r   r   r   :  s)   € ÷ /ñ / 4ð /°Cð /¸~ð /ÐY]ñ /r   c           	     óš  €  V P                  V R24       Uu. uF%  pVP                  4       '       g   K  W28w  g   K#  VNK'  	  pp\        \        Te   ^M^ ,
          ^ 4      p\        T4      T8:  d   R# R R lpTP                  TRR7       YER  F‹  pYwP                  TP                  R,           4      TP                  TP                  R,           4      3 F@  p\        P                  ! \        4      ;_uu_ 4        TP                  RR	7       RRR4       KB  	  K�  	  R# u upi   \         d     R# i ; i  + '       g   i     Kr  ; i)
u„  Keep only the ``_CORRUPT_BACKUP_RETENTION`` newest (by mtime)
``<db>.corrupt.<hash>.bak`` files plus their ``-wal``/``-shm`` copies.
``keep`` (the just-created backup) is never pruned regardless of mtime â€”
``shutil.copy2`` preserves the source timestamp, which may be older than
existing backups. Best-effort: prune failures never mask the corruption
error the caller is about to raise.z.corrupt.*.bakNc               ó    € V ^8„  d   QhRRRR/# )r   Úitemr   r   Úfloatr   )r   s   "r   r   Ú,_prune_corrupt_backups.<locals>.__annotate__M  s   € ÷ ñ �Tð ˜eñ r   c                ó\   €  V P                  4       P                  #   \         d     R # i ; i)g        )r�   Úst_mtimerT   )r¶   s   &r   Ú_mtimeÚ&_prune_corrupt_backups.<locals>._mtimeM  s+   € ð	Ø—9‘9“;×'Ñ'Ð'øÜô 	Úð	ús   ‚ œ+ª+T)rn   Úreverseú-walú-shm)Ú
missing_ok)ÚglobÚis_filerT   ÚmaxÚ_CORRUPT_BACKUP_RETENTIONr‚   ÚsortrN   rO   r(   r)   Úunlink)	rL   r²   r³   Ú	candidateÚbackupsÚbudgetr»   ÚstaleÚvictims	   &&&      r   Ú_prune_corrupt_backupsrÌ   :  s&  € ðð $Ÿ[™[¨I¨;°nÐ)EÔFó
áF�	Ø× Ñ ×"ô à'0Ñ'8÷ ˆIÙFð 	ð 
ô Ô*°4Ò3C©aÈÕKÈQÓO€FÜ
ˆ7ƒ|�vÔÙõð ‡L�L�V T€LÔ*Ø˜Ó!ˆØŸo™o¨e¯j©j¸6Õ.AÓBÀEÇOÁOÐTY×T^ÑT^ÐagÕTgÓDhÓiˆFÜ×$Ò$¤W×-Õ-Ø—‘¨�Ô.÷ .Ñ-ó jó "ùò%
øô
 ô Úðú÷ .×-Ð-ús8   ‚D' ˜D"µD"½D"ÁD' Ã=D9	Ä"D' Ä'D6Ä5D6Ä9E
c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   r�   r   )r   s   "r   r   r   Z  s   € ÷ 8ñ 8˜Tð 8 nñ 8r   c                ó‚  a€ V P                  4       pVP                  pVP                  p^ RIHp V! V4      '       d#   \
        P                  P                  RV4       R# \        P                  ! 4       p VP                  R4      ;_uu_ 4       o\        V3R lR4       F  pVP                  V4       K  	  RRR4       Y# RTP                  4       R,           R	2,          pTP                  T8w  d   R# TP                  4       '       g%    \         P"                  ! Y4       \%        Y#TR
7       R F¼  pY#T,           ,          p	Y'P                  T,           ,          p
T	P                  T8w  g>   T	P                  4       '       d(   T
P                  T8w  g   T
P                  4       '       d   K{  \&        P(                  ! \        4      ;_uu_ 4        \         P"                  ! Yš4       RRR4       K¾  	  T#   + '       g   i     ELE; i  \         d     R# i ; i  \         d     R# i ; i  + '       g   i     EK  ; i)a«  Copy a corrupt DB (and WAL/SHM sidecars) to a content-addressed backup.
The name is deterministic in the main DB's sha256, so repeated quarantines
of the same bytes reuse one backup while changed bytes get a separate one.
Returns the main backup path, or ``None`` if the copy failed (the caller
still raises loudly). Writes are confined to the DB's parent directory:
the basename derives only from ``path.name`` + content hash.)Úhas_live_connectionz±refusing to quarantine %s: a connection to it is still open in this process, and fingerprinting the file would cancel that connection's POSIX locks. Close all connections first.NÚrbc                 ó&   <€ S P                  R4      # )i   i   )Úread)r>   s   €r   Ú<lambda>Ú$_backup_corrupt_db.<locals>.<lambda>x  s   ø€  f§k¡k°+Ô&>r   r   z	.corrupt.:Né   Nz.bak)r³   )r¾   r¿   )rf   rL   rO   r#   rÏ   r   rW   ÚerrorÚhashlibÚsha256rP   ÚiterÚupdaterT   Ú	hexdigestÚexistsÚshutilÚcopy2rÌ   r(   r)   )r   ÚresolvedrL   r²   rÏ   ÚdigestÚchunkrÇ   ÚsuffixÚsidecarÚsidecar_backupr>   s   &          @r   Ú_backup_corrupt_dbrå   Z  sÆ  ø€ ð �|‰|‹~€HØ�_‰_€FØ—‘€Iõ @á˜8×$Ò$Ü�‰�‰ðEð ô		
ñ Ü�^Š^Ó€FðØ�]‰]˜4× Ô  FÜÔ>ÀÖD�Ø—‘˜eÖ$ñ E÷ !ð
 ˜; i°×0@Ñ0@Ó0BÀ3Õ0GÐ/HÈÐMÕM€Ià×Ñ˜6Ô!ÙØ×Ñ×Òð	Ü�LŠL˜Ô-ô
 	˜v°yÕAÛ"ˆØ¨Õ.Õ/ˆØ§>¡>°FÕ#:Õ;ˆà�N‰N˜fÔ$¨G¯N©N×,<Ò,<Ø×$Ñ$¨Ô.°.×2GÑ2G×2IÒ2IáÜ× Ò ¤×)Õ)Ü�LŠL˜Ô1÷ *Ñ)ñ #ð Ð÷7 !× Ð ûô ô Úðûô ô 	Úð	ú÷ *×)Ñ)úsN   Á6H Â)G4Â8H ÄH ÇH,Ç4H	Ç?H ÈH ÈHÈHÈH)È(H)È,H>z+^wrong # of entries in index (?P<index>.+)$z*^row \d+ missing from index (?P<index>.+)$c               ó    € V ^8„  d   QhRRRR/# )r   Úmessagesú	list[str]r   r0   r   )r   s   "r   r   r   Ÿ  s   € ÷ Fñ F Yð F°4ñ Fr   c                ó~   € \        V 4      ^8H  ;'       d)    V ^ ,          P                  4       P                  4       R8H  # )zDTrue iff ``PRAGMA integrity_check`` output is the single ``ok`` row.Úok)r‚   ÚstripÚlower)rç   s   &r   Ú_integrity_messages_okrí   Ÿ  s4   € äˆx‹=˜AÑ×EÐE (¨1¥+×"3Ñ"3Ó"5×";Ñ";Ó"=ÀÑ"EÐEr   c               ó    € V ^8„  d   QhRRRR/# )r   r-   r   r   rè   r   )r   s   "r   r   r   ¤  s   € ÷ Sñ SÐ1ð S°iñ Sr   c                ó²   € V P                  R4      P                  4       pV Uu. uF'  q"f   K  V^ ,          f   K  \        V^ ,          4      NK)  	  up# u upi )z>Return all ``PRAGMA integrity_check`` message rows as strings.zPRAGMA integrity_check)r&   Úfetchallre   )r-   Úrowsrq   s   &  r   Ú_run_integrity_checkrò   ¤  sD   € à�<‰<Ð0Ó1×:Ñ:Ó<€DÙ#'ÓR¡4˜CŒK¸sÀ1½vŒKŒC��A•ŽK¡4ÑRÐRùÒRs   ¤A®A»Ac               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   rè   r   )r   s   "r   r   r   ª  s   € ÷ ñ ˜4ð  Iñ r   c                óz   € \        V 4      p \        V4      VP                  4        #   TP                  4        i ; i)uÜ   Open ``path`` read/write (so SQLite can recover/checkpoint a healthy WAL
/ hot-journal DB before we judge it) and return ``integrity_check``
messages. ``OperationalError`` (locked/busy) propagates raw â€” not corruption.)r.   rò   r+   )r   Úprobes   & r   Ú_probe_integrityrö   ª  s.   € ô ˜DÓ!€EðÜ# EÓ*à�‰�øˆ�‰�ús   �
( ¨:c               ó    € V ^8„  d   QhRRRR/# )r   rç   rè   r   zOptional[list[str]]r   )r   s   "r   r   r   µ  s   € ÷ ñ  ið Ð4Gñ r   c                óR  € . pV  F”  pT;'       g    RP                  4       pV'       g   K'  \         F  pVP                  V4      pV'       g   K   M	   R# VP                  R4      P                  4       pV'       g   K{  Wa9  g   Kƒ  VP	                  V4       K–  	  T;'       g    R# )zÉDistinct index names iff EVERY message is index-repairable, else ``None``
(caller fails closed; also ``None`` for no messages). First-appearance
order is preserved so the REINDEX pass is deterministic.r�   NÚindex)rë   Ú _REPAIRABLE_INDEX_ERROR_PATTERNSÚmatchÚgroupÚappend)rç   ÚnamesÚrawÚmessageÚpatternrû   rO   s   &      r   Ú_repairable_index_namesr  µ  s�   € ð €EÛˆØ—9�9˜"×#Ñ#Ó%ˆßÙß7ˆGØ—M‘M 'Ó*ˆEß‰uÙñ 8ò
 Ø�{‰{˜7Ó#×)Ñ)Ó+ˆß‰4�DÖ%Ø�L‰L˜Öñ ð �=ˆ=�DÐr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   Úindex_namesrè   r   ztuple[bool, list[str]]r   )r   s   "r   r   r   Ê  s"   € ÷ 6ñ 6¨ð 6¸9ð 6ÐI_ñ 6r   c                ó  €  \        V 4      p  T F*  pTP                  RR4      pTP	                  RT R24       K,  	  \        T4      p TP                  4        \        T4      T3#   \        P                   d   pRRT 2.3u Rp?# Rp?ii ; i  \        P                   d    TP	                  R4        L{i ; i  \        P                   d#   pRRT 2.3u Rp?TP                  4        # Rp?ii ; i  TP                  4        i ; i)	a  REINDEX the named indexes (per-index first; bare ``REINDEX`` fallback if
a parsed name is an internal/auto index), then re-run integrity_check.
Returns ``(clean, post_repair_messages)``; never raises. Callers must hold
the board's cross-process init flock so nothing connects mid-repair.Fzcould not reopen for REINDEX: NÚ"z""z	REINDEX "ÚREINDEXzREINDEX failed: )r.   r$   rm   Úreplacer&   rò   r+   rí   )r   r  r-   rr   rO   Úescapedrç   s   &&     r   Ú_attempt_index_reindex_repairr
  Ê  sý   € ð
?Ü˜tÓ$ˆðð	$Û#�ØŸ,™, s¨DÓ1�Ø—‘˜y¨¨	°Ð3Ö4ñ $ô (¨Ó-‰ð 	�
‰
ŒÜ! (Ó+¨XÐ5Ð5øô �=‰=ô ?ØÐ7¸°uÐ=Ð>Ð>Õ>ûð?ûô �}‰}ô 	$à�L‰L˜Ö#ð	$ûô �=‰=ô 1ØÐ)¨#¨Ð/Ð0Ð0Ô0à�
‰
�ûð1ûð 	�
‰
�úsi   ‚A( �0B ¿B> Á(BÁ=B
ÂBÂ
BÂ&B;Â8B> Â:B;Â;B> Â>C5ÃC0ÃC5ÃC8 Ã0C5Ã5C8 Ã8D
c               ó    € V ^8„  d   QhRRRR/# )r   rß   r   r   r0   r   )r   s   "r   r   r   ã  s   € ÷ ñ  ð ¨ñ r   c                óœ   €  V P                  4       '       * ;'       g    V P                  4       P                  ^ 8H  #   \         d     R# i ; i)uJ   True for a missing / zero-byte / unstat-able DB file â€” nothing to probe.T)rÜ   r�   r‘   rT   )rß   s   &r   Ú_missing_or_emptyr  ã  sB   € ðØ—?‘?Ó$Ô$×DÐD¨¯©«×(?Ñ(?À1Ñ(DÐDøÜô Úðús   ‚< ž< ¼AÁ
Ac               ó    € V ^8„  d   QhRRRR/# )r   rß   r   r   z)tuple[Optional[list[str]], Optional[str]]r   )r   s   "r   r   r   ë  s   € ÷ ]ñ ] Dð ]Ð-Vñ ]r   c                óò   €  \        V 4      p\	        T4      '       d   TR3# TRT'       d
   T^ ,          MR: 23#   \        P                   d    h \        P                   d   pRRT 23u Rp?# Rp?ii ; i)a  ``(messages, reason)`` from an integrity probe; ``reason`` is ``None``
when healthy and ``messages`` is ``None`` when sqlite refused to open the
file at all. ``OperationalError`` (lock/busy) is NOT corruption and
propagates raw so a locked healthy DB is never quarantined.Nzsqlite refused to open file: zintegrity_check returned ú<no row>)rö   r$   ÚOperationalErrorr”   rí   )rß   rç   rr   s   &  r   Ú_probe_for_corruptionr  ë  s€   € ð
;Ü# HÓ-ˆô
 ˜h×'Ò'Ø˜ˆ~ÐØÐ0Ç°¸!¶ÈjÑ0[Ð\Ð\Ð\øô ×#Ñ#ô ØÜ× Ñ ô ;ØÐ4°S°EÐ:Ð:Õ:ûð;ús!   ‚; »(A6Á$A6Á%A1Á+A6Á1A6c               ó    € V ^8„  d   QhRRRR/# rŠ   r   )r   s   "r   r   r   û  s   € ÷ ,9ñ ,9¨ð ,9°ñ ,9r   c                ól  €  V P                  4       p\        T4      '       g   \        T4      \        9   d   R# \        T4      w  r#Tf   R# \        T4      p\        T;'       g    . 4      pT'       d¥   \        P                  P                  RTRP                  T4      \        T4      4       \        Y4      w  rgT'       d=   \        P                  P                  RTRP                  T4      \        T4      4       R# T RT'       d
   T^ ,          MR: 2p\        YT4      h  \         d     R# i ; i)a¼  Run ``PRAGMA integrity_check`` on an existing non-empty DB file.

Narrow auto-repair when the failure is ONLY index-scoped (table b-trees
intact): content-addressed corrupt backup FIRST, REINDEX under the
caller-held init flock, re-check, proceed only if clean. Anything else
(page corruption, ``malformed``, unclean re-check) fails closed: back up
the file + sidecars and raise :class:`KanbanDbCorruptError` so callers
never recreate the schema on top of a damaged DB. ``OperationalError``
(lock/busy) is NOT corruption and propagates raw (no spurious backup).
No-op for missing / zero-byte files and paths already proven healthy this
process. All writes are confined to the resolved path's parent.
Nu|   kanban DB %s failed integrity_check with index-only errors (%s); pre-repair backup at %s â€” attempting REINDEX auto-repair.ú, zckanban DB %s auto-repaired via REINDEX (%s); integrity_check now clean. Pre-repair copy kept at %s.zC; REINDEX auto-repair attempted but integrity_check still returned r  )rf   rT   r  re   r
   r  rå   r  r   rW   rX   Újoinr¤   r
  rš   )r   rß   rç   rž   Úbackupr  ÚrepairedÚposts   &       r   Ú_guard_existing_db_is_healthyr  û  s  € ðØ—<‘<“>ˆô ˜×"Ò"¤c¨(£mÔ7IÔ&IÙÜ,¨XÓ6Ñ€HØ‚~Ùô   Ó)€FÜ)¨(¯.¨.°bÓ9€KßÜ�‰×ÑðPà�d—i‘i Ó,¬m¸FÓ.Cô	
ô
 7°xÓM‰ˆßÜ�H‰H×ÑðIà˜$Ÿ)™) KÓ0´-ÀÓ2Gôñ
 àˆhð ß)-˜d 1žg°:Ñ@ðBð 	ô ˜x°Ó
8Ð8øô; ô Úðús   ‚D$ Ä$D3Ä2D3c                  ó’   € ] tR tRt$ RtR]R&   R]R&   ]! ]R7      tR]R	&   ]! ]R7      t	R]R
&   Rt
R]R&   ]! ]R7      tR]R&   RtR# )ÚRepairResulti*  a  Outcome of :func:`repair_db`. ``status``: ``"ok"`` (already clean),
``"repaired"`` (index-only errors, REINDEX applied, re-check clean;
``backup_path`` is the pre-repair copy), ``"corrupt"`` (non-index error
class, or re-check still dirty), ``"missing"`` (no / zero-byte file).re   Ústatusr   r^   )Údefault_factoryrè   rç   Úpost_repair_messagesNr�   rœ   Ú	reindexedr   )r¨   r©   rª   r«   r¬   Ú__annotations__r   Úlistrç   r  rœ   r   r­   r   r   r   r  r  *  sL   ‡ ñMð
 ƒKØƒMÙ°Ô5€HˆiÓ5Ù&+¸DÔ&AÐ˜)ÓAØ"&€K�Ó&Ù °Ô6€Iˆy×6r   r  Úboardc               ó$   € V ^8„  d   QhRRRRRR/# )r   r^   r�   r#  úOptional[str]r   r  r   )r   s   "r   r   r   9  s"   € ÷ -
ñ -
�~ð -
¸ð -
ÐQ]ñ -
r   c               ó"  € V e   T M\         P                  ! VR7      p VP                  4       p\	        V4      '       d   \        RVR7      # \        V4      ;_uu_ 4        \        V4      w  rEVf-   \        RV\        V4      .\        V4      R7      uuRRR4       # Vf   \        RW4R7      uuRRR4       # \        V4      p\        V4      pV'       g   \        RW4VR7      uuRRR4       # \        W74      w  r‰\        ;_uu_ 4        \        P                  \        V4      4       RRR4       \        V'       d   R	MRVVV	VVR
7      uuRRR4       #   \         d    Tp EL7i ; i  + '       g   i     LI; i  + '       g   i     R# ; i)a®  Probe a kanban DB and apply the narrow index-REINDEX repair if needed.
Same policy as :func:`_guard_existing_db_is_healthy` (quarantine BEFORE
any mutation; REINDEX under the init flock; anything non-index stays
corrupt), but returns a :class:`RepairResult` instead of raising so
``hermes kanban repair`` can pick its exit code. ``OperationalError``
(locked/busy) still propagates raw: a locked healthy DB must not be
quarantined.N©r#  Úmissing)r  r^   Úcorrupt)r  r^   rç   rœ   rê   )r  r^   rç   r  )r  r^   rç   r  rœ   r   )r   Úkanban_db_pathrf   rT   r  r  r\   r  re   rå   r  r
  Ú
_INIT_LOCKr
   Údiscard)
r^   r#  r   rß   rç   rž   r  r  r  r  s
   &$        r   Ú	repair_dbr-  9  sM  € ð Ò)‰7¬s×/AÒ/AÈÔ/N€DðØ—<‘<“>ˆô ˜×"Ò"Ü 9°hÔ?Ð?ä	! (×	+Õ	+Ü0°Ó:ÑˆØÒô  Ø ¨(¼cÀ&»k¸]Ü.¨xÓ8ô÷ 
,Ò	+ð Š>Ü t°XÔQ÷ 
,Ò	+ô $ HÓ-ˆÜ-¨hÓ7ˆßÜ y¸(ÐciÔj÷! 
,Ò	+ô" 7°xÓM‰ˆ÷ ŽZÜ×&Ñ&¤s¨8£}Ô5÷ äß!)‘:¨yØØØ!%ØØ!ô
÷- 
,Ò	+øô ô Ø‹ðú÷2 �Zú÷) 
,×	+Ð	+úsG   ŸE Á 3E=ÂE=Â9+E=Ã/E=Ä
E*	Ä)"E=ÅE'Å&E'Å*E:Å5E=Å=F	c               ó    € V ^8„  d   QhRRRR/# )r   r-   r   r   r0   r   )r   s   "r   r   r   i  s   € ÷ ñ Ð/ð °Dñ r   c                ó‚   €  V P                  R4      P                  4       pVRJ#   \        P                   d     R# i ; i)u  Whether an open connection actually sees the kanban schema. ``tasks`` is
the sentinel (SCHEMA_SQL always creates it; SQLite loses tables
all-or-nothing), so one ``sqlite_master`` lookup on the resident page 1
suffices â€” cheap by design, it runs on every steady-state connect().zGSELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks' LIMIT 1FN)r&   rj   r$   r”   )r-   rq   s   & r   Ú_schema_is_presentr0  i  sJ   € ð
Ø�l‰lØUó
ç
‰(‹*ð 	ð �dˆ?Ðøô	 × Ñ ô ò ðús   ‚% ¥>½>c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   ztuple[sqlite3.Connection, Any]r   )r   s   "r   r   r   y  s   € ÷  ñ  ˜4ð  Ð0Nñ  r   c                ó&  € \        V 4      p \        P                  Vn        \        P
                  Vn        \        ;_uu_ 4        ^ RIH	p V! VRV P                   R2R7       VP                  R4       VP                  R4       VP                  R4       VP                  R4       VP                  R	4       VP                  R
4       V! V4      pRRR4       W$3#   + '       g   i     TX3# ; i  \         d    TP                  4        h i ; i)ad  Open ``path`` with the kanban PRAGMA set, then run ``under_lock(conn)``.
WAL activation and ``under_lock`` share the ``_INIT_LOCK`` critical section:
WAL setup can take an exclusive lock while SQLite creates sidecars for a
fresh DB, and concurrent gateway startup threads must not race before
``_INITIALIZED_PATHS`` is populated. Closed if anything raises.)Úapply_wal_with_fallbackúkanban.db (Ú)©Údb_labelzPRAGMA synchronous=FULLzPRAGMA wal_autocheckpoint=100z!PRAGMA journal_size_limit=8388608zPRAGMA foreign_keys=ONzPRAGMA secure_delete=ONzPRAGMA cell_size_check=ONN)r.   r$   ÚRowÚrow_factoryr   Ú_lossy_textÚtext_factoryr+  Úhermes_state_walr3  rO   r&   r*   r+   )r   Ú
under_lockr-   r3  Úouts   &&   r   Ú_open_configuredr?  y  sè   € ô ˜4Ó €DðÜ"Ÿ;™;ˆÔÜŸO™OˆÔßŽZõ AÙ# D°[ÀÇÁÀÈ1Ð3MÕNð �L‰LÐ2Ô3Ø�L‰LÐ8Ô9ð �L‰LÐ<Ô=Ø�L‰LÐ1Ô2à�L‰LÐ2Ô3à�L‰LÐ4Ô5Ù˜TÓ"ˆC÷% ð, ˆ9Ð÷- ŽZð, �ˆ9Ðûô ô Ø�
‰
ŒØðús*   �8C3 ÁBCÃC3 ÃC0	Ã(C3 Ã0C3 Ã3Dc               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   r#  r%  r   rA   r   )r   s   "r   r   r   œ  s&   € ÷ -wñ -w¨$ð -w°}ð -wÈñ -wr   c                ó6  € V P                  4       '       d   R# Vf£   V P                  R,          pVP                  4       '       dx   ^ RIp VP                  VP	                  RR7      4      p\        T\        4      '       d<   TP                  R4      '       d%   \        RT P                  P                  : R24      hR#  \        P                  ! V4      pT'       d   T\        P                  8X  d   R# \        P                  ! T4      pTP                  R4      '       d   \        RT: R24      h\        P                  ! T4      P                  4       '       g   \        RT: RT R	24      hR#   \
        \        3 d     R# i ; i  \         d     R# i ; i)
ub  Refuse to create a fresh DB for a board that is not live.

``board=None`` (or ``default``) is unaffected â€” except that a None board
can still *resolve* through the current-board context (``--board`` scope,
``HERMES_KANBAN_BOARD``, the ``current`` file) to a named board dir; an
``archived`` tombstone in that dir gets the same refusal. For any other
slug whose DB file does not exist yet, opening it would mkdir the board
directory and leave an empty DB-only stub â€” exactly how archived boards
(moved to ``_archived/`` with an ``archived`` tombstone ``board.json``) and
hard-deleted boards (no directory left at all) used to reappear as empty
active boards via stale dashboard/gateway read paths (#43243). Creation
is the job of :func:`kanban_db.create_board` (which writes the metadata
first); read paths get an actionable error instead of a resurrected board.
Nz
board.jsonz	utf-8-sig)ÚencodingÚarchivedzkanban board z/ is archived; refusing to recreate its databasez= does not exist; create it with `hermes kanban boards create Ú`)rÜ   rL   ÚjsonÚloadsÚ	read_textrT   Ú
ValueErrorÚ
isinstanceÚdictrh   rO   r   Ú_normalize_board_slugÚDEFAULT_BOARDÚread_board_metadataÚboard_metadata_path)r   r#  Ú	tombstonerE  rÿ   ÚslugÚmetas   &&     r   Ú_refuse_dead_board_resurrectionrR  œ  sl  € ð ‡{�{‡}‚}ÙØ‚}ð
 —K‘K ,Õ.ˆ	Ø×Ñ×ÒÛðØ—j‘j ×!4Ñ!4¸kÐ!4Ó!JÓK�ô ˜#œt×$Ò$¨¯©°×)<Ò)<Ü Ø# D§K¡K×$4Ñ$4Ñ#7ð 88ð 8óð ñ 	ðÜ×(Ò(¨Ó/ˆ÷ �4œ3×,Ñ,Ô,ÙÜ×"Ò" 4Ó(€DØ‡x�x�
×ÒÜ˜=¨©Ð0_Ð`ÓaÐaÜ×"Ò" 4Ó(×/Ñ/×1Ò1Ü˜=¨©Ð0mÐnrÐmsÐstÐuÓvÐvñ 2øô# œZÐ(ô Úðûô ô Úðús$   Á!E1 Ã F	 Å1FÆFÆ	FÆFc               ó$   € V ^8„  d   QhRRRRRR/# )r   r^   r�   r#  r%  r   r   r   )r   s   "r   r   r   Ì  s'   € ÷ Dñ D�^ð D°mð DÐOañ Dr   c               ód  a	€ V e   T M\         P                  ! VR7      p^ RIHp V! V4      '       d”   \        P
                  ! VP                  4       P                  4       R,           RR7      p\        P                  Vn	        \         P                  Vn        \        V4      '       g   VP                  4        \        R4      hV# \        W!4       VP                   P#                  RRR7       \%        VP                  4       4      o	S	\&        9   dy   \)        V\        4      w  rEV'       d   V# VP                  4        \*        ;_uu_ 4        \&        P-                  S	4       RRR4       \         P.                  P1                  R	V4       \3        V4      ;_uu_ 4        ^ R
IHp V! VRVP8                   R2R7       \;        V4       \=        V4       \%        VP                  4       4      o	R V	3R llp\)        W'4      w  rHRRR4       V#   + '       g   i     L±; i  + '       g   i     X# ; i)a„  Open (and initialize if needed) the kanban DB. WAL is (re)enabled on
every connection so a re-created file stays robust; the first connection
per path auto-runs :func:`init_db`, later ones skip via
``_INITIALIZED_PATHS``. Path: explicit ``db_path``, else ``board``, else
:func:`kanban_db_path` (``HERMES_KANBAN_DB`` -> ``HERMES_KANBAN_BOARD`` ->
``<root>/kanban/current`` -> ``default``).Nr'  )Úkanban_path_is_fencedz?mode=roT)ÚurizOKanban descendants require an initialized board; ask its owner to initialize itrH   zqkanban DB %s lost its schema after this process initialized it (deleted or replaced externally); re-initializing.)Úpreflight_db_writabilityr4  r5  r6  c               ó    € V ^8„  d   QhRRRR/# ©r   r-   r   r   rA   r   )r   s   "r   r   Úconnect.<locals>.__annotate__  s   € ÷ 	1ñ 	1Ð"4ð 	1¸ñ 	1r   c                ó    <€ S\         9  dB   V P                  \        P                  4       \	        V 4       \         P                  S4       R # R # ©N)r
   Úexecutescriptr   Ú
SCHEMA_SQLÚ_migrate_add_optional_columnsÚadd)r-   rß   s   &€r   Ú_init_if_neededÚ connect.<locals>._init_if_needed  s;   ø€ ð Ô1Ô1Ø×"Ñ"¤3§>¡>Ô2Ü-¨dÔ3Ü"×&Ñ& xÖ0ñ 2r   )r   r*  Úagent.delegation_contextrU  r$   r%   rf   Úas_urir8  r9  r:  r;  r0  r+   ÚPermissionErrorrR  rL   rM   re   r
   r?  r+  r,  rW   rX   r\   Úhermes_staterW  rO   r˜   r  )
r^   r#  r   rU  r-   Úschema_presentrW  ra  Ú_rß   s
   &$       @r   r%   r%   Ì  s¨  ø€ ð Ò)‰7¬s×/AÒ/AÈÔ/N€DÝ>Ù˜T×"Ò"ô �Š˜tŸ|™|›~×4Ñ4Ó6¸ÕCÈÔNˆÜ"Ÿ;™;ˆÔÜŸO™OˆÔÜ! $×'Ò'Ø�J‰JŒLÜ!Ð"sÓtÐtØˆÜ# DÔ0Ø‡K�K×Ñ˜d¨TÐÔ2ô �4—<‘<“>Ó"€HØÔ%Ô%Ü/°Ô6HÓIÑˆßØˆKð
 	�
‰
ŒßŽZô ×&Ñ& xÔ0÷ ô 	�‰×ÑðAàô	
ô 
" $×	'Õ	'õ 	:Ù  °+¸d¿i¹i¸[ÈÐ0JÕKô 	  Ô%Ü% dÔ+Ü�t—|‘|“~Ó&ˆ÷	1ð 	1ô # 4Ó9‰ˆ÷) 
(ð* €K÷? �Zú÷ 
(Ö	'ð* €Kús   ÅHÆA$HÈH	ÈH/	c               ó    € V ^8„  d   QhRRRR/# )r   r^   r�   r#  r%  r   )r   s   "r   r   r     s   € ÷ ñ ˜^ð ¸mñ r   c          	   #  ót  "  € \        WR7      p Vx € \        P                  ! \        4      ;_uu_ 4        VP	                  4        RRR4       R#   + '       g   i     R# ; i  \        P                  ! \        4      ;_uu_ 4        TP	                  4        RRR4       i   + '       g   i     i ; i; i5i)uH  Open a kanban DB connection and guarantee it is closed on exit. Use
instead of ``with kb.connect() as conn:`` â€” sqlite3's context manager only
commits/rolls back, it does NOT close the fd, so long-lived processes
(gateway, dashboard) leak FDs until ``[Errno 24] Too many open files``.

See #33159 for the production incident.
)r^   r#  N)r%   r(   r)   r*   r+   )r^   r#  r-   s   &$ r   Úconnect_closingrk    sk   é € ô ˜7Ô0€DðØŠ
ä× Ò ¤×+Õ+Ø�J‰JŒL÷ ,×+×+Ò+ûŒZ× Ò ¤×+Õ+Ø�J‰JŒL÷ ,×+×+Ð+ÿsJ   ‚B8�A% ”"B8¶AÁ
B8ÁA"	Á	B8Á%#B5ÂB"	Â	B5Â"B2Â-B5Â5B8c               ó$   € V ^8„  d   QhRRRRRR/# )r   r^   r�   r#  r%  r   r   r   )r   s   "r   r   r   $  s!   € ÷ ñ �^ð °mð Ètñ r   c               óÆ  € V e   T M\         P                  ! VR7      p\        W!4       VP                  P	                  RRR7       \
        ;_uu_ 4        \        P                  \        VP                  4       4      4       RRR4       \        P                  ! \        V4      4      ;_uu_ 4         RRR4       V#   + '       g   i     LC; i  + '       g   i     T# ; i)u  Create the schema if it doesn't exist; return the path used. Unlike
:func:`connect`'s cached first-time auto-init, this always re-runs the
migration pass â€” callers that know the on-disk schema may have drifted
(tests writing legacy event kinds, external upgrades) use it to force it.Nr'  TrH   )r   r*  rR  rL   rM   r+  r
   r,  re   rf   r(   Úclosingr%   )r^   r#  r   s   &$ r   Úinit_dbro  $  s“   € ð
 Ò)‰7¬s×/AÒ/AÈÔ/N€DÜ# DÔ0Ø‡K�K×Ñ˜d¨TÐÔ2ç	ŽÜ×"Ñ"¤3 t§|¡|£~Ó#6Ô7÷ 
ä	×	Ò	œG D›M×	*Õ	*Ø÷ 
+à€K÷	 
�úç	*Ö	*à€Kús   Á-B<Â0CÂ<C	ÃC 	c               ó$   € V ^8„  d   QhRRRRRR/# )r   r-   r   Útablere   r   r	   r   )r   s   "r   r   r   �  s'   € ÷ Pñ PÐ*ð P°3ð P¸8ñ Pr   c                ób   € V P                  R V R24       Uu0 uF  q"R,          kK  	  up# u upi )úPRAGMA table_info(r5  rO   )r&   )r-   rq  rq   s   && r   Ú_column_namesrt  �  s3   € Ø#'§<¡<Ð2DÀUÀGÈ1Ð0MÔ#NÓOÑ#N˜C��KˆKÑ#NÑOÐOùÒOs   ˜,c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r-   r   rq  re   r   r0   r   )r   s   "r   r   r   “  s"   € ÷ ñ Ð*ð °3ð ¸4ñ r   c                óL   € V P                  R V R24      P                  4       RJ# )z<SELECT name FROM sqlite_master WHERE type='table' AND name='Ú'N)r&   rj   )r-   rq  s   &&r   Ú_table_existsry  “  s-   € Ø�<‰<Ø
FÀuÀgÈQÐOóç�hƒj˜ðð r   c               ó    € V ^8„  d   QhRRRR/# rY  r   )r   s   "r   r   r   ™  s   € ÷ L"ñ L"Ð(:ð L"¸tñ L"r   c                ó  € \        V R4      p\        \        ,            F  w  r#W!9  g   K  \        V RW#4       K  	  \        V R4      p\         FD  w  r#rEW!9  g   K  \        V RW#4      pV'       g   K%  Vf   K+  WA9   g   K3  V P                  V4       KF  	  \         F  w  r#W!9  g   K  \        V RW#4       K  	  V P                  R4       V P                  R4       V P                  R4       V P                  R4       R\        V R4      9  d   \        V RRR	4       V P                  R
4       \        V R4      '       dK   \        V R4      p\         F4  w  r#W'9   d   K  \        V RW#4       VR8X  g   K#  V P                  R4       K6  	  \        V R4      '       d<   \        V R4      p\         F  w  r#W(9  g   K  \        V RW#4       K  	  \        V 4       R F  w  ršV P                  RW©34       K  	  \        V 4       R# )zGAdd columns introduced after v1 to legacy DBs (called via ``init_db``).ÚtasksNzOCREATE INDEX IF NOT EXISTS idx_tasks_assignee_status ON tasks(assignee, status)z<CREATE INDEX IF NOT EXISTS idx_tasks_tenant ON tasks(tenant)zJCREATE INDEX IF NOT EXISTS idx_tasks_idempotency ON tasks(idempotency_key)zDCREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id)Úrun_idÚtask_eventszrun_id INTEGERzDCREATE INDEX IF NOT EXISTS idx_events_run ON task_events(run_id, id)Úkanban_notify_subsÚdelivery_modezSUPDATE kanban_notify_subs SET delivery_mode = 'notify+wake' WHERE platform != 'tui'Ú	task_runsz.UPDATE task_events SET kind = ? WHERE kind = ?))ÚreadyÚpromoted)ÚpriorityÚreprioritized)Úspawn_auto_blockedÚgave_up)rt  Ú_BASE_TASK_COLUMNSÚ_EARLY_TASK_COLUMNSÚ_add_column_if_missingÚ_RENAMED_TASK_COLUMNSr&   Ú_LATER_TASK_COLUMNSry  Ú_NOTIFY_SUB_COLUMNSÚ_TASK_RUN_COLUMNSÚ_backfill_legacy_inflight_runsÚ_rebuild_drifted_tables)r-   ÚcolsrO   ÚddlÚlegacyÚcopy_sqlÚaddedÚnotify_colsÚrun_colsÚoldÚnews   &          r   r_  r_  ™  sÍ  € ä˜˜wÓ'€DÜ'Ô*=×=Ð=‰	ˆØÖÜ" 4¨°$Ö<ñ >ô ˜˜wÓ'€D÷ (=Ñ#ˆ�6ØÖÜ*¨4°¸$ÓDˆEß‰u˜Ô+°¶Ø—‘˜XÖ&ñ	 (=÷
 )‰	ˆØÖÜ" 4¨°$Ö<ñ )ð 	‡L�LÐbÔcØ‡L�LÐOÔPØ‡L�LÐ]Ô^Ø‡L�LÐWÔXð ”} T¨=Ó9Ô9Ü˜t ]°HÐ>NÔOð 	‡L�LÐWÔXä�TÐ/×0Ò0Ü# DÐ*>Ó?ˆß,‰IˆDØÔ"ÙÜ" 4Ð)=¸tÔIØ�Ö&ð —‘ð.öñ -ô �T˜;×'Ò'Ü   {Ó3ˆß*‰IˆDØÖ#Ü& t¨[¸$ÖDñ +ô 	' tÔ,ó‰ˆð
 	�‰ÐEÈÀzÖRñô ˜DÖ!r   c               ó    € V ^8„  d   QhRRRR/# rY  r   )r   s   "r   r   r   è  s   € ÷ /ñ /Ð);ð /Àñ /r   c                óŽ  € \        V 4      ;_uu_ 4        V P                  R4      P                  4       pV Fï  pVR,          ;'       g    \        \        P                  ! 4       4      pV P                  RVR,          VR,          VR,          VR,          VR,          VR	,          VR
,          V34      pV P                  RVP
                  VR,          34      pVP                  ^8w  g   Kµ  V P                  R\        \        P                  ! 4       4      VP
                  34       Kñ  	  RRR4       R#   + '       g   i     R# ; i)aw  One-shot backfill: tasks 'running' before runs existed carried
claim_lock / claim_expires / worker_pid on the task row; synthesize a
matching task_runs row so end-run / heartbeat have something to write.
write_txn serializes against concurrent dispatchers, and the per-row
UPDATE uses ``current_run_id IS NULL`` as a CAS guard so a racing claim
can't produce an orphaned row.z´SELECT id, assignee, claim_lock, claim_expires, worker_pid,        max_runtime_seconds, last_heartbeat_at, started_at FROM tasks WHERE status = 'running' AND current_run_id IS NULLÚ
started_ata:  
                INSERT INTO task_runs (
                    task_id, profile, status,
                    claim_lock, claim_expires, worker_pid,
                    max_runtime_seconds, last_heartbeat_at,
                    started_at
                ) VALUES (?, ?, 'running', ?, ?, ?, ?, ?, ?)
                ÚidÚassigneeÚ
claim_lockÚclaim_expiresÚ
worker_pidÚmax_runtime_secondsÚlast_heartbeat_atzKUPDATE tasks SET current_run_id = ? WHERE id = ? AND current_run_id IS NULLz_UPDATE task_runs SET status = 'reclaimed',     outcome = 'reclaimed', ended_at = ? WHERE id = ?N)Ú	write_txnr&   rð   r   rQ   Ú	lastrowidÚrowcount)r-   Úinflightrq   ÚstartedÚcurÚupds   &     r   r�  r�  è  s  € ô 
�4��Ø—<‘<ðBó
÷
 ‰(‹*ð 	ó ˆCØ˜,Õ'×;Ð;¬3¬t¯yªy«{Ó+;ˆGØ—,‘,ðð ˜•I˜s :�°°LÕ0AØ˜Õ(¨#¨lÕ*;ØÐ-Õ.°Ð4GÕ0HØð	óˆCð( —,‘,ð:à—‘  D¥	Ð*óˆCð
 �|‰|˜qÖ Ø—‘ð#ô œŸš›Ó% s§}¡}Ð5ö	ñ9 ÷ 
��Šús   ”4D3Á	BD3Ã+>D3Ä3E	r~  Útask_commentsr�  r  c               ó$   € V ^8„  d   QhRRRRRR/# rv  r   )r   s   "r   r   r   U  s'   € ÷ Nñ NÐ/ð N¸ð NÀñ Nr   c                ó–  € V P                  RV R24      P                  4       pV'       g   R# VR8X  dD   \        R V 4       R4      pVRJ;'       d%    VR,          ;'       g    RP                  4       R	8g  # \        R
 V 4       R4      pVf   R# VR,          ;'       g    RP                  4       R	8H  ;'       d
    VR,          '       * # )zGTrue when ``table`` still carries the legacy (pre-AUTOINCREMENT) shape.rs  r5  Fr  c              3  óD   "  € T F  qR ,          R8X  g   K  Vx € K  	  R# 5i)rO   Úlast_event_idNr   ©Ú.0Úcs   & r   Ú	<genexpr>Ú%_table_has_drifted.<locals>.<genexpr>[  s   é € ÐD™t˜!¨¥y°OÑ'C—A’A›tùó   ‚ –
 NÚtyper�   ÚINTEGERc              3  óD   "  € T F  qR ,          R8X  g   K  Vx € K  	  R# 5i)rO   r�  Nr   r°  s   & r   r³  r´  ^  s   é € Ð8™d˜¨¥i°4Ñ&7—1’1›dùrµ  Úpk)r&   rð   ÚnextÚupper)r-   rq  ÚinfoÚleiÚid_cols   &&   r   Ú_table_has_driftedr¿  U  s¹   € à�<‰<Ð,¨U¨G°1Ð5Ó6×?Ñ?ÓA€DßÙØÐ$Ô$ÜÑD™tÓDÀdÓKˆØ˜$ˆ×KÐK C¨¥K×$5Ð$5°2×#<Ñ#<Ó#>À)Ñ#KÐKäÑ8™dÓ8¸$Ó?€FØ‚~ÙØ˜•×%Ð% 2×,Ñ,Ó.°)Ñ;×LÐLÀÀtÅÔMÐMr   c               ó    € V ^8„  d   QhRRRR/# rY  r   )r   s   "r   r   r   d  s   € ÷ -ñ -Ð"4ð -¸ñ -r   c                óÎ  aa€ \          Uu. uF  p\        W4      '       g   K  VNK  	  ppV'       g   R# V P                  R4        V EF  p\         V,          w  rEV P                  RV R24       Uu. uF  qfR,          NK  	  pp\        P                  P                  RV4       V P                  RV RV R	24       V P                  V4       \        W4      oVR
8X  d	   RRuopRp	MRRRuor‰RP                  VV3R lV 4       4      p
V P                  RV RV
 V RV
 V	 RV R	24       V P                  RV R	24       V F  pV P                  V4       K  	  EK  	  V P                  R4       R# u upi u upi   \         d\    \        P                  ! \        P                  4      ;_uu_ 4        T P                  R4       RRR4       h   + '       g   i     h ; ii ; i)uÉ  Rebuild any kanban table whose column types drifted from SCHEMA_SQL.

Drifted boards crash the gateway notifier (``int(None)`` on a NULL id) and
never match ``id > cursor``, silently losing every notification. Legacy
TEXT ids are dropped (AUTOINCREMENT reassigns) and cursors reset to 0, so
the first post-migration tick replays history once â€” safe for a feature
that was already fully broken. One transaction under ``connect()``'s init
locks so an interruption can't leave a table half-renamed. Idempotent.

Each affected table is rebuilt with the standard SQLite pattern â€” CREATE new â†’ INSERT shared columns â†’
DROP old â†’ RENAME â€” recreating its indexes too (DROP TABLE takes them down). See #35096.
NúBEGIN IMMEDIATErs  r5  rO   z7kanban migration: rebuilding %s to match current schemazALTER TABLE z RENAME TO Ú_legacyr  r¯  z, last_event_idz-, COALESCE(CAST(last_event_id AS INTEGER), 0)r�  r�   r  c              3  óJ   <"  € T F  qS9   g   K  VS8w  g   K  Vx € K  	  R # 5ir\  r   )r±  r²  ÚdropÚnew_colss   & €€r   r³  Ú*_rebuild_drifted_tables.<locals>.<genexpr>…  s   øé € Ð T©H q¸X¹¤È!ÈtÉ)§¢«Hùs   ƒ#�#™
#zINSERT INTO z (z	) SELECT z FROM zDROP TABLE ÚCOMMITÚROLLBACK)Ú_REBUILD_SPECSr¿  r&   r   rW   r¼  rt  r  r*   r(   r)   r$   r  )r-   ÚtÚdriftedrq  Ú
create_sqlÚ
index_sqlsr²  Úold_colsÚ
extra_colsÚextra_selectÚcols_csvÚ	index_sqlrÅ  rÆ  s   &           @@r   r�  r�  d  sÌ  ù€ õ )ÓH™.�QÔ,>¸t×,G�qˆq™.€GÐHßÙà‡L�LÐ"Ô#ðÜˆEÜ%3°EÕ%:Ñ"ˆJØ+/¯<©<Ð:LÈUÈGÐSTÐ8UÔ+VÓWÑ+V a˜&Ÿ	˜	Ñ+VˆHÐWÜ�H‰H�M‰MÐSÐUZÔ[Ø�L‰L˜<¨ w¨k¸%¸ÀÐHÔIØ�L‰L˜Ô$Ü$ TÓ1ˆHØÐ,Ô,à#2Ð4EÐ ��jØN‘ð 26°r¸2Ð.��jØ—y‘yÕ T©HÓ TÓTˆHØ�L‰LØ˜u˜g R¨ z°*°ð >Ø"˜ L >°¸°w¸gðGôð �L‰L˜; u g¨WÐ5Ô6Û'�	Ø—‘˜YÖ'ô (ñ) ð, 	�‰�XÖùò9 Iùò Xøô* ô Ü× Ò ¤×!9Ñ!9×:Õ:Ø�L‰L˜Ô$÷ ;à÷ ;Ö:àúðúsA   ‹E4£E4Á,E> Á3E9ÂC.E> Å9E> Å>7G$Æ5GÇ	G$ÇG Ç	G$c               ó    € V ^8„  d   QhRRRR/# rY  r   )r   s   "r   r   r   ”  s   € ÷ 
ñ 
Ð'9ð 
¸dñ 
r   c                óJ  € ^ RI Hp  V P                  R4      P                  4       pV'       d,   V^ ,          e!   \	        V^ ,          4      P                  4       MRpTR8X  d   R# T! T 4      RJ d   \        P                  ! R4      hR#   \        P                   d     R# i ; i)u.  Raise ``sqlite3.DatabaseError`` if the file is shorter than its header
page count claims (torn-extend). Both sides are read WITHOUT opening the
database file (``PRAGMA page_count`` on the existing connection; ``stat()``
for disk): an earlier bare ``open(path,"rb")`` probe was wrong because
``close()`` cancels every POSIX advisory lock this process holds on the
file, silently dropping concurrent writers' (and a running VACUUM's) locks
and letting other processes write into a database a writer still believed
it owned (sqlite.org/howtocorrupt.html Â§2.2).)Úfile_length_matches_headerzPRAGMA journal_modeNr�   ÚwalFzTtorn-extend detected: the database file is shorter than its header page count claims)	r#   rÖ  r&   rj   re   rì   r$   rm   r”   )r-   rÖ  rq   Újournal_modes   &   r   Ú_check_file_length_invariantrÙ  ”  s—   € õ Gð
Ø�l‰lÐ0Ó1×:Ñ:Ó<ˆß.1°c¸!µfÒ6H”s˜3˜q�6“{×(Ñ(Ô*Èbˆð �uÔÙá! $Ó'¨5Ó0Ü×#Ò#ð'ó
ð 	
ñ 1øô �=‰=ô Úðús   ˆ&B	 ¯-B	 Â	B"Â!B"g{®Gáz”?g333333Ã?c               ó    € V ^8„  d   QhRRRR/# )r   rr   r'   r   r0   r   )r   s   "r   r   r   »  s   € ÷ ñ ˜ð ¨$ñ r   c                óÆ   € \        V \        P                  4      ;'       dA    R \        V 4      P	                  4       9   ;'       g    R\        V 4      P	                  4       9   # )zdatabase is lockedzdatabase is busy)rI  r$   r  re   rì   )rr   s   &r   Ú_is_busy_errorrÜ  »  sQ   € Ü�cœ7×3Ñ3Ó4÷ ð Ø¤ C£§¡Ó 0Ñ0÷ 	2ð 	2Ø¤ S£§¡Ó!1Ñ1ðr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r-   r   Úsqlre   r   rA   r   )r   s   "r   r   r   Â  s'   € ÷ Mñ MÐ'9ð MÀð MÈñ Mr   c                óL  € \        \        ^,           4       F  p V P                  V4        R# 	  R#   \        P                   d\   p\        T4      '       d   T\        8X  d   h \        P                  ! \        P                  ! \        \        4      4        Rp?K‡  Rp?ii ; i)r   N)ÚrangeÚ_BUSY_MAX_RETRIESr&   r$   r  rÜ  rQ   rU   ÚrandomÚuniformÚ_BUSY_RETRY_MIN_SÚ_BUSY_RETRY_MAX_S)r-   rÞ  Úattemptrr   s   &&  r   Ú_execute_boundary_with_retryrç  Â  sx   € ÜÔ*¨QÕ.Ö/ˆð	MØ�L‰L˜ÔÚó 0øô ×'Ñ'ô 	MÜ! #×&Ò&¨'Ô5FÔ*FØÜ�JŠJ”v—~’~Ô&7Ô9JÓK×LÒLûð	Mús   ›3³B#ÁABÂB#c               ó    € V ^8„  d   QhRRRR/# )r   r-   r   r   r%  r   )r   s   "r   r   r   Í  s   € ÷ ñ Ð*ð ¨}ñ r   c                óÊ   €  V P                  R4      ;'       g    R F  w  rpVR8X  g   K  T;'       g    Ru # 	  R#   \        P                  \        \        3 d     R# i ; i)zLFilesystem path of *conn*'s main database (None for in-memory / unreadable).zPRAGMA database_listÚmainNr   )r&   r$   rm   Ú	TypeErrorrH  )r-   Ú_seqrO   Úfiles   &   r   Ú_main_db_filerî  Í  sf   € ðØ $§¡Ð-CÓ D× JÐ JÈÒ JÑˆD˜Ø�vŽ~Ø—|�|˜tÒ#ñ !Kñ
 øô �M‰Mœ9¤jÐ1ô ØÙðús   ‚> š> ¬> µ> º> ¾ A"Á!A"Úallow_nestedFc               ó    € V ^8„  d   QhRRRR/# )r   r-   r   rï  r0   r   )r   s   "r   r   r   Ù  s   € ÷ 3+ñ 3+Ð&ð 3+¸ñ 3+r   c             #  ó  "  € \         P                  ! \        V 4      4       \        V RR4      '       d\   V'       g   \	        R4      hR\
        P                  ! ^4       2pV P                  RV 24        V x € V P                  RV 24       R# \        V R	4        V x €  \        V R
4       \        T 4       R#   \         ds    \        P                  ! \        P                  4      ;_uu_ 4        T P                  RT 24       T P                  RT 24       RRR4       h   + '       g   i     h ; ii ; i  \         d\    \        P                  ! \        P                  4      ;_uu_ 4        T P                  R4       RRR4       h   + '       g   i     h ; ii ; i  \         d\    \        P                  ! \        P                  4      ;_uu_ 4        T P                  R4       RRR4       h   + '       g   i     h ; ii ; i5i)uÖ  IMMEDIATE write transaction; a claim CAS inside is atomic â€” at most one
concurrent writer succeeds.

Nesting is an explicit opt-in (``allow_nested=True`` â†’ savepoint; otherwise
a loud ``RuntimeError``). Only composition primitives (``create_task``,
``add_comment``) opt in â€” helpers with post-commit side effects
(``complete_task`` & co.) must never run under an open outer transaction,
since those side effects would fire while the outer txn can still roll back.
Úin_transactionFz×write_txn: already inside a transaction. Nested composition must opt in explicitly with write_txn(conn, allow_nested=True) (savepoint semantics; the inner RELEASE is not durable until the outer transaction commits).Úhermes_nested_z
SAVEPOINT zRELEASE zROLLBACK TO NrÂ  rÈ  rÉ  )r   Ú$_assert_not_delegated_child_mutationrî  ÚgetattrÚRuntimeErrorÚsecretsÚ	token_hexr&   r*   r(   r)   r$   r  rç  rÙ  )r-   rï  Ú	savepoints   &$ r   r¤  r¤  Ø  s³  é € ô ×,Ò,¬]¸4Ó-@ÔAÜˆtÐ% u×-Ò-ßÜð2óð ð %¤W×%6Ò%6°qÓ%9Ð$:Ð;ˆ	Ø�‰�z ) Ð-Ô.ð	1ØŠJð �L‰L˜8 I ;Ð/Ô0Ùä  Ð'8Ô9ð+ØŠ
ð	Ü(¨¨xÔ8ô 	% TÖ*øô9 ô 	Ü×$Ò$¤W×%=Ñ%=×>Õ>Ø—‘˜|¨I¨;Ð7Ô8Ø—‘˜x¨	 {Ð3Ô4÷ ?ð ÷ ?Ö>ð úð		ûô* ô 	ô ×$Ò$¤W×%=Ñ%=×>Õ>Ø—‘˜ZÔ(÷ ?à÷ ?Ö>àúð	ûô ô ô × Ò ¤×!9Ñ!9×:Õ:Ø�L‰L˜Ô$÷ ;à÷ ;Ö:àúðüs”   ‚:H½8HÁ6B; Á:"HÂF$ Â"D; Â.HÂ;7D8Ã2)D$Ä	D8Ä$D4Ä/	D8Ä8HÄ;7F!Å2FÆ	F!ÆFÆ	F!Æ!HÆ$7H
ÇG6Ç-	H
Ç6HÈ	H
È
H)Ú	kanban_dbr\  )
)Úbodyz	body TEXT)rž  zassignee TEXT)r„  zpriority INTEGER DEFAULT 0)Ú
created_byzcreated_by TEXT)rœ  zstarted_at INTEGER)Úcompleted_atzcompleted_at INTEGER)Úworkspace_kindz.workspace_kind TEXT NOT NULL DEFAULT 'scratch')Úworkspace_pathzworkspace_path TEXT)rŸ  zclaim_lock TEXT)r   zclaim_expires INTEGER))Útenantztenant TEXT)Úresultzresult TEXT)Úbranch_namezbranch_name TEXT)Ú
project_idzproject_id TEXT)Úidempotency_keyzidempotency_key TEXT))Úconsecutive_failuresz/consecutive_failures INTEGER NOT NULL DEFAULT 0Úspawn_failureszCUPDATE tasks SET consecutive_failures = COALESCE(spawn_failures, 0))r¡  zworker_pid INTEGERNN)Úlast_failure_errorzlast_failure_error TEXTÚlast_spawn_errorz6UPDATE tasks SET last_failure_error = last_spawn_error))r¢  zmax_runtime_seconds INTEGER)r£  zlast_heartbeat_at INTEGER)Úcurrent_run_idzcurrent_run_id INTEGER)Úworkflow_template_idzworkflow_template_id TEXT)Úcurrent_step_keyzcurrent_step_key TEXT)Úskillszskills TEXT)Úmax_retrieszmax_retries INTEGER)Úmodel_overridezmodel_override TEXT)Úprovider_overridezprovider_override TEXT)Úreasoning_effortzreasoning_effort TEXT)Ú	goal_modez$goal_mode INTEGER NOT NULL DEFAULT 0)Úgoal_max_turnszgoal_max_turns INTEGER)Úcompletion_contractzcompletion_contract TEXT)Ú
session_idzsession_id TEXT)Ú
block_kindzblock_kind TEXT)Úblock_recurrencesz,block_recurrences INTEGER NOT NULL DEFAULT 0©Úworker_started_atzworker_started_at INTEGER))Úlast_ping_event_idz-last_ping_event_id INTEGER NOT NULL DEFAULT 0)Únotifier_profileznotifier_profile TEXT)r€  z,delivery_mode TEXT NOT NULL DEFAULT 'notify')Ú	chat_typezchat_type TEXT)Úuser_id_altzuser_id_alt TEXT)Údelivery_metadatazdelivery_metadata TEXT)r  )z¦CREATE TABLE task_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, run_id INTEGER, kind TEXT NOT NULL, payload TEXT, created_at INTEGER NOT NULL))z@CREATE INDEX idx_events_task ON task_events(task_id, created_at)z6CREATE INDEX idx_events_run ON task_events(run_id, id))z CREATE TABLE task_comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, author TEXT NOT NULL, body TEXT NOT NULL, created_at INTEGER NOT NULL))zDCREATE INDEX idx_comments_task ON task_comments(task_id, created_at))a}  CREATE TABLE task_runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, profile TEXT, step_key TEXT, status TEXT NOT NULL, claim_lock TEXT, claim_expires INTEGER, worker_pid INTEGER, worker_started_at INTEGER, max_runtime_seconds INTEGER, last_heartbeat_at INTEGER, started_at INTEGER NOT NULL, ended_at INTEGER, outcome TEXT, summary TEXT, metadata TEXT, error TEXT))z<CREATE INDEX idx_runs_task ON task_runs(task_id, started_at)z1CREATE INDEX idx_runs_status ON task_runs(status))aÃ  CREATE TABLE kanban_notify_subs ( task_id TEXT NOT NULL, platform TEXT NOT NULL, chat_id TEXT NOT NULL, thread_id TEXT NOT NULL DEFAULT '', user_id TEXT, user_id_alt TEXT, chat_type TEXT, notifier_profile TEXT, delivery_mode TEXT NOT NULL DEFAULT 'notify', delivery_metadata TEXT, created_at INTEGER NOT NULL, last_event_id INTEGER NOT NULL DEFAULT 0, last_ping_event_id INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (task_id, platform, chat_id, thread_id)))z;CREATE INDEX idx_notify_task ON kanban_notify_subs(task_id))_Ú__conditional_annotations__r¬   Ú
__future__r   r(   r×   râ  Úrer÷  rÝ   r$   Ú	threadingrQ   Údataclassesr   r   Úhermes_cli.sqlite_utilr   rŠ  Úpathlibr   Útypingr   r   Úsetr
   r!  ÚRLockr+  r“   r   rÄ   rS   rV   r   r.   r?   rE   Úcontextmanagerr\   ra   ri   rb   ÚLockrg   rs   rˆ   r˜   rö  rš   r¤   rÌ   rå   Úcompilerú   rí   rò   rö   r  r
  r  r  r  r  r-  r0  r?  rR  r%   rk  ro  rˆ  r‰  r‹  rŒ  r�  rŽ  rt  ry  r_  r�  rÊ  r¿  r�  rÙ  rá  rä  rå  rÜ  rç  rî  r¤  Ú
hermes_clirú  r   )r  s   @r   Ú<module>r,     s½  øðôõ #ã Û Û Û 	Û Û Û Û Û Ý !Ý Ý RÝ Ý Ý ñ  #›uÐ �HÓ $Ø�_Š_Ó€
Ø'€Ø!Ð ð Ð ð
 "Ð ØÐ õ]õõ6õ$
4ð ×Ñô)ó ð)ðX ×Ñô,ó ð,ðt $)Ð  Ø)+Ð Ð&Ó +Ø —~’~Ó'Ð õLõ6õô<
˜<ô 
õN÷/õ@8ð@ ‡J‚JÐ=Ó>Ø‡J‚JÐ<Ó=ð$Ð  õFõ
Sõõõ*6õ2õ]õ ,9ð^ ÷7ð 7ó ð7ñ-
È÷ -
ð -
õ`õ  õF-wñ`DÀd÷ Dð DðN ×ÑñÈd÷ ó ðñ Àd÷ ð ð.Ð ðÐ ð
Ð ðÐ ð4
Ð ðÐ õPõõL"õ^/ð~ ð 	ð ð ð ð ð ð ðA,€õ^Nõ-õ`
ðD Ð ØÐ ØÐ õõMõð ×Ñð3+Àö 3+ó ð3+÷p (r   