+
    jHàjåÕ ã            4      óô  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t^ RIHtHt ^ RIHt ^ RIHt ^ RIHtHtHt ^ RIHt ^ RIHt ]P<                  ! ]4      t R	 R
 lt!ER×R R llt"ER×R R llt#R R lt$R^ /R R llt%R^/R R llt&RR0t'0 ERØmt(^t)0 ERÙmt*R R lt+],! R ]! 4        4       4      t-]P\                  R8H  t/ERÚt0ER×R R llt1R R  lt2R! R" lt3R# R$ lt4R% R& lt5R'R/R( R) llt6R'R/R* R+ llt7ERÛt8R'RR,R-/R. R/ llt9ERÜt:ER¶t;^xt<ER×R0 R1 llt=^t>^Kt?^Nt@R2 R3 ltAR4 R5 ltBR6tC]! R7RR87      tDR9]ER:&   ]! R;R-R87      tFR<]ER=&   ]PŽ                  R> 4       tH]PŽ                  R? R@ l4       tI]P”                  ! RA4      tKRB RC ltLRD RE ltMRF RG ltNRH RI ltORJ RK ltPRL RM ltQRN RO ltRRP RQ ltSRR RS ltTER×RT RU lltUER×RV RW lltVRX RY ltWRZ R[ ltXR\ R] ltYER×R^ R_ lltZER×R` Ra llt[ER×Rb Rc llt\ER×Rd Re llt]ER×Rf Rg llt^] ! Rh Ri4      4       t_ERÝt`ERÞtaERßtbERàtc] ! R‚ Rƒ4      4       td] ! R„ R…4      4       te] ! R† R‡4      4       tf] ! Rˆ R‰4      4       tgRŠthR‹ RŒ ltiR� RŽ ltjR� R� ltkR‘ R’ ltlR“ R” ltmR• R– ltnR— R˜ ltoR™ Rš ltpRkRRlRRoRRpRRqRRsRRuRRn^ R›ERáRœR-RwRRxRR�RR{RR~RRRR€RRžR-R�RRŸRR|RR'RRtRR RR¡RR}R/R¢ R£ lltqR¤ R¥ ltrR¦ R§ ltsR¨ R© lttRª R« ltuRrR/R¬ R­ lltvR® R¯ ltwR°R±R²R³RnR´RµR¶RmR·RlR¸RjR¹RºR»R¼R½/	txR¾]ER¿&   RlRRmRRuRR|RRÀR-RÁRRÂRRyRRzR/	RÃ RÄ lltyRÅ RÆ ltzER×RÇ RÈ llt{RÉ RÊ lt|RË RÌ lt}RÍR/RÎ RÏ llt~RÐ RÑ ltRÒ RÓ lt€RÔ RÕ lt�RÖ R× lt‚RØ RÙ ltƒRÚ RÛ lt„RÜ RÝ lt…RÞ Rß lt†Rà Rá lt‡Râ Rã ltˆRä Rå lt‰Ræ^ /Rç Rè lltŠ ! Ré Rê]‹4      tŒRë Rì lt�Rí Rî ltŽRïRRðRR'RRñR/Rò Ró llt�RïRRô^ RðR/Rõ Rö llt�R÷ Rø lt‘Rù Rú lt’Rû Rü lt“Rý Rþ lt”Rÿ ER  lt•ER×ERR/ER ER lllt–ERRERRERRRmR/ER ER llt—ER	 ER
 lt˜ER ER lt™ER ER ltšER ER lt›ER ER ltœ]�! 4       tžER]EER&   ERRERRER]ž/ER ER lltŸERRERRERRER]ž/ER ER llt ER ER lt¡ER×ER ER llt¢ER ER lt£ER×ER  ER! llt¤ER" ER# lt¥ER$ ER% lt¦ER&R/ER' ER( llt§ER)RER*R/ER+ ER, llt¨ER)RER*R/ER- ER. llt©ER×ER/ ER0 lltªER1ER2ER3ER4ER5ER5RRER6R/t«ER×ER7 ER8 llt¬ER)RER*R/ER9 ER: llt­ER; ER< lt®ER=RER>R/ER? ER@ llt¯ERA ERB lt°ERC ERD lt±ERERER=R/ERF ERG llt²ERHR-ERER/ERI ERJ llt³ERK ERL lt´]P”                  ! ERM4      tµERN ERO lt¶ ! ERP ERQ]‹4      t· ! ERR ERS]‹4      t¸ ! ERT ERU]¹4      tº ! ERV ERW]‹4      t»ERX ERY lt¼RvRERRERRERZRER[RER\ER]ER^R-/ER_ ER` llt½ERat¾ERb ERc lt¿ERd ERe ltÀERf ERg ltÁRðERh/ERi ERj lltÂERk ERl ltÃERm ERn ltÄERo ERp ltÅERq ERr ltÆERs ERt ltÇERu ERv ltÈERw ERx ltÉRðERh/ERy ERz lltÊER{ ER| ltËRjRRkRRnRRvRERRERRR'R/ER} ER~ lltÌERERERRER[R/ER€ ER� lltÍER‚ ERƒ ltÎER„ ER… ltÏERRERRER†RER[RER^R-ER‡R-/ERˆ ER‰ lltÐERŠ ER‹ ltÑERŒ ER� ltÒER[R/ERŽ ER� lltÓERERR,R-/ER� ER‘ lltÔER’ ER“ ltÕER” ER• ltÖER– ER— lt×ER˜ ER™ ltØERš ER› ltÙRjRRkRRlRERœR/ER� ERž lltÚER=R/ERŸ ER  lltÛER¡ ER¢ ltÜER£ ER¤ ltÝER¥ ER¦ ltÞERERER[R/ER§ ER¨ lltßER© ERª ltàER« ER¬ ltáER­ ER® ltâER¯ ER° ltãER± ER² ltäER³ ER´ ltåERµERâ/ER· ER¸ lltæERµERâR'R/ER¹ ERº lltçR'R/ER» ER¼ lltèER½RR'R/ER¾ ER¿ lltéERÀ ERÁ ltêERÂ ERÃ ltëERÄER]ERÅRERÆR/ERÇ ERÈ lltìERÉ ERÊ ltíERË ERÌ ltîERÍ ERÎ ltïERÏ ERÐ ltðERÑ ERÒ ltñ^ ERÓIòHótóHôtôHõtõHötöH÷t÷HøtøHùtùHútú ^ ERÔIûHütüHýtýHþtþ ^ ERÕIÿEH Et EHEtEHEtEHEt ^ ERÖEIEHEtEHEtEHEtEHEtEH	Et	EH
Et
EHEtEHEtEHEtEHEtEHEt R# (ã  u'  SQLite-backed Kanban board shared across profiles (the cross-profile coordination primitive).

Lives under the shared Hermes root: ``default`` board DB at ``<root>/kanban.db`` (pre-boards
back-compat), other boards at ``<root>/kanban/boards/<slug>/``; a worker on one board never sees
another. Board resolution: ``board=`` arg > ``HERMES_KANBAN_BOARD`` > ``HERMES_KANBAN_DB`` (pins the
file path) > ``<root>/kanban/current`` > ``default`` â€” but only for unfenced callers; the dispatcher
injects these into workers, and dispatched workers (``HERMES_KANBAN_TASK``), delegated children and
board-enumerating machine flows (gateway notifier/watcher/dispatcher, ``pin_first_board_resolution``)
always resolve through the pin, so workers physically cannot see other boards.
Concurrency: WAL + ``BEGIN IMMEDIATE`` + compare-and-swap on ``tasks.status``/``claim_lock`` â€”
SQLite serializes writers so one claimer wins, losers see zero rows (no retries, no distributed
locks). Schema: tasks, task_links, task_comments, task_events, task_runs, attachments, notify subs.
)ÚannotationsN)Ú
ContextVarÚToken)Ú	dataclass)ÚPath)ÚAnyÚIterableÚOptional)ÚDEFAULT_STATUSES)Úget_toolset_namesc               ó    € V ^8„  d   QhRRRR/# ©é   Úvaluer   Úreturn© )Úformats   "Ú9/home/ubuntu/.hermes/hermes-agent/hermes_cli/kanban_db.pyÚ__annotate__r   (   s   € ÷ ñ �sð ˜sñ ó    c                óX   € \        V \        4      '       d   V P                  RRR7      # V # )a¼  ``bytes`` -> ``str`` with U+FFFD for undecodable sequences; anything else passes through.

Installed as every board connection's ``text_factory`` (a TEXT cell holding
invalid UTF-8 otherwise aborts the whole ``fetchall`` with "Could not decode
to UTF-8") and applied to BLOB-typed cells in the ``from_row`` constructors
(task, comment, event, run), so one
corrupt row degrades to replacement characters instead of taking the board
listing down.úutf-8Úreplace©Úerrors)Ú
isinstanceÚbytesÚdecode©r   s   &r   Ú_lossy_textr   (   s)   € ô �%œ×ÒØ�|‰|˜G¨Iˆ|Ó6Ð6Ø€Lr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Úrowr   ÚcolÚstrÚdefaultr   r   )r   s   "r   r   r   6   s(   € ÷ ñ �#ð ˜Cð ¨#ð ¸ñ r   c                óF   € V e   WP                  4       9  d   V# W,          # )zJ``row[col]`` tolerant of the column being absent from the SELECT / schema.)Úkeys)r!   r"   r$   s   &&&r   Ú_row_getr'   6   s   € à
‚{�c§¡£Ô+ØˆØ�8€Or   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   r$   r   r   )r   s   "r   r   r   =   s!   € ÷ ñ �Cð  #ð °ñ r   c                ój   € V '       g   V#  \         P                  ! V 4      #   \         d    Tu # i ; i)zPDecode a JSON text column; any decode failure or empty value yields ``default``.)ÚjsonÚloadsÚ	Exception)r   r$   s   &&r   Ú_json_orr-   =   s2   € çØˆðÜ�zŠz˜%Ó Ð øÜô ØŠðús   Œ" ¢2±2c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   Údictr   )r   s   "r   r   r   G   s   € ÷ 6ñ 6�cð 6˜dñ 6r   c                óN   € \        V / 4      p\        V\        4      '       d   V# / # )zNDecode a JSON text column that must be an object; anything else yields ``{}``.)r-   r   r/   )r   Úparseds   & r   Ú
_json_dictr2   G   s%   € ä�e˜RÓ €FÜ ¬×-Ò-ˆ6Ð5°2Ð5r   Úminimumc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Únamer#   r$   Úintr3   r   r   )r   s   "r   r   r   M   s(   € ÷ 
ñ 
�3ð 
 ð 
°#ð 
¸cñ 
r   c               óÀ   € \         P                  P                  V R4      P                  4       pV'       d    \	        V4      pYB8¼  d   T# V#   \
         d    Tu # i ; i)z[Integer env override: absent/empty/non-integer/below ``minimum`` falls back to ``default``.Ú )ÚosÚenvironÚgetÚstripr6   Ú
ValueError)r5   r$   r3   Úrawr1   s   &&$  r   Ú_env_intr?   M   sX   € ä
�*‰*�.‰.˜˜rÓ
"×
(Ñ
(Ó
*€Cß
ð	Ü˜“XˆFð ÔØˆMØ€Nøô	 ô 	ØŠNð	ús   ¸A ÁAÁAÚtimeoutc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   Úcwdr   Úargsr#   r@   r6   r   úOptional[str]r   )r   s   "r   r   r   Z   s(   € ÷ 1ñ 1�$ð 1˜sð 1¨Sð 1¸-ñ 1r   c          
     ó   €  \         P                  ! RR\        V 4      .VORRRRVRR7      pTP                  ^ 8w  d   R# TP
                  ;'       g    R	P                  4       ;'       g    R#   \         d     R# i ; i)
z^Run ``git -C cwd args`` and return stripped stdout, or ``None`` on any failure / empty output.Úgitz-CTr   r   F)Úcapture_outputÚtextÚencodingr   r@   ÚcheckNr8   )Ú
subprocessÚrunr#   r,   Ú
returncodeÚstdoutr<   )rB   r@   rC   Úresults   &$* r   Ú_git_outrP   Z   s€   € ðÜ—’Ø�Dœ#˜c›(Ð* TÐ*Ø d°WÀYØ 5ô
ˆð ×Ñ˜AÔÙØ�M‰M×Ð˜R×&Ñ&Ó(×0Ð0¨DÐ0øô	 ô Úðús   ‚+A. Á.A=Á<A=ÚrunningÚblockedc               ó    € V ^8„  d   QhRRRR/# )r   ÚeffortrD   r   r   )r   s   "r   r   r   v   s   € ÷ Sñ S }ð S¸ñ Sr   c                óð   € ^ RI Hp \        T ;'       g    R4      P                  4       P	                  4       pV'       g   R# VR8X  g   W!9   d   V# RP                  R.VO54      p\        RV RV : 24      h)uå   ``VALID_REASONING_EFFORTS`` or ``"none"`` (thinking off), case-insensitive;
empty/None = inherit the profile's own effort (NULL). Anything else raises â€”
a typo'd level must not quietly hand the task back to the profile default.)ÚVALID_REASONING_EFFORTSr8   NÚnoneú, z reasoning_effort must be one of ú, got )Úhermes_constantsrV   r#   r<   ÚlowerÚjoinr=   )rT   rV   r   Úalloweds   &   r   Únormalize_reasoning_effortr^   v   sp   € õ 9ä�—�˜"Ó×#Ñ#Ó%×+Ñ+Ó-€EßÙØ�„˜%Ô:ØˆØ�i‰i˜Ð:Ð"9Ñ:Ó;€GÜ
Ð7¸°yÀÀvÁjÐQÓ
RÐRr   c              #  ó@   "  € T F  qP                  4       x € K  	  R # 5i©N)Úcasefold)Ú.0r5   s   & r   Ú	<genexpr>rc   …   s   é € ÐPÑ<O°D§¡§ Ó<Oùs   ‚Úwin32c               ó    € V ^8„  d   QhRRRR/# )r   Úpathzstr | Path | Noner   ÚNoner   )r   s   "r   r   r   Š   s   € ÷ cñ cÐ/@ð cÈDñ cr   c                ó`   € ^ RI Hp T! V f   \        4       MT 4      '       d   \        R4      hR# )a˜  Reject Kanban mutations from ``delegate_task`` child contexts.

The tool/CLI fast-fail guards are UX, not a trust boundary (a child can shell
out or import this module); the invariant lives here so every ``write_txn``
user and board-metadata mutator fails closed before touching durable state.
*path* is the board DB / metadata root being mutated; ``None`` means the
lineage's own board (``kanban_home()``).
)Úkanban_path_is_fencedNzAdelegate_task child contexts cannot mutate Kanban tasks or boards)Úagent.delegation_contextri   Úkanban_homeÚPermissionError)rf   ri   s   & r   Ú$_assert_not_delegated_child_mutationrm   Š   s,   € õ ?á¨dªlœ[œ]À×EÒEÜÐaÓbÐbñ Fr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Úeventr#   Útask_idÚfieldsr   r   rg   r   )r   s   "r   r   r   ™   s.   € ÷ 	Fñ 	F sð 	F°Sð 	FÀCð 	FÈDñ 	Fr   c                óœ   €  ^ RI Hp V! V 3RVR\        4       /VB  R#   \         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)zËBest-effort lifecycle hook. Call AFTER the write txn commits (plugins never
run under the SQLite write lock, always see durable state); failures are
swallowed so an observer can never break a transition.©Úinvoke_hookrp   Úprofile_namez#kanban lifecycle hook %s failed: %sN)Úhermes_cli.lifecyclert   Ú_hook_profile_namer,   Ú_logÚdebug)ro   rp   rq   rt   Úexcs   &&,  r   Ú_fire_kanban_lifecycle_hookr{   ™   sK   € ðFÝ4á�EÑX 7ÐXÔ9KÓ9MÐXÐQWÔXøÜô FÜ�
‰
Ð8¸%×EÒEûðFús   ‚ ŸAªAÁAc               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   ro   r#   ÚtaskúOptional[Task]rp   Úrun_idúOptional[int]rq   r   r   rg   r   )r   s   "r   r   r   ¥   s8   € ÷ ñ ˜3ð  nð ¸sð ÈMð Ðehð Ðmqñ r   c           	     ób   € \        W3R\        4       RV'       d   VP                  MRRV/VB  R# )zSLifecycle hook for a task transition; ``assignee`` from the (possibly missing) row.ÚboardÚassigneeNr   )r{   Úget_current_boardrƒ   )ro   r}   rp   r   rq   s   &&&&,r   Ú_fire_task_hookr…   ¥   s8   € äØñÜ/Ó1ðç"&�—’¨Dðà9?ðàCIôr   c               ó   € V ^8„  d   QhRR/# ©r   r   r#   r   )r   s   "r   r   r   ­   s   € ÷ ñ ˜Cñ r   c                 óB   € ^ RI Hp   V ! 4       #   \         d     R# i ; i)zKActive profile for hook payloads; ``"default"`` when it cannot be resolved.©Úget_active_profile_namer$   )Úhermes_cli.profilesrŠ   r,   r‰   s    r   rw   rw   ­   s%   € å;ðÙ&Ó(Ð(øÜô Úðús   ˆ ��c               ó    € V ^8„  d   QhRRRR/# )r   ro   r#   r   Úboolr   )r   s   "r   r   r   ·   s   € ÷ ñ  Sð ¨Tñ r   c                óD   €  ^ RI Hp V! V 4      #   \         d     R# i ; i)z•Hot-path short-circuit: skip payload assembly when nothing subscribes.
Inspection failure counts as unconsumed (dropping an observer is always safe).)Úhas_hookF)rv   r�   r,   )ro   r�   s   & r   Ú_kanban_observer_consumedr�   ·   s&   € ðÝ1á˜‹ÐøÜô Úðús   ‚ �žr‚   c               ó0   € V ^8„  d   QhRRRRRRRRR	R
RR/# )r   Úconnúsqlite3.Connectionr}   ÚTaskÚworkspace_pathr#   Úpidr€   r‚   rD   r   rg   r   )r   s   "r   r   r   Â   sF   € ÷ Añ AØ
ðAØ$(ðAØ:=ðAØDQðAàðAð 
ñAr   c               óZ  € \        R4      '       g   R#  \        RVP                  T;'       g    \        4       VP                  \        WP                  4      V'       d   \        V4      MR\        V4      R7       R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)zM``on_kanban_worker_spawned`` AFTER the PID is durably persisted; best-effort.Úon_kanban_worker_spawnedN)r‚   rƒ   r   Ú
worker_pidr•   z%kanban worker spawned hook failed: %s)r�   r{   Úidr„   rƒ   Ú_current_run_idr6   r#   r,   rx   ry   )r’   r}   r•   r–   r‚   rz   s   &&&&$ r   Ú_fire_worker_spawned_hookrœ   Â   s‚   € ô
 %Ð%?×@Ò@ÙðAÜ#Ø&¨¯©°u×7SÐ7SÔ@QÓ@SØ—]‘]¬?¸4ÇÁÓ+Iß#&”s˜3”x¨DÄÀ^ÓAT÷	
øô
 ô AÜ�
‰
Ð:¸C×@Ò@ûðAús   •A> ¯AA> Á>B*Â	B%Â%B*c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r’   r“   rp   r#   Úchanged_fieldsúIterable[str]r‚   rD   r   rg   r   )r   s   "r   r   r   Ó   s6   € ÷ ?ñ ?Ø
ð?Ø'*ð?Ø<Ið?àð?ð 
ñ?r   c          
     óh  € \        R4      '       g   R#  V P                  RV34      P                  4       p\        RY;'       g    \	        4       V'       d
   VR,          MRV'       d
   VR,          MR\        V4      R7       R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)z¾``on_kanban_task_updated`` AFTER a non-lifecycle task mutation commits
(also for direct-SQL surfaces like dashboard field editors).
``changed_fields`` carries field NAMES only, never values.Úon_kanban_task_updatedNz7SELECT assignee, current_run_id FROM tasks WHERE id = ?rƒ   Úcurrent_run_id)r‚   rƒ   r   rž   z#kanban task updated hook failed: %s)	r�   ÚexecuteÚfetchoner{   r„   Úlistr,   rx   ry   )r’   rp   rž   r‚   r!   rz   s   &&&$  r   Únotify_task_updatedr¦   Ó   s•   € ô %Ð%=×>Ò>Ùð
?Ø�l‰lØEÈÀzó
ç
‰(‹*ð 	ô 	$Ø$ g×5QÐ5QÔ>OÓ>Qß(+�S˜–_°ß,/�3Ð'Ö(°TÌ$È~ÓJ^÷	
øô
 ô ?Ü�
‰
Ð8¸#×>Ò>ûð?ús)   •/B ÁB ÁB Á)B ÂB1ÂB,Â,B1Údry_runFc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rO   ÚDispatchResultr‚   rD   r§   r�   r   rg   r   )r   s   "r   r   r   ò   s2   € ÷  @ñ  @Øð @Ø&3ð @ØEIð @à	ñ @r   c          	     óà  a € \        R4      '       g   R#  ^ RIHp \        4       pVf    \	        4       pRpS P                  '       d   RpMJ\        ;QJ d#    V 3R l\         4       F  '       g   K   RM	  RM! V 3R l\         4       4      '       g   R	pV! RW\        V4      VS R
7       R#   \
         d    Rp Lˆi ; i  \
         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)u  ``on_kanban_dispatch_tick`` â€” strictly AFTER ``_dispatch_tick_lock`` is
released so a slow subscriber cannot stall a sibling dispatcher.

Re-port of PR #56066 per the #64231 batch disposition: renamed to the taxonomy form and called by
``dispatch_once`` strictly AFTER ``_dispatch_tick_lock`` has been released â€” the original fired inside
the lock, so a slow subscriber could extend the single-writer critical section and stall a sibling
dispatcher's tick. Observer-only and fully best-effort: any subscriber failure is swallowed.
Úon_kanban_dispatch_tickNrs   ÚokÚskipped_lockedc              3  ó<   <"  € T F  p\        SV4      x € K  	  R # 5ir`   )Úgetattr)rb   ÚfrO   s   & €r   rc   Ú+_fire_dispatch_tick_hook.<locals>.<genexpr>  s   øé € ÐGÑ1F¨A”W˜V Q×'Ð'Ó1Fùs   ƒTFÚidle)r‚   ru   r§   ÚoutcomerO   z$kanban dispatch tick hook failed: %s)r�   rv   rt   rw   r„   r,   r­   ÚanyÚ_TICK_ACTIVITY_FIELDSr�   rx   ry   )rO   r‚   r§   rt   ru   r³   rz   s   f$$    r   Ú_fire_dispatch_tick_hookr¶   ò   sÊ   ø€ ô %Ð%>×?Ò?Ùð@Ý4ä)Ó+ˆØŠ=ðÜ)Ó+�ð ˆØ× × Ð Ø&‰Gß“ÔGÕ1FÓG——’ÔGÕ1FÓG×GÒGØˆGÙØ%¨UÜ˜“M¨7¸6÷	
øô ô Ø’ðûô ô @Ü�
‰
Ð9¸3×?Ò?ûð@úsQ   –C «
B. µC Á	C ÁC Á2!C ÂC Â.B>Â;C Â=B>Â>C ÃC-ÃC(Ã(C-c               ó    € V ^8„  d   QhRRRR/# )r   Úttl_secondsr€   r   r6   r   )r   s   "r   r   r   '  s   € ÷ ]ñ ]¨Mð ]ÀSñ ]r   c                óX   € V e   \        ^\        V 4      4      # \        R\        ^R7      # )zIExplicit ``ttl_seconds`` > ``HERMES_KANBAN_CLAIM_TTL_SECONDS`` > default.ÚHERMES_KANBAN_CLAIM_TTL_SECONDS)r3   )Úmaxr6   r?   ÚDEFAULT_CLAIM_TTL_SECONDS)r¸   s   &r   Ú_resolve_claim_ttl_secondsr½   '  s,   € àÒÜ�1”c˜+Ó&Ó'Ð'äÐ5Ô7PÐZ[Ô\Ð\r   c               ó   € V ^8„  d   QhRR/# ©r   r   r6   r   )r   s   "r   r   r   =  s   € ÷ Vñ V cñ Vr   c                 ó"   € \        R\        4      # )zN``HERMES_KANBAN_CRASH_GRACE_SECONDS`` (0 = immediate, for tests) else default.Ú!HERMES_KANBAN_CRASH_GRACE_SECONDS)r?   ÚDEFAULT_CRASH_GRACE_SECONDSr   r   r   Ú_resolve_crash_grace_secondsrÃ   =  s   € äÐ7Ô9TÓUÐUr   c               ó   € V ^8„  d   QhRR/# r¿   r   )r   s   "r   r   r   B  s   € ÷ fñ f¨cñ fr   c                 ó"   € \        R\        4      # )zV``HERMES_KANBAN_RATE_LIMIT_COOLDOWN_SECONDS`` (0 = next tick, for tests) else default.Ú)HERMES_KANBAN_RATE_LIMIT_COOLDOWN_SECONDS)r?   Ú#DEFAULT_RATE_LIMIT_COOLDOWN_SECONDSr   r   r   Ú$_resolve_rate_limit_cooldown_secondsrÈ   B  s   € äÐ?ÔAdÓeÐer   r$   Ú$hermes_kanban_current_board_override)r$   zContextVar[str | None]Ú_CURRENT_BOARD_OVERRIDEÚ(hermes_kanban_pin_first_board_resolutionzContextVar[bool]Ú_PIN_FIRST_BOARD_RESOLUTIONc               #  ó    "  € \         P                  R4      p  Rx € \         P                  V 4       R#   \         P                  T 4       i ; i5i)u¸  Resolve board paths env-pin-first for machine flows that enumerate boards.

The gateway notifier, per-subscription cursor writes and the embedded
dispatcher poll every board slug from ``list_boards()`` â€” the slug is not
caller intent, it is an iteration variable. On a box whose environment pins
``HERMES_KANBAN_DB`` (the dispatcher default) every slug must map to that
one pinned file: the notifier dedupes resolved DB paths, and per-slug
paths read empty boards nobody writes, silently killing wake
notifications. Explicit cross-board intent (CLI ``--board``, a model
tool's ``board=``) is a USER property and must never run inside this
context; with no pin set this context changes nothing.
TN)rÌ   ÚsetÚreset)Útokens    r   Úpin_first_board_resolutionrÑ   T  s<   é € ô (×+Ñ+¨DÓ1€Eð1Ûä#×)Ñ)¨%Ö0øÔ#×)Ñ)¨%Õ0üó   ‚A™4 �A´AÁAc               ó   € V ^8„  d   QhRR/# )r   Úslugr#   r   )r   s   "r   r   r   j  s   € ÷ -ñ -˜sñ -r   c              #  ó    "  € \         P                  V 4      p Rx € \         P                  V4       R#   \         P                  T4       i ; i5i)z2Pin the active board for the current context only.N)rÊ   rÎ   rÏ   )rÔ   rÐ   s   & r   Úscoped_current_boardrÖ   i  s<   é € ô  7×:Ñ:¸4Ó@€Eð-Ûä×%Ñ% eÖ,øÔ×%Ñ% eÕ,ürÒ   z^[a-z0-9][a-z0-9\-_]{0,63}$c               ó    € V ^8„  d   QhRRRR/# )r   rÔ   rD   r   r   )r   s   "r   r   r   x  s   € ÷ 
ñ 
 ð 
°-ñ 
r   c                óÊ   € V e(   \        V 4      P                  4       P                  4       MRpV'       g   R# \        P	                  V4      '       g   \        RV : R24      hV# )z>Lowercase + strip a slug; validate; return ``None`` for empty.Nr8   zinvalid board slug zc: must be 1-64 chars, lowercase alphanumerics / hyphens / underscores, not starting with '-' or '_')r#   r<   r[   Ú_BOARD_SLUG_REÚmatchr=   )rÔ   Úss   & r   Ú_normalize_board_slugrÜ   x  s`   € à%)Ò%5ŒˆD‹	�‰Ó×ÑÔ!¸2€AßÙÜ×Ñ ×"Ò"ÜØ! $¡ð *Rð Só
ð 	
ð €Hr   c               ó    € V ^8„  d   QhRRRR/# )r   r‚   rD   r   r#   r   )r   s   "r   r   r   …  s   € ÷ 9ñ 9˜Mð 9¨cñ 9r   c                ó4   € \        V 4      ;'       g    \        # r`   )rÜ   ÚDEFAULT_BOARD©r‚   s   &r   Ú_slug_or_defaultrá   …  s   € Ü  Ó'×8Ð8¬=Ð8r   c               ó    € V ^8„  d   QhRRRR/# )r   rÔ   r#   r   r   )r   s   "r   r   r   ‰  s   € ÷ ñ ˜ð  ñ r   c                óB   € \        V 4      pV'       g   \        R 4      hV# )zboard slug is required)rÜ   r=   )rÔ   Únormeds   & r   Ú_require_slugrå   ‰  s    € Ü" 4Ó(€FßÜÐ1Ó2Ð2Ø€Mr   c               ó   € V ^8„  d   QhRR/# ©r   r   r   r   )r   s   "r   r   r   �  s   € ÷ %ñ %�Tñ %r   c                 óº   € \         P                  P                  RR4      P                  4       p V '       d   \	        V 4      P                  4       # ^ RIHp V! 4       # )zÙ``HERMES_KANBAN_HOME`` else ``get_default_hermes_root()``. Shared across
profiles BY DESIGN: resolving through the active profile's HERMES_HOME would
fork the board per profile and break the dispatcher/worker handoff.ÚHERMES_KANBAN_HOMEr8   ©Úget_default_hermes_root)r9   r:   r;   r<   r   Ú
expanduserrZ   rë   )Úoverriderë   s     r   rk   rk   �  sD   € ô �z‰z�~‰~Ð2°BÓ7×=Ñ=Ó?€HßÜ�H‹~×(Ñ(Ó*Ð*Ý8Ù"Ó$Ð$r   c               ó   € V ^8„  d   QhRR/# rç   r   )r   s   "r   r   r   ›  s   € ÷ /ñ /�Tñ /r   c                 ó2   € \        4       R,          R,          # )u’   ``<root>/kanban/boards`` â€” parent of the *additional* named boards.
``default`` is deliberately not here (its DB stays at ``<root>/kanban.db``).ÚkanbanÚboards©rk   r   r   r   Úboards_rootró   ›  s   € ô ‹=˜8Õ# hÕ.Ð.r   c               ó   € V ^8„  d   QhRR/# rç   r   )r   s   "r   r   r   ¡  s   € ÷ 0ñ 0˜Dñ 0r   c                 ó2   € \        4       R,          R,          # )u_   ``<root>/kanban/current`` â€” one-line slug written by ``boards switch``; absent = ``default``.rð   Úcurrentrò   r   r   r   Úcurrent_board_pathr÷   ¡  s   € ä‹=˜8Õ# iÕ/Ð/r   c               ó   € V ^8„  d   QhRR/# r‡   r   )r   s   "r   r   r   ¦  s   € ÷ )ñ )˜3ñ )r   c                 óH  € R R lp \         P                  4       ;'       g    RP                  4       \        P                  P                  RR4      P                  4       3 F  pV ! V4      pV'       g   K  Vu # 	   \        4       pVP                  4       '       dV   VP                  RR7      P                  4       pV'       d.    \        V4      pV'       d   \        V4      '       d   V# \        # \        #   \         d	     \        # i ; i  \         d	     \        # i ; i)uê   Active slug: context override -> ``HERMES_KANBAN_BOARD`` -> ``<root>/kanban/current``
(only while that board exists) -> ``DEFAULT_BOARD``. A malformed/stale slug
falls through â€” the dispatcher must never crash on a hand-edited file.c               ó    € V ^8„  d   QhRRRR/# )r   Ú	candidater#   r   rD   r   )r   s   "r   r   Ú'get_current_board.<locals>.__annotate__ª  s   € ÷ ñ ˜Sð  ]ñ r   c                óÐ   € V '       g   R #  \        V 4      pT'       d   \        T4      '       g   R # \        T4      P	                  R4      '       d   R # T#   \         d     R # i ; i)NÚarchived)rÜ   r=   Úboard_existsÚread_board_metadatar;   )rû   rä   s   & r   Ú	_existingÚ$get_current_board.<locals>._existingª  s]   € ßÙð	Ü*¨9Ó5ˆF÷ œ<¨×/Ò/Ùô ˜vÓ&×*Ñ*¨:×6Ò6ÙØˆøô ô 	Úð	ús   ŒA ÁA%Á$A%r8   ÚHERMES_KANBAN_BOARDú	utf-8-sig©rI   )rÊ   r;   r<   r9   r:   r÷   ÚexistsÚ	read_textrÜ   rÿ   r=   ÚOSErrorrß   )r  rû   Úfoundr°   Úvalrä   s         r   r„   r„   ¦  sù   € õô" 
!×	$Ñ	$Ó	&×	,Ð	,¨"×3Ñ3Ó5Ü
�
‰
�‰Ð,¨bÓ1×7Ñ7Ó9óˆ	ñ ˜)Ó$ˆß‰5ØŠLñðÜÓ ˆØ�8‰8�:Š:à—+‘+ {�+Ó3×9Ñ9Ó;ˆCßðÜ2°3Ó7�Fß¤,¨v×"6Ò"6Ø%˜ô
 ÐŒ=Ðøô	 "ô Øô Ðð	ûäô ØÜÐðús1   Á=AD Ã%C8 Ã8DÄD Ä
DÄD ÄD!Ä D!c               ó    € V ^8„  d   QhRRRR/# )r   rÔ   r#   r   r   r   )r   s   "r   r   r   Ò  s   € ÷ ñ ˜Cð  Dñ r   c                ó²   € \        4        \        V 4      p\        4       pVP                  P	                  RRR7       VP                  VR,           RR7       V# )u”   Persist ``slug`` as the active board; returns the file written. Does NOT
check the board exists â€” callers do (so ``boards switch <typo>`` errors).T©ÚparentsÚexist_okÚ
r   r  )rm   rå   r÷   ÚparentÚmkdirÚ
write_text)rÔ   rä   rf   s   &  r   Úset_current_boardr  Ò  sL   € ô )Ô*Ü˜4Ó €FÜÓ€DØ‡K�K×Ñ˜d¨TÐÔ2Ø‡O�O�F˜T•M¨G€OÔ4Ø€Kr   c               ó   € V ^8„  d   QhRR/# ©r   r   rg   r   )r   s   "r   r   r   Ý  s   € ÷ &ñ &˜Tñ &r   c                 óÈ   € \        4        \        P                  ! \        4      ;_uu_ 4        \	        4       P                  4        RRR4       R#   + '       g   i     R# ; i)zLRemove ``<root>/kanban/current`` so the active board reverts to ``default``.N)rm   Ú
contextlibÚsuppressÚFileNotFoundErrorr÷   Úunlinkr   r   r   Úclear_current_boardr  Ý  s8   € ä(Ô*Ü	×	Ò	Ô.×	/Õ	/ÜÓ×#Ñ#Ô%÷ 
0×	/×	/Ò	/ús   ­AÁA!	c               ó    € V ^8„  d   QhRRRR/# ©r   r‚   rD   r   r   r   )r   s   "r   r   r   ä  s   € ÷ 3ñ 3�]ð 3¨dñ 3r   c                ó6   € \        4       \        V 4      ,          # )u¿   ``<root>/kanban/boards/<slug>/``. For ``default`` this holds metadata
only (board.json, workspaces/, logs/) â€” its DB stays at ``<root>/kanban.db``
for back-compat (:func:`kanban_db_path`).
)ró   rá   rà   s   &r   Ú	board_dirr   ä  s   € ô
 ‹=Ô+¨EÓ2Õ2Ð2r   c               ó    € V ^8„  d   QhRRRR/# )r   r‚   rD   r   r�   r   )r   s   "r   r   r   ì  s   € ÷ -ñ -˜ð -°ñ -r   c                óZ   € \        V 4      pV\        8X  d   R# \        \        V4      4      # )z>Board has a ``board.json`` on disk; ``default`` always exists.T)rá   rß   Ú_dir_holds_boardr   )r‚   rÔ   s   & r   rÿ   rÿ   ì  s&   € ä˜EÓ"€DØŒ}ÔÙÜœI d›OÓ,Ð,r   c               ó    € V ^8„  d   QhRRRR/# )r   r‚   rD   r   r   )r   s   "r   r   r   ö  s   € ÷ ñ  ð °-ñ r   c                óÈ   € V e   \        V 4      # \        P                  4       ;'       g    RP                  4       pV'       d    \        V4      # R#   \         d     R# i ; i)z¼Explicit caller intent: a direct ``board=`` argument, else the scoped
``--board`` context (CLI ``hermes kanban --board``, dashboard plugin_api);
``None`` when the caller expressed neither.Nr8   )rÜ   rÊ   r;   r<   r=   )r‚   Úctxs   & r   Ú_explicit_board_slugr'  ö  sb   € ð ÒÜ$ UÓ+Ð+ô
 #×&Ñ&Ó(×.Ð.¨B×
5Ñ
5Ó
7€Cß
ð	Ü(¨Ó-Ð-ñ øô ô 	Úð	ús   Á
A ÁA!Á A!c               ó   € V ^8„  d   QhRR/# ©r   r   r�   r   )r   s   "r   r   r   	  s   € ÷ -ñ - tñ -r   c                 óT   € \         P                  4       '       d   R# ^ RIHp  V ! 4       # )u‘  Whether an explicit ``board=`` (or scoped ``--board``) must still resolve
through the ``HERMES_KANBAN_DB``-style env pins instead of its own board dir.

True for machine flows wrapped in :func:`pin_first_board_resolution` and
for every execution the dispatcher fences: its own workers (they carry
``HERMES_KANBAN_TASK``) and delegated children / descendants (the
``HERMES_DELEGATED_CHILD_CONTEXT`` marker). The pins ARE the "workers
physically cannot see other boards" isolation (5ec6baa), and
``agent.delegation_context.kanban_path_is_fenced`` checks the pinned path /
fenced root â€” an explicit board that resolved elsewhere would also escape
that fence.T©Úexplicit_board_intent_is_pinned)rÌ   r;   rj   r,  r+  s    r   Ú_explicit_board_intent_pinnedr-  	  s"   € ô #×&Ñ&×(Ò(ÙÝHÙ*Ó,Ð,r   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   Úenv_varrD   r‚   Údefault_partsútuple[str, ...]Úleafr#   r   r   r   )r   s   "r   r   r     s4   € ÷ "ñ "Øð"Ø#0ð"ØAPð"ØX[ð"à	ñ"r   c                óx  € V '       d/   \         P                  P                  V R4      P                  4       MRp\	        V4      pV'       d.   Ve   \        4       '       d   \        V4      P                  4       # Vf   \        4       pV\        8X  d   \        4       P                  ! V!  # \        V4      V,          # )ut  Shared resolver. An explicit ``board=`` argument â€” or the scoped
``--board`` context (:func:`scoped_current_board`) â€” outranks the ``env_var``
pin ONLY where no fence applies (see :func:`_explicit_board_intent_pinned`):
user-facing cross-board intent (CLI ``--board``, a model tool's ``board=``)
is honored, but machine flows that enumerate boards (gateway notifier /
watcher / dispatcher ticks) and dispatched or delegated workers keep
resolving through the pin. Without explicit intent the ``env_var`` override
pins the file, else legacy ``<root>/<default_parts>`` for the ``default``
board, else ``board_dir(slug)/leaf``.r8   )r9   r:   r;   r<   r'  r-  r   rì   r„   rß   rk   Újoinpathr   )r/  r‚   r0  r2  ÚpinrÔ   s   &&&&  r   Ú_board_pathr6    s‰   € ÷ 29Œ"�*‰*�.‰.˜ "Ó
%×
+Ñ
+Ô
-¸b€CÜ Ó&€Dß
�’Ô =× ?Ò ?Ü�C‹y×#Ñ#Ó%Ð%Ø‚|Ü Ó"ˆØŒ}ÔÜ‹}×%Ò% }Ñ5Ð5Ü�T‹?˜TÕ!Ð!r   c               ó    € V ^8„  d   QhRRRR/# r  r   )r   s   "r   r   r   2  s   € ÷ Oñ O˜-ð O°4ñ Or   c                ó   € \        RV RR4      # )z�``kanban.db`` path: ``HERMES_KANBAN_DB`` pins it (injected into workers);
``default`` -> ``<root>/kanban.db`` (back-compat), else the board dir.ÚHERMES_KANBAN_DBú	kanban.db)r:  ©r6  rà   s   &r   Úkanban_db_pathr<  2  s   € ô Ð)¨5°.À+ÓNÐNr   c               ó    € V ^8„  d   QhRRRR/# r  r   )r   s   "r   r   r   8  s   € ÷ gñ g˜=ð g°Dñ gr   c                ó   € \        RV RR4      # )z†Per-board scratch workspace root (``HERMES_KANBAN_WORKSPACES_ROOT`` wins);
``default`` keeps the legacy ``<root>/kanban/workspaces/``.ÚHERMES_KANBAN_WORKSPACES_ROOTÚ
workspaces)rð   r@  r;  rà   s   &r   Úworkspaces_rootrA  8  s   € ô Ð6¸Ð?WÐYeÓfÐfr   c               ó    € V ^8„  d   QhRRRR/# r  r   )r   s   "r   r   r   >  s   € ÷ jñ j˜Mð j°Tñ jr   c                ó   € \        RV RR4      # )z›Per-board attachments root (``HERMES_KANBAN_ATTACHMENTS_ROOT`` wins). Workers
read attachments by absolute path, so remote terminal backends must mount it.ÚHERMES_KANBAN_ATTACHMENTS_ROOTÚattachments)rð   rE  r;  rà   s   &r   Úattachments_rootrF  >  s   € ô Ð7¸Ð@YÐ[hÓiÐir   c               ó$   € V ^8„  d   QhRRRRRR/# ©r   rp   r#   r‚   rD   r   r   r   )r   s   "r   r   r   D  s!   € ÷ 3ñ 3 #ð 3¨mð 3Àtñ 3r   c                ó(   € \        VR7      V ,          # )z?Return the per-task attachment directory ``<root>/<task_id>/``.rà   )rF  ©rp   r‚   s   &&r   Útask_attachments_dirrK  D  s   € ä %Ô(¨7Õ2Ð2r   c               ó    € V ^8„  d   QhRRRR/# r  r   )r   s   "r   r   r   I  s   € ÷ @ñ @˜=ð @°Dñ @r   c                ó   € \        RV RR4      # )zyPer-board worker log dir (logs follow the board so ``hermes kanban log``
is unambiguous when two boards share a task id).NÚlogs)rð   rN  r;  rà   s   &r   Úworker_logs_dirrO  I  s   € ô �t˜UÐ$6¸Ó?Ð?r   c                  ó
  € ] tR tRt$ RtR]R&   R]R&   R]R&   R]R&   R]R	&   R
]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   RtR]R&   RtR]R&   RtR]R&   Rt	R]R&   ^ t
R
]R&   RtR]R&   RtR]R&   RtR]R&   RtR]R&   RtR]R &   RtR]R!&   RtR]R"&   RtR#]R$&   RtR]R%&   RtR]R&&   RtR]R'&   RtR]R(&   R)tR*]R+&   RtR]R,&   RtR]R-&   RtR]R.&   ^ tR
]R/&   RtR]R0&   ]R1 R2 l4       tR3tR# )4r”   iQ  z1In-memory view of a row from the ``tasks`` table.r#   rš   ÚtitlerD   Úbodyrƒ   Ústatusr6   ÚpriorityÚ
created_byr€   Ú
created_atÚ
started_atÚcompleted_atÚworkspace_kindr•   Ú
claim_lockÚclaim_expiresÚtenantNÚbranch_nameÚ
project_idrO   Úidempotency_keyÚconsecutive_failuresr™   Úlast_failure_errorÚmax_runtime_secondsÚlast_heartbeat_atr¢   Úworkflow_template_idÚcurrent_step_keyzOptional[list]ÚskillsÚmodel_overrideÚprovider_overrideÚreasoning_effortÚmax_retriesFr�   Ú	goal_modeÚgoal_max_turnsÚ
session_idÚ
block_kindÚblock_recurrencesÚcompletion_contractc               ó    € V ^8„  d   QhRRRR/# )r   r!   úsqlite3.Rowr   r”   r   )r   s   "r   r   ÚTask.__annotate__‚  s   € ÷ 
ñ 
˜;ð 
¨4ñ 
r   c                	ó¤  a€ R	V3R llp\        V! R4      4      p\        V\        4      '       d%   V Uu. uF  qD'       g   K  \        V4      NK  	  upMR pT ! R
/ \         Uu/ uF  qf\        SV,          4      bK  	  upB\         Uu/ uF  qf\        V! V4      4      bK  	  upB\         Uu/ uF  qfV! V4      bK  	  upB\         Uu/ uF  qfV! V4      ;'       g    R bK  	  upBRV! RV! R^ 4      4      RV! RV! R4      4      RTR\        V! R4      4      R\        V! R4      ;'       g    ^ 4      /B # u upi u upi u upi u upi u upi )Nc                ó.   <€ \        \        SW4      4      # r`   )r   r'   )r"   r$   r!   s   &&€r   Ú<lambda>ÚTask.from_row.<locals>.<lambda>ƒ  s   ø€ ¤k´(¸3ÀÓ2MÔ&Nr   rf  r`  Úspawn_failuresra  Úlast_spawn_errorrk  ro  r`   r   )r-   r   r¥   r#   Ú_TASK_REQUIRED_COLUMNSr   Ú_TASK_EPOCH_COLUMNSÚ	_to_epochÚ_TASK_OPTIONAL_COLUMNSÚ_TASK_EMPTY_IS_NULL_COLUMNSr�   r6   )Úclsr!   Úgr1   rÛ   Úskills_valuer"   s   &f     r   Úfrom_rowÚTask.from_row�  s_  ø€ æNˆÜ™!˜H›+Ó&ˆÜ8BÀ6Ì4×8PÒ8P©Ó4© 1³!œœ˜Až©Ò4ÐVZˆÙñ 
Ý5KÓLÑ5K¨c”K  C¥Ó)Ò)Ñ5KÑLð
å1DÓEÑ1D¨#”I™a ›fÓ%Ò%Ñ1DÑEð
õ '=Ó=Ñ&<˜s‘A�c“FŠ{Ñ&<Ñ=ð
õ /JÓJÑ.I s‘A�c“F—N�N˜dÒ"Ñ.IÑJñ	
ñ "#Ð#9¹1Ð=MÈqÓ;QÓ!Rð
ñ  !Ð!5±qÐ9KÓ7LÓMð
ð  ð
ô ™1˜[›>Ó*ð
ô "¡!Ð$7Ó"8×"=Ð"=¸AÓ>ò
ð 	
ùò 5ùâLùÚEùÚ=ùÚJs)   µ	D9ÁD9Á#D>Â	EÂ.EÃ
EÃEr   ) Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__annotations__r]  r^  rO   r_  r`  r™   ra  rb  rc  r¢   rd  re  rf  rg  rh  ri  rj  rk  rl  rm  rn  ro  rp  Úclassmethodr‚  Ú__static_attributes__r   r   r   r”   r”   Q  s_  ‡ á;àƒGØƒJØ
ÓØÓØƒKØƒMØÓØÓØÓØÓØÓØ!Ó!ØÓØ Ó ØÓØ!%€K�Ó%Ø $€J�Ó$Ø €FˆMÓ Ø%)€O�]Ó)à !Ð˜#Ó!Ø $€J�Ó$Ø(,Ð˜Ó,Ø)-Ð˜Ó-Ø'+Ð�}Ó+Ø$(€N�MÓ(Ø*.Ð˜-Ó.Ø&*Ð�mÓ*Ø!€FˆNÓ!Ø$(€N�MÓ(Ø'+Ð�}Ó+Ø&*Ð�mÓ*à!%€K�Ó%ð €IˆtÓØ$(€N�MÓ(Ø $€J�Ó$à $€J�Ó$ØÐ�sÓØ)-Ð˜Ó-àô
ó ô
r   r”   rQ  rR  rƒ   rS  rT  rU  rY  r•   rV  r]  r^  r\  rO   r_  rb  rd  re  rj  rm  rp  rg  rh  ri  rl  c                  óÒ   € ] tR tRt$ RtR]R&   R]R&   R]R&   R]R	&   R]R
&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   R]R&   ]R R l4       tRtR# )ÚRuni§  zˆOne attempt at a task (``task_runs`` row): opened on claim, closed on
complete/block/crash/timeout/reclaim; carries the handoff summary.r6   rš   r#   rp   rD   ÚprofileÚstep_keyrS  rZ  r€   r[  r™   rb  rc  rW  Úended_atr³   ÚsummaryúOptional[dict]ÚmetadataÚerrorc               ó    € V ^8„  d   QhRRRR/# )r   r!   rr  r   r�  r   )r   s   "r   r   ÚRun.__annotate__¾  s   € ÷ 
ñ 
˜;ð 
¨3ñ 
r   c                	ó<  € T ! R/ R Uu/ uF  q"\        W,          4      bK  	  upBR Uu/ uF  q"\        W,          4      bK  	  upBR\        VR,          4      R\        VR,          4      ;'       g    ^ R\        \        VR,          4      4      /B # u upi u upi )rp   rš   rW  r“  r   )
rp   rŽ  r�  rS  rZ  r™   rb  r³   r‘  r”  )r[  rc  r�  )r   r|  r6   r-   )r  r!   r"   s   && r   r‚  ÚRun.from_row½  s³   € áñ 

ñ7óñ7¨s”[ ¥Ó*Ò*ñ 7ñð

ñ 4fÓfÑ3e¨C”I˜c�hÓ'Ò'Ñ3eÑfñ

ô �3�t•9‹~ð

ô +4°C¸Õ4EÓ*F×*KÐ*KÈ!ð

ô œk¨#¨j­/Ó:Ó;ò

ð 
	
ùòùò gs
   ‰BªBr   N©	r„  r…  r†  r‡  rˆ  r‰  rŠ  r‚  r‹  r   r   r   r�  r�  §  sx   ‡ ñJð 	ƒGØƒLØÓØÓØƒKØÓØ Ó ØÓØ&Ó&Ø$Ó$ØƒOØÓØÓØÓØÓØÓàô
ó ô
r   r�  c                  ó`   € ] tR tRt$ R]R&   R]R&   R]R&   R]R&   R]R&   ]R	 R
 l4       tRtR# )ÚCommentiÌ  r6   rš   r#   rp   ÚauthorrR  rV  c               ó    € V ^8„  d   QhRRRR/# )r   Úrrr  r   r›  r   )r   s   "r   r   ÚComment.__annotate__Õ  s   € ÷ 
ñ 
˜ð 
¨ñ 
r   c           
     	ó¬   € T ! VR ,          VR,          \        VR,          4      \        VR,          4      \        VR,          4      ;'       g    ^ R7      # )rš   rp   rœ  rR  rV  )rš   rp   rœ  rR  rV  )r   r|  ©r  rž  s   &&r   r‚  ÚComment.from_rowÔ  sG   € áØ��w  )¥´[ÀÀ8ÅÓ5MÜ˜Q˜v�YÓ'´I¸aÀ½oÓ4N×4SÐ4SÐRSô
ð 	
r   r   N)r„  r…  r†  r‡  r‰  rŠ  r‚  r‹  r   r   r   r›  r›  Ì  s-   ‡ àƒGØƒLØƒKØ
ƒIØƒOàô
ó ô
r   r›  c                  ó‚   € ] tR tRt$ RtR]R&   R]R&   R]R&   R]R&   R	]R
&   R]R&   R	]R&   R]R&   ]R R l4       tRtR# )Ú
AttachmentiÜ  z<In-memory view of a row from the ``task_attachments`` table.r6   rš   r#   rp   ÚfilenameÚstored_pathrD   Úcontent_typeÚsizeÚuploaded_byrV  c               ó    € V ^8„  d   QhRRRR/# )r   rž  rr  r   r¤  r   )r   s   "r   r   ÚAttachment.__annotate__ê  s   € ÷ 
ñ 
˜ð 
¨ñ 
r   c                	óÌ   € T ! VR ,          VR,          VR,          VR,          VR,          VR,          ;'       g    ^ VR,          \        VR,          4      ;'       g    ^ R7      # )	rš   rp   r¥  r¦  r§  r¨  r©  rV  )rš   rp   r¥  r¦  r§  r¨  r©  rV  )r|  r¡  s   &&r   r‚  ÚAttachment.from_rowé  s\   € áØ��w  )¥°q¸µ}Ø˜-Õ(°q¸Õ7HØ�6•—�˜a¨Q¨}Õ-=Ì)ÐTUÐVbÕTcÓJd×JiÐJiÐhiô
ð 	
r   r   Nr™  r   r   r   r¤  r¤  Ü  sB   ‡ áFàƒGØƒLØƒMØÓØÓØ
ƒIØÓØƒOàô
ó ô
r   r¤  c                  ón   € ] tR tRt$ R]R&   R]R&   R]R&   R]R&   R]R	&   R
tR]R&   ]R R l4       tRtR
# )ÚEventiò  r6   rš   r#   rp   Úkindr’  ÚpayloadrV  Nr€   r   c               ó    € V ^8„  d   QhRRRR/# )r   r!   rr  r   r¯  r   )r   s   "r   r   ÚEvent.__annotate__ü  s   € ÷ 
ñ 
˜;ð 
¨5ñ 
r   c                	óê   € \        VR 4      pT ! VR,          VR,          \        VR,          4      \        \        VR,          4      4      \        VR,          4      ;'       g    ^ \	        V4      R7      # )r   rš   rp   r°  r±  rV  )rš   rp   r°  r±  rV  r   )r'   r   r-   r|  Ú_opt_int)r  r!   r   s   && r   r‚  ÚEvent.from_rowû  sc   € ä˜#˜xÓ(ˆÙØ�4�y # i¥.´{À3ÀvÅ;Ó7OÜœ[¨¨Y­Ó8Ó9ÄiÐPSÐT`ÕPaÓFb×FgÐFgÐfgÜ˜FÓ#ô
ð 	
r   r   )	r„  r…  r†  r‡  r‰  r   rŠ  r‚  r‹  r   r   r   r¯  r¯  ò  s8   ‡ àƒGØƒLØ
ƒIØÓØƒOØ €FˆMÓ àô
ó ô
r   r¯  uï'  
CREATE TABLE IF NOT EXISTS tasks (
    id                   TEXT PRIMARY KEY,
    title                TEXT NOT NULL,
    body                 TEXT,
    assignee             TEXT,
    status               TEXT NOT NULL,
    priority             INTEGER DEFAULT 0,
    created_by           TEXT,
    created_at           INTEGER NOT NULL,
    started_at           INTEGER,
    completed_at         INTEGER,
    workspace_kind       TEXT NOT NULL DEFAULT 'scratch',
    workspace_path       TEXT,
    branch_name          TEXT,
    -- Optional link to a first-class Project (hermes_cli/projects_db). When set,
    -- the task's worktree is anchored under the project's primary repo with a
    -- deterministic branch name instead of a random wt/<task-id> fallback.
    project_id           TEXT,
    claim_lock           TEXT,
    claim_expires        INTEGER,
    tenant               TEXT,
    result               TEXT,
    idempotency_key      TEXT,
    -- Unified consecutive-failure counter. Incremented on spawn
    -- failure, timeout, or crash; reset only on successful completion.
    -- The circuit breaker in _record_task_failure trips when this
    -- exceeds DEFAULT_FAILURE_LIMIT consecutive non-successes.
    consecutive_failures INTEGER NOT NULL DEFAULT 0,
    worker_pid           INTEGER,
    -- Restart-stable fingerprint of worker_pid ("<boot/instantiation epoch>|<start time>",
    -- kanban_db_dispatch._process_fingerprint) recorded at spawn: liveness and kills require pid
    -- AND fingerprint to agree, so a PID recycled after a reboot is never read as our worker or
    -- signalled. NULL = legacy row (pre-fingerprint spawn); 'unverified' = capture failed at
    -- spawn (held while live, never signalled). Column keeps its INTEGER affinity for the
    -- start-time-only integer values older rows carry.
    worker_started_at    INTEGER,
    -- Short excerpt of the most recent failure's error text.
    last_failure_error   TEXT,
    max_runtime_seconds  INTEGER,
    last_heartbeat_at    INTEGER,
    -- Pointer into task_runs for the currently-active run (NULL if no
    -- run is in-flight). Denormalised for cheap reads.
    current_run_id       INTEGER,
    -- Forward-compat for v2 workflow routing. In v1 the kernel writes
    -- these when the task is opted into a template but otherwise ignores
    -- them; the dispatcher doesn't consult them for routing yet.
    workflow_template_id TEXT,
    current_step_key     TEXT,
    -- Force-loaded skills for the worker on this task, stored as JSON.
    -- Passed to the worker via `--skills`. NULL or empty array = no extras.
    skills               TEXT,
    -- Per-task model override. When set, the dispatcher passes -m <model>
    -- to the worker, overriding the profile's default model. NULL = use
    -- the profile default.
    model_override       TEXT,
    -- Provider the model override belongs to. When set (alongside
    -- model_override), the dispatcher passes --provider <name> so the
    -- worker resolves the model against the right backend instead of the
    -- profile's configured provider. NULL = profile provider.
    provider_override    TEXT,
    -- Per-task reasoning effort for the worker (minimal|low|medium|high|
    -- xhigh|max|ultra, or 'none' for thinking off). When set, the dispatcher
    -- passes --reasoning <level> so the worker runs at that depth regardless
    -- of the profile's agent.reasoning_effort. NULL = profile setting.
    reasoning_effort     TEXT,
    -- Per-task override for the consecutive-failure circuit breaker.
    -- The value is the failure count at which the breaker trips â€” e.g.
    -- ``max_retries=1`` blocks on the first failure. NULL (the common
    -- case) falls through to the dispatcher-level ``kanban.failure_limit``
    -- config and then ``DEFAULT_FAILURE_LIMIT``.
    max_retries          INTEGER,
    -- When 1, the dispatched worker runs in a Ralph-style goal loop: an
    -- auxiliary judge re-evaluates the worker's response against the
    -- card title/body after each turn and feeds a continuation prompt
    -- back into the SAME session until the judge agrees the work is done
    -- or ``goal_max_turns`` is exhausted. NULL/0 = classic single-shot
    -- worker (the default).
    goal_mode            INTEGER NOT NULL DEFAULT 0,
    -- Goal-loop turn budget for ``goal_mode`` workers. NULL = use the
    -- goals-engine default.
    goal_max_turns       INTEGER,
    -- Originating chat/agent session id when the task was created from
    -- inside an agent loop that propagated ``HERMES_SESSION_ID``. NULL
    -- for tasks created from the CLI, dashboard, or any path that doesn't
    -- set the env var, and for an id with no ``sessions`` row in this
    -- profile's state.db (kanban_create verifies before stamping). Indexed
    -- so per-session list queries stay cheap on larger boards.
    session_id           TEXT,
    -- Typed block reason set by ``block_task`` (one of VALID_BLOCK_KINDS, or
    -- NULL for legacy/un-typed blocks). Drives routing: ``dependency`` never
    -- sits in ``blocked`` (goes to ``todo`` for parent-gating); the others go
    -- to ``blocked`` for a human. Preserved across unblock so a re-block for
    -- the SAME kind can be recognised as a loop.
    block_kind           TEXT,
    -- Unblock-loop counter. Incremented each time a task is re-blocked for the
    -- same truly-blocked reason after having been unblocked. When it reaches
    -- BLOCK_RECURRENCE_LIMIT the task is routed to ``triage`` instead of
    -- ``blocked`` so a cron can't spin it forever. Reset to 0 only on a
    -- successful completion â€” NOT on unblock (resetting on unblock is exactly
    -- the amnesia that let the loop run unbounded).
    block_recurrences    INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS task_links (
    parent_id  TEXT NOT NULL,
    child_id   TEXT NOT NULL,
    PRIMARY KEY (parent_id, child_id)
);

CREATE TABLE IF NOT EXISTS 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
);

CREATE TABLE IF NOT EXISTS 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
);

-- Historical attempt record. Each time the dispatcher claims a task, a
-- new row is created here; claim state, PID, heartbeat, runtime cap,
-- and structured summary all live on the run, not the task. Multiple
-- rows per task id when the task was retried after crash/timeout/block.
-- v2 of the kanban schema will use ``step_key`` to drive per-stage
-- workflow routing; in v1 the column is nullable and unused (kernel
-- ignores it).
CREATE TABLE IF NOT EXISTS task_runs (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id             TEXT NOT NULL,
    profile             TEXT,
    step_key            TEXT,
    status              TEXT NOT NULL,
    -- status: running | done | blocked | crashed | timed_out | failed | released
    claim_lock          TEXT,
    claim_expires       INTEGER,
    worker_pid          INTEGER,
    -- Spawn-time start fingerprint of worker_pid (see tasks.worker_started_at). Retained with
    -- worker_pid after the run ends so a worker that outlives its terminal transition can
    -- still be found and reaped; NULL = legacy row, never signalled.
    worker_started_at   INTEGER,
    max_runtime_seconds INTEGER,
    last_heartbeat_at   INTEGER,
    started_at          INTEGER NOT NULL,
    ended_at            INTEGER,
    outcome             TEXT,
    -- outcome: completed | blocked | crashed | timed_out | spawn_failed |
    --          gave_up | reclaimed | (null while still running)
    summary             TEXT,
    metadata            TEXT,
    error               TEXT
);

-- Files attached to a task (PDFs, images, source documents). The blob
-- lives on disk under ``attachments_root(board)/<task_id>/<stored_name>``;
-- this row carries metadata + the absolute ``stored_path`` so the
-- dashboard can list/download and ``build_worker_context`` can surface
-- the absolute path to the worker (which has full file-tool access). See
-- #35338.
CREATE TABLE IF NOT EXISTS task_attachments (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id      TEXT NOT NULL,
    filename     TEXT NOT NULL,
    stored_path  TEXT NOT NULL,
    content_type TEXT,
    size         INTEGER NOT NULL DEFAULT 0,
    uploaded_by  TEXT,
    created_at   INTEGER NOT NULL
);

-- Subscription from a gateway source (platform + chat + thread) to a
-- task. The gateway's kanban-notifier watcher tails task_events and
-- pushes ``completed`` / ``blocked`` / ``spawn_auto_blocked`` events to
-- the original requester so human-in-the-loop workflows close the loop.
CREATE TABLE IF NOT EXISTS 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)
);

CREATE INDEX IF NOT EXISTS idx_tasks_status          ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_links_child           ON task_links(child_id);
CREATE INDEX IF NOT EXISTS idx_links_parent          ON task_links(parent_id);
CREATE INDEX IF NOT EXISTS idx_comments_task         ON task_comments(task_id, created_at);
CREATE INDEX IF NOT EXISTS idx_events_task           ON task_events(task_id, created_at);
CREATE INDEX IF NOT EXISTS idx_runs_task             ON task_runs(task_id, started_at);
CREATE INDEX IF NOT EXISTS idx_runs_status           ON task_runs(status);
CREATE INDEX IF NOT EXISTS idx_attachments_task      ON task_attachments(task_id, created_at);
CREATE INDEX IF NOT EXISTS idx_notify_task           ON kanban_notify_subs(task_id);
c               ó   € V ^8„  d   QhRR/# r‡   r   )r   s   "r   r   r   Û  s   € ÷ 'ñ '�cñ 'r   c                 ó<   € R\         P                  ! ^4      ,           # )z’``t_`` + 4 hex bytes (collision ~1e-3 at 100k tasks; 2 bytes would hit 50%
by 10k). Idempotency belongs to ``idempotency_key``, not id uniqueness.Út_)ÚsecretsÚ	token_hexr   r   r   Ú_new_task_idr¼  Û  s   € ð ”'×#Ò# AÓ&Õ&Ð&r   c               ó   € V ^8„  d   QhRR/# r‡   r   )r   s   "r   r   r   á  s   € ÷ #ñ #�Sñ #r   c                 óš   € ^ RI p  V P                  4       ;'       g    RpV R\        P                  ! 4        2#   \         d    Rp L)i ; i)z:Return a ``host:pid`` string that identifies this claimer.NÚunknownÚ:)ÚsocketÚgethostnamer,   r9   Úgetpid)rÁ  Úhosts     r   Ú_claimer_idrÅ  á  sO   € ãðØ×!Ñ!Ó#×0Ð0 yˆð ˆV�1”R—Y’Y“[�MÐ"Ð"øô ô ØŠðús   †: �: ºA
Á	A
c               ó   € V ^8„  d   QhRR/# r‡   r   )r   s   "r   r   r   ë  s   € ÷ 0ñ 0�cñ 0r   c                 óJ   € \        4       P                  R^4      ^ ,           R2# )zF``"<host>:"`` prefix shared by every claim lock issued from this host.rÀ  )rÅ  Úsplitr   r   r   Ú_host_prefixrÉ  ë  s$   € ä‹m×!Ñ! # qÓ)¨!Õ,Ð-¨QÐ/Ð/r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚmodelrD   Úproviderr   z#tuple[Optional[str], Optional[str]]r   )r   s   "r   r   r   ò  s"   € ÷ ñ  Mð ¸]ð ÐOrñ r   c                óÎ   € T ;'       g    RP                  4       ;'       g    Rp T;'       g    RP                  4       ;'       g    RpV'       d   V '       g   \        R4      hW3# )u½   Strip both; a provider without a model is rejected (a bare ``--provider``
would re-resolve the profile's model against another backend â€” exactly
the mismatch the override exists to kill).r8   Nz+provider_override requires a model_override)r<   r=   )rË  rÌ  s   &&r   Ú_validate_model_overriderÎ  ò  sU   € ð �[ˆ[�b×ÑÓ!×)Ð) T€EØ—�˜B×%Ñ%Ó'×/Ð/¨4€HßŸÜÐFÓGÐGØˆ?Ðr   c               ó    € V ^8„  d   QhRRRR/# )r   rƒ   rD   r   r   )r   s   "r   r   r   ý  s   € ÷ ,ñ , -ð ,°Mñ ,r   c                ó*   € V f   R# ^ RI Hp V! V 4      # )zHLowercase-assignee normalization for Kanban rows (dashboard/CLI parity).N)Únormalize_profile_name)r‹   rÑ  )rƒ   rÑ  s   & r   Ú_canonical_assigneerÒ  ý  s   € àÒÙÝ:á! (Ó+Ð+r   c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r   r’   r“   r^  rD   Úproject_source_task_idrY  r#   r•   r   z-tuple[Optional[str], Any, Optional[str], str]r   )r   s   "r   r   r     sF   € ÷ )Eñ )EØ
ð)EØ*7ð)EØQ^ð)Eàð)Eà)6ð)Eð 3ñ)Er   c                ó`  € Ve$   \        V4      P                  4       ;'       g    RMRpV'       g   RRRV3# ^ RIHp Rp VP	                  4       ;_uu_ 4       pVP                  Wq4      pRRR4       Xf.   T'       d&   \        YT\        T4      4      w  r†Te
   TR8X  d   RpTf   RRRT3# TR8X  d   TP                  '       d   RpTR8X  d,   Tf(   TP                  '       d   \        TP                  4      pTP                  Y†T3#   + '       g   i     L§; i  \         d    Rp L¹i ; i)aÃ  ``(project_id, project_obj, project_repo, workspace_kind)`` for ``create_task``.

A project-linked task is anchored to the project's primary repo as a
worktree with a deterministic branch (slug + task id). Projects live in the
creator's per-profile projects.db, but the stored repo path is absolute so
the cross-profile dispatcher needs no projects.db access. ``project_repo``
is set when the worktree path must still be derived from the new task id.
N©Úprojects_dbÚscratchÚworktree)
r#   r<   Ú
hermes_clir×  Úconnect_closingÚget_projectr,   Ú_project_from_source_taskÚprimary_pathrš   )	r’   r^  rÔ  rY  r•   Ú_pdbÚproject_repoÚ_pconnÚproject_objs	   &&&&&    r   Ú_resolve_project_linkrã    s.  € ð 7AÒ6L”#�j“/×'Ñ'Ó)×1Ð1¨TøÐRV€JßØ�T˜4 Ð/Ð/Ý.à"&€LðØ×!Ñ!×#Ô# vØ×*Ñ*¨6Ó>ˆK÷ $ð Ò×5Ü$=Ø˜
¤CÐ(>Ó$?ó%
Ñ!ˆð Ò" ~¸Ô'BØ'ˆNØÒð �T˜4 Ð/Ð/ð ˜Ô" {×'?×'?Ð'?Ø#ˆØ˜Ô#¨Ò(>À;×C[×C[ÐC[ô ˜;×3Ñ3Ó4ˆØ�>‰>˜;°nÐDÐD÷- $×#ûäô ØŠðús0   ÁD ÁD
Á+D Ä
D	ÄD ÄD ÄD-Ä,D-c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rß  r   r^  r#   Úsource_task_idr   ztuple[Any, Optional[str]]r   )r   s   "r   r   r   2  s4   € ÷ '%ñ '%Ø
ð'%Ø$'ð'%Ø58ð'%ØJMð'%àñ'%r   c                óô  € \        W4      pVe5   VP                  V8X  d$   VP                  R8X  d   VP                  '       g   R# \	        VP                  4      pVP                  4       '       d7   VP                  VP                  8X  d   VP                  P                  R8X  g   R# RpVP                  '       d—   VP                  P                  R4      w  rxp	V'       dq   W”P                  8X  g%   V	P                  VP                   R24      '       d=   \        P                  ! \        4      ;_uu_ 4        VP                  V4      pRRR4       Vf=   \        P                  ! \        4      ;_uu_ 4        VP                  V4      pRRR4       V'       g   R# \!        VP                  P                  4      p
VP#                  W&V^ V
R7      pWº3#   + '       g   i     L‘; i  + '       g   i     Ld; i)a™  Recover a Project (and its repo) from a canonical project-linked
worktree task on this board. Worker profiles have their own projects.db
while the Kanban DB is shared, so this carries the repo + branch
convention forward without opening the creator's store and without
reusing the source task's literal worktree path. ``(None, None)`` when
the source task is not a ``<repo>/.worktrees/<id>`` project worktree.NrÙ  ú
.worktreesÚ/Ú-)rš   rÔ   r5   rV  rÞ  )NN)Úget_taskr^  rY  r•   r   Úis_absoluter5   rš   r  r]  Ú	partitionÚ
startswithr  r  r=   Únormalize_slugr#   ÚProject)r’   rß  r^  rå  Úsource_taskÚsource_pathÚproject_slugÚprefixÚ	separatorr2  rà  râ  s   &&&&        r   rÝ  rÝ  2  s‹  € ô ˜4Ó0€KàÒØ×"Ñ" jÔ0Ø×&Ñ&¨*Ô4Ø×&×&Ð&àÐÜ�{×1Ñ1Ó2€Kà×Ñ×!Ò!Ø×Ñ §¡Ô.Ø×Ñ×#Ñ# |Ô3àÐØ€LØ××ÐØ"-×"9Ñ"9×"CÑ"CÀCÓ"HÑˆ˜4ß˜$§.¡.Ô0°D·O±OÀ{Ç~Á~ÐFVÐVWÐDX×4YÒ4YÜ×$Ò$¤Z×0Õ0Ø#×2Ñ2°6Ó:�÷ 1àÒÜ× Ò ¤×,Õ,Ø×.Ñ.¨zÓ:ˆL÷ -çØÐÜ�{×)Ñ)×0Ñ0Ó1€LØ—,‘,Ø¨|ÈÐXdð ó €Kð Ð$Ð$÷ 1×0ú÷ -×,ús   Ä9GÅ9G'ÇG$	Ç'G7	c               ó    € V ^8„  d   QhRRRR/# )r   rf  úOptional[Iterable[str]]r   zOptional[list[str]]r   )r   s   "r   r   r   \  s   € ÷ %ñ %Ð#:ð %Ð?Rñ %r   c                óþ  € V f   R# . p\        4       p. pV  F�  pV'       g   K  \        V4      P                  4       pV'       g   K0  RV9   d   \        RV: R24      hVP	                  4       \
        9   d   VP                  V4       Ks  WR9   d   K{  VP                  V4       VP                  V4       KŸ  	  V'       d>   RP                  R V 4       4      p\        V4      ^8X  d   RMRp\        V R	V R
24      hV# )zÇStrip/dedupe a skills list. Commas are refused (a comma-joined string must
not land in one argv slot); toolset names are rejected all at once because
agents that confuse the two usually pass several.NÚ,z!skill name cannot contain comma: zA (pass a list of separate names instead of a comma-joined string)rX   c              3  ó8   "  € T F  p\        V4      x € K  	  R # 5ir`   )Úrepr)rb   Úns   & r   rc   Ú)_normalize_task_skills.<locals>.<genexpr>x  s   é € Ð:©M qœ4 Ÿ7˜7«Mùó   ‚zis a toolset namezare toolset namesÚ zü, not skill name(s). Put toolsets in the assignee profile's `toolsets:` config instead of per-task skills. Skills are named skill bundles (e.g. `blogwatcher`, `github-code-review`); toolsets are runtime capabilities (e.g. `web`, `browser`, `terminal`).)
rÎ   r#   r<   r=   ra   ÚKNOWN_TOOLSET_NAMESÚappendÚaddr\   Úlen)rf  ÚcleanedÚseenÚtoolset_typosrÛ   r5   ÚquotedÚnouns   &       r   Ú_normalize_task_skillsr  \  s  € ð ‚~ÙØ€GÜ“U€DØ!€MÛˆßÙÜ�1‹v�|‰|‹~ˆßÙØ�$Œ;ÜØ3°D±8ð <Sð Tóð ð �=‰=‹?Ô1Ô1Ø× Ñ  Ô&ÙØŒ<ÙØ�‰�ŒØ�‰�tÖñ# ÷$ Ø—‘Ñ:©MÓ:Ó:ˆÜ&)¨-Ó&8¸AÔ&=Ñ"ÐCVˆÜØˆh�a˜�vð @ð @ó
ð 	
ð €Nr   r  Útriagerf  rk  Úinitial_statusrÔ  Úcreator_task_idc          :     óŒ   € V ^8„  d   QhRRRRRRRRRRR	RR
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR RR!RR"RR#RR$RR%R/# )&r   r’   r“   rQ  r#   rR  rD   rƒ   rU  rY  r•   r]  r\  rT  r6   r  rŸ   r	  r�   r_  rb  r€   rf  rö  rj  rg  rh  ri  rk  rl  r
  rm  r‚   r^  rÔ  r  rp  r   r   )r   s   "r   r   r   „  sP  € ÷ m&ñ m&Ø
ðm&Ø(+ðm&Ø3@ðm&àðm&à0=ðm&ð "ðm&ð ;Hðm&ð ð	m&ð 0=ð	m&ð PSð	m&ð
 ðm&ð
 *.ðm&ð
 IVðm&ð 'ðm&ð 8Oðm&ð ðm&ð 8Eðm&ð %ðm&ð @Mðm&ð ðm&ð .;ðm&ð TWðm&ð ðm&ð .;ðm&ð P]ðm&ð *ðm&ð #ðm&ð 'ðm&ð 	ñm&r   c          "     ó
	  € ^ RI HpHp ^ RIHp V! V4      p\        VV4      w  pp\        V4      p\        V4      pV'       d   VP                  4       '       g   \        R4      hV\        9  d   \        R\        \        4       24      hVfE   VR8w  d>    \        V4      P                  R4      ;'       g    RP                  4       ;'       g    RpVf   RpV\        9  d    \        R	\        \        4       R
V: 24      hVe$   \!        V4      P                  4       ;'       g    RpV'       d   VR8w  d   \        R4      h\#        V VVWV4      w  ppp p\$        ;QJ d    . R V
 4       F  NK  	  5M! R V
 4       4      p
\'        V4      p!V'       d3   V P)                  RV34      P+                  4       p"V"'       d
   V"R,          # \-        \.        P.                  ! 4       4      p#Vf9   V f5   VR-9   d.   \        V4      P                  R4      p$V$'       d   \!        V$4      p\1        ^4       EFù  p%\3        4       p& \5        V RR7      ;_uu_ 4        V! W
VW¸4      w  p'pVeN   VR8X  dG   V '       d*   V'       g"   \6        P8                  P;                  V RV&4      pV'       g   \=        VV&V4      pT P)                  RT&VP                  4       Y#T'T	TT#YVTTYŒ\?        V4      V!e   \@        PB                  ! V!4      MR\?        V4      TTTV'       d   ^M^ \?        V4      VV34       V
 F  p(\E        V V(V&4       K  	  \G        T T&RRTRT'R\I        V
4      RTRTRTRTRTRTRV!'       d   \I        V!4      MRR\K        V4      ;'       g    RR VR!V/4       V'R"8X  d   \G        T T&R"R#R$RR"R%T;'       g    R&/4       V'R'8X  dF   V
 U)u. uF  p)\M        V V)4      R.9  g   K  V)NK  	  p*p)V*'       d   \G        V V&R(R#R)R*V*^ ,          /4       V! V V&VV#R+7       \O        V V&V
V#R+7       RRR4       V&u # 	  \U        R,4      h  \         d     ELqi ; iu up)i   + '       g   i     L8; i  \P        PR                   d    T%^8X  d   h  EKT  i ; i)/uµ  Create a task (optionally under ``parents``); returns its id.

Status: ``ready`` unless a parent is not ``done`` (``todo``); ``triage=True``
forces ``triage``; ``initial_status="blocked"`` parks it for human ops.
``idempotency_key``: an existing non-archived task with the key is returned
instead of a duplicate. ``max_runtime_seconds``: cap before the dispatcher
SIGTERMs and re-queues. ``model_override``/``provider_override`` pin the
worker model (provider requires model); ``reasoning_effort`` is independent.
``creator_task_id``: inherit durable session/subscriptions independently of
dependency edges; an explicit ``session_id`` still wins.
``project_source_task_id``: cross-profile fallback when ``project_id`` is not
in the active profile's projects.db â€” see ``_resolve_project_link``.
``workspace_kind=None`` (omitted) inherits a project-scoped board's project;
an explicit ``"scratch"`` or ``project_id=""`` is a request for no project.
)Úinitial_task_stateÚinherit_creator_origin)Úvalidate_contractztitle is requiredzinitial_status must be one of NrØ  r^  r8   zworkspace_kind must be one of rY   rÙ  z1branch_name is only valid for worktree workspacesc              3  ó8   "  € T F  q'       g   K  Vx € K  	  R # 5ir`   r   ©rb   Úps   & r   rc   Úcreate_task.<locals>.<genexpr>Å  s   é € Ð,™w˜!«!—A’A›wùó   ‚	�
zhSELECT id FROM tasks WHERE idempotency_key = ? AND status != 'archived' ORDER BY created_at DESC LIMIT 1rš   Údefault_workdirT©Úallow_nestedrç  a|  
                    INSERT INTO tasks (
                        id, title, body, assignee, status, priority,
                        created_by, created_at, workspace_kind, workspace_path,
                        branch_name, project_id, tenant, idempotency_key,
                        max_runtime_seconds,
                        skills, max_retries, model_override, provider_override,
                        reasoning_effort,
                        goal_mode, goal_max_turns, session_id, completion_contract
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    Úcreatedrƒ   rS  r  r  r\  rY  r•   r]  rf  rk  rg  rh  rR   Úreasonr
  ÚactorÚuserÚtodoÚdependency_waitÚparent_not_doner  )rV  Úunreachable>   ÚdirrÙ  ©Údonerþ   )+Úhermes_cli.kanban_db_graphr  r  Úhermes_cli.kanban_pr_acceptancer  rÎ  r^   rÒ  r<   r=   ÚVALID_INITIAL_STATUSESÚsortedÚ_board_meta_forr;   r,   ÚVALID_WORKSPACE_KINDSr#   rã  Útupler  r£   r¤   r6   ÚtimeÚranger¼  Ú	write_txnr9   rf   r\   Ú_project_branch_namerµ  r*   ÚdumpsÚ_linkÚ_append_eventr¥   r�   Ú_task_statusÚ_inherit_notify_subsÚsqlite3ÚIntegrityErrorÚRuntimeError)+r’   rQ  rR  rƒ   rU  rY  r•   r]  r\  rT  r  r	  r_  rb  rf  rj  rg  rh  ri  rk  rl  r
  rm  r‚   r^  rÔ  r  rp  r  r  r  râ  rà  Úskills_listr!   ÚnowÚboard_defaultÚattemptrp   Útask_statusr–   r  Úgatings+   &$$$$$$$$$$$$$$$$$$$$$$$$$$$               r   Úcreate_taskr=  „  s’  € ÷< VÝAá+Ð,?Ó@ÐÜ(@ÀÐQbÓ(cÑ%€NÐ%Ü1Ð2BÓCÐÜ" 8Ó,€Hß˜Ÿ™ŸšÜÐ,Ó-Ð-ØÔ3Ô3ÜÐ9¼&ÔAWÓ:XÐ9YÐZÓ[Ð[ð
 Ò˜n°	Ô9ð	Ü)¨%Ó0×4Ñ4°\ÓB×HÐHÀb×OÑOÓQ×YÐYÐUYˆJð ÒØ"ˆØÔ2Ô2ÜØ,¬VÔ4IÓ-JÐ,Kð LØ!Ñ$ð&ó
ð 	
ð ÒÜ˜+Ó&×,Ñ,Ó.×6Ð6°$ˆß�~¨Ô3ÜÐLÓMÐMä<QØˆjÐ0°.ó=Ñ9€J�˜\¨>÷ ŒeÑ,™wÓ,�e‰eÑ,™wÓ,Ó,€GÜ(¨Ó0€K÷ Ø�l‰lð/à1@Ð0Bó
÷ ‰(‹*ð	 	÷
 Ø�t•9Ðä
Œd�iŠi‹kÓ
€Cð Ò ,Ò"6¸>ÐM`Ô;`Ü'¨Ó.×2Ñ2Ð3DÓEˆßÜ  Ó/ˆNô ˜—8ˆÜ“.ˆðQ	ô ˜4¨d×3Ö3Ù&8¸ÈÐX^Ó&gÑ#�˜Vð Ò*¨~ÀÔ/Kß#¯NÜ)+¯©¯©°lÀLÐRYÓ)Z˜ß&Ü&:¸;ÈÐQVÓ&W˜à—‘ð
ð   §¡£°ÀÈXØ" C¨Ø# Z°Ü Ð!4Ó5Ø3>Ò3JœŸ
š
 ;Ô/ÐPTÜ  Ó-¨~Ð?PÐRbß&™¨A¬x¸Ó/GÈÐUhðôó, #�CÜ˜$  WÖ-ñ #äØØØà" HØ  +Ø!¤4¨£=Ø)¨?Ø  &Ø(¨.Ø(¨.Ø% {Ø$ jØ ·{¤$ {Ô"3ÈØ#¤T¨)£_×%<Ð%<¸Ø(¨.Ø+Ð->ðô	ð(  )Ô+Ü!ØØØ!Ø!Ð#3°X¸yÈ'ÐS]×SgÐSgÐagÐhô	ð  &Ô(ñ *1Óf© A´LÀÀqÓ4IÐQeÑ4eŸa˜a©�FÐfßÜ%Ø Ø#Ø-Ø%Ð'8¸(ÀFÈ1ÅIÐNô	ñ ' t¨W°oÐRUÕVÜ$ T¨7°GÈÕL÷U 4ðV ŠNña ôh �}Ó
%Ð%øô{ ô 	Úð	üò\ g÷A 4×3ûôX ×%Ñ%ô 	Ø˜!Œ|Øó ð	ús   Â P6 Â8P6 ÃP6 È<Q É QÉ2QÉ:(QÊ#C!QÎ(QÎ.QÏQ
ÏQ
Ï>QÐ	Q Ð6QÑQÑQÑQÑQ Ñ RÒRc               ó    € V ^8„  d   QhRRRR/# )r   r‚   rD   r   r/   r   )r   s   "r   r   r   4  s   € ÷ Hñ H˜=ð H¨Tñ Hr   c                óD   € \        V '       d   V 4      # \        4       4      # r`   )r   r„   rà   s   &r   r(  r(  4  s   € Ü¯˜uÓGÐGÔ3DÓ3FÓGÐGr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   râ  r   rp   r#   rQ  rD   r   r   )r   s   "r   r   r   8  s)   € ÷ ñ  cð °Cð Àð ÐR_ñ r   c                ón   € ^ RI Hp  TP                  YT;'       g    RR7      #   \         d     R# i ; i)é    rÖ  r8   )rQ  N)rÚ  r×  Úbranch_name_forr,   )râ  rp   rQ  rß  s   &&& r   r.  r.  8  s8   € Ý.ðØ×#Ñ# KÀÇÀÈÐ#ÓLÐLøÜô Úðús   ˆ% �% ¥4³4c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r’   r“   Ú	parent_idr#   Úchild_idr   rg   r   )r   s   "r   r   r   A  s)   € ÷ ñ Ð"ð ¨sð ¸cð Àdñ r   c                ó,   € V P                  R W34       R# )zDINSERT OR IGNORE INTO task_links (parent_id, child_id) VALUES (?, ?)N©r£   )r’   rE  rF  s   &&&r   r0  r0  A  s   € Ø‡L�LØNØ	Ðör   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   ÚidsrŸ   r   ú	list[str]r   )r   s   "r   r   r   H  s"   € ÷ 0ñ 0Ð.ð 0°]ð 0Àyñ 0r   c                ó,  € \        V4      pV'       g   . # RP                  R\        V4      ,          4      pV P                  RV R2V4      P	                  4       pV Uu0 uF  qDR,          kK  	  ppV Uu. uF  qfV9  g   K  VNK  	  up# u upi u upi )z5Subset of ``ids`` (order kept) with no ``tasks`` row.rø  Ú?z"SELECT id FROM tasks WHERE id IN (Ú)rš   )r¥   r\   r  r£   Úfetchall)r’   rJ  ÚplaceholdersÚrowsrž  Úpresentr  s   &&     r   Ú_missing_task_idsrS  H  sˆ   € ä
ˆs‹)€CßØˆ	Ø—8‘8˜C¤# c£(�NÓ+€LØ�<‰<Ð<¸\¸NÈ!ÐLÈcÓR×[Ñ[Ó]€DÙ $Ó%¡˜1��wˆw¡€GÐ%ÙÓ/‘s�! wÑ.�AˆA‘sÑ/Ð/ùò &ùÚ/s   ÁBÁ6BÂBc          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r’   r“   rF  r#   r  rŸ   rV  r€   r   rg   r   )r   s   "r   r   r   S  s6   € ÷ #ñ #Ø
ð#Ø(+ð#Ø6Cð#àð#ð 
ñ#r   c               ó”  € \        \        P                  R V 4       4      4      pV'       g   R# V P                  RV34      P	                  4       p\        Ve
   VR,          M^ 4      pRP                  R\        V4      ,          4      pT P                  RV R2T\        Ve   TM\        P                  ! 4       4      V.VO54       R# )	a4  Copy parents' notify subscriptions to a child, cursor caught up to the
child's current event so a late ``link_tasks`` never replays history.

Single owner of inheritance (create_task, link_tasks, decompose). It must
copy EVERY routing/delivery column: dropping ``chat_type`` made DM-originated
completions wake a fresh group session instead of the originating DM.

Omitting columns here silently degrades routing: a DM-originated child completion falls back to
chat_type='group' and wakes a fresh group-scoped session instead of the originating DM (issue #73030).
c              3  ó8   "  € T F  q'       g   K  Vx € K  	  R # 5ir`   r   r  s   & r   rc   Ú'_inherit_notify_subs.<locals>.<genexpr>a  s   é € Ð$=±¨1»1§Q¢Q³ùr  NzHSELECT COALESCE(MAX(id), 0) AS cursor FROM task_events WHERE task_id = ?Úcursorrø  rM  aù  
        INSERT OR IGNORE INTO kanban_notify_subs
            (task_id, platform, chat_id, thread_id, user_id, user_id_alt,
             chat_type, notifier_profile, delivery_mode, delivery_metadata,
             created_at, last_event_id)
        SELECT ?, platform, chat_id, thread_id, user_id, user_id_alt,
               COALESCE(chat_type, 'dm'), notifier_profile,
               COALESCE(delivery_mode, 'notify'), delivery_metadata, ?, ?
          FROM kanban_notify_subs
         WHERE task_id IN (z
)
        )	r*  r/   Úfromkeysr£   r¤   r6   r\   r  r+  )r’   rF  r  rV  Ú
parent_idsr!   rX  rP  s   &&&$    r   r3  r3  S  s·   € ô ”t—}‘}Ñ$=±Ó$=Ó=Ó>€JßÙØ
�,‰,ØRÐU]ÐT_óç�hƒjð ô  #¢/��X–°qÓ9€FØ—8‘8˜C¤# j£/Õ1Ó2€LØ‡L�Lð	ð )˜>ð *	ð
	ð 
”3 ZÒ%;‘zÄÇÂÃÓMÈvÐcÐXbÑcör   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   r~   r   )r   s   "r   r   r   y  s"   € ÷ /ñ /Ð%ð /°ð /¸ñ /r   c                ó‚   € V P                  R V34      P                  4       pV'       d   \        P                  V4      # R# )z SELECT * FROM tasks WHERE id = ?N)r£   r¤   r”   r‚  ©r’   rp   r!   s   && r   rê  rê  y  s4   € Ø
�,‰,Ð9¸G¸:Ó
F×
OÑ
OÓ
Q€Cß!$Œ4�=‰=˜ÓÐ.¨$Ð.r   r  úcreated_at ASC, id ASCzcreated-desczcreated_at DESC, id DESCzpriority DESC, created_at ASCzpriority-desczpriority ASC, created_at ASCzstatus ASC, created_at ASCzassignee ASC, created_at ASCztitle ASC, id ASCÚupdatedz+started_at DESC NULLS LAST, created_at DESCzcompleted-descz%completed_at DESC NULLS LAST, id DESCúdict[str, str]ÚVALID_SORT_ORDERSÚinclude_archivedÚlimitÚorder_byc               óD   € V ^8„  d   QhRRRRRRRRRRRR	R
RRRRRRRRR/# )r   r’   r“   rƒ   rD   rS  r\  rm  rb  r�   rc  r€   rd  rd  re  r   z
list[Task]r   )r   s   "r   r   r   �  sp   € ÷ ,ñ ,Ø
ð,Ø+8ð,ØIVð,àð,à.;ð,àVZð,ð ð,ð ,9ð,ð (ð	,ð CPð	,ð
 ñ,r   c       	        óÜ  € Ve'   V\         9  d   \        R\        \         4       24      hRp
. pR\        V4      3RV3RV3RV3RV3RV	33 F)  w  rÍVf   K  V
R	V R
2,          p
VP	                  V4       K+  	  V'       g   VR8w  d
   V
R,          p
Vek   VP                  4       P                  4       pV\        9  d*   \        R\        \        P                  4       4       24      hV
R\        V,           2,          p
M	V
R,          p
V'       d   V
R\        V4       2,          p
V P                  W«4      P                  4       pV Uu. uF  p\        P                  V4      NK  	  up# u upi )Nzstatus must be one of zSELECT * FROM tasks WHERE 1=1rƒ   rS  r\  rm  rd  re  ú AND ú = ?rþ   z AND status != 'archived'zorder_by must be one of z
 ORDER BY z' ORDER BY priority DESC, created_at ASCz LIMIT )ÚVALID_STATUSESr=   r'  rÒ  r   r<   r[   ra  r&   r6   r£   rO  r”   r‚  )r’   rƒ   rS  r\  rm  rb  rc  rd  rd  re  ÚqueryÚparamsr"   r
  rQ  rž  s   &$$$$$$$$$      r   Ú
list_tasksrl  �  sp  € ð Ò˜f¬NÔ:ÜÐ1´&¼Ó2HÐ1IÐJÓKÐKØ+€EØ€Fà	Ô(¨Ó2Ð3°hÀÐ5GÈ(ÐTZÐI[Ø	�zÐ"Ð%;Ð=QÐ$RØ	Ð-Ð.ó‰ˆð
 Œ?Ø�u˜S˜E Ð&Õ&ˆEØ�M‰M˜#Öñ÷  ¨*Ô 4ØÐ,Õ,ˆØÒØ—>‘>Ó#×)Ñ)Ó+ˆØÔ,Ô,ÜÐ7¼Ô?P×?UÑ?UÓ?WÓ8XÐ7YÐZÓ[Ð[Ø�:Ô/°Õ9Ð:Ð;Õ;‰àÐ:Õ:ˆßØ�7œ3˜u›:˜,Ð'Õ'ˆØ�<‰<˜Ó&×/Ñ/Ó1€DÙ&*Ó+¡d ŒD�M‰M˜!Ö¡dÑ+Ð+ùÒ+s   ÅE)c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   rŽ  rD   r   r�   r   )r   s   "r   r   r   ®  s*   € ÷ ñ Ð(ð °3ð Àð ÐSWñ r   c                óä  € \        V4      p\        V 4      ;_uu_ 4        V P                  RV34      P                  4       pV'       g    RRR4       R# VR,          e   VR,          R8X  d   \	        RV R24      hVR	,          V8w  d   V P                  R
W!34       MV P                  RW!34       \        WRR	VRVR	,          /4       RRR4       \        WR4       R#   + '       g   i     L; i)zMAssign/reassign; raises RuntimeError while the task is running under a claim.z;SELECT status, claim_lock, assignee FROM tasks WHERE id = ?NFrZ  rS  rQ   zcannot reassign zS: currently running (claimed). Wait for completion or reclaim the stale lock first.rƒ   z_UPDATE tasks SET assignee = ?, consecutive_failures = 0, last_failure_error = NULL WHERE id = ?z*UPDATE tasks SET assignee = ? WHERE id = ?ÚassignedÚfromT)rƒ   )rÒ  r-  r£   r¤   r6  r1  r¦   )r’   rp   rŽ  r!   s   &&& r   Úassign_taskrq  ®  sî   € ä! 'Ó*€GÜ	�4��Ø�l‰lØIÈGÈ:ó
ç
‰(‹*ð 	÷ Ø÷ 
‰ð ˆ|ÕÒ(¨S°­]¸iÔ-GÜØ" 7 )ð ,Gð Góð ð ˆz�?˜gÔ%à�L‰Lð9Ø;BÐ:Lõð
 �L‰LÐEÈÐGYÔZô 	Ø˜:¨
°G¸VÀSÈÅ_Ð'Uô	
÷+ 
ô2 ˜ }Ô5Ù÷5 
�ús   Ÿ+CÁA5CÃC/	c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rp   r#   rË  rD   rÌ  r   r�   r   )r   s   "r   r   r   Î  s4   € ÷ ñ Ø
ðØ'*ðØ3@ðØLYðà	ñr   c                óJ   € \        W#4      w  r#\        WRW#3RRVRV/RRR7      # )zÄSet (empty ``model`` clears BOTH) the per-task model/provider override.
Allowed while ``running``: it applies on the NEXT dispatch, which is the
rate-limit-recovery flow (set, then reclaim/retry).zGUPDATE tasks SET model_override = ?, provider_override = ? WHERE id = ?Úmodel_override_setrË  rÌ  zcannot set model override©Úarchived_msg)rg  rh  )rÎ  Ú_set_task_override)r’   rp   rË  rÌ  s   &&&&r   Úset_model_overriderx  Î  s=   € ô /¨uÓ?�O€EÜØØQÐTYÐSdØ˜w¨¨z¸8ÐDØ/Ð>Yô	ð r   c               ó<   € V ^8„  d   QhRRRRRRRRRRR	R
RRRRRR/	# )r   r’   r“   rp   r#   Úsqlrk  r*  Ú
event_kindr±  r/   rž   r1  rv  r   r�   r   )r   s   "r   r   r   Ý  sV   € ÷ ñ Ø
ðØ'*ðØ14ðØ>CðØQTðØ_cðà#ðà69ðð 
ñr   c               ó$  € \        V 4      ;_uu_ 4        \        W4      pVf    RRR4       R# VR8X  d   \        V RV 24      hV P                  V. VOVN54       \	        WWE4       RRR4       \        WV4       R#   + '       g   i     L; i)z…Per-task override write: refuse archived tasks, record ``event_kind``,
then fire the task-updated observer AFTER commit (RFC #58548).NFrþ   z on archived task T)r-  r2  r6  r£   r1  r¦   )	r’   rp   rz  rk  r{  r±  rž   rv  rS  s	   &&&&&&&$ r   rw  rw  Ý  s†   € ô 
�4��Ü˜dÓ,ˆØŠ>Ø÷ 
‰ð �ZÔÜ , Ð/AÀ'ÀÐKÓLÐLØ�‰�SÐ,˜FÐ, GÑ,Ô-Ü�d ZÔ9÷ 
ô ˜ ~Ô6Ù÷ 
�ús   ”A?¯:A?Á?B	c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   rT   rD   r   r�   r   )r   s   "r   r   r   ï  s*   € ÷ 	ñ 	Ð1ð 	¸Cð 	Èð 	Ð[_ñ 	r   c                óB   € \        V4      p\        WRV3RRV/RRR7      # )zÒSet (empty clears; ``"none"`` pins thinking OFF) the per-task reasoning
effort. Independent of the model override so clearing one never resets the
other; applies on the NEXT dispatch, so settable while running.z2UPDATE tasks SET reasoning_effort = ? WHERE id = ?Úreasoning_effort_setri  zcannot set reasoning effortru  )ri  )r^   rw  )r’   rp   rT   s   &&&r   Úset_reasoning_effortr€  ï  s6   € ô (¨Ó/€FÜØÐKÈfÈYØÐ!3°VÐ <ØÐ,Iôð r   Úexpected_child_run_idc          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rE  r#   rF  r�  r€   r   r�   r   )r   s   "r   r   r   ý  s:   € ÷ 8ñ 8Ø
ð8àð8ð ð8ð
 )ð8ð 
ñ8r   c               óÐ  € W8X  d   \        R4      hRp\        V 4      ;_uu_ 4        \        WV.4      pV'       d   \        RRP                  V4       24      hV P	                  RV34      P                  4       pVR,          R8X  d%   Ve   VR	,          V8w  d   \        R
V RV R24      h\        WV4      '       d   \        RV RV R24      h\        WV4       \        W4      R9  d?   V P	                  RV34      pVP                  ^8H  pV'       d   \        V VRRRRRRV/4       \        V VRRVRV/4       \        WV34       RRR4       V#   + '       g   i     T# ; i)aî  Link ``parent_id -> child_id``. Returns True when the link gated a
``ready`` child back to ``todo`` (the new parent is not yet terminal), so
callers can surface the demotion instead of a silent status flip.

A running child cannot normally be gated retroactively, so reject the edge
rather than record a dependency that did not constrain the active run. The
owning worker may link its own active run for a subsequent dependency-block
handoff by supplying its trusted ``expected_child_run_id``.
za task cannot depend on itselfFzunknown task(s): rX   z5SELECT status, current_run_id FROM tasks WHERE id = ?rS  rQ   Nr¢   zcannot link z -> z: child is already runningzlinking z would create a cycleúBUPDATE tasks SET status = 'todo' WHERE id = ? AND status = 'ready'r  r  r  ÚdemotedTr  ÚlinkedÚchildr"  )r=   r-  rS  r\   r£   r¤   Ú_would_cycler0  r2  Úrowcountr1  r3  )r’   rE  rF  r�  ÚgatedÚmissingr‡  Úcurs   &&&$    r   Ú
link_tasksr�  ý  s‰  € ð  ÔÜÐ9Ó:Ð:Ø€EÜ	�4��Ü# D°hÐ*?Ó@ˆßÜÐ0°·±¸7Ó1CÐ0DÐEÓFÐFØ—‘ØCÀhÀ[ó
ç
‰(‹*ð 	ð ��?˜iÔ'Ø!Ò)ØÐ%Õ&Ð*?Ô?ä˜|¨I¨;°d¸8¸*ÐD^Ð_Ó`Ð`Ü˜¨×2Ò2Ü˜x¨	 {°$°x°jÐ@UÐVÓWÐWÜˆd˜xÔ(ô ˜Ó(Ð0DÔDØ—,‘,ØTØ�óˆCð —L‘L AÑ%ˆEßÜØØØ%ØÐ0°)¸TÀ8ÈYÐWô	ô 	ØØØØ�y '¨8Ð4ô		
ô 	˜T¨i¨\Ô:÷I 
ðJ €L÷K 
ŽðJ €Lús   §C/EÄ3EÅE%	c               ó(   € V ^8„  d   QhRRRRRRRR/# ©r   r’   r“   rE  r#   rF  r   r�   r   )r   s   "r   r   r   8  s)   € ÷ ñ Ð)ð °cð ÀSð ÈTñ r   c                ó  € \        4       pV.pV'       dm   VP                  4       pWQ8X  d   R# WS9   d   K(  VP                  V4       V P                  RV34      P	                  4       pVP                  R V 4       4       Kt  R# )z?True iff ``parent_id`` is already a descendant of ``child_id``.Tz3SELECT child_id FROM task_links WHERE parent_id = ?c              3  ó2   "  € T F  qR ,          x € K  	  R# 5i)rF  Nr   )rb   rž  s   & r   rc   Ú_would_cycle.<locals>.<genexpr>F  s   é € Ð1©D q�z—]’]«Dùs   ‚F)rÎ   Úpopr  r£   rO  Úextend)r’   rE  rF  r  ÚstackÚnoderQ  s   &&&    r   rˆ  rˆ  8  sp   € ä‹5€DØˆJ€Eß
Ø�y‰y‹{ˆØÔÙØŒ<ÙØ�‰�ŒØ�|‰|ØAÀDÀ7ó
ç
‰(‹*ð 	ð 	�‰Ñ1©DÓ1Ö1Ùr   c               ó(   € V ^8„  d   QhRRRRRRRR/# r�  r   )r   s   "r   r   r   J  s)   € ÷ ñ Ð)ð °cð ÀSð ÈTñ r   c                ó   € \        V 4      ;_uu_ 4        V P                  R W34      pVP                  ^ 8„  pV'       d   \        WRRVRV/4       RRR4       X'       d   \	        V 4       V#   + '       g   i     L%; i)z;DELETE FROM task_links WHERE parent_id = ? AND child_id = ?Úunlinkedr  r‡  N)r-  r£   r‰  r1  Úrecompute_ready)r’   rE  rF  rŒ  Úremoveds   &&&  r   Úunlink_tasksrœ  J  sk   € Ü	�4��Ø�l‰lØIÈIÐK`ó
ˆð —,‘, Ñ"ˆßÜ˜$¨*°xÀÈGÐU]Ð6^Ô_÷ 
÷ ô 	˜ÔØ€N÷ 
�ús   ”<A-Á-A=	c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r   r’   r“   Úwantr#   Úwhererp   r   rK  r   )r   s   "r   r   r   Y  s1   € ÷ #ñ #Ð(ð #°ð #¸Cð #È#ð #ÐR[ñ #r   c                ó�   € V P                  R V RV RV 2V34      P                  4       pV Uu. uF  qUV,          NK  	  up# u upi )zSELECT z FROM task_links WHERE z = ? ORDER BY ©r£   rO  )r’   rž  rŸ  rp   rQ  rž  s   &&&&  r   Ú_linked_idsr¢  Y  sS   € Ø�<‰<Ø
�$�Ð.¨u¨g°^ÀDÀ6ÐJÈWÈJóç�hƒjð 	ñ "Ó"™T˜ˆd�GˆG™TÑ"Ð"ùÒ"s   ¯Ac               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   rp   r#   r   rK  r   )r   s   "r   r   r   c  s"   € ÷ ?ñ ?Ð'ð ?°#ð ?¸)ñ ?r   c                ó   € \        V R RV4      # )rE  rF  ©r¢  ©r’   rp   s   &&r   rZ  rZ  c  s   € Ü�t˜[¨*°gÓ>Ð>r   c               ó$   € V ^8„  d   QhRRRRRR/# r¤  r   )r   s   "r   r   r   g  s"   € ÷ ?ñ ?Ð&ð ?°ð ?¸ñ ?r   c                ó   € \        V R RV4      # )rF  rE  r¦  r§  s   &&r   Ú	child_idsrª  g  s   € Ü�t˜Z¨°gÓ>Ð>r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   Útask_idsrŸ   r   zdict[str, dict]r   )r   s   "r   r   r   k  s"   € ÷ ñ Ð0ð ¸Mð Èoñ r   c                óà  € \        \        P                  R V 4       4      4      pV Uu/ uF	  q3R. R. /bK  	  ppV'       g   V# RP                  R V 4       4      pR F†  w  rgpV P	                  RV RV RV R	V R
V R2\        V4      4      P                  4        FC  p	WIR,          ,          V,          P                  RV	R,          RV	R,          RV	R,          /4       KE  	  Kˆ  	  V# u upi )zABulk-load compact direct graph state for graph-aware diagnostics.c              3  óJ   "  € T F  q'       g   K  \        V4      x € K  	  R # 5ir`   )r#   )rb   rp   s   & r   rc   Ú&task_graph_contexts.<locals>.<genexpr>m  s   é € Ð$UÁ°gËW¤\¤S¨§\ \Ãùs   ‚	#�#r  Úchildrenrø  c              3  ó&   "  € T F  pR x € K	  	  R# 5i©rM  Nr   ©rb   Ú_s   & r   rc   r¯  r  s   é € Ð5© A�C«ùó   ‚z	SELECT l.zQ AS owner_id, t.id, t.title, t.status FROM task_links l JOIN tasks t ON t.id = l.z	 WHERE l.z IN (z) ORDER BY l.z, t.idÚowner_idrš   rQ  rS  ))r  rF  rE  )r°  rE  rF  )r¥   r/   rY  r\   r£   r*  rO  r   )
r’   r¬  Úordered_idsrp   ÚcontextsrP  ÚbucketÚownÚotherr!   s
   &&        r   Útask_graph_contextsr¼  k  s  € ä”t—}‘}Ñ$UÁÓ$UÓUÓV€KÙHSÓTÉ¸W˜) R¨°RÐ8Ò8É€HÐTßØˆà—8‘8Ñ5©Ó5Ó5€LÛkÑˆ�UØ—<‘<Ø˜�uð :Ø:?¸ð AØ�e˜5  ¨m¸C¸5ÀðHäINÈ{ÓI[ó
÷ ‰(‹*ò	ˆCð
 ˜•_Õ% fÕ-×4Ñ4Ø�s˜4•y '¨3¨w­<¸À3ÀxÅ=ÐQöóñ lð €Oùò Us   ªC+c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   r/   r   )r   s   "r   r   r     s"   € ÷ 9ñ 9Ð/ð 9¸#ð 9À$ñ 9r   c                ó(   € \        W.4      V,          # )z6Return compact direct parent/child state for one task.)r¼  r§  s   &&r   Útask_graph_contextr¿    s   € ä˜t YÓ/°Õ8Ð8r   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r   r’   r“   rp   r#   rœ  rR  r   r6   r   )r   s   "r   r   r   †  s1   € ÷ 'ñ 'Ð(ð '°3ð 'Àð 'È3ð 'ÐSVñ 'r   c                ó4  € V'       d   VP                  4       '       g   \        R 4      hV'       d   VP                  4       '       g   \        R4      h\        \        P                  ! 4       4      p\	        V RR7      ;_uu_ 4        \        W4       V P                  RWP                  4       VP                  4       V34      p\        WRRVR\        V4      /4       \        VP                  ;'       g    ^ 4      uuRRR4       #   + '       g   i     R# ; i)	zcomment body is requiredzcomment author is requiredTr  úQINSERT INTO task_comments (task_id, author, body, created_at) VALUES (?, ?, ?, ?)Ú	commentedrœ  r  N)
r<   r=   r6   r+  r-  Ú_require_taskr£   r1  r  Ú	lastrowid)r’   rp   rœ  rR  r8  rŒ  s   &&&&  r   Úadd_commentrÆ  †  sÃ   € ß�t—z‘z—|’|ÜÐ3Ó4Ð4ß˜Ÿ™ŸšÜÐ5Ó6Ð6Ü
Œd�iŠi‹kÓ
€Cô 
�4 d×	+Ö	+Ü�dÔ$Ø�l‰lð"Ø$+¯\©\«^¸T¿Z¹Z»\È3Ð#Oó
ˆô 	�d [°8¸VÀUÌCÐPTËIÐ2VÔWÜ�3—=‘=×%Ð% AÓ&÷ 
,×	+×	+Ó	+ús   ÂA5DÄD	c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   rp   r#   r   rg   r   )r   s   "r   r   r   ˜  s"   € ÷ 4ñ 4Ð*ð 4°Sð 4¸Tñ 4r   c                óp   € V P                  R V34      P                  4       '       g   \        RV 24      hR# )z SELECT 1 FROM tasks WHERE id = ?úunknown task N)r£   r¤   r=   r§  s   &&r   rÄ  rÄ  ˜  s7   € Ø�<‰<Ð:¸W¸JÓG×PÑP×RÒRÜ˜=¨¨	Ð2Ó3Ð3ñ Sr   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r   r’   r“   Útabler#   rp   Úorderr   zlist[sqlite3.Row]r   )r   s   "r   r   r   �  s1   € ÷ ñ Ð'ð °ð ¸cð È#ð ÐRcñ r   c                óP   € V P                  R V RV 2V34      P                  4       # )zSELECT * FROM z WHERE task_id = ? ORDER BY r¡  )r’   rÌ  rp   rÍ  s   &&&&r   Ú
_task_rowsrÏ  �  s/   € Ø�<‰<Ø
˜˜Ð;¸E¸7ÐCÀgÀZóç�hƒjðr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   úlist[Comment]r   )r   s   "r   r   r   £  s'   € ÷ gñ gÐ*ð g°Sð g¸]ñ gr   c                ón   € \        V R VR4       Uu. uF  p\        P                  V4      NK  	  up# u upi )Útask_commentszcreated_at ASC)rÏ  r›  r‚  ©r’   rp   rž  s   && r   Úlist_commentsrÕ  £  s3   € Ü)3°D¸/È7ÐTdÔ)eÓfÑ)e AŒG×Ñ˜QÖÑ)eÑfÐfùÒfó   ‘2Úafter_idc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   r×  r6   r   rÑ  r   )r   s   "r   r   r   §  s,   € ÷ 	/ñ 	/Ø
ð	/Ø'*ð	/Ø9<ð	/àñ	/r   c               ó¬   € V P                  RV\        V4      34      P                  4       pV Uu. uF  p\        P	                  V4      NK  	  up# u upi )u�   Comments with ``id > after_id`` â€” keyed on rowid, not ``created_at``, so a
same-second burst is never skipped (live worker comment bridge).zlSELECT id, task_id, author, body, created_at FROM task_comments WHERE task_id = ? AND id > ? ORDER BY id ASC)r£   r6   rO  r›  r‚  )r’   rp   r×  rQ  rž  s   &&$  r   Úlist_comments_afterrÚ  §  sT   € ð
 �<‰<ð	7Ø9@Ä#ÀhÃ-Ð8Pó÷ �hƒjð 	ñ *.Ó.© AŒG×Ñ˜QÖ©Ñ.Ð.ùÒ.s   °Ac                  ó   € ] tR tRtRtRtR# )ÚAttachmentTooLargeiµ  z†Attachment over the size cap. A ``ValueError`` so generic 400 handlers
still catch it while the tool/CLI can give a 413-style message.r   N©r„  r…  r†  r‡  rˆ  r‹  r   r   r   rÜ  rÜ  µ  s   † õGr   rÜ  c               ó    € V ^8„  d   QhRRRR/# )r   r>   r#   r   r   )r   s   "r   r   r   º  s   € ÷ 	ñ 	˜sð 	 sñ 	r   c                óD  € T ;'       g    RP                  RR4      P                  R4      R,          P                  4       pRP                  R V 4       4      P                  4       pVP	                  R4      P                  4       pV'       g   \        R4      hVR,          # )	zßClient filename -> safe basename: strip directories (both separators),
control chars and leading dots (no dotfiles, no traversal); ValueError when
nothing usable remains. Only ever joined under the per-task attachments dir.r8   Ú\rè  c              3  óf   "  € T F'  qP                  4       '       g   K  VR 9  g   K#  Vx € K)  	  R# 5i)Ú N)Úisprintable)rb   Úchs   & r   rc   Ú(_safe_attachment_name.<locals>.<genexpr>¿  s"   é € ÐN¡˜"¯©×(8”2¸RÀvÑ=M—2’2£ùs   ‚1ž1§
1Ú.zinvalid attachment filename:NéÈ   Néÿÿÿÿ)r   rÈ  r<   r\   Úlstripr=   )r>   r5   s   & r   Ú_safe_attachment_namerê  º  s‚   € ð �IˆI�2×Ñ˜t SÓ)×/Ñ/°Ó4°RÕ8×>Ñ>Ó@€DØ�7‰7ÑN¡ÓNÓN×TÑTÓV€DØ�;‰;�sÓ×!Ñ!Ó#€DßÜÐ6Ó7Ð7Ø��:Ðr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Údest_dirr   Ú	safe_namer#   r   r   )r   s   "r   r   r   Æ  s!   € ÷  ñ   4ð  °Cð  ¸Dñ  r   c                ó¨   € VP                  R4      w  r#pTp^pW,          P                  4       '       d   V RV RV V 2pV^,          pK3  W,          # )zN``foo.pdf`` -> ``foo.pdf``, ``foo (1).pdf``, ... first one that doesn't exist.ræ  z (rN  )rì  r  )rì  rí  ÚstemÚdotÚextrû   rû  s   &&     r   Ú_collision_free_pathrò  Æ  s^   € à×(Ñ(¨Ó-�N€DˆsØ€IØ	€AØÕ×
'Ñ
'×
)Ò
)Ø�f˜B˜q˜c  3 %¨ uÐ-ˆ	Ø	ˆQ�ŠØÕÐr   r§  r©  Ú	max_bytesc               ó<   € V ^8„  d   QhRRRRRRRRRR	R
R	RR	RRRR/	# )r   r’   r“   rp   r#   r¥  Údatar   r§  rD   r©  r‚   ró  r€   r   r6   r   )r   s   "r   r   r   Ñ  sZ   € ÷ ñ Ø
ðØ'*ðØ69ðØAFðàðà5Bðð ðð -:ðð 	ñ	r   c          
     ó&  € Vf   \         p\        V4      V8”  d   \        RVR	,           R24      h\        V4      p\	        WR7      p	V	P                  RRR7       \        W˜4      p
V
P                  V4        \        WV
P                  \        V
P                  4       4      V\        V4      VR7      #   \         dS    \        P                  ! \        4      ;_uu_ 4        T
P!                  RR7       RRR4       h   + '       g   i     h ; ii ; i)
a"  Single attachment write path (dashboard, tools, CLI): size cap, safe
basename, collision-free blob under :func:`task_attachments_dir`, then the
metadata row. Raises :class:`AttachmentTooLarge` / ``ValueError``; a blob
whose row insert fails is removed before re-raising. Returns the new id.Nzattachment exceeds z	 MB limitrà   Tr  )r¥  r¦  r§  r¨  r©  ©Ú
missing_oké   )ÚKANBAN_ATTACHMENT_MAX_BYTESr  rÜ  rê  rK  r  rò  Úwrite_bytesÚadd_attachmentr5   r#   Úresolver,   r  r  r  r  )r’   rp   r¥  rõ  r§  r©  r‚   ró  rí  rì  Ú	dest_paths   &&&&$$$$   r   Ústore_attachment_bytesrÿ  Ñ  sò   € ð ÒÜ/ˆ	Ü
ˆ4ƒy�9ÔÜ Ð#6°yÀ[Õ7QÐ6RÐR[Ð!\Ó]Ð]Ü% hÓ/€IÜ# GÔ9€HØ‡N�N˜4¨$€NÔ/Ü$ XÓ9€IØ×Ñ˜$Ôð
ÜØ I§N¡NÄÀI×DUÑDUÓDWÓ@XØ%¬C°«IÀ;ô
ð 	
øô ô ô × Ò ¤×)Õ)Ø×Ñ¨ÐÔ-÷ *à÷ *Ö)àúðús$   Á8:B3 Â3-DÃ C<Ã3	DÃ<DÄ	Dr¨  c               ó8   € V ^8„  d   QhRRRRRRRRRRR	R
RRRR
/# )r   r’   r“   rp   r#   r¥  r¦  r§  rD   r¨  r6   r©  r   r   )r   s   "r   r   r   ð  sN   € ÷ 'ñ 'Ø
ð'Ø'*ð'Ø9<ð'ØKNð'àð'à.1ð'àDQð'ð 	ñ'r   c               óH  € V'       d   VP                  4       '       g   \        R4      hV'       d   VP                  4       '       g   \        R4      h\        \        P                  ! 4       4      p\	        V 4      ;_uu_ 4        \        W4       V P                  RWP                  4       W4\        V4      Wg34      p\        WRRVP                  4       R\        V4      RV/4       \        VP                  ;'       g    ^ 4      uuRRR4       #   + '       g   i     R# ; i)	zSRecord the metadata row (+ ``attached`` event) for a blob the caller already wrote.zattachment filename is requiredz"attachment stored_path is requiredz‡INSERT INTO task_attachments (task_id, filename, stored_path, content_type, size, uploaded_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)Úattachedr¥  r¨  ÚbyN)	r<   r=   r6   r+  r-  rÄ  r£   r1  rÅ  )	r’   rp   r¥  r¦  r§  r¨  r©  r8  rŒ  s	   &&$$$$$  r   rü  rü  ð  sÕ   € ÷
 ˜8Ÿ>™>×+Ò+ÜÐ:Ó;Ð;ß˜k×/Ñ/×1Ò1ÜÐ=Ó>Ð>Ü
Œd�iŠi‹kÓ
€CÜ	�4��Ü�dÔ$Ø�l‰lð+ð —n‘nÓ&¨Ä3ÀtÃ9ÈkÐ_ó	
ˆô 	Ø˜:Ø˜Ÿ™Ó)¨6´3°t³9¸dÀKÐPô	
ô �3—=‘=×%Ð% AÓ&÷ 
��‹ús   ÂBDÄD!	c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   zlist[Attachment]r   )r   s   "r   r   r   	  s(   € ÷ uñ uÐ-ð u¸ð uÐ@Pñ ur   c                ón   € \        V R VR4       Uu. uF  p\        P                  V4      NK  	  up# u upi )Útask_attachmentsr^  )rÏ  r¤  r‚  rÔ  s   && r   Úlist_attachmentsr  	  s5   € Ü,6°tÐ=OÐQXÐZrÔ,sÓtÑ,s qŒJ×Ñ Ö"Ñ,sÑtÐtùÒtrÖ  c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   Úattachment_idr6   r   zOptional[Attachment]r   )r   s   "r   r   r     s#   € ÷ 9ñ 9Ð+ð 9¸Cð 9ÐDXñ 9r   c                óz   € V P                  R V34      P                  4       pVf   R# \        P                  V4      # )z+SELECT * FROM task_attachments WHERE id = ?N)r£   r¤   r¤  r‚  )r’   r
  rž  s   && r   Úget_attachmentr    s9   € Ø�‰ÐBÀ]ÐDTÓU×^Ñ^Ó`€AØ’9ˆ4Ð8¤*×"5Ñ"5°aÓ"8Ð8r   c               ó$   € V ^8„  d   QhRRRRRR/# r	  r   )r   s   "r   r   r     s#   € ÷ ñ Ð.ð ¸sð ÐG[ñ r   c           	     óf  € \        V 4      ;_uu_ 4        \        W4      pVf    RRR4       R# V P                  RV34       V P                  RVP                  34      P	                  4       RJp\        WP                  RRVP                  /4       RRR4       X'       gi   \        P                  ! \        4      ;_uu_ 4        \        XP                  4      pVP                  4       '       d   VP                  4        RRR4       V# X#   + '       g   i     L‚; i  + '       g   i     X# ; i)zTDelete the row (source of truth) and best-effort its blob; None when no row matched.Nz)DELETE FROM task_attachments WHERE id = ?z<SELECT 1 FROM task_attachments WHERE stored_path = ? LIMIT 1Úattachment_removedr¥  )r-  r  r£   r¦  r¤   r1  rp   r¥  r  r  r  r   Úis_filer  )r’   r
  ÚattÚhas_remaining_blob_referencer  s   &&   r   Údelete_attachmentr    så   € ä	�4��Ü˜TÓ1ˆØŠ;Ø÷ 
‰ð 	�‰Ð@À=ÐBRÔSØ'+§|¡|ØJØ�_‰_Ðó(
÷ ‰(‹*˜Dð(!Ð$ô 	�dŸK™KÐ)=À
ÈCÏLÉLÐ?YÔZ÷ 
÷ (Ü× Ò ¤×)Õ)Ü�S—_‘_Ó%ˆAØ�y‰y�{Š{Ø—‘”
÷ *ð €Jˆ3€J÷ 
�ú÷ *Ö)ð €Jús   ”D¯A#DÃ<DÄD	ÄD0	c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   zlist[Event]r   )r   s   "r   r   r   &  s'   € ÷ kñ kÐ(ð k°3ð k¸;ñ kr   c                ón   € \        V R VR4       Uu. uF  p\        P                  V4      NK  	  up# u upi )Útask_eventsr^  )rÏ  r¯  r‚  rÔ  s   && r   Úlist_eventsr  &  s1   € Ü'1°$¸ÀwÐPhÔ'iÓjÑ'i !ŒE�N‰N˜1ÖÑ'iÑjÐjùÒjrÖ  c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r   r’   r“   rp   r#   rœ  rR  rV  r6   r   rg   r   )r   s   "r   r   r   *  s<   € ÷ ñ Ø
ðØ'*ðØ47ðØ?BðØPSðà	ñr   c                ó.   € V P                  RWW434       R# )zvRaw comment INSERT for callers already inside a write txn (``add_comment``
opens its own txn and emits ``commented``).rÂ  NrH  )r’   rp   rœ  rR  rV  s   &&&&&r   Ú_insert_commentr  *  s   € ð
 	‡L�Lð	Ø '°ÐBör   r   c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   r’   r“   rp   r#   r°  r±  r’  r   r€   r   rg   r   )r   s   "r   r   r   5  s>   € ÷ ñ Ø
ðØ'*ðØ25ðØ@Nðàðð 
ñr   c               ó|   € V P                  RWV\        V4      \        \        P                  ! 4       4      34       R# )zbInsert an event row inside the caller's txn; ``run_id`` groups it by attempt (NULL = task-scoped).z[INSERT INTO task_events (task_id, run_id, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)N)r£   Ú_json_or_nullr6   r+  )r’   rp   r°  r±  r   s   &&&&$r   r1  r1  5  s6   € ð
 	‡L�Lð	!Ø#*°D¼-ÈÓ:PÔRUÔVZ×V_ÒV_ÓVaÓRbÐ"cör   r‘  r”  r“  c               ó8   € V ^8„  d   QhRRRRRRRRRRR	R
RRRR/# )r   r’   r“   rp   r#   r³   r‘  rD   r”  r“  r’  rS  r   r€   r   )r   s   "r   r   r   @  sN   € ÷ ñ Ø
ðØ'*ðØ8;ðØFSðàðà+9ðàJWðð ñr   c          
     óÞ   € \        \        P                  ! 4       4      p\        W4      pVf   R# T P                  RT;'       g    TW#V\	        V4      Wx34       V P                  RV34       V# )a£  Close the active run (``status`` defaults to ``outcome``) and clear
``current_run_id``; None when no run was active (never-claimed task).

``worker_pid`` / ``worker_started_at`` / ``claim_lock`` stay on the closed
row: they are the only evidence left of the OS process once the task row
is wiped, and :func:`kanban_db_dispatch.reap_terminal_workers` needs them
to end a worker that survived its own terminal transition.NaH  
        UPDATE task_runs
           SET status        = ?,
               outcome       = ?,
               summary       = ?,
               error         = ?,
               metadata      = ?,
               ended_at      = ?,
               claim_expires = NULL
         WHERE id = ?
           AND ended_at IS NULL
        z3UPDATE tasks SET current_run_id = NULL WHERE id = ?)r6   r+  r›   r£   r  )	r’   rp   r³   r‘  r”  r“  rS  r8  r   s	   &&$$$$$  r   Ú_end_runr   @  sl   € ô Œd�iŠi‹kÓ
€CÜ˜TÓ+€FØ‚~ÙØ‡L�Lð	ð 
×	Ð	�7˜G¨e´]À8Ó5LÈcÐZôð 	‡L�LÐFÈÈ
ÔSØ€Mr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rH   rD   rc  r6   r   r#   r   )r   s   "r   r   r   b  s!   € ÷ -ñ -�mð -¨Cð -°Cñ -r   c                ó~   € T ;'       g    RP                  4       P                  4       pV'       d   V^ ,          RV # R# )zSFirst non-blank-stripped line of ``text`` capped at ``limit`` chars; "" when empty.r8   N)r<   Ú
splitlines)rH   rc  Úliness   && r   Ú_first_liner%  b  s7   € à�ZˆZ�R×ÑÓ ×+Ñ+Ó-€Eß$ˆ5��8�F�UÐÐ,¨"Ð,r   c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   r€   r   )r   s   "r   r   r   h  s   € ÷ 5ñ 5�Cð 5˜Mñ 5r   c                ó$   € V e   \        V 4      # R# )zP``int(value)`` or ``None`` when ``value`` is ``None`` (NULL column passthrough).N)r6   r   s   &r   rµ  rµ  h  s   € àÒ*Œ3ˆu‹:Ð4°Ð4r   c               ó    € V ^8„  d   QhRRRR/# )r   Úobjr   r   rD   r   )r   s   "r   r   r   m  s   € ÷ @ñ @�sð @˜}ñ @r   c                óF   € V '       d   \         P                  ! V RR7      # R# )z7JSON text for a payload/metadata column; falsy -> NULL.F©Úensure_asciiN)r*   r/  )r)  s   &r   r  r  m  s   € ç25Œ4�:Š:�c¨Ô.Ð?¸4Ð?r   c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   rp   r#   r   rD   r   )r   s   "r   r   r   r  s"   € ÷ *ñ *Ð)ð *°Cð *¸Mñ *r   c                ój   € V P                  RV34      P                  4       pV'       d
   VR,          # R# )zGCurrent ``tasks.status`` for ``task_id``, or ``None`` when no such row.z%SELECT status FROM tasks WHERE id = ?rS  N©r£   r¤   r]  s   && r   r2  r2  r  s/   € à
�,‰,Ð>ÀÀ
Ó
K×
TÑ
TÓ
V€Cßˆ3ˆx�=Ð) TÐ)r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   r€   r   )r   s   "r   r   r   x  s'   € ÷ Qñ QÐ,ð Q°sð Q¸}ñ Qr   c                óš   € V P                  R V34      P                  4       pV'       d"   VR,          '       d   \        VR,          4      # R# )z-SELECT current_run_id FROM tasks WHERE id = ?r¢   N)r£   r¤   r6   r]  s   && r   r›   r›   x  sA   € Ø
�,‰,ÐFÈÈ
Ó
S×
\Ñ
\Ó
^€Cß),°Ð5E×1FÔ1FŒ3ˆsÐ#Õ$Ó%ÐPÈDÐPr   r   Ú_UNSETrŽ  c               ó<   € V ^8„  d   QhRRRRRRRRRRR	R
RRRRRR/	# )r   r’   r“   rp   r#   r³   rS  r‘  rD   r“  r’  Ú
synthesizer�   rŽ  r   r   r€   r   )r   s   "r   r   r   ‚  sX   € ÷ ñ Ø
ðØ'*ðØ8;ðØEHðàðà-;ðàPTðð ðð ñ	r   c          	     óV   € \        WW#WER7      pVf   V'       d   \        WW$WWR7      pV# )uY  :func:`_end_run`; when no run was active and ``synthesize`` holds, record a
zero-duration run instead so the handoff fields survive in attempt history.
``profile`` overrides the profile read off the task row for the synthesized
run â€” transitions that reassign the task (e.g. review handoff) pass the
acting profile captured before the rewrite.©r³   rS  r‘  r“  )r³   r‘  r“  rŽ  )r   Ú_synthesize_ended_run)	r’   rp   r³   rS  r‘  r“  r5  rŽ  r   s	   &&$$$$$$ r   Ú_end_or_synthesize_runr9  ‚  s-   € ô �d¨WÈWÔh€FØ‚~Ÿ*Ü& t¸gÐaiÔ{ˆØ€Mr   c               ó8   € V ^8„  d   QhRRRRRRRRRRR	R
RRRR/# )r   r’   r“   rp   r#   r³   r‘  rD   r”  r“  r’  rŽ  r   r   r6   r   )r   s   "r   r   r   ’  sP   € ÷ "#ñ "#Ø
ð"#Ø'*ð"#Ø8;ð"#ØFSð"#àð"#à+9ð"#ð ð"#ð 	ñ	"#r   c               ó`  € \        \        P                  ! 4       4      pV P                  RV34      P                  4       pV\        J d   V'       d
   VR,          MRpV'       d
   VR,          MRp	V P                  RWW’W#V\        V4      Ww3
4      p
\        V
P                  ;'       g    ^ 4      # )aÈ  Zero-duration closed run for a terminal transition on a never-claimed
task, so the handoff fields aren't silently dropped (``_end_run`` is a
no-op then). ``started_at == ended_at`` keeps elapsed stats honest. Does
NOT touch the tasks row.

``profile`` overrides the profile read off the task row: transitions that
reassign the task (e.g. review handoff) pass the acting profile captured
before the rewrite, so the run names the actor, not the new assignee.z9SELECT assignee, current_step_key FROM tasks WHERE id = ?rƒ   Nre  zå
        INSERT INTO task_runs (
            task_id, profile, step_key,
            status, outcome,
            summary, error, metadata,
            started_at, ended_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        )r6   r+  r£   r¤   r3  r  rÅ  )r’   rp   r³   r‘  r”  r“  rŽ  r8  Útrowr�  rŒ  s   &&$$$$$    r   r8  r8  ’  s¡   € ô Œd�iŠi‹kÓ
€CØ�<‰<ØCÀgÀZóç�hƒjð 	ð ”&Óß&*�$�zÖ"°ˆß+/ˆtÐ&Ö'°T€HØ
�,‰,ð	ð ˜h°À5Ì-ÐX`ÓJaØð	
ó€Cô ˆs�}‰}×!Ð! Ó"Ð"r   c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   rp   r#   r   r�   r   )r   s   "r   r   r   ¹  s'   € ÷ Jñ JÐ.ð J¸ð JÀñ Jr   c                ó0  € V P                  RV34      P                  4       pV'       d   VR,          R8X  d   R# V P                  RW34      P                  4       p\        V4      ;'       d+    \        \        VR,          4      P	                  R4      4      # )uç  True when the newest ``blocked``/``unblocked``/``gave_up`` event says the
block must wait for an operator: an explicit ``kanban_block`` (#28712), or a
breaker trip ``_record_task_failure`` stamped ``sticky`` â€” the clean-exit
protocol-violation budget or a systemic same-error wave. Those trip on a
policy independent of ``consecutive_failures``, so ``recompute_ready``'s
counter check cannot see them â€” without this the trip is promoted back to
``ready`` in the same tick and the card respawns forever. A plain
(unified-budget) ``gave_up`` carries no marker and is judged by the counter,
so raising ``failure_limit`` or ``assign_task`` to a fresh profile still
releases it; a task with no such event at all (direct DB edit) auto-recovers.
zlSELECT kind FROM task_events WHERE task_id = ? AND kind IN ('blocked', 'unblocked') ORDER BY id DESC LIMIT 1r°  rR   TzÂSELECT payload FROM task_events WHERE task_id = ? AND kind = 'gave_up' AND id > COALESCE(  (SELECT MAX(id) FROM task_events WHERE task_id = ? AND kind = 'unblocked'), 0) ORDER BY id DESC LIMIT 1r±  Ústicky)r£   r¤   r�   r2   r;   )r’   rp   r!   Útrips   &&  r   Ú_has_sticky_blockrB  ¹  s�   € ð �,‰,ð	#à%, Jó÷ �hƒjð	 ÷
 ˆs�6�{˜iÔ'ÙØ�<‰<ð	#ð &-Ð$6ó	÷
 �hƒjð 	ô �‹:×IÐIœ$œz¨$¨y­/Ó:×>Ñ>¸xÓHÓIÐIr   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rp   r#   r°  r   r€   r   zOptional[sqlite3.Row]r   )r   s   "r   r   r   Õ  s;   € ÷ 	Nñ 	NØ
ð	NØ'*ð	NØ25ð	NØ?Lð	Nàñ	Nr   c                ó”   € RpW3pVe   VR,          p. VO\        V4      N5pV P                  VR,           V4      P                  4       # )zFNewest ``task_events`` row of ``kind`` (optionally scoped to one run).z>SELECT payload FROM task_events WHERE task_id = ? AND kind = ?z AND run_id = ?z ORDER BY id DESC LIMIT 1)r6   r£   r¤   )r’   rp   r°  r   rz  rk  s   &&&&  r   Ú_latest_eventrE  Õ  sU   € ð K€CØ&˜o€FØÒØÐ Õ ˆØ'�6Ð'œ3˜v›;Ñ'ˆØ�<‰<˜Ð9Õ9¸6ÓB×KÑKÓMÐMr   c               ó$   € V ^8„  d   QhRRRRRR/# ©r   r’   r“   rp   r#   r   r   )r   s   "r   r   r   á  s"   € ÷ ñ Ð%7ð À#ð È#ñ r   c                ó¶   € V P                  RV34      P                  4       p\        \        VR4      4      pR F  pVP	                  V4      R8X  g   K   R# 	  R# )zŠ``review`` when the newest lifecycle event carries a review
``resume_status``/``retry_status``/``source_status``, else ``ready`` (legacy).a$  SELECT payload FROM task_events WHERE task_id = ? AND kind IN ('blocked', 'block_loop_detected', 'dependency_wait', 'gave_up', 'unblocked', 'changes_requested', 'review_reopened', 'status', 'reclaimed', 'stale', 'timed_out', 'crashed', 'spawn_failed', 'rate_limited') ORDER BY id DESC LIMIT 1r±  ÚreviewÚready)Úresume_statusÚretry_statusÚsource_status)r£   r¤   r2   r'   r;   )r’   rp   r!   r±  Úkeys   &&   r   Ú_resume_status_from_eventsrO  á  sa   € ð �,‰,ð	%ð
 (/ jó÷ �hƒjð ô œ # yÓ1Ó2€GÛAˆØ�;‰;�sÓ˜xÖ'Úñ Bñ r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   Úfailure_limitz
int | Noner   r6   r   )r   s   "r   r   r   ó  s#   € ÷ ;ñ ;Ð,ð ;¸Zð ;ÐSVñ ;r   c           
     ó&  € Vf   \         p^ p\        V 4      ;_uu_ 4        V P                  R4      P                  4       pV EF.  pVR,          pVR,          pVR8X  d   \	        W4      '       d   K0  V P                  RV34      P                  4       p\
        ;QJ d    R V 4       F  '       d   K   RM	  R	M! R V 4       4      '       g   K�  \        W4      pVR8X  d\   \        VR
,          ;'       g    ^ 4      p	VR,          p
V
e   \        V
4      M
\        V4      pW›8¼  d   Kæ  V P                  RW…34       MV P                  RW…34       \        YRVR8w  d   RV/MR4       V^,          pEK1  	  RRR4       V#   + '       g   i     T# ; i)u,  Promote ``todo``/``blocked`` tasks whose parents are all done/archived;
returns the count. Opens its own IMMEDIATE txn â€” call OUTSIDE any write txn.

``blocked`` is skipped when sticky (explicit ``kanban_block``) or when
``consecutive_failures`` reached the limit (else the breaker could never
trip). Limit order matches ``_record_task_failure``: ``max_retries`` >
``failure_limit`` > ``DEFAULT_FAILURE_LIMIT``.

1. The most recent block event was a worker-initiated ``kanban_block`` â€” those stay blocked until an
explicit ``kanban_unblock`` (#28712).
NzcSELECT id, status, consecutive_failures, max_retries FROM tasks WHERE status IN ('todo', 'blocked')rš   rS  rR   zYSELECT t.status FROM tasks t JOIN task_links l ON l.parent_id = t.id WHERE l.child_id = ?c              3  ó8   "  € T F  qR ,          R9   x € K  	  R# 5i)rS  Nr"  r   r  s   & r   rc   Ú"recompute_ready.<locals>.<genexpr>  s   é € ÐHÁ¸1�X•;Ð"6Ö6Ãùrý  FTr`  rj  z?UPDATE tasks SET status = ? WHERE id = ? AND status = 'blocked'z<UPDATE tasks SET status = ? WHERE id = ? AND status = 'todo'ÚpromotedrJ  )	ÚDEFAULT_FAILURE_LIMITr-  r£   rO  rB  ÚallrO  r6   r1  )r’   rQ  rU  Ú	todo_rowsr!   rp   Ú
cur_statusr  rK  ÚfailuresÚ
task_limitÚeffective_limits   &&          r   rš  rš  ó  s‰  € ð ÒÜ-ˆØ€HÜ	�4��Ø—L‘Lð=ó
÷ ‰(‹*ð 	ô ˆCØ˜$•iˆGØ˜X�ˆJØ˜YÔ&Ô+<¸T×+KÒ+KáØ—l‘lð'à)0¨
ó÷ ‰h‹jð	 ÷
 ‹sÑHÁÓH�s�sŠsÑHÁÓH×HÔHÜ :¸4Ó I�Ø Ô*ô  # 3Ð'=Õ#>×#CÐ#CÀ!ÓD�HØ!$ ]Õ!3�Jà+5Ò+Aœ˜JœÜ  Ó/ð $ð  Ô2Ù Ø—L‘Lð>Ø@MÐ?Wõð
 —L‘LØVØ&Ð0ôô Ø :Ø1>À'Ô1I�X˜}Ñ-Ètôð ˜A•“ñM ÷ 
ðX €O÷Y 
ŽðX €Oús%    A=E?ÂE?Â2E?Ã&E?Ã6A?E?Å?F	c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r   3  s"   € ÷ ñ Ð/ð ¸#ð À$ñ r   c                óH   € V P                  RV34      P                  4       RJ # )zEReturn whether every direct parent is terminal for dependency gating.zƒSELECT 1 FROM task_links l JOIN tasks p ON p.id = l.parent_id WHERE l.child_id = ? AND p.status NOT IN ('done', 'archived') LIMIT 1Nr0  r§  s   &&r   Ú_parents_satisfiedr_  3  s0   € à�<‰<ð	;ð >E¸Jó	÷
 �hƒj�Dðð r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   zlist[tuple[str, str]]r   )r   s   "r   r   r   =  s#   € ÷ 8ñ 8Ð0ð 8¸3ð 8ÐCXñ 8r   c                ó�   € V P                  RV34      P                  4       pV Uu. uF  q3R,          VR,          3NK  	  up# u upi )z÷``(parent_id, status)`` for every direct parent :func:`_parents_satisfied`
still counts as open (``done`` / ``archived`` release the child), in id
order, so a refusal or a board view can name the blockers instead of the
caller guessing. Read-only.z–SELECT p.id, p.status FROM task_links l JOIN tasks p ON p.id = l.parent_id WHERE l.child_id = ? AND p.status NOT IN ('done', 'archived') ORDER BY p.idrš   rS  r¡  )r’   rp   rQ  r!   s   &&  r   Úunsatisfied_parentsrb  =  sO   € ð
 �<‰<ð	ð "˜ó	÷
 �hƒjð 	ñ 37Ó7±$¨3��Y˜˜H�Ó&±$Ñ7Ð7ùÒ7s   ¦AÚevent_extrac               ó8   € V ^8„  d   QhRRRRRRRRRRR	RR
RRR/# )r   r’   r“   rp   r#   rM  ÚlockÚexpiresr6   r8  rc  r’  r   r€   r   )r   s   "r   r   r   K  sN   € ÷ ,ñ ,Ø
ð,Ø'*ð,Ø;>ð,ØFIð,ØTWð,Ø^að,à"ð,ð ñ,r   c               ó²  € V P                  RV R2W4WQ34      pVP                  ^8w  d   R# V P                  RV34      P                  4       pT P                  RY'       d
   VR,          MRV'       d
   VR,          MRY4V'       d
   VR,          MRV34      p	V	P                  p
V P                  R	W¡34       \	        YR
RVRVRV
/T;'       g    / CV
R7       V
# )zqCAS ``source_status -> running``, open a run row, emit ``claimed``; None
when the CAS lost. Caller holds the txn.zê
        UPDATE tasks
           SET status        = 'running',
               claim_lock    = ?,
               claim_expires = ?,
               started_at    = COALESCE(started_at, ?)
         WHERE id = ?
           AND status = 'z,'
           AND claim_lock IS NULL
        NzNSELECT assignee, max_runtime_seconds, current_step_key FROM tasks WHERE id = ?zÞ
        INSERT INTO task_runs (
            task_id, profile, step_key, status,
            claim_lock, claim_expires, max_runtime_seconds,
            started_at
        ) VALUES (?, ?, ?, 'running', ?, ?, ?, ?)
        rƒ   re  rb  z0UPDATE tasks SET current_run_id = ? WHERE id = ?Úclaimedre  rf  r   ©r   )r£   r‰  r¤   rÅ  r1  )r’   rp   rM  re  rf  r8  rc  rŒ  r<  Úrun_curr   s   &&&&&&$    r   Ú_claim_and_open_runrk  K  s  € ð �,‰,ðð (˜ð )	ð		ð 
˜Ð%ó€Cð ‡|�|�qÔÙØ�<‰<ð	"Ø$+ :ó÷ �hƒjð 	ð �l‰lð	ð ®�T˜*Ö%°4×UY¸Ð>PÖ9QÐ_cØ¿$˜4Ð 5Ö6ÀDÈ#ð	
ó€Gð ×Ñ€FØ‡L�LÐCÀfÐEVÔWÜØ�yØ	��y '¨8°VÐSÀ×@QÐ@QÈrÐSÐ\bõð €Mr   r¸   Úclaimerc          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# ©r   r’   r“   rp   r#   r¸   r€   rl  rD   r   r~   r   )r   s   "r   r   r   z  s6   € ÷  ñ  Ø
ð Ø'*ð Ø<Ið àð ð ñ r   c          
     óò  € \        \        P                  ! 4       4      pT;'       g    \        4       pV\        V4      ,           p\	        V 4      ;_uu_ 4        \        W4      '       g.   V P                  RV34       \        WRRR/4        RRR4       R# \        WR
VRR7       \        WRWVV4      pVf    RRR4       R# \        W4      pRRR4       \        R	XVX4       V#   + '       g   i     L ; i)z Atomically transition ``ready -> running``.

Returns the claimed ``Task`` on success, ``None`` if the task was
already claimed (or is not in ``ready`` status).
r„  Úclaim_rejectedr  Úparents_not_doneNrJ  zinvariant recovery on re-claim©Ústatusesr8  ÚnoteÚkanban_task_claimed)rJ  )r6   r+  rÅ  r½   r-  r_  r£   r1  Ú_reclaim_dangling_runrk  rê  r…   )	r’   rp   r¸   rl  r8  re  rf  r   rh  s	   &&$$     r   Ú
claim_taskrw  z  sâ   € ô Œd�iŠi‹kÓ
€CØ×#Ð#”k“m€DØÔ.¨{Ó;Õ;€GÜ	�4��ô " $×0Ò0Ø�L‰Lð4Ø6=°Zôô ˜$Ð)9¸HÐFXÐ;YÔZØ÷ 
‰ô 	Ø J°CÐ>^õ	
ô % T°G¸TÈCÓPˆØŠ>Ø÷# 
‰ô$ ˜4Ó)ˆ÷% 
ô& Ð)¨7°G¸VÔDØ€N÷) 
�ús   Á5C&Â"C&ÃC&Ã&C6	c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# rn  r   )r   s   "r   r   r   �  s6   € ÷ 'ñ 'Ø
ð'Ø'*ð'Ø<Ið'àð'ð ñ'r   c               óè  € \        \        P                  ! 4       4      pT;'       g    \        4       pV\        V4      ,           p\	        V 4      ;_uu_ 4        \        W4      '       gA   V P                  RV34      pVP                  ^8X  d   \        WRRRRR/4        RRR4       R# \        WRWVVRR/R7      pVf    RRR4       R# \        W4      uuRRR4       #   + '       g   i     R# ; i)	zªAtomic ``review -> running`` (None when lost). Parents are re-checked
(one may have reopened meanwhile) and a NEW run tracks the reviewer
separately from the implementer.zZUPDATE tasks SET status = 'todo' WHERE id = ? AND status = 'review' AND claim_lock IS NULLr  r  Úparent_reopenedrM  rI  N)rc  )r6   r+  rÅ  r½   r-  r_  r£   r‰  r1  rk  rê  )	r’   rp   r¸   rl  r8  re  rf  r…  r   s	   &&$$     r   Úclaim_review_taskr{  �  sÛ   € ô Œd�iŠi‹kÓ
€CØ×#Ð#”k“m€DØÔ.¨{Ó;Õ;€GÜ	�4��Ü! $×0Ò0Ø—l‘lðLØNUÈZóˆGð ×Ñ 1Ô$ÜØÐ#4ØÐ0°/À8ÐLôð ÷ 
‰ô %Ø˜8 T°CÀoÐW_ÐE`ô
ˆð Š>Ø÷! 
‰ô" ˜Ó&÷# 
��‹ús   ÁAC Â*C Ã
C Ã C1	c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r’   r“   rp   r#   r   r€   r   r   )r   s   "r   r   r   »  s2   € ÷ Mñ MØ
ðMØ'*ðMØ4AðMàñMr   c                ó¤   € Vf   \        W4      pVf   R# \        WRV4      p\        \        VR4      4      pVP	                  R4      R8X  d   R# R# )uÀ   ``review`` when the run's ``claimed`` event says ``source_status=review``,
else ``ready`` â€” one place, so crash/timeout/reclaim can't silently turn a
reviewer run into an implementation run.rJ  rh  r±  rM  rI  )r›   rE  r2   r'   r;   )r’   rp   r   ro   r±  s   &&&  r   Ú_retry_status_for_runr~  »  sU   € ð ‚~Ü  Ó/ˆØ‚~ÙÜ˜$¨°FÓ;€EÜœ %¨Ó3Ó4€GØ—{‘{ ?Ó3°xÔ?ˆ8ÐLÀWÐLr   Ú	completedr#  Úreview_requestedrI  Úchanges_requestedr  c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   Úexpected_run_idr€   r   rD   r   )r   s   "r   r   r   Ô  s,   € ÷ ñ Ø
ðØ'*ðØ=Jðàñr   c                óò  € \        W4      pVf   R# VeŽ   V P                  R\        V4      V34      P                  4       pV'       d   VR,          e   \	        VR,          4      MRp\
        P                  V4      pVe   V# Vf   VP                  \        V4      8w  d   R# VP                  R9   d:   V P                  RV34      P                  4       pV'       d   VR,          R8X  d   R# VP                  # )	uÍ   Lifecycle status as seen by ONE run: terminal handoffs bind to that run,
any other ownership loss is ``superseded`` â€” otherwise an old goal loop
would read the successor's live ``running`` and mutate it.Nz:SELECT outcome FROM task_runs WHERE id = ? AND task_id = ?r³   Ú
supersededzGSELECT kind FROM task_events WHERE task_id = ? ORDER BY id DESC LIMIT 1r°  r�  >   r  rJ  )	rê  r£   r6   r¤   r#   Ú_RUN_OUTCOME_TERMINAL_STATUSr;   r¢   rS  )r’   rp   rƒ  r}   r!   r³   Úterminal_statusro   s   &&&     r   Úgoal_run_statusrˆ  Ô  sê   € ô �DÓ"€DØ‚|ÙØÒ"Ø�l‰lØHÜ�Ó! 7Ð+ó
÷ ‰(‹*ð 	÷ *-°°YµÒ1K”#�c˜)•nÔ%ÐQUˆÜ6×:Ñ:¸7ÓCˆØÒ&Ø"Ð"ØÒ $×"5Ñ"5¼¸_Ó9MÔ"MÙØ‡{�{Ð'Ô'Ø—‘ð'Ø)0¨
ó
÷ ‰(‹*ð 	÷ �U˜6•]Ð&9Ô9Ù&Ø�;‰;Ðr   c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r’   r“   rp   r#   r¸   r€   rl  rD   r   r�   r   )r   s   "r   r   r   ò  s6   € ÷ ñ Ø
ðØ'*ðØ<Iðàðð 
ñr   c               ód  € \        \        P                  ! 4       4      \        V4      ,           pT;'       g    \        4       p\	        V 4      ;_uu_ 4        V P                  RWAV34      pVP                  ^8w  d    RRR4       R# \        WV4        RRR4       R#   + '       g   i     R# ; i)z0Extend a running claim; True if we still own it.zYUPDATE tasks SET claim_expires = ? WHERE id = ? AND status = 'running' AND claim_lock = ?NFT)r6   r+  r½   rÅ  r-  r£   r‰  Ú_extend_run_claim)r’   rp   r¸   rl  rf  re  rŒ  s   &&$$   r   Úheartbeat_claimrŒ  ò  s‡   € ô
 ”$—)’)“+ÓÔ!;¸KÓ!HÕH€GØ×#Ð#”k“m€DÜ	�4��Ø�l‰lðEØGNÐY]ÐF^ó
ˆð �<‰<˜1ÔØ÷ 
‰ô 	˜$¨Ô1Ø÷ 
��Šús   Á'BÂBÂB/	c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   rf  r6   r   r€   r   )r   s   "r   r   r   	  s)   € ÷ ñ Ð.ð ¸ð Àsð È}ñ r   c                óJ   € \        W4      pVe   V P                  RW#34       V# )zKMirror a task claim extension onto its active run row; returns that run id.z3UPDATE task_runs SET claim_expires = ? WHERE id = ?)r›   r£   )r’   rp   rf  r   s   &&& r   r‹  r‹  	  s)   € ä˜TÓ+€FØÒØ�‰ÐJÈWÐL]Ô^Ø€Mr   Ú	signal_fnrQ  c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rQ  r€   r   r6   r   )r   s   "r   r   r   	  s)   € ÷ pñ pØ
ðpØ@Mðpàñpr   c               ó>  € \        \        P                  ! 4       4      p^ p\        4       pV P                  RV34      P	                  4       p^ RIHpHp V EF¯  p	V! W	R,          4      '       d   K  V	R,          ;'       g    RP                  V4      p
\        V	R,          4      pVRJ;'       d    W;,
          \        8„  p\        V	R4      pV
'       d?   V	R	,          '       d0   \        V	R	,          V4      '       d   V'       g   \        W	V4       K¼  \        V	R	,          V	R,          WR
7      p\        V4      '       d!   \!        W	R,          V	R,          W>RR7       EK	  \#        V 4      ;_uu_ 4        V! W	R,          \%        VP'                  R4      4      R7      '       d    RRR4       EKX  \)        W	R,          4      pV P                  RWùR,          V	R,          W9R	,          34      pVP*                  ^8w  d    RRR4       EK±  \-        W	R,          VRV	R,           2RV	R,          R	\/        V	R	,          4      R\        V	R,          4      RVRVRV
R\%        V4      RV/R7      pV^,          pRRR4       \1        W	R,          RV	R,           2RVRRR	\/        V	R	,          4      RX/R7       \3        R4      '       g   EKm  \5        RV	R,          \7        4       V	R,          X\/        V	R	,          4      \%        V4      VR7       EK²  	  V#   + '       g   i     L¤; i)uù  Reclaim ``running`` tasks whose claim expired; returns the count reclaimed.

Every reclaim that actually releases a claim is a non-success attempt and
is booked through ``_record_task_failure`` (#111306): a claim that expired
without a worker ever spawning otherwise loops claim -> reclaim -> claim
with ``consecutive_failures`` stuck at 0, so the breaker never trips.
``reclaim_task`` (operator path) deliberately resets the counter instead.

A host-local worker that is still alive gets its claim *extended* instead
(a slow model can sit longer than the TTL inside one tool-free call, so no
heartbeat) â€” unless ``last_heartbeat_at`` is older than
``DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDS`` (wedged; ``_touch_activity``
keeps any genuinely active worker fresh). Safe to call often.

Reclaiming a live worker mid-flight produces the spawn- then-immediately-reclaim loop seen on slow
models that spend longer than ``DEFAULT_CLAIM_TTL_SECONDS`` inside a single tool-free LLM call (#23025):
no tool calls means no ``kanban_heartbeat``, even though the subprocess is healthy.
Backstop (#29747 gap 3): if the worker's PID is still alive but its ``last_heartbeat_at`` is stale by
more than ``DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDS`` (1h), the worker has been making no observable
progress and we reclaim anyway â€” even if ``_pid_alive`` is still true. This catches the
wedged-in-a-logic-loop case where the process is technically running but accomplishing nothing.
``_touch_activity`` (run_agent.py) bridges chunk-level liveness into ``last_heartbeat_at`` via #31752,
so any genuinely active worker keeps its heartbeat fresh as a side effect of normal API traffic.
``enforce_max_runtime`` and ``detect_crashed_workers`` remain the upper bounds for genuinely wedged or
dead workers.
zÁSELECT id, claim_lock, worker_pid, worker_started_at, claim_expires, last_heartbeat_at,        assignee FROM tasks WHERE status = 'running' AND claim_expires IS NOT NULL   AND claim_expires < ?)Úowner_reclaim_blockedÚ owner_reclaim_blocked_after_killrš   rZ  r8   rc  NÚworker_started_atr™   ©r�  rW  Úttl_expired_worker_alive©r  Útermination_attempted)Ú	signalledzñUPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL WHERE id = ? AND status = 'running' AND claim_lock IS ? AND claim_expires IS NOT NULL AND claim_expires < ? AND worker_pid IS ?zstale_lock=Ú
stale_lockr[  r8  Ú
host_localÚheartbeat_stalerL  ©r”  r±  Ú	reclaimedF)r³   rQ  Úrelease_claimÚend_runÚevent_payload_extraÚon_kanban_worker_stale_claimrƒ   )r‚   rƒ   r   r™   rœ  rL  )r6   r+  rÉ  r£   rO  Ú hermes_cli.kanban_owner_recoveryr’  r“  rí  r|  Ú)DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDSr'   Ú_worker_aliveÚ_extend_live_stale_claimÚ_terminate_reclaimed_workerÚ_worker_survived_terminationÚ_defer_reclaim_for_live_workerr-  r�   r;   r~  r‰  Ú_record_reclaimrµ  Ú_record_task_failurer�   r{   r„   )r’   r�  rQ  r8  rž  Úhost_prefixÚstaler’  r“  r!   r›  Úhbrœ  rW  ÚterminationrL  rŒ  r   s   &$$               r   Úrelease_stale_claimsr°  	  sà  € ô: Œd�iŠi‹kÓ
€CØ€IÜ“.€KØ�L‰Lð	"ð %( 6ó÷ �hƒjð 
÷ iÜˆÙ  ¨4¥y×1Ò1ÙØ˜,Õ'×-Ð-¨2×9Ñ9¸+ÓFˆ
Ü�sÐ.Õ/Ó0ˆð  D˜.×cÐc¨c­hÔ:cÑ-cˆÜ˜cÐ#6Ó7ˆ
ß˜3˜|×,Ô,´¸sÀ<Õ?PÐR\×1]Ò1]ß'Ü$ T°Ô4Ùä1Ø�Õ˜s <Õ0¸Iô
ˆô (¨×4Ò4Ü*Ø˜$•i  \Õ!2°CØ1õò Ü�t�_�_ñ 0°¸$µiÌ4ÐP[×P_ÑP_Ð`wÓPxÓKy×zÓzØ÷	 Š_ô
 1°¸4µyÓAˆLØ—,‘,ð&ð  4�y¨#¨lÕ*;¸SÀlÕBSÐTóˆCð �|‰|˜qÔ Ø÷! Š_ô" %Ø˜$•i Ø# C¨Õ$5Ð#6Ð7à  # lÕ"3Ø ¤(¨3¨|Õ+<Ó"=Ø#¤Y¨s°?Õ/CÓ%DØ'¨Ø˜3Ø  *Ø%¤t¨OÓ'<Ø" Lð	ôˆFð ˜�NˆI÷? ôF 	Ø�d•)˜{¨3¨|Õ+<Ð*=Ð>Ø¨}Ø¨Ø!-¬x¸¸LÕ8IÓ/JÈNÐ\hÐ iõ		
ô %Ð%C×DÕDÜ'Ø.°°Dµ	ÔARÓATØ˜Z�°ÄHÈSÐQ]ÕM^ÓD_Ü $ _Ó 5ÀL÷ð ñG ðP Ð÷a �_ús   Å21LÆ.ALÈA)LÌLc               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r   r’   r“   rp   r#   r¯  r/   r”  r±  r   r€   r   )r   s   "r   r   r   	  s<   € ÷ 
ñ 
Ø
ð
Ø'*ð
Ø9=ð
ØILð
ØW[ð
àñ
r   c          	     ób   € \        WRRW2R7      pVP                  V4       \        WRWER7       V# )zŠClose the active run as ``reclaimed`` and emit the ``reclaimed`` event
(payload merged with the termination report). Caller holds the txn.rž  )r³   rS  r”  r“  ri  )r   Úupdater1  )r’   rp   r¯  r”  r±  r   s   &&&$$ r   rª  rª  	  s6   € ô
 Ø˜{°;Àeô€Fð ‡N�N�;ÔÜ�$ ¨gÕEØ€Mr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   r!   rr  r8  r6   r   rg   r   )r   s   "r   r   r   Œ	  s*   € ÷ 
ñ 
Ð#5ð 
¸Kð 
Ècð 
ÐVZñ 
r   c                óÞ  € V\        4       ,           p\        V 4      ;_uu_ 4        V P                  RW1R,          VR,          V34      pVP                  ^8w  d    RRR4       R# \	        WR,          V4      p\        WR,          RRRR\        VR,          4      RVR,          R	\        VR
,          4      RVR\        VR,          4      /VR7       RRR4       R#   + '       g   i     R# ; i)z±TTL-expired claim whose host-local worker is alive: extend instead of
reclaiming (``claim_extended`` event). CAS on the same expired lock so a
concurrent reclaimer wins cleanly.z”UPDATE tasks SET claim_expires = ? WHERE id = ? AND status = 'running'   AND claim_lock IS ?   AND claim_expires IS NOT NULL   AND claim_expires < ?rš   rZ  NÚclaim_extendedr  Ú	pid_aliver™   Úclaim_expires_wasr[  Úclaim_expires_nowrc  ri  )r½   r-  r£   r‰  r‹  r1  r6   r|  )r’   r!   r8  Únew_expiresrŒ  r   s   &&&   r   r¦  r¦  Œ	  sØ   € ð Ô2Ó4Õ4€KÜ	�4��Ø�l‰lð&ð )4¸µYÀÀLÕ@QÐSVÐ'Wó
ˆð �<‰<˜1ÔØ÷ 
‰ô # 4¨T­°KÓ@ˆÜØ�d•)Ð-à˜+Øœc # lÕ"3Ó4Ø˜c ,Õ/Ø#¤Y¨s°?Õ/CÓ%DØ# [Ø#¤Y¨sÐ3FÕ/GÓ%Hðð õ	
÷ 
��Šús   ¥6CÁ%A,CÃC,	r  c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   r  rD   r   r�   r   )r   s   "r   r   r   ª	  s,   € ÷ !ñ !Ø
ð!Ø'*ð!Ø7Dð!à	ñ!r   c               ó  € V P                  RV34      P                  4       pV'       g   R# VR,          R8w  d   VR,          f   R# VR,          p\        VR,          WSVR,          R	7      p\        V 4      ;_uu_ 4        \	        W4      pV P                  R
WqV34      pVP
                  ^8w  d    RRR4       R# \        YTV'       d   RV 2MRV 2RRRVRVRV/R7       RRR4       \        W4       R#   + '       g   i     L; i)zƒOperator reclaim regardless of TTL: release the claim, restore the source
phase, reset the failure counter. False when not running.úPSELECT status, claim_lock, worker_pid, worker_started_at FROM tasks WHERE id = ?FrS  rQ   rZ  Nr™   r”  r•  zÀUPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL WHERE id = ? AND status IN ('running', 'ready', 'blocked') AND claim_lock IS ?zmanual_reclaim: zmanual_reclaim lock=ÚmanualTr  Ú	prev_lockrL  r�  )r£   r¤   r§  r-  r~  r‰  rª  Ú_clear_failure_counter)	r’   rp   r  r�  r!   r¿  r¯  rL  rŒ  s	   &&$$     r   Úreclaim_taskrÁ  ª	  s  € ð
 �,‰,ØZÐ]dÐ\fóç�hƒjð ÷ ÙØ
ˆ8…}˜	Ô! c¨,Õ&7Ò&?áØ�LÕ!€IÜ-ØˆLÕ˜9ÀcÐJ]ÕF^ô`€Kä	�4��Ü,¨TÓ;ˆØ�l‰lð"ð %1¸9Ð#Eó	
ˆð �<‰<˜1ÔØ÷ 
‰ô 	Ø˜;ß17Ð$ V HÑ-Ð?SÐT]ÐS^Ð=_Ø˜t X¨v°{ÀIÈ~Ð_kÐlõ	
÷ 
ô  ˜4Ô)Ù÷# 
�ús   Á?2C7Â;'C7Ã7D	Úreclaim_firstc               ó0   € V ^8„  d   QhRRRRRRRRR	RR
R/# )r   r’   r“   rp   r#   rŽ  rD   rÂ  r�   r  r   r   )r   s   "r   r   r   Î	  s>   € ÷ ñ Ø
ðØ'*ðØ5BðØVZðàðð 
ñr   c               ó~   € V'       d   \        YT;'       g    RR7        \        WV4      #   \         d     R# i ; i)u�   Reassign (None unassigns); a running task is refused unless
``reclaim_first`` releases its claim â€” the "this profile's model is broken" path.Úreassignr—  F)rÁ  rq  r6  )r’   rp   rŽ  rÂ  r  s   &&&$$r   Úreassign_taskrÆ  Î	  s?   € ÷ ä�T¨6×+?Ð+?°ZÕ@ðÜ˜4¨'Ó2Ð2øÜô ò ðús   ¡- ­<»<c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   Úcompleting_task_idr#   Úclaimed_idsrŸ   r   ztuple[list[str], list[str]]r   )r   s   "r   r   r   à	  s,   € ÷ &ñ &Ø
ð&Ø25ð&ØDQð&à ñ&r   c                óÆ  € \        \        P                  R T;'       g    .  4       4      4      pV'       g   . . 3# V P                  RV34      P	                  4       pVf   . V3# VR,          pRP                  R.\        V4      ,          4      pV P                  RV R2\        V4      4      P                  4       pV Uu/ uF  qˆR	,          VR
,          bK  	  p	p\        \        W4      4      p
. p. pV Fa  pV	P                  V4      pVRJ;'       d'    T;'       d    Wå8H  ;'       g    Wá8H  ;'       g    WÚ9   pV'       d   TMTP                  V4       Kc  	  W¼3# u upi )zèPartition ``claimed_ids`` into (verified, phantom). Verified = the row
exists AND ``created_by`` is the completing task's assignee or id, OR the
card is linked as its child (created elsewhere, attached by the worker).
Never mutates.c              3  ó–   "  € T F?  p\        V4      P                  4       '       g   K$  \        V4      P                  4       x € KA  	  R # 5ir`   )r#   r<   )rb   Úxs   & r   rc   Ú(_verify_created_cards.<locals>.<genexpr>ç	  s2   é € Ð _Ñ:K°AÔPSÐTUÓPV×P\ÑP\×P^¤¤ Q£§¡§ Ó:Kùs
   ‚!A	¨!A	z'SELECT assignee FROM tasks WHERE id = ?Nrƒ   rø  rM  z.SELECT id, created_by FROM tasks WHERE id IN (rN  rš   rU  )r¥   r/   rY  r£   r¤   r\   r  r*  rO  rÎ   rª  r;   r   )r’   rÈ  rÉ  Úorderedr!   Úcompleting_assigneerP  rQ  rž  r	  Úlinked_childrenÚverifiedÚphantomÚcidrU  Útrusteds   &&&             r   Ú_verify_created_cardsrÕ  à	  sm  € ô ”4—=‘=Ñ _¸+×:KÐ:KÈÐ:KÓ _Ó_Ó`€GßØ�2ˆvˆà
�,‰,Ð@ÐCUÐBWÓ
X×
aÑ
aÓ
c€CØ
‚{à�7ˆ{ÐØ˜j�/Ðð —8‘8˜S˜E¤C¨£LÕ0Ó1€LØ�<‰<Ø
8¸¸ÀaÐHÌ%ÐPWË.óç�hƒjð 	ñ 04Ó4©t¨!ˆt�W�a˜•oÒ%©t€EÐ4ô !$¤I¨dÓ$GÓ H€Oà€HØ€GÛˆØ—Y‘Y˜s“^ˆ
Ø DÐ(÷ 
ð 
Ø ×FÐF ZÑ%F÷ &ð &ØÑ/÷&ð &àÑ%ð 	÷
 ‰ '×1Ñ1°#Ö6ñ ð ÐÐùò! 5s   ÃEz\bt_[a-f0-9]{8,}\bc               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rH   r#   r   rK  r   )r   s   "r   r   r   
  s'   € ÷ Sñ SÐ&8ð SÀð SÈ	ñ Sr   c                óz   € V'       g   . # \        V \        P                  \        P	                  V4      4      4      # )zT``t_<hex>`` references in ``text`` that don't resolve to a task (deduped; advisory).)rS  r/   rY  Ú_TASK_ID_PROSE_REÚfindall)r’   rH   s   &&r   Ú_scan_prose_for_phantom_idsrÚ  
  s,   € çØˆ	Ü˜T¤4§=¡=Ô1B×1JÑ1JÈ4Ó1PÓ#QÓRÐRr   c                  ó2   a € ] tR tRtRtR V 3R lltRtV ;t# )ÚHallucinatedCardsErrori
  z¸``complete_task`` refused: ``created_cards`` has ids that don't exist or
weren't created by this worker (``.phantom``). A ``ValueError`` so tool
error handlers treat it as recoverable.c               ó    € V ^8„  d   QhRRRR/# )r   rÒ  rK  rÈ  r#   r   )r   s   "r   r   Ú#HallucinatedCardsError.__annotate__
  s   € ÷ 
ñ 
 	ð 
¸sñ 
r   c                	óv   <€ \        V4      V n        W n        \        SV `  R RP                  V4       24       R# )z`completion blocked: claimed created_cards that do not exist or were not created by this worker: rX   N)r¥   rÒ  rÈ  ÚsuperÚ__init__r\   )ÚselfrÒ  rÈ  Ú	__class__s   &&&€r   rá  ÚHallucinatedCardsError.__init__
  s;   ø€ Ü˜G“}ˆŒØ"4ÔÜ‰Ñð3Ø37·9±9¸WÓ3EÐ2FðHö	
r   )rÈ  rÒ  ©r„  r…  r†  r‡  rˆ  rá  r‹  Ú__classcell__©rã  s   @r   rÜ  rÜ  
  s   ø† ñ/÷
ö 
r   rÜ  c                  ó2   a € ] tR tRtRtR V 3R lltRtV ;t# )ÚEmptyCompletionErrori"
  zÌ``complete_task`` refused: no substantive ``result``, ``summary``, or
stored result. A ``ValueError`` so tool error handlers treat it as
recoverable. Review approvals are exempt (the human is the record).c               ó   € V ^8„  d   QhRR/# ©r   rp   r#   r   )r   s   "r   r   Ú!EmptyCompletionError.__annotate__'
  s   € ÷ 
ñ 
 ñ 
r   c                	ó:   <€ Wn         \        SV `	  R V R24       R# )zcompletion blocked: z" has no result or summary evidenceN)rp   rà  rá  ©râ  rp   rã  s   &&€r   rá  ÚEmptyCompletionError.__init__'
  s"   ø€ ØŒÜ‰ÑØ" 7 )Ð+MÐNö	
r   )rp   rå  rç  s   @r   ré  ré  "
  s   ø† ñK÷
ö 
r   ré  c                  ó   € ] tR tRtRtRtR# )ÚArtifactPreservationErrori.
  z?Raised when a declared scratch deliverable cannot be preserved.r   NrÝ  r   r   r   rñ  rñ  .
  s   † ÝIr   rñ  c                  ó2   a € ] tR tRtRtR V 3R lltRtV ;t# )ÚLiveClaimErrori2
  a7  ``complete_task`` refused: the task is ``running`` under a live claim and
the caller neither owns its run (``expected_run_id``) nor passed ``force``.
Completing anyway would close the worker's run row underneath a process
that is still executing. A ``ValueError`` so tool error handlers treat it
as recoverable.c               ó   € V ^8„  d   QhRR/# rë  r   )r   s   "r   r   ÚLiveClaimError.__annotate__9
  s   € ÷ 
ñ 
 ñ 
r   c                	ó,   <€ \         SV `  V R 24       R# )z™ is running under a live worker claim; pass expected_run_id (worker ownership) or force=True (explicit operator override) instead of closing the live runN)rà  rá  rî  s   &&€r   rá  ÚLiveClaimError.__init__9
  s   ø€ Ü‰ÑØˆið &ð &ö	
r   r   rå  rç  s   @r   ró  ró  2
  s   ø† ñ÷
ö 
r   ró  c               ó   € V ^8„  d   QhRR/# r)  r   )r   s   "r   r   r   A
  s   € ÷ ñ ˜Dñ r   c                ó¸   € \        V R,          R8H  ;'       d?    V R,          RJ;'       d,    V R,          ;'       d    \        V R,          V R,          4      4      # )a‹  True when a ``running`` task's claim still protects a run: the worker process
it spawned exists (PID + start-time fingerprint). A claim whose worker is gone,
or a library/CLI claim that never spawned one, has no run to protect. TTL expiry
is deliberately not consulted: ``reclaim_stale_tasks`` extends, not reclaims, the
claim of a live worker, so the process is the liveness authority here too.rS  rQ   rZ  Nr™   r”  )r�   r¥  )r<  s   &r   Ú_claim_is_liverú  A
  sm   € ô ØˆX�˜)Ñ#÷ 	Ið 	IØ�Õ dÐ*÷	Ið 	Ià�Õ÷	Ið 	Iô ˜$˜|Õ,¨dÐ3FÕ.GÓHó	ð r   Úcreated_cardsrƒ  Úfire_lifecycle_hookTÚforcec               ó@   € V ^8„  d   QhRRRRRRRRRR	R
RRRRRRRRR/
# )r   r’   r“   rp   r#   rO   rD   r‘  r“  r’  rû  rö  rƒ  r€   rü  r�   rý  r   r   )r   s   "r   r   r   O
  st   € ÷ hñ hØ
ðhØ'*ðhØ7Dðhàðhà-;ðhð +ðhð ERðhð ð	hð .2ð	hð
 
ñhr   c               ó^  € \        \        P                  ! 4       4      p	\        W4      '       g   R# ^ RIHp
Hp \        YYS;'       g    T4      p\        WW#R7       \        WWCVR7      pVe   TMTpV
! WWd4      pVRJ d   R# \        V 4      ;_uu_ 4        \        W4      '       g    RRR4       R# Ve   V! WV4      '       g    RRR4       R# V P                  RV34      P                  4       pV'       d
   VR,          MRpVf-   V'       g%   V'       d   \        V4      '       d   \        V4      hRpW)V3pVe   VR	,          p. VO\        V4      N5pV P                  VV4      P                  ^8w  d    RRR4       R# \        V\         4      '       d   \#        WWI4       \%        WR
RVVR7      pVfU   V'       g   V'       g   V'       g   VR8X  d6   YÔppVR8X  d   V'       g   V'       g   \&        pRRRR/p\)        WR
VVR7      pTpVR8X  d   V'       g   \&        p\+        WR
\-        VVWÄ4      VR7       RRR4       \/        WXW2V4       \1        W4       \3        V 4       \5        W4       \7        W4      pV'       d   \9        RVVVVR7       R#   + '       g   i     Ld; i)u(  ``running|ready|blocked|review -> done``; records ``result``.

``ready`` is accepted for manual CLI completion, ``review`` for human
approval. A ``running`` task under a live claim is only completed with
proof of ownership (``expected_run_id``) or ``force=True`` (explicit
operator override) â€” otherwise :class:`LiveClaimError`, the same fence
:func:`request_review` applies. With no active run the handoff fields survive via
:func:`_synthesize_ended_run`. ``summary`` (defaults to ``result``) and
``metadata`` land on the closing run for :func:`build_worker_context`.
``created_cards`` are verified first â€” a phantom id raises
:class:`HallucinatedCardsError` after an auditable event; afterwards the
prose is scanned for unresolvable ``t_<hex>`` refs (advisory event only).
Completions from non-review statuses need evidence: a stripped ``result``
or ``summary``, or a stripped result already stored on the card. Empty or
whitespace-only evidence raises :class:`EmptyCompletionError` after an
auditable event. Approving a card out of ``review`` stays exempt.
F)Úprepare_acceptanceÚrecord_acceptance)rO   r‘  ©r‘  rO   Nr½  rS  aô  
                UPDATE tasks
                   SET status       = 'done',
                       result       = ?,
                       completed_at = ?,
                       claim_lock   = NULL,
                       claim_expires= NULL,
                       worker_pid   = NULL,
                       block_kind   = NULL,
                       block_recurrences = 0
                 WHERE id = ?
                   AND status IN ('running', 'ready', 'blocked', 'review')
                ú AND current_run_id = ?r  r#  r7  rI  rM  Úapprovalr¾  ©r³   r‘  r“  ri  Úkanban_task_completed)r‘  T)r6   r+  r_  Ú%hermes_cli.kanban_pr_acceptance_storer   r  Ú_gate_created_cardsÚ_gate_empty_completionÚ!_merge_completion_prose_artifactsr-  r£   r¤   rú  ró  r‰  r   r/   Ú_stage_completion_artifactsr   Ú_REVIEW_APPROVED_NOTEr8  r1  Ú_completed_event_payloadÚ_flag_phantom_prose_refsrÀ  rš  Ú_cleanup_workspacerê  r…   )r’   rp   rO   r‘  r“  rû  rƒ  rü  rý  r8  r   r  Úverified_cardsÚhandoff_summaryÚ
acceptancer<  Úprior_statusrz  rk  r   Úsynth_summaryÚsynth_metadataÚevent_summaryÚ
_done_tasks   &&$$$$$$$               r   Úcomplete_taskr  O
  st  € ô. Œd�iŠi‹kÓ
€Cä˜d×,Ò,Ùß[Ü(¨¸×GXÐGXÐRXÓY€NÜ˜4°ÕIÜ0Ø�x¸ô€Hð ")Ò!4‘g¸&€OÙ# D°?ÓM€JØ�UÓÙÜ	�4��ô " $×0Ò0Ø÷	 
‰ð
 Ò!Ñ*;¸DÈ:×*VÒ*VØ÷ 
‰ð �|‰|Ø^ØˆJó
÷ ‰(‹*ð 	÷ *.�t˜H–~°4ˆð Ò"¯5·T¼nÈT×>RÒ>RÜ  Ó)Ð)ðˆð   gÐ.ˆØÒ&ØÐ,Õ,ˆCØ4�vÐ4œs ?Ó3Ñ4ˆFØ�<‰<˜˜VÓ$×-Ñ-°Ô2Ø÷G 
‰ôH �h¤×%Ò%Ü'¨°xÔEÜØ ;°vÀØô
ˆð
 Š>Ÿw¯(·fÀÐPXÔ@XØ,;˜>ˆMØ˜xÔ'·ÇnÜ 5�Ø"1°8¸ZÈÐ!R�Ü*Ø {¸MÐTbôˆFð (ˆØ˜8Ô#¯MÜ1ˆMÜØ˜;Ü$ V¨]¸NÓUØõ	
÷m 
ôv ˜T¨F°GÀ^ÔTä˜4Ô)Ü�DÔÜ�tÔ%Ü˜$Ó(€JßÜÐ/°¸WÀfÐVeÕfÙ÷G 
�úsU   ÂJÂ4JÃ(JÃ;JÄJÄA!JÆ=JÇJÇJÇJÇ,JÇ4+JÈ  JÊJ,	z,Review approved without additional evidence.c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r’   r“   rp   r#   rû  rö  Úpreview_textrD   r   rK  r   )r   s   "r   r   r   ½
  s4   € ÷ ñ Ø
ðØ'*ðØ;RðØboðàñr   c                ó  € V'       g   . # \        WV4      w  rEV'       dO   \        V 4      ;_uu_ 4        \        YRRTRTR\        V^È4      ;'       g    R/4       RRR4       \	        WQ4      hV#   + '       g   i     L; i)zÒVerify ``created_cards`` BEFORE the main write txn; returns the verified
ids. A phantom id is recorded in its own tiny txn (auditable) then raised
as :class:`HallucinatedCardsError` without touching task state.Ú completion_blocked_hallucinationÚphantom_cardsr  Úsummary_previewN)rÕ  r-  r1  r%  rÜ  )r’   rp   rû  r  r  r  s   &&&&  r   r  r  ½
  sx   € ÷ Øˆ	Ü$9¸$ÈÓ$WÑ!€NßÜ�t�_�_ÜØÐAà# ]Ø$ nØ%¤{°<ÀÓ'E×'MÐ'MÈðô÷ ô % ]Ó<Ð<ØÐ÷ �_ús   ´(A1Á1B	c               ó    € V ^8„  d   QhRRRR/# )r   r   rD   r   r�   r   )r   s   "r   r   r   Ô
  s   € ÷ :ñ :˜]ð :¨tñ :r   c                ó^   € \        V R J;'       d    \        V 4      P                  4       4      # r`   )r�   r#   r<   r   s   &r   Ú_substantive_textr!  Ô
  s'   € Ü�˜TÐ!×8Ð8¤c¨%£j×&6Ñ&6Ó&8Ó9Ð9r   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rp   r#   rO   rD   r‘  r   rg   r   )r   s   "r   r   r   Ø
  s:   € ÷ (ñ (Ø
ð(àð(ð ð	(ð
 ð(ð 
ñ(r   c               óÒ  € V P                  RV34      P                  4       pVf   R# VR,          R8X  d   R# VR,          p\        V4      '       g#   \        V4      '       g   \        V4      '       d   R# \        V 4      ;_uu_ 4        \	        YRR\        V^È4      ;'       g    RR\        V^È4      ;'       g    R/4       RRR4       \        V4      h  + '       g   i     L; i)	z³Refuse a completion that would leave the card with no evidence.

Review approvals are exempt: a human vouches for the card and
``_REVIEW_APPROVED_NOTE`` is the documented record.
z-SELECT status, result FROM tasks WHERE id = ?NrS  rI  rO   Úcompletion_blocked_empty_resultÚresult_previewr  )r£   r¤   r!  r-  r1  r%  ré  )r’   rp   rO   r‘  r!   Ústoreds   &&$$  r   r	  r	  Ø
  sË   € ð �,‰,Ø7Ø	ˆ
ó÷ �hƒjð ð ‚{ÙØ
ˆ8…}˜Ô ÙØ��]€FÜ˜× Ò Ô$5°g×$>Ò$>ÔBSÐTZ×B[ÒB[ÙÜ	�4��ÜØÐ<à ¤+¨f°cÓ":×"BÐ"B¸dØ!¤;¨w¸Ó#<×#DÐ#DÀðô	
÷ 
ô ˜wÓ
'Ð'÷ 
�ús   Â	1CÂ;CÃC&	Úkanban_completec               ó0   € V ^8„  d   QhRRRRRRRRR	RR
R/# )r   r’   r“   rp   r#   r“  r/   r8  r6   r©  r   z
list[Path]r   )r   s   "r   r   r   ú
  s>   € ÷ ñ Ø
ðØ'*ðØ6:ðØADðàðð ñr   c               ó  € \        WV4       VP                  R. 4       Uu. uF  p\        V4      NK  	  ppV F>  p\        WVP                  \        V4      VP                  4       P                  W4R7       K@  	  V# u upi )z¤Copy scratch artifacts to the attachments dir and record each as an
attachment row; returns the copies so the caller can discard them if its
transaction rolls back.Ú_staged_artifacts)r¥  r¦  r¨  rV  r©  )Ú%_persist_scratch_completion_artifactsr“  r   Ú_insert_completion_attachmentr5   r#   ÚstatÚst_size)r’   rp   r“  r8  r©  r¦  Ústagedrf   s   &&&&$   r   r  r  ú
  su   € ô *¨$¸ÔBØ3;·<±<Ð@SÐUWÔ3XÓYÑ3X KŒd�;ÖÑ3X€FÐYÛˆÜ%Ø D§I¡I¼3¸t»9Ø—‘“×$Ñ$°÷	
ñ ð
 €Mùò Zs   ¡A=c               ó    € V ^8„  d   QhRRRR/# )r   r“  r   r   rK  r   )r   s   "r   r   r     s   € ÷ Rñ R cð R¨iñ Rr   c                ó^  € \        V \        4      '       g   . # V P                  R4      p\        V\        \        34      '       g   . # V Uu. uFU  p\        V\
        4      '       g   K  \        V4      P                  4       '       g   K<  \        V4      P                  4       NKW  	  up# u upi )z=Non-blank string paths declared in ``metadata["artifacts"]``.Ú	artifacts)r   r/   r;   r¥   r*  r#   r<   )r“  r>   r  s   &  r   Ú_cleaned_artifact_pathsr3    ss   € ä�h¤×%Ò%Øˆ	Ø
�,‰,�{Ó
#€CÜ�cœD¤%˜=×)Ò)Øˆ	Ù$'ÓQ¡C˜q¬:°a¼×+=ŒNÄ#ÀaÃ&Ç,Á,Ç.ŒNŒC�‹F�L‰LŽN¡CÑQÐQùÒQs   ÁB*Á)B*Â
B*c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   rO   rD   r  r  rK  r“  r   r   r/   r   )r   s   "r   r   r     s4   € ÷ ñ ØðØ*7ðØIRðØ^aðà	ñr   c                óÒ   € RV '       d   \        V 4      M^ R\        VR4      ;'       g    R/pV'       d   W$R&   \        V\        4      '       d   \	        V4      pV'       d   WTR&   V# )a  ``completed`` event payload: first summary line (400 chars) so gateway
notifiers / dashboard WS render without a second round-trip; verified
cards; and ``metadata["artifacts"]`` promoted so the notifier can upload
them as native attachments without fetching the run row.Ú
result_lenr‘  é�  Nr  r2  )r  r%  r   r/   r3  )rO   r  r  r“  r±  r  s   &&&&  r   r  r    sa   € ð 	§V”c˜&”k°Ø”;˜}¨cÓ2×:Ð:°dð€G÷ Ø$2Ð Ñ!Ü�(œD×!Ò!Ü)¨(Ó3ˆßØ#*�KÑ Ø€Nr   c               ó4   € V ^8„  d   QhRRRRRRRRR	RR
RRR/# )r   r’   r“   rp   r#   r   r€   r‘  rD   rO   r  rK  r   rg   r   )r   s   "r   r   r   .  sF   € ÷ ñ Ø
ðØ'*ðØ4Aðàðà$1ðàCLðð 
ñr   c                óL  € RP                  \        RW4.4      4      pV'       g   R# \        W4       Uu. uF  qw\        V4      9  g   K  VNK  	  ppV'       d2   \	        V 4      ;_uu_ 4        \        WRRVRR/VR7       RRR4       R# R# u upi   + '       g   i     R# ; i)zÃAdvisory post-commit scan of summary+result for unresolvable ``t_<hex>``
references; emits ``suspected_hallucinated_references`` in its own txn so
the completion is already durable. Never blocks.rþ  NÚ!suspected_hallucinated_referencesÚphantom_refsÚsourceÚcompletion_summaryri  )r\   ÚfilterrÚ  rÎ   r-  r1  )	r’   rp   r   r‘  rO   r  Ú	scan_textr  r;  s	   &&&&&&   r   r  r  .  s�   € ð —‘œ  wÐ&7Ó8Ó9€IßÙÜ:¸4ÔKÓlÑK˜!ÔX[Ð\jÓXkÑOk—A�AÑK€LÐlßÜ�t�_�_ÜØÐBØ ¨xÐ9MÐNÐW]õ÷ ‰_ñ ùò mç�_ˆ_ús   ´BÁ
BÁ-BÂB#	c               ó0   € V ^8„  d   QhRRRRRRRRR	RR
R/# )r   r’   r“   rp   r#   r“  r’  r‘  rD   rO   r   r   )r   s   "r   r   r   A  s>   € ÷ !ñ !Ø
ð!Ø'*ð!Ø6Dð!ØR_ð!àð!ð ñ!r   c               óV  € \        W4      pVf   V# \        V4      '       g   V# RP                  R W43 4       4      pV'       g   V# \        P                  ! \        V4      4      p. p\        P                  ! VR,           V4       F`  p	V	P                  ^ 4      P                  R4      p
\        V
4      pVP                  4       '       g   KF  VP                  \        V4      4       Kb  	  V'       g   V# \        V\        4      '       d   \        V4      M/ pVP                  R4      p\        V\        \         34      '       d   \        V4      M. pV Uu0 uF  p\        V4      kK  	  ppV F.  pVV9  g   K  VP                  V4       VP#                  V4       K0  	  WìR&   V# u upi )z®Legacy workers named deliverables only by absolute path in prose; add
those that exist under the scratch workspace to ``metadata["artifacts"]``
before cleanup can erase them.r  c              3  ó8   "  € T F  q'       g   K  Vx € K  	  R # 5ir`   r   )rb   Úparts   & r   rc   Ú4_merge_completion_prose_artifacts.<locals>.<genexpr>M  s   é € Ð@Ñ&7˜d»4—T’TÓ&7ùr  z(?:[/\\][^\s`\"'<>]+)z	.,;:!?)]}r2  )Ú_scratch_workspaceÚ_is_managed_scratch_pathr\   ÚreÚescaper#   ÚfinditerÚgroupÚrstripr   r  r   r   r/   r;   r¥   r*  r  )r’   rp   r“  r‘  rO   Ú	workspacerH   ró  Ú
discoveredrÚ   r>   rû   r_  ÚexistingÚmergedrf   r  s   &&&$$            r   r
  r
  A  s\  € ô # 4Ó1€IØÒØˆÜ# I×.Ò.ØˆØ�9‰9Ñ@ wÑ&7Ó@Ó@€DßØˆÜ�YŠY”s˜9“~Ó&€FØ€JÜ—’˜VÐ&>Õ>ÀÖEˆØ�k‰k˜!‹n×#Ñ# KÓ0ˆÜ˜“Iˆ	Ø×Ñ×ÔØ×Ñœc )›nÖ-ñ	 F÷
 ØˆÜ *¨8´T× :Ò :Œd�8ŒnÀ€GØ�{‰{˜;Ó'€HÜ)¨(´T¼5°M×BÒBŒT�(Œ^È€FÙ"(Ó)¡&˜$ŒC�ŽI¡&€DÐ)ÛˆØ�tÖØ�M‰M˜$ÔØ�H‰H�TŽNñ ð "ˆKÑØ€Nùò *s   ÅF&c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r’   r“   rp   r#   r“  r/   r   rg   r   )r   s   "r   r   r   e  s2   € ÷ N
ñ N
Ø
ðN
Ø'*ðN
Ø6:ðN
à	ñN
r   c           	     óZ  aa€ VP                  R4      p\        V\        \        34      '       g   R# \	        W4      pVf   R# \        V4      w  rVV'       g   R#  VP                  4       p\        YR7      o. p\        4       oRp	R TT3R llp
T EFo  p\        T\        4      '       d   \        T4      P                  4       MRpT'       g   K?  \        T4      P                  4       p TP                  4       pTP                  T4      '       g   TP                  T4       K“  RpTP!                  4       '       g   RT 2pM/TP#                  4       P$                  \&        8”  d   R	\&         R
T 2pT'       d   T
! 4        \)        T4      hRp SP+                  RRR7       \-        STP.                  S4      p\1        TTT4       SP;                  T4       TP                  \        TP                  4       4      4       Rp	EKr  	  T	'       dL   Y‚R&   T Uu. uF5  pTP=                  \        SP                  4       4      4      '       g   K3  TNK7  	  upTR&   R# R#   \         d     R# i ; i  \         d    TP                  T4        EKû  i ; i  \2         d‹   pTeR   \4        P6                  ! \        4      ;_uu_ 4        TP9                  RR7       RRR4       M  + '       g   i     M; iT
! 4        \        T\(        4      '       d   h \)        RT RT 24      ThRp?ii ; iu upi )zHCopy scratch-workspace completion artifacts before cleanup removes them.r2  Nrà   Fc               ó   € V ^8„  d   QhRR/# r  r   )r   s   "r   r   Ú;_persist_scratch_completion_artifacts.<locals>.__annotate__~  s   € ÷ Bñ B˜Tñ Br   c                 ó    <€ \        SS 4       R # r`   )Ú_discard_staged_copies)Úattachment_dirÚused_destinationss   €€r   Ú_discard_copiesÚ>_persist_scratch_completion_artifacts.<locals>._discard_copies~  s   ø€ ÜÐ0°.ÖAr   r8   z@declared scratch artifact is unavailable or not a regular file: z&declared scratch artifact exceeds the z-byte limit: Tr  r÷  z-could not preserve declared scratch artifact z: r*  )r;   r   r¥   r*  rE  Ú_managed_scratch_path_inforý  r  rK  rÎ   r#   r<   r   rì   r   Úis_relative_tor  r-  r.  rú  rñ  r  Ú_unique_attachment_pathr5   Ú_copy_cappedr,   r  r  r  r  rí  )r’   rp   r“  Úraw_artifactsrL  Ú
is_managedr‚   Úworkspace_rootÚ	persistedÚchangedrX  ÚitemÚartifactÚsrcÚresolved_srcÚproblemÚdestrz   rf   rV  rW  s   &&&                @@r   r+  r+  e  sÖ  ù€ ð —L‘L Ó-€MÜ�m¤d¬E ]×3Ò3Ùä" 4Ó1€IØÒÙÜ2°9Ó=Ñ€JßÙðØ"×*Ñ*Ó,ˆô *¨'Ô?€NØ€IÜ#&£5ÐØ€G÷Bñ Bô ˆÜ(2°4¼×(=Ò(=”3�t“9—?‘?Ô$À2ˆßÙÜ�8‹n×'Ñ'Ó)ˆð	ØŸ;™;›=ˆLð
 ×*Ñ*¨>×:Ò:Ø×Ñ˜XÔ&ÙàˆØ�{‰{�}Š}ØXÐYaÐXbÐc‰GØ×ÑÓ ×(Ñ(Ô+FÔFà8Ü.Ð/¨}¸X¸JðHð ÷ ÙÔÜ+¨GÓ4Ð4à#ˆð	Ø× Ñ ¨¸Ð Ô=Ü*¨>¸<×;LÑ;LÐN_Ó`ˆDÜ˜ t¨XÔ6ð 	×Ñ˜dÔ#Ø×Ñœ˜TŸ\™\›^Ó,Ô-Ø‹ñY ÷\ Ø )�Ñá&ó)
Ù&�T¨$¯/©/¼#¸n×>TÑ>TÓ>VÓ:W×*X�DˆD™Yñ)
ˆÐ$Ó%ñ øôs ô Úðûô$ ô 	Ø×Ñ˜XÔ&Ûð	ûô2 ô 		ØÒÜ×(Ò(¬×1Õ1Ø—K‘K¨4�KÔ0÷ 2×1×1Ð1úáÔÜ˜#Ô8×9Ò9ØÜ+Ø?À¸zÈÈCÈ5ÐQóàðûð		üò )
s`   ÁI Ã*I,Æ7JÈ0L(É
L(ÉI)É(I)É,JÊJÊL%Ê&L ËK	Ë	L ËK-Ë(8L Ì L%c               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚcopieszIterable[Path]rV  r   r   rg   r   )r   s   "r   r   r   ¶  s!   € ÷ ñ  >ð À4ð ÈDñ r   c                ój  € V  FI  p\         P                  ! \        4      ;_uu_ 4        \        V4      P	                  RR7       RRR4       KK  	  \         P                  ! \        4      ;_uu_ 4        VP                  4        RRR4       R#   + '       g   i     K›  ; i  + '       g   i     R# ; i)z‰Remove staged attachment copies whose DB rows never committed; a leaked
copy would make the retry stage ``name_1.ext`` next to an orphan.Tr÷  N)r  r  r  r   r  Úrmdir)rj  rV  Úcopieds   && r   rU  rU  ¶  sy   € ó ˆÜ× Ò ¤×)Õ)Ü�‹L×Ñ¨4ÐÔ0÷ *Ñ)ñ ô 
×	Ò	œW×	%Õ	%Ø×ÑÔ÷ 
&Ñ	%÷ *×)Ð)úç	%×	%Ð	%ús   ¨BÁ2B!ÂBÂ!B2	c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   re  r   rh  rd  r#   r   rg   r   )r   s   "r   r   r   À  s(   € ÷ 
*ñ 
*�dð 
* $ð 
*°#ð 
*¸$ñ 
*r   c           	     óŽ  € V P                  R4      ;_uu_ 4       q1P                  R4      ;_uu_ 4       p^ pVP                  R4      ;p'       d?   V\        V4      ,          pV\        8”  d   \	        RV 24      hVP                  V4       KW   RRR4       RRR4       R#   + '       g   i     L; i  + '       g   i     R# ; i)zLChunked copy that aborts if the file grows past the attachment cap mid-copy.ÚrbÚxbz6declared scratch artifact grew beyond the size limit: Nrù  )ÚopenÚreadr  rú  rñ  Úwrite)re  rh  rd  Úsource_fileÚdestination_filerm  Úchunks   &&&    r   r]  r]  À  s‘   € à	�‰�$�Œ˜;¯	©	°$¯¬Ð;KØˆØ"×'Ñ'¨Ó4Ð4ˆeÖ4Ø”c˜%“jÕ ˆFØÔ3Ô3Ü/ØLÈXÈJÐWóð ð ×"Ñ" 5Ö)ð 5÷ )8�‰¯¯ú��ˆús#   šB3³AB 	ÂB3Â B0Â+B3Â3C	c               ó8   € V ^8„  d   QhRRRRRRRRRRR	RR
RRR/# )r   r’   r“   rp   r#   r¥  r¦  r¨  r6   rV  r©  r   rg   r   )r   s   "r   r   r   Í  sX   € ÷ fñ fØ
ðfØ'*ðfØ9<ðfØKNðfØVYðfàðfà"%ðfð 
ñfr   c               óV   € V P                  RWW4We34       \        WRRVRVRV/4       R# )zCRecord a worker-produced artifact in the existing attachment table.zŠINSERT INTO task_attachments (task_id, filename, stored_path, content_type, size, uploaded_by, created_at) VALUES (?, ?, ?, NULL, ?, ?, ?)r  r¥  r¨  r  N)r£   r1  )r’   rp   r¥  r¦  r¨  rV  r©  s   &&$$$$$r   r,  r,  Í  s>   € ð
 	‡L�Lð	*ð 
˜K¨{ÐGô	ô �$ ¨j¸(ÀFÈDÐRVÐXcÐ-dÖer   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Ú	directoryr   r¥  r#   Úusedz	set[Path]r   r   )r   s   "r   r   r   Û  s)   € ÷ 	ñ 	 tð 	°sð 	À)ð 	ÐPTñ 	r   c                ó*  € \        V4      P                  ;'       g    Rp\        V4      P                  ;'       g    R\        V4      P                  rTW,          p^pWb9   g   VP	                  4       '       d   W RV V 2,          pV^,          pK6  V# )zCReturn a non-conflicting path under ``directory`` for ``filename``.rd  r´  )r   r5   rï  Úsuffixr  )r{  r¥  r|  rí  rï  r~  rû   Úidxs   &&&     r   r\  r\  Û  sƒ   € ä�X“×#Ñ#×1Ð1 z€IÜ˜	“?×'Ñ'×5Ð5¨:´t¸I³×7MÑ7Mˆ&ØÕ%€IØ
€CØ
Ô
˜y×/Ñ/×1Ò1Ø &¨¨#¨¨v¨hÐ 7Õ7ˆ	Øˆq�ŠØÐr   c               ó@   € V ^8„  d   QhRRRRRRRRRR	R
RRRRRRRRR/
# )r   r’   r“   rp   r#   rQ  rD   rR  rT  r€   rO   r‘  r“  r’  r‚   r   r�   r   )r   s   "r   r   r   ç  sr   € ÷ Jñ JØ
ðJØ'*ðJØ6CðJà
ðJà*7ðJð ðJð ,9ðJð ð	Jð -:ð	Jð
 
ñJr   c               ó,  € RV3RV3RV33 U	U
u. uF  w  ršV
f   K  V	NK  	  pp	p
\        V 4      ;_uu_ 4        \        W4      pVe   Ve   VR8w  d    RRR4       R# . p. pRV3RV3RV33 F0  w  ršV
f   K  VP                  V	 R24       VP                  V
4       K2  	  Ve4   VP                  R4       VP                  V4       VP                  R	4       V'       g    RRR4       R# V P                  R
RP	                  V4       R2. VOVN54       Ve   \        WRRV/4       Vf3   V U	u. uF  q™R8w  g   K  V	NK  	  pp	V'       d   \        WRRV/4       EMVe   TMTpVP                  R4       Ve   VP                  R4       V P                  RV34      P                  4       pVf   \        WRVVR7      pMT\        VR,          4      pV P                  RVV34       Ve+   V P                  R\        P                  ! VRR7      V34       \        YRRR	R.Ve   R.M. ,           RV'       d   \        V4      M^ R\        VR4      ;'       g    R/VR7       RRR4       \        WW¸R7       R# u up
p	i u up	i   + '       g   i     L*; i)zCEdit task fields, optionally backfilling a completed task's result.rQ  rR  rT  Nr#  Frh  z
result = ?rO   úUPDATE tasks SET rX   z WHERE id = ?ÚreprioritizedÚeditedrq   r‘  r“  zÕ
            SELECT id FROM task_runs
             WHERE task_id = ?
               AND outcome = 'completed'
             ORDER BY COALESCE(ended_at, started_at, 0) DESC, id DESC
             LIMIT 1
            r  r  rš   z-UPDATE task_runs SET summary = ? WHERE id = ?z.UPDATE task_runs SET metadata = ? WHERE id = ?r+  r6  r7  ri  rà   T)r-  r2  r   r£   r\   r1  r¤   r8  r6   r*   r/  r  r%  r¦   )r’   rp   rQ  rR  rT  rO   r‘  r“  r‚   Úfieldr   rž   rS  Úassignmentsrk  Únon_priority_fieldsr  rL   r   s   &&$$$$$$$          r   Ú	edit_taskrˆ  ç  s¼  € ð %,¨UÐ#3°f¸d°^ÀjÐRZÐE[Ñ"\ôÙ"\‘,�%Ø÷ 	ˆÑ"\ð ñ ô 
�4��Ü˜dÓ,ˆØŠ>˜fÒ0°V¸vÔ5EØ÷ 
‰ð ˆØˆØ% uÐ-°¸¨~À
ÈHÐ?UÓV‰LˆEØÔ Ø×"Ñ" e W¨D >Ô2Ø—‘˜eÖ$ñ Wð ÒØ×Ñ˜|Ô,Ø�M‰M˜&Ô!Ø×!Ñ! (Ô+ßØ÷ 
‰ð  	�‰Ø §	¡	¨+Ó 6Ð7°}ÐEØˆfÐ�gÑô	
ð ÒÜ˜$¨¸:ÀxÐ:PÔQØŠ>Ù6DÓ"\±n¨UÐQ[ÑH[§5 5±nÐÐ"\ß"Ü˜d¨X¸ÐBUÐ7VÔWùà)0Ò)<™gÀ&ˆOØ×!Ñ! )Ô,ØÒ#Ø×%Ñ% jÔ1Ø—,‘,ðð ˆJó	
÷ ‰(‹*ð ð Š{Ü.Ø¨;ÀÐZbô‘ô ˜S �Y›�Ø—‘ÐLÈÐ`fÐNgÔhØÒ'Ø—L‘LØHÜŸš H¸5ÔAÀ6ÐJôô Ø˜xà˜x¨Ð3ÀxÒG[¸
±|ÐacÕdØ ·¤# f¤+¸QØœ{¨?¸CÓ@×HÐHÀDðð
 õ÷k 
ô| ˜ ~ÕCÙùóGùò6 #]÷/ 
�úsT   �I8œI8¸JÁJÁ8A'JÃ JÃ+AJÄ0I>Ä=I>ÅC3JÈ7JÉ
JÉ>JÊJ	r°  c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   r’   r“   rp   r#   r  rD   r°  rƒ  r€   r   r�   r   )r   s   "r   r   r   4  sF   € ÷ [ñ [Ø
ð[Ø'*ð[Ø7Dð[à
ð[à1>ð[ð 
ñ[r   c               ó–  € Ve(   V\         9  d   \        R\        \         4       R24      h\        V 4      ;_uu_ 4        V P	                  RV34      P                  4       pVf    RRR4       R# VR,          R8X  do   Ve   Vf   \        VR4      e    RRR4       R# V P	                  R	W134      P                  pV^8w  d    RRR4       R# \        WRR
VRVRR/4        RRR4       R# VR,          R8X  d   \        W4      MRpTpRp	VR8X  d   \        W4      '       d   RpRp	\        Y2T\        VR4      \        \        VR4      ;'       g    ^ 4      R7      w  r«rÍpV	'       d	   WŽR&   WžR&   RV
 RV R2p. VOVN5pVe   VR,          p. VO\        V4      N5pV P	                  Wý4      P                  ^8w  d    RRR4       R# \        WRRV\        V4      R7      p\        WW¾VR7       \        W4      pVR8X  d   \!        RVVVVR7        RRR4       R#  RRR4       \!        RXVXVR7       R#   + '       g   i     L"; i)a  ``running``/``ready`` -> ``blocked`` (or ``todo`` / ``triage``, see
:func:`_route_block`). ``kind='dependency'`` with no incomplete parent is
re-kinded to ``needs_input`` (sticky) so ``recompute_ready`` cannot
promote it into a context-free respawn. ``transient`` still counts
toward the loop breaker so a forever-flaky task escalates. True on any
transition.

An already-``blocked`` card that the failure breaker parked UNTYPED
(``block_kind IS NULL``, no live run) is classified in place when *kind*
is supplied: ``block_kind``/``block_recurrences`` are set and a ``blocked``
audit event is appended, while status, failure evidence and the terminal
runs stay exactly as the breaker left them. A typed block, a card with a
live run, or a kind-less call on a blocked card are still refused.
Nzblock kind must be one of z or NonezDSELECT status, block_kind, block_recurrences FROM tasks WHERE id = ?FrS  rR   rn  zŒUPDATE tasks SET block_kind = ?, block_recurrences = 1 WHERE id = ? AND status = 'blocked' AND block_kind IS NULL AND current_run_id IS NULLr°  r  Úclassified_in_placeTrQ   rJ  Ú
dependencyÚneeds_inputÚno_open_parentro  )Ú	prev_kindÚprev_recurrencesÚrequested_kindÚrekind_reasonzF
                UPDATE tasks
                   SET status        = 'z¡',
                       claim_lock    = NULL,
                       claim_expires = NULL,
                       worker_pid    = NULL,
                       úe
                 WHERE id = ?
                   AND status IN ('running', 'ready')
                r  ©r³   rS  r‘  r5  ri  Úkanban_task_blockedr—  )ÚVALID_BLOCK_KINDSr=   r'  r-  r£   r¤   r'   r‰  r1  r~  r_  Ú_route_blockr6   r9  r�   rê  r…   )r’   rp   r  r°  rƒ  Úcur_rowÚ
classifiedrM  r‘  r’  Ú
new_statusr{  Úset_sqlrk  r±  rz  r   Úblocked_tasks   &&$$$             r   Ú
block_taskr�  4  s˜  € ð$ Ò˜DÔ(9Ô9ÜÐ5´fÔ=NÓ6OÐ5PÐPXÐYÓZÐZÜ	�4��Ø—,‘,ØRÐU\ÐT^ó
ç
‰(‹*ð 	ð Š?Ø÷ 
‰ð �8Õ 	Ô)ØŠ|˜Ò:¼hÀwÐP\Ó>]Ò>iØ÷ 
‰ð  Ÿ™ð-ð �ó	÷
 ‰hð ð ˜QŒØ÷/ 
‰ô0 ˜$¨Ø˜˜h¨Ð0EÀtð5ô ð ÷7 
‰ð8 AHÈÕ@QÐU^Ô@^Ô-¨dÔ<ÐdkˆØˆØˆð
 �<ÔÔ$6°t×$EÒ$EØ ˆDØ,ˆMÜ;GØ˜-´8¸GÀ\Ó3RÜ ¤¨'Ð3FÓ!G×!LÐ!LÈ1ÓMô<
Ñ8ˆ
 °÷ Ø(6Ð$Ñ%Ø'4�OÑ$ð)à)3¨ð 5ð  �yð !ð	ˆð $�6Ð#˜7Ñ#ˆØÒ&ØÐ,Õ,ˆCØ4�vÐ4œs ?Ó3Ñ4ˆFØ�<‰<˜Ó$×-Ñ-°Ô2Ø÷y 
‰ôz (Ø 9°YÈÔ[_Ð`fÓ[gô
ˆô 	�d ZÀÕHÜ Ó.ˆØ�<ÔäÐ1°<ÀÈ&ÐY_Õ`Ø÷K 
‰ðD  ÷E 
ôL Ð)¨<¸À&ÐQWÕXÙ÷O 
�ús+   ¿'H8Á0%H8Â%H8ÃH8Ã,CH8Ç	A
H8È8I	c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r   r°  rD   r  rM  r#   r�  r�  r6   r   z!tuple[str, str, str, tuple, dict]r   )r   s   "r   r   r   ’  sF   € ÷ Gñ GØ
ðGØ!.ðGØ?BðGàðGà03ðGð 'ñGr   c               ó²   € RVRV RV/pV R8X  d	   RRRV 3V3# W08X  d
   V^,           M^pRpRVRV R	VRV/pV\         8¼  d   \         VR
&   RRWpV3V3# RRWpV3V3# )a   ``(new_status, event_kind, set_sql, params, payload)`` for :func:`block_task`.

``dependency`` never enters the human ``blocked`` bucket: it waits in
``todo`` for ``recompute_ready``, so a cron never sees a dependency-wait
as something to "unblock". Callers that pass ``dependency`` with no
incomplete parent are re-kinded to ``needs_input`` before this runs
(see :func:`block_task`). Every other kind counts unblock-loop
recurrences: block_task only fires from running/ready (AFTER an unblock
returned the task to the pool), so a stored ``block_kind`` equal to the
incoming one means blocked -> unblocked -> re-block for the same cause
(un-typed None compares equal to a prior un-typed block). At
``BLOCK_RECURRENCE_LIMIT`` the task routes to ``triage`` for a human.
r  r°  rM  rŒ  r  r  zblock_kind    = ?z?block_kind    = ?,
                       block_recurrences = ?Úrecurrencesrc  r	  Úblock_loop_detectedrR   )ÚBLOCK_RECURRENCE_LIMIT)r°  r  rM  r�  r�  r±  r   r›  s   &&&$$   r   r—  r—  ’  s�   € ð" ˜ ¨¨ÀÐN€GØˆ|ÔØÐ(Ð*=À¸wÈÐOÐOØ*3Ô*;Ð" QÖ&À€KØP€GØ˜ ¨¨}¸kÈ?Ð\iÐj€GØÔ,Ô,Ü1ˆ�ÑØÐ.°ÀÐ9LÈgÐUÐUØ�i °Ð*=¸wÐFÐFr   c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r   ¯  s   € ÷ ñ ˜sð  sñ r   c                óÖ  € \        V \        4      '       d   ^ RIHp V! V RR7      # \        V \        4      '       d/   V P                  4        UUu/ uF  w  r#V\        V4      bK  	  upp# \        V \        4      '       d   V  Uu. uF  p\        V4      NK  	  up# \        V \        4      '       d-   \        ;QJ d    . R V  4       F  NK  	  5# ! R V  4       4      # V # u uppi u upi )zBRedact secrets at the domain boundary for durable review handoffs.)Úredact_sensitive_textT)rý  c              3  ó8   "  € T F  p\        V4      x € K  	  R # 5ir`   )Úredact_review_value)rb   rc  s   & r   rc   Ú&redact_review_value.<locals>.<genexpr>º  s   é € ÐA¹5°4Ô(¨×.Ð.»5ùrý  )	r   r#   Úagent.redactr¥  r/   Úitemsr§  r¥   r*  )r   r¥  rN  rc  s   &   r   r§  r§  ¯  s¹   € ä�%œ×ÒÝ6á$ U°$Ô7Ð7Ü�%œ×ÒØ@EÇÁÄÔNÁ±9°3�Ô(¨Ó.Ò.ÁÒNÐNÜ�%œ×ÒÙ6;Ó<±e¨dÔ# DÖ)±eÑ<Ð<Ü�%œ×ÒßŒuÑA¹5ÓA�uÐAˆuÑA¹5ÓAÓAÐAØ€Lùó Oùâ<s   ÁC ÂC&ÚreviewerÚwith_reasonc               ó8   € V ^8„  d   QhRRRRRRRRR	RR
RRRRR/# )r   r’   r“   rp   r#   r‘  rD   r“  r’  r«  rƒ  r€   rý  r�   r¬  r   )r   s   "r   r   r   ¾  s^   € ÷ ~ñ ~Ø
ð~Ø'*ð~Ø8Eð~àð~à/<ð~ð #ð~ð 37ð~ð NRñ~r   c               óÊ  a€ R!R V3R lllp\        V4      p\        V4      p\        WW2RR7      p\        \        P                  ! 4       4      p	. p
 \	        V 4      ;_uu_ 4        \        W4      '       g   V! RR4      uuRRR4       # V P                  RV34      P                  4       pVf   V! RR4      uuRRR4       # Vf-   V'       g%   \        V4      '       d   V! RR	4      uuRRR4       # Vf%   \        W4      pVRJ d   V! RR
4      uuRRR4       # \        V4      pRpVR,          e<   V P                  RVR,          34      P                  4       pV'       d
   VR,          MRpVf   VR,          V8w  d
   VR,          pVe   RMRpVf   RMRp. Vf   R"MV3OVNVf   R"M\        V4      3O5pV P                  RV,           R,           V,           V4      pVP                  ^8w  d   V! RR4      uuRRR4       # \        V\        4      '       d   \        WW9RR7      p
\        YRRY#\!        T;'       g    T4      VR7      pR\#        VR4      ;'       g    RRVRV/p\%        V4      pV'       d   VVR&   \'        WRVVR7       RRR4       T! R 4      #   + '       g   i     L; i  \(         d'    T
'       d   \+        Yª^ ,          P,                  4       h i ; i)#aê  ``running``/``ready`` -> ``review``; never touches block recurrence accounting.

Implementer and reviewer are recorded on the event so requested changes
route back to the right profile; ``reviewer`` reassigns the task, and on
re-review defaults to the latest ``changes_requested`` provenance. A live
claim is only cleared with proof of ownership (``expected_run_id``) or
``force=True``. Returns ``bool``, or ``(ok, reason)`` with ``with_reason``.

``metadata["artifacts"]`` names the handoff's deliverable
files; a review handoff is the last implementer transition, and the
*reviewer's* completion is what cleans the managed scratch workspace up, so
the files are staged into the task's durable attachments dir here and the
staged paths ride the ``review_requested`` payload for the notifier to
upload. A declared artifact that cannot be preserved raises
:class:`ArtifactPreservationError`, rolling the whole transition back: the
task stays ``running`` and retryable, with no attachments and no event.
Nc               ó    € V ^8„  d   QhRRRR/# )r   r¬   r�   r  rD   r   )r   s   "r   r   Ú$request_review.<locals>.__annotate__Õ  s   € ÷ 3ñ 3�ð 3˜}ñ 3r   c                ó   <€ S'       d   W3# T # r`   r   )r¬   r  r¬  s   &&€r   Ú_retÚrequest_review.<locals>._retÕ  s   ø€ ß*�ˆ|Ð2°Ð2r   r  Fz%parent dependencies are not satisfiedzjSELECT assignee, status, claim_lock, current_run_id, worker_pid, worker_started_at FROM tasks WHERE id = ?útask not foundzŸtask is running under a live claim; pass expected_run_id (worker ownership) or force=True (explicit operator override) instead of clearing the live run's claimz„re-review has no durable reviewer provenance (the latest changes_requested event is missing or malformed); pass reviewer= explicitlyr¢   z*SELECT profile FROM task_runs WHERE id = ?rŽ  rƒ   ú, assignee = ?r8   r  zå
                UPDATE tasks
                   SET status        = 'review',
                       claim_lock    = NULL,
                       claim_expires = NULL,
                       worker_pid    = NULL
                r“  zOtask is not in running/ready (or expected_run_id did not match the current run)Úkanban_request_review)r©  r€  rI  )r³   rS  r‘  r“  r5  rŽ  r‘  r7  Úimplementerr«  r2  ri  Tr`   r   )r§  r
  r6   r+  r-  r_  r£   r¤   rú  Ú_prior_reviewerrÒ  r‰  r   r/   r  r9  r�   r%  r3  r1  r,   rU  r  )r’   rp   r‘  r“  r«  rƒ  rý  r¬  r²  r8  Ústaged_copiesr<  r·  ÚarowÚassignee_sqlÚ	run_guardrk  rŒ  r   r±  r/  s   &&$$$$$d             r   Úrequest_reviewr½  ¾  s  ø€ ÷.3ò 3ô " 'Ó*€GÜ" 8Ó,€Hô 1°ÀÐbfÔg€HÜ
Œd�iŠi‹kÓ
€Cð !#€MðYÜ�t�_�_Ü% d×4Ò4Ù˜EÐ#JÓK÷ Š_ð —<‘<ð<Ø>E¸Zó÷ ‰h‹jð ð Š|Ù˜EÐ#3Ó4÷ Š_ð Ò&¯u¼È×9MÒ9MÙØð Ió÷ Š_ð$ ÒÜ*¨4Ó9�Ø˜uÓ$ÙØð  @ó÷+ Š_ô4 +¨8Ó4ˆHð ˆKØÐ$Õ%Ò1Ø—|‘|Ø@ØÐ*Õ+Ð-ó÷ ‘(“*ð ÷ 26˜d 9žo¸4�ØÒ" t¨JÕ'7¸8Ô'CØ" :Õ.�Ø/7Ò/CÑ+ÈˆLØ-Ò5™Ð;TˆIð'Ø Ò(‘"¨x¨kð'Ø<Cð'à'Ò/‘"´c¸/Ó6JÐ5Lñ'ˆFð —,‘,ðð #õ#ð&õ	ð  õ	 ð óˆCð �|‰|˜qÔ ÙØÐló÷A Š_ôF ˜(¤D×)Ò)Ü ;Ø 8Ð>Uô!�ô ,ØÐ'9À(Ø¼tÀG×DWÐDWÈxÓ?XØ#ôˆFð œ; w°Ó4×<Ð<¸Ø˜{Ø˜HðˆGô
 -¨XÓ6ˆFßØ'-�˜Ñ$Ü˜$Ð);¸WÈVÕT÷i ñr �‹:Ð÷s �_ûôj ô ßÜ" =ÀÕ2B×2IÑ2IÔJØðús�   ÁJ1 Á%JÁ?
J1 Â
-JÂ7
J1 ÃJÃJÃ'
J1 Ã2JÄ
J1 ÄAJÅ"BJÇ8
J1 ÈAJÉ/JÊJ1 ÊJ.	Ê)J1 Ê.J1 Ê1K"ËK"c               ó    € V ^8„  d   QhRRRR/# )r   r’   r“   rp   r#   r   )r   s   "r   r   r   ?  s   € ÷ Qñ QÐ,ð Q°sñ Qr   c                ó   € V P                  RV34      P                  4       pVf   R# \        WRVR,          4      p\        \	        VR4      4      P                  R4      p\        V\        4      '       d   VP                  4       '       d   V# R# )z«Reviewer recorded by the latest ``changes_requested`` run's event.
``None`` = first review (no such run); ``False`` = a run exists but its
provenance is missing/malformed.zeSELECT id FROM task_runs WHERE task_id = ? AND outcome = 'changes_requested' ORDER BY id DESC LIMIT 1Nr�  rš   r±  r«  F)	r£   r¤   rE  r2   r'   r;   r   r#   r<   )r’   rp   Úchanges_runÚchanges_eventr«  s   &&   r   r¸  r¸  ?  s‡   € ð —,‘,ð	#à%, Jó÷ �hƒjð	 ð
 ÒÙÜ! $Ð1DÀkÐRVÕFWÓX€MÜœ( =°)Ó<Ó=×AÑAÀ*ÓM€HÜ! (¬C×0Ò0°X·^±^×5EÒ5Eˆ8ÐPÈ5ÐPr   c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   rD   r   )r   s   "r   r   r   O  s   € ÷ Gñ G˜ð G ñ Gr   c                ób   € \        V \        4      '       d   V P                  4       '       d   V # R # r`   )r   r#   r<   r   s   &r   Ú_nonblank_strrÄ  O  s#   € Ü˜u¤c×*Ò*¨u¯{©{¯}ª}ˆ5ÐFÀ$ÐFr   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rp   r#   r  rƒ  r€   r   útuple[bool, Optional[str]]r   )r   s   "r   r   r   S  s;   € ÷ Cñ CØ
ðCØ'*ðCØ7:ðCØMZðCàñCr   c               ó  € \        \        T;'       g    R4      4      P                  4       pV'       g   R# \        V 4      ;_uu_ 4        V P	                  RV34      P                  4       pVf   RuuRRR4       # VR,          pVR,          R8w  g   Vf   RuuRRR4       # Ve&   \        V4      \        V4      8w  d   RuuRRR4       # \        WRV4      p\        \        VR4      4      pVP                  R	4      R
8w  d   RuuRRR4       # \        WR4      pVf   RuuRRR4       # \        \        VR,          4      P                  R4      4      p	V	f   RuuRRR4       # \        \        VR,          4      4      p
\        W4      pV P	                  RW¹V\        V4      34      pVP                  ^8w  d   RuuRRR4       # \        WRW²R7      p\!        V VRRVRV	RV
RV/VR7       RRR4       RV	3#   + '       g   i     RX	3# ; i)zÍClose an active reviewer run (claimed from ``review``) and hand the task
back to the implementer from the latest ``review_requested`` event, parent
gating reapplied. Returns ``(ok, implementer | reason)``.r8   z?SELECT status, assignee, current_run_id FROM tasks WHERE id = ?Nr¢   rS  rQ   rh  r±  rM  rI  r€  r·  rƒ   aP  
            UPDATE tasks
               SET status = ?,
                   assignee = COALESCE(?, assignee),
                   claim_lock = NULL,
                   claim_expires = NULL,
                   worker_pid = NULL, worker_started_at = NULL
             WHERE id = ? AND status = 'running' AND current_run_id = ?
            r�  ©r³   rS  r‘  r  r«  ri  T)Fzreason is required)Fr´  )Fz#task is not in an active review run)Fzrun_id mismatch)Fz&active run was not claimed from review)Fzno prior review_requested event)Fz2review handoff has no valid implementer provenance)Fz"task changed during review handoff)r#   r§  r<   r-  r£   r¤   r6   rE  r2   r'   r;   rÄ  rÒ  Ú_landing_status_after_parentsr‰  r   r1  )r’   rp   r  rƒ  Útask_rowr¢   Úclaimed_eventÚclaimed_payloadÚrequested_eventr·  r«  rš  rŒ  r   s   &&$$          r   Úrequest_changesrÎ  S  s  € ô Ô$ V§\ \¨rÓ2Ó3×9Ñ9Ó;€FßØ*Ð*ä	�4��Ø—<‘<ØMÐPWÈzó
ç
‰(‹*ð 	ð ÒØ*÷ 
Šð "Ð"2Õ3ˆØ�HÕ Ô*¨nÒ.DØ?÷ 
Šð Ò&¬3¨~Ó+>Ä#ÀoÓBVÔ+VØ+÷ 
Šô & d°YÀÓOˆÜ$¤X¨m¸YÓ%GÓHˆØ×Ñ˜Ó/°8Ô;ØB÷ 
Šô" (¨Ð7IÓJˆØÒ"Ø;÷' 
Šô( $¤J¨¸yÕ/IÓ$J×$NÑ$NÈ}Ó$]Ó^ˆØÒØN÷- 
Šô. '¤}°X¸jÕ5IÓ'JÓKˆä2°4ÓAˆ
ð �l‰lðð  g¬s°>Ó/BÐCó
ˆð �<‰<˜1ÔØ>÷S 
ŠôT ØÐ#6¸zô
ˆô 	ØØØà˜&Ø˜{Ø˜HØ˜*ð	ð õ	
÷[ 
ðr �ÐÐ÷s 
Žðr �ÐÐús7   Á
'G9Á<G9Â#G9Ã9G9ÄG9Ä,/G9Å&AG9Ç&G9Ç9H	c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   r’   r“   rp   r#   r  r  rD   r§   r�   r   rÆ  r   )r   s   "r   r   r   ™  s>   € ÷ .ñ .Ø
ð.Ø'*ð.Ø69ð.ØCPð.àð.ð  ñ.r   c               ó:  € \        W4      pVf	   RRV R23# VR9  d   RRV RV: R23# V P                  RV34      P                  4       pV Uu. uF  qwR,          R9  g   K  VR	,          NK  	  ppV'       d   RR
RP                  V4       RV R23# V'       d   R# \	        V 4      ;_uu_ 4        V P                  RV34      p	V	P
                  ^8w  d   RRV R23uuRRR4       # \        WRRVRV/4       RRR4       R# u upi   + '       g   i     R# ; i)z¥Operator promotion ``todo``/``blocked`` -> ``ready`` with an audit event.
Refused while a parent is unfinished; ``dry_run`` only validates.
Returns ``(ok, reason)``.NFztask z
 not foundz is z-; promote only applies to 'todo' or 'blocked'z_SELECT t.id, t.status FROM tasks t JOIN task_links l ON l.parent_id = t.id WHERE l.child_id = ?rS  rš   z!unsatisfied parent dependencies: rX   zž (the ready -> running claim re-checks parents, so promotion cannot bypass them; complete the parents or drop the link with `hermes kanban unlink <parent_id> z`)zPUPDATE tasks SET status = 'ready' WHERE id = ? AND status IN ('todo', 'blocked')z  status changed during promotionÚpromoted_manualr  r  )r  rR   r"  )TN)r2  r£   rO  r\   r-  r‰  r1  )
r’   rp   r  r  r§   rY  r  r  ÚunsatisfiedÚupds
   &&$$$     r   Úpromote_taskrÔ  ™  sg  € ô ˜dÓ,€JØÒØ˜˜g˜Y jÐ1Ð1Ð1àÐ,Ô,ØØ�G�9˜D ¡ð /"ð #ð
ð 	
ð �l‰lð	à!( 
ó÷ �hƒjð	 ñ
 %,ÓW¡G˜q°­{ÐBVÑ/V”7�1�T—7�7¡G€KÐWßØØ/°·	±	¸+Ó0FÐ/Gð H1ð 29°	¸ð=ð
ð 	
÷ ØÐä	�4��Ø�l‰lð=Ø?F¸jó
ˆð �<‰<˜1ÔØ˜E ' Ð*JÐKÐK÷ 
Šô 	�dÐ%6¸À%ÈÐSYÐ8ZÔ[÷ 
ð Ðùò+ X÷ 
Žð Ðús   ÁDÁ$DÂ2,D	Ã)D	Ä	D	c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r’   r“   rp   r#   r8  r6   rt  r   rg   r   )r   s   "r   r   r   Ê  s4   € ÷ 
ñ 
Ø
ð
Ø'*ð
Ø>Að
ØILð
à	ñ
r   c          	     ó  € RP                  R V 4       4      pV P                  RV R2V.VO54      P                  4       pV'       d8   VR,          '       d'   V P                  RWC\        VR,          4      34       R# R# R# )z„Close a leaked open run before a status flip so the invariant
``current_run_id IS NULL <=> run row terminal`` holds; no-op normally.rX   c              3  ó&   "  € T F  pR x € K	  	  R# 5ir²  r   r³  s   & r   rc   Ú(_reclaim_dangling_run.<locals>.<genexpr>Ï  s   é € Ð3©( Q�S«(ùrµ  z=SELECT current_run_id FROM tasks WHERE id = ? AND status IN (rN  r¢   a;  
            UPDATE task_runs
               SET status = 'reclaimed', outcome = 'reclaimed',
                   summary = COALESCE(summary, ?),
                   ended_at = ?,
                   claim_lock = NULL, claim_expires = NULL, worker_pid = NULL
             WHERE id = ? AND ended_at IS NULL
            N)r\   r£   r¤   r6   )r’   rp   rs  r8  rt  rP  r­  s   &&$$$  r   rv  rv  Ê  s‡   € ð
 —9‘9Ñ3©(Ó3Ó3€LØ�L‰LØ
GÈÀ~ÐUVÐWØ	Ð�8Ñó÷ �hƒjð 
÷ �Ð'×(Ô(Ø�‰ðð œ˜EÐ"2Õ3Ó4Ð5ö
	
ñ )�ur   c               ó$   € V ^8„  d   QhRRRRRR/# rG  r   )r   s   "r   r   r   â  s'   € ÷ Dñ DÐ(:ð DÀSð DÈSñ Dr   c                ó,   € \        W4      '       d   R# R# )u—   ``ready`` if every parent is terminal else ``todo`` â€” the re-gate shared by
unblock/reopen so neither can spawn a child whose upstream is unfinished.rJ  r  )r_  r§  s   &&r   rÉ  rÉ  â  s   € ô )¨×7Ò7ˆ7ÐC¸VÐCr   c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r   è  s"   € ÷ )ñ )Ð)ð )°Cð )¸Dñ )r   c                óÖ  € \        \        P                  ! 4       4      p\        V 4      ;_uu_ 4        \        W4      R8X  d   \	        W4      MRp\        WRVRR7       \        W4      pVR8X  d
   VR8X  d   RMTpV P                  RWQ34      pVP                  ^8w  d    RRR4       R# \        YR	VR8w  g   VR8w  d   R
VRV/MR4        RRR4       R#   + '       g   i     R# ; i)zŒ``blocked``/``scheduled`` -> its resumable phase (parent re-gated; ``review``
when that is where it left off), closing any leaked run first.rR   rJ  zinvariant recovery on unblockrr  rI  z›UPDATE tasks SET status = ?, current_run_id = NULL, consecutive_failures = 0, last_failure_error = NULL WHERE id = ? AND status IN ('blocked', 'scheduled')NFÚ	unblockedrS  rK  T)rR   Ú	scheduled)
r6   r+  r-  r2  rO  rv  rÉ  r£   r‰  r1  )r’   rp   r8  rK  Úlanding_statusrš  rŒ  s   &&     r   Úunblock_taskrà  è  sô   € ô Œd�iŠi‹kÓ
€CÜ	�4��ô ˜DÓ*¨iÔ7ô ' tÔ5àð 	ô
 	ØÐ$<À#Ø0õ	
ô
 7°tÓEˆð  Ô(¨]¸hÔ-Fñ àð 	ð �l‰lðBàDNÐCXó
ˆð
 �<‰<˜1ÔØ÷9 
‰ô: 	Ø˜;ð  Ô(¨M¸WÔ,Dð ˜: ¸ÑFàô	
ð ÷K 
��Šús   ²A/CÂ+"CÃC(	c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r     s"   € ÷ !ñ !Ð/ð !¸#ð !À$ñ !r   c           
     óP  € \        \        P                  ! 4       4      p\        V 4      ;_uu_ 4        \        WRVRR7       \	        W4      p\        WR4      p\        \        VR4      4      p\        VP                  R4      4      pV.V'       d   V3MROVN5pT P                  RV'       d   RMR,           R	,           V4      pVP                  ^8w  d    R
R
R
4       R# RV/p	V'       d   WiR&   \        YRV	RR/8w  d   T	MR
4        R
R
R
4       R#   + '       g   i     R
# ; i)a  ``review`` -> ``ready``/``todo`` so the implementer re-runs on the new
comments; restores the implementer from the ``review_requested`` event.
Preserves ``consecutive_failures`` and the block loop counter (review is
not a block; only :func:`complete_task` clears them).z#invariant recovery on review reopenrr  r€  r±  r·  z‰UPDATE tasks SET status = ?, current_run_id = NULL, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL rµ  r8   z# WHERE id = ? AND status = 'review'NFrS  Úreview_reopenedrJ  T)rI  r   )r6   r+  r-  rv  rÉ  rE  r2   r'   rÄ  r;   r£   r‰  r1  )
r’   rp   r8  rš  Úreview_eventÚhandoffr·  rk  rŒ  r±  s
   &&        r   Úreopen_review_taskræ    s  € ô
 Œd�iŠi‹kÓ
€CÜ	�4��ÜØ K°SØ6õ	
ô 3°4ÓAˆ
Ü$ TÐ4FÓGˆÜœX l°IÓ>Ó?ˆÜ# G§K¡K°Ó$>Ó?ˆØ#-Ð"aÇ;°+±ÐTVÐ"aÐY`Ñ"aˆØ�l‰lðdç#.Ñ°Bõ8ð 4õ4ð ó
ˆð �<‰<˜1ÔØ÷) 
‰ð* $,¨ZÐ"8ˆßØ%0�MÑ"ÜØÐ,¸ÀXÈwÐDWÔ9W©gÐ]aô	
ð ÷7 
��Šús   ²A<DÂ/(DÃ!DÃ-DÄD%	c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r’   r“   rp   r#   rœ  r   zdict[str, Any]r   )r   s   "r   r   r   8  s8   € ÷ \Fñ \FØ
ð\FØ'*ð\FØ7:ð\Fàñ\Fr   c               ó  € \        V P                  4      p\        \        P                  ! 4       4      p. p. p\	        V RR7      ;_uu_ 4        V P                  RV34      P                  4       pV EFH  pVR,          p	V	R%9  d   K  Rp
RpV	R8X  d   Rp
MdV	R8X  d^   \        WR	,          VR
,          4      p
VP                  VR,          VR,          VR,          34       \        WR	,          RRRV R2R7      pV P                  RVR	,          34       R	VR	,          RV	RRRV
/p\        YR	,          RRV/VP                  4        UUu/ uF  w  rÞVR	8w  g   K  WÞbK  	  uppCVR7       \        WR	,          RRRRRRVRV	RV
/VR7       \        WR	,          VRV RV	 R V
 R!2V4       VP                  V4       EKK  	  RRR4       V'       g   V F  w  ppp\        VVVR"7       K  	  R#VR$V/# u uppi   + '       g   i     L?; i)&u™  THE done-reopen invalidation: every ``ready``/``review``/``running``/``done``
descendant of a reopened ancestor is demoted to ``todo`` and re-gated.
Every surface that reopens a done task (dashboard PATCH/drag) routes here.

Composes under the caller's txn (``allow_nested=True``) so the flip and the
retractions commit atomically. Each descendant gets a
``descendant_invalidated`` event, the legacy ``status`` event the live feed
renders, and a comment naming the ancestor. Running descendants are closed
``reclaimed`` and their workers killed strictly post-commit (audit trail
before death) â€” when composed, the CALLER must drain ``terminations``
after its own commit. ``consecutive_failures`` resets (deliberate operator
action), the opposite of :func:`reopen_review_task`.

Returns ``{"invalidated": [{id, prior_status, new_status, resume_status}],
"terminations": [(worker_pid, claim_lock, worker_started_at)]}``.
Tr  aè  
            WITH RECURSIVE descendants(id) AS (
                SELECT child_id FROM task_links WHERE parent_id = ?
                UNION
                SELECT l.child_id
                FROM task_links l
                JOIN descendants d ON d.id = l.parent_id
            )
            SELECT t.id, t.status, t.current_run_id, t.worker_pid, t.claim_lock, t.worker_started_at
            FROM descendants d
            JOIN tasks t ON t.id = d.id
            ORDER BY t.id
            rS  rJ  rI  rQ   Nrš   r¢   r™   rZ  r”  rž  r  z	ancestor z	 reopenedrÈ  zÉUPDATE tasks SET status = 'todo', completed_at = NULL, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, current_run_id = NULL, consecutive_failures = 0 WHERE id = ?r  rš  rK  Údescendant_invalidatedÚancestorri  r  Úancestor_reopenedr  Úprevious_statuszInvalidated: ancestor z was reopened; retracted from 'z' to 'todo' (will resume via 'z').)rW  ÚinvalidatedÚterminations>   r#  rJ  rI  rQ   )r�   Úin_transactionr6   r+  r-  r£   rO  r~  r   r   r1  rª  r  r§  )r’   rp   rœ  Úcaller_owns_txnr8  rí  rî  rQ  r!   rì  rK  r   ÚentryÚkÚvr–   rZ  rW  s   &&$               r   Ú(invalidate_descendants_for_parent_reopenrô  8  sJ  € ô& ˜4×.Ñ.Ó/€OÜ
Œd�iŠi‹kÓ
€CØ(*€KØMO€LÜ	�4 d×	+Ö	+Ø�|‰|ðð ˆJó
÷ ‰(‹*ð 	ô  ˆCØ! (�mˆOØÐ&LÔLÙØ#ˆMØˆFØ (Ô*Ø (‘Ø  IÔ-Ü 5°dÀ½IÀsÐK[ÕG\Ó ]�Ø×#Ñ# S¨Õ%6¸¸LÕ8IÈ3ÐObÕKcÐ$dÔeÜ!Ø˜d�)¨[ÀØ'¨ y°	Ð:ô�ð �L‰LðOàQTÐUYÕQZÐP\ôð �c˜$•i °Ø˜f o°}ðˆEô Ø˜$•iÐ!9Ø˜WÐV¸%¿+¹+¼-Ô(U¹-±$°!È1ÐPTÉ9¬¨ª¹-Ò(UÐVØõô Ø˜$•i à˜f hÐ0CÀXÈwØ% ¸Èðð õô Ø˜$•i Ð+AÀ'Àð K#Ø#2Ð"3ð 4%Ø%2 O°3ð*8à9<ôð
 ×Ñ˜u×%ñ] ÷# 
,÷@ ó ,8Ñ'ˆC�˜ZÜ'¨¨ZÀJ×Oñ ,8à˜;¨¸ÐEÐEùó1 )V÷[ 
,×	+ús&   ÁC;G5ÅG/ÅG/ÅA G5Ç/G5Ç5H	rœ  c               ó4   € V ^8„  d   QhRRRRRRRRRRR	RR
R/# )r   r’   r“   rp   r#   rQ  rD   rR  rƒ   rœ  r   r�   r   )r   s   "r   r   r   —  sF   € ÷ 6ñ 6Ø
ð6Ø'*ð6Ø6Cð6à
ð6à*7ð6àHUð6ð 
ñ6r   c               óJ  € Ve"   VP                  4       '       g   \        R4      h\        V4      p\        V 4      ;_uu_ 4        V P	                  RV34      P                  4       pVf    RRR4       R# R.p. p. p	Veh   VP                  4       VR,          ;'       g    R8w  dB   VP                  R4       VP                  VP                  4       4       V	P                  R4       VeV   T;'       g    RVR	,          ;'       g    R8w  d4   VP                  R
4       VP                  V4       V	P                  R	4       VeK   YFR,          ;'       g    R8w  d4   VP                  R4       VP                  V4       V	P                  R4       VP                  V4       V P	                  RRP                  V4       R2\        V4      4      p
V
P                  ^8w  d    RRR4       R# V	'       dt   V'       dl   VP                  4       '       dV   \        WVP                  4       RRP                  V	4      ,           R,           \        \        P                  ! 4       4      4       \        YRV	'       d   RV	/MR4       RRR4       \        V 4       R#   + '       g   i     L; i)zäUpdate title/body/assignee (when given) and move ``triage -> todo`` in one
txn; False when not in triage. Lands in ``todo`` (not ``ready``) so parent
gating still applies; the audit comment is written only when a field changed.
Nztitle cannot be blankzJSELECT title, body, assignee FROM tasks WHERE id = ? AND status = 'triage'Fzstatus = 'todo'rQ  r8   z	title = ?rR  zbody = ?rƒ   zassignee = ?r‚  rX   z# WHERE id = ? AND status = 'triage'u   Specified â€” updated z and promoted to todo.Ú	specifiedrž   T)r<   r=   rÒ  r-  r£   r¤   r   r\   r*  r‰  r  r6   r+  r1  rš  )r’   rp   rQ  rR  rƒ   rœ  rN  Úsetsrk  rž   rŒ  s   &&$$$$     r   Úspecify_triage_taskrù  —  s   € ð Ò §¡§¢ÜÐ0Ó1Ð1Ü" 8Ó,€HÜ	�4��Ø—<‘<ØXØˆJó
÷ ‰(‹*ð 	ð ÒØ÷ 
‰ð -Ð-ˆØˆØ$&ˆØÒ §¡£°8¸GÕ3D×3JÐ3JÈÔ!KØ�K‰K˜Ô$Ø�M‰M˜%Ÿ+™+›-Ô(Ø×!Ñ! 'Ô*ØÒ § ¨°¸&Õ1A×1GÐ1GÀRÔ HØ�K‰K˜
Ô#Ø�M‰M˜$ÔØ×!Ñ! &Ô)ØÒ H¸*Õ1E×1MÐ1MÈÔ$NØ�K‰K˜Ô'Ø�M‰M˜(Ô#Ø×!Ñ! *Ô-Ø�‰�gÔØ�l‰lØ §	¡	¨$£Ð0ð 11ð 2Ü38¸³=ó
ˆð �<‰<˜1ÔØ÷9 
‰÷: Ÿf¨¯©¯ªäØ˜vŸ|™|›~Ø(¨4¯9©9°^Ó+DÕDÐG_Õ_Ü”D—I’I“KÓ ôô
 	Ø˜;ß2@Ð˜~Ñ.Àdô	
÷I 
ôT �DÔÙ÷W 
�úsF   Á'JÁ5)JÂAJÃ4JÄAJÅBJÇ)JÇ1JÇ9JÈA.JÊJ"	c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r   Ð  s"   € ÷ ,ñ ,Ð)ð ,°Cð ,Ètñ ,r   c          
     óˆ  € \        V 4      ;_uu_ 4        V P                  RV34      P                  4       pV'       g    RRR4       R# VR,          R8H  pVR,          VR,          VR,          rvpV P                  R	V34      pVP                  ^8w  d    RRR4       R# \	        WR
R
RR7      p	\        WRRV	R7       RRR4       X'       d;   \        XXVXR7      p
\        V 4      ;_uu_ 4        \        WRV
X	R7       RRR4       \        V 4       \        W4       R#   + '       g   i     Lj; i  + '       g   i     L;; i)u  Archive a task; a *running* task's host-local worker is terminated.

Clearing ``worker_pid`` in the DB alone left the OS process running past its
own archive â€” it kept executing (and pushing work) against a task nothing
tracked anymore (#76196). Snapshot pid+claim inside the archive txn so the
kill is contingent on THIS caller winning the archive transition (a losing
concurrent archiver must never signal the pid); the kill itself runs after
commit â€” ``_poll_worker_exit`` can wait ~5 s and must not hold the write
lock. Post-release kill is safe here because ``archived`` is terminal: no
dispatcher can spawn a duplicate worker off the released claim. The
termination outcome lands as its own ``archive_worker_termination`` event so
the ``archived`` event stays atomic with the status flip.
r½  NFrS  rQ   r™   rZ  r”  z¤UPDATE tasks SET status = 'archived',     claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL WHERE id = ? AND status != 'archived'rž  z#task archived with run still activerÈ  rþ   ri  r•  Úarchive_worker_terminationT)	r-  r£   r¤   r‰  r   r1  r§  rš  r  )r’   rp   r�  r!   Úwas_runningÚprev_pidr¿  Úprev_startedrŒ  r   r¯  s   &&$        r   Úarchive_taskr   Ð  s  € ô 
�4��Ø�l‰lØ^ØˆJó
÷ ‰(‹*ð 	÷ Ø÷ 
‰ð ˜(•m yÑ0ˆØ,/°Õ,=¸sÀ<Õ?PÐRUÐViÕRj˜\ˆØ�l‰lð4à6=°Zó
ˆð
 �<‰<˜1ÔØ÷ 
‰ô" Ø ;°{Ø9ô
ˆô 	�d Z°¸fÕE÷+ 
÷, Ü1°(¸IÐQZÐgsÔtˆÜ�t�_�_Ü˜$Ð)EÀ{Ð[aÕb÷ ô �DÔä�tÔ%Ù÷= 
�ú÷0 �_ús$   ”+DÁ	ADÂDÃ.D1ÄD.	Ä1E	c               ó$   € V ^8„  d   QhRRRRRR/# rÈ  r   )r   s   "r   r   r   ÿ  s'   € ÷ Kñ KÐ!3ð K¸cð KÀdñ Kr   c                ól   € V P                  RW34       R F  pV P                  RV R2V34       K  	  R# )zKDelete every row referencing ``task_id`` (schema has no ON DELETE CASCADE).z:DELETE FROM task_links WHERE parent_id = ? OR child_id = ?zDELETE FROM z WHERE task_id = ?N)rÓ  r  Ú	task_runsÚkanban_notify_subsrH  )r’   rp   rÌ  s   && r   Ú_delete_task_relationsr  ÿ  s:   € à‡L�LÐMÐPWÐOaÔbÛTˆØ�‰�| E 7Ð*<Ð=À¸zÖJó Ur   c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r     s"   € ÷ !ñ !Ð1ð !¸Cð !ÀDñ !r   c                óö   € \        V 4      ;_uu_ 4        \        W4      R8w  d    RRR4       R# \        W4       V P                  RV34      pVP                  ^8H  uuRRR4       #   + '       g   i     R# ; i)z~Hard-delete an ARCHIVED task (+ related rows); anything else must be
archived first so data loss takes two deliberate actions.rþ   NFúDELETE FROM tasks WHERE id = ?)r-  r2  r  r£   r‰  ©r’   rp   rŒ  s   && r   Údelete_archived_taskr
    sY   € ô 
�4��Ü˜Ó&¨*Ô4Ø÷ 
‰ô 	˜tÔ-Ø�l‰lÐ;¸g¸ZÓHˆØ�|‰|˜qÑ ÷ 
��‹ús   ”A'°,A'Á'A8	c               ó$   € V ^8„  d   QhRRRRRR/# r>  r   )r   s   "r   r   r     s"   € ÷ ñ Ð(ð °3ð ¸4ñ r   c                óî   € \        V 4      ;_uu_ 4        V P                  RV34      pVP                  ^8w  d    RRR4       R# \        W4       RRR4       \	        V 4       R#   + '       g   i     L; i)zIHard-delete a task and its related rows in one txn; False when not found.r  NFT)r-  r£   r‰  r  rš  r	  s   && r   Údelete_taskr    sX   € ä	�4��Ø�l‰lÐ;¸g¸ZÓHˆØ�<‰<˜1ÔØ÷ 
‰ô 	˜tÔ-÷	 
ô
 �DÔÙ÷ 
�ús   ”&A$ÁA$Á$A4	c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r’   r“   rp   r#   r  rD   rƒ  r€   r   r�   r   )r   s   "r   r   r     s6   € ÷ ñ Ø
ðØ'*ðØ7Dðà"ðð 
ñr   c               ój  € \        V 4      ;_uu_ 4        V.pRpVe$   VR,          pVP                  \        V4      4       V P                  WT4      P                  ^8w  d    RRR4       R# \        WRRV\        V4      R7      p\        WRRV/VR7        RRR4       R	#   + '       g   i     R# ; i)
zjPark in ``scheduled`` (waiting on time, not a human; not dispatchable)
until ``unblock_task`` re-gates it.a'  
            UPDATE tasks
               SET status       = 'scheduled',
                   claim_lock   = NULL,
                   claim_expires= NULL,
                   worker_pid   = NULL
             WHERE id = ?
               AND status IN ('todo', 'ready', 'running', 'blocked')
        Nr  FrÞ  r”  r  ri  T)r-  r   r6   r£   r‰  r9  r�   r1  )r’   rp   r  rƒ  rk  rz  r   s   &&$$   r   Úschedule_taskr    s¢   € ô 
�4��Ø$˜Iˆðˆð Ò&ØÐ,Õ,ˆCØ�M‰Mœ#˜oÓ.Ô/Ø�<‰<˜Ó$×-Ñ-°Ô2Ø÷ 
‰ô  (Ø ;°{ÈFÔ_cÐdjÓ_kô
ˆô 	�d [°8¸VÐ2DÈVÕTØ÷) 
��Šús   ”AB!Á,+B!Â!B2	c               ó$   € V ^8„  d   QhRRRRRR/# rG  r   )r   s   "r   r   r   ;  s"   € ÷ ,ñ ,Ð1ð ,¸Cð ,ÀCñ ,r   c                ó€  € \        W4      pV'       g   \        RV 24      h^ RIHpHpHpHpHpHp \        \        P                  ! 4       4      p	. p
V! W¢4       V! V
\        W4      4       V! W W4       V! W W4       V! W W)4       V! V
\        W4      V	4       RP                  V
4      P                  4       R,           # )a  Everything a worker should read about its task: header, body,
attachments, prior attempts, done-parent handoffs, the assignee's recent
work, comments. Lists are tail-capped and fields char-capped
(``_CTX_MAX_*``) so the prompt stays bounded on pathological boards.rÊ  )Ú_ctx_attachmentsÚ_ctx_commentsÚ_ctx_headerÚ_ctx_parent_resultsÚ_ctx_prior_attemptsÚ_ctx_role_historyr  )rê  r=   Úhermes_cli.kanban_db_contextr  r  r  r  r  r  r6   r+  r  rÕ  r\   rK  )r’   rp   r}   r  r  r  r  r  r  r8  r$  s   &&         r   Úbuild_worker_contextr  ;  s¨   € ô
 �DÓ"€DßÜ˜=¨¨	Ð2Ó3Ð3÷÷ ô
 Œd�iŠi‹kÓ
€CØ€EÙ�ÔÙ�UÔ,¨TÓ;Ô<Ù˜ WÔ2Ù˜ WÔ2Ù�e 4Ô-Ù�%œ tÓ5°sÔ;Ø�9‰9�UÓ×"Ñ"Ó$ tÕ+Ð+r   c               ó    € V ^8„  d   QhRRRR/# )r   r’   r“   r   r/   r   )r   s   "r   r   r   U  s   € ÷ ñ Ð(ð ¨Tñ r   c                ól  € / pV P                  R4       F  p\        VR,          4      WR,          &   K   	  \        V 4      pV P                  R4      P                  4       p\        \        P                  ! 4       4      pV'       d%   VR,          e   V\        VR,          4      ,
          MRpRVRVR	VR
V/# )zQPer-status + per-assignee counts and the oldest ``ready`` age (staleness signal).zRSELECT status, COUNT(*) AS n FROM tasks WHERE status != 'archived' GROUP BY statusrû  rS  z>SELECT MIN(created_at) AS ts FROM tasks WHERE status = 'ready'ÚtsNÚ	by_statusÚby_assigneeÚoldest_ready_age_secondsr8  )r£   r6   Ú_counts_by_assigneer¤   r+  )r’   r  r!   r  Ú
oldest_rowr8  Úoldest_ready_ages   &      r   Úboard_statsr$  U  sº   € à "€IØ�|‰|ð	5öˆô $' s¨3¥x£=ˆ	�h•-Ó ñ	ô & dÓ+€Kà—‘ØHóç�hƒjð ô Œd�iŠi‹kÓ
€C÷ ˜* TÕ*Ò6ð 
Œs�:˜dÕ#Ó$Ö	$Ø<@ð ð 	�YØ�{Ø"Ð$4Øˆsð	ð r   c               ó    € V ^8„  d   QhRRRR/# )r   r’   r“   r   zdict[str, dict[str, int]]r   )r   s   "r   r   r   q  s   € ÷ 	ñ 	Ð0ð 	Ð5Nñ 	r   c                ó    € / pV P                  R4       F6  p\        VR,          4      VP                  VR,          / 4      VR,          &   K8  	  V# )z4``{assignee: {status: n}}`` over non-archived tasks.zSELECT assignee, status, COUNT(*) AS n FROM tasks WHERE status != 'archived' AND assignee IS NOT NULL GROUP BY assignee, statusrû  rƒ   rS  )r£   r6   Ú
setdefault)r’   Úcountsr!   s   &  r   r!  r!  q  sQ   € à(*€FØ�|‰|ð	$öˆô
 ADÀCÈÅHÃˆ×Ñ˜#˜j�/¨2Ó.¨s°8­}Ó=ñð €Mr   c               ó   € V ^8„  d   QhRR/# )r   r   r€   r   )r   s   "r   r   r   }  s   € ÷ ñ �mñ r   c                ón  € V f   R# \        V \        \        34      '       d   \        V 4      # \        V 4      P	                  4       pV'       g   R#  \        V4      #   \
         d     Mi ; i ^ RIHp TP                  T4      p\        TP                  4       4      #   \
        \        3 d     R# i ; i)zMEpoch seconds from int/float/numeric string/ISO-8601; None for empty/invalid.N)Údatetime)
r   r6   Úfloatr#   r<   r=   r+  ÚfromisoformatÚ	timestampr  )r
  rÛ   r+  Údts   &   r   r|  r|  }  sš   € à
‚{ÙÜ�#œœU�|×$Ò$Ü�3‹xˆÜˆC‹�‰Ó€AßÙðÜ�1‹vˆøÜô ÙðúðÝ%Ø×#Ñ# AÓ&ˆÜ�2—<‘<“>Ó"Ð"øÜœÐ ô Úðús$   Á
A ÁA+Á*A+Á//B ÂB4Â3B4c               ó    € V ^8„  d   QhRRRR/# )r   r}   r”   r   r/   r   )r   s   "r   r   r   “  s   € ÷ ñ �4ð ˜Dñ r   c                óò   € \        \        P                  ! 4       4      pV P                  V P                  V P                  rCpRVe	   W,
          MRRVe	   W,
          MRRVe   YC;'       g    T,
          /# R/# )zEReturn age metrics for a single task. All values are seconds or None.Úcreated_age_secondsNÚstarted_age_secondsÚtime_to_complete_seconds)r6   r+  rV  rW  rX  )r}   r8  Ú_cÚ_sÚ_cos   &    r   Útask_ager8  “  sr   € ä
Œd�iŠi‹kÓ
€CØ—/‘/ 4§?¡?°D×4EÑ4EˆC€Bà¨2ª>˜sžx¸tØ¨2ª>˜sžx¸tØ"¸º C¯8¨8°Õ$4ðð ð NRðð r   c               ó    € V ^8„  d   QhRRRR/# )r   Úolder_than_secondsr6   r   r   )r   s   "r   r   r      s   € ÷ ñ ¨3ð °3ñ r   c                óJ   € \        V 4      p V ^ 8  d   \        RV : R24      hV # )uè   Normalise a gc retention window, rejecting negatives.

Shared by both gc sweeps: a negative window puts the cutoff in the future,
so "older than cutoff" would match every row / file instead of none â€”
refuse before any sweep runs.
z%older_than_seconds must be >= 0, got z*: a negative retention selects everything.)r6   r=   )r:  s   &r   Ú_retention_secondsr<     sA   € ô Ð/Ó0ÐØ˜AÔÜØ3Ð4FÑ3Ið J7ð 7ó
ð 	
ð Ðr   r:  i  c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   r:  r6   r   r   )r   s   "r   r   r   °  s#   € ÷ "ñ "Ð&ð "¸sð "ÐX[ñ "r   c               ó   € \        \        P                  ! 4       4      \        V4      ,
          p\        V 4      ;_uu_ 4        V P	                  RV34      pRRR4       \        XP
                  ;'       g    ^ 4      #   + '       g   i     L/; i)zÞPrune old done/archived events, retaining decomposition identity until task deletion.

``older_than_seconds=0`` means everything older than now; the CLI maps
``--event-retention-days 0`` to "disabled" before calling this.
z�DELETE FROM task_events WHERE created_at < ? AND kind != 'decomposed' AND task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'archived'))N)r6   r+  r<  r-  r£   r‰  )r’   r:  ÚcutoffrŒ  s   &$  r   Ú	gc_eventsr@  °  sh   € ô ”—’“ÓÔ 2Ð3EÓ FÕF€FÜ	�4��Ø�l‰lðJØLRÈ9ó
ˆ÷ 
ô
 ˆs�|‰|× Ð ˜qÓ!Ð!÷ 
�ús   ÁA=Á=B	c               ó$   € V ^8„  d   QhRRRRRR/# )r   r:  r6   r‚   rD   r   r   )r   s   "r   r   r   ¿  s"   € ÷ ñ ¨#ð À}ð Ð`cñ r   c                óè  € \        V 4      p \        VR7      pVP                  4       '       g   ^ # \        P                  ! 4       V ,
          p^ pVP	                  4        F|  p\
        P                  ! \        4      ;_uu_ 4        VP                  4       '       d9   VP                  4       P                  V8  d   VP                  4        V^,          pRRR4       K~  	  V#   + '       g   i     K“  ; i)zÕDelete worker log files older than the cutoff on one board; returns the count.

``older_than_seconds=0`` means everything older than now; the CLI maps
``--log-retention-days 0`` to "disabled" before calling this.
rà   N)r<  rO  r  r+  Úiterdirr  r  r  r  r-  Úst_mtimer  )r:  r‚   Úlog_dirr?  r›  r  s   $$    r   Úgc_worker_logsrF  ¿  s­   € ô ,Ð,>Ó?ÐÜ EÔ*€GØ�>‰>×ÒÙÜ�YŠY‹[Ð-Õ-€FØ€GØ�_‰_ÖˆÜ× Ò ¤×)Õ)Ø�y‰y�{Š{˜qŸv™v›x×0Ñ0°6Ô9Ø—‘”
Ø˜1•�÷ *Ñ)ñ ð
 €N÷	 *×)Ð)ús   ÂAC Ã C1c               ó$   € V ^8„  d   QhRRRRRR/# rH  r   )r   s   "r   r   r   Õ  s!   € ÷ ;ñ ;˜Sð ;¨Mð ;ÀTñ ;r   c               ó.   € \        VR7      V  R2,          # )zqWorker log path (may not exist). The dispatcher always passes ``board``
explicitly to avoid resolution ambiguity.rà   z.log)rO  rJ  s   &$r   Úworker_log_pathrI  Õ  s   € ô  Ô'¨W¨I°TÐ*:Õ:Ð:r   Ú
tail_bytesc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rp   r#   rJ  r€   r‚   rD   r   r   )r   s   "r   r   r   Û  s,   € ÷ ñ ØðØ!.ðØ>Kðàñr   c               ój  € \        WR7      pVP                  4       '       g   R#  Vf   VP                  RRR7      # VP                  4       P                  p\        VR4      ;_uu_ 4       pWA8”  ds   VP                  WA,
          4       VP                  4       pVP                  4       P                  R4      '       g'   VP                  4       V8¼  d   VP                  V4       VP                  4       P                  RRR	7      uuRRR4       #   + '       g   i     R# ; i  \         d     R# i ; i)
zNWorker log text (last ``tail_bytes`` when set); None when the file is missing.rà   Nr  r   )rI   r   rp  ó   
r   r   )rI  r  r  r-  r.  rr  ÚseekÚtellÚreadlineÚendswithrs  r   r  )rp   rJ  r‚   rf   r¨  r°   Úprobes   &$$    r   Úread_worker_logrS  Û  sã   € ô ˜7Ô0€DØ�;‰;�=Š=ÙðØÒØ—>‘>¨;¸y�>ÓIÐIØ�y‰y‹{×"Ñ"ˆÜ�$˜×Ô ØÔ Ø—‘�tÕ(Ô)ð Ÿ™›�Ø—z‘z“|×,Ñ,¨U×3Ò3¸¿¹»ÀDÔ8HØ—F‘F˜5”MØ—6‘6“8—?‘? 7°9�?Ó=÷ ××Óûô ô Úðús5   ¦D# ½.D# Á+BDÄ
D# ÄD 	ÄD# Ä D# Ä#D2Ä1D2c               ó   € V ^8„  d   QhRR/# )r   r   rK  r   )r   s   "r   r   r   õ  s   € ÷ ñ ˜yñ r   c                 ój  €  ^ RI Hp  V ! 4       pVR,          p\        4       pTP	                  4       '       d   TP                  R4       TP                  4       '       d(    TP                  R TP                  4        4       4       \        T4      #   \         d    . u # i ; i  \         d     L+i ; i)zŠProfiles with a ``config.yaml`` plus the implicit ``default``; reads paths
directly to avoid importing ``hermes_cli.profiles`` at startup.rê   Úprofilesr$   c              3  ó¦   "  € T FG  qP                  4       '       g   K  VR ,          P                  4       '       g   K9  VP                  x € KI  	  R# 5i)zconfig.yamlN)Úis_dirr  r5   )rb   Úes   & r   rc   Ú(list_profiles_on_disk.<locals>.<genexpr>  s2   é € ÐpÑ)? AÇ8Á8Ç:œÐSTÐWdÕSd×RmÑRm×Roœ˜ŸžÓ)?ùs   ‚AžA½A)rZ   rë   r,   rÎ   r  r  rX  r³  rC  r  r'  )rë   Údefault_rootÚprofiles_dirÚnamess       r   Úlist_profiles_on_diskr^  õ  s¤   € ðÝ<Ù.Ó0ˆØ# jÕ0ˆô “e€EØ×Ñ×ÒØ�	‰	�)ÔØ×Ñ×Òð	Ø�L‰LÑp¨×)=Ñ)=Ô)?ÓpÔpô �%‹=Ðøô ô ØŠ	ðûô ô 	Ùð	ús#   ‚B Á &B$ ÂB!Â B!Â$B2Â1B2c               ó    € V ^8„  d   QhRRRR/# )r   r’   r“   r   z
list[dict]r   )r   s   "r   r   r   
  s   € ÷ ñ Ð,ð °ñ r   c                óÎ   € \        \        4       4      p\        V 4      p\        V\        V4      ,          4       Uu. uF  pRVRW19   RVP	                  V/ 4      /NK  	  up# u upi )z‰``{"name", "on_disk", "counts"}`` for every on-disk profile or task
assignee, so a fresh profile appears in pickers before it has a task.r5   Úon_diskr(  )rÎ   r^  r!  r'  r;   )r’   ra  r(  r5   s   &   r   Úknown_assigneesrb  
  sk   € ô Ô'Ó)Ó*€GÜ  Ó&€Fô ˜7¤S¨£[Õ0Ô1óá1ˆDð 
��y $¡/°8¸V¿Z¹ZÈÈbÓ=QÓRÙ1ñð ùò s   ¼#A"Úinclude_activeÚ
state_typeÚ
state_namec               ó0   € V ^8„  d   QhRRRRRRRRR	RR
R/# )r   r’   r“   rp   r#   rc  r�   rd  rD   re  r   z	list[Run]r   )r   s   "r   r   r     s>   € ÷ +ñ +Ø
ð+Ø'*ð+Ø?Cð+àð+à2?ð+ð ñ+r   c               ó~  € VRJ VRJ ,          '       d   \        R4      hVe   VR	9  d   \        R4      hRpV.pV'       g
   VR,          pVe   VRV R2,          pVP                  V4       VR,          pV P                  WV4      P                  4       pV Uu. uF  p\        P                  V4      NK  	  up# u upi )
z†Runs in start order; ``include_active=False`` = closed only; ``state_type``
(``status``/``outcome``) + ``state_name`` filter together.Nz:state_type and state_name must both be set or both omittedz(state_type must be 'status' or 'outcome'z)SELECT * FROM task_runs WHERE task_id = ?z AND ended_at IS NOT NULLrg  rh  z  ORDER BY started_at ASC, id ASC)rS  r³   )r=   r   r£   rO  r�  r‚  )	r’   rp   rc  rd  re  Úqrk  rQ  rž  s	   &&$$$    r   Ú	list_runsri    sÀ   € ð 	�dÐ˜z¨TÐ1×2Ô2ÜÐUÓVÐVØÒ *Ð4IÔ"IÜÐCÓDÐDØ3€AØ ˜	€FßØ	Ð(Õ(ˆØÒØ	ˆu�Z�L Ð%Õ%ˆØ�‰�jÔ!ØÐ	+Õ+€AØ�<‰<˜Ó"×+Ñ+Ó-€DÙ%)Ó*¡T ŒC�L‰L˜ŽO¡TÑ*Ð*ùÒ*s   ÂB:c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   r   r6   r   úOptional[Run]r   )r   s   "r   r   r   -  s"   € ÷ .ñ .Ð$ð .¨cð .°mñ .r   c                ó”   € V P                  R \        V4      34      P                  4       pV'       d   \        P	                  V4      # R# )z$SELECT * FROM task_runs WHERE id = ?N)r£   r6   r¤   r�  r‚  )r’   r   r!   s   && r   Úget_runrm  -  s8   € Ø
�,‰,Ð=ÄÀFÃ¸~Ó
N×
WÑ
WÓ
Y€Cß #Œ3�<‰<˜ÓÐ-¨Ð-r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   rp   r#   r   rk  r   )r   s   "r   r   r   2  s"   € ÷ .ñ .Ð'ð .°#ð .¸-ñ .r   c                ó‚   € V P                  RV34      P                  4       pV'       d   \        P                  V4      # R# )zDReturn the most recent run regardless of outcome (active or closed).zSSELECT * FROM task_runs WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT 1N)r£   r¤   r�  r‚  r]  s   && r   Ú
latest_runrp  2  s>   € à
�,‰,ð	4Ø6=°Zó÷ �hƒjð ÷ !$Œ3�<‰<˜ÓÐ-¨Ð-r   c               ó$   € V ^8„  d   QhRRRRRR/# r.  r   )r   s   "r   r   r   ;  s"   € ÷ +ñ +Ð+ð +°cð +¸mñ +r   c                ój   € V P                  RV34      P                  4       pV'       d
   VR,          # R# )z—Newest non-empty run summary, or None. Workers hand off via ``summary`` and
leave ``tasks.result`` NULL, so views need this or a done task looks empty.z—SELECT summary FROM task_runs WHERE task_id = ? AND summary IS NOT NULL AND summary != '' ORDER BY COALESCE(ended_at, started_at) DESC, id DESC LIMIT 1r‘  Nr0  r]  s   && r   Úlatest_summaryrs  ;  s<   € ð �,‰,ð	HàJQÈó÷ �hƒjð	 ÷
 !ˆ3ˆy�>Ð* dÐ*r   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   F  s"   € ÷ 6ñ 6Ð-ð 6¸ð 6È>ñ 6r   c                óî   € \        V4      pV'       g   / # RP                  R V 4       4      pV P                  RV R2V4      P                  4       pV Uu/ uF  qUR,          VR,          bK  	  up# u upi )z€``{task_id: newest non-empty run summary}`` in one query (window function,
SQLite >= 3.25); tasks without a summary are omitted.rø  c              3  ó&   "  € T F  pR x € K	  	  R# 5ir²  r   r³  s   & r   rc   Ú#latest_summaries.<locals>.<genexpr>L  ó   é € Ð-© A�C«ùrµ  aD  
        SELECT task_id, summary FROM (
            SELECT task_id, summary,
                   ROW_NUMBER() OVER (
                       PARTITION BY task_id
                       ORDER BY COALESCE(ended_at, started_at) DESC, id DESC
                   ) AS rn
              FROM task_runs
             WHERE task_id IN (zZ)
               AND summary IS NOT NULL AND summary != ''
        ) WHERE rn = 1
        rp   r‘  ©r¥   r\   r£   rO  ©r’   r¬  rJ  rP  rQ  rž  s   &&    r   Úlatest_summariesr{  F  sƒ   € ô ˆx‹.€CßØˆ	Ø—8‘8Ñ-©Ó-Ó-€LØ�<‰<ð ð !-˜~ð .	ð	ð 	ó÷ �hƒjð 	ñ 15Ó5±¨1ˆi�L˜!˜I�,Ò&±Ñ5Ð5ùÒ5ó   ÁA2c               ó$   € V ^8„  d   QhRRRRRR/# )r   r’   r“   r¬  rŸ   r   zdict[str, int]r   )r   s   "r   r   r   _  s#   € ÷ 9ñ 9Ð"4ð 9Àð 9ÐR`ñ 9r   c                óî   € \        V4      pV'       g   / # RP                  R V 4       4      pV P                  RV R2V4      P                  4       pV Uu/ uF  qUR,          VR,          bK  	  up# u upi )z–``{task_id: started_at of the run ``tasks.current_run_id`` points at}``
in one query; tasks with no active run (NULL or dangling pointer) are omitted.rø  c              3  ó&   "  € T F  pR x € K	  	  R# 5ir²  r   r³  s   & r   rc   Ú*current_run_started_ats.<locals>.<genexpr>e  rx  rµ  z{SELECT t.id AS task_id, r.started_at AS started_at FROM tasks t JOIN task_runs r ON r.id = t.current_run_id WHERE t.id IN (rN  rp   rW  ry  rz  s   &&    r   Úcurrent_run_started_atsr�  _  s~   € ô ˆx‹.€CßØˆ	Ø—8‘8Ñ-©Ó-Ó-€LØ�<‰<ð	à&˜ qð	*ð 	ó	÷
 �hƒjð 	ñ 48Ó8±4¨aˆi�L˜!˜L�/Ò)±4Ñ8Ð8ùÒ8r|  )Ú_default_board_display_namer#  Úboard_metadata_pathÚcreate_boardÚlist_boardsr   Úremove_boardÚwrite_board_metadata)Ú_INITIALIZED_PATHSÚinit_dbr-  )r  rF  rZ  rE  )rV  rÇ   r©   rÀ  r©  Ú
_pid_aliver«  r§  r¥  r¨  Ú_worker_terminal_timeout_envr`   >   Ú	transientÚ
capabilityrŒ  r�  >   r!  rØ  rÙ  i  �)Úspawnedrž  rU  Úreconciled_orphansÚreaped_terminal_workersÚcrashedr­  Ú	timed_outÚauto_blockedÚrate_limitedÚauto_assigned_defaultÚrespawn_guardedÚskipped_per_profile_cappedÚskipped_unassignedÚskipped_nonspawnablei„  )
rš   rQ  rR  rƒ   rS  rT  rU  rY  r•   rZ  )rV  rW  rX  r[  rc  )r]  r^  r\  rO   r_  r™   rb  r¢   rd  re  rj  rm  rp  )rg  rh  ri  rl  rn  r   i �' (  Ú__conditional_annotations__rˆ  Ú
__future__r   r  r*   r9   rG  rº  r4  rK   ÚsysÚloggingr+  Úcontextvarsr   r   Údataclassesr   Úpathlibr   Útypingr   r   r	   Úhermes_cli.kanban_workflowr
   ri  Útoolsetsr   Ú	getLoggerr„  rx   r   r'   r-   r2   r?   rP   r&  r–  r¢  r)  r^   Ú	frozensetrÿ  ÚplatformÚ_IS_WINDOWSrú  rm   r{   r…   rw   r�   rœ   r¦   rµ   r¶   r¼   r¤  ÚRECLAIM_DEFER_GRACE_SECONDSr½   rÂ   ÚKANBAN_RATE_LIMIT_EXIT_CODEÚ"KANBAN_TERMINAL_PROVIDER_EXIT_CODErÃ   rÈ   rß   rÊ   r‰  rÌ   ÚcontextmanagerrÑ   rÖ   ÚcompilerÙ   rÜ   rá   rå   rk   ró   r÷   r„   r  r  r   rÿ   r'  r-  r6  r<  rA  rF  rK  rO  r”   rz  r{  r}  r~  r�  r›  r¤  r¯  Ú
SCHEMA_SQLr¼  rÅ  rÉ  rÎ  rÒ  rã  rÝ  r  r=  r(  r.  r0  rS  r3  rê  ra  rl  rq  rx  rw  r€  r�  rˆ  rœ  r¢  rZ  rª  r¼  r¿  rÆ  rÄ  rÏ  rÕ  rÚ  r=   rÜ  rê  rò  rÿ  rü  r  r  r  r  r  r1  r   r%  rµ  r  r2  r›   Úobjectr3  r9  r8  rB  rE  rO  rš  r_  rb  rk  rw  r{  r~  r†  rˆ  rŒ  r‹  r°  rª  r¦  rÁ  rÆ  rÕ  rØ  rÚ  rÜ  ré  r6  rñ  ró  rú  r  r  r  r!  r	  r  r3  r  r  r
  r+  rU  r]  r,  r\  rˆ  r�  r—  r§  r½  r¸  rÄ  rÎ  rÔ  rv  rÉ  rà  ræ  rô  rù  r   r  r
  r  r  r  r$  r!  r|  r8  r<  r@  rF  rI  rS  r^  rb  ri  rm  rp  rs  r{  r�  Úhermes_cli.kanban_db_boardsr‚  r#  rƒ  r„  r…  r   r†  r‡  Úhermes_cli.kanban_db_connectrˆ  r‰  r-  Úhermes_cli.kanban_db_workspacer  rF  rZ  rE  Úhermes_cli.kanban_db_dispatchrV  rÇ   r©   rÀ  r©  rŠ  r«  r§  r¥  r¨  r‹  )rš  s   @r   Ú<module>r³     sÜ  øðôõ #ã Û Û 	Û 	Û Û Û Û 
Û Û ß )Ý !Ý ß *Ñ *å IÝ &à×Ò˜Ó"€õ
÷ð ÷ð õ6ð
¸÷ 
ð1°2÷ 1ð" $ YÐ/Ð ó MÐ ð Ð Û6Ð õSñ  ÑPÑ<MÔ<OÓPÓPÐ Ø�l‰l˜gÑ%€Ù.Ð ÷cð cõ	FõõõðAà÷Að"?à÷?ñ.Ð ð @Ø6:ð @ØLQ÷ @ñJ $Ð ñ -4Ð )ð "Ð ÷]ð ]ð !Ð ð !Ð ð
 &(Ð "õVõ
fð €Ù2<Ø*°Dô3Ð Ð/ó ñ
 1;Ø.¸ô1Ð Ð-ó ð
 ×Ññ1ó ð1ð( ×Ñô-ó ð-ð —’Ð:Ó;€õ
õ9õõ%õ/õ0õ
)õXõ&÷3ð 3÷-ð -õõ&-õ$"÷.Oð O÷gð g÷jð j÷3ð 3÷
@ð @ð ÷@
ð @
ó ð@
ñHÐ ñ iÐ ñÐ ñÐ ð
 ÷!
ð !
ó ð!
ðH ÷
ð 
ó ð
ð ÷
ð 
ó ð
ð* ÷
ð 
ó ð
ð(O€
õh'õ#õ0õõ,õ)EõX'%õT%ðPm&ØCGðm&à"ðm&à@Dðm&ð %)ðm&ð KOðm&ð "&ð	m&ð @Dð	m&ð VWð	m&ñ
  ðm&ð
 16ðm&ð
 Y]ðm&ð *.ðm&ð RVðm&ð "&ðm&ð HLðm&ð (,ðm&ð PTðm&ð ðm&ð >Bðm&ð Zcðm&ð !%ðm&ð >Bðm&ð `dðm&ð -1ðm&ð &*ðm&ð *.÷m&õ`Hõõõ0ð#à $÷#õL/ð Ð'ØÐ.ØÐ/ØÐ3ØÐ*ØÐ.ØÐ ØÐ<ØÐ=ð
%Ð �>ó 
ð,Ø;?ð,ØY]ð,à ð,à>Bð,à]bð,ð  ð,ð <@ð,ð +/ð	,ð SW÷	,õB÷@ð õõ$	ð8ð
 ,0÷8õvõ$õ#õ?õ?õõ(9õ'õ$4õ
õgð	/Ø?@÷	/ôG˜ô Gõ
	õ ðà"&ðàEIðð  ðð =A÷ð>'à"&ð'à45ð'àTX÷'õ2uõ9õ
õ(köóà ÷ò ñØVZñàñà<@ðàZ^÷ñ ÷D-÷5÷
@÷
*÷Qñ ‹h€‰Ô ñà!ñà>Bñð ÷ñ ñ "#ØVZñ"#àñ"#à<@ñ"#ð ÷"#ñ "#÷NJ÷8	Nò 	N÷÷$;ò ;÷@÷8ñ,à%)÷,ñ ,ñ^ ØLPñ à!÷ ñ  ñF'ØLPñ'à!÷'ñ '÷<Mò Mñ" ‘Ù™ÙÑ,ØˆyÙ�yð Ð ÷ò ñ<ØLPñà!÷ñ ÷$ñpØ+/ñpØPT÷pñ p÷f
÷
ñ<!ØGKñ!ØW[÷!ñ !ñHØ]bñà ÷ñ ÷$&ðT —J’JÑ4Ó5Ð ÷Sö
˜Zô 
ö	
˜:ô 	
öJ ô Jö
�Zô 
÷ðhØGKñhà!ñhà>Bñhð .2ñhð UYñhñ !%ñ	hð 5:÷	hñ hñV GÐ ÷÷.:÷(ðDá(÷ñ ÷"R÷÷2÷&!÷HN
÷b÷
*ðfá(9÷fñ f÷	ðJØFJðJàðJà:>ðJð !ñJð <@ñJð  $ð	Jð =A÷	Jñ JñZ[ØGKñ[àñ[àAE÷[ñ [÷|G÷:ñ~ØHLñ~à#ñ~à?Cñ~ð &*ñ~ð :?ñ~ð UZ÷~ñ ~÷BQ÷ GñCØ]a÷Cñ CñL.ØSWð.à÷.ñ .÷b
÷0D÷)÷X!÷H\Fð~6ØFJð6àð6à:>ñ6àX\÷6ñ 6ñr,Àd÷ ,ñ ,÷^K÷!÷ñØGKñà%)÷ñ ÷>,÷4÷8	÷÷,÷ñ "Á^÷ "ñ "ñ±ð ÐW[÷ ñ ð,;¸D÷ ;ñ ;ñØ15ðØNR÷ñ ÷4÷*ñ+ÙFJñ+à $ñ+àBF÷+ñ +÷,.÷
.÷+÷6÷29÷"	÷ 	ô 	÷ò ÷
÷ ô ÷÷ ÷ ÷ ÷ ÷ ó r   