+
    jHàj~  ã                  ó  € 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
HtHtHtHt ^ RIHt ^ RIHt ^ RIHt ]P*                  ! ]4      t]P0                  ! 4       t^tRPtRQtRtRtR	tR
t ]!! R04      t"RRt#]$! ]#4      ]^,
          8X  g   Q hRt%Rt&RSt'Rt(]PR                  ! R]PT                  4      t+R R lt,R R lt-R R lt.]&3R R llt/R R lt0R R lt1R R lt2R R lt3R  t4R! R" lt5R# R$ lt6R% t7R& R' lt8R( R) lt9R* R+ lt:R, R- lt;R.R/R/ R0 llt<R.R/R1 R2 llt=R3 R4 lt>R5 R6 lt?RTR7 R8 llt@RTR9 R: lltARTR; R< lltBR=R>R?R>R@R/RA RB lltCRURCRRDR/RE RF llltDRURGR/RH RI llltERURJ RK lltFRL RM ltGRURN RO lltHR# )Va°  Durable delivery-obligation ledger for gateway final responses (rows in the shared ``state.db``;
WAL, owner pid + process-start liveness, bounded retention) so a crash between finalize and
platform ACK cannot lose a response silently. Checkpoints: record_obligation() 'pending' before
any send | mark_attempting() 'attempting' right before the await | mark_delivered() 'delivered'
only on SendResult.success | mark_failed() 'failed' on a definitive rejection. Crash semantics
(never silently resend an ambiguous send): pending = never started, redeliver plainly; attempting
= crashed mid-await, platform MAY have it, redeliver WITH a visible recovered marker; failed =
rejected once, restart is a retry boundary, also marked; delivered = prune. Attempts are capped
and stale rows expire, both -> 'abandoned' (kept briefly, then pruned). Everything is
best-effort: ledger failures must never block a send; callers wrap every call in try/except.
)ÚannotationsN)ÚAnyÚDictÚListÚOptional)Úclassify_dead_error)Úadd_column_if_missing©Úget_process_hermes_homeiô  u_   â™»ï¸� Recovered reply â€” the gateway restarted during delivery, so this may be a duplicate:

u   â™»ï¸� Recovered reply â€” the messaging platform reconnected after the original delivery failed, so this may be a duplicate:

u„   â™»ï¸� Recovered reply â€” the messaging platform's rate limit refused the original, so part of it may already have arrived above:

Úsend_path_degradedzflood_control:g      N@g       @z3flood control exceeded.*?retry in\s+(\d+(?:\.\d+)?)c               ó    € V ^8„  d   QhRRRR/# )é   ÚtextÚstrÚreturnúOptional[float]© )Úformats   "Ú</home/ubuntu/.hermes/hermes-agent/gateway/delivery_ledger.pyÚ__annotate__r   U   s   € ÷ ñ ˜#ð  /ñ ó    c                óº   € \         P                  T ;'       g    R4      pV'       g   R#  \        VP                  ^4      4      #   \        \
        3 d     R# i ; i)z\Seconds asked for by a flood error still carrying the platform's own wording, else ``None``.Ú N)Ú_RAW_FLOOD_REÚsearchÚfloatÚgroupÚ	TypeErrorÚ
ValueError)r   Úmatchs   & r   Ú_raw_flood_waitr    U   sM   € ä× Ñ  § ¨Ó,€EßÙðÜ�U—[‘[ “^Ó$Ð$øÜ”zÐ"ô Úðús   «A ÁAÁAc               ó    € V ^8„  d   QhRRRR/# ©r   Úerrorr   r   Úboolr   )r   s   "r   r   r   `   s   € ÷ Iñ I˜Sð I Tñ Ir   c                ór   € \        T ;'       g    R4      P                  4       P                  4       \        9   # )zOTrue for a row that only an adapter reconnect may retry (no timer, no backoff).r   )r   ÚstripÚlowerÚ_RUNTIME_RETRYABLE_ERRORS©r#   s   &r   Úis_reconnect_onlyr*   `   s+   € äˆu�{ˆ{˜Ó×!Ñ!Ó#×)Ñ)Ó+Ô/HÑHÐHr   c               ó    € V ^8„  d   QhRRRR/# r"   r   )r   s   "r   r   r   e   s   € ÷ Tñ T˜#ð T $ñ Tr   c                ó¸   € \        T ;'       g    R4      P                  4       P                  4       pVP                  \        4      ;'       g    \        V4      RJ# )z©True for a flood refusal: the adapters' fail-closed ``flood_control:<seconds>`` result, or a
row still carrying the platform's own flood wording (see ``_RAW_FLOOD_RE``).r   N)r   r&   r'   Ú
startswithÚFLOOD_ERROR_PREFIXr    )r#   r   s   & r   Úis_flood_errorr/   e   sH   € ô ˆu�{ˆ{˜Ó×!Ñ!Ó#×)Ñ)Ó+€DØ�?‰?Ô-Ó.×SÐS´/À$Ó2GÈtÐ2SÐSr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r#   r   Údefaultr   r   r   )r   s   "r   r   r   l   s"   € ÷ )ñ )˜cð )¨Eð )ÐTYñ )r   c                óR  € \        T ;'       g    R4      P                  4       P                  4       pTpVP                  \        4      '       d,    \        V\        \        4      R P                  4       4      pM\        V4      pVe   TpV^ 8”  d   V# T#   \         d    Tp Li ; i)zdThe wait the platform asked for, read from ``flood_control:<seconds>``; ``default`` when unreadable.r   N)	r   r&   r'   r-   r.   r   Úlenr   r    )r#   r1   r   ÚwaitÚraws   &&   r   Úflood_wait_secondsr6   l   sœ   € äˆu�{ˆ{˜Ó×!Ñ!Ó#×)Ñ)Ó+€DØ€DØ‡�Ô)×*Ò*ð	Ü˜œcÔ"4Ó5Ð6Ð7×=Ñ=Ó?Ó@‰Dô ˜dÓ#ˆØŠ?ØˆDØ˜!”8ˆ4Ð( Ð(øô ô 	ØŠDð	ús   Á)B ÂB&Â%B&c               ó    € V ^8„  d   QhRRRR/# )r   Úsecondsr   r   r   r   )r   s   "r   r   r   ~   s   € ÷ Tñ T˜sð T uñ Tr   c                óž   €  \        V 4      p\	        \        VR4      \        4      \        ,           #   \        \        3 d
    \        p L>i ; i)a  How long a redelivery timer sleeps for a wait of ``seconds``: capped so a huge or bogus value cannot
park the timer for hours (the row's own deadline, not the timer, decides eligibility when it fires),
plus a little slack so the timer lands after the deadline rather than on it.ç        )r   r   r   ÚFLOOD_RETRY_DEFAULT_SECONDSÚminÚmaxÚFLOOD_RETRY_CAP_SECONDSÚFLOOD_RETRY_SLACK_SECONDS)r8   r4   s   & r   Úflood_retry_delayr@   ~   sG   € ð+Ü�W‹~ˆô Œs�4˜‹~Ô6Ó7Ô:SÕSÐSøô ”zÐ"ô +Ü*Šð+ús   ‚2 ²AÁAc               ó    € V ^8„  d   QhRRRR/# )r   Ú
updated_atr   r   r   r   )r   s   "r   r   r   ‰   s   € ÷ ñ ˜cð  eñ r   c                ó^   €  \        T ;'       g    R 4      #   \        \        3 d     R # i ; i)r:   )r   r   r   )rB   s   &r   Ú_failed_stamprD   ‰   s1   € ðÜ�Z×&Ð& 3Ó'Ð'øÜ”zÐ"ô Úðús   ‚ � —,«,c               ó$   € V ^8„  d   QhRRRRRR/# )r   rB   r   Ú
last_errorr   r   r   )r   s   "r   r   r   �   s&   € ÷ Fñ F ð F°#ð F¸%ñ Fr   c                ó8   € \        V 4      \        V4      ,           # )a  Earliest moment a flood-refused row may be resent: the refusal's timestamp (``mark_failed`` sets
``updated_at``) plus the platform's wait. Enforced by the sweeps so neither an early timer nor a
reconnect sweep spends a redelivery attempt inside the penalty window.)rD   r6   )rB   rF   s   &&r   Úflood_not_beforerH   �   s   € ô ˜Ó$Ô'9¸*Ó'EÕEÐEr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rB   r   rF   Úattemptsr   r   r   )r   s   "r   r   r   —   s.   € ÷ Eñ E ð E°#ð EÀð EÈñ Er   c                ó†  € \        V4      '       d   \        W4      # \        T;'       g    R4      P                  4       P	                  4       p\        V4      '       d   \        V 4      # \        V4      '       d   R# \        T;'       g    ^ 4      pV\        ^,
          8¼  d   R# \        V 4      \        V,          ,           # )u  Earliest moment a failed row may be resent, or ``None`` for a row the runtime must leave alone:
a flood refusal keeps the platform's own wait, an allowlisted reconnect error is due at once, a
whole-chat death is final, and any other rejection backs off by the attempts already spent â€” but
never spends the LAST budgeted attempt. An unclassified outage can outlast any timer, and a row
the timer abandoned would be lost for good; leaving one attempt keeps it recoverable by the boot
sweep after a restart, which is a real recovery signal.r   N)r/   rH   r   r&   r'   r*   rD   r   ÚintÚMAX_ATTEMPTSÚ_RETRY_BACKOFF_SECONDS)rB   rF   rJ   r   Úspents   &&&  r   Úretry_not_beforerP   —   s˜   € ô �j×!Ò!Ü 
Ó7Ð7Üˆz×Ð˜RÓ ×&Ñ&Ó(×.Ñ.Ó0€DÜ˜×ÒÜ˜ZÓ(Ð(Ü˜4× Ò ÙÜ�—�˜AÓ€EØ”˜qÕ Ô ÙÜ˜Ó$Ô'=¸eÕ'DÕDÐDr   c                 ó$   € \        4       R ,          # )zstate.dbr	   r   r   r   Ú_db_pathrR   «   s   € ô
 #Ó$ zÕ1Ð1r   c               ó   € V ^8„  d   QhRR/# )r   r   úsqlite3.Connectionr   )r   s   "r   r   r   ³   s   € ÷ Dñ DÐ$ñ Dr   c                 ó@   € ^ RI Hp  V ! \        4       RRR\        R7      # )é    ©Úopen_dbzstate.db (delivery_ledger)i'  N)Údb_labelÚbusy_timeout_msÚrow_factoryÚ
initialize)Úhermes_cli.sqlite_utilrX   rR   Ú_initialize_schemarW   s    r   Ú_connectr_   ³   s&   € Ý.ñ ”8“:Ð(DÐV\Ø#Ô0BôDð Dr   c               ó    € V ^8„  d   QhRRRR/# )r   ÚconnrT   r   ÚNoner   )r   s   "r   r   r   ¼   s   € ÷ gñ gÐ/ð g°Dñ gr   c                óª   € V P                  R 4       RV P                  R4       Uu0 uF  q^,          kK  	  up9  d   \        V RRR4       R# R# u upi )a5  CREATE TABLE IF NOT EXISTS delivery_obligations (
            obligation_id TEXT PRIMARY KEY,
            session_key TEXT NOT NULL,
            platform TEXT NOT NULL,
            chat_id TEXT NOT NULL,
            thread_id TEXT,
            content TEXT NOT NULL,
            state TEXT NOT NULL,
            attempts INTEGER NOT NULL DEFAULT 0,
            created_at REAL NOT NULL,
            updated_at REAL NOT NULL,
            owner_pid INTEGER,
            owner_started_at INTEGER,
            last_error TEXT,
            adapter_profile TEXT
        )Úadapter_profilez'PRAGMA table_info(delivery_obligations)Údelivery_obligationszadapter_profile TEXTN)Úexecuter   )ra   Úrows   & r   r^   r^   ¼   sV   € Ø‡L�Lð	ôð$ °4·<±<Ð@iÔ3jÓ kÑ3j¨C Q§ Ñ3jÑ kÔkÜ˜dÐ$:Ð<MÐOeÖfñ lùÒ ks   ¦Ac                 ó.   € ^ RI Hp  V ! \        4       4      # )rV   ©Útransaction)r]   rj   r_   ri   s    r   Ú_transactionrk   Ó   s   € Ý2á”x“zÓ"Ð"r   c               ó    € V ^8„  d   QhRRRR/# )r   ÚpidrL   r   zOptional[int]r   )r   s   "r   r   r   Ù   s   € ÷ ñ �Sð ˜]ñ r   c                óD   €  ^ RI Hp V! V 4      #   \         d     R# i ; i)rV   )Úget_process_start_timeN)Úgateway.statusro   Ú	Exception)rm   ro   s   & r   Ú_start_timerr   Ù   s'   € ðÝ9Ù% cÓ*Ð*øÜô Úðús   ‚ �žc               ó   € V ^8„  d   QhRR/# )r   r   ztuple[int, Optional[int]]r   )r   s   "r   r   r   á   s   € ÷ !ñ !Ð/ñ !r   c                 óF   € \         P                  ! 4       p V \        V 4      3# ©N)ÚosÚgetpidrr   )rm   s    r   Ú_owner_stamprx   á   s   € Ü
�)Š)‹+€CØ”˜CÓ Ð Ð r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rm   r   Ú
started_atr   r$   r   )r   s   "r   r   r   æ   s!   € ÷ ñ �cð  sð ¨tñ r   c                ó  € V '       g   R#  \        V 4      p \        T 4      pTf    ^ RIHp  \        T! T 4      4      #  ^ RIHp TRJ ;'       g	    T! Y4      #   \        \        3 d     R# i ; i  \         d_    \        P                  R8X  d    R#  \        P                  ! T ^ 4        R#   \         d   p\        T\        4      u Rp?u # Rp?ii ; ii ; i  \         d     R# i ; i  \        \        3 d     R# i ; i)zFTrue when the recorded owning process still exists (pid + start time).FN)Ú_pid_existsÚntT)Ústart_time_fingerprints_match)rL   r   r   rr   rp   r|   rq   rv   ÚnameÚkillÚOSErrorÚ
isinstanceÚPermissionErrorr$   r~   )rm   rz   Úcurrent_startr|   Úexcr~   s   &&    r   Ú_owner_aliver†   æ   sú   € çÙðÜ�#‹hˆô   Ó$€MØÒð		8Ý2ð	Ü™ CÓ(Ó)Ð)ðÝ@Ø˜TÐ!×]Ð]Ñ%BÀ:Ó%]Ð]øô/ ”zÐ"ô Úðûô ô 	8Ü�w‰w˜$ŒÚð8Ü—’˜˜Q”ÚøÜô 8Ü! #¤Ó7×7ûð8úð	8ûô ô 	Úð	ûô
 ”zÐ"ô Úðúso   ŒA §A2 ®C Á C0 ÁC0 ÁA/Á.A/Á2 CÂB0Â0CÂ;CÃ
CÃCÃCÃCÃC-Ã,C-Ã0DÄDc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Úsession_keyr   Úmessage_refÚcontentr   r   )r   s   "r   r   r     s.   € ÷ pñ p sð p¸ð pÀsð pÈsñ pr   c                óˆ   € \         P                  ! V  RV RV 2P                  RR4      4      P                  4       R,          # )zÆStable id: same turn + same content re-records idempotently, while distinct threads/topics on one
chat never collide (session_key carries platform/chat/thread; ``message_ref`` = inbound message id).Ú|zutf-8Úreplace:Né   N)ÚhashlibÚsha256ÚencodeÚ	hexdigest)rˆ   r‰   rŠ   s   &&&r   Úcompute_obligation_idr“     sA   € ô �>Š>˜[˜M¨¨;¨-°q¸¸	ÐB×IÑIÈ'ÐS\Ó]Ó^×hÑhÓjÐknÕoÐor   rd   c               ó8   € V ^8„  d   QhRRRRRRRRRRRRR	RR
R/# )r   Úobligation_idr   rˆ   ÚplatformÚchat_idÚ	thread_idúOptional[str]rŠ   rd   r   rb   r   )r   s   "r   r   r     sI   € ÷ #ñ #¨ð #¸#ð #Èð #ÐWZð #Ø!.ð#Ø9<ð#ØO\ð#Øhlñ#r   c                ó¾  € \         P                   ! 4       \        4       upw  r‰\        ;_uu_ 4        \        4       ;_uu_ 4       p
T
P	                  RYT\        V4      V'       d   \        V4      MRYWYxY–'       d   \        V4      P                  4       MR34       \        W§4       RRR4       RRR4       R#   + '       g   i     L; i  + '       g   i     R# ; i)zBRecord a final response as owed to the platform (state='pending').a;  INSERT OR REPLACE INTO delivery_obligations
               (obligation_id, session_key, platform, chat_id, thread_id,
                content, state, attempts, created_at, updated_at,
                owner_pid, owner_started_at, adapter_profile)
               VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?, ?, ?, ?)Nr1   )Útimerx   Ú_DB_LOCKrk   rf   r   r&   Ú_prune_unlocked)r•   rˆ   r–   r—   r˜   rŠ   rd   Únowrm   Ústartedra   s   $$$$$$$    r   Úrecord_obligationr      s”   € ô Ÿ)š)›+¤|£~Ð€C‰ˆ#ß	Ž”<—>”> TØ�‰ðIð
 ¨´3°w³<×S\ÄÀYÄÐbfØ˜3 WÎo¬c°/Ó.B×.HÑ.HÔ.JÐclðnô	oô 	˜Ô"÷ "�‰—>—>ú��ˆús$   ±CÁA"B8	Â&CÂ8CÃCÃC	c               ó<   € V ^8„  d   QhRRRRRRRRRRRRR	R
RRRR/	# )r   r•   r   rˆ   r–   r—   r˜   r™   rŠ   Úsincer   rd   r   rb   r   )r   s   "r   r   r      sQ   € ÷ +ñ +¨cð +Àð +Èsð +Ð]`ð +Ø'4ð+Ø?Bð+ØKPð+à-:ð+àFJñ+r   c                ó�  € \         P                   ! 4       p\        ;_uu_ 4        \        4       ;_uu_ 4       p	T	P                  RYT\	        V4      V'       d   \	        V4      MRYXY‡'       d   \	        V4      P                  4       MRWV34       RRR4       RRR4       R#   + '       g   i     L; i  + '       g   i     R# ; i)ag  Adopt a reply a killed process persisted but never ledgered. Unowned, so this boot's sweep
claims it, and 'attempting', because a streamed reply may already be on screen: it is
redelivered once, with the recovered marker. A no-op when the same reply was already ledgered
since *since* (the turn start), and idempotent across boots that die before their sweep.aá  INSERT OR IGNORE INTO delivery_obligations
               (obligation_id, session_key, platform, chat_id, thread_id,
                content, state, attempts, created_at, updated_at,
                owner_pid, owner_started_at, adapter_profile)
               SELECT ?, ?, ?, ?, ?, ?, 'attempting', 0, ?, ?, NULL, NULL, ?
               WHERE NOT EXISTS (SELECT 1 FROM delivery_obligations
                                 WHERE session_key = ? AND content = ? AND created_at >= ?)Nr1   )r›   rœ   rk   rf   r   r&   )
r•   rˆ   r–   r—   r˜   rŠ   r¢   rd   rž   ra   s
   $$$$$$$$  r   Úrecord_crash_left_replyr¤      s€   € ô �)Š)‹+€Cß	Ž”<—>”> TØ�‰ð_ð ¨´3°w³<×S\ÄÀYÄÐbfØ˜3Æ¤ OÓ 4× :Ñ :Ô <ÐU^Ø 5ð*ô
	+÷ "�‰—>—>ú��ˆús#   ¤B4·AB!	ÂB4Â!B1Â,B4Â4C	c               ó    € V ^8„  d   QhRRRR/# ©r   r•   r   r   rb   r   )r   s   "r   r   r   6  s   € ÷ /ñ / 3ð /¨4ñ /r   c                ó   € \        V R 4       R# )Ú
attemptingN©Ú_update_state©r•   s   &r   Úmark_attemptingr¬   6  s   € Ü�- Ö.r   c               ó    € V ^8„  d   QhRRRR/# r¦   r   )r   s   "r   r   r   :  s   € ÷ .ñ . #ð .¨$ñ .r   c                ó   € \        V R 4       R# )Ú	deliveredNr©   r«   s   &r   Úmark_deliveredr°   :  s   € Ü�- Ö-r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r•   r   r#   r   rb   r   )r   s   "r   r   r   >  s!   € ÷ 8ñ 8˜sð 8¨3ð 8¸ñ 8r   c                ó"   € \        V R VR7       R# )Úfailedr)   Nr©   )r•   r#   s   &&r   Úmark_failedr´   >  s   € Ü�- °×7r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r•   r   r#   r   r$   r   )r   s   "r   r   r   B  s!   € ÷ !ñ !¨ð !°Sð !À$ñ !r   c                ót  € \        4       w  r#Vf   R# \        ;_uu_ 4        \        4       ;_uu_ 4       pTP                  R\        P                  ! 4       V'       d
   VR,          MRWV34      pRRR4       RRR4       \        XP                  4      #   + '       g   i     L-; i  + '       g   i     L8; i)al  Return an unsent runtime claim to ``failed`` without spending an attempt.

Runtime recovery claims before clearing ``resume_pending`` so two reconnect paths cannot send the
same row; if the flag cannot be cleared no send was attempted and the claim must not consume the
redelivery budget. Fail-closed to the exact current process instance and ``attempting`` state.NFa;  UPDATE delivery_obligations
               SET state='failed', attempts=CASE
                       WHEN attempts > 0 THEN attempts - 1 ELSE 0 END,
                   updated_at=?, last_error=?
               WHERE obligation_id=? AND state='attempting'
                 AND owner_pid IS ? AND owner_started_at IS ?ºNiô  N)rx   rœ   rk   rf   r›   r$   Úrowcount)r•   r#   rm   rŸ   ra   Úcursors   &&    r   Úrelease_runtime_claimrº   B  sx   € ô  “>�L€CØ‚Ùß	Ž”<—>”> TØ—‘ðAô �YŠY‹[¯˜% ž+°D¸-ÈgÐVóXˆ÷ "�ô �—‘Ó Ð ÷ "—>ú��ús"   ¡B'´;B	Á/B'ÂB$ÂB'Â'B7	c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r•   r   Ústater#   r   rb   r   )r   s   "r   r   r   W  s.   € ÷ Qñ Q ð Q¨Sð Q¸ð QÀdñ Qr   c                ó,  € \         ;_uu_ 4        \        4       ;_uu_ 4       pTP                  R T\        P                  ! 4       V'       d
   VR,          MRV 34       RRR4       RRR4       R#   + '       g   i     L; i  + '       g   i     R# ; i)zwUPDATE delivery_obligations
               SET state=?, updated_at=?, last_error=?
               WHERE obligation_id=?r·   N)rœ   rk   rf   r›   )r•   r¼   r#   ra   s   &&& r   rª   rª   W  sR   € ß	Ž”<—>”> TØ�‰ð(ð ”D—I’I“K·  t¦¸4ÀÐOô		Q÷ "�‰—>—>ú��ˆús"   �B¢;A/	ÁBÁ/A?Á:BÂB	ÚruntimeFÚfloodrF   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r   Úneeds_markerr$   r¾   r¿   rF   r™   r   zdict[str, Any]r   )r   s   "r   r   r   `  s;   € ÷ \ñ \Ø#ð\Ø.2ð\ØCGð\à*ð\à6Dñ\r   c               óà   € V
'       d   \         MV	'       d   \        MRpRV RVRVRVRVRVRV/V'       d   V'       d   R	V/M/ CR
V/CV	'       d   RR/M/ CV'       d   RV/M/ CRV^,           /C# )aò  Claimed-row dict handed back for redelivery. A marked row names its own cause: ``flood`` (a reply
the rate limit refused, possibly after accepting part of it) gets FLOOD_MARKER at boot or at runtime, a
``runtime`` reconnect replay gets RECONNECTED_MARKER, and a boot-recovered crash keeps the runner's
restart marker default. ``last_error`` is the row's pre-claim error, carried so a runtime claim that is
released unsent goes back to ``failed`` with the same error and keeps its retry eligibility.Nr•   rˆ   r–   r—   r˜   rŠ   rÁ   ÚmarkerÚprofileÚruntime_recoveryTrF   rJ   )ÚFLOOD_MARKERÚRECONNECTED_MARKER)Úoidrˆ   r–   r—   r˜   rŠ   rJ   rÄ   rÁ   r¾   r¿   rF   rÃ   s   &&&&&&&&$$$$ r   Ú_claimed_rowrÉ   `  s›   € ÷ #�\¿wÕ);ÈD€FØ˜S -°¸jÈ(ÐT]Ð_fØ˜ I¨w¸Èð\ç%1·f�˜&Ñ!À"ð\àGPÐRYñ\÷ .5Ð" DÑ)¸"ð\÷ .8�˜jÑ)¸Rð	\ð CMÈhÐYZÍlñ	\ð \r   Údeliverable_platformsÚdeliverable_targetsc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rž   r   rÊ   zOptional[set]rË   r   úlist[dict[str, Any]]r   )r   s   "r   r   r   p  s0   € ÷ Nñ N˜?ð NÈ]ð NØ+8ðNØDXñNr   c               óB  € V e   T M\         P                   ! 4       \        4       up w  r4. p\        ;_uu_ 4        \        4       ;_uu_ 4       pVP	                  R4      P                  4       pV EFu  w  r‰r«rÍrïpppppp\        VV4      '       d   K#  V\        8¼  g   V V,
          \        8”  d   VP	                  RW34       KU  Ve   W¡9  g   Ve   V
V3V9  d   Kn  VR8H  ;'       d    \        V4      pV'       dv   V \        VV4      8  de   VP	                  RW4VVV34      pVP                  '       d:   TP                  RTRT	RT
R	TR
TRTRT;'       g    RRVRRR\        VV4      /
4       EK  TP	                  RY4T V'       d   ^M^ VVV34      pVP                  '       g   EK:  TP                  \        Y‰Y«YÍTT;'       g    RVR8g  ;'       g    V^ 8„  VR7
      4       EKx  	  RRR4       RRR4       V#   + '       g   i     L; i  + '       g   i     T# ; i)a£  Claim undelivered rows owned by dead processes; return them for redelivery.

Claiming atomically re-stamps the owner to THIS process, moves the row to 'attempting' and increments
``attempts`` (the UPDATE is guarded on the previous owner stamp, so a second gateway racing the same
sweep cannot double-claim).
Rows over the attempts cap or stale cutoff become 'abandoned'. ``deliverable_platforms`` restricts
claiming to platforms the caller can send on this boot: ``attempts`` is the redelivery budget and
must only be spent on a real send, else a platform that failed to connect burns one attempt per boot
and hits the cap having never been sent once (the stale cutoff still bounds untouched rows).
``deliverable_targets`` further scopes multiplexed gateways by exact ``(platform, adapter_profile)``
so one connected bot cannot spend another disconnected bot's retry budget.

A flood-refused row still inside its wait is adopted (owner re-stamped, no attempt spent) and
returned flagged ``adopted`` with its ``not_before``: the caller clears its session's resume flag
like any other claimed row, since the answer is in the ledger, but must not send it; the flood
timer does once the wait has passed. A legacy row without ``adapter_profile`` is normalised to
``'default'`` on claim or adoption (the caller only accepts such rows when it is not multiplexed),
because the runtime sweep matches profiles exactly and could otherwise never claim it.NaB  SELECT obligation_id, session_key, platform, chat_id, thread_id,
                      content, state, attempts, created_at,
                      owner_pid, owner_started_at, adapter_profile, last_error, updated_at
               FROM delivery_obligations
               WHERE state IN ('pending', 'attempting', 'failed')zlUPDATE delivery_obligations
                       SET state='abandoned', updated_at=? WHERE obligation_id=?r³   zøUPDATE delivery_obligations
                       SET owner_pid=?, owner_started_at=?,
                           adapter_profile=COALESCE(adapter_profile, 'default')
                       WHERE obligation_id=? AND (owner_pid IS ? OR owner_pid=?)r•   rˆ   r–   r—   r˜   rŠ   rÄ   r1   rJ   ÚadoptedTÚ
not_beforeap  UPDATE delivery_obligations
                   SET owner_pid=?, owner_started_at=?, attempts=attempts+1, updated_at=?,
                       adapter_profile=COALESCE(adapter_profile, 'default'), state='attempting',
                       last_error=CASE WHEN ? THEN NULL ELSE last_error END
                   WHERE obligation_id=? AND (owner_pid IS ? OR owner_pid=?)Úpending)rÁ   r¿   )r›   rx   rœ   rk   rf   Úfetchallr†   rM   ÚSTALE_AFTER_SECONDSr/   rH   r¸   ÚappendrÉ   )rž   rÊ   rË   rm   rŸ   Úclaimedra   ÚrowsrÈ   rˆ   r–   r—   r˜   rŠ   r¼   rJ   Ú
created_atÚ	owner_pidÚowner_started_atrd   rF   rB   Ú	flood_rowr¹   s   &$$                     r   Úsweep_recoverablerÛ   p  s2  € ð( "%¢™#´d·i²i³kÄ<Ã>Ð€C‰ˆ#Ø$&€Gß	Ž”<—>”> TØ�|‰|ðEó
÷ ‰(‹*ð 	ô W[ñSˆS˜x°)ÀeÐWaØÐ(¨/¸:ÀzÜ˜IÐ'7×8Ò8ÙØœ<Ô'¨C°*Õ,<Ô@SÔ+SØ—‘ðTØVYÐU_ôañ Ø&Ò2°xÔ7\Ø+Ò7¸XÀÐ<WÐ_rÔ<rÙØ Ñ)×HÐH¬n¸ZÓ.HˆIß˜SÔ#3°JÀ
Ó#KÔKð Ÿ™ðTð  3¨	°9Ð=ó?�ð —?—?�?Ø—N‘NØ'¨¨m¸[È*ÐV^Ø! 7¨K¸ÀIÈwØ! ?×#?Ð#?°iÀÈXØ! 4¨Ô7GÈ
ÐT^Ó7_ð	$aô bò
 ð
 —\‘\ðPð
 ˜s¯¡A¸¸3À	È9ÐUóWˆFð ��‹ð
 —‘œ|¨C¸hÐQZÐemØ,;×,HÐ,H¸yØ9>À)Ñ9K×9[Ð9[ÈxÐZ[É|Ðclô n÷ oñY W[÷ "�ðp €N÷q "—>ú�Žðp €NúsD   ¹HÁBG:	Ã(G:	Ã<AG:	ÅAG:	Æ(*G:	ÇG:	Ç(HÇ:H
ÈHÈH	rÄ   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r–   r   rž   r   rÄ   r™   r   rÍ   r   )r   s   "r   r   r   Á  s*   € ÷ 8ñ 8 sð 8°ð 8Ø&3ð8Ø?Sñ8r   c               ó0  € Ve   TM\         P                   ! 4       \        4       upw  r4Vf   . # V'       d   VR8X  d   RM
\        V4      p. p\        ;_uu_ 4        \	        4       ;_uu_ 4       pVP                  RV 34      P                  4       pV FÉ  w  ršr¼rÞpppppppVV8w  g   VV8w  g   VV8w  d   K%  \        VVV4      pVf   K8  WVV3pV\        8¼  g   VV,
          \        8”  d   VP                  RV4       Kn  VV8  d   Kw  VP                  RV4      pVP                  '       g   K�  VP                  \        WšW¼WÞVVRR\        V4      VR7      4       KË  	  RRR4       RRR4       V#   + '       g   i     L; i  + '       g   i     T# ; i)aW  Claim this process's failed rows that are due for another send, for one adapter.

``profile`` scopes multiplexed gateways to the bot identity that owned the failed send (``None`` =
primary/default adapter); unowned rows and rows owned by another process are left for the
startup/dead-owner sweep. Startup recovery ignores rows owned by a live gateway, so a response
rejected with ``send_path_degraded`` would stay stranded when only the adapter reconnects; this closes
that gap without weakening ownership: only rows stamped to this exact process instance, only rows
past their ``retry_not_before`` deadline, same attempts/staleness bounds, every update guarded by the
prior owner stamp and ``failed`` state. Claimed rows always carry a marker (the failed send's ack is not safe to
infer): the reconnect one, or the rate-limit one for a flood-refused row.Nr1   a,  SELECT obligation_id, session_key, platform, chat_id, thread_id,
                      content, attempts, created_at, owner_pid,
                      owner_started_at, last_error, adapter_profile, updated_at
               FROM delivery_obligations
               WHERE state='failed' AND platform=?zÜUPDATE delivery_obligations
                       SET state='abandoned', updated_at=?
                       WHERE obligation_id=? AND state='failed'
                         AND owner_pid IS ? AND owner_started_at IS ?z÷UPDATE delivery_obligations
                   SET state='attempting', attempts=attempts+1, updated_at=?, last_error=NULL
                   WHERE obligation_id=? AND state='failed'
                     AND owner_pid IS ? AND owner_started_at IS ?T)rÁ   r¾   r¿   rF   )r›   rx   r   rœ   rk   rf   rÒ   rP   rM   rÓ   r¸   rÔ   rÉ   r/   )r–   rž   rÄ   rm   rŸ   Úexpected_profilerÕ   ra   rÖ   rÈ   rˆ   Úrow_platformr—   r˜   rŠ   rJ   r×   rØ   rÙ   rF   rd   rB   ÚdueÚowner_guardr¹   s   &&$                      r   Úsweep_failed_for_runtimerâ   Á  s¦  € ð "%¢™#´d·i²i³kÄ<Ã>Ð€C‰ˆ#Ø‚Øˆ	ß(/°7¸iÔ3G‘yÌSÐQXË\ÐØ$&€Gß	Ž”<—>”> TØ�|‰|ð6ð 9A°{óD÷
 EMÁHÃJð 	ó W[ñSˆS˜|°iÈ(ÐT^ØÐ(¨*°oÀzàÐ"2Ô2°iÀ3Ô6FÐJZÐ^eÔJeÙÜ" :¨z¸8ÓDˆCØŠ{ÙØ YÐ0@ÐAˆKØœ<Ô'¨C°*Õ,<Ô@SÔ+SØ—‘ðIð KVô	Wñ
 Ø�SŒyÙð —\‘\ðEð GRó	SˆFð
 ��Šð —‘œ|¨C¸lÐU^Ø,4°oÐTXÐbfÜ2@ÀÓ2LÐYcô eö fñ9 W[÷ "�ðN €N÷O "—>ú�ŽðN €Nús+   ÁFÁ.B<E1	Ä/0E1	ÅFÅ1FÅ<FÆF	c               ó    € V ^8„  d   QhRRRR/# )r   rž   r   r   rÍ   r   )r   s   "r   r   r   ü  s   € ÷ Fñ F˜ð FÐ4Hñ Fr   c                óº  € V e   T M\         P                   ! 4       \        4       up w  rVf   . # \        ;_uu_ 4        \        4       ;_uu_ 4       pVP	                  RW34      P                  4       pRRR4       RRR4       / pX Fn  w  rgr‰r«\        V	4      '       d   K  \        W‰V
4      pVe   V
\        8¼  g   W,
          \        8”  d   KH  Yg;'       g    R3pWÕ9  g   WÅV,          8  g   Kj  WÅV&   Kp  	  \        VP                  4       4       UUUu. uF  w  w  rnpRVRVRV/NK  	  uppp#   + '       g   i     LÆ; i  + '       g   i     LÑ; iu upppi )aj  This process's failed rows that still await redelivery, one entry per adapter identity with the
earliest deadline (``not_before``). The runner arms one redelivery timer per entry, so a row adopted
at boot, skipped because its wait had not passed, or rejected again is never stranded. Rows past the
attempts cap or stale cutoff are left for the sweeps to abandon.NzÈSELECT platform, adapter_profile, updated_at, last_error, attempts, created_at
               FROM delivery_obligations
               WHERE state='failed' AND owner_pid IS ? AND owner_started_at IS ?r1   r–   rÄ   rÐ   )r›   rx   rœ   rk   rf   rÒ   r*   rP   rM   rÓ   ÚsortedÚitems)rž   rm   rŸ   ra   rÖ   Úearliestr–   rd   rB   rF   rJ   r×   rà   ÚkeyrÄ   s   &              r   Úpending_retriesré   ü  s7  € ð
 "%¢™#´d·i²i³kÄ<Ã>Ð€C‰ˆ#Ø‚Øˆ	ß	Ž”<—>”> TØ�|‰|ðTàVYÐUcóe÷ fnÑemÓeoð 	÷ "�ð
 $&€HÛSWÑOˆ :¸8ô ˜Z×(Ò(ÙÜ˜z°xÓ@ˆØŠ;˜(¤lÔ2°sÕ7GÔK^Ô6^ÙØ×5Ð5¨IÐ6ˆØÔ #°­Ö"5Ø�S‹Mñ TXô -3°8·>±>Ó3CÔ,DõFÙ,DÑ(Ñ#� Sð ˜ 9¨g°|ÀSÓIÙ,DóFð F÷# "—>ú��üô"Fs)   ½EÁ"D0	Á2EÄEÄ0E Ä;EÅE	c               ó    € V ^8„  d   QhRRRR/# )r   rž   r   r   rb   r   )r   s   "r   r   r     s   € ÷ 4ñ 4˜uð 4¨ñ 4r   c                óä   € V P                  RV\        ,
          34       V P                  R4      P                  4       ^ ,          pV\        8”  d!   V P                  RV\        ,
          34       R# R# )u_   Retention DELETEs on the caller's open connection â€” must run inside the caller's transaction.zhDELETE FROM delivery_obligations
           WHERE state IN ('delivered', 'abandoned') AND updated_at < ?z)SELECT COUNT(*) FROM delivery_obligationsaq  DELETE FROM delivery_obligations WHERE obligation_id IN (
                 SELECT obligation_id FROM delivery_obligations
                 ORDER BY CASE state
                            WHEN 'delivered' THEN 0
                            WHEN 'abandoned' THEN 1
                            ELSE 2
                          END, updated_at ASC
                 LIMIT ?)N)rf   Ú_RETENTION_SECONDSÚfetchoneÚ	_MAX_ROWS)ra   rž   Útotals   && r   r�   r�     sl   € à‡L�Lð	KØMPÔSeÕMeÐLgôið �L‰LÐDÓE×NÑNÓPÐQRÕS€EØŒyÔØ�‰ðð  %¤yÕ0Ð2ö	4ñ r   c               ó    € V ^8„  d   QhRRRR/# )r   ÚconfigzOptional[dict[str, Any]]r   r$   r   )r   s   "r   r   r   +  s   € ÷ 	ñ 	Ð3ð 	¸tñ 	r   c                ó$  €  V f   ^ RI Hp V! 4       p V P                  R4      ;'       g    / P                  RR4      p\        V\        4      '       d"   VP                  4       P                  4       R9  # \        V4      #   \         d     R# i ; i)z>Read the ``gateway.delivery_ledger`` config gate (default on).)Úload_configÚgatewayÚdelivery_ledgerT>   Ú0ÚnoÚoffÚfalse)	Úhermes_cli.configró   Úgetr‚   r   r&   r'   r$   rq   )rñ   ró   Úvalues   &  r   Úledger_enabledrý   +  s�   € ðØŠ>Ý5Ù “]ˆFØ—‘˜IÓ&×,Ð,¨"×1Ñ1Ð2CÀTÓJˆÜKUÐV[Ô]`×KaÒKaˆu�{‰{‹}×"Ñ"Ó$Ð,GÑGÐrÔgkÐlqÓgrÐrøÜô Úðús   ‚(B  «A	B  Á5
B  Â BÂBi€Q i€:	 )g      >@g      ^@g      Œ@)r   ru   )IÚ__doc__Ú
__future__r   r�   Úloggingrv   ÚreÚsqlite3Ú	threadingr›   Útypingr   r   r   r   Úgateway.dead_targetsr   r]   r   Úhermes_constantsr
   Ú	getLoggerÚ__name__ÚloggerÚLockrœ   rM   rÓ   rì   rî   ÚRECOVERED_MARKERrÇ   rÆ   Ú	frozensetr(   rN   r3   r.   r;   r>   r?   ÚcompileÚ
IGNORECASEr   r    r*   r/   r6   r@   rD   rH   rP   rR   r_   r^   rk   rr   rx   r†   r“   r    r¤   r¬   r°   r´   rº   rª   rÉ   rÛ   râ   ré   r�   rý   r   r   r   Ú<module>r     sß  ðñ
õ #ã Û Û 	Û 	Û Û Û ß ,Ó ,å 4Ý 8Ý 4à	×	Ò	˜8Ó	$€Ø�>Š>Ó€ð €Ø"Ð Ø%Ð Ø€	ð
 wÐ ðIÐ ð9€ñ &Ð';Ð&<Ó=Ð à&Ð Ù
Ð!Ó" l°QÕ&6Ô6Ð 6Ð6ð &Ð Ø"Ð Ø#Ð ØÐ ð —
’
ÐQÐSU×S`ÑS`Óa€õõIõ
Tð 5P÷ )õ$TõõFõEò(2õDõgò.#õõ!õ
õDpð#Ø_c÷#ð$+à=A÷+õ,/õ.÷8÷!÷*Qð\Ø5:ð\ØJOð\à-1÷\ñ NÐ]að NØ;?÷Nð Nñb8Ø6:÷8ð 8÷vFõ:4÷$	ñ 	r   