+
    jHàjÞB  ã                  ó  € R t ^ RIH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 ]'       d   ^ R	IHt R;t]]]]3tR
tR R ltR R ltR R ltRRRRRRRRRRRRRR/R R lltR R ltR<RRRR/R R  llltR<R!RRRRRR"RR#RRR/R$ R% llltRR/R& R' lltR(^/R) R* lltR+ R, lt RRR-R/R. R/ llt!RRR-R/R0 R1 llt"R2 R3 lt#RR/R4 R5 llt$RR/R6 R7 llt%RR/R8 R9 llt&^ R:I'H(t) R# )=am  Notification subscriptions consumed by the gateway kanban-notifier: per-(task, platform, chat, thread) rows with delivery metadata, unseen-event cursors and purge of stale done-task subs.

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)ÚPath)ÚAny)ÚIterable)ÚMapping)ÚOptional)ÚTYPE_CHECKING)ÚEventzDWHERE task_id = ? AND platform = ? AND chat_id = ? AND thread_id = ?c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	é   Útask_idÚstrÚplatformÚchat_idÚ	thread_idúOptional[str]ÚreturnÚtuple© )Úformats   "Ú@/home/ubuntu/.hermes/hermes-agent/hermes_cli/kanban_db_notify.pyÚ__annotate__r   #   s0   € ÷ 9ñ 9�cð 9 Sð 9°3ð 9À=ð 9ÐUZñ 9ó    c                ó   € YY#;'       g    R 3# )Ú r   )r   r   r   r   s   &&&&r   Ú_sub_keyr   #   s   € Ø˜w¯¨°RÐ8Ð8r   c               ó    € V ^8„  d   QhRRRR/# )r   ÚmetadataúOptional[Mapping[str, Any]]r   r   r   )r   s   "r   r   r   '   s   € ÷ Dñ DÐ/Jð DÈ}ñ Dr   c                ó  € \        V \        4      '       g   R# V P                  4        UUu/ uF/  w  rVf   K  \        V\        4      '       g   K#  \	        V4      VbK1  	  pppV'       g   R# \
        P                  ! VRRR7      # u uppi )zFSerialize platform send metadata stored on notification subscriptions.NT)Ú	sort_keysÚ
separators)Ú,Ú:)Ú
isinstancer   ÚitemsÚ_SCALAR_TYPESr   ÚjsonÚdumps)r   ÚkeyÚvalueÚcleans   &   r   Ú _encode_notify_delivery_metadatar,   '   sy   € ä�h¤×(Ò(Ùð #Ÿ.™.Ô*ôá*‰JˆCØô 	ä!+¨E´=×!Aô 	ŒˆC‹�%ŠÙ*ð 
ñ ÷
 ÙÜ�:Š:�e t¸
ÔCÐCùós   ¬B¹BÁBc               ó    € V ^8„  d   QhRRRR/# )r   Úrawr   r   zdict[str, Any]r   )r   s   "r   r   r   5   s   € ÷ `ñ `¨#ð `°.ñ `r   c                ó†  € \        V \        4      '       d   \        V 4      # V '       g   / #  \        P                  ! \        V 4      4      p\        T\        4      '       g   / # TP                  4        UUu/ uF)  w  r#\        T\        4      '       g   K  \        T4      TbK+  	  upp#   \         d    / u # i ; iu uppi ©N)	r$   r   Údictr'   Úloadsr   Ú	Exceptionr%   r&   )r.   Údatar)   r*   s   &   r   Ú _decode_notify_delivery_metadatar5   5   s˜   € Ü�#”w×ÒÜ�C‹yÐßØˆ	ðÜ�zŠzœ#˜c›(Ó#ˆô �dœD×!Ò!Øˆ	Ø.2¯j©j¬lÔ_©l¡
 ¼jÈÔP]×>^ŒOŒC�‹H�eŠO©lÒ_Ð_øô	 ô ØŠ	ðüó `s   ­B* Á7B=ÂB=Â*B:Â9B:r   Úuser_idÚuser_id_altÚ	chat_typeÚnotifier_profileÚdelivery_modeÚdelivery_metadatac               ó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   Úconnúsqlite3.Connectionr   r   r   r   r   r   r6   r7   r8   r9   r:   r;   r   r   ÚNoner   )r   s   "r   r   r   C   s�   € ÷ Jñ JØ
ðJð ðJð ð	Jð
 ðJð ðJð ðJð ðJð ðJð $ðJð !ðJð 3ðJð 
ñJr   c       
   
     óN  € V	\         9   d   T	MRpT;'       g    VR8X  d   RMRp\        WW44      p\        P                  ! V 4      ;_uu_ 4        V P	                  R\
        ,           V4      P                  4       pV'       d   \        VR,          4      M/ p\        V4      pV
'       d   VP                  V
4       V'       d   \        V4      MRpT P	                  R. VOVNVNT;'       g    RNVNVNVN\        \        P                  ! 4       4      NVN54       R	VR
3RVR3RVR3RVR3RVR
3RVR
33 FP  w  pppV'       g   K  V'       d
   RV RV R2MRpV P	                  RV R2\
        ,           V,           V.VO54       KR  	  RRR4       R#   + '       g   i     R# ; i)aæ  Register a gateway source wanting terminal-state notifications for
``task_id``; idempotent on (task, platform, chat, thread).

``user_id_alt`` (Signal UUID, Feishu union_id, ...) and ``chat_type`` are
replayed on active wake: ``build_session_key`` prefers the alt id, so
omitting it would key the wake into a different session. ``None`` keeps an
existing row's value. ``delivery_mode``: ``None`` leaves an existing row
untouched, an explicit valid value is last-write-wins, unknown falls back
to ``"notify"``. ``delivery_metadata`` merges supplied routing anchors
into an existing row so re-subscribing never discards them. New subs start
caught up (``last_event_id`` =
``MAX(task_events.id)``) so the notifier never replays history at boot.
NÚ
api_serverúnotify+wakeÚnotifyz1SELECT delivery_metadata FROM kanban_notify_subs r;   a—  
            INSERT OR IGNORE INTO kanban_notify_subs
                (task_id, platform, chat_id, thread_id, user_id, user_id_alt,
                 chat_type, notifier_profile, delivery_mode, delivery_metadata,
                 created_at, last_event_id)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
                    COALESCE((SELECT MAX(id) FROM task_events WHERE task_id = ?), 0))
            Údmr8   Fr6   Tr7   r9   r:   z AND (z IS NULL OR z = '')r   zUPDATE kanban_notify_subs SET z = ? )Ú_NOTIFY_DELIVERY_MODESr   Ú_kbÚ	write_txnÚexecuteÚ_SUB_KEY_WHEREÚfetchoner5   r1   Úupdater,   ÚintÚtime)r=   r   r   r   r   r6   r7   r8   r9   r:   r;   Ú
valid_modeÚinsert_moder)   ÚexistingÚexisting_metadataÚmerged_metadataÚmetadata_jsonÚcolumnr*   Ú	fill_onlyÚguards   &$$$$$$$$$$           r   Úadd_notify_subrW   C   sÓ  € ð6 #0Ô3IÔ"I‘Èt€Jð ×YÐY°(¸lÔ2J¡ÐPX€KÜ
�7 gÓ
9€CÜ	�Š�t×	Õ	Ø—<‘<Ø?Ä.ÕPØó
÷ ‰(‹*ð 	÷ `hÔ<¸XÐFYÕ=ZÔ[ÐmoÐÜÐ0Ó1ˆßØ×"Ñ"Ð#4Ô5ßM\Ô8¸ÔIÐbfˆØ�‰ððØðØðØ*ðØ,5×,=Ð,=¸ðØ?Oðàðà*ðä,/´·	²	³Ó,<ðà>Eñô	
ð$ ˜) UÐ+Ø˜ Ð&Ø˜K¨Ð.ØÐ!1°4Ð8Ø˜j¨%Ð0Ø  -°Ð7ó)
Ñ$ˆF�E˜9÷ ÙßCL�f˜V˜H L°°¸Ñ?ÐRTˆEØ�L‰LØ0°°¸Ð>ÄÕOÐRWÕWØ�˜‘öñ)
÷7 
×	×	Ò	ús    ÁA3FÃ(FÃ,A!FÅ;FÆF$	c               ó$   € V ^8„  d   QhRRRRRR/# )r   Únotifier_profilesúOptional[Iterable[str]]Úinclude_unownedÚboolr   ztuple[str, list[str]]r   )r   s   "r   r   r   �   s(   € ÷ 6ñ 6Ø.ð6ð ð6ð ñ	6r   c               óò  € V f   R. 3# \        V  Uu0 uF=  p\        V4      P                  4       '       g   K$  \        V4      P                  4       kK?  	  up4      p. p. pV'       dG   VP                  RRP	                  R V 4       4      ,           R,           4       VP                  V4       V'       d   VP                  R4       V'       g   R. 3# RR	P	                  V4      ,           R,           V3# u upi )
zCBuild an optional SQL predicate for notification profile ownership.r   znotifier_profile IN (r"   c              3  ó&   "  € T F  pR x € K	  	  R# 5i)Ú?Nr   )Ú.0Ú_s   & r   Ú	<genexpr>Ú)_notify_profile_filter.<locals>.<genexpr>�   s   é € Ð9PÁxÀ!½#Ãxùs   ‚Ú)z1notifier_profile IS NULL OR notifier_profile = ''Ú0Ú(z) OR ()Úsortedr   ÚstripÚappendÚjoinÚextend)rY   r[   ÚpÚprofilesÚclausesÚparamss   &$    r   Ú_notify_profile_filterrp   �   sÇ   € ð Ò Ø�2ˆvˆäÑ/@ÓSÑ/@¨!ÄCÈÃFÇLÁLÇN”~”s˜1“v—|‘|–~Ñ/@ÑSÓT€HØ€GØ€FßØ�‰Ð.°·±Ñ9PÁxÓ9PÓ1PÕPÐSVÕVÔWØ�‰�hÔßØ�‰ÐJÔKßØ�BˆwˆØ�—‘˜wÓ'Õ'¨#Õ-¨vÐ5Ð5ùò Ts
   ’!C4¸C4rY   r[   Fc          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r=   r>   r   r   rY   rZ   r[   r\   r   z
list[dict]r   )r   s   "r   r   r   ¦   s:   € ÷ !ñ !Ø
ð!àð!ð /ð	!ð
 ð!ð ñ!r   c               óú  € \        W#R7      w  rE. p. pVe#   VP                  R4       VP                  V4       V'       d#   VP                  V4       VP                  V4       RpV'       d'   VRRP                  R V 4       4      ,           ,          p. p	V P	                  W‡4      P                  4        FC  p
\        V
4      pRV9   d   \        VP                  R4      4      VR&   V	P                  V4       KE  	  V	# )a  List subscriptions, optionally restricted to notifier profile owners.

No ``notifier_profiles`` -> all subscriptions. Gateway notifiers pass the
profiles they own so they cannot claim another gateway's events;
``include_unowned`` (dispatch owner) covers legacy rows without a stamp.
©r[   ztask_id = ?z SELECT * FROM kanban_notify_subsú WHERE ú AND c              3  ó.   "  € T F  pR V R2x € K  	  R# 5i)rf   rd   Nr   )r`   Úclauses   & r   rb   Ú#list_notify_subs.<locals>.<genexpr>À   s   é € Ð'JÁE¸&¨!¨F¨8°1­ÃEùs   ‚r;   )	rp   ri   rk   rj   rH   Úfetchallr1   r5   Úget)r=   r   rY   r[   Úowner_whereÚowner_paramsÚwherero   ÚsqlÚoutÚrowÚitems   &&$$        r   Úlist_notify_subsr‚   ¦   sÞ   € ô !7Øô!Ñ€Kð €EØ€FØÒØ�‰�]Ô#Ø�‰�gÔßØ�‰�[Ô!Ø�‰�lÔ#Ø
,€CßØˆy˜7Ÿ<™<Ñ'JÁEÓ'JÓJÕJÕJˆØ€CØ�|‰|˜CÓ(×1Ñ1Ö3ˆÜ�C‹yˆØ $Ô&Ü(HÈÏÉÐReÓIfÓ(gˆDÐ$Ñ%Ø�
‰
�4Öñ	 4ð
 €Jr   Úboardr   r   c               ó8   € V ^8„  d   QhRRRRRRRRR	RR
RRRRR/# )r   Údb_pathzOptional[Path]rƒ   r   rY   rZ   r[   r\   r   r   r   r   rL   r   )r   s   "r   r   r   Ê   sZ   € ÷ 2ñ 2Øð2ð ð2ð /ð	2ð
 ð2ð ð2ð ð2ð ð2ð 	ñ2r   c               ó–  € V e   T M\         P                  ! VR7      pVP                  4       '       g   ^ # \        W#R7      w  r‰. p
. pV'       d'   V
P	                  RV R24       VP                  V	4       RV3RV3RV33 F-  w  rÍVf   K  V
P	                  V4       VP	                  V4       K/  	  R	pV
'       d    VR
RP                  V
4      ,           ,          p\        P                  ! VP                  4       P                  4       R,           RR7      p  VP                  Wë4      P                  4       pT'       d   \#        T^ ,          4      M^  TP!                  4        #   \        P                   d;   pR\        T4      P                  4       9   d    Rp?TP!                  4        ^ # h Rp?ii ; i  TP!                  4        i ; i)ué  Count ``kanban_notify_subs`` rows via a read-only connection â€” the
notifier's cheap zero-subscription early exit. Unlike :func:`connect` it
never creates the file, runs init/migration or opens writable; WAL rows are
still visible so a fresh sub is never missed. Missing DB / missing table
counts as zero; platform matches case-insensitively (as notifier routing),
chat/thread exactly. Raises :class:`sqlite3.Error` if the DB exists but is
unreadable â€” callers pick their own fallback.
N)rƒ   rs   rf   rd   zLOWER(platform) = LOWER(?)zchat_id = ?zthread_id = ?z'SELECT COUNT(*) FROM kanban_notify_subsrt   ru   z?mode=roT)Úurizno such table)rF   Úkanban_db_pathÚexistsrp   ri   rk   rj   Úsqlite3ÚconnectÚresolveÚas_urirH   rJ   ÚOperationalErrorr   ÚlowerÚcloserL   )r…   rƒ   rY   r[   r   r   r   Úpathr{   r|   rn   ro   rw   r*   Úqueryr=   r€   Úexcs   &$$$$$$           r   Úcount_notify_subsr”   Ê   s}  € ð$ Ò)‰7¬s×/AÒ/AÈÔ/N€DØ�;‰;�=Š=ÙÜ 6Øô!Ñ€Kð €GØ€FßØ�‰˜˜;˜- qÐ)Ô*Ø�‰�lÔ#à	% xÐ0Ø	˜Ð Ø	˜)Ð$ó‰ˆð
 ÔØ�N‰N˜6Ô"Ø�M‰M˜%Ö ñð 6€EßØ�˜WŸ\™\¨'Ó2Õ2Õ2ˆÜ�?Š?˜4Ÿ<™<›>×0Ñ0Ó2°ZÕ?ÀTÔJ€Dð	ð	Ø—,‘,˜uÓ-×6Ñ6Ó8ˆC÷
 "Œs�3�q•6Œ{ qÐ(à�
‰
�øô ×'Ñ'ô 	Ø¤# c£(§.¡.Ó"2Ô2Ûð 	�
‰
Žð ûð	ûð 	�
‰
�ús<   ÄE$ Ä7F6 Ä?F6 Å$F3Å9F.ÆF6 Æ-F.Æ.F3Æ3F6 Æ6Gc               ó0   € 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   s   "r   r   r   ÿ   sF   € ÷ ñ Ø
ðð ðð ð	ð
 ðð ðð 
ñr   c               óâ   € \         P                  ! V 4      ;_uu_ 4        V P                  R \        ,           \	        WW44      4      pRRR4       XP
                  ^ 8„  #   + '       g   i     L; i)zDELETE FROM kanban_notify_subs N)rF   rG   rH   rI   r   Úrowcount)r=   r   r   r   r   Úcurs   &$$$$ r   Úremove_notify_subr™   ÿ   sR   € ô 
�Š�t×	Õ	Ø�l‰lØ-´Õ>Ü�W¨Ó;ó
ˆ÷ 
ð
 �<‰<˜!ÑÐ÷ 
×	ús   Ÿ(AÁA.	Úmax_age_daysc               ó$   € V ^8„  d   QhRRRRRR/# )r   r=   r>   rš   rL   r   r   )r   s   "r   r   r     s#   € ÷ %"ñ %"Ð'9ð %"ÈCð %"ÐY\ñ %"r   c               óŽ  €  \        V4      pV^ 8:  d   ^ # \        \        P                  ! 4       4      VR,          ,
          p\        P
                  ! V 4      ;_uu_ 4        V P                  RV34      pRRR4       \        XP                  ;'       g    ^ 4      #   \        \        3 d    ^p L£i ; i  + '       g   i     LH; i)uŒ  Delete notify subs whose task sat in ``done``/``blocked`` untouched for
longer than ``max_age_days`` (``<= 0`` disables); returns rows deleted.

Subs survive ``done`` because a reopened task must still notify its origin,
which accumulates forever on never-archiving boards. ``blocked`` is
abandoned (unlike ``backlog``/``ready``) so it reaps on the same clock. Age
= latest event, else ``completed_at``, else ``created_at`` â€” any activity,
including a reopen, exempts the sub.

The notifier keeps subscriptions alive through ``done`` because a completed task can be reopened (review
corrections, continuation) and the reopened cycle must still notify its origin session. On boards that
never archive, that retention would otherwise accumulate subscription rows forever â€” each one scanned
every notifier tick. This GC bounds that: a task that has been ``done`` with no new events for the
retention window is treated as settled and its subscriptions are purged. ``blocked`` tasks
(circuit-breaker trips, dead workers) are reaped on the same clock â€” they are abandoned, not idle,
unlike a ``backlog``/``ready`` card that is merely waiting for pickup (#100955).
i€Q zïDELETE FROM kanban_notify_subs WHERE task_id IN ( SELECT t.id FROM tasks t WHERE t.status IN ('done', 'blocked') AND COALESCE(  (SELECT MAX(e.created_at) FROM task_events e   WHERE e.task_id = t.id),  t.completed_at, t.created_at, 0 ) < ?)N)rL   Ú	TypeErrorÚ
ValueErrorrM   rF   rG   rH   r—   )r=   rš   ÚdaysÚcutoffr˜   s   &$   r   Úpurge_stale_done_notify_subsr¡     s¡   € ð$Ü�<Ó ˆð ˆq„yÙÜ”—’“Ó  u¥Õ,€FÜ	�Š�t×	Õ	Ø�l‰lðð ˆIó

ˆ÷ 
ô ˆs�|‰|× Ð ˜qÓ!Ð!øô# ”zÐ"ô ØŠðú÷
 
×	ús   ‚B Á B4ÂB1Â0B1Â4C	c               ó0   € 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   zOptional[int]r   )r   s   "r   r   r   7  s<   € ÷ >ñ >Ø
ð>Ø'*ð>Ø69ð>ØDGð>ØTað>àñ>r   c           	     óœ   € V P                  R\        ,           \        WW44      4      P                  4       pVf   R# \	        VR,          4      # )zI``last_event_id`` of one subscription row, or ``None`` when unsubscribed.z-SELECT last_event_id FROM kanban_notify_subs NÚlast_event_id)rH   rI   r   rJ   rL   )r=   r   r   r   r   r€   s   &&&&& r   Ú_notify_cursorr¥   7  sK   € ð �,‰,Ø7¼.ÕHÜ� GÓ7ó÷ �hƒjð ð ’;ˆ4Ð=¤C¨¨OÕ(<Ó$=Ð=r   Úkindsc               ó4   € 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¦   rZ   r   ztuple[int, list[Event]]r   )r   s   "r   r   r   B  sP   € ÷ ñ Ø
ðð ðð ð	ð
 ðð ðð #ðð ñr   c               óð  € \        WW#V4      pVf   ^ . 3# V'       d   \        V4      MRpRV'       d0   RRP                  R\        V4      ,          4      ,           R,           MR,           R,           pW.p	V'       d   V	P	                  V4       V P                  W‰4      P                  4       p
V
 Uu. uF"  p\        P                  P                  V4      NK$  	  pp\        V.R	 V
 4       O4      pWÜ3# u upi )
z‘Return ``(new_cursor, events)`` with ``id > last_event_id``. The cursor
is NOT advanced here; call :func:`advance_notify_cursor` after delivery.
Nz7SELECT * FROM task_events WHERE task_id = ? AND id > ? zAND kind IN (r"   r_   z) r   zORDER BY id ASCc              3  óF   "  € T F  p\        VR ,          4      x € K  	  R# 5i)ÚidN)rL   )r`   Úrs   & r   rb   Ú(unseen_events_for_sub.<locals>.<genexpr>\  s   é € Ð7±$¨QœC  $¥ŸL˜L³$ùs   ‚!)r¥   Úlistrj   Úlenrk   rH   ry   rF   r	   Úfrom_rowÚmax)r=   r   r   r   r   r¦   ÚcursorÚ	kind_listÚqro   Úrowsr«   r   Úmax_ids   &$$$$$        r   Úunseen_events_for_subr¶   B  sß   € ô ˜D¨8¸iÓH€FØ‚~Ø�"ˆuˆß$”�U”¨$€IàAßFOˆ?˜SŸX™X c¬C°	«NÕ&:Ó;Õ;¸dÖBÐUWõ	Yà
õ	ð ð
 !Ð)€FßØ�‰�iÔ Ø�<‰<˜Ó"×+Ñ+Ó-€DÙ*.Ó
/©$ QŒ3�9‰9×Ñ˜aÖ ©$€CÐ
/Ü�&Ð8Ñ7±$Ó7Ð8Ó9€FØˆ;Ðùò 0s   Â1(C3c               ó4   € 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¦   rZ   r   ztuple[int, int, list[Event]]r   )r   s   "r   r   r   `  sP   € ÷ .ñ .Ø
ð.ð ð.ð ð	.ð
 ð.ð ð.ð #ð.ð "ñ.r   c               ó>  € \         P                  ! V 4      ;_uu_ 4        \        WW#V4      pVf   ^ ^ . 3uuRRR4       # \        WW#WER7      w  rxV'       g   Wf. 3uuRRR4       # \	        V \        WW44      Wv4       WgV3uuRRR4       #   + '       g   i     R# ; i)a½  Atomically claim unseen events for one subscription.

Returns ``(old_cursor, new_cursor, events)``; when events are returned the
row's ``last_event_id`` has already been advanced inside ``BEGIN IMMEDIATE``,
so concurrent gateway watchers on the same board DB serialize on SQLite's
writer lock and only the first claims a given event range. Callers send the
events, then leave the cursor or call :func:`rewind_notify_cursor` on
delivery failure.
N)r   r   r   r   r¦   )rF   rG   r¥   r¶   Ú_cas_cursorr   )	r=   r   r   r   r   r¦   Ú
old_cursorÚ
new_cursorÚeventss	   &$$$$$   r   Úclaim_unseen_events_for_subr½   `  s�   € ô$ 
�Š�t×	Õ	Ü# D°8ÀiÓPˆ
ØÒØ�a˜�8÷ 
Ò	ô 3Ø¨HØô
Ñˆ
÷ Ø¨2Ð-÷ 
Ò	ô 	�Dœ( 7°gÓIÈ:ÔbØ vÐ-÷ 
×	×	Ó	ús   ŸBÁ BÁ&BÂB	c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r   r=   r>   r)   r   r»   rL   Úexpectedr   zsqlite3.Cursorr   )r   s   "r   r   r   €  s2   € ÷ ñ Ð(ð ¨uð À#ð ÐQTð ÐYgñ r   c                óz   € V P                  R\        ,           R,           \        V4      .VO\        V4      N54      # )z<Move ``last_event_id`` only if it still equals ``expected``.ú0UPDATE kanban_notify_subs SET last_event_id = ? z AND last_event_id = ?)rH   rI   rL   )r=   r)   r»   r¿   s   &&&&r   r¹   r¹   €  s9   € à�<‰<Ø:¼^ÕKÐNfÕfÜ	ˆZ‹Ð.˜3Ð.¤ H£Ñ.óð r   c               ó4   € V ^8„  d   QhRRRRRRRRRRR	R
RR/# )r   r=   r>   r   r   r   r   r   r   r»   rL   r   r?   r   )r   s   "r   r   r   ˆ  sP   € ÷ 
ñ 
Ø
ð
ð ð
ð ð	
ð
 ð
ð ð
ð ð
ð 
ñ
r   c               óä   € \         P                  ! V 4      ;_uu_ 4        V P                  R \        ,           \	        V4      .\        WW44      O54       RRR4       R#   + '       g   i     R# ; i)rÁ   N©rF   rG   rH   rI   rL   r   )r=   r   r   r   r   r»   s   &$$$$$r   Úadvance_notify_cursorrÅ   ˆ  sL   € ô 
�Š�t×	Õ	Ø�‰Ø>ÄÕOÜ�‹_ÐOœx¨¸7ÓNÑOô	
÷ 
×	×	Ò	úó   Ÿ5AÁA/	c               ó4   € 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   Úevent_idrL   r   r?   r   )r   s   "r   r   r   ˜  sF   € ÷ 

ñ 

Ø
ð

Ø*-ð

Ø9<ð

ØGJð

àð

à/2ð

ð 
ñ

r   c               óä   € \         P                  ! V 4      ;_uu_ 4        V P                  R\        ,           \	        V4      .\        WW44      O54       RRR4       R#   + '       g   i     R# ; i)zBCheckpoint a sent ping independently of the retryable wake cursor.zNUPDATE kanban_notify_subs SET last_ping_event_id = MAX(last_ping_event_id, ?) NrÄ   )r=   r   r   r   r   rÈ   s   &$$$$$r   Úrecord_notify_pingrÊ   ˜  sO   € ô
 
�Š�t×	Õ	Ø�‰Ø\Üõä�‹]ÐMœX g¸ÓLÑMô	
÷ 
×	×	Ò	úrÆ   c               ó8   € V ^8„  d   QhRRRRRRRRRRR	R
RR
RR/# )r   r=   r>   r   r   r   r   r   r   Úclaimed_cursorrL   rº   r   r\   r   )r   s   "r   r   r   ¥  sZ   € ÷ ñ Ø
ðð ðð ð	ð
 ðð ðð ðð ðð 
ñr   c               óÂ   € \         P                  ! V 4      ;_uu_ 4        \        V \        WW44      We4      pRRR4       XP                  ^ 8„  #   + '       g   i     L; i)z�Undo a claim when delivery fails. The CAS guard only rewinds if no later
notifier advanced the row, so retries never clobber newer progress.
N)rF   rG   r¹   r   r—   )r=   r   r   r   r   rÌ   rº   r˜   s   &$$$$$$ r   Úrewind_notify_cursorrÎ   ¥  sF   € ô 
�Š�t×	Õ	Ü˜$¤¨¸GÓ OÐQ[Ólˆ÷ 
à�<‰<˜!ÑÐ÷ 
×	ús   ŸAÁA	)Ú	kanban_db)rC   rB   Úwaker0   )*Ú__doc__Ú
__future__r   r'   rŠ   rM   Úpathlibr   Útypingr   r   r   r   r   Úhermes_cli.kanban_dbr	   rE   r   rL   Úfloatr\   r&   rI   r   r,   r5   rW   rp   r‚   r”   r™   r¡   r¥   r¶   r½   r¹   rÅ   rÊ   rÎ   Ú
hermes_clirÏ   rF   r   r   r   Ú<module>rØ      s¨  ðñõ #ã Û Û Ý Ý Ý Ý Ý Ý  çÝ*ð
 ;Ð à�c˜5 $Ð'€ð X€õ9õDõ`ðJð  $ðJð "ðJð "&ðJð  $ðJð '+ðJð $(ðJð 6:÷JõZ6ñ,!ð 26ð	!ð
 "÷!ð !ñH2ð  ð2ð 26ð	2ð
 "ð2ð #ð2ð "ð2ð  $÷2ð 2ðjð  $÷ð %"ÐRT÷ %"õP>ðð  $ðð &*÷ð<.ð  $ð.ð &*÷.õ@ð
ð  $÷
ð 

à#÷

ðð  $÷÷( (r   