+
    jHàjP ã                  ób  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Ht ^ RIHt ^ RIHt ^ RIHt ^ RIHt ^ RIHt ^ R	IHt ^ R
IHt ^ RIHt ^ RIHt ^ RIHt ]'       d   ^ RIHt ^t^t^xt]P@                  ! R]PB                  4      t"Rt#Rt$Rt%]P@                  ! R]PB                  4      t&] ! R R4      4       t'R R lt(Rt)Rt*/ t+R],R&   / t-R],R&   R R lt.R  R! lt/R" R# lt0R$ R% lt1]P@                  ! R&]Pd                  ! ]4      ,           R',           ]Pf                  4      t4RûR( R) llt5R* R+ lt6R, R- lt7R.t8R/ R0 lt9R1 R2 lt:R3 R4 lt;R5 R6 lt<RûR7 R8 llt=R9 R: lt>R;RR<R/R= R> llt?R;R/R? R@ llt@RA RB ltARC RD ltBRE RF ltCRGRRHR/RI RJ lltDR;R/RK RL lltERtFRM^ R;R/RN RO lltGRP RQ ltHRR RS ltI^tJ^2tKRT RU ltLRVtM]P@                  ! RW4      tNRX RY ltORZ R[ ltPRûR\ R] lltQ] ! R^ R_4      4       tRRûR`RRaR/Rb Rc llltSRûR`RRaR/Rd Re llltT] ! Rf Rg4      4       tURûRh Ri lltVRj Rk ltWRûRl Rm lltXRnRRoRpRqRpRrRpRsRRtRp/Ru Rv lltYRwRRxR/Ry Rz lltZR{ R| lt[R} R~ lt\RR€/R� R‚ llt]RƒR„R…R†R‡R†/t^RüR‰RˆRŠRpR‹R/RŒ R� lllt_RŽ R� lt`R� R‘ ltaR’ R“ ltbR” R• ltcR– R— ltdR˜ R™ lteRš R› ltfRœ R� ltgRžth^ti^tjRŸ R  ltkRûR¡ R¢ lltlR£ R¤ ltmR¥ R¦ ltnR§ R¨ ltoRûR© Rª lltpRûR« R¬ lltqR­RR®RR¯RpR°RR±RRn]RM^ RaRR²RR³RR´Rµ/R¶ R· lltrR¸ R¹ ltsRº R» lttR¼ R½ ltuRaR/R¾ R¿ lltvRÀ RÁ ltwRÂ RÃ ltxRÄRRÅR/RÆ RÇ lltyRÈ RÉ ltzR­RR®RR¯RpR°RR±RRn]RM^ RaRR²RR³RR´Rµ/RÊ RË llt{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…]EP                  RáRµ/Râ Rã ll4       t‡Rä Rå ltˆ]‰! 4       tŠRæ],Rç&   Rè Ré lt‹Rê Rë ltŒRì Rí lt�Rî Rï ltŽRaR/Rð Rñ llt�RòRóR°RRn]RôRRõR/Rö R÷ llt�^ RøI‘H’t“ ^ RùI‘H”t• ^ RúI‘H–t— R# )ýaµ  Dispatcher: crash/stale/orphan detection, failure accounting and the respawn circuit breaker, memory-aware concurrency caps, the one-shot ``dispatch_once`` pass, worker spawning (``_default_spawn``), worker-log rotation and the long-lived ``run_daemon`` loop.

Split out of ``hermes_cli.kanban_db``; origin-resident helpers are reached
late-bound via ``_kb`` (import-cycle breaking) so monkeypatching
``kanban_db.<name>`` keeps working.
)ÚannotationsN)Ú	dataclass)Úfield)ÚPath)ÚAny)ÚCallable)ÚIterable)ÚIterator)ÚMapping)ÚOptional)ÚTYPE_CHECKING)ÚKANBAN_WORKER_EXIT_TRAILER)ÚTaskz÷\b(quota|rate[\s_\-]?limit|429|403|auth|authenticat(?:e|es|ed|ing|ion)|authoriz(?:e|es|ed|ing|ation)|authoris(?:e|es|ed|ing|ation)|authz|unauthorized|forbidden|billing|subscription|access[\s_]denied|permission[\s_]denied|invalid[\s_]api[\s_]key)\bi  i,  i€Q z-https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+c                  óÜ  € ] tR t^Xt$ Rt^ tR]R&   ^ tR]R&   ]! ]	R7      t
R]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR	]R
&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    ]! ]	R7      tR]R&    RtR]R&    RtR]R&   RtR# )ÚDispatchResultuü  Outcome of a single ``dispatch`` pass.

``kanban.default_assignee`` applied this tick before spawning (#27145). Surfaces the auto-assignment to
telemetry / CLI / dashboard so the operator can see when the dispatcher is acting on the fallback rule
``kanban.max_in_progress_per_profile`` (#21582). Each entry is ``(task_id, assignee,
current_running_count)``. NOT an operator-actionable failure â€” the task will be picked up on a
subsequent tick when the assignee has capacity. Separate bucket so telemetry / dashboards can show "this
profile is busy" vs
the board's dispatch lock (issue #35240). A losing dispatcher does no DB writes this tick â€” the lock
holder is making progress on the same board. This is the steady-state signal that a single-writer guard
is
ÚintÚ	reclaimedÚpromoted©Údefault_factoryú	list[str]Úreconciled_orphansÚreaped_terminal_workerszlist[tuple[str, str, str]]ÚspawnedÚskipped_unassignedÚauto_assigned_defaultÚskipped_nonspawnablezlist[tuple[str, str, int]]Úskipped_per_profile_cappedÚcrashedÚauto_blockedÚ	timed_outÚstalezlist[tuple[str, str]]Úrespawn_guardedÚrate_limitedFÚboolÚskipped_lockedNúOptional[str]Úmemory_pressure© )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r   Ú__annotations__r   r   Úlistr   r   r   r   r   r   r   r   r   r    r!   r"   r#   r%   r'   Ú__static_attributes__r(   ó    ÚB/home/ubuntu/.hermes/hermes-agent/hermes_cli/kanban_db_dispatch.pyr   r   X   sP  ‡ ñð €IˆsÓØ€HˆcÓÙ$)¸$Ô$?Ð˜	Ó?ð-á).¸tÔ)DÐ˜YÓDð&á*/ÀÔ*E€GÐ'ÓEØ:Ù$)¸$Ô$?Ð˜	Ó?ð+á',¸TÔ'BÐ˜9ÓBðEñ ',¸DÔ&AÐ˜)ÓAð9ñ >CÐSWÔ=XÐÐ :ÓXðKñ ¨tÔ4€GˆYÓ4ØBÙ#°DÔ9€L�)Ó9ØEÙ °Ô6€IˆyÓ6ØBÙ¨TÔ2€Eˆ9Ó2ØXÙ-2À4Ô-H€OÐ*ÓHðRñ $°DÔ9€L�)Ó9ðNð !€N�DÓ ðKà%)€O�]Ó)óLr1   r   c               ó    € V ^8„  d   QhRRRR/# )é   Úresultsz"Iterable[Optional[DispatchResult]]ÚreturnÚstrr(   )Úformats   "r2   Ú__annotate__r9   š   s   € ÷ ñ Ð"Dð Èñ r1   c                ón  € / pRpV  FÈ  pVf   K	  VP                    F   w  rEVP                  V^ 4      ^,           W&   K"  	  VP                  '       d0   VP                  R^ 4      \        VP                  4      ,           VR&   VP                  '       d   VP                  R^ 4      ^,           VR&   VP
                  '       g   K¼  VP
                  pKÊ  	  \        VP                  4       4       UUu. uF  w  rgV RV 2NK  	  pppV'       d   VP                  RV 24       RP                  V4      # u uppi )uÌ  One line naming why the tick(s) held ready work back, or ``""``.

``active_pr=1, recent_success=2, rate_limited=1, skipped_locked=1,
memory_pressure=critical`` â€” the respawn-guard reasons counted per task
plus the tick-level holds. Feeds the "dispatcher stuck" warnings of the
CLI daemon and the embedded gateway dispatcher, which otherwise report a
bare zero-spawn count while ``hermes kanban tail`` is the only place the
guard reason is written (#111910).
Nr#   r%   Ú=zmemory_pressure=ú, )
r"   Úgetr#   Úlenr%   r'   ÚsortedÚitemsÚappendÚjoin)	r5   ÚcountsÚpressureÚresÚ_task_idÚreasonÚkÚvÚpartss	   &        r2   Údescribe_suppressionrK   š   s  € ð  €FØ"€HÛˆØŠ;ÙØ #× 3Ô 3ÑˆHØ#ŸZ™Z¨°Ó2°QÕ6ˆF‹Nñ !4à××ÐØ%+§Z¡Z°ÀÓ%BÄSÈ×IYÑIYÓEZÕ%ZˆF�>Ñ"Ø××ÐØ'-§z¡zÐ2BÀAÓ'FÈÕ'JˆFÐ#Ñ$Ø××ÒØ×*Ñ*ŠHñ ô %+¨6¯<©<«>Ô$:Ô;Ñ$:™D˜A�ˆs�!�A�3‹ZÑ$:€EÑ;ßØ�‰Ð'¨ zÐ2Ô3Ø�9‰9�UÓÐùó <s   Ã/D1iX  i   zdict[int, tuple[int, float]]Ú_recent_worker_exitszdict[int, subprocess.Popen]Ú_live_worker_procsc               ó    € V ^8„  d   QhRRRR/# )r4   Ú
returncoder   r6   r(   )r8   s   "r2   r9   r9   É   s   € ÷ )ñ )¨Sð )°Sñ )r1   c                ó4   € \        V 4      ^ÿ,          ^,          # )zLEncode a ``Popen.returncode`` in the wait-status layout the registry stores.)r   )rO   s   &r2   Ú_wait_status_from_returncoderQ   É   s   € ä�
‹O˜dÕ" qÕ(Ð(r1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   Úpidr   Ú
raw_statusr6   ÚNoner(   )r8   s   "r2   r9   r9   Î   s!   € ÷ 1ñ 1˜Sð 1¨cð 1°dñ 1r1   c                ór  € V '       d   V ^ 8:  d   R# \         P                   ! 4       p\        V4      V3\        \        V 4      &   \        \        4      \        ^,          8”  d`   V\
        ,
          p\        P                  4        UUUu. uF  w  pw  rVWc8  g   K  VNK  	  uppp F  p\        P                  VR4       K  	  \        \        4      \        8”  dW   \        \        P                  4       R R7      pVR\        V4      ^,            F  w  ry\        P                  VR4       K  	  R# R# u upppi )zLRecord a reaped child's exit status; duplicate pids overwrite (latest wins).Nc                ó"   € V ^,          ^,          # )é   r(   )Úkvs   &r2   Ú<lambda>Ú%_record_worker_exit.<locals>.<lambda>Ú   s   € ÀbÈÅeÈAÆhr1   )Úkey)	Útimer   rL   r>   Ú_RECENT_WORKER_EXITS_MAXÚ_RECENT_WORKER_EXIT_TTL_SECONDSr@   Úpopr?   )
rS   rT   ÚnowÚcutoffÚpÚ_sÚtÚ_pidÚorderedÚ_s
   &&        r2   Ú_record_worker_exitri   Î   sï   € ç�#˜”(ÙÜ
�)Š)‹+€CÜ&)¨*£o°sÐ%;Ôœ˜S›Ñ"Ü
ÔÓ Ô#;¸qÕ#@Ô@ØÔ6Õ6ˆÜ)=×)CÑ)CÔ)EÕTÑ)E™:˜1™g˜rÈÉ—Q�QÑ)EÖTˆDÜ ×$Ñ$ T¨4Ö0ñ Uä
ÔÓ Ô#;Ô;äÔ-×3Ñ3Ó5Ñ;NÔOˆØÐ2¤ W£°Õ!2Ó3‰GˆDÜ ×$Ñ$ T¨4Ö0ó 4ñ <ùô Us   ÂD2ÂD2c               ó    € V ^8„  d   QhRRRR/# )r4   rS   r   r6   ztuple[str, Optional[int]]r(   )r8   s   "r2   r9   r9   ß   s   € ÷ ñ ˜sð Ð'@ñ r1   c                óÔ   € \         P                  \        V 4      4      pVf   R# Vw  r#\        V4      pV^,          pV^ 8X  d   \        V^,	          ^ÿ,          4      # V^8w  d   RV3# R# )a0  ``(kind, code)`` for a reaped worker PID: ``clean_exit`` (rc 0 while
still ``running`` = protocol violation), ``rate_limited``
(``KANBAN_RATE_LIMIT_EXIT_CODE``, never counts as a failure),
``nonzero_exit``, ``signaled`` (``code`` is the signal), ``unknown`` (pid
not in the reap registry; ``code`` None).Úsignaled)ÚunknownN)rL   r=   r   Ú_exit_code_kind)rS   ÚentryÚrawrh   Úsignal_numbers   &    r2   Ú_classify_worker_exitrr   ß   ss   € ô !×$Ñ$¤S¨£XÓ.€EØ‚}Ø Ð Ø�F€Cô
 ˆc‹(€CØ˜$•J€MØ˜ÔÜ  q¥¨DÕ0Ó1Ð1Ø˜ÔØ˜MÐ*Ð*ØÐr1   c               ó    € V ^8„  d   QhRRRR/# )r4   Úcoder   r6   ztuple[str, int]r(   )r8   s   "r2   r9   r9   ö   s   € ÷ "ñ "˜#ð " /ñ "r1   c                ó€   € V ^ 8X  d   R# V \         P                  8X  d   RV 3# V \         P                  8X  d   RV 3# RV 3# )zC``(kind, code)`` for a worker's exit code, however it was observed.r#   Úterminal_providerÚnonzero_exit)Ú
clean_exité    )Ú_kbÚKANBAN_RATE_LIMIT_EXIT_CODEÚ"KANBAN_TERMINAL_PROVIDER_EXIT_CODE)rt   s   &r2   rn   rn   ö   sL   € àˆq„yØ Ð ØŒs×.Ñ.Ô.Ø Ð%Ð%ØŒs×5Ñ5Ô5Ø# TÐ*Ð*Ø˜DÐ!Ð!r1   Ú^z	(\d+)\s*$c               ó$   € V ^8„  d   QhRRRRRR/# )r4   Útask_idr7   Úboardr&   r6   úOptional[int]r(   )r8   s   "r2   r9   r9     s!   € ÷ 1ñ 1 3ð 1¨}ð 1Èñ 1r1   c                óÐ   €  \         P                  ! V RVR7      p\        P	                  T;'       g    R4      pT'       d   \        TR,          4      # R#   \         d     R# i ; i)ug  Exit code from the trailer the worker CLI wrote to its own log; None when absent.

The durable twin of ``_recent_worker_exits``: written by the worker itself
(``hermes_cli.quiet_single_query.exit_single_query``), so it is there whether
or not the process running this sweep ever reaped the worker. Last trailer
wins â€” the log is append-mode across re-runs.
é   ©Ú
tail_bytesr€   NÚ éÿÿÿÿ)rz   Úread_worker_logÚ	ExceptionÚ_EXIT_TRAILER_REÚfindallr   )r   r€   rp   Úmatchess   &&  r2   Ú_worker_log_exit_coder�     s]   € ðÜ×!Ò! '°dÀ%ÔHˆô ×&Ñ& s§y y¨bÓ1€Gß&Œ3ˆw�r�{ÓÐ0¨DÐ0øô ô Úðús   ‚A ÁA%Á$A%c               ó   € V ^8„  d   QhRR/# )r4   r6   z	list[int]r(   )r8   s   "r2   r9   r9     s   € ÷ ñ ˜Yñ r1   c                 ó  € . p \         P                  '       d{   \        \        P	                  4       4       FW  w  rVP                  4       pVf   K  \        V\        V4      4       \        P                  VR4       V P                  V4       KY  	  V #    \        P                  ! R\        P                  4      w  rT^ 8X  d    T # \        Y4       T P                  T4       KQ    \         d     T # i ; i  \         d     T # i ; i)a  Reap exited workers without blocking; returns reaped PIDs. POSIX reaps
every child via ``waitpid(-1)``; Windows polls the ``Popen`` handles
parked by ``_default_spawn`` (the only way to learn a child's exit code
there), so the rate-limit sentinel exit is classified on both hosts.Nr‡   )rz   Ú_IS_WINDOWSr/   rM   r@   Úpollri   rQ   r`   rA   ÚosÚwaitpidÚWNOHANGÚChildProcessErrorr‰   )ÚreapedrS   ÚprocrO   Ústatuss        r2   Úreap_worker_zombiesr™     só   € ð
 €FÜ
‡‡€ÜÔ0×6Ñ6Ó8Ö9‰IˆCØŸ™›ˆJØÒ!ÙÜ Ô%AÀ*Ó%MÔNÜ×"Ñ" 3¨Ô-Ø�M‰M˜#Öñ :ð ˆðØðÜ Ÿjšj¨¬R¯Z©ZÓ8‘�ð �aŒxØð
 €Mô	   Ô,Ø�M‰M˜#Öøô %ô Øð €Mðûô ô ØØ€Mðús<   ÂC7 Â'C% Â=C7 ÃC7 Ã%C4Ã0C7 Ã3C4Ã4C7 Ã7DÄDc               ó    € V ^8„  d   QhRRRR/# ©r4   rS   r�   r6   r$   r(   )r8   s   "r2   r9   r9   4  s   € ÷ /ñ /�Mð / dñ /r1   c                ó„  € V '       d   V ^ 8:  d   R# ^ RI Hp V! \        V 4      4      '       g   R# \        P                  R8X  d{    \        R\        V 4       R2RRR7      ;_uu_ 4       pV FE  pVP                  R	4      '       g   K  R
VP                  R^4      ^,          9   d    RRR4       R#  M	  RRR4       R# \        P                  R8X  d”    \        P                  ! RRRR\        \        V 4      4      .\        P                  \        P                  RRR^RR7      pVP                   ^ 8w  d   R# R
VP"                  ;'       g    RP%                  4       9   d   R#  R# R#   + '       g   i     R# ; i  \        \        \        3 d     R# i ; i  \        \        P&                  \(        3 d     R# i ; i)uH  Return True if ``pid`` is still running on this host.

Uses ``gateway.status._pid_exists`` (OpenProcess on Windows, ``os.kill(pid, 0)``
on POSIX). **DO NOT** call ``os.kill(pid, 0)`` directly on Windows â€” there
``sig=0`` is ``CTRL_C_EVENT`` broadcast to the console group, potentially
killing unrelated processes.

Zombies (exited, not yet reaped) still pass the existence check, so a
worker would look "alive" forever between exit and reap. Linux: peek at
``/proc/<pid>/status`` and treat ``State: Z`` as dead; macOS: ask ``ps``
for the BSD ``stat`` field and treat ``Z`` as dead.
F)Ú_pid_existsÚlinuxz/proc/z/statusÚrzutf-8)ÚencodingzState:ÚZÚ:NÚdarwinÚpsz-ozstat=z-pTÚreplace)ÚstdoutÚstderrÚtextr    ÚerrorsÚtimeoutÚcheckr†   )Úgateway.statusr�   r   ÚsysÚplatformÚopenÚ
startswithÚsplitÚFileNotFoundErrorÚPermissionErrorÚOSErrorÚ
subprocessÚrunr7   ÚPIPEÚDEVNULLrO   r¦   ÚstripÚSubprocessErrorÚTimeoutError)rS   r�   ÚfÚliner—   s   &    r2   Ú
_pid_aliver¾   4  s�  € ÷ �#˜”(ÙÝ*Ù”s˜3“x× Ò ÙÜ
‡|�|�wÔð
	Ü˜œs 3›x˜j¨Ð0°#À×HÕHÈAÛ�DØ—‘ x×0Ô0à $§*¡*¨S°!Ó"4°QÕ"7Ô7Ø#(÷ IÑHñ ñ ÷ Iñ6 ô# 
�‰˜Ô	!ð	Ü—>’>Ø�t˜W d¬C´°C³«MÐ:Ü!—‘Ü!×)Ñ)Ø G°IØØôˆDð �‰ !Ô#ÙØ�t—{‘{×(Ð( b×/Ñ/Ó1Ô1Ùð 2ó
 ÷7 IÖHñ6 ûô) "¤?´GÐ<ô 	àñ$ ð)	ûô" œ×3Ñ3´\ÐBô 	àÙð	úsa   Á#E> Á*E*Â	E*Â(E> Â2E*Â6E> ÃA"F Ä:F ÅF Å*E;	Å5E> Å;E> Å>FÆFÆ F?Æ>F?Ú
unverifiedc               ó    € V ^8„  d   QhRRRR/# )r4   rS   r   r6   r&   r(   )r8   s   "r2   r9   r9   l  s   € ÷ 6ñ 6˜cð 6 mñ 6r1   c                ó`   € ^ RI Hp ^ RIHp V! \	        V 4      4      pVf   R# V! 4        RV 2# )aâ  Restart-stable identity of a live process: ``"<instantiation epoch>|<start time>"``. The start
time alone (``/proc/<pid>/stat`` field 22 on Linux) is clock ticks since THIS boot, so a row that
survives a reboot could match an unrelated process with the same PID and the same tick value;
``gateway.drain_control.current_instantiation_epoch`` (``boot_id`` + PID-1 start) changes on every
reboot / container recreate, so the composed value never survives one. ``None`` when unreadable.)Úcurrent_instantiation_epoch)Úget_process_start_timeNÚ|)Úgateway.drain_controlrÂ   r¬   rÃ   r   )rS   rÂ   rÃ   Ústarts   &   r2   Ú_process_fingerprintrÇ   l  s6   € õ BÝ5Ù"¤3 s£8Ó,€EØ‚}ÙÙ)Ó+Ð,¨A¨e¨WÐ5Ð5r1   c               ó    € V ^8„  d   QhRRRR/# r›   r(   )r8   s   "r2   r9   r9   z  s   € ÷ .ñ .�}ð .°Tñ .r1   c                óx   € \         P                  ! V 4      '       g   R# V\        8X  d   R# \        W4      '       * # )aa  True when ``pid`` is live AND is still the worker we spawned. ``started_at`` is the fingerprint
recorded by ``_set_worker_pid``; after a reboot (or any PID recycle) an unrelated process can own
the number, so bare existence is never enough to extend a claim or to signal. A legacy row without
a fingerprint keeps the existence answer: killing it is the pre-fingerprint behaviour and the row is
rewritten with a fingerprint on its next spawn. An UNVERIFIED spawn also keeps the existence answer
(a claim is never released beside a possibly-live worker) but ``_terminate_reclaimed_worker``
refuses to signal it.FT)rz   r¾   ÚUNVERIFIED_WORKER_FINGERPRINTÚ_pid_recycled)rS   Ú
started_ats   &&r2   Ú_worker_aliverÍ   z  s0   € ô �>Š>˜#×ÒÙØÔ2Ô2ÙÜ˜SÓ-Ô-Ð-r1   c               ó    € V ^8„  d   QhRRRR/# r›   r(   )r8   s   "r2   r9   r9   ‰  s   € ÷ ñ �}ð °Tñ r1   c                ó*  € Ve	   V '       g   R# V\         8X  d   R# \        V\        4      '       d   RV9   d   \        \	        V 4      4      V8g  # ^ RIHpHp V! \	        V 4      4      pVf   R#  V! WA4      '       * #   \        \        3 d     R# i ; i)aT  True when a live ``pid`` is NOT the process fingerprinted at spawn (or the fingerprint can no
longer be read). Signalling it would hit a stranger. ``None`` fingerprint = legacy row, never
recycled; the UNVERIFIED marker is always foreign. An integer fingerprint (rows written before the
boot witness was added) compares the start time only.FTrÄ   )Ú_start_times_agreerÃ   )
rÊ   Ú
isinstancer7   rÇ   r   r¬   rÐ   rÃ   Ú	TypeErrorÚ
ValueError)rS   rÌ   rÐ   rÃ   Úcurrents   &&   r2   rË   rË   ‰  s†   € ð
 Ò§ÙØÔ2Ô2ÙÜ�*œc×"Ò" s¨jÔ'8Ü#¤C¨£HÓ-°Ñ;Ð;ßIÙ$¤S¨£XÓ.€GØ‚ÙðÙ% gÓ:Ô:Ð:øÜ”zÐ"ô Úðús   Á0A= Á=BÂBc               ó   € V ^8„  d   QhRR/# )r4   r6   z$Optional[Callable[[int, int], None]]r(   )r8   s   "r2   r9   r9   ž  s   € ÷ 4ñ 4Ð?ñ 4r1   c                ó^   € V e   V # \        \        R4      '       d   \        P                  # R# )zD``signal_fn`` test hook, else ``os.kill`` when the platform has one.NÚkill)Úhasattrr’   r×   )Ú	signal_fns   &r2   Ú_kill_fnrÚ   ž  s)   € àÒØÐÜœb &×)Ò)Œ2�7‰7Ð3¨tÐ3r1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   rS   r   rÌ   r�   r6   r$   r(   )r8   s   "r2   r9   r9   ¥  s!   € ÷ ñ ˜3ð ¨Mð ÀTñ r1   c                ó~   € \        ^
4       F-  p\        W4      '       g    R# \        P                  ! R4       K/  	  R# )z@Poll ~5 s (10 x 0.5 s) for ``pid`` to die; True once it is gone.Tg      à?F)ÚrangerÍ   r]   Úsleep)rS   rÌ   rh   s   && r2   Ú_poll_worker_exitrß   ¥  s.   € ä�2ŽYˆÜ˜S×-Ò-ÚÜ�
Š
�3Žñ ñ r1   c               ó    € V ^8„  d   QhRRRR/# )r4   rS   r   r6   r$   r(   )r8   s   "r2   r9   r9   ®  s   € ÷ ñ ˜ð  ñ r1   c                ó–   €  V ! \        V4      \        \        R\        P                  4      4       R#   \        \
        3 d     R# i ; i)z8Best-effort SIGKILL; True when the signal was delivered.ÚSIGKILLTF)r   ÚgetattrÚsignalÚSIGTERMÚProcessLookupErrorr´   )r×   rS   s   &&r2   Ú_sigkillrç   ®  s<   € ðáŒS�‹X”wœv y´&·.±.ÓAÔBÙøÜ¤Ð(ô Úðús   ‚/3 ³AÁArÙ   rÌ   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   rS   r�   Ú
claim_lockr&   r6   zdict[str, Any]r(   )r8   s   "r2   r9   r9   ¸  s&   € ÷ :ñ :Ø	ð:àð:ð ñ:r1   c          
     ó  € RV '       d   \        V 4      MRRRRRRRRR/pV '       d   V ^ 8:  g	   V'       g   V# \        V4      P                  \        P                  ! 4       4      '       g   V# RVR&   \        V4      pVf   V# V\        8X  d&   RVR	&   \        P                  ! V 4      '       * VR&   V# \        P                  ! V 4      '       d   \        W4      '       d   RVR&   RVR
&   V# RVR&    V! \        V 4      \        P                  4       \        Y4      '       d   RTR&   T# \        Y4      '       d   \        YP4      '       g   T# RTR&   \        Y4      '       * TR&   T#   \         d    RTR&   Tu # \         d    Tu # i ; i)uô  Best-effort host-local worker termination for reclaim paths. ``started_at`` is the spawn-time
fingerprint: when the live process no longer matches it, the PID was recycled and nothing is
signalled â€” the worker is gone, which is what the reclaim wanted (``terminated`` = True). An
UNVERIFIED spawn (fingerprint capture failed) that is still live is never signalled either, but
it is reported as surviving (``signal_refused``) so the reclaim holds the claim instead of
spawning a duplicate beside it.Úprev_pidNÚ
host_localFÚtermination_attemptedÚ
terminatedÚsigkillTÚsignal_refusedÚpid_recycled)r   r7   r°   rz   Ú_host_prefixrÚ   rÊ   r¾   rË   rä   rå   ræ   r´   rß   rÍ   rç   )rS   ré   rÙ   rÌ   Úinfor×   s   &&$$  r2   Ú_terminate_reclaimed_workerrô   ¸  sˆ  € ð 	§”C˜”H¨Ø�eØ Ø�eØ�5ð€D÷ �#˜”(§*ØˆÜˆz‹?×%Ñ%¤c×&6Ò&6Ó&8×9Ò9ØˆØ€DˆÑä�IÓ€DØ‚|ØˆØÔ2Ô2à!%ˆÐÑÜ!$§¢°Ó!4Ô4ˆˆ\ÑØˆÜ
‡~‚~�c×Òœ}¨S×=Ò=Ø!ˆˆ\ÑØ#ˆˆ^ÑØˆà$(€DÐ	 Ñ!ðÙŒS�‹X”v—~‘~Ô&ô ˜×)Ò)Ø!ˆˆ\ÑØˆÜ�S×%Ò%Ü˜×"Ò"ØˆKØˆˆY‰Ü*¨3Ó;Ô;€DˆÑØ€Køô! ô ð "ˆˆ\ÑØŠÜô ØŠðús   Ã3 E) Å)FÅ=FÆFÆFc               ó    € V ^8„  d   QhRRRR/# ©r4   Úconnúsqlite3.Connectionr6   r   r(   )r8   s   "r2   r9   r9   õ  s   € ÷ ñ Ð 2ð È)ñ r1   c          	     óx  € V P                  R\        \        P                  ! 4       4      \        ,
          34      P	                  4       p\
        P                  ! 4       p. pV F  p \        WW1V4       K  	  V#   \         d6    \
        P                  P                  RTR,          TR,          RR7        KV  i ; i)uÎ  End host-local workers that outlived their run (issue #111791) â€” a worker
that called ``kanban_complete`` and then hung keeps its ``state.db`` sidecar
fds open and no ``running``-only sweep can see it once ``tasks.worker_pid`` is
cleared. Keys on the closed ``task_runs`` row's retained pid + spawn
fingerprint: a legacy row (NULL fingerprint) or a recycled PID is never
signalled; a pid that is simply gone just has its evidence cleared. A run
that ended less than ``TERMINAL_WORKER_REAP_GRACE_SECONDS`` ago is left
alone so a worker still finalising after its own transition is not killed.
One row's failure (signal, /proc probe) is logged and skips only that row.
Returns the task ids whose worker was terminated.z¶SELECT id, task_id, worker_pid, worker_started_at, claim_lock FROM task_runs WHERE ended_at IS NOT NULL AND ended_at <= ? AND worker_pid IS NOT NULL AND worker_started_at IS NOT NULLzAkanban dispatch: terminal worker reap failed for run %s (task %s)Úidr   T©Úexc_info)Úexecuter   r]   Ú"TERMINAL_WORKER_REAP_GRACE_SECONDSÚfetchallrz   rò   Ú_reap_terminal_worker_rowr‰   Ú_logÚdebug)r÷   rÙ   ÚrowsÚhost_prefixr–   Úrows   &$    r2   Úreap_terminal_workersr  õ  s¬   € ð �<‰<ð	Gô 
ŒT�YŠY‹[Ó	Ô>Õ	>Ð@ó	÷
 �hƒjð 	ô ×"Ò"Ó$€KØ€FÛˆð	Ü% d°ÈÖPñ ð €Møô ô 	Ü�H‰H�N‰NØSØ�D•	˜3˜y�>°Dð ÷ ð	ús   Á&A9Á9<B9Â8B9c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r  r7   r–   r   r6   rU   r(   )r8   s   "r2   r9   r9     s"   € ÷ &ñ &°cð &Èið &Ð\`ñ &r1   c                óô  € \        VR ,          4      VR,          reV\        P                  ! 4       8X  g2   \        VR,          ;'       g    R4      P	                  V4      '       g   R# V\
        8X  d   \        P                  ! V4      '       d   R# \        WV4      pRpV'       d&   \        WQR,          W6R7      pVR,          '       g   R# \        P                  ! V 4      ;_uu_ 4        V P                  RVR,          WV34       V'       d/   \        P                  ! WR	,          R
RVRV/VCVR,          R7       RRR4       V'       d   VP                  VR	,          4       R# R#   + '       g   i     L4; i)Ú
worker_pidÚworker_started_atré   r†   N©rÙ   rÌ   rî   zzUPDATE task_runs SET worker_pid = NULL, worker_started_at = NULL WHERE id = ? AND worker_pid = ? AND worker_started_at = ?rú   r   Úterminal_worker_reapedrS   ©Úrun_id)r   r’   Úgetpidr7   r°   rÊ   rz   r¾   rÍ   rô   Ú	write_txnrý   Ú_append_eventrA   )	r÷   r  r  rÙ   r–   rS   ÚfingerprintÚaliveÚterminations	   &&&&&    r2   r   r     s(  € Ü˜3˜|Õ,Ó-¨sÐ3FÕ/GˆØ
Œb�iŠi‹kÔ¤ S¨Õ%6×%<Ð%<¸"Ó!=×!HÑ!HÈ×!UÒ!UÙØÔ3Ô3¼¿ºÀs×8KÒ8KÙÜ˜#Ó+€EØ€KßÜ1Ø�\Õ"¨iôQˆà˜<×(Ô(ÙÜ	�Š�t×	Õ	Ø�‰ðHà��Y˜Ð)ô	
÷
 Ü×ÒØ˜)•nÐ&>Ø˜Ð0°+ÐMÀÐMÐVYÐZ^ÕV_õ÷ 
÷ Ø�‰�c˜)•nÖ%ñ ÷ 
×	ús   Ã)AE'Å'E7	c               ó    € V ^8„  d   QhRRRR/# )r4   r  Údictr6   r$   r(   )r8   s   "r2   r9   r9   /  s   € ÷ ñ ¨dð °tñ r1   c                óÖ   € \        V P                  R4      ;'       dI    V P                  R4      ;'       g    V P                  R4      ;'       d    V P                  R4      '       * 4      # )u„  True when we tried to kill our own host-local worker and it is still alive.

Reclaiming then would release the claim and spawn a second worker while the
first still runs â€” the duplication loop. Only host-local workers we actually
signalled count; a non-local lock or no-op attempt (no ``os.kill``) must fall
through to the normal release path since we cannot manage that worker anyway.
rì   rí   rð   rî   )r$   r=   )r  s   &r2   Ú_worker_survived_terminationr  /  s_   € ô Ø�‰˜Ó%÷ 	.ð 	.Ø�_‰_Ð4Ó5×ZÐZ¸¿¹ÐIYÓ9Z÷	.ð 	.à—‘ Ó-Ô-óð r1   c               ó4   € V ^8„  d   QhRRRRRRRRR	R
RRRR/# )r4   r÷   rø   r   r7   ré   r&   ra   r   r  r  rG   r6   rU   r(   )r8   s   "r2   r9   r9   >  sW   € ÷ Uñ UØ
ðUàðUð ðUð 
ð	Uð
 ðUð ðUð 
ñUr1   c          
     óÈ  € V\         P                  ,           p\         P                  ! V 4      ;_uu_ 4        V P                  RWaV34      pVP                  ^8w  d    RRR4       R# \         P
                  ! W4      pVe   V P                  RWh34       RVRVRV/p	V	P                  V4       \         P                  ! WRW˜R7       RRR4       R#   + '       g   i     R# ; i)	aC  Hold a claim whose worker survived termination instead of releasing it.

Extends ``claim_expires`` by ``RECLAIM_DEFER_GRACE_SECONDS`` so the task
stays ``running`` (no duplicate spawn) and records ``reclaim_deferred``.
The next tick retries the kill; not spawning a duplicate is what lets the
throttled worker finally die.
zZUPDATE tasks SET claim_expires = ? WHERE id = ? AND status = 'running' AND claim_lock IS ?Nz3UPDATE task_runs SET claim_expires = ? WHERE id = ?rG   ré   Úclaim_expires_nowÚreclaim_deferredr  )rz   ÚRECLAIM_DEFER_GRACE_SECONDSr  rý   ÚrowcountÚ_current_run_idÚupdater  )
r÷   r   ré   ra   r  rG   ÚgraceÚcurr  Úpayloads
   &&&&&$    r2   Ú_defer_reclaim_for_live_workerr$  >  sÅ   € ð  ”#×1Ñ1Õ1€EÜ	�Š�t×	Õ	Ø�l‰lðFà˜ZÐ(ó
ˆð
 �<‰<˜1ÔØ÷ 
Ñ	ô ×$Ò$ TÓ3ˆØÒØ�L‰LÐNÐQVÐP_Ô`Ø˜V \°:Ð?RÐTYÐZˆØ�‰�{Ô#Ü×Ò˜$Ð);¸WÕT÷ 
×	×	Ò	ús   ¶'CÁ'ACÃC!	ÚnoteÚexpected_run_idc          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r4   r÷   rø   r   r7   r%  r&   r&  r�   r6   r$   r(   )r8   s   "r2   r9   r9   _  s:   € ÷ #ñ #Ø
ð#àð#ð ð	#ð
 #ð#ð 
ñ#r1   c          
     ó   € \        \        P                  ! 4       4      p\        P                  ! V 4      ;_uu_ 4        RpWA3pVe   VR,          pV\        V4      3,          pV P	                  WV4      pVP
                  ^8w  d    RRR4       R# Ve   \        V4      M\        P                  ! W4      pVe   V P	                  RWH34       \        P                  ! YRV'       d   RV/MRVR7       RRR4       R	#   + '       g   i     R	# ; i)
a  Record a ``heartbeat`` event + touch ``last_heartbeat_at``.

Liveness signal orthogonal to the PID check: a worker whose forked child
(train loop, crawl) is stuck can still have a live Python process.
Returns False if the task is not running or its claim expired.
zJUPDATE tasks SET last_heartbeat_at = ? WHERE id = ? AND status = 'running'Nz AND current_run_id = ?Fz7UPDATE task_runs SET last_heartbeat_at = ? WHERE id = ?Ú	heartbeatr%  r  T)r   r]   rz   r  rý   r  r  r  )	r÷   r   r%  r&  ra   ÚsqlÚparamsr"  r  s	   &&$$     r2   Úheartbeat_workerr,  _  sð   € ô Œd�iŠi‹kÓ
€CÜ	�Š�t×	Õ	ØZˆØ˜ˆØÒ&ØÐ,Õ,ˆCØ”s˜?Ó+Ð-Õ-ˆFØ�l‰l˜3Ó'ˆØ�<‰<˜1ÔØ÷ 
Ñ	ð Ò*ô �Ô ä×$Ò$ TÓ3ð 	ð
 ÒØ�L‰LÐRÐUXÐTaÔbÜ×ÒØ˜;ß"ˆV�T‰N¨Øõ	
÷! 
ñ* ÷+ 
Ö	ñ* ús   ½A	C<ÂA"C<Ã<D	c               ó    € V ^8„  d   QhRRRR/# rö   r(   )r8   s   "r2   r9   r9   …  s   € ÷ cñ cÐ0ð cÈ	ñ cr1   c               óÆ  € . p\        \        P                  ! 4       4      p\        P                  ! 4       pV P	                  R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	P                  V4      '       g   KG  T\        P                  ! VR,          4      ;'       g    T,
          p
\        VR,          4      pW«8  d   K�  \        VR,          4      pVR,          p\        P                  ! VR	4      pV\        8X  d@   \        P                  ! V4      '       d$   \        P                  P                  R
WÜ4       EK  Rp\!        V4      pVe�   \        P                  ! V4      '       d   \#        WÎ4      '       gp   \$        P&                  ! \(        \*        4      ;_uu_ 4        V! V\,        P.                  4       RRR4       \1        WÎ4       \3        WÎ4      '       d   \5        VV4      pR\        V
4       RV R2p\        P6                  ! V 4      ;_uu_ 4        V! WRR7      '       d    RRR4       EK  \        P8                  ! W4      pV P	                  RVWÜVR,          34      pVP:                  ^8X  d\   RVR\        V
4      RVRVRV/p\        P<                  ! WRRVVR7      p\        P>                  ! WRVVR7       VPA                  V4       RRR4       XP:                  ^8X  g   EK¿  \C        WVRRRRVRVRX/R7       EKÙ  	  V#   + '       g   i     EL^; i  + '       g   i     LV; i)uy  Terminate workers whose per-task ``max_runtime_seconds`` has elapsed.

SIGTERM, short grace, then SIGKILL. Emits ``timed_out`` and restores the
task's source phase so the next tick re-spawns the same kind of worker â€”
unless the circuit breaker already gave up, leaving it blocked. Host-local
only (same reasoning as ``detect_crashed_workers``). ``signal_fn`` is a test hook.
aq  SELECT t.id, t.worker_pid, t.worker_started_at,        COALESCE(r.started_at, t.started_at) AS active_started_at,        t.max_runtime_seconds, t.claim_lock FROM tasks t LEFT JOIN task_runs r ON r.id = t.current_run_id WHERE t.status = 'running' AND t.max_runtime_seconds IS NOT NULL   AND COALESCE(r.started_at, t.started_at) IS NOT NULL   AND t.worker_pid IS NOT NULL©Úowner_reclaim_blockedÚ owner_reclaim_blocked_after_killrú   ré   r†   Úactive_started_atÚmax_runtime_secondsr	  r
  z^kanban: task %s worker pid %s exceeded max runtime but has no verified identity; not signalledFNzelapsed z
s > limit ÚsT©Ú	signalledzØUPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, last_heartbeat_at = NULL WHERE id = ? AND status = 'running'   AND worker_pid = ? AND claim_lock IS ?rS   Úelapsed_secondsÚlimit_secondsrï   Úretry_statusr    ©Úoutcomer˜   ÚerrorÚmetadatar  )r<  r;  Úrelease_claimÚend_runÚevent_payload_extra)"r   r]   rz   rò   rý   rÿ   Ú hermes_cli.kanban_owner_recoveryr0  r1  r°   Ú	_to_epochÚ_row_getrÊ   r¾   r  ÚwarningrÚ   rË   Ú
contextlibÚsuppressræ   r´   rä   rå   rß   rÍ   rç   r  Ú_retry_status_for_runr  Ú_end_runr  rA   Ú_record_task_failure)r÷   rÙ   r    ra   r  r  r0  r1  r  ÚlockÚelapsedÚlimitrS   ÚtidrÌ   Úkilledr×   r<  r9  r"  r#  r  s   &$                    r2   Úenforce_max_runtimerO  …  sé  € ð €IÜ
Œd�iŠi‹kÓ
€CÜ×"Ò"Ó$€Kà�<‰<ð	)ó	÷ �hƒjð 	÷ iÜˆÙ  ¨4¥y×1Ò1ÙØ�<Õ ×&Ð& BˆØ�‰˜{×+Ò+Ùð œŸš sÐ+>Õ'?Ó@×GÐGÀCÕHˆÜ�CÐ-Õ.Ó/ˆØŒ?Ùä�#�lÕ#Ó$ˆØ�$�iˆÜ—\’\ #Ð':Ó;ˆ
ØÔ6Ô6¼3¿>º>È#×;NÒ;Nô �H‰H×Ñð 7Ø8;ôBâð ˆÜ˜	Ó"ˆØÒ¤S§^¢^°C×%8Ò%8¼]È3×=[Ò=[Ü×$Ò$Ô%7¼×AÕAÙ�Sœ&Ÿ.™.Ô)÷ Bô ˜cÔ.Ü˜S×-Ò-Ü! $¨Ó,�àœ3˜w›<˜.¨
°5°'¸Ð;ˆÜ�]Š]˜4× Õ ñ 0°ÀT×JÓJØ÷	 !Ò ô
 ×4Ò4°TÓ?ˆLØ—,‘,ð;ð
 ˜s¨¨\Õ):Ð;óˆCð �|‰|˜qÔ à˜3Ø%¤s¨7£|Ø# UØ˜vØ" Lð�ô ŸšØ {¸;Ø¨'ô�ô ×!Ò! $¨[¸'È&ÕQØ× Ñ  Ô%÷7 !ð> �<‰<˜1×Ü ØØØ#Ø#ØØ%*¨C°¸FÀNÐT`Ð$a÷ð ñI ðX Ð÷a B×AÐAú÷ !× ús   ÇL<ÉMÉ$BMÌ<MÍM Ústale_timeout_secondsc               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   rP  r   r6   r   r(   )r8   s   "r2   r9   r9   ð  s-   € ÷ dñ dØ
ðdð ðdð
 ñdr1   c               óð  € V^ 8:  d   . # \        \        P                  ! 4       4      p. pV P                  R4      P                  4       p^ RIHpHp V EF‰  pV! WR,          4      '       d   K  VR,          f   K(  T\        P                  ! VR,          4      ;'       g    T,
          p	W‘8  d   K^  \        P                  ! VR,          4      p
V
e	   W:,
          MRpVe   V\        8  d   Kš  VR,          pVR,          pVR,          ;'       g    R	p\        P                  ! WÎV\        P                  ! VR
4      R7      p\        V4      '       d   \        WWãVRR7       EK  \        P                  ! V 4      ;_uu_ 4        V! W\        VP!                  R4      4      R7      '       d    RRR4       EKc  \        P"                  ! W4      pV P                  RVWØR,          34      pVP$                  ^8w  d    RRR4       EK±  R\        V	4      R\        P&                  ! V
4      R\        P&                  ! V4      RTRV'       d   \        V4      MRRV/pVP)                  V4       \        P*                  ! YRRVe   R\        V4       R2MRR\        V	4       R2,           VR7      p\        P,                  ! WRVVR7       VP/                  V4       RRR4       EKŒ  	  V#   + '       g   i     EK¢  ; i)aI  Reclaim ``running`` tasks with no heartbeat progress; returns their ids.

Stale = running longer than ``stale_timeout_seconds`` (active run's
``started_at``, else ``tasks.started_at``) AND ``last_heartbeat_at`` NULL or
older than ``_STALE_HEARTBEAT_GAP_SECONDS``. Task returns to its source
phase, run closes ``outcome='stale'``, a live host-local worker is killed.
``0`` disables the check; ``signal_fn`` is a test hook. Deliberately NOT
counted via ``_record_task_failure``: an absent heartbeat is not a worker
failure, and counting it would let long-running tasks trip the breaker.
zìSELECT t.id, t.worker_pid, t.worker_started_at, t.last_heartbeat_at, t.claim_lock,        COALESCE(r.started_at, t.started_at) AS active_started_at FROM tasks t LEFT JOIN task_runs r ON r.id = t.current_run_id WHERE t.status = 'running'r/  rú   r2  NÚlast_heartbeat_atr	  ré   r†   r
  r  Úheartbeat_stale_worker_alive)rG   rí   r5  zÅUPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, last_heartbeat_at = NULL WHERE id = ? AND status = 'running'   AND claim_lock IS ?r7  Úheartbeat_age_secondsÚtimeout_secondsrS   r9  r!   zno heartbeat for zs zno heartbeat everz after z	s runningr:  r  )r   r]   rý   rÿ   rA  r0  r1  rz   rB  Ú_STALE_HEARTBEAT_GAP_SECONDSrô   rC  r  r$  r  r$   r=   rG  r  Ú_opt_intr   rH  r  rA   )r÷   rP  rÙ   ra   r   r  r0  r1  r  rK  Úlast_hbÚhb_agerS   rM  rJ  r  r9  r"  r#  r  s   &$$                 r2   Údetect_stale_runningr[  ð  s§  € ð   Ô!Øˆ	ä
Œd�iŠi‹kÓ
€CØ€Ià�<‰<ð	%ó÷ �hƒjð 	÷ iÜˆÙ  ¨4¥y×1Ò1ÙØÐ"Õ#Ò+ÙØœŸš sÐ+>Õ'?Ó@×GÐGÀCÕHˆØÔ*Ùä—-’- Ð$7Õ 8Ó9ˆØ$+Ò$7�#–-¸TˆØÒ &Ô+GÔ"GÙà�,ÕˆØ�$�iˆØ�<Õ ×&Ð& Bˆä×5Ò5Ø ´s·|²|ÀCÐI\Ó7]ô_ˆô
 (¨×4Ò4Ü*Ø˜4 kØ5õò ä�]Š]˜4× Õ á/°ÄTÈ+Ï/É/ÐZqÓJrÓEs×tÓtØ÷ !Ò ô ×4Ò4°TÓ?ˆLØ—,‘,ð(ð
 ˜s¨Õ$5Ð6óˆCð �|‰|˜qÔ Ø÷ !Ò ð" "¤3 w£<Ø#¤S§\¢\°'Ó%:Ø'¬¯ª°fÓ)=Ø!Ð#8Ø§3”s˜3”x¨DØ ðˆGð �N‰N˜;Ô'ä—\’\ØØ¨ð Ò)ð (¬¨F« }°BÑ7à,Øœc '›l˜^¨9Ð5õ	6ð
 !ô	ˆFô ×Ò˜d¨°'À&ÕIØ×Ñ˜SÔ!÷K !Ò ñ= ðJ Ð÷O !× Ñ ús   Æ*K#Æ6AK#ÈCK#Ë#K5c               ó    € V ^8„  d   QhRRRR/# rö   r(   )r8   s   "r2   r9   r9   W  s   € ÷ Dñ DÐ%7ð D¸Iñ Dr1   c                óJ  € \        \        P                  ! 4       4      p. pV P                  R4      P                  4       p^ RIHp V EFÃ  pV! WR,          4      '       d   K  VR,          pVR,          pV'       dJ   \        V\        P                  ! VR4      4      '       d#   \        P                  P                  RWg4       K~  \        P                  ! V 4      ;_uu_ 4        V! W4      '       d    RRR4       K¶  V P                  RWeR	,          VR
,          34      pVP                  ^8w  d    RRR4       Kô  RRR	VR	,          R
\        P                  ! VR
,          4      RV'       d   \        V4      MRRV/p	\        P                  ! WRRRV	R7      p
\        P                  ! WRRV4       \        P                   ! WRWšR7       VP#                  V4       RRR4       \        P                  P%                  RWeR	,          V4       EKÆ  	  V#   + '       g   i     L?; i)a  Requeue ``running`` cards with broken claim bookkeeping; returns their ids.

A task ``running`` with NULL ``claim_lock``/``claim_expires`` (crash
mid-claim, manual SQL, DB restore) is a zombie forever: ``release_stale_claims``
needs ``claim_expires``, ``detect_crashed_workers`` needs a host-local lock +
pid, ``detect_stale_running`` is off by default. Orphans go back to ``ready``
with a comment, leaked run closed, ``reconciled`` event; a row with a live
host-local PID is deferred so no duplicate spawns beside it.
z›SELECT id, claim_lock, claim_expires, worker_pid, worker_started_at FROM tasks WHERE status = 'running'   AND (claim_lock IS NULL OR claim_expires IS NULL))r0  rú   r	  r
  ue   kanban reconcile: task %s has broken claim bookkeeping but pid %s is alive on this host â€” deferringNzâUPDATE tasks SET status = 'ready', claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, last_heartbeat_at = NULL WHERE id = ? AND status = 'running'   AND claim_lock IS ? AND claim_expires IS ?ré   Úclaim_expiresrG   Úorphaned_runningra   r   z0orphaned running card (broken claim bookkeeping)r:  Ú
dispatcheru_   reconciliation: card was 'running' with no valid claim (dead/gone worker) â€” requeued to readyÚ
reconciledr  zRkanban reconcile: requeued orphaned running task %s (claim_lock=%r, worker_pid=%r))r   r]   rý   rÿ   rA  r0  rÍ   rz   rC  r  r  r  r  rB  rH  Ú_insert_commentr  rA   ró   )r÷   ra   ra  r  r0  r  rM  rS   r"  r#  r  s   &          r2   Úreconcile_orphaned_runningrc  W  sã  € ô Œd�iŠi‹kÓ
€CØ€JØ�<‰<ð	>ó÷ �hƒjð	 	õ
 GÜˆÙ  ¨4¥y×1Ò1ÙØ�$�iˆØ�,Õˆß”= ¤c§l¢l°3Ð8KÓ&L×MÒMä�H‰H�N‰Nð=Ø>Aôñ Ü�]Š]˜4× Õ Ù$ T×/Ò/Ø÷ !Ñ ð —,‘,ð?ð
 ˜,Õ'¨¨_Õ)=Ð>óˆCð �|‰|˜qÔ Ø÷ !Ñ ð Ð,Ø˜c ,Õ/Ø¤§¢¨s°?Õ/CÓ!DØ¯#œc #œh°4Ø�sðˆGô —\’\ØØ#¨KØHØ ô	ˆFô ×ÒØ˜<ð;àô	ô ×Ò˜d¨°wÕNØ×Ñ˜cÔ"÷C !ôD 	�‰�‰ð-Ø.1°|Õ3DÀc÷	
ñ] ðd Ð÷M !× ús   Ã$HÃ>4HÄ<0HÅ-A.HÈH"c               ó    € V ^8„  d   QhRRRR/# )r4   Ú
error_textr7   r6   r(   )r8   s   "r2   r9   r9   ž  s   € ÷ ñ  3ð ¨3ñ r1   c                ó¬   € \         P                  ! RRV R,          4      p\         P                  ! RRV4      pVP                  4       P                  4       # )zTNormalize an error message (strip PIDs, timestamps) so same-root-cause errors group.z\bpid \d+\bzpid N:NéP   Nz\b\d{10,}\bz<TS>)ÚreÚsubÚlowerr¹   )re  Úfps   & r2   Ú_error_fingerprintrl  ž  s?   € ä	�Š� ¨°C­Ó	9€BÜ	�Š� ¨Ó	+€BØ�8‰8‹:×ÑÓÐr1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r   r7   r6   r   r(   )r8   s   "r2   r9   r9   °  s"   € ÷ ñ Ð%7ð À#ð È#ñ r1   c                óZ  € ^ pV P                  RV\        34      P                  4       pV F|  pVR,          ;'       g    RpVR8X  d   K  VR8X  dV   \        P                  ! VR,          4      P                  R4      '       g   RVR	,          ;'       g    R9   d   V^,          pK{   V# 	  V# )
aè  Count the task's trailing run of clean-exit protocol violations.

Walks closed runs newest-first (including the one ``detect_crashed_workers``
just closed). ``rate_limited`` runs are neutral and skipped (a quota wall
says nothing about the task); any other closed run breaks the streak, so
the budget counts ONLY protocol violations. Violations are recognized by the
``protocol_violation`` run-metadata marker, with the error text as fallback
for runs recorded before the marker existed.
zrSELECT outcome, error, metadata FROM task_runs WHERE task_id = ? AND ended_at IS NOT NULL ORDER BY id DESC LIMIT ?r;  r†   r#   r   r=  Úprotocol_violationzprotocol violationr<  )rý   Ú_PROTOCOL_VIOLATION_SCAN_LIMITrÿ   rz   Ú
_json_dictr=   )r÷   r   Ústreakr  r  r;  s   &&    r2   Ú_protocol_violation_streakrs  °  s±   € ð €FØ�<‰<ð	#ð 
Ô0Ð1ó	÷
 �hƒjð 	ó ˆØ�i•.×&Ð& BˆØ�nÔ$ÙØ�iÔÜ�NŠN˜3˜z�?Ó+×/Ñ/Ð0D×EÒEØ#¨¨G­×(:Ð(:¸Ô;à�a�KˆFÙØØ€Mñ ð €Mr1   u/  worker exited cleanly (rc=0) without kanban_complete, kanban_block or kanban_request_review â€” protocol violation. If the prior run already did the work, verify it and report it via kanban_complete (or kanban_request_review); a run without a terminal kanban call counts as failed no matter what it did.u7   [â”€â”�â•�â•­â•®â•°â•¯â”‚â”ƒâ”Œâ”�â””â”˜]+|â˜¤\s*Hermesc               ó   € V ^8„  d   QhRR/# ©r4   r6   r7   r(   )r8   s   "r2   r9   r9   ä  s   € ÷ -ñ -˜cñ -r1   c                 ó   € ^ RI Hp  V ! R4      # )z^The CLI exit-summary header (``cli_session_mixin.show_exit_summary``), in the active language.©re   zcli.session.exit_resume_hint©Ú
agent.i18nre   rw  s    r2   Ú_exit_summary_markerrz  ä  s   € åÙÐ+Ó,Ð,r1   c               ó   € V ^8„  d   QhRR/# )r4   r6   ztuple[str, ...]r(   )r8   s   "r2   r9   r9   ê  s   € ÷ Gñ G˜_ñ Gr1   c                 ó$   € ^ RI Hp  RRV ! R4      3# )ry   rw  zsession_id:zQuery:zcli.chat.initializing_agentrx  rw  s    r2   Ú_log_noise_prefixesr}  ê  s   € ÝØ˜8¡QÐ'DÓ%EÐFÐFr1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r   r7   r€   r&   r6   r(   )r8   s   "r2   r9   r9   ï  s!   € ÷ "ñ " #ð "¨mð "Àsñ "r1   c                óþ  €  \         P                  ! V RVR7      pT'       g   R# \        P	                  RT4      pTP                  \        4       4      pTR8w  d   TRT p. pTP                  4        Fc  p\        P	                  RT4      P                  4       pT'       g   K1  TP                  \        4       4      '       d   KR  TP                  T4       Ke  	  RP                  T4      RR #   \         d     R# i ; i)a  Best-effort read of a dead worker's last printed text, for the board diagnostic.

A ``chat -q`` worker's stdout/stderr are redirected to its per-task log
(``_default_spawn``), so when it exits without a terminal board call the
reason is usually sitting there: the model's own explanation of why it could
not comply (#88603), or the rendered provider error (#46593). The reap used to
discard it in favour of a canned message on every retry. Trims the CLI exit
summary, rule lines and the ``session_id:`` trailer; returns "" (never raises)
on a missing/empty log.

``board`` must come from the dispatching tick: ambient current-board resolution
is wrong for every board but the one the dispatcher thread happens to call
"current", so the log would silently not be found.
rƒ   r„   r†   NÚ r‡   ipþÿÿ)rz   rˆ   r‰   rŠ   ri  Úrfindrz  Ú
splitlinesÚ_LOG_CHROMEr¹   r°   r}  rA   rB   )r   r€   rp   ÚcutÚlinesÚlns   &&    r2   Ú_worker_final_outputr‡  ï  sÖ   € ðÜ×!Ò! '°dÀ%ÔHˆ÷ ÙÜ
×
Ñ
˜r 3Ó
'€CØ
�)‰)Ô(Ó*Ó
+€CØ
ˆb„yØ�$�3ˆiˆØ€EØ�n‰nÖˆÜ�_‰_˜R Ó$×*Ñ*Ó,ˆß‰2�b—m‘mÔ$7Ó$9×:Ô:Ø�L‰L˜Öñ ð �8‰8�E‹?˜4˜5Ð!Ð!øô ô Úðús   ‚C- Ã-C<Ã;C<c                  ó�   € ] tR tRt$ RtR]R&   R]R&   R]R&   R]R&   R	]R
&   RtR]R&   RtR]R&   RtR]R&    ]	R R l4       t
RtR# )Ú_DeadWorkeri  z;How ``detect_crashed_workers`` should book one dead worker.r7   Úkindr�   rt   re  Ú
event_kindr  Úevent_payloadFr$   ro  r#   rv   c               ó   € V ^8„  d   QhRR/# ru  r(   )r8   s   "r2   r9   Ú_DeadWorker.__annotate__   s   € ÷ Bñ B˜Sñ Br1   c                	ó.   € V P                   '       d   R # R# )r#   r   ©r#   )Úselfs   &r2   Úrun_outcomeÚ_DeadWorker.run_outcome  s   € ð "&×!2×!2Ð!2ˆ~ÐA¸	ÐAr1   r(   N)r)   r*   r+   r,   r-   r.   ro  r#   rv   Úpropertyr’  r0   r(   r1   r2   r‰  r‰    s\   ‡ áEà
ƒIØ
ÓØƒOØƒOØÓØ$Ð˜Ó$Ø€L�$ÓØ#Ð�tÓ#ðHð ôBó ôBr1   r‰  r   r€   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# ©	r4   rS   r   Úclaimerr&   r   r€   r6   r‰  r(   )r8   s   "r2   r9   r9   &  s6   € ÷ ñ Ø	ðØ$ðØBOðàðð ñr1   c               óÔ   € \        WW#VR7      pV'       dR   VP                  '       g@   \        W4R7      pV'       d,   V;P                  RV: 2,          un        WeP                  R&   V# )a2  Map a dead worker's reaped exit status to its reclaim bookkeeping.

A clean exit or a crash carries the worker's own last output (``worker_output``
in the event payload, appended to the error text) so the board and the retry
worker see WHY instead of a bare label; a rate-limited requeue does not need it.
©r   r€   ©r€   z Worker's last output: Úworker_output)Ú_classify_dead_worker_exitr#   r‡  re  rŒ  )rS   r—  Ú	exit_coder   r€   Údeadr›  s   &&&$$  r2   Ú_classify_dead_workerrŸ  &  sX   € ô & c°IÐV[Ô\€Dß�t×(×(Ð(Ü,¨WÔBˆßØ�OŠOÐ!8¸Ñ8IÐJÕJ�OØ2?×Ñ˜Ñ/Ø€Kr1   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# r–  r(   )r8   s   "r2   r9   r9   9  sH   € ÷ DIñ DIØ	ðDIàðDIð
 ðDIð ðDIð ñDIr1   c               ó  € \        V 4      w  rVVe   \        \        V4      4      w  rVM,VR8X  d&   V'       d   \        W4R7      pVe   \        V4      w  rVVR8X  d   \	        WV\
        RRV RVRVRR/RR	7      # VR
8X  d   \	        WVRV  R2R
RV RVRV/RR7      # VR8X  d"   \	        WVRV  RV R2RRV RVRVRVRR/RR7      # VR8X  d
   RV  RV 2pMVR8X  d
   RV  RV 2pMRV  R2pRV RV/p	Ve   VR8w  d	   WYR&   WiR&   \	        WVVRV	4      # )am  Exit status -> reclaim bookkeeping, before the worker's own words are folded in.

``exit_code`` is the authority's exact-run result for a managed worker (a child of the
profile owner, never of this dispatcher, so it cannot be reaped here). Otherwise the reap
registry only knows children of THIS process; a per-tick dispatcher reads the exit trailer
the worker left in its log instead, so the same death gets the same booking (protocol
violation / rate-limit requeue / crash) as under the gateway-embedded dispatcher. A worker
that never reached its exit epilogue (killed, OOM) leaves no trailer and stays a plain crash.
rm   rš  rx   ro  rS   r—  r�  T)ro  r#   zpid uI    exited rate-limited (quota wall) â€” requeued without counting a failurer�  rv   z+ exited on a terminal provider error (exit ud   ): the provider rejected this profile's credential or model â€” fix the configuration, then unblock.r   Ú	exit_kind)rv   rw   z exited with code rl   z killed by signal z
 not alive)rr   rn   r   r�   r‰  Ú_PROTOCOL_VIOLATION_ERROR)
rS   r—  r�  r   r€   rŠ  rt   Úloggedre  rŒ  s
   &&&$$     r2   rœ  rœ  9  s¢  € ô" ' sÓ+�J€DØÒÜ$¤S¨£^Ó4‰
ˆˆdØ	�Ô	ŸwÜ& wÔ<ˆØÒÜ(¨Ó0‰JˆDØˆ|Ôô ØÔ1Ð3Gð �C˜ G¨[¸$Ð@TÐVZÐ[Ø#ô
ð 	
ð ˆ~Ôô ØØ�3�%Ð`ÐaØØ�C˜ G¨[¸$Ð?Øô
ð 	
ð Ð"Ô"ô ØØ�3�%ÐBÀ4À&ð IZð ZàØ�C˜ G¨[¸$ÀÈTÐSfÐhlÐmØ"ô
ð 	
ð ˆ~ÔØ˜C˜5Ð 2°4°&Ð9‰
Ø	�Ô	Ø˜C˜5Ð 2°4°&Ð9‰
à˜C˜5 
Ð+ˆ
Ø˜C ¨GÐ4€MØÒ˜D IÔ-Ø%)�kÑ"Ø%)�kÑ"Ü�t :¨y¸-ÓHÐHr1   c                  óŒ   € ] tR tRt$ Rt]! ]R7      tR]R&   ]! ]R7      t	R]R&   ]! ]R7      t
R]R&   ]! ]R7      tR	]R
&   RtR# )Ú_CrashSweepi€  zFEverything ``detect_crashed_workers`` collects inside its reclaim txn.r   r   r   r#   z'list[tuple[str, int, str, _DeadWorker]]Úcrash_detailsz
list[dict]Úexited_hook_payloadsr(   N)r)   r*   r+   r,   r-   r   r/   r   r.   r#   r§  r¨  r0   r(   r1   r2   r¦  r¦  €  sI   ‡ áPá¨tÔ4€GˆYÓ4Ù#°DÔ9€L�)Ó9ñ >CÐSWÔ=X€MÐ:ÓXñ (-¸TÔ'BÐ˜*×Br1   r¦  c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r€   r&   r6   r¦  r(   )r8   s   "r2   r9   r9   Ž  s(   € ÷ Jñ JÐ 2ð J¸=ð JÐT_ñ Jr1   c                ó†  € \        4       p\        P                  ! V 4      ;_uu_ 4        V P                  R4      P	                  4       p\        P
                  ! 4       p^ RIHpHp V EF8  pVR,          ;'       g    RpVP                  V4      '       g   K0  \        P                  ! \        P                  ! VR4      4      p	V	e7   \        P                  ! 4       V	,
          \        P                  ! 4       8  d   K•  \        VR,          \        P                  ! VR4      4      '       d   KÅ  V! WR	,          R
R7      '       d   KÞ  \        VR,          4      p
V! WR	,          W§R,          4      p\!        W§R,          W·R	,          VR7      p\        P"                  ! WR	,          4      pWÜP$                  R&   V P                  RW×R	,          W§R,          34      pVP&                  ^8w  d   EK„  \        P(                  ! WR	,          VP*                  VP*                  VP,                  \/        VP$                  4      R7      p\        P0                  ! WR	,          VP2                  VP$                  VR7       VP4                  P7                  RVR	,          RVR,          RVRV
RVP8                  RVP:                  RVP*                  RV/4       VP<                  '       g   VP>                  '       d-   V P                  RVP,                  R,          VR	,          34       VP<                  '       d&   VP<                  P7                  VR	,          4       EKê  VP@                  P7                  VR	,          4       VPB                  P7                  VR	,          W§R,          V34       EK;  	  RRR4       V#   + '       g   i     T# ; i)zCRelease every host-local ``running`` task whose worker PID is dead.z‰SELECT id, worker_pid, worker_started_at, claim_lock, started_at, assignee FROM tasks WHERE status = 'running' AND worker_pid IS NOT NULL)Úmanaged_run_exit_coder0  ré   r†   rÌ   Nr	  r
  rú   T)Úcrash_sweepr™  r9  z¾UPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL WHERE id = ? AND status = 'running'   AND worker_pid = ? AND claim_lock IS ?r:  r  r   Úassigneer  r¢  r�  r;  z4UPDATE tasks SET last_failure_error = ? WHERE id = ?ºNiô  N)"r¦  rz   r  rý   rÿ   rò   rA  r«  r0  r°   rB  rC  r]   Ú_resolve_crash_grace_secondsrÍ   r   rŸ  rG  rŒ  r  rH  r’  re  r  r  r‹  r¨  rA   rŠ  rt   r#   ro  r   r§  )r÷   r€   Úsweepr  r  r«  r0  r  rJ  rÌ   rS   r�  rž  r9  r"  r  s   &&              r2   Ú_reclaim_dead_workersr±  Ž  sü  € ä‹M€EÜ	�Š�t×	Õ	Ø�|‰|ðBó
÷ ‰(‹*ð	 	ô
 ×&Ò&Ó(ˆßaÜˆCØ�|Õ$×*Ð*¨ˆDØ—?‘? ;×/Ò/Ùô Ÿš¤s§|¢|°C¸Ó'FÓGˆJØÒ%¬$¯)ª)«+¸
Õ*BÄS×EeÒEeÓEgÔ*gÙÜ˜S Õ.´·²¸SÐBUÓ0V×WÒWÙñ % T¨t­9À$×GÓGÙä�c˜,Õ'Ó(ˆCá-¨d¸µI¸sÈÕDUÓVˆIÜ(¨°,Õ.?ÀÐX\ÕT]ÐejÔkˆDÜ×4Ò4°T¸t½9ÓEˆLØ1=×Ñ˜~Ñ.Ø—,‘,ð;ð  4�y¨#°<Õ/@ÐAóˆCð �|‰|˜qÔ ÚÜ—\’\Ø˜$•iØ×(Ñ(°×1AÑ1AØ—o‘oÜ˜d×0Ñ0Ó1ô	ˆFô ×Ò˜d¨¥I¨t¯©À×@RÑ@RÐ[aÕbØ×&Ñ&×-Ñ-Ø˜3˜t�9Ø˜C 
�OØ˜&Ø˜cØ˜TŸY™YØ˜TŸY™YØ˜4×+Ñ+Ø ð	/ô 	ð × × Ð  D×$;×$;Ð$;ð —‘ØJØ—_‘_ TÕ*¨C°­IÐ6ôð × × Ð Ø×"Ñ"×)Ñ)¨#¨d­)×4à—‘×$Ñ$ S¨¥YÔ/Ø×#Ñ#×*Ñ*¨C°­I°sÀÕ<MÈtÐ+T×Uñ} ÷ 
ðN €L÷O 
Ö	ðN €Lús&   ©AN/Á<N/ÂIN/Ë/=N/Ì-A8N/Î/O 	c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r§  r/   r6   r   r(   )r8   s   "r2   r9   r9   Û  s'   € ÷ Mñ MÐ-ð M¸dð MÀyñ Mr1   c                ó  € . p/ pV F7  w    rE\        VP                  4      pVP                  V^ 4      ^,           W6&   K9  	  V EF;  w  rxr•VP                  p
VP                  '       dƒ   \	        W4      pV P                  RV34      P                  4       pVf   KW  \        P                  ! VR4      pVe   \        V4      M\        pW¾8  d   K‹  \        WV
RVRRRRVRV	R	VR
V/R7	      pMxVP                  '       d   \        WV
RRRRRVRV	RR/R7      pMMVP                  \        V
4      ^ 4      ^8¬  pRVRV	/pV'       d   RVR&   \        YT
RV'       d   ^MRRRVR7      pV'       g   EK*  VP                  V4       EK>  	  V# )u†  Count each crash against the breaker; returns the task ids it tripped.

Protocol violations get a BOUNDED violation-only budget independent of
``consecutive_failures`` (per-task ``max_retries`` takes precedence);
systemic same-error crashes (>= 3 identical fingerprints this tick) and
terminal provider errors (credential revoked, model gone â€” a retry cannot
heal them) trip immediately.
z*SELECT max_retries FROM tasks WHERE id = ?NÚmax_retriesr   TFrS   r—  Úprotocol_violationsÚprotocol_violation_limit)r<  r;  Úfailure_limitÚ
force_tripr>  r?  r@  rv   )r<  r;  r¸  r>  r?  r@  Ústicky)r<  r;  r·  r>  r?  r@  )rl  re  r=   ro  rs  rý   Úfetchonerz   rC  r   Ú!_PROTOCOL_VIOLATION_FAILURE_LIMITrI  rv   rA   )r÷   r§  r   Ú	fp_countsrh   rž  rk  rM  rS   r—  re  rr  ÚtrowÚtask_overrideÚviolation_limitÚtrippedÚis_systemicÚextras   &&                r2   Ú_account_crashesrÃ  Û  sº  € ð !€LØ "€IÛ&‰ˆˆ1ˆaÜ §¡Ó0ˆØ!Ÿ™ b¨!Ó,¨qÕ0ˆ	‹ñ 'ô $1Ñˆ�'Ø—_‘_ˆ
Ø×"×"Ð"Ü/°Ó:ˆFØ—<‘<Ð LÈsÈfÓU×^Ñ^Ó`ˆDØŠ|ÙÜŸLšL¨¨}Ó=ˆMà&3Ò&?”�MÔ"ÔEfð ð Ô'ñ ô +ØØ Ø!Ø-ØØ#Øà˜3Ø˜wØ)¨6Ø.°ð	%ô‰Gð ×#×#Ð#ô
 +ØØ Ø!ØØ#ØØ%*¨C°¸GÐEXÐZ^Ð$_ô‰Gð $Ÿ-™-Ô(:¸:Ó(FÈÓJÈaÑOˆKØ˜C ¨GÐ4ˆEßà"&��h‘Ü*ØØ Ø!ß#.™a°DØ#ØØ$)ôˆG÷ Š7Ø×Ñ ×$ñ} $1ð~ Ðr1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r€   r&   r6   r   r(   )r8   s   "r2   r9   r9   +  s#   € ÷ .ñ .Ð!3ð .¸Mð .ÐU^ñ .r1   c                óØ  € \        WR7      pVP                  '       d   \        WP                  4      M. pV\        n        VP
                  \        n        VP                  '       dy   \        P                  ! R4      '       d]   \        P                  ! 4       pVP                   F7  p\        V4      p\        P                  ! RVP                  R4      3RV/VB  K9  	  VP                  # )uà  Reclaim ``running`` tasks whose worker PID is no longer alive.

Restores the source phase immediately (no waiting for the claim TTL), for
tasks claimed by *this host* only â€” other hosts' PIDs are meaningless.
Clean exit while ``running`` is a protocol violation with a bounded
violation-only retry budget; ``KANBAN_RATE_LIMIT_EXIT_CODE`` is a quota
wall, released WITHOUT counting a failure and surfaced via the
``_last_rate_limited`` attribute (the return stays crashed-only).
rš  Úon_kanban_worker_exitedr   r€   )r±  r§  rÃ  Údetect_crashed_workersÚ_last_auto_blockedr#   Ú_last_rate_limitedr¨  rz   Ú_kanban_observer_consumedÚget_current_boardr  Ú_fire_kanban_lifecycle_hookr`   r   )r÷   r€   r°  r   Ú_boardÚhook_fieldss   &&    r2   rÇ  rÇ  +  sÍ   € ô " $Ô4€EàBG×BU×BUÐBUÔ# D×*=Ñ*=Ô>Ð[]€Lð 1=ÔÔ-Ø05×0BÑ0BÔÔ-ð ×!×!Ð!¤c×&CÒ&CÐD]×&^Ò&^Ü×&Ò&Ó(ˆØ ×5Ô5ˆKÜ˜{Ó+ˆKÜ×+Ò+ð" *Ø—‘ 	Ó*ñ%ð& ð'ð( ô)ñ 6ð0 �=‰=Ðr1   r·  r¸  Fr>  r?  r@  Úinfrastructurec               ó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
/# )r4   r÷   rø   r   r7   r<  r;  r·  z
int | Noner¸  r$   r>  r?  r@  zOptional[dict]rÏ  r6   r(   )r8   s   "r2   r9   r9   \  sƒ   € ÷ xñ xØ
ðxàðxð ðxð
 ðxð ðxð ðxð ðxð ðxð (ðxð ðxð 
ñxr1   c               ó¾  € Vf   \         pVR,          p\        P                  ! V 4      ;_uu_ 4        V P                  RV34      P	                  4       p
V
f    RRR4       R# V'       d   \        P
                  ! WV
R,          4      MV
R,          R8X  d   RMRp\        V
R	,          4      V	'       d   ^ M^,           p\        P                  ! V
R
4      pVe   \        V4      RrþM\        V4      RrþV	'       g   V'       g–   WÎ8¼  g�   V'       d   V P                  RW¼W!34       MV P                  RWÂV34       V'       dL   RVRV/pV	'       d   RVR&   \        P                  ! WW3VVR7      p\        P                  ! WVRV/VCVR7        RRR4       R# T P                  RV'       d   RMR,           R,           WÂV34       RVRVRVRVRVRV/pRpV'       d&   \        P                  ! WRRVRVRVRVRVRV/R7      pV'       d   RVR&   V'       d   VP                  V4       \        P                  ! WRVVR7        RRR4       R#   + '       g   i     R# ; i)uù  Record a non-success outcome and maybe trip the circuit breaker; every
non-success path funnels through here so ``consecutive_failures`` stays
consistent. Returns True when the task was auto-blocked.

``release_claim=True, end_run=True``: spawn-failure path (task still
running with an open run â€” restore source phase or ``blocked``, release
claim, close run). Both False: timeout/crash path (caller already restored
the phase and closed the run; only the counter moves, a trip flips to
``blocked`` + ``gave_up``). Threshold: per-task ``max_retries`` >
``failure_limit`` > ``DEFAULT_FAILURE_LIMIT``. ``force_trip`` trips
unconditionally (caller applied its own bounded-retry policy).

``infrastructure=True``: the host refused the spawn (no restart-safe scope,
#114720) â€” nothing about the card ran, so the run and event are recorded
with ``infrastructure: true`` but ``consecutive_failures`` is left alone and
the breaker never trips; the card stays retryable and
:func:`check_respawn_guard` spaces the retries.
Nr®  zXSELECT consecutive_failures, status, max_retries, current_run_id FROM tasks WHERE id = ?FÚcurrent_run_idr˜   ÚreviewÚreadyÚconsecutive_failuresr´  Útaskr`  zÇUPDATE tasks SET status = ?, claim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, consecutive_failures = ?, last_failure_error = ? WHERE id = ? AND status = 'running'zNUPDATE tasks SET consecutive_failures = ?, last_failure_error = ? WHERE id = ?Úfailuresr9  TrÏ  r:  r<  r  z%UPDATE tasks SET status = 'blocked', zVclaim_lock = NULL, claim_expires = NULL, worker_pid = NULL, worker_started_at = NULL, r†   zjconsecutive_failures = ?, last_failure_error = ? WHERE id = ? AND status IN ('running', 'ready', 'review')Úeffective_limitÚlimit_sourceÚtrigger_outcomeÚgave_upr¹  )ÚDEFAULT_FAILURE_LIMITrz   r  rý   rº  rG  r   rC  rH  r  r   )r÷   r   r<  r;  r·  r¸  r>  r?  r@  rÏ  r  r9  r×  r¾  rØ  rÙ  Údetailr  r#  s   &&&$$$$$$$         r2   rI  rI  \  sk  € ð> ÒÜ-ˆØ�$�K€EÜ	�Š�t×	Õ	Ø�l‰lð&Ø(/ zó
÷ ‰(‹*ð 	ð Š;Ø÷ 
Ñ	÷ ô ×%Ò% d°SÐ9IÕ5JÔKà! (�m¨xÔ7‘(¸Wð 	ô
 �sÐ1Õ2Ó3¿N±qÐPQÕRˆô Ÿš S¨-Ó8ˆØÒ$Ü,/°Ó,>À™\ä,/°Ó,>À˜\ç§*°Ô0Kßà—‘ð:ð "¨UÐ<õð —‘ð:à gÐ.ô÷ Ø$ h°ÀÐM�ß!Ø/3�FÐ+Ñ,ÜŸšØ¨7È%ÐZ`ô�ô ×!Ò! $°¸7ÀEÐ:TÈVÐ:TÐ]cÕdØ÷] 
Ñ	ðd 	�‰Ø3çñ hØ%'õ)ðHõHð
 ˜gÐ&ô	
ð ˜Ø˜Ø˜LØ�UØ˜wØ˜Lð
ˆð ˆßä—\’\Ø y¸È%à Ø% wØ% Ø" LØ" Lðô	ˆF÷ ð !%ˆG�HÑßØ�N‰NÐ.Ô/Ü×Ò˜$¨°GÀFÕKØ÷m 
×	×	Ò	úsO   ²'IÁ#IÁ+BIÃ:IÄIÄ0IÅIÅ>IÆIÆ+AIÈIÈ,IÉI	r  ré   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r4   r÷   rø   r   r7   rS   r   r6   rU   r(   )r8   s   "r2   r9   r9   ×  s0   € ÷ pñ pÐ,ð p°sð pÀð pÐjnñ pr1   c               óÞ  € \        \        V4      4      ;'       g    \        p\        P                  ! V 4      ;_uu_ 4        Vf   \        P
                  ! W4      pV P                  R\        V4      WQW4V34      pVP                  '       d   V P                  R\        V4      WS34       \        P                  ! WRR\        V4      RV/VR7       RRR4       R#   + '       g   i     R# ; i)aì  Publish the launcher only while this claim has no executing worker yet, together with its
restart-stable fingerprint (``_process_fingerprint``); the ``spawned`` event carries both. The
fingerprint is what lets every later liveness/kill decision tell OUR worker from a process that
recycled the PID after a reboot. A failed capture is persisted as ``UNVERIFIED_WORKER_FINGERPRINT``,
never NULL: NULL is the legacy pre-fingerprint row whose bare-PID kill authority a new spawn must
not inherit.Nz«UPDATE tasks SET worker_pid = ?, worker_started_at = ? WHERE id = ? AND status = 'running' AND current_run_id IS ? AND worker_pid IS NULL AND (? IS NULL OR claim_lock = ?)úGUPDATE task_runs SET worker_pid = ?, worker_started_at = ? WHERE id = ?r   rS   rÌ   r  )	rÇ   r   rÊ   rz   r  r  rý   r  r  )r÷   r   rS   r  ré   rÌ   r"  s   &&&$$  r2   Ú_set_worker_pidrá  ×  s½   € ô &¤c¨#£hÓ/×PÐPÔ3P€JÜ	�Š�t×	Õ	ØŠ>Ü×(Ò(¨Ó7ˆFØ�l‰lð0ô �‹X�z¨FÀ
ÐKó	Mˆð
 �<�<ˆ<Ø�L‰LÐbÜ˜c›( JÐ7ô9ä×Ò˜$¨°U¼CÀ»HÀlÐT^Ð4_ÐhnÕo÷ 
×	×	Ò	ús   ÁBCÃC,	c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r4   r÷   rø   r   r7   r  r   rS   r6   r$   r(   )r8   s   "r2   r9   r9   í  s1   € ÷ ñ Ð-ð ¸ð ÀSð Èsð ÐW[ñ r1   c                óÞ  € \        \        V4      4      ;'       g    \        p\        P                  ! V 4      ;_uu_ 4        V P                  RV34      P                  4       pVe&   VR,          R8w  g   VR,          \        V4      8w  d    RRR4       R# VR,          f°   VR,          ;'       g    R	P                  \        P                  ! 4       4      '       du   V P                  R
\        V4      WA34       V P                  R\        V4      V\        V4      34       \        P                  ! WRR\        V4      RV/\        V4      R7       RRR4       R#   + '       g   i     R# ; i)aü  Worker-side half of ``_set_worker_pid``, run by the worker before its first model call.

A dispatcher killed between spawning the worker and ``_set_worker_pid`` leaves the run with no
pid: no liveness check can see the worker, so a TTL expiry reclaims the card and spawns a second
worker beside it. The worker fills the missing pid itself (``worker_registered``). False when
``run_id`` is no longer the card's live run: the card was reclaimed before this worker got here,
and it must exit without working it.zMSELECT status, current_run_id, worker_pid, claim_lock FROM tasks WHERE id = ?Nr˜   ÚrunningrÒ  Fr	  ré   r†   zCUPDATE tasks SET worker_pid = ?, worker_started_at = ? WHERE id = ?rà  Úworker_registeredrS   rÌ   r  T)
rÇ   r   rÊ   rz   r  rý   rº  r°   rò   r  )r÷   r   r  rS   rÌ   r  s   &&&&  r2   Úadopt_worker_pidræ  í  s+  € ô &¤c¨#£hÓ/×PÐPÔ3P€JÜ	�Š�t×	Õ	Ø�l‰lÐjØ#˜:ó'ß'/¡x£zð 	àŠ;˜#˜h�-¨9Ô4¸Ð<LÕ8MÔQTÐU[ÓQ\Ô8\Ø÷	 
Ñ	ð ˆ|ÕÒ$¨#¨lÕ*;×*AÐ*A¸r×)MÑ)MÌc×N^ÒN^ÓN`×)aÒ)aØ�L‰LÐ^Ü˜c›( JÐ8ô:à�L‰LÐbÜ˜c›( J´°F³Ð<ô>ä×Ò˜dÐ-@À5Ì#ÈcË(ÐT`ÐblÐBmÜ%(¨£[õ2÷ 
ñ ÷ 
Ö	ñ ús    ÁAEÂEÂ2*EÃA4EÅE,	c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r   r7   r6   rU   r(   )r8   s   "r2   r9   r9     s"   € ÷ 
ñ 
Ð!3ð 
¸cð 
Àdñ 
r1   c                ó¢   € \         P                  ! V 4      ;_uu_ 4        V P                  RV34       RRR4       R#   + '       g   i     R# ; i)zÿReset the unified consecutive-failures counter.

Called from ``complete_task`` on success. NOT called on spawn success: a
spawn proves the worker could start, not that the run will succeed, so
timeouts and crashes must accumulate across spawn boundaries.
zQUPDATE tasks SET consecutive_failures = 0, last_failure_error = NULL WHERE id = ?N)rz   r  rý   )r÷   r   s   &&r2   Ú_clear_failure_counterré    s8   € ô 
�Š�t×	Õ	Ø�‰ð5àˆJô	
÷ 
×	×	Ò	ús	   Ÿ=½A	ÚlanerÔ  c               ó(   € V ^8„  d   QhRRRRRRRR/# )r4   r÷   rø   r   r7   rê  r6   r&   r(   )r8   s   "r2   r9   r9     s2   € ÷ eñ eØ
ðeØ'*ðeØ58ðeàñer1   c               óÆ  € V P                  RV34      P                  4       pVf   R# \        \        P                  ! 4       4      p\        P
                  ! 4       pV P                  RV34      P                  4       pVex   VR,          R8X  dj   V^ 8”  dc   \        P                  ! VR,          4      P                  R4      '       d1   \        P                  ! VR,          4      pVe   WG,
          V8  d   R	# VeJ   VR,          R
8X  d<   V^ 8:  d   R# \        P                  ! VR,          4      pVe   WG,
          V8  d   R# R# \        P                  ! VR,          4      pVe
   VR,          MRp	V'       d%   V	R8w  d   \        P                  V4      '       d   R# VR8X  d   R# \        R \        V RW\        ,
          R4       4       RR7      p
V
e    \        \        V RWR4      4      '       g   R# \        V RW\         ,
          RVR7       UUu. uFI  w  r¼\"        P                  \        P                  ! VR,          4      ;'       g    R4      '       g   KG  VNKK  	  pppV'       dq   \        ;QJ d5    R \        V RV\        V4      RRRR 7       4       F  '       g   K   RM*	  R!M&! R \        V RV\        V4      RRRR 7       4       4      '       d   R# R"# R# u uppi )#ui  Return a guard reason if ``task_id`` should NOT be re-spawned, else None.

Called per ready/review row before any claim attempt. Priority order:
``"infrastructure_cooldown"`` (latest run is a ``spawn_failed`` the host
refused â€” no restart-safe scope â€” within the cooldown; never counted),
``"rate_limit_cooldown"`` (latest run ``rate_limited`` within the cooldown;
checked BEFORE ``blocker_auth`` because the requeue stamps a quota-flavored
``last_failure_error`` that would otherwise park the task forever â€” that
path never increments ``consecutive_failures``), ``"blocker_auth"``
(quota/auth pattern; the breaker still trips eventually), then for the
ready lane only ``"recent_success"`` (completed run within the window, unless
a re-queue event arrived after it â€” a deliberate re-run) and ``"active_pr"``
(PR URL in a recent comment; re-spawning risks a duplicate PR â€” unless a
handoff event followed the comment: the named profile must work on that
PR). The review lane skips the last two: they are the *inputs* to a review
handoff. Stale / dead claim locks are NOT a guard reason â€” the reclaim
passes own those.
z1SELECT last_failure_error FROM tasks WHERE id = ?Nz{SELECT outcome, ended_at, metadata FROM task_runs WHERE task_id = ? AND ended_at IS NOT NULL ORDER BY ended_at DESC LIMIT 1r;  Úspawn_failedr=  rÏ  Úended_atÚinfrastructure_cooldownr#   Úrate_limit_cooldownÚlast_failure_errorr   Úblocker_authrÓ  c              3  ó*   "  € T F	  w  rVx € K  	  R # 5i©Nr(   )Ú.0re   rh   s   &  r2   Ú	<genexpr>Ú&check_respawn_guard.<locals>.<genexpr>c  s   é € ð gñ "f™˜•1ó "fùs   ‚Ú	task_runszAND outcome = 'completed')ÚdefaultÚtask_eventsz<AND kind IN ('status', 'promoted', 'unblocked', 'reclaimed')Úrecent_successÚtask_commentsz, body)ÚcolsÚ
unreadableÚbodyr†   c              3  óZ   "  € T F!  w  r\        VR ,          VR,          4      x € K#  	  R# 5i)rŠ  r#  N)Ú_is_handoff_event)rõ  rh   Úes   &  r2   rö  r÷  t  s2   é € ð zñ Gy¹d¸aÔ   6¥¨A¨i­L×9Ð9ó Gyùs   ‚)+z@AND kind IN ('assigned', 'changes_requested', 'review_reopened')z, kind, payloadT)rý  ÚstrictFÚ	active_pr)rý   rº  r   r]   rz   Ú$_resolve_rate_limit_cooldown_secondsrq  r=   rB  Ú_lossy_textÚ_RESPAWN_BLOCKER_REÚsearchÚmaxÚ_rows_sinceÚ_RESPAWN_GUARD_SUCCESS_WINDOWÚanyÚ_RESPAWN_GUARD_PR_WINDOWÚ_RESPAWN_GUARD_PR_URL_RE)r÷   r   rê  r  ra   Úrl_cooldownÚ
latest_runrî  ÚerrÚlatest_outcomeÚsuccessre   ÚcÚpr_timess   &&$           r2   Úcheck_respawn_guardr    s¶  € ð* �,‰,Ø;Ø	ˆ
ó÷ �hƒjð ð ‚{Ùä
Œd�iŠi‹kÓ
€Cô ×:Ò:Ó<€KØ—‘ð	)ð 
ˆ
ó	÷
 �hƒjð ð Ò *¨YÕ"7¸>Ô"IØ˜Œ?œsŸ~š~¨j¸Õ.DÓE×IÑIÐJZ×[Ò[Ü—}’} Z°
Õ%;Ó<ˆHØÒ#¨­¸;Ô(FÙ0ØÒ *¨YÕ"7¸>Ô"IØ˜!Ôñ Ü—=’= ¨JÕ!7Ó8ˆØÒ S¥^°{Ô$BÙ(ñ ô �/Š/˜#Ð2Õ3Ó
4€CØ.8Ò.D�Z 	Ö*È$€Nß
ˆ~ Ô*Ô/B×/IÑ/IÈ#×/NÒ/NÙð ˆxÔÙô ñ g¤Øˆk˜7Ô*GÕ$GÐIdô"fó gØptôv€GàÒ¤3¤{Ø�- Ð3qó(s÷ $tò $táô *¨$°ÀÔPhÕJhØ/7ÀCõIô Vñ I‘d�aä+×2Ñ2´3·?²?À1ÀVÅ9Ó3M×3SÐ3SÐQS×T÷ �ñ I€Hñ V÷ ç‹3ñ zÄkØ�m W¬c°(«mØRÐYjÐswõGyó z�3�3Š3ñ zÄkØ�m W¬c°(«mØRÐYjÐswõGyó z÷ zò zñ ÙáùóVs   È9KÉ
KÉKrø  rî  rú  Ú
created_atrü  r†   rý  r  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/	# )r4   r÷   rø   Útabler7   r   Úsincer   Úwhererý  r  r$   rþ  r�   r6   zIterator[tuple[int, Any]]r(   )r8   s   "r2   r9   r9   €  sQ   € ÷ ñ Ð(ð °ð ¸sð È3ð ÐWZð ØðØ(,ðØBOðØ[tñr1   c             #  ó*  "  € \         V,          pRT T RT RT RT RV'       d   RMR R2p	V P                  W’V34       FL  p
\        P                  ! V
^ ,          4      pVf   TMTpVf   K.  V'       d
   W³8”  g   K>  MW³8¼  g   KG  Wº3x € KN  	  R	# 5i)
aq  ``(epoch, row)`` for ``task_id``'s ``table`` rows timed at/after ``since`` (after, if ``strict``).
SQLite sorts TEXT above every INTEGER, so the indexed range also returns every text stamp (written
outside the mutators, #97455); those are decided on their parsed epoch. An unreadable time is
dropped (never guards or lifts) unless the caller substitutes ``unreadable``.zSELECT z FROM z WHERE task_id = ? z AND r€  Ú>z>=z ?N)Ú_GUARD_TIME_COLSrý   rz   rB  )r÷   r  r   r  r  rý  r  rþ  Úcolr*  r  re   s   &&&&&$$$    r2   r
  r
  €  s�   é € ô ˜5Õ
!€CØ�C�5˜˜˜f U GÐ+>¸u¸gÀUÈ3È%Èq×X^ÑQTÐdhÐPiÐikÐ
l€CØ�|‰|˜C¨5Ð!1Ö2ˆÜ�MŠM˜#˜a�&Ó!ˆØš)‰J¨ˆØŒ=¯6˜aŸi°q¶zØ�&ŒLó	 3ùs   ‚A(BÁ/BÁ7BÁ?BÂBc               ó$   € V ^8„  d   QhRRRRRR/# )r4   rŠ  r7   r#  r&   r6   r$   r(   )r8   s   "r2   r9   r9   �  s!   € ÷ >ñ >˜Cð >¨-ð >¸Dñ >r1   c                ó  € V R8w  d   R# \         P                  ! V/ 4      p\        V\        4      '       d   VP	                  R4      R8X  d   R# VP	                  R4      p\        V4      ;'       d    RV9   ;'       d    VR,          V8g  # )u˜  Only an ``assigned`` event that moves the card to a DIFFERENT profile is
a handoff. A no-op re-assign (devâ†’dev via CLI/dashboard/``reassign
--reclaim``), an unassign, or the dispatcher's own
``kanban.default_assignee`` write would otherwise lift ``active_pr`` for
the very implementer that opened the PR. Events without ``from`` (written
before it was recorded) are not trusted as handoffs â€” fail closed.ÚassignedTÚsourceúkanban.default_assigneeFr­  Úfrom)rz   Ú_json_orrÑ   r  r=   r$   )rŠ  r#  ÚdataÚtos   &&  r2   r  r  �  su   € ð ˆzÔÙÜ�<Š<˜ Ó$€DÜ�dœD×!Ò! T§X¡X¨hÓ%7Ð;TÔ%TÙØ	�‰�*Ó	€BÜ�‹8×=Ð=˜ $™×=Ð=¨4°­<¸2Ñ+=Ð=r1   c               ó   € V ^8„  d   QhRR/# )r4   r6   zOptional[Callable[[str], bool]]r(   )r8   s   "r2   r9   r9   Ÿ  s   € ÷ ñ Ð;ñ r1   c                 óz   aaa€  ^ RI HoHo \	        S4      oSf   S# R TTT3R llp T #   \         d     R# i ; i)u¿  ``hermes_cli.profiles.profile_exists``, or ``None`` when it cannot be
imported (local import avoids a cycle; callers fall back to trusting the
assignee).

When ``kanban.dispatch_profiles`` is set (#110995) the returned predicate
additionally requires the assignee to be listed, fail-closed â€” so a card
assigned to ``default`` is only claimable by homes that opted into it.
Foreign assignees land in the existing ``skipped_nonspawnable`` bucket.
)Únormalize_profile_nameÚprofile_existsNc               ó    € V ^8„  d   QhRRRR/# )r4   Únamer7   r6   r$   r(   )r8   s   "r2   r9   Ú(_profile_exists_fn.<locals>.__annotate__±  s   € ÷ Añ A�Sð A˜Tñ Ar1   c                óv   <€  S! V 4      pTS9   ;'       d    \        S! T 4      4      #   \          d     R # i ; i)F)rÓ   r$   )r.  ÚcanonÚ	allowlistr+  r,  s   & €€€r2   Ú_gatedÚ"_profile_exists_fn.<locals>._gated±  sE   ø€ ð	Ù*¨4Ó0ˆEð ˜	Ñ!×@Ð@¤d©>¸$Ó+?Ó&@Ð@øô ô 	Úð	ús   ƒ) ©8·8)Úhermes_cli.profilesr+  r,  r‰   Ú_dispatch_profile_allowlist)r3  r2  r+  r,  s    @@@r2   Ú_profile_exists_fnr7  Ÿ  sK   ú€ ðßNô ,Ð,BÓC€IØÒØÐ÷Aò Að €Møô ô Úðús   …+ «:¹:c               ó   € V ^8„  d   QhRR/# )r4   r6   zOptional[frozenset]r(   )r8   s   "r2   r9   r9   »  s   € ÷ .ñ .Ð;Nñ .r1   c                ó@  €  ^ RI Hp V! RR7      ;'       g    / P                  R/ 4      p\        T\        4      '       d   RT9  d   R# TR,          pTe-   \        T\        4      '       d@   TP                  4       '       g*   \        P
                  P                  R4       \        4       # \        T\        \        34      '       d   T Uu. uF  p\        T4      NK  	  upM\        T4      P!                  R	4      p\#        4       pT F  p TP%                  T ! T4      4       K  	  \        T4      #   \         dI   p\        P
                  P                  R\        T4      P                  T4       \        4       u Rp?# Rp?ii ; iu upi   \&         d     K“  i ; i)
uã  Per-home claim allowlist ``kanban.dispatch_profiles`` (#110995).

On a shared board (one ``kanban.db`` mounted across several Hermes homes),
every home's ``profile_exists`` returns True for ``default`` â€” the root
profile every home has â€” so a card assigned to ``default`` is claimable by
every home's dispatcher. A home opts out of foreign claims by declaring
which assignees it may claim::

    kanban:
      dispatch_profiles: ["sage", "researcher"]   # or "sage,researcher"

Returns ``None`` only when the key is absent from the user config (upstream
behavior: any existing profile is claimable). A present value is
fail-closed: an empty list, ``null`` or a bare ``dispatch_profiles:`` claims
nothing. The user layer is read without the ``DEFAULT_CONFIG`` merge (whose
``None`` placeholder would make the key look present in every home), and a
config read that raises also claims nothing â€” a corrupt config on a shared
board must never widen this home's claim scope silently (#113620).
)Úload_user_config_effectiveT)Úfail_closedÚkanbanus   kanban: could not read kanban.dispatch_profiles (%s: %s) â€” this home claims no cards until the config is readableNÚdispatch_profilesu   kanban: kanban.dispatch_profiles is present but empty â€” this home claims no cards; omit the key to allow any existing profileÚ,)Úhermes_cli.config_effectiver:  r=   r‰   rz   r  rD  Útyper)   Ú	frozensetrÑ   r
   r7   r¹   r/   Útupler±   ÚsetÚaddrÓ   )r+  r:  r<  Úexcrp   ÚnÚnamesÚalloweds   &       r2   r6  r6  »  sT  € ð(	ÝJÙ,¸Ô>×DÐDÀ"×IÑIÈ(ÐTVÓWˆô �fœg×&Ò&Ð*=ÀVÔ*KÙØ
Ð$Õ
%€CØ
‚{”z #¤s×+Ò+°C·I±I·K²KÜ�‰×ÑðJô	
ô ‹{ÐÜ%/°´d¼E°]×%CÒ%C™SÓ!™S˜ŒS�ŽV™SÒ!ÌÈSËÏÉÐX[ÓI\€EÜ‹e€GÛˆð	Ø�K‰KÑ.¨qÓ1Ö2ñ ô
 �WÓÐøô/ ô Ü�‰×ÑðEä�‹I×Ñ ô	
ô
 ‹{Õûðüò  "øô
 ô 	Úð	ús:   ‚D3 ˜D3 ÃF	ÄFÄ3FÄ>=FÅ;FÆFÆFÆFc               ó   € V ^8„  d   QhRR/# ru  r(   )r8   s   "r2   r9   r9   ì  s   € ÷ Wñ W¨Cñ Wr1   c                 ó¶   €  ^ RI Hp  \        T 4      pTf   R# T'       d   RP	                  \        T4      4      # R#   \         d   pRT R2u Rp?# Rp?ii ; i)a  Human-readable resolution of ``kanban.dispatch_profiles`` for this home.

Surfaced by ``hermes kanban diagnostics`` so an operator on a shared board
can see what a home believes it may claim (#113620): ``any`` (key absent),
the sorted allowed names, or ``none (fail-closed: ...)``.
)r+  z)none (fail-closed: profiles unavailable: Ú)Nr  r<   u‘   none (fail-closed: kanban.dispatch_profiles is present but names no valid profile, or the config could not be read â€” omit the key to allow any))r5  r+  r‰   r6  rB   r?   )r+  rE  r2  s      r2   Ú"dispatch_profile_allowlist_summaryrL  ì  sa   € ðBÝ>ô ,Ð,BÓC€IØÒÙßØ�y‰yœ 	Ó*Ó+Ð+ñVøô ô BØ:¸3¸%¸qÐAÕAûðBús   ‚= ½AÁAÁAÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r˜   r7   r6   r$   r(   )r8   s   "r2   r9   r9      s'   € ÷ @ñ @Ð+ð @°Sð @¸Tñ @r1   c                óî   a€ V P                  R V34      P                  4       pV'       g   R# \        4       oSf   R# \        ;QJ d    V3R lV 4       F  '       g   K   R# 	  R# ! V3R lV 4       4      # )zdSELECT DISTINCT assignee FROM tasks WHERE status = ? AND assignee IS NOT NULL AND claim_lock IS NULLFTc              3  óB   <"  € T F  pS! VR ,          4      x € K  	  R# 5i)r­  Nr(   )rõ  r  r,  s   & €r2   rö  Ú!_has_spawnable.<locals>.<genexpr>  s   øé € Ð?¹$°3‰~˜c *�o×.Ð.»$ùs   ƒ)rý   rÿ   r7  r  )r÷   r˜   r  r,  s   && @r2   Ú_has_spawnablerQ     sj   ø€ Ø�<‰<ð	Kà	ˆ	ó÷ �hƒjð	 	÷
 ÙÜ'Ó)€NØÒáß‹3Ô?¹$Ó?�3Œ3Ð?Š3Ð?ˆ3Ô?¹$Ó?Ó?Ð?r1   c               ó    € V ^8„  d   QhRRRR/# ©r4   r÷   rø   r6   r$   r(   )r8   s   "r2   r9   r9     s   € ÷ )ñ )Ð0ð )°Tñ )r1   c                ó   € \        V R4      # )a"  True iff a ready+assigned+unclaimed task maps to a real Hermes profile.

Lets health telemetry tell "stuck" (``0 spawned`` with spawnable work) from
"correctly idle" (only control-plane lanes waiting on ``claim_task``). Falls
back to "any assigned" when ``profile_exists`` is unimportable.
rÔ  ©rQ  ©r÷   s   &r2   Úhas_spawnable_readyrW    s   € ô ˜$ Ó(Ð(r1   c               ó    € V ^8„  d   QhRRRR/# rS  r(   )r8   s   "r2   r9   r9     s   € ÷ *ñ *Ð1ð *°dñ *r1   c                ó   € \        V R4      # )z2:func:`has_spawnable_ready` for the review column.rÓ  rU  rV  s   &r2   Úhas_spawnable_reviewrZ    s   € ä˜$ Ó)Ð)r1   c               ó   € V ^8„  d   QhRR/# )r4   r6   r$   r(   )r8   s   "r2   r9   r9     s   € ÷ ñ  ñ r1   c                 ó¨   €  ^ RI Hp  \        V ! 4       ;'       g    / P                  R/ 4      P                  RR4      4      #   \         d     R# i ; i)z�Whether review tasks dispatch automatically. Default true (Hermes ships
``sdlc-review``); operators disable it for human-only review boards.
©Úload_configr<  Úreview_dispatchT)Úhermes_cli.configr^  r$   r=   r‰   r]  s    r2   Úreview_dispatch_enabledra    sM   € ðÝ1Ü‘[“]×(Ð( b×-Ñ-¨h¸Ó;×?Ñ?Ð@QÐSWÓXÓYÐYøÜô Úðús   ‚A ›&A ÁAÁAi   c               ó   € V ^8„  d   QhRR/# )r4   r6   r  r(   )r8   s   "r2   r9   r9   9  s   € ÷ ñ ˜tñ r1   c                 óX   €  ^ RI Hp  V ! 4       ;'       g    / #   \         d    / u # i ; i)u  Best-effort system memory snapshot (KiB values), ``{}`` when unknown.

Local import keeps ``kanban_db`` importable without the gateway package.
Module-level indirection is also the test seam â€” conftest patches this to
``{}`` so results don't depend on the CI runner's live memory.
©Úsample_memory)Úgateway.lifecycle_ledgerre  r‰   rd  s    r2   Ú_system_memory_samplerg  9  s.   € ðÝ:Ù‹×$Ð$ "Ð$øÜô ØŠ	ðús   ‚ – ™)¨)c               ó    € V ^8„  d   QhRRRR/# )r4   ÚsampleúOptional[Mapping[str, Any]]r6   r�   r(   )r8   s   "r2   r9   r9   G  s    € ÷ ]ñ ]Ð+Fð ]ÐR_ñ ]r1   c                ó  € V f   \        4       p V P                  R4      p\        V\        4      '       g   \        V\        4      '       d   V^ 8:  d   R# VR,          \
        ,          p\        \        \        V\        4      4      # )zêMemory-derived default for ``kanban.max_in_progress`` when unset:
``clamp(MemTotal / MEMORY_GUARD_MB_PER_WORKER, FLOOR, CEILING)``. Returns
``None`` (no cap) when total memory is unknown, so macOS/Windows dev
machines are unaffected.
NÚmem_total_kibi   )
rg  r=   rÑ   r$   r   ÚMEMORY_GUARD_MB_PER_WORKERr	  ÚDERIVED_MAX_IN_PROGRESS_FLOORÚminÚDERIVED_MAX_IN_PROGRESS_CEILING)ri  Ú	total_kibÚworkerss   &  r2   Úderive_default_max_in_progressrs  G  sh   € ð ‚~Ü&Ó(ˆØ—
‘
˜?Ó+€IÜ�)œT×"Ò"¬*°YÄ×*DÒ*DÈ	ÐUVÌÙØ˜DÕ Ô%?Õ?€GÜÔ,¬c°'Ô;ZÓ.[Ó\Ð\r1   c               ó    € V ^8„  d   QhRRRR/# )r4   Ú
configuredr�   r6   r(   )r8   s   "r2   r9   r9   V  s   € ÷ ,ñ ,¨ð ,¸-ñ ,r1   c                ó"   € V e   V # \        4       # )zœEffective global concurrency cap: explicit config wins, else the
memory-derived default. All config-parsing callers route through this so
both paths agree.
)rs  )ru  s   &r2   Úresolve_max_in_progressrw  V  s   € ð
 ÒØÐÜ)Ó+Ð+r1   c               ó   € V ^8„  d   QhRR/# )r4   r6   r�   r(   )r8   s   "r2   r9   r9   `  s   € ÷ 'ñ ' Mñ 'r1   c                 óþ   €  ^ RI Hp  V ! 4       ;'       g    / P                  R/ 4      P                  R4      pTf   R#  \	        T4      pT^8¼  d   T# R#   \         d     R# i ; i  \
        \        3 d     R# i ; i)zßRead ``kanban.max_in_progress`` from config, or None when unset/invalid.

Shared so every dispatch entry point agrees on "explicitly configured": a
positive integer wins, anything else falls through to the derived default.
)Úload_config_readonlyr<  Úmax_in_progressN)r`  rz  r=   r‰   r   rÒ   rÓ   )rz  rp   Úivals      r2   Úconfigured_max_in_progressr}  `  sŠ   € ðÝ:Ù#Ó%×+Ð+¨×0Ñ0°¸2Ó>×BÑBÐCTÓUˆð ‚{ÙðÜ�3‹xˆð ˜1”9ˆ4Ð& $Ð&øô ô Úðûô ”zÐ"ô Úðús'   ‚A –"A ¿A' ÁA$Á#A$Á'A<Á;A<c               ó    € V ^8„  d   QhRRRR/# )r4   r÷   rø   r6   r   r(   )r8   s   "r2   r9   r9   t  s   € ÷ ñ Ð0ð °Sñ r1   c                ó†   €  \        V P                  R4      P                  4       ^ ,          4      #   \         d     ^ # i ; i)u  Number of tasks in ``status='running'``.

Used by the multi-board sweep to count OTHER boards' workers against the
host-level budget â€” the memory-derived cap bounds the machine, not the
board. Fails open to 0 so a broken board doesn't brick dispatch on healthy ones.
z3SELECT COUNT(*) FROM tasks WHERE status = 'running')r   rý   rº  r‰   rV  s   &r2   Úcount_running_tasksr€  t  sE   € ðÜØ�L‰LØEóç‰h‹j˜õó
ð 	
øô
 ô Úðús   ‚.1 ±A ¿A c               ó    € V ^8„  d   QhRRRR/# )r4   r€   r&   r6   r   r(   )r8   s   "r2   r9   r9   …  s   € ÷ "ñ "¨Mð "ÀSñ "r1   c           
     óê  €  \        \        P                  ! V R7      P                  4       P	                  4       4      p \        P                  ! RR7      p^ pT Fô  pTP                  R4      ;'       g    \        P                  p \        P                  ! TR7      P                  4       p\        TP	                  4       4      pTe	   Yq8X  d   Kw  TP                  4       '       g   K�  \        P                  ! TR7      p T\        T4      ,          p\        P                  ! \
        4      ;_uu_ 4        TP                  4        RRR4       Kö  	  T#   \
         d    Rp EL&i ; i  \
         d     ^ # i ; i  + '       g   i     EK2  ; i  \        P                  ! \
        4      ;_uu_ 4        TP                  4        RRR4       i   + '       g   i     i ; i; i  \
         d     EK—  i ; i)aN  Total ``running`` tasks across every board EXCEPT ``board``.

Caps bound the HOST, but each board's tick only sees its own DB; without
this a derived cap of N gets multiplied by the number of active boards.
Boards are matched by resolved DB path, so ``HERMES_KANBAN_DB`` (pins every
board to one file) yields 0. Fails open per board.
rš  NF)Úinclude_archivedÚslug)r7   rz   Úkanban_db_pathÚ
expanduserÚresolver‰   Úlist_boardsr=   ÚDEFAULT_BOARDÚexistsÚ_kbcÚconnectr€  rE  rF  Úclose)	r€   Úcurrent_pathÚboardsÚtotalÚmetar„  ÚpathÚresolvedÚothers	   &        r2   Ú count_running_tasks_other_boardsr•  …  sr  € ðÜœ3×-Ò-°EÔ:×EÑEÓG×OÑOÓQÓRˆðÜ—’°%Ô8ˆð €EÛˆØ�x‰x˜Ó×4Ð4¤3×#4Ñ#4ˆð	Ü×%Ò%¨DÔ1×<Ñ<Ó>ˆDÜ˜4Ÿ<™<›>Ó*ˆHØÒ'¨HÔ,DÙØ—;‘;—=’=ÙÜ—L’L tÔ,ˆEð"ØÔ,¨UÓ3Õ3�ä×(Ò(¬×3Õ3Ø—K‘K”M÷ 4Ñ3ñ ð" €Løô1 ô Ø‹ðûô ô Úðú÷  4×3Ñ3û”Z×(Ò(¬×3Õ3Ø—K‘K”M÷ 4×3×3Ð3þäô 	Ûð	ús�   ‚<E ¿E( ÂAG"ÃG"Ã)G"ÄFÄ"G"Ä5E:ÅG"ÅE%Å$E%Å(E7Å6E7Å:FÆG"ÆG"Æ#GÆ2GÇ	GÇGÇGÇG"Ç"G2Ç1G2c               ó    € V ^8„  d   QhRRRR/# )r4   ri  rj  r6   r7   r(   )r8   s   "r2   r9   r9   ª  s   € ÷ ñ Ð#>ð È#ñ r1   c                ó²   € V f   \        4       p V '       g   R#  ^ RIHp V! V P                  R4      V P                  R4      4      #   \         d     R# i ; i)u9  Classify system memory pressure: ok/elevated/critical/unknown.

Reuses :func:`gateway.memory_status.classify_pressure` so "critical" matches
the dashboard banner and lifecycle-ledger OOM heuristics. ``unknown``
(non-Linux, read failure) imposes no restriction â€” never brick dispatch
where /proc is unavailable.
rm   )Úclassify_pressureÚmem_available_kibrl  )rg  Úgateway.memory_statusr˜  r=   r‰   )ri  r˜  s   & r2   Ú_memory_pressure_levelr›  ª  sQ   € ð ‚~Ü&Ó(ˆßÙðÝ;Ù  §¡Ð,?Ó!@À&Ç*Á*È_ÓB]Ó^Ð^øÜô Úðús   š,A ÁAÁAÚspawn_fnÚttl_secondsÚdry_runÚ	max_spawnr{  Údefault_assigneeÚmax_in_progress_per_profileÚreconcile_orphansTc               óH   € 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/# ©r4   r÷   rø   r�  r�   rž  r$   rŸ  r{  r·  r   rP  r€   r&   r   r¡  r¢  r6   r   r(   )r8   s   "r2   r9   r9   ½  s‚   € ÷ 8ñ 8Ø
ð8ð ð	8ð
 ð8ð ð8ð #ð8ð ð8ð ð8ð ð8ð $ð8ð "/ð8ð ð8ð ñ8r1   c               óè  a aaaaaaaaa	a
a€ R VV V	VVVV
VVVVV3R llp \         P                  ! SR7      p\        P
                  ! T4      ;_uu_ 4       pT'       g   \        RR7      pMT! 4       p\        P                  ! S T4       RRR4       \         P                  ! XSSR7       T#   \         d&    T! 4       p\         P                  ! TSSR7       Tu # i ; i  + '       g   i     L^; i)a‘  Run one dispatcher tick under the board's single-writer lock.

Wraps :func:`_dispatch_once_locked` in the non-blocking :func:`_dispatch_tick_lock`
so two dispatchers on one ``kanban.db`` never race a write tick on WAL
frames. The loser returns an empty ``DispatchResult`` with
``skipped_locked=True`` and writes nothing; the lock is keyed on the
resolved DB path so unrelated boards tick in parallel.
c               ó   € V ^8„  d   QhRR/# )r4   r6   r   r(   )r8   s   "r2   r9   Ú#dispatch_once.<locals>.__annotate__Ô  s   € ÷ 
ñ 
˜.ñ 
r1   c                 ó2   <€ \        SS	SSSSSS
S SSSR 7      # ))rœ  r�  rž  rŸ  r{  r·  rP  r€   r   r¡  r¢  )Ú_dispatch_once_locked)r€   r÷   r   rž  r·  r{  r¡  rŸ  r¢  rœ  rP  r�  s   €€€€€€€€€€€€r2   Ú_locked_tickÚ#dispatch_once.<locals>._locked_tickÔ  s4   ø€ Ü$ØØØ#ØØØ+Ø'Ø"7ØØ-Ø(CØ/ô
ð 	
r1   rš  )r€   rž  T)r%   N)rz   r…  r‰   Ú_fire_dispatch_tick_hookr‹  Ú_dispatch_tick_lockr   Ú_maybe_checkpoint_wal)r÷   rœ  r�  rž  rŸ  r{  r·  rP  r€   r   r¡  r¢  rª  Údb_pathÚresultÚhelds   fddddddddddd    r2   Údispatch_oncer²  ½  s·   ÿû€ ÷.
÷ 
ó 
ð Ü×$Ò$¨5Ô1ˆô 
×	!Ò	! '×	*Ô	*¨dßÜ#°4Ô8‰Fá!“^ˆFä×&Ò& t¨WÔ5÷ 
+ô × Ò  ¨u¸gÕFØ€Møô ô á“ˆÜ×$Ò$ V°5À'ÕJØŠð	ú÷
 
+×	*ús   ¢B. Á4C!Â.-CÃCÃ!C1	c               ó(   € V ^8„  d   QhRRRRRRRR/# ©	r4   rÖ  r   Ú	workspacer7   r€   r&   r6   r�   r(   )r8   s   "r2   r9   r9   ø  s)   € ÷ 
)ñ 
) 4ð 
)°Cð 
)Àð 
)ÐR_ñ 
)r1   c                ó²   € ^ RI p VP                  V 4      pRVP                  9   d   V ! WVR7      # V ! W4      #   \        \        3 d    T ! Y4      u # i ; i)z�Back-compat: older spawn_fn signatures (and test stubs) accept only
``(task, workspace)``; pass ``board`` only when the callable supports it.Nr€   rš  )ÚinspectÚ	signatureÚ
parametersrÒ   rÓ   )rœ  rÖ  rµ  r€   r·  Úsigs   &&&&  r2   Ú_call_spawn_fnr»  ø  s_   € ó ð)Ø×Ñ Ó)ˆØ�c—n‘nÔ$Ù˜D°5Ô9Ð9Ù˜Ó(Ð(øÜ”zÐ"ô )Ù˜Ó(Ò(ð)ús   †+: ²: ºAÁAc               óH   € 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/# )r4   r÷   rø   r  zsqlite3.Rowr­  r7   r°  r   rê  rž  r$   r�  r�   r€   r&   r·  r   Úper_profile_capÚper_profile_runningzdict[str, int]r6   r(   )r8   s   "r2   r9   r9     sŽ   € ÷ |ñ |Ø
ð|à	ð|ð ð|ð ð	|ð ð|ð ð|ð ð|ð ð|ð ð|ð #ð|ð (ð|ð 
ñ|r1   c               óü
  a
a€ VR,          p\        4       pVeÎ   V! V4      '       gÀ   VP                  P                  V4       V'       g›   \        P                  ! V 4      ;_uu_ 4        V P                  RV34      P                  4       pVe3   VR,          R8w  g%   VR,          \        P                  ! RV/4      8w  d   \        P                  ! WRRV/4       RRR4       R# R# S
e9   SP                  V^ 4      pVS
8¼  d    VP                  P                  WÂV34       R# \        WVR	7      pVek   VP                  P                  VV34       V'       gD   \        P                  ! V 4      ;_uu_ 4        \        P                  ! WR
RV/4       RRR4       R# R# R V
V3R llpV'       d(   VP                  P                  WÂR34       V! V4       R# VR8X  d   \        P                  M\        P                  pV! WVR7      pVf   R#  RpVP                   R8X  d   \"        P$                  ! VVR7      w  ppM\"        P&                  ! VVR7      p \"        P0                  ! V VP,                  \3        V4      4       VP                   R8X  df   \"        P4                  ! T VP,                  T;'       g<    VP6                  ;'       g    RP9                  4       ;'       g    RVP,                   24       \"        P:                  ! V VP,                  VP                   4       VR8X  d<   \=        \>        PA                  . VPB                  ;'       g    . ORN4      4      Vn!         \E        V	e   T	M\F        V\3        V4      V4      pV'       d8   \I        V VP,                  \K        V4      VPL                  VPN                  R7       \        PP                  ! V V\3        V4      VVR7       VP                  P                  VP,                  VPR                  ;'       g    R\3        V4      34       V! VPR                  4       R#   + '       g   i     R# ; i  + '       g   i     R# ; i  \(         dV   p\+        T TP,                  RT 2RTRRR7      '       d&   TP.                  P                  TP,                  4        Rp?R# Rp?ii ; i  \(         d¢   p^ RI*H+p \Y        TT4      pT'       d,   \        PZ                  P]                  RTP,                  T4       \+        T TP,                  \3        T4      RTRRTR7      '       d&   TP.                  P                  TP,                  4        Rp?R# Rp?ii ; i)z­Guard, claim, resolve the workspace and spawn one ready/review row.
Returns True when a spawn slot was consumed (real or ``dry_run``); every
skip is recorded on ``result``.
rú   NzaSELECT kind, payload FROM task_events WHERE task_id = ? ORDER BY created_at DESC, id DESC LIMIT 1rŠ  r   r#  r­  F©rê  r"   rG   c               ó    € V ^8„  d   QhRRRR/# )r4   r.  r7   r6   rU   r(   )r8   s   "r2   r9   Ú)_dispatch_lane_task.<locals>.__annotate__D  s   € ÷ Mñ M˜3ð M 4ñ Mr1   c                ó`   <€ Se)   V '       d   SP                  V ^ 4      ^,           SV &   R # R # R # rô  )r=   )r.  r½  r¾  s   &€€r2   Ú_count_spawnÚ)_dispatch_lane_task.<locals>._count_spawnD  s2   ø€ ð Ò&¯4Ø(;×(?Ñ(?ÀÀaÓ(HÈ1Õ(LÐ Ó%ñ ,0Ñ&r1   r†   TrÓ  )r�  Úworktreerš  zworkspace: rí  )r;  r·  r>  r?  zwt/zsdlc-review)r  ré   )ÚRestartSafeScopeUnavailablezIkanban dispatcher: spawn of %s deferred, host cannot place the worker: %s)r;  r·  r>  r?  rÏ  )/r7  r   rA   rz   r  rý   rº  Ú_json_or_nullr  r=   r   r  r"   r   Úclaim_review_taskÚ
claim_taskÚworkspace_kindÚ_kbwÚ_resolve_worktree_workspaceÚresolve_workspacer‰   rI  rú   r   Úset_workspace_pathr7   Úset_branch_nameÚbranch_namer¹   Ú_maybe_emit_scratch_tipr/   r  ÚfromkeysÚskillsr»  Ú_default_spawnrá  r   rÒ  ré   Ú_fire_worker_spawned_hookr­  Útools.process_registryrÇ  rÑ   r  rD  )r÷   r  r­  r°  rê  rž  r�  r€   r·  rœ  r½  r¾  r   r,  ÚlastrÔ   Úguard_reasonrÄ  ÚclaimÚclaimedÚresolved_branch_namerµ  rE  rS   rÇ  rÏ  s   &&&&$$$$$$dd              r2   Ú_dispatch_lane_taskrÝ    s‹  ù€ ð& �$�i€Gô
 (Ó)€NØÒ!©.¸×*BÒ*BØ×#Ñ#×*Ñ*¨7Ô3÷ Ü—’˜t×$Õ$Ø—|‘|ð@ØBIÀóMçMUÉXËZð ð ’L D¨¥LÐ4JÔ$JØ 	�?¬c×.?Ò.?ÀÈXÐ@VÓ.WÔWÜ×%Ò% dÐ5KÈjÐZbÐMcÔd÷ %ó ð Ò"Ø%×)Ñ)¨(°AÓ6ˆØ�oÔ%Ø×-Ñ-×4Ñ4°gÈÐ5QÔRÙÜ& t¸4Ô@€LØÒØ×Ñ×%Ñ% w°Ð&=Ô>÷ Ü—’˜t×$Õ$Ü×!Ò! $Ð1BÀXÈ|ÐD\Ô]÷ %ã÷Mñ M÷ Ø�‰×Ñ˜w°"Ð5Ô6Ù�XÔÙØ%)¨XÔ%5ŒC×!Ò!¼3¿>¹>€EÙ�D¨{Ô;€GØ‚ÙðØ#ÐØ×!Ñ! ZÔ/Ü.2×.NÒ.NÈwÐ^cÔ.dÑ+ˆIÑ+ä×.Ò.¨w¸eÔD‰Iô 	×Ò˜D '§*¡*¬c°)«nÔ=Ø×Ñ Ô+Ü×Ò˜T 7§:¡:Ð/C÷  0Að  0AÈ×H[ÑH[×HaÐHaÐ_a×GhÑGhÓGj÷  0Að  0AÐpsÐt{×t~Ñt~Ðsð  oAô  	BÜ× Ò   w§z¡z°7×3IÑ3IÔJØˆxÔô œdŸm™mÐ,T¨w¯~©~×/CÐ/CÀÐ,TÀmÐ,TÓUÓVˆŒðÜ¨Ò)=™XÄ>ÐSZÔ\_Ð`iÓ\jÐlqÓrˆßÜ˜D '§*¡*¬c°#«h¸w×?UÑ?UÐbi×btÑbtÕuä×%Ò% d¨G´S¸³^ÀSÐPUÕVð 	�‰×Ñ˜wŸz™z¨7×+;Ñ+;×+AÐ+A¸rÄ3ÀyÃ>ÐRÔSÙ�W×%Ñ%Ô&Ù÷Y %Ö$ñ ú÷& %Ö$áûô, ô ÜØ�'—*‘* ¨C¨5Ð1Ø"°-ÈtÐ]a÷
ó 
ð ×Ñ×&Ñ& w§z¡zÔ2Ýûðûô6 ô ÝFô $ CÐ)DÓEˆßÜ�H‰H×ÑÐhÐjq×jtÑjtÐvyÔzÜØ�'—*‘*œc #›hØ"°-ÈtÐ]aØ)÷
ó 
ð
 ×Ñ×&Ñ& w§z¡zÔ2ÝûðúsX   Á)A2QÅ7QÈ.Q, È>Q, Í'B6S Ð$S ÑQ	ÑQ)	Ñ,SÑ7A
SÓSÓU;ÓBU6Õ6U;c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r4   r÷   rø   r   r7   r­  rž  r$   r6   r(   )r8   s   "r2   r9   r9   „  s4   € ÷ ñ Ø
ðØ'*ðØ69ðØGKðà	ñr1   c               óX  € V'       d   R#  \         P                  ! V 4      ;_uu_ 4        V P                  RW!34       \         P                  ! WRRVRR/4       RRR4       R#   + '       g   i     R# ; i  \         d'    \         P
                  P                  RY!RR	7        R
# i ; i)a  Persist ``kanban.default_assignee`` on an unassigned ready row.

Mutating the row keeps board state honest: the task is legitimately owned
by the default, not "unassigned but secretly routed". ``dry_run`` reports
without writing. Returns False when the write failed.
TzRUPDATE tasks SET assignee = ? WHERE id = ? AND (assignee IS NULL OR assignee = '')r"  r­  r#  r$  Nz?kanban dispatch: failed to apply default_assignee=%r to task %srû   F)rz   r  rý   r  r‰   r  r  )r÷   r   r­  rž  s   &&&$r2   Ú_apply_default_assigneerà  „  s¡   € ÷ ÙðÜ�]Š]˜4× Õ Ø�L‰Lð:àÐ#ôô
 ×ÒØ˜zØ˜X xÐ1JÐKô÷ !ñ  ÷! !Ö ñ  ûô ô Ü�‰�‰ØMØ¨ð 	ô 	
ò ðús.   ŒA8 ª0A$ÁA8 Á$A5	Á/A8 Á5A8 Á8-B)Â(B)c               ó4   € V ^8„  d   QhRRRRRRRRRR	R
RRR/# )r4   r÷   rø   r°  r   rP  r   r·  r¢  r$   r€   r&   r6   rU   r(   )r8   s   "r2   r9   r9   £  sW   € ÷ Mñ MØ
ðMàðMð ð	Mð
 ðMð ðMð ðMð 
ñMr1   c               óæ  € \        4        \        V 4      Vn        \        P                  ! WR7      Vn        V'       d   \        V 4      Vn        \        WR7      Vn	        \        WR7      Vn        VP                  P                  \        \        R. 4      4       VP                  P                  \        \        R. 4      4       \!        V 4      Vn        \        P$                  ! WR7      Vn        R# )zEReclaim stale/orphaned/crashed/timed-out running tasks, then promote.)r·  )rP  rš  rÈ  rÉ  N)r™   r  r   rz   Úrelease_stale_claimsr   rc  r   r[  r!   rÇ  r   r   Úextendrã   r#   rO  r    Úrecompute_readyr   )r÷   r°  rP  r·  r¢  r€   s   &&$$$$r2   Ú_run_reclaim_phaseræ  £  s³   € ô ÔÜ%:¸4Ó%@€FÔ"Ü×/Ò/°ÔR€FÔßÜ$>¸tÓ$DˆÔ!Ü'¨ÔZ€F„LÜ+¨DÔ>€F„Nð ×Ñ×ÑœwÔ'=Ð?SÐUWÓXÔYØ
×Ñ×ÑœwÔ'=Ð?SÐUWÓXÔYÜ*¨4Ó0€FÔÜ×)Ò)¨$ÔL€F†Or1   c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r4   r÷   rø   r°  r   rŸ  r�   r{  r€   r&   r6   ztuple[bool, Optional[int]]r(   )r8   s   "r2   r9   r9   ¼  sD   € ÷ :ñ :Ø
ð:àð:ð ð	:ð
 #ð:ð ð:ð  ñ:r1   c               ó¦  € ^ pRpVf   Ve   \        V 4      pVe   WR8¼  d   R# W%,
          pVe/   V\        V4      ,           pWs8¼  d   R# W7,
          pVe   Wh8”  d   Tp\        4       p	V	R8X  d(   W‘n        \        P
                  P                  R4       R# V	R8X  d3   W‘n        Ve   V^8”  d"   \        P
                  P                  R4       ^pRV3# )uÈ  ``(may_spawn, spawn_budget)`` for this tick; ``budget None`` = uncapped.

``max_spawn`` is a live per-board concurrency cap (running + this tick's
spawns), not a per-tick budget â€” a per-tick reading would grow concurrency
by N every tick. ``max_in_progress`` is a HOST-level cap: running workers on
every other board count against the same budget, else N boards multiply the
cap by N â€” exactly the fan-out the memory-derived default exists to prevent.
NÚcriticalznkanban dispatch: system memory pressure is critical; spawning no new workers this tick (deferred, not dropped)Úelevatedz_kanban dispatch: system memory pressure is elevated; limiting to at most 1 new worker this tickT)FN)r€  r•  r›  r'   rz   r  rD  )
r÷   r°  rŸ  r{  r€   Úrunning_countÚspawn_budgetÚtotal_runningÚ	remainingrD   s
   &&$$$     r2   Ú_tick_spawn_budgetrï  ¼  sõ   € ð& €MØ"&€LØÒ Ò ;Ü+¨DÓ1ˆð ÒØÔ%ØÐØ Õ0ˆàÒ"Ø%Ô(HÈÓ(OÕOˆØÔ+ØÐØ#Õ3ˆ	ØÒ <Ô#;Ø$ˆLô &Ó'€HØ�:ÔØ!)ÔÜ�‰×ÑðHô	
ð ÐØ�:ÔØ!)ÔØÒ <°!Ô#3Ü�H‰H×Ñð=ôð ˆLØ�ÐÐr1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r÷   rø   r˜   r7   r6   úlist[sqlite3.Row]r(   )r8   s   "r2   r9   r9   ù  s#   € ÷ ñ Ð'ð °ð Ð9Jñ r1   c                óH   € V P                  RV R24      P                  4       # )z-Unclaimed rows of one lane in dispatch order.z/SELECT id, assignee FROM tasks WHERE status = 'z?' AND claim_lock IS NULL ORDER BY priority DESC, created_at ASC)rý   rÿ   )r÷   r˜   s   &&r2   Ú
_lane_rowsró  ù  s0   € à�<‰<ð	Ø!˜(ð #1ð	1ó÷ �hƒjð	r1   r½  r¾  c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r4   r÷   rø   Úreview_rowsrñ  r½  r�   r¾  zOptional[dict[str, int]]r6   r$   r(   )r8   s   "r2   r9   r9   	  s:   € ÷ ñ Ø
ðà"ðð #ð	ð
 2ðð 
ñr1   c               ó  € V'       g   R# \        4       pT;'       g    / pV F`  pVR,          pV'       g   K  Ve   V! V4      '       g   K*  Ve   VP                  V^ 4      V8¼  d   KG  \        WR,          RR7      e   K_   R# 	  R# )u¥  Mirror review dispatch gates before reserving ready-lane capacity.

Unavailable profile metadata retains the historic fail-open behavior. A
review row that :func:`_dispatch_lane_task` would refuse this tick â€” its
assignee already at the per-profile cap, or respawn-guarded â€” cannot
consume the reservation, so it must not withhold capacity from an
otherwise ready task (one such row would pin ``ready_budget`` to 0).
Fr­  rú   rÓ  rÀ  T)r7  r=   r  )r÷   rõ  r½  r¾  r,  rä  r  r­  s   &&$$    r2   Ú_any_spawnable_reviewr÷  	  s   € ÷ ÙÜ'Ó)€NØ!×'Ð' R€GÛˆØ�z•?ˆßÙØÒ%©n¸X×.FÒ.FÙØÒ&¨7¯;©;°xÀÓ+CÀÔ+VÙÜ˜t¨¥Y°XÔ>ÔFÚñ ñ r1   c               ó    € V ^8„  d   QhRRRR/# )r4   r   r&   r6   r(   )r8   s   "r2   r9   r9   "	  s   € ÷ ñ °ð À-ñ r1   c                óš   € T ;'       g    RP                  4       ;'       g    RpV'       d   \        4       pVe   V! V4      '       g   R# V# )al  ``kanban.default_assignee`` when it names a real profile this home may
claim (``kanban.dispatch_profiles`` gated, same predicate as the spawn
gate). Otherwise ``None`` so an unassigned shared-board card is never
written to. When the profiles module isn't importable trust the
operator's config: the downstream check still buckets a missing profile
as nonspawnable.r†   N)r¹   r7  )r   r.  r,  s   &  r2   Ú_resolve_default_assigneerú  "	  sE   € ð ×"Ð" ×)Ñ)Ó+×3Ð3¨t€DßÜ+Ó-ˆØÒ%©n¸T×.BÒ.BÙØ€Kr1   c               óH   € 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(   )r8   s   "r2   r9   r9   4	  s�   € ÷ eñ eØ
ðeð ð	eð
 ðeð ðeð #ðeð ðeð ðeð ðeð $ðeð "/ðeð ðeð ñer1   c          
     óh  € \        4       p\        WVWkVR7       \        WWEVR7      w  rÞV'       g   V# \        V R4      p\	        4       '       d   \        V R4      M. p\        V
\        4      '       d
   V
^ 8”  d   T
MRp/ pVe5   V P                  R4       F  p\        VR,          4      VVR,          &   K!  	  TpVe0   V^ 8”  d)   \        V VVVR	7      '       d   \        V^,
          ^ 4      p\        W2VWaVVR
7      p\        V	4      p	^ pV F®  pVe
   VV8¼  d    M¢VR,          pV'       gl   V	'       d   \        V VR,          W“R7      '       g%   VP                  P                  VR,          4       Kh  T	pVP                  P                  VR,          4       \!        V VVV3RR/VB '       g   K¥  V^,          pK°  	  V Fm  pVe   VV8¼  d    V# VR,          '       g%   VP                  P                  VR,          4       KD  \!        V VVR,          V3RR/VB '       g   Kd  V^,          pKo  	  V# )a5  One dispatcher tick: reclaim stale/crashed running tasks, promote
todo -> ready, then atomically claim each spawnable ready/review row and
call ``spawn_fn(task, workspace_path, board) -> Optional[int]``, recording
the PID so later ticks catch crashes before the TTL. Cap semantics:
:func:`_tick_spawn_budget`.)rP  r·  r¢  r€   )rŸ  r{  r€   rÔ  rÓ  NzmSELECT assignee, COUNT(*) AS n FROM tasks WHERE status = 'running' AND assignee IS NOT NULL GROUP BY assigneerF  r­  )r½  r¾  )rž  r�  r€   r·  rœ  r½  r¾  rú   )rž  rê  )r   ræ  rï  ró  ra  rÑ   r   rý   r÷  r	  r  rú  rà  r   rA   r   rÝ  )r÷   rœ  r�  rž  rŸ  r{  r·  rP  r€   r   r¡  r¢  r°  Ú	may_spawnrì  Ú
ready_rowsrõ  r½  r¾  ÚprowÚready_budgetÚlane_kwargsr   r  Úrow_assignees   &$$$$$$$$$$$             r2   r©  r©  4	  sS  € ô( Ó€FÜØÐ,AØ#ÐPUõô 1Ø 	ÐRWôÑ€I÷ Øˆä˜D 'Ó*€Jô 1H×0IÒ0I”*˜T 8Ô,Èr€Kô 	Ð.´×4Ò4Ø'¨!Ô+ñ 2ð ð ð +-ÐØÒ"Ø—L‘Lð ö
ˆDô
 58¸¸S½	³NÐ  ZÕ 0Ó1ñ
ð  €LØÒ L°1Ô$4Ô9NØˆkØ'Ð=P÷:ó :ô ˜<¨!Õ+¨QÓ/ˆÜ"&Ø¸Ø#Ø'Ð=Pô#€Kô
 1Ð1AÓBÐØ€GÛˆØÒ#¨°<Ô(?ÙØ˜:•ˆß÷ $Ô+BØ�c˜$•iÐ!1÷,ó ,ð ×)Ñ)×0Ñ0°°TµÔ;ÙØ+ˆLØ×(Ñ(×/Ñ/°°Dµ	Ô:Ü˜t S¨,¸Ñ\ÀWÐ\ÐP[×\Ò\Ø�q�LŠGñ ó* ˆØÒ#¨°<Ô(?Øð €Mð �:�ŒØ×%Ñ%×,Ñ,¨S°­YÔ7ÙÜ˜t S¨#¨j­/¸6Ñ`ÈÐ`ÐT_×`Ò`Ø�q�LŠGñ ð €Mr1   Úminimumc               ó(   € V ^8„  d   QhRRRRRRRR/# )r4   Úvaluer   rù  r   r  r6   r(   )r8   s   "r2   r9   r9   œ	  s(   € ÷ 4ñ 4˜ð 4 sð 4¸ð 4ÀCñ 4r1   c               ó`   €  \        V 4      pY28¼  d   T# T#   \        \        3 d    Tu # i ; irô  )r   rÒ   rÓ   )r  rù  r  Úparseds   &&$ r2   Ú_positive_intr  œ	  s=   € ðÜ�U“ˆð Ô&ˆ6Ð3¨GÐ3øô ”zÐ"ô ØŠðús   ‚ —-¬-c               ó   € V ^8„  d   QhRR/# ©r4   r6   r   r(   )r8   s   "r2   r9   r9   ¤	  s   € ÷ 5ñ 5˜Yñ 5r1   c                 ó(   € \         P                  RR.# )uˆ   Interpreter-bound Hermes CLI invocation (``hermes_cli.main`` is the
console-script target â€” there is no top-level ``hermes`` package).ú-mzhermes_cli.main©r­   Ú
executabler(   r1   r2   Ú_module_hermes_argvr  ¤	  s   € ô �N‰N˜DÐ"3Ð4Ð4r1   c               ó    € V ^8„  d   QhRRRR/# )r4   r’  r7   r6   r(   )r8   s   "r2   r9   r9   ª	  s   € ÷ Nñ N ð N¨ñ Nr1   c                óÌ   € \         P                  P                  V 4      p\         P                  P                  V4      '       d   V# \         P                  P	                  V4      # )z>Return an absolute filesystem path for a resolved Hermes shim.)r’   r’  r†  ÚisabsÚabspath)r’  Úexpandeds   & r2   Ú_absolute_hermes_pathr  ª	  s@   € ä�w‰w×!Ñ! $Ó'€HÜ—w‘w—}‘} X×.Ò.ˆ8ÐM´B·G±G·O±OÀHÓ4MÐMr1   c               ó    € V ^8„  d   QhRRRR/# )r4   r  r7   r6   r$   r(   )r8   s   "r2   r9   r9   °	  s   € ÷ 	ñ 	˜Cð 	 Dñ 	r1   c                óz  € \         P                  P                  V 4      pVP                  R4      ;'       g…    \         P                  P	                  V4      ;'       g^    \        \         P                  P                  V4      4      ;'       g.    RV9   ;'       g!    \        \        P                  ! RV4      4      # )zDReturn true when a command override is an explicit path, not a name.Ú~Ú\z
^[A-Za-z]:)	r’   r’  r†  r°   r  r$   Údirnamerh  Úmatch)r  r  s   & r2   Ú_looks_like_pathr  °	  s•   € ä�w‰w×!Ñ! %Ó(€Hà×Ñ˜CÓ ÷ 	3ð 	3Ü�7‰7�=‰=˜Ó"÷	3ð 	3ä”—‘—‘ Ó)Ó*÷	3ð 	3ð �8Ñ÷	3ð 	3ô ”—’˜¨Ó1Ó2ðr1   c               ó    € V ^8„  d   QhRRRR/# )r4   r’  r7   r6   r$   r(   )r8   s   "r2   r9   r9   ¼	  s   € ÷ 3ñ 3 ð 3¨ñ 3r1   c                ó@   € V P                  4       P                  R4      # )zEReturn true for Windows shell/batch shims that should not be argv[0].)z.cmdz.bat)rj  Úendswith©r’  s   &r2   Ú_is_windows_batch_shimr!  ¼	  s   € à�:‰:‹<× Ñ Ð!1Ó2Ð2r1   c               ó    € V ^8„  d   QhRRRR/# )r4   Úcommandr7   r6   r   r(   )r8   s   "r2   r9   r9   Á	  s   € ÷ <ñ < ð <¨	ñ <r1   c                óH  € \         P                  '       d-   \        P                  P	                  V 4      ^,          '       d   V .# \        P
                  P                  R4      ;'       g    RpVP                  R4       Uu. uF  q"'       g   K  W,           NK  	  up# u upi )z:Return executable names to try for an unqualified command.ÚPATHEXTz.COM;.EXE;.BAT;.CMDÚ;)rz   r�   r’   r’  ÚsplitextÚenvironr=   r±   )r#  rp   Úexts   &  r2   Ú_path_search_namesr*  Á	  so   € ä�?�?ˆ?œbŸg™g×.Ñ.¨wÓ7¸×:Ô:ØˆyÐÜ
�*‰*�.‰.˜Ó
#×
<Ð
<Ð'<€CØ%(§Y¡Y¨s¤^Ó;¡^˜c³sŒMˆG�MˆM¡^Ñ;Ð;ùÒ;s   Â	BÂBc               ó    € V ^8„  d   QhRRRR/# )r4   r#  r7   r6   r&   r(   )r8   s   "r2   r9   r9   É	  s   € ÷ ñ  ð ¨ñ r1   c                ó,  € \         P                  P                  RR4      P                  \         P                  4       FÒ  pV'       d   VR8X  d   K  \         P
                  P                  V4      p\        V 4       F�  p\         P
                  P                  W#4      p\         P
                  P                  V4      '       g   KI  \        P                  '       g.   \         P                  ! V\         P                  4      '       g   KŒ  Vu u # 	  KÔ  	  R# )u
  Resolve a bare command from PATH without implicit current-dir search.

On Windows ``shutil.which`` may search the current directory before PATH
for bare names â€” unsafe for a dispatcher. Only explicit PATH entries are
considered; empty / ``.`` entries are skipped.
ÚPATHr†   Ú.N)r’   r(  r=   r±   Úpathsepr’  r†  r*  rB   Úisfilerz   r�   ÚaccessÚX_OK)r#  Úraw_dirÚ	directoryr.  Ú	candidates   &    r2   Ú_safe_which_no_cwdr6  É	  s«   € ô —:‘:—>‘> &¨"Ó-×3Ñ3´B·J±JÖ?ˆß˜' Sœ.ÙÜ—G‘G×&Ñ& wÓ/ˆ	Ü& wÖ/ˆDÜŸ™Ÿ™ YÓ5ˆIÜ�w‰w�~‰~˜i×(Ô(¬c¯o¯o¨oÄÇÂÈ9ÔVX×V]ÑV]×A^ÔA^Ø Ô ó 0ñ	 @ñ r1   c               ó    € V ^8„  d   QhRRRR/# )r4   r’  r7   r6   r   r(   )r8   s   "r2   r9   r9   Û	  s   € ÷ )ñ )˜Cð ) Iñ )r1   c                ó|   € \         P                  '       d   \        V 4      '       d   \        4       # \	        V 4      .# )z»argv for a resolved Hermes executable path. Windows batch shims
(``.cmd``/``.bat``) are unsafe as argv[0] because the argument vector
includes task-derived values; prefer the module form.)rz   r�   r!  r  r  r   s   &r2   Ú_hermes_path_argvr9  Û	  s/   € ô ‡‡€Ô1°$×7Ò7Ü"Ó$Ð$Ü! $Ó'Ð(Ð(r1   c               ó   € V ^8„  d   QhRR/# r
  r(   )r8   s   "r2   r9   r9   ä	  s   € ÷ !!ñ !!˜iñ !!r1   c                 ó  € ^ RI p ^ RIp\        P                  P	                  RR4      P                  4       pV'       dE   \        V4      '       d   \        V4      # \        V4      pV'       d   \        V4      # \        4       #  V P                  P                  R4      e   \        4       #  \        P                  '       d   \        R4      MVP                  R4      pV'       d   \        V4      # \        4       #   \         d     L\i ; i)a»  Resolve the ``hermes`` invocation as argv for ``Popen``: ``$HERMES_BIN``
(path-like -> absolute; bare names keep PATH semantics, never a
same-directory file), then the running interpreter's ``sys.executable -m
hermes_cli.main`` (exactly this install; also covers shim-less cron,
systemd ``User=``, launchd), then ``which("hermes")`` (Windows: safe PATH
search, batch shims fall back to the module form) only when ``hermes_cli``
is not importable. The module argv must win over PATH: a PATH-first lookup
lets an attacker-planted ``hermes`` shadow the running install (#111569).
Mirrors ``gateway.run._resolve_hermes_bin``; local because ``hermes_cli``
sits below ``gateway`` in the dependency order.
NÚ
HERMES_BINr†   Ú
hermes_cliÚhermes)Úimportlib.utilÚshutilr’   r(  r=   r¹   r  r9  r6  r  ÚutilÚ	find_specr‰   rz   r�   Úwhich)Ú	importlibr@  Úenv_binÚresolved_env_binÚ
hermes_bins        r2   Ú_resolve_hermes_argvrH  ä	  s×   € ó Ûä�j‰j�n‰n˜\¨2Ó.×4Ñ4Ó6€GßÜ˜G×$Ò$Ü$ WÓ-Ð-Ü-¨gÓ6ÐßÜ$Ð%5Ó6Ð6Ü"Ó$Ð$ðØ�>‰>×#Ñ# LÓ1Ò=Ü&Ó(Ð(ð >ô
 25··°Ô# HÔ-ÀfÇlÁlÐS[ÓF\€JßÜ  Ó,Ð,ÜÓ Ð øô ô Ùðús   Â&C; Ã;D	ÄD	c               ó$   € V ^8„  d   QhRRRRRR/# )r4   r3  r�   Úcurrent_timeoutr&   r6   r(   )r8   s   "r2   r9   r9   
  s&   € ÷ ñ Ø&ðà"ðð ñr1   c                óH  € V f   R#  \        V 4      pT^ 8:  d   R# \        ^T\        ,
          4      p T'       d#   \        \        T4      P                  4       4      M^ pYC8¼  d   R# \        T4      #   \        \        3 d     R# i ; i  \        \        3 d    ^ p L@i ; i)zíReturn a worker-scoped TERMINAL_TIMEOUT override, if needed.

When ``max_runtime_seconds`` exceeds the terminal tool's default timeout,
raise only the child's default so a long command isn't killed by the
generic terminal default first.
N)r   rÒ   rÓ   r	  Ú%KANBAN_TERMINAL_TIMEOUT_GRACE_SECONDSr7   r¹   )r3  rJ  ÚruntimeÚdesiredÚexistings   &&   r2   Ú_worker_terminal_timeout_envrP  
  s¤   € ð Ò"ÙðÜÐ)Ó*ˆð �!„|Ùä�!�WÔDÕDÓE€Gðß8G”3”s˜?Ó+×1Ñ1Ó3Ô4ÈQˆð ÔÙÜˆw‹<Ðøô ”zÐ"ô Úðûô ”zÐ"ô ØŠðús"   ˆA3 ´,B Á3BÂBÂB!Â B!Ú	bind_homec               ó    € V ^8„  d   QhRRRR/# )r4   Úhermes_homer7   rQ  r$   r(   )r8   s   "r2   r9   r9   &
  s   € ÷ *3ñ *3 sð *3¸$ñ *3r1   c             #  ó„  "  € ^ RI HpHpHp ^ RIHpHpHp ^ RIH	pH
p	 ^ RIHp
Hp \        V 4      p\        VP!                  4       4      \        \        V! 4       4      P!                  4       4      8H  pR;p;pp V'       d   V! \        V4      4      MRpT! V'       d	   V
! V4      MV! V4      V'       d   RM
\        V4      R7      pV'       d   T! YÍ'       d   V! 4       MRR7      MRpRx € Ve	   V	! V4       Ve	   V! V4       Ve   V! V4       R# R#   Te	   T	! T4       Te	   T! T4       Te
   T! T4       i i ; i5i)uÝ  Bind an assigned profile's runtime scope (secrets + terminal policy, optionally home) for
one dispatch-side read or spawn-env build.

The dispatcher runs detached from any turn, so nothing binds a profile for it: ``load_config``,
the toolset probes' ``get_secret`` reads and ``build_subprocess_env``'s passthrough resolution
all fall back to the LAUNCH profile's ambient ``os.environ`` / ``TERMINAL_*``. Binding was
previously conditional on ``is_multiplex_active()``, so on a single-profile host a worker for
profile B was built entirely from the dispatcher's own environment.

``bind_home=False`` for the spawn-env build: which variables may cross into a child is the
DISPATCHER's ``terminal.env_passthrough`` policy (#109494, read through the home override) â€”
only their VALUES come from the assignee's scope, so that branch binds the secret scope alone.
Toolset resolution binds the home and the terminal policy, as it always has.

The secret mapping is never widened: a profile that is not this process's own home gets its own
``.env`` + external sources ONLY, while the launch home keeps its established
env-over-``.env`` precedence (``launch_secret_scope``) so systemd / ``op run`` injection still
resolves for a standalone dispatcher.
)Úbuild_profile_secret_scopeÚreset_secret_scopeÚset_secret_scope)Úget_process_hermes_homeÚreset_hermes_home_overrideÚset_hermes_home_override)Úinstall_profile_terminal_scopeÚreset_terminal_scope)Úlaunch_secret_scopeÚlaunch_terminal_envN)Úprofile_home)Úenv_overlay)Úagent.secret_scoperU  rV  rW  Úhermes_constantsrX  rY  rZ  Útools.terminal_scoper[  r\  Ú!tui_gateway.launch_profile_policyr]  r^  r   r7   r‡  )rS  rQ  rU  rV  rW  rX  rY  rZ  r[  r\  r]  r^  ÚhomeÚis_launch_homeÚ
home_tokenÚsecret_tokenÚterminal_tokens   &$               r2   Ú_worker_profile_scoperj  %
  s,  é € ÷* dÑcßnÑnßYßZä�Ó€DÜ˜Ÿ™›Ó(¬C´Ñ5LÓ5NÓ0O×0WÑ0WÓ0YÓ,ZÑZ€NØ15Ð5€JÐ5� ð3ß<EÑ-¬c°$«iÔ8È4ˆ
Ù'ß)7Ñ Ô%Ñ=WÐX\Ó=]ß!/™´S¸³Yô@ˆ÷ U^ñ 8Ø¶~Ñ1Ô3È4õQØcgð 	ãàÒ%Ù  Ô0ØÒ#Ù˜|Ô,ØÒ!Ù& zÖ2ñ "øð	 Ò%Ù  Ô0ØÒ#Ù˜|Ô,ØÒ!Ù& zÕ2ð "üs0   ‚A7E Á:$D ÂD Â6D ÃD Ã.(E Ä'D=Ä=E c               ó    € V ^8„  d   QhRRRR/# )r4   rS  r&   r6   zOptional[list[str]]r(   )r8   s   "r2   r9   r9   S
  s   € ÷ ñ ¨mð Ð@Sñ r1   c                óL  € V '       g   R#  ^ RI Hp ^ RIHp \	        V 4      ;_uu_ 4        V! 4       p\        V! VR4      4      pRRR4       X;'       g    R#   + '       g   i     L; i  \         d-   p\        P                  P                  RT T4        Rp?R# Rp?ii ; i)a�  Return the assigned profile's effective CLI toolsets for a worker.

Resolved at dispatch time and passed as an explicit ``--toolsets`` pin so
worker startup cannot fall back to a stale root/active-profile config or a
profile whose top-level ``toolsets`` is only the kanban orchestrator
surface. ``model_tools`` still appends the task-scoped kanban lifecycle
tools when ``HERMES_KANBAN_TASK`` is set.
Nr]  )Ú_get_platform_toolsÚclizEkanban worker: could not resolve CLI toolsets for HERMES_HOME=%r (%s))
r`  r^  Úhermes_cli.tools_configrm  rj  r?   r‰   rz   r  r  )rS  r^  rm  ÚcfgÚtoolsetsrE  s   &     r2   Ú_resolve_worker_cli_toolsetsrr  S
  s†   € ÷ ÙðÝ1Ý?ä" ;×/Õ/Ù“-ˆCÜÑ1°#°uÓ=Ó>ˆH÷ 0ð ×Ð˜4Ð÷ 0×/ûô ô Ü�‰�‰ØSØØô	
õ
 ûðús4   ŒA, «AÁA, ÁA, ÁA)	Á$A, Á,B#Á7!BÂB#zset[str]Ú_retagged_workspace_rootsc               ó    € V ^8„  d   QhRRRR/# )r4   Úworkspaces_root_pathr7   r6   rU   r(   )r8   s   "r2   r9   r9   q
  s   € ÷ Pñ P¸ð PÀñ Pr1   c                ó*  € V \         9   d   R#  ^ RIHpHp V! 4       p VP	                  V 4       V! V4       \         P                  V 4       R#   T! T4       i ; i  \         d,   p\        P                  P                  RT4        Rp?R# Rp?ii ; i)a  Reclaim pre-tag worker rows in state.db so they leave the session lists.

Best-effort: the durable gate is ``state_meta`` in
``retag_kanban_worker_sessions``; the in-process set avoids reopening
state.db on every spawn. A tick must never fail because a session DB was
busy or missing.
N)ÚacquireÚrelease_or_closez0kanban worker: legacy session retag skipped (%s))
rs  Úhermes_state_registryrw  rx  Úretag_kanban_worker_sessionsrD  r‰   rz   r  r  )ru  rw  rx  ÚdbrE  s   &    r2   Ú_retag_legacy_worker_sessionsr|  q
  s€   € ð Ô8Ô8ÙðPßCñ ‹Yˆð	!Ø×+Ñ+Ð,@ÔAá˜RÔ Ü!×%Ñ%Ð&:Ö;øñ ˜RÕ ûäô PÜ�‰�‰ÐIÈ3×OÒOûðPús-   �A ŸA °A Á
AÁA ÁBÁ' BÂBc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r4   rÖ  r   Úprofile_argr7   rS  r&   r6   r   r(   )r8   s   "r2   r9   r9   Š
  s.   € ÷ Eñ E�tð E¨#ð E¸Mð EÈiñ Er1   c                ó(   € ^ RI pVP                  RR.# )zGThe profile owner executes; this subprocess only waits for its receipt.Nr  zhermes_cli.kanban_worker_clientr  )rÖ  r~  rS  r­   s   &&& r2   Ú_worker_argvr€  Š
  s   € ãØ�N‰N˜DÐ"CÐDÐDr1   c               ó    € V ^8„  d   QhRRRR/# )r4   rÖ  r   r€   r&   r(   )r8   s   "r2   r9   r9   �
  s   € ÷ 
 ñ 
 ˜4ð 
 ¨ñ 
 r1   c                óÆ   € \         P                  ! VR7      pVP                  RRR7       W P                   R2,          p^ RIHpHp V! V.V! 4       O5!   \        VR4      # )zåAppend-mode per-task log (a re-run on unblock appends, never overwrites),
rotated first. Anchored at the board root (not the shared kanban root) so
`hermes kanban log` reads its own file and boards sharing task ids don't
collide.rš  T)ÚparentsÚexist_okz.log)Úrotate_worker_logÚworker_log_rotation_configÚab)rz   Úworker_logs_dirÚmkdirrú   Úhermes_cli.kanban_worker_logr…  r†  r¯   )rÖ  r€   Úlog_dirÚlog_pathr…  r†  s   &&    r2   Ú_open_worker_logr�  �
  sX   € ô
 ×!Ò!¨Ô.€GØ‡M�M˜$¨€MÔ.ØŸG™G˜9 DÐ)Õ)€HßZÙ�hÐ>Ñ!;Ó!=Ô>Ü�˜$ÓÐr1   c               ó$   € V ^8„  d   QhRRRRRR/# )r4   rÖ  r   r#  r   r6   r(   )r8   s   "r2   r9   r9   �
  s!   € ÷ #ñ # Dð #°9ð #Àñ #r1   c                óú   € ^ RI Hp V P                  f9   V! VRV P                   R2RRR7      pVP                  R8w  d   \        R4      hV# V! VRV P                   RV P                   2RRR7      P                  # )	a:  Wrap a systemd-hosted dispatcher's worker in the shared restart-safe scope.

Kanban workers are long-lived agentic runs that outlive the dispatcher
tick, so they never take cron's degraded mode under the managed gateway:
``require_restart_safe_scope=True`` makes the helper raise
``RestartSafeScopeUnavailable`` there (an infrastructure spawn failure the
dispatcher does not charge to the card). Under any other systemd unit
(``Type=oneshot`` dispatch timers, #113612) ``outlives_parent=True`` gets the
worker its own scope so the unit's cgroup teardown cannot kill it.
)Úrestart_safe_gateway_child_argvzkanban-z-run-missingT)Úunit_suffixÚrequire_restart_safe_scopeÚoutlives_parentÚ
in_processzbcannot create restart-safe systemd scope for Kanban worker: the claimed task has no current run idz-run-)r×  r�  rÒ  rú   ÚmodeÚRuntimeErrorÚargv)rÖ  r#  r�  Údispatchs   &&  r2   Ú_restart_safe_worker_argvr™  �
  s˜   € õ Gà×ÑÒ"ñ 3ØØ! $§'¡' ¨,Ð7Ø'+Ø ô	
ˆð �=‰=˜LÔ(Üð9óð ð ˆá*ØØ˜dŸg™g˜Y e¨D×,?Ñ,?Ð+@ÐAØ#'Øô	÷
 �dðr1   c               ó(   € V ^8„  d   QhRRRRRRRR/# r´  r(   )r8   s   "r2   r9   r9   Ã
  s/   € ÷ Pñ P˜ð P¨#ð P¸ð PÐR_ñ Pr1   c               ó`	  € V P                   '       g   \        RV P                   R24      h^ RIHpHp V! V P                   4      p^ RIHp ^ RIH	pH
pHp	  V! V4      p
\        V
4      ;'       d	    V! V
4      pV
'       d   \        V
RR7      M\        P                   ! 4       ;_uu_ 4        T! V! 4       ;'       g    TR	R
7      pRRR4       ^ RIHp V F  pXP'                  VR4       K  	  V
'       d   V
XR&   V	! WÊ4       V P(                  '       d   V P(                  XR&   V P                  XR&   WR&   RVR&   V'       dO   \*        P,                  P/                  V4      '       d*   \*        P,                  P1                  V4      '       d   WR&   V P2                  '       d   V P2                  VR&   V P4                  e   \7        V P4                  4      VR&   V P8                  '       d   V P8                  VR&   V P:                  '       d5   RVR&   V P<                  e"   \7        \?        V P<                  4      4      VR&   R$ F2  p\A        V PB                  VPE                  V4      4      pVf   K.  VWÏ&   K4  	  \7        \F        PH                  ! VR7      4      VR&   \7        \F        PJ                  ! VR7      4      VR&   \F        PL                  ! V4      ;'       g    \F        PN                  ! 4       TR&   W\R&   ^ RI(H)p VP'                  VR4       VP'                  RR4       \U        WVPE                  R4      4      p^ R I+H,p V! V\[        \\        4      P_                  4       P`                  P`                  4      p\c        V V4      p^ R!I2H3p V! V4      p\i        W4      p \j        Pl                  ! T\*        P,                  P1                  V4      '       d   TMR\j        Pn                  T\j        Pp                  TR	\F        Pr                  '       d   \j        Pt                  M^ R"7      p\F        Pr                  '       d   T\z        TP|                  &   TP|                  #   \         d    Rp
 ELi ; i  + '       g   i     ELµ; i  \         d    TPw                  4        \y        R#4      hi ; i)%aœ  Fire-and-forget ``hermes -p <profile> chat -q ...`` subprocess.

Returns the child's PID so the dispatcher can detect crashes before the
claim TTL expires; completion is still observed via the worker's own
``complete`` / ``block`` transitions. ``board`` pins the child's
``HERMES_KANBAN_DB`` / ``HERMES_KANBAN_BOARD`` / workspaces_root to the
board the task was claimed from, so workers cannot see other boards.
ztask z has no assignee)r+  Úresolve_profile_env)Úis_multiplex_active)Ú_is_routed_homeÚbuild_subprocess_envÚstrip_launch_profile_envNF)rQ  T)Úscrub_secretsÚinherit_profile_home)Ú_VAR_MAPÚHERMES_HOMEÚHERMES_TENANTÚHERMES_KANBAN_TASKÚHERMES_KANBAN_WORKSPACEr<  ÚHERMES_SESSION_SOURCEÚTERMINAL_CWDÚHERMES_KANBAN_BRANCHÚHERMES_KANBAN_RUN_IDÚHERMES_KANBAN_CLAIM_LOCKÚ1ÚHERMES_KANBAN_GOAL_MODEÚHERMES_KANBAN_GOAL_MAX_TURNSrš  ÚHERMES_KANBAN_DBÚHERMES_KANBAN_WORKSPACES_ROOTÚHERMES_KANBAN_BOARDÚHERMES_PROFILE)ÚDELEGATED_CHILD_ENV_MARKERÚ
HERMES_TUI)Úpin_hermes_tree_on_pythonpath)Úsystemd_user_bus_env)ÚcwdÚstdinr¦   r§   ÚenvÚstart_new_sessionÚcreationflagszv`hermes` executable not found on PATH. Install Hermes Agent or activate its venv before running the kanban dispatcher.)ÚTERMINAL_TIMEOUTÚTERMINAL_MAX_FOREGROUND_TIMEOUT)?r­  rÓ   rú   r5  r+  rœ  ra  r�  Útools.environments.localrž  rŸ  r   r²   r$   rj  rE  ÚnullcontextÚgateway.session_contextr£  r`   Útenantr’   r’  r  ÚisdirrÑ  rÒ  r7   ré   Ú	goal_modeÚgoal_max_turnsr   rP  r3  r=   rz   r…  Úworkspaces_rootÚ_normalize_board_slugrË  Úagent.delegation_contextr´  r€  Úcron.scheduler_worker_envr¶  r   Ú__file__r‡  Úparentr™  r×  r·  r�  rµ   ÚPopenr¸   ÚSTDOUTr�   ÚCREATE_NO_WINDOWr�  r–  rM   rS   )rÖ  rµ  r€   r+  rœ  r~  r�  rž  rŸ  r   r_  Úroutedrº  r£  r\   ÚvarÚoverrider´  Úcmdr¶  r·  Úlog_fr—   s   &&$                    r2   rÕ  rÕ  Ã
  sâ  € ð �=�=ˆ=Ü˜5 §¡ 	Ð)9Ð:Ó;Ð;çOá(¨¯©Ó7€Kå6ßhÑhðÙ*¨;Ó7ˆô �,Ó×AÐA¡O°LÓ$A€F÷ COÔ
 ¸Õ
>Ü×%Ò%Ó'÷(ñ (á"Ù-Ó/×9Ð9°6Ø!%ô
ˆ÷(õ 1ÛˆØ�‰��TÖñ ÷ Ø)ˆˆMÑñ 	! Ô3Ø‡{‡{€{Ø#Ÿ{™{ˆˆOÑØ $§¡€CÐÑØ%.Ð!Ñ"ð $,€CÐÑ ÷ ”R—W‘W—]‘] 9×-Ò-´"·'±'·-±-À	×2JÒ2JØ'ˆNÑØ××ÐØ&*×&6Ñ&6ˆÐ"Ñ#Ø×ÑÒ&Ü&)¨$×*=Ñ*=Ó&>ˆÐ"Ñ#Ø‡‡€Ø*.¯/©/ˆÐ&Ñ'ð ‡~‡~€~Ø),ˆÐ%Ñ&Ø×ÑÒ*Ü25´c¸$×:MÑ:MÓ6NÓ2OˆCÐ.Ñ/ÛFˆÜ/°×0HÑ0HÈ#Ï'É'ÐRUË,ÓWˆØÔØˆC‹Hñ Gô "¤#×"4Ò"4¸5Ô"AÓB€CÐÑÜ+.¬s×/BÒ/BÈÔ/OÓ+P€CÐ'Ñ(ô "%×!:Ò!:¸5Ó!A×!\Ð!\ÄS×EZÒEZÓE\€CÐÑð (ÐÑåCØ‡G�GÐ&¨Ô-ð ‡G�GˆL˜$Ôä
�t¨#¯'©'°-Ó*@Ó
A€Cõ HÙ
'¨¬T´(«^×-CÑ-CÓ-E×-LÑ-L×-SÑ-SÓ
T€Cô $ D¨#Ó
.€CÝ;Ù
˜sÓ
#€CÜ˜TÓ)€Eð
Ü×ÒØÜŸW™WŸ]™]¨9×5Ò5‘	¸4Ü×$Ñ$ØÜ×$Ñ$ØØ"Ü9<¿¿¸œ*×5Ò5Èaô	
ˆô$ ‡‡€Ø'+Ô˜4Ÿ8™8Ñ$Ø�8‰8€Oøôw ô ð ‹ðú÷(÷ (ð (ûôH ô 
Ø�‰ŒÜð^ó
ð 	
ð
ús1   ÁQ Â,Q2Î"A/R ÐR ÑQ/Ñ.Q/Ñ2R	Ò'R-Úintervalg      N@Ú
stop_eventÚon_tickc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r4   rÔ  ÚfloatrŸ  r�   r·  r   r6   rU   r(   )r8   s   "r2   r9   r9   Z  s2   € ÷ 4*ñ 4*àð4*ð ð4*ð ð	4*ð 
ñ4*r1   c           	     ó”  a€ ^ RI pSf   VP                  4       oV3R lpVP                  4       VP                  4       J dg   R F`  p\	        \
        VR4      pVf   K  \        P                  ! \        \        4      ;_uu_ 4        \
        P
                  ! W†4       RRR4       Kb  	  SP                  4       '       g«    \        \        4       4      p	\        P                  ! \        P                  ! 4       4      ;_uu_ 4       p
\!        V
VV	VR7      pRRR4       Ve5   \        P                  ! \"        4      ;_uu_ 4        V! X4       RRR4       M SP)                  V R7       KÀ  R#   + '       g   i     EK8  ; i  + '       g   i     Lt; i  + '       g   i     LN; i  \"         d    ^ RIpTP'                  4         Lri ; i)uŠ  Run the dispatcher in a loop until interrupted.

Calls :func:`dispatch_once` every ``interval`` seconds; exits cleanly on
SIGINT / SIGTERM so it is systemd-friendly. ``stop_event`` and ``on_tick``
are test hooks. Each tick resolves ``kanban.max_in_progress`` exactly like
the gateway dispatcher and ``hermes kanban dispatch`` â€” the standalone
daemon must not be the one uncapped entry point.
Nc                ó(   <€ SP                  4        R # rô  )rC  )Ú_signumÚ_framerÕ  s   &&€r2   Ú_handleÚrun_daemon.<locals>._handleo  s   ø€ Ø�‰Ör1   )rŸ  r{  r·  )rª   )ÚSIGINTrå   )Ú	threadingÚEventÚcurrent_threadÚmain_threadrã   rä   rE  rF  rÓ   r´   Úis_setrw  r}  Úclosingr‹  rŒ  r²  r‰   Ú	tracebackÚ	print_excÚwait)rÔ  rŸ  r·  rÕ  rÖ  rà  rÝ  Úsig_namerº  r{  r÷   rE   ræ  s   $$$d$        r2   Ú
run_daemonrê  Z  sV  ø€ ó  àÒØ—_‘_Ó&ˆ
õð
 ×ÑÓ! Y×%:Ñ%:Ó%<Ó<Û-ˆHÜœ& (¨DÓ1ˆCØŒÜ×(Ò(¬´W×=Õ=Ü—M’M #Ô/÷ >Ñ=ñ .ð ×Ñ×!Ò!ð	"ô 6Ô6PÓ6RÓSˆOÜ×#Ò#¤D§L¢L£N×3Ô3°tÜ#ØØ'Ø$3Ø"/ô	�÷ 4ð Ò"Ü×(Ò(¬×3Õ3Ù˜C”L÷ 4Ð3ð #ð 	�‰ ˆÖ)ñ' "÷ >×=Ñ=ú÷ 4×3ú÷ 4×3ûäô 	"ãØ×ÑÖ!ð	"úsU   ÂE*Â?AF% ÄE?Ä.F% Å	FÅ
F% Å*E<Å?F	Æ
F% ÆF"	ÆF% Æ"F% Æ%GÇG)Ú	kanban_db)Úkanban_db_connect)Úkanban_db_workspacerô  )r†   )˜Ú__conditional_annotations__r-   Ú
__future__r   rE  r’   rh  rä   Úsqlite3rµ   r­   r]   Údataclassesr   r   Úpathlibr   Útypingr   r   r   r	   r
   r   r   Úhermes_cli.quiet_single_queryr   Úhermes_cli.kanban_dbr   rÜ  rL  rþ   ÚcompileÚ
IGNORECASEr  r  Ú#DEFAULT_RATE_LIMIT_COOLDOWN_SECONDSr  r  r   rK   r_   r^   rL   r.   rM   rQ   ri   rr   rn   ÚescapeÚ	MULTILINErŠ   r�   r™   r¾   rÊ   rÇ   rÍ   rË   rÚ   rß   rç   rô   r  r   r  r$  r,  rO  rW  r[  rc  rl  r»  rp  rs  r£  rƒ  rz  r}  r‡  r‰  rŸ  rœ  r¦  r±  rÃ  rÇ  rI  rá  ræ  ré  r  r  r
  r  r7  r6  rL  rQ  rW  rZ  ra  rm  rn  rp  rg  rs  rw  r}  r€  r•  r›  r²  r»  rÝ  rà  ræ  rï  ró  r÷  rú  r©  r  r  r  r  r!  r*  r6  r9  rH  rP  Úcontextmanagerrj  rr  rC  rs  r|  r€  r�  r™  rÕ  rê  r=  rë  rz   rì  r‹  rí  rÌ  )rî  s   @r2   Ú<module>rü     s  øðôõ #ã Û 	Û 	Û Û Û Û 
Û Ý !Ý Ý Ý Ý Ý Ý Ý Ý Ý  å DçÝ)ð
 Ð ð
 )+Ð %ð &)Ð "ð —j’jð"ð ‡M�MóÐ ð !%Ð ð '*Ð #ð !Ð àŸ:š:Ø4Ø‡M�MóÐ ð ÷>Lð >Ló ð>LõBðJ #&Ð ØÐ Ø57Ð Ð2Ó 7ð
 35Ð Ð/Ó 4õ)õ
1õ"õ."ð —:’:Øˆ2�9Š9Ð/Ó0Õ0°<Õ?ÀÇÁóÐ ÷
1õ õ</ðj !-Ð õ6õ.õõ*4÷õð:ð ð	:ð
 ÷:ðzÀ÷ õ<&õ8õUðB#ð ð	#ð
 &*÷#ðLc¸t÷ cðP  $Ð ðdð "#ðdð ÷	dõNDõNð %&Ð !ð "$Ð õðNð ð$ �jŠjÐSÓT€õ-õG÷
"ðB ÷Bð Bó ðBñ*ØRVðà÷ð ñ&DIð
 "ðDIð  ÷DIð DIðN ÷
Cð 
Có ð
C÷JõZM÷`.ðbxð !%ðxð ðxð  ðxð ðxð +/ðxð !÷xðvpÐPTð pÐae÷ põ,õ2
ðeØ;B÷eðP   ¨]¸LÈ/Ð[gÐhÐ ñØðØ/4ðØRV÷ð õ>õ õ8.õbWõ(@õ)õ*õ
ð& !Ð ð !"Ð Ø"#Ð õ÷]õ,õ'õ(÷""÷Jð&8ð ð8ð "&ð	8ð
 ð8ð  $ð8ð &*ð8ð /ð8ð "#ð8ð  ð8ð '+ð8ð 26ð8ð #÷8õv
)õ|õ~ð>Mð  ÷Mõ2:õzðð &*ð	ð
 59÷õ@ð$eð ðeð "&ð	eð
 ðeð  $ðeð &*ðeð /ðeð "#ðeð  ðeð '+ðeð 26ðeð #÷eðP4¸a÷ 4õ5õNõ	õ3õ
<õõ$)õ!!õHð: ×Òð*3Àö *3ó ð*3õZñ8 '*£eÐ ˜8Ó +õPõ2Eõ
 õ#ðLPÈ÷ Pðn4*àð4*ð  $ð4*ð /ð	4*ð
 ð4*ð ÷4*õr (Ý 0ß 2r1   