+
    jHàj  ã                  ó,  € R t ^ RIHt ^ RIt^ RIt^ RIHt ^ RIHtHtH	t	 ^ RI
HtHt ]P                  ! R4      tRtRt^<t]P&                  ! R	]P(                  4      tR
 R 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/R R lltR R ltR# )u¨  Hold a job's fires while a provider's usage window is known to be closed (#89376).

A quota-exhausted provider answers with an explicit ``retry after <N>s`` (Codex 429: the
``AuthError`` from ``hermes_cli.auth_codex._codex_quota_exhausted_error``). When the whole
fallback chain is unavailable, re-firing on cadence is guaranteed to fail identically until
the window reopens â€” every fire is a usage probe plus a delivered failure alert. The failing
run's alert says the job is held; ``mark_job_run`` then parks ``next_run_at`` at the recovery
boundary (or the first legal occurrence after it, when several fall inside the window) and
stamps ``quota_hold_until`` so the stale-error re-arm
(``cron.jobs._job_is_stale_error_recurring``) does not pull the job back early.

Complement to ``cron/unreachable_retry.py``: this one moves ``next_run_at`` out of a known
closed provider window. Any run that reaches the model clears the marker.
)ÚannotationsN)Údatetime)ÚAnyÚDictÚOptional)ÚnowÚsafe_strftimezcron.schedulerÚquota_hold_untilÚquota_hold_cron_exprzretry after (\d+)sc               ó    € V ^8„  d   QhRRRR/# )é   ÚexcÚBaseExceptionÚreturnúOptional[float]© )Úformats   "Ú4/home/ubuntu/.hermes/hermes-agent/cron/quota_hold.pyÚ__annotate__r   &   s   € ÷ ñ  =ð °_ñ ó    c                óô  € ^ RI HpHp \        4       pT pVeà   \	        V4      V9  dÐ   VP                  \	        V4      4       \        WA4      '       dƒ   V! V4      '       du   \        VRR4      pVfC   \        P                  \        V4      4      pV'       d   \        VP                  ^4      4      MRpVe   \        V4      ^ 8”  d   \        V4      # R# VP                  ;'       g    VP                  pKã  R# )a  Seconds the provider said it will stay closed, or None when *exc* (or anything in its
cause chain) is not a rate-limited ``AuthError`` carrying a wait hint. Anchored on the
AuthError itself, never on arbitrary text, so an unrelated "retry after" in an agent's
output cannot park a job.)Ú	AuthErrorÚis_rate_limited_auth_errorNÚretry_after)Úhermes_cli.authr   r   ÚsetÚidÚaddÚ
isinstanceÚgetattrÚ_RETRY_AFTER_REÚsearchÚstrÚfloatÚgroupÚ	__cause__Ú__context__)r   r   r   ÚseenÚcurÚhintÚms   &      r   Úhold_seconds_from_failurer+   &   s½   € ÷
 Fä“U€DØ#&€CØ
Š/œb ›g¨TÔ1Ø�‰”�C“ÔÜ�c×%Ò%Ñ*DÀS×*IÒ*IÜ˜3 ¨tÓ4ˆDØŠ|Ü#×*Ñ*¬3¨s«8Ó4�ß,-”u˜QŸW™W Q›ZÔ(°4�Ø"&Ò"2´u¸T³{ÀQ´”5˜“;ÐPÈDÐPØ�m‰m×.Ð.˜sŸ™ŠÙr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Újobúdict[str, Any]r   zOptional[datetime]r   Úboolr   )r   s   "r   r   r   ;   s'   € ÷ Mñ M�^ð MÐ*<ð MÈñ Mr   c                óÎ   € ^ RI HpHp V P                  \        4      '       d   V! V P                  \        4      4      MRpVRJ;'       d    T! YA;'       g    \        4       4      # )zSTrue while the job is parked inside a provider window (an expired marker is inert).)Ú_instant_afterÚ_parse_awareN)Ú	cron.jobsr1   r2   ÚgetÚ	STATE_KEYÚ_hermes_now)r-   r   r1   r2   Úuntils   &&   r   Úhold_activer8   ;   sJ   € ç6à03·±¼	×0BÒ0B‰L˜Ÿ™¤Ó+Ô,È€EØ˜Ð×LÐL¡°×7KÐ7K¼k»mÓ!LÐLr   c               ó    € V ^8„  d   QhRRRR/# )r   r-   r.   r   ÚNoner   )r   s   "r   r   r   C   s   € ÷ %ñ %�^ð %¨ñ %r   c                ó^   € V P                  \        R 4       V P                  \        R 4       R # ©N)Úpopr5   ÚSCHEDULE_EXPR_KEY)r-   s   &r   Úclear_stater?   C   s   € Ø‡G�GŒI�tÔØ‡G�GÔ˜tÖ$r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r-   r.   Únext_runr"   r   r/   r   )r   s   "r   r   r   H   s!   € ÷ ñ ˜.ð °Cð ¸Dñ r   c                óþ   € V P                  R4      ;'       g    / pVP                  R4      R8H  ;'       dH    V P                  \        4      V8H  ;'       d(    V P                  \        4      VP                  R4      8H  # )zÞTrue for the exact off-lattice cron fire parked by ``plan_hold``.

The expression fingerprint keeps a direct ``jobs.json`` schedule edit from inheriting the
exception: edited schedules must still re-anchor without firing.
ÚscheduleÚkindÚcronÚexpr)r4   r5   r>   )r-   rA   rC   s   && r   Úis_recovery_firerG   H   sp   € ð �w‰w�zÓ"×(Ð( b€Hà�‰�VÓ Ñ&÷ 	?ð 	?Ø�G‰G”IÓ (Ñ*÷	?ð 	?à�G‰GÔ%Ó&¨(¯,©,°vÓ*>Ñ>ðr   c               ó    € V ^8„  d   QhRRRR/# )r   Úhold_secondsr#   r   r   r   )r   s   "r   r   r   V   s   € ÷ Sñ S˜eð S¨ñ Sr   c                óX   € ^ RI Hp V! \        4       \        V 4      \        ,           4      # )é    )Ú_seconds_after)r3   rL   r6   r#   ÚHOLD_SLACK_SECONDS)rI   rL   s   & r   Ú_window_endrN   V   s   € Ý(áœ+›-¬¨|Ó)<Ô?QÕ)QÓRÐRr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r-   r.   Únatural_nextr   Ú
window_endr   r/   r   )r   s   "r   r   r   \   s2   € ÷ Wñ WØ	ðWØ'/ðWØ=EðWà	ñWr   c                óØ   € ^ RI HpHp V P                  \        4      '       d   R# T! V P                  R4      ;'       g    / 4      p\        V4      ;'       d    V! W4      V^,          8¬  # )uB  One off-lattice recovery fire, and only for a sparse schedule.

Bounded: a job already carrying ``quota_hold_until`` IS the recovery fire failing again, so
it waits for the natural schedule instead of re-parking at every hold boundary (the
probe-per-window cost the hold exists to prevent). Sparse: the natural occurrence must be at
least half a cadence period past the boundary â€” the same half-period rule as
``cron.jobs._compute_grace_seconds`` â€” otherwise the recovery fire is a near-duplicate of the
natural one (hourly job, hold ending at :58, would fire :58 AND :00).
)Ú_elapsed_secondsÚ_schedule_cadence_secondsFrC   )r3   rS   rT   r4   r5   r/   )r-   rP   rQ   rS   rT   Úcadences   &&&   r   Ú_recovery_worthwhilerV   \   sW   € ÷ Fà
‡w�wŒy×ÒÙÙ'¨¯©°
Ó(;×(AÐ(A¸rÓB€GÜ�‹=×VÐVÑ-¨lÓGÈ7ÐUVÍ;ÑVÐVr   Úrecover_consumed_fireFc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r-   r.   rI   r#   rW   r/   r   r   )r   s   "r   r   r   p   s,   € ÷ &ñ &Ø	ð&Ø',ð&ØHLð&à	ñ&r   c          
     ót  € ^ RI HpHpHp V P	                  R4      ;'       g    / pVP	                  R4      pVR9  g   V P	                  R4      R8X  d   \        V 4       R# \        V4      pV! V P	                  R4      4      p	V	R	J ;'       g	    V! W˜4      p
VR8H  ;'       d&    V
'       * ;'       d    T;'       d    \        W	V4      pV
'       g   V'       g   \        V 4       R# VR8X  d8   V
'       d0   V! WhP                  4       4      ;'       g    VP                  4       pMVP                  4       pV'       d   VP	                  R
4      V \        &   MV P                  \        R	4       WÀR&   WÀ\        &   \        P                  RV P	                  RV P	                  RR4      4      \        V4      V4       R# )a<  Called under the jobs lock AFTER ``_advance_after_run`` computed the schedule's natural
``next_run_at`` for a failed run. A scheduled sparse cron may retry its consumed fire at the
recovery boundary; manual runs keep the natural schedule. Otherwise coalesce fires through
the closed window. Returns True when parked.)Ú_instant_beforer2   Úcompute_next_runrC   rD   rE   ÚstateÚpausedFÚnext_run_atNrF   ut   Job '%s': provider usage window closed for %.0fs â€” holding fires until %s instead of failing on every cadence tickÚnamer   Ú?T>   rE   Úinterval)r3   rZ   r2   r[   r4   r?   rN   rV   Ú	isoformatr>   r=   r5   ÚloggerÚwarningr#   )r-   rI   rW   rZ   r2   r[   rC   rD   rQ   rP   ÚblockedÚrecoverÚparkeds   &&$          r   Ú	plan_holdrh   p   s�  € ÷ JÑIà�w‰w�zÓ"×(Ð( b€HØ�<‰<˜Ó€DØÐ'Ô'¨3¯7©7°7Ó+;¸xÔ+GÜ�CÔÙÜ˜\Ó*€JÙ §¡¨Ó 6Ó7€LØ˜dÐ"×OÐO¡o°lÓ&O€GØ�v‰~÷ Gð G g¤+÷ Gð GÐ2G÷ Gð GÜ'¨¸:ÓFð çŸ7Ü�CÔÙØˆv„~Ÿ'á! (×,@Ñ,@Ó,BÓC×]Ð]Àz×G[ÑG[ÓG]‰à×%Ñ%Ó'ˆßà!)§¡¨fÓ!5ˆÔÒà�‰Ô! 4Ô(ØˆÑØŒ	�NÜ
‡N�Nð	(à�‰�˜Ÿ™  cÓ*Ó+¬U°<Ó-@À&ôJñ r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r-   r.   rI   r   r   r"   r   )r   s   "r   r   r   ™   s!   € ÷ 
ñ 
�^ð 
°?ð 
Àsñ 
r   c                óÖ   € V'       d0   V P                  R4      ;'       g    / P                  R4      R
9  d   R# \        V4      p\        V4      R,          pRVR R\        VR4       R	2# )zOLine appended to the ONE failure alert delivered on entering the hold, else "".rC   rD   Ú g      ¬@z1
The provider's usage window is closed for about z.1fzh. This job is held through z%Y-%m-%d %H:%M %Zzt and resumes at the first safe opportunity afterwards; no further alerts are sent while the provider is unavailable.>   rE   ra   )r4   rN   r#   r   )r-   rI   rQ   Úhourss   &&  r   Úhold_noticerm   ™   st   € ç˜CŸG™G JÓ/×5Ð5°2×:Ñ:¸6ÓBÐJ^Ô^ÙÜ˜\Ó*€JÜ�,Ó &Õ(€Eà
<¸UÀ3¸Kð HÜ  Ð-@ÓAÐBð C`ð	`ðr   r<   )Ú__doc__Ú
__future__r   ÚloggingÚrer   Útypingr   r   r   Úhermes_timer   r6   r   Ú	getLoggerrc   r5   r>   rM   ÚcompileÚ
IGNORECASEr    r+   r8   r?   rG   rN   rV   rh   rm   r   r   r   Ú<module>rw      s�   ðñõ #ã Û 	Ý ß &Ñ &ç 9à	×	Ò	Ð+Ó	,€ð €	Ø*Ð ð Ð à—*’*Ð2°B·M±MÓB€õ÷*Mõ%õ
õSõWð(&ØOT÷&÷R
r   