+
    jHàjår ã                   óÆ  a € ERª Et0 t R t^ RIt^ RIt^ RIHt ^ RIHtHt ^ RI	t	^ RI
t
^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt ^ RIt ^ RIt^ RIHtHtHtHt ^ RIHt ^ RIHt ^ RIHtHtH t  ^ RI!H"t" ^ R	I#H$t$ ^ R
I%H&t& ^ RI'H(t( ^ RI)H*t*H+t+H,t,H-t-H.t.H/t/H0t0H1t1H2t2 ]
Pf                  ! ]44      t5^ RI6H7t8 ^ RI6H9t9 ^ RI:H;t; ^ RI<H=t=H>t>H?t?H@t@ RsARsB] ^ k RtCRsD] ^k R R ltE]! 4       P�                  4       tG]GR,          tH]HR,          tI]HR,          tJ]HR,          tK^<tL]Pš                  ! 4       tN]Pž                  ! 4       tP/ tQ] ^k ]P¤                  ! 4       tS]Pž                  ! 4       tTRtU]HR,          tV^xtW]! RR7       ! R R4      4       tX]! RRR 7      tY] ^k  ! R! R"4      tZ]! R#RR 7      t[] ^k ]P¸                  R$ R% l4       t]R& R' lt^]X! ]H]I]V4      t_R( R) lt`]P¸                  R* R+ l4       taR, R- ltbR.tcR/tdR0 R1 lteR2 R3 ltfR4 R5 ltgR6 R7 lthR8 R9 lti]P¸                  R: 4       tj]P¸                  R; R< l4       tkR= R> ltl]P¸                  R? R@ l4       tm]n! RA04      toRB RC ltpER«RD RE lltqRF RG ltrER¬RH RI llts]n! 0 ER­m4      ttRLtuRM RN ltvRO RP ltwRQ RR ltxRS RT ltyRU RV ltzRW RX lt{RY RZ lt|R[ R\ lt}R] R^ lt~R_ R` ltRa Rb lt€Rc Rd lt�Re Rf lt‚Rg tƒRh Ri lt„Rj^Rk^<RlRm/t…Rn Ro lt†/ RpRqbRrRqbRsRtbRuRtbRvRwbRxRwbRyRwbRzR{bR|R{bR}R{bR~RbR€RbR�RbR‚RbRƒR„bR…R„bR†R‡bRˆR‡/Ct‡R‰RŠR‹RŠRŒRŠR�RŽR�RŽR�R‘R’R‘/tˆR“ R” lt‰R• R– ltŠR— R˜ lt‹R™ Rš ltŒR› Rœ lt�R� Rž ltŽRŸ R  lt�R¡ R¢ lt�R£ R¤ lt‘R¥ R¦ lt’R§ R¨ lt“R© Rª lt”R« R¬ lt•R­ R® lt–R¯R/R° R± llt—^xt˜R²t™R³ R´ ltšRµt›R¶ R· ltœ^ s�] ^k ^tž. tŸ] ^k R¸ R¹ lt / t¡] ^k Rº R» lt¢R¼ R½ lt£R¾ R¿ lt¤RÀ RÁ lt¥RÂ RÃ lt¦RÄt§RÅt¨RÆt©RÇ RÈ ltª^ s«] ^k . t¬] ^	k RÉ RÊ lt­RË RÌ lt®ER®RÍ RÎ llt¯RÏ RÐ lt°ER¯RÒ RÓ llt±RÔ RÕ lt²RÖ R× lt³RØ RÙ lt´RÚ RÛ ltµRÜ RÝ lt¶RÞ Rß lt·Rà Rá lt¸Râ Rã lt¹Rä Rå ltºRæ Rç lt»Rè Ré lt¼Rê Rë lt½Rì Rí lt¾Rî Rï lt¿RðR/Rñ Rò lltÀRó Rô ltÁRõ Rö ltÂ^tÃRðRR÷RÑ/Rø Rù lltÄRðRR÷RÑ/Rú Rû lltÅ]Æ! 4       tÇER®Rü Rý lltÈRþ Rÿ ltÉER  ER ltÊER ER ltËER ER ltÌERRÑ/ER ER lltÍER	 ER
 ltÎER ER ltÏER ER ltÐER ER ltÑER ER ltÒER ER ltÓER]ÍER]ÍER]ÎRJ]ÍER]ÍER]ÍER]ÐER]ËRK]ÔER]ÑER]ÒER]Í/tÕ] ^
k ER]ÐERER ER]ÍER]ÍER]ÍER ]Ó/tÖ] ^k ER! ER" lt×ER# ER$ ltØER°ER% ER& lltÙER' ER( ltÚ ! ER) ER*]Û4      tÜER+ ER, ltÝER¯ER- ER. lltÞER/ ER0 ltßER1 ER2 ltàER3 ER4 ltáER5 ER6 ltâER7 ER8 ltãER9 ER: ltäER; ER< ltåER®ER= ER> lltæER? ER@ ltçER®ERA ERB lltèERC ERD ltéEREtêERF ERG ltëERH ERI ltìERJ ERK ltíERL ERM ltîERN ERO ltïERP ERQ ltðERR ERS ltñERTRÑ/ERU ERV lltòER±ERWRERXRERYRÑERTRÑERZRER[RÑ/ER\ ER] llltóER^ ER_ ltôER` ERa ltõERb ERc ltöERd ERe lt÷ERf ERg ltøERh ERi ltùERj ERk ltúERl ERm ltûERn ERo ltüERp ERq ltýERr] ERsRÑERtRÑERuRÑ/ERv ERw lltþERx ERy ltÿ^Et ERz ER{ lEtER| ER} lEtRðR/ER~ ER llEtER€ ER� lEt] ! ER‚ ERƒ4      4       EtER„ ER… lEtER† ER‡ lEtERˆ ER‰ lEt] ! ERŠ ER‹4      4       Et	ERŒ ER� lEt
ERŽ ER� lEtER� ER‘ lEtER’ ER“ lEtER” ER• lEtER– ER— lEtER˜ ER™ lEtERš ER› lEtERœ ER� lEt^2EtERž ERŸ lEtER  ER¡ lEtER¢ ER£ lEtER¤ ER¥ lEtER¦ ER§ lEtER«ER¨ ER© llEtR#   ] d    Rt EL…i ; i  ] d    Rt EL�i ; i(²  zcCron job storage: ~/.hermes/cron/jobs.json; output in
~/.hermes/cron/output/{job_id}/{timestamp}.mdN)Ú
ContextVar)Ú	dataclassÚfield)ÚdatetimeÚ	timedeltaÚtimezoneÚUTC©ÚPath)Úget_hermes_home)ÚCLAIM_TTL_INACTIVITY_HEADROOMÚFIRE_CLAIM_SKEW_SECONDSÚFIRE_CLAIM_TTL_SECONDS)Úcron_env_setting)Ú_validate_job_mode_invariants)Ú_claim_owner_is_dead)Ústore_health)	ÚOptionalÚDictÚListÚAnyÚCallableÚSetÚTupleÚUnionÚ
Collection)Únow)Úget_timezone©Úrecord_cron_missed)Úatomic_replaceÚatomic_write_textÚfsync_directoryÚmkstemp_besideç      N@ç        c                ó$   € V ^8„  d   QhR\         /# ©é   Úreturn©Úbool)Úformats   "Ú./home/ubuntu/.hermes/hermes-agent/cron/jobs.pyÚ__annotate__r.   8   s   € ÷ ñ œ$ñ ó    c                 óä   € \         f*   \        P                  ! 4       \        8¼  d    ^ RIHp  V sRs \        \         4      #   \
         d$    \        P                  ! 4       \        ,           s L<i ; i)aF  Import croniter on first use; honor a pre-set HAS_CRONITER override.

An ImportError is NOT latched: caching False would pin a single transient failure
(wrong interpreter, shadowed path) for the whole process lifetime, leaving every
recurring job's next_run_at None until a gateway restart (#127182). Stay None and
re-probe once ``_CRONITER_RETRY_SECONDS`` have passed (so a genuinely absent package does
not cost a finder walk per call on every due-scan tick), and due-scan recovery re-arms the
schedules as soon as the import succeeds. An explicit True/False override always wins.)ÚcroniterT)ÚHAS_CRONITERÚtimeÚ	monotonicÚ_croniter_retry_atr1   ÚImportErrorÚ_CRONITER_RETRY_SECONDSr+   )Ú	_croniters    r-   Ú_ensure_croniterr9   8   s_   € ô Ò¤§¢Ó 0Ô4FÔ Fð	LÝ6Ø ˆHØˆLô ”ÓÐøô ô 	LÜ!%§¢Ó!1Ô4KÕ!KÒð	Lús   ¨
A Á+A/Á.A/Úcronú	jobs.jsonÚticker_heartbeatÚticker_last_successg      >@ÚoutputT)Úfrozenc                   óH   a € ] tR t^vt o ]V 3R lR l4       tV 3R ltRtV tR# )Ú_CronStorePathsc                ó$   <€ V ^8„  d   QhRS[ RR/# )r(   Úcron_dirr)   rA   r	   )r,   Ú__classdict__s   "€r-   r.   Ú_CronStorePaths.__annotate__}   s    ø€ ÷ Jñ J™tð JÐ(9ñ Jr/   c                ó0   € V ! WR ,          VR,          4      # )r;   r>   © )ÚclsrC   s   &&r-   Úfor_dirÚ_CronStorePaths.for_dir|   s   € á�8¨Õ3°XÀÕ5HÓIÐIr/   c                ó>   <€ V ^8„  d   Qh/ S[ ;R&   S[ ;R&   S[ ;R&   # )r(   rC   Ú	jobs_fileÚ
output_dirr	   )r,   rD   s   "€r-   r.   rE   v   s(   ø‡ ‚ á�Nñ ñ �Oñ ñ Ñò	 r/   rG   N)	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__ÚclassmethodrI   Ú__annotate_func__Ú__static_attributes__Ú__classdictcell__©rD   s   @r-   rA   rA   v   s"   ø‡ € ð ÷Jó ðJ÷ ƒ r/   rA   Úcron_store_override)Údefaultc                   ó6   a € ] tR t^…t o RtV 3R lR ltRtV tR# )Ú_SelfRemovalDeliveryzKMutable run-local marker shared with the agent's copied ContextVar context.c                ó    <€ V ^8„  d   QhRS[ /# ©r(   Újob_id©Ústr)r,   rD   s   "€r-   r.   Ú!_SelfRemovalDelivery.__annotate__ˆ   s   ø€ ÷ ñ ™sñ r/   c                ó    € Wn         R V n        R# )FN©r]   Úremoved©Úselfr]   s   &&r-   Ú__init__Ú_SelfRemovalDelivery.__init__ˆ   s   € ØŒØˆŽr/   rb   N)rN   rO   rP   rQ   Ú__doc__rf   rT   rU   rV   s   @r-   rZ   rZ   …   s   ø‡ € ÙU÷ö r/   rZ   Úself_removal_deliveryc                ó$   € V ^8„  d   QhR\         /# r\   r^   )r,   s   "r-   r.   r.   ’   s   € ÷ ,ñ ,¬ñ ,r/   c              #  ó¶   "  € \        V 4      p\        P                  V4      p Vx € \        P                  V4       R#   \        P                  T4       i ; i5i)zEPermit this run's final delivery after it removes its own job record.N)rZ   Ú_self_removal_deliveryÚsetÚreset)r]   ÚmarkerÚtokens   &  r-   Úself_removal_delivery_scoperq   ‘   sH   é € ô " &Ó)€FÜ"×&Ñ& vÓ.€Eð,ØŠä×$Ñ$ UÖ+øÔ×$Ñ$ UÕ+üs   ‚!A¤? ¨A¿AÁAc                ó0   € V ^8„  d   QhR\         R\        /# ©r(   r]   r)   ©r_   r+   )r,   s   "r-   r.   r.   œ   s   € ÷ Añ A¬#ð A´$ñ Ar/   c                ó  a € \         P                  4       pVe$   VP                  S 8w  g   VP                  '       g   R# \        ;QJ d'    V 3R l\        4        4       F  '       d   K   R# 	  R# ! V 3R l\        4        4       4      # )zªWhether the active run deleted exactly its own job record and no record has since taken
its id (a replacement record belongs to another owner, so that stays fail-closed).Fc              3   óJ   <"  € T F  qP                  R 4      S8g  x € K  	  R# 5i©ÚidN©Úget©Ú.0Úitemr]   s   & €r-   Ú	<genexpr>Ú0self_removal_delivery_allowed.<locals>.<genexpr>¢   s   øé € Ð@±K¨D�x‰x˜‹~ Ö'³Kùs   ƒ #T)rl   rz   r]   rc   ÚallÚ	load_jobs)r]   ro   s   f r-   Úself_removal_delivery_allowedr‚   œ   s[   ø€ ô $×'Ñ'Ó)€FØ‚~˜Ÿ™¨&Ô0¸¿¿¸Ùß‹3Ô@´I´KÓ@�3Œ3Ð@Š3Ð@ˆ3Ô@´I´KÓ@Ó@Ð@r/   c                ó$   € V ^8„  d   QhR\         /# r'   )rA   )r,   s   "r-   r.   r.   ©   s   € ÷ 2ñ 2œ_ñ 2r/   c                 ó  € \         P                  4       p V e   V # \        \        \        \
        4      pV\        8w  d   V# \        4       P                  4       pV\        8X  d   V# \        P                  VR,          4      # )aR  Paths pinned to this execution context's profile. Precedence: (1) active use_cron_store()
override; (2) deliberately re-pointed module constants; (3) the ACTIVE profile home via
get_hermes_home(), so re-pointing HERMES_HOME after import uses ITS OWN store rather than the
user's real jobs.json frozen at import; (4) import-time constants.r:   )Ú_cron_store_overriderz   rA   ÚCRON_DIRÚ	JOBS_FILEÚ
OUTPUT_DIRÚ_IMPORT_STOREr   ÚresolveÚ
HERMES_DIRrI   )ÚoverrideÚlive_constantsÚhomes      r-   Ú_current_cron_storer�   ©   sm   € ô
 $×'Ñ'Ó)€HØÒØˆÜ$¤X¬y¼*ÓE€NØœÔ&ØÐÜÓ×$Ñ$Ó&€DØŒzÔØÐÜ×"Ñ" 4¨&¥=Ó1Ð1r/   c                ó:   € V ^8„  d   QhR\         \        ,          /# )r(   rŽ   ©r_   r
   )r,   s   "r-   r.   r.   »   s   € ÷ *ñ *œœt�ñ *r/   c              #  ó  "  € \         P                  \        P                  \	        V 4      P                  4       P                  4       R,          4      4      p Rx € \         P                  V4       R#   \         P                  T4       i ; i5i)z@Route cron storage to ``home`` without mutating process globals.r:   N)r…   rm   rA   rI   r
   Ú
expanduserrŠ   rn   )rŽ   rp   s   & r-   Úuse_cron_storer”   º   sf   é € ô !×$Ñ$Ü×Ñ¤ T£
× 5Ñ 5Ó 7× ?Ñ ?Ó AÀFÕ JÓKóM€Eð*Ûä×"Ñ" 5Ö)øÔ×"Ñ" 5Õ)üs   ‚ABÁA3 ÁBÁ3B
Â
Bc                ó$   € V ^8„  d   QhR\         /# r'   r	   )r,   s   "r-   r.   r.   Å   s   € ÷ ,ñ ,œTñ ,r/   c                 ó*   € \        4       P                  # )z>Return the output directory for the active cron store context.)r�   rM   rG   r/   r-   Úget_cron_output_dirr—   Å   s   € äÓ ×+Ñ+Ð+r/   i  g     À‚@c                ó$   € V ^8„  d   QhR\         /# r'   ©Úfloat)r,   s   "r-   r.   r.   Õ   s   € ÷ 
^ñ 
^¬ñ 
^r/   c                 ó  € \        R4      P                  4       p  V '       d   \        V 4      M\        pV^ 8:  d   \        \        4      # \        V\        ,          \        \        4      4      #   \        \
        3 d
    \        p LSi ; i)u®   One-shot running-claim TTL from ``HERMES_CRON_TIMEOUT``: unset/invalid â†’ 600s â†’ 1800s;
``0`` (unlimited) â†’ the fixed floor; positive N â†’ ``max(N * headroom, floor)``.ÚHERMES_CRON_TIMEOUT)	r   Ústriprš   Ú _DEFAULT_CRON_INACTIVITY_TIMEOUTÚ
ValueErrorÚ	TypeErrorÚONESHOT_RUN_CLAIM_TTL_SECONDSÚmaxr   )ÚrawÚtimeouts     r-   Ú_oneshot_run_claim_ttl_secondsr¥   Õ   st   € ô Ð0Ó
1×
7Ñ
7Ó
9€Cð3ß #”%˜”*Ô)Iˆð �!„|ÜÔ2Ó3Ð3ÜˆwÔ6Õ6¼Ô>[Ó8\Ó]Ð]øô	 œ	Ð"ô 3Ü2Šð3ús   ›A. Á.BÂBc                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   â   s   € ÷ ñ ¬ð ´ñ r/   c                ót   €  ^ RI Hp V! V 4      #   \         d    \        P	                  RT RR7        R# i ; i)u  True when the scheduler in THIS process is still running ``job_id``: the run_claim TTL alone
cannot distinguish "claiming tick died" from "alive but slow". Lazy import: scheduler imports
us.

Direct liveness signal for stale-entry recovery (#62002): the run_claim TTL alone cannot distinguish
"the claiming tick died" from "the run is alive but slow" â€” a run stalled on network I/O (or a laptop
that slept mid-run) legitimately outlives the TTL. The in-process ticker and the run share this process,
so the scheduler's running set settles the common single-gateway case without any claim-age guesswork.

Home-scoped: one process ticks every profile, so the host-wide union of bare job ids would let
profile A's running ``daily-brief`` keep profile B's stale one-shot alive forever.
)Úis_job_runningzsCron running-set liveness check failed for job %r; keeping the entry to avoid deleting a possibly live one-shot runT©Úexc_info)Úcron.schedulerr¨   Ú	ExceptionÚloggerÚwarning)r]   r¨   s   & r-   Ú_job_running_in_this_processr¯   â   sE   € ðÝ1Ù˜fÓ%Ð%øÜô Ü�‰ðCà˜Tð 	ô 	#ò ðús   ‚ �#7¶7c                ó$   € V ^8„  d   QhR\         /# r'   r	   )r,   s   "r-   r.   r.   ú   s   € ÷ 9ñ 9œñ 9r/   c                 ó8   € \        4       P                  R,          # )z=Return the advisory lock path for the current cron directory.z
.jobs.lock)r�   rC   rG   r/   r-   Ú_jobs_lock_filer²   ú   s   € äÓ ×)Ñ)¨LÕ8Ð8r/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# )r(   r¤   r)   )rš   r   r+   )r,   s   "r-   r.   r.   ÿ   s   € ÷ ñ ¤Uð ¬x¼­~ñ r/   c                óÐ  € \         e[   \        P                  ! 4       V,           p  \         P                  ! V \         P                  \         P
                  ,          4       R# \        e7   \        P                  ! V P                  4       \        P                  ^4       R# R#   \         d8    \        P                  ! 4       T8¼  d    R# \        P                  ! R4        K¿  i ; i)a  Bounded exclusive lock: True when acquired, False on timeout, None when no backend exists. A
blocking flock(LOCK_EX) taken under the in-process lock would let a wedged sibling freeze
EVERY cron function forever, so poll LOCK_NB against a deadline; the caller picks the
degraded mode.NTFgš™™™™™¹?)Úfcntlr3   r4   ÚflockÚLOCK_EXÚLOCK_NBÚOSErrorÚsleepÚmsvcrtÚlockingÚfilenoÚLK_LOCK)Úlock_fdr¤   Údeadlines   && r-   Ú_acquire_flockrÁ   ÿ   s˜   € ô
 ÒÜ—>’>Ó# gÕ-ˆØð Ü—’˜G¤U§]¡]´U·]±]Õ%BÔCÙô
 ÒÜ�Š�w—~‘~Ó'¬¯©¸Ô;ÙÙøô ô  Ü—>’>Ó# xÔ/Ú Ü—
’
˜3—ð ús   §:B# Â#%C%ÃC%Ã$C%c                ó   € V ^8„  d   QhRR/# ©r(   r)   NrG   )r,   s   "r-   r.   r.     s   € ÷ 
ñ 
˜tñ 
r/   c                óH  €  \         e'   \         P                  ! V \         P                  4       M<\        e5   \        P                  ! V P                  4       \        P                  ^4       V P                  4        R#   \         d     Li ; i  T P                  4        i ; i)zIUnlock (best effort) and close a lock file opened for ``_acquire_flock``.N)	rµ   r¶   ÚLOCK_UNr»   r¼   r½   ÚLK_UNLCKr¹   Úclose)r¿   s   &r-   Ú_release_flockrÈ     sk   € ðÜÒÜ�KŠK˜¤§¡Õ/ÜÒÜ�NŠN˜7Ÿ>™>Ó+¬V¯_©_¸aÔ@ð 	�‰Žøô ô Ùðûð 	�‰�ús-   ‚-A> ¯B °<A> Á>BÂ	B ÂBÂB ÂB!c            	   #  óü  "  € \        \        R^ 4      p V '       d7   V ^,           \        n         Rx € \        ;P                  ^,          un        R# \        ;_uu_ 4        ^\        n        R\        n        Rp  \        4        \        \        4       RRR7      pVP                  ^ 4       \        V\        4      RJ da   \        P                  R\        \        4       4       \        P                  ! \        4      ;_uu_ 4        VP!                  4        RRR4       Rp Rx € Te   \%        T4       ^ \        n        R\        n         RRR4       R#   \        ;P                  ^,          un        i ; i  + '       g   i     Li; i  \         d!   p\        P#                  RT4        Rp?L�Rp?ii ; i  Te   \%        T4       i i ; i  ^ \        n        R\        n        i ; i  + '       g   i     R# ; i5i)	uÈ  Serialize a load_jobsâ†’modifyâ†’save_jobs critical section: in-process RLock (parallel tick
threads) plus a cross-process flock on ``<cron dir>/.jobs.lock`` (gateway vs. CLI writes â€”
otherwise a `cron pause` could be clobbered and keep firing). Nested calls in one thread
reuse the held lock. Without a flock backend, or on flock timeout (logged loudly), it
degrades to in-process-only locking: a briefly torn cross-process write beats a dead
scheduler.ÚdepthNúa+ú	utf-8-sig©ÚencodingFu«   Timed out after %.0fs waiting for the cron jobs lock (%s) â€” another process is holding it. Proceeding with in-process locking only so the scheduler stays alive (#60703).zSjobs.json cross-process lock unavailable (%s); proceeding with in-process lock only)ÚgetattrÚ_jobs_lock_staterÊ   Ú_jobs_file_lockÚ
load_stampÚensure_dirsÚopenr²   ÚseekrÁ   Ú_JOBS_LOCK_TIMEOUT_SECONDSr­   ÚerrorÚ
contextlibÚsuppressr¹   rÇ   r®   rÈ   )rÊ   r¿   Úes      r-   Ú
_jobs_lockrÛ   !  s‘  é € ô Ô$ g¨qÓ1€EßØ!&¨¥ÔÔð	(Ûä×"Ò" aÕ'Õ"Ùç	ŽØ!"ÔÔð
 '+ÔÔ#Øˆð	/ðJÜ”ÜœÓ0°$ÀÔM�Ø—‘˜Q”Ü! 'Ô+EÓFÈ%ÓOÜ—L‘LðAô 3´OÓ4EôGô $×,Ò,¬W×5Õ5ØŸ™œ÷ 6à"�Gð
,ÛàÒ&Ü" 7Ô+à%&ÔÔ"Ø*.ÔÕ'÷E 
‰øô ×"Ò" aÕ'Ö"ú÷. 6×5ûô ô Jä—‘ð  FØGH÷Jñ JûðJûð Ò&Ü" 7Õ+ð 'ûð &'ÔÔ"Ø*.ÔÕ'ú÷E 
�ˆüsš   ‚,G<¯E ³-G<Á G(Á;B
F
ÄE7	Ä
F
Ä!F8Ä%GÄ4G(Å
G<ÅE4Å4G<Å7FÆF
Æ
F5ÆF0Æ+GÆ0F5Æ5GÆ8G
Ç
GÇG%Ç%G(Ç(G9	Ç3	G<c                ó$   € V ^8„  d   QhR\         /# r\   r^   )r,   s   "r-   r.   r.   X  s   € ÷ 2ñ 2œ3ñ 2r/   c              #  óL  "  € \        4       P                  pVP                  4        RV  2p\        ;_uu_ 4        \        P                  V\        P                  ! 4       4      pRRR4       XP                  \        R7      '       g   \        P                  RV4       Rx € R# \        P                  P                  R/ 4      pW$9   d    WB,          x € VP                  4        R#  \        4        VR\         P"                  ! \         P$                  V4      P&                   R2,          pRpRp \)        VR	R
R7      pVP+                  ^ 4       \-        V\        4      pVf   \        P                  R4       MV'       g   \        P                  RV4       \/        V4      pWtV&    Vx € VP3                  VR4       Ve%   V'       d   \5        V4       MVP7                  4        VP                  4        R#   + '       g   i     EL�; i  TP                  4        i ; i  \0         d!   p	\        P                  RY	4        Rp	?	L¤Rp	?	ii ; i  TP3                  TR4       Te&   T'       d   \5        T4       i TP7                  4        i i ; i  TP                  4        i ; i5i)züSerialize one job's owner mutations and external side effects. Unlike the global jobs lock
this may be held across network delivery; scoped to one profile + job so unrelated jobs keep
progressing. Fails closed when cross-process locking is unavailable.ú::N)r¤   z9Timed out waiting for local fire fence %s; failing closedFÚheldz.fire-z.lockrË   úutf-8rÍ   z1No cross-process lock backend for cron fire fencez3Timed out waiting for fire fence %s; failing closedz&Cron fire fence unavailable for %s: %s)r�   rC   rŠ   Ú_fire_fence_locks_guardÚ_fire_fence_locksÚ
setdefaultÚ	threadingÚRLockÚacquirerÖ   r­   r×   Ú_fire_fence_lock_stateÚ__dict__ÚreleaserÓ   ÚuuidÚuuid5ÚNAMESPACE_URLÚhexrÔ   rÕ   rÁ   r+   r¹   ÚpoprÈ   rÇ   )
r]   rC   Úlock_keyÚ
local_lockÚ
held_locksÚ	lock_pathr¿   ÚacquiredÚresultÚexcs
   &         r-   Ú_fire_job_lockrö   W  s  é € ô
 #Ó$×-Ñ-€HØ×"Ñ"Ó$Ð% R¨ xÐ0€Hß	 Ö	 Ü&×1Ñ1°(¼I¿OºOÓ<MÓNˆ
÷ 
!ð ×ÑÔ&@Ð×AÒAÜ�‰ÐPÐRZÔ[ØŠÙä'×0Ñ0×;Ñ;¸FÀBÓG€JØÔð	!ØÕ&Ò&à×ÑÔ ÙðÜŒØ ¬¯
ª
´4×3EÑ3EÀxÓ(P×(TÑ(TÐ'UÐUZÐ[Õ[ˆ	ØˆØˆð
	PÜ˜9 d°WÔ=ˆGØ�L‰L˜ŒOÜ# GÔ-GÓHˆFØŠ~Ü—‘ÐPÕQßÜ—‘ÐRÐT]Ô^Ü˜F“|ˆHð  (�8Ñð	$ØŠNà�N‰N˜8 TÔ*ØÒ"ßÜ" 7Õ+à—M‘M”Oà×ÑÖ÷Y 
!×	 Ð	 ûð ×ÑÕ ûô" ô 	PÜ�L‰LÐAÀ6×OÑOûð	Pûð �N‰N˜8 TÔ*ØÒ"ßÜ" 7Õ+à—M‘M•Oð	 #ûð 	×ÑÕüs©   ‚8J$º*G8Á$#J$ÂAJ$Ã
H ÃJ$Ã(AJ Ä1AH! Æ!H! Æ#J Æ(I Æ,J Ç
J Ç&J$Ç8H		È	J$ÈHÈJ$È!IÈ,IÉJ ÉIÉJ ÉJÉ.JÊJ ÊJ!Ê!J$c                óV   € V ^8„  d   QhR\         R\        . \        3,          R\        /# )r(   r]   Úfnr)   )r_   r   r   )r,   s   "r-   r.   r.   �  s*   € ÷ ñ œcð ¤x°´C°Õ'8ð ¼Sñ r/   c                óš   € \        V 4      ;_uu_ 4       pV'       g    RRR4       R# V! 4       uuRRR4       #   + '       g   i     R# ; i)zYRun ``fn()`` holding the job's fire fence; False (fail closed) when it can't be acquired.NF)rö   )r]   rø   ró   s   && r-   Ú_under_fire_fencerú   �  s3   € ä	˜×	Ô	 8ßØ÷ 
 Ñ	ñ ‹t÷ 
 ×	×	Ó	ús   ”
9¨9¹A
	c                ó0   € V ^8„  d   QhR\         R\         /# )r(   r]   Úexpected_ownerr^   )r,   s   "r-   r.   r.   –  s   € ÷ ñ œSð ´Sñ r/   c          	   #  ó  a "  € \        S 4      ;_uu_ 4       pV'       g   Rx €  RRR4       R# \        4       ;_uu_ 4        \        V 3R l\        4        4       R4      p\	        V\
        4      '       d   VP                  R4      MRp\	        V\
        4      ;'       d    VP                  R4      V8H  pVf   \        S 4      pRRR4       Xx € RRR4       R#   + '       g   i     L; i  + '       g   i     R# ; i5i)z¡Hold a per-job fence while an owner performs an external side effect. A missing record
is accepted only for the active run that removed this exact job (#111039).FNc              3   óV   <"  € T F  qP                  R 4      S8X  g   K  Vx € K   	  R# 5irw   ry   r{   s   & €r-   r~   Ú#fire_claim_fence.<locals>.<genexpr>ž  s    øé € ÐQ© ¿¹À»È&Ñ8PŸš«ùs   ƒ)Ÿ
)Ú
fire_claimÚby)rö   rÛ   Únextr�   Ú
isinstanceÚdictrz   r‚   )r]   rü   ró   ÚjobÚclaimÚ
owns_claims   f$    r-   Úfire_claim_fencer  •  sº   øé € ô 
˜×	Ô	 8ßØŠKØ÷ 
 Ñ	ô �\�\ÜÔQ¬¬ÓQÐSWÓXˆCÜ-7¸¼T×-BÒ-B�C—G‘G˜LÔ)ÈˆEÜ# E¬4Ó0×VÐV°U·Y±Y¸t³_ÈÑ5VˆJØŠ{Ü:¸6ÓB�
÷ ð Ò÷ 
 Ñ	÷ �\ú÷	 
 ×	Ð	üsE   ƒD—C-¥
D¯C-ÁAC	Â $C	ÃC-Ã
DÃC*Ã%C-Ã-C>	Ã8	Drx   c                ó0   € V ^8„  d   QhR\         R\        /# rs   r‘   )r,   s   "r-   r.   r.   «  s   € ÷ 	3ñ 	3œCð 	3¤Dñ 	3r/   c                óJ  € \        T ;'       g    R4      P                  4       pV'       dP   VR9   gI   RV9   gB   RV9   g;   \        V4      P                  4       '       g   \        V4      P                  '       d   \        RV : 24      h\        4       P                  V,          # )z˜Resolve a job's output directory, rejecting any path-escape attempt (``..``, absolute
paths, separators): only a single safe path component is accepted.Ú Ú/Ú\z%Invalid cron job id for output path: >   Ú.ú..)r_   r�   r
   Úis_absoluteÚdriverŸ   r�   rM   )r]   Útexts   & r-   Ú_job_output_dirr  «  s|   € ô ˆv�|ˆ|˜Ó×"Ñ"Ó$€Dç�D˜KÔ'¨3°$¬;¸$À$¼,Ü�‹:×!Ñ!×#Ò#¤t¨D£z×'7×'7Ð'7äÐ@ÀÁ
ÐKÓLÐLÜÓ ×+Ñ+¨dÕ2Ð2r/   c                ó~   € V ^8„  d   QhR\         \        ,          R\         \        ,          R\        \        ,          /# )r(   ÚskillÚskillsr)   )r   r_   r   Úlist)r,   s   "r-   r.   r.   ·  s0   € ÷ ñ ¤¬#¥ð ¼xÌ½}ð ÔX\Ô]`ÕXañ r/   c                ó  € Vf   V '       d   V .M. pM%\        V\        4      '       d   V.pM\        V4      p. pV FI  p\        T;'       g    R4      P                  4       pV'       g   K0  WS9  g   K8  VP	                  V4       KK  	  V# )zPNormalize legacy/single-skill and multi-skill inputs into a unique ordered list.r  )r  r_   r  r�   Úappend)r  r  Ú	raw_itemsÚ
normalizedr}   r  s   &&    r-   Ú_normalize_skill_listr  ·  sv   € à‚~ß$�U‘G¨"‰	Ü	�FœC×	 Ò	 Ø�H‰	ä˜“Lˆ	Ø€JÛˆÜ�4—:�:˜2‹×$Ñ$Ó&ˆß‰4�DÖ*Ø×Ñ˜dÖ#ñ ð Ðr/   c                ót   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          /# ©r(   r  r)   ©r  r_   r   )r,   s   "r-   r.   r.   Ç  s*   € ÷ ñ œT¤#¤s (�^ð ´´S¼#°Xµñ r/   c                ó¤   € \        V 4      p\        VP                  R4      VP                  R4      4      pW!R&   V'       d
   V^ ,          MRVR&   V# )zLReturn a job dict with canonical `skills` and legacy `skill` fields aligned.r  r  N)r  r  rz   )r  r  r  s   &  r-   Ú_apply_skill_fieldsr!  Ç  sI   € ä�c“€JÜ" :§>¡>°'Ó#:¸J¿N¹NÈ8Ó<TÓU€FØ!ˆxÑß'-˜& ž)°4€JˆwÑØÐr/   c                ó<   € V ^8„  d   QhR\         R\        R\        /# )r(   ÚvalueÚfallbackr)   ©r   r_   )r,   s   "r-   r.   r.   Ð  s!   € ÷ 5ñ 5œCð 5¬3ð 5¼ñ 5r/   c                ó$   € V f   V# \        V 4      # )zFCoerce legacy/hand-edited nullable cron fields to strings for readers.r^   )r#  r$  s   &&r-   Ú_coerce_job_textr'  Ð  s   € à’}ˆ8Ð4¬#¨e«*Ð4r/   ÚscriptÚno_agentz†Cron job has nothing to run: the prompt is blank and no script or skill(s) are set. Provide a prompt, a script, or at least one skill.c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  ©r  r_   r   r+   )r,   s   "r-   r.   r.   Þ  s&   € ÷ Jñ Jœd¤3¬ 8�nð J´ñ Jr/   c                ó˜  a € \        S P                  R4      4      P                  4       '       g/   \        S P                  R4      4      P                  4       '       d   R# \        S P                  R4      S P                  R4      4      '       d   R# \        ;QJ d    V 3R lR 4       F  '       g   K   R# 	  R# ! V 3R lR 4       4      # )	z®True when a job record has nothing runnable (blank prompt, no script, no skills) AND at
least one payload field is explicitly present. ``no_agent`` already requires a script.Úpromptr(  Fr  r  c              3   ó,   <"  € T F	  qS9   x € K  	  R # 5i©NrG   )r|   Úkr  s   & €r-   r~   Ú'job_payload_is_empty.<locals>.<genexpr>å  s   øé € ÐIÑ!H˜A�CŽxÓ!Hùó   ƒT)r-  r(  r  r  )r'  rz   r�   r  Úany©r  s   fr-   Újob_payload_is_emptyr5  Þ  s�   ø€ ô ˜Ÿ™ Ó)Ó*×0Ñ0×2Ò2Ô6FÀsÇwÁwÈxÓGXÓ6Y×6_Ñ6_×6aÒ6aÙÜ˜SŸW™W WÓ-¨s¯w©w°xÓ/@×AÒAÙß‹3ÔIÑ!HÓI�3Œ3ÐIŠ3ÐIˆ3ÔIÑ!HÓIÓIÐIr/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r  )r,   s   "r-   r.   r.   è  s"   € ÷ ñ ¤4¬¬S¨¥>ð ´cñ r/   c                 óT  € \        V P                  R 4      4      P                  4       pV'       d   V# V P                  R4      p\        V\        4      '       d@   R F7  p\        VP                  V4      4      P                  4       pV'       g   K5  Vu # 	  R# Ve   \        V4      # R# )Úschedule_displayÚscheduleÚ?)Údisplayr#  ÚexprÚrun_at)r'  rz   r�   r  r  r_   )r  r;  r9  Úkeyr  s   &    r-   Ú_schedule_display_for_jobr?  è  sŒ   € Ü˜sŸw™wÐ'9Ó:Ó;×AÑAÓC€GßØˆØ�w‰w�zÓ"€HÜ�(œD×!Ò!Û9ˆCÜ# H§L¡L°Ó$5Ó6×<Ñ<Ó>ˆDß‰tØ’ñ :ñ ð 
Ò	Ü�8‹}ÐÙr/   c                ót   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          /# r  r  )r,   s   "r-   r.   r.   ÷  s*   € ÷ ñ œt¤C¬ H�~ð ´$´s¼C°xµ.ñ r/   c                óf  € \        V 4      p\        VP                  R4      R4      ;q!R&   \        VP                  R4      4      ;q1R&   \        VP                  R4      4      P                  4       pV'       g™   T;'       gn    VP                  R4      '       d   VR,          ^ ,          MR;'       g=    \        VP                  R4      4      P                  4       ;'       g    T;'       g    RpVR	,          P                  4       ;'       g    RpWAR&   \	        V4      VR
&   \        V4      VR&   V# )z¨Read-safe job shape: legacy/hand-edited records may have nullable ``prompt``, ``name``,
``schedule_display``. Storage is untouched; consumers never crash on formatting.rx   Úunknownr-  Únamer  r  r(  úcron jobºNé2   Nr8  Ústate)r!  r'  rz   r�   r?  Úeffective_job_state)r  r  r]   r-  rC  Úlabel_sources   &     r-   Ú_normalize_job_recordrJ  ÷  s"  € ô % SÓ)€JÜ 0°·±ÀÓ1EÀyÓ QÐQ€F˜ÑÜ$4°Z·^±^ÀHÓ5MÓ$NÐN€F˜Ñ!Ü˜JŸN™N¨6Ó2Ó3×9Ñ9Ó;€Dßà÷ ð Ø+5¯>©>¸(×+CÒ+C�
˜8Õ$ QÖ'È÷ð ä 
§¡¨xÓ 8Ó9×?Ñ?ÓA÷ð ð ÷ð ð ð 	ð ˜CÕ ×&Ñ&Ó(×6Ð6¨JˆØˆvÑÜ%>¸zÓ%J€JÐ!Ñ"ô .¨jÓ9€JˆwÑØÐr/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r+  )r,   s   "r-   r.   r.     s&   € ÷ `ñ `œ4¤¤S �>ð `¬dñ `r/   c                óœ   € \        V P                  R4      4      P                  4       R8H  ;'       g    \        V P                  R4      4      # )z>True when the record carries any operator-facing pause signal.rG  ÚpausedÚ	paused_at)r'  rz   r�   r+   r4  s   &r-   Ú_has_pause_markerrO    s;   € ä˜CŸG™G GÓ,Ó-×3Ñ3Ó5¸ÑA×_Ð_ÄTÈ#Ï'É'ÐR]ÓJ^ÓE_Ð_r/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r+  )r,   s   "r-   r.   r.     s&   € ÷ Iñ Iœœc¤3˜h�ð I¬Dñ Ir/   c                óh   € \        V P                  RR4      4      ;'       d    \        V 4      '       * # )z§True iff the scheduler may fire this job: ``enabled`` plus pause markers as a second gate so
a contradictory half-paused record never fires even before self-heal runs.ÚenabledT)r+   rz   rO  r4  s   &r-   Úis_job_runnablerS    s+   € ô �—‘˜	 4Ó(Ó)×HÐHÔ2CÀCÓ2HÔ.HÐHr/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r  )r,   s   "r-   r.   r.     s"   € ÷ !ñ !œT¤#¤s (�^ð !´ñ !r/   c                ó8  € \        V P                  R4      4      P                  4       pVR9   d   V# V P                  RR4      '       g'   \        V 4      '       g   VR8X  d   R# T;'       g    R# VR8X  g   V P                  R4      '       d   R# T;'       g    R# )z²Operator-facing state derived from ``enabled``: an enabled job must never display as paused
(list looked frozen while jobs kept firing). Terminal states are preserved regardless.rG  rR  TrM  rN  Ú	scheduled>   r×   Ú	completed)r'  rz   r�   rO  )r  Ústoreds   & r-   rH  rH    s‰   € ô ˜cŸg™g gÓ.Ó/×5Ñ5Ó7€FØÐ'Ô'ØˆØ�7‰7�9˜d×#Ò#Ü˜S×!Ò! V¨xÔ%7ÙØ×!Ð!˜Ð!à�Ô˜SŸW™W [×1Ò1ÙØ× Ð �[Ð r/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r+  )r,   s   "r-   r.   r.   *  s"   € ÷ 6ñ 6œœc¤3˜h�ð 6¬Dñ 6r/   c                ó*   € V P                  R4      R9   # )z=Return whether a job record is in a terminal scheduler state.rG  >   r×   rW  ry   r4  s   &r-   Úis_terminal_jobr[  *  s   € à�7‰7�7ÓÐ5Ñ5Ð5r/   c                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# r  r+  )r,   s   "r-   r.   r.   /  s"   € ÷ ñ ¤4¬¬S¨¥>ð ´dñ r/   c                ó”   € V P                  R4      R8H  ;'       d.    V P                  R4      ;'       g    / P                  R4      R9   # )uv  True for a recurring job stuck in ``state=error`` (set ONLY when ``compute_next_run()`` fails
for a cron/interval job: croniter missing, malformed schedule). Such a job still has future
occurrences once the issue resolves, so treating it as terminal would block due-scan self-heal,
pre-advance, dispatch claim and ``resume_job`` â€” wedging it forever.

Unlike ``state=completed`` (a one-shot that genuinely has no more occurrences, ever), an error-state
recurring job still has a schedule with future occurrences once the underlying issue resolves â€” it is
stuck pending a ``next_run_at`` recompute, not truly done. See #16265.
rG  r×   r9  Úkind>   r:   Úintervalry   r4  s   &r-   Ú_is_recoverable_error_jobr`  /  sK   € ð 	�‰�Ó˜GÑ#÷ 	Lð 	LØ�W‰W�ZÓ ×&Ð& B×+Ñ+¨FÓ3Ð7KÑKðr/   c                ó$   € V ^8„  d   QhR\         /# ©r(   Úpathr	   )r,   s   "r-   r.   r.   ?  s   € ÷ ñ ”dñ r/   c                ó"   € ^ RI Hp V! V 4       R# )zŸOwner-only (0700) via the shared helper, so cron/ and cron/output honor the same managed/
container/HERMES_HOME_MODE rules as the rest of HERMES_HOME (#10757).)Ú_secure_dirN)Úhermes_cli.configre  )rc  Ú_shared_secure_dirs   & r-   re  re  ?  s   € õ DÙ�tÖr/   c                ó$   € V ^8„  d   QhR\         /# rb  r	   )r,   s   "r-   r.   r.   F  s   € ÷ ñ ”tñ r/   c                ó"   € ^ RI Hp V! V 4       R# )zJOwner-only (0600) via the shared helper (managed/container skip included).)Ú_secure_fileN)rf  rj  )rc  Ú_shared_secure_files   & r-   rj  rj  F  s   € åEÙ˜Ör/   c                ó^   € V ^8„  d   QhR\         R\        \        P                  ,          RR/# )r(   rc  Úbeforer)   N)r
   r   ÚosÚstat_result)r,   s   "r-   r.   r.   L  s*   € ÷ 3ñ 3¤4ð 3´¼"¿.¹.Õ1Ið 3Èdñ 3r/   c                óÜ  € Ve   \         P                  R8w  d   R# \        \         RR4      p\        \         RR4      pVe   Vf   R#  V! 4       pV^ 8w  g$   VP                  VP                  3WC! 4       38X  d   R# \         P
                  ! WP                  VP                  4       R#   \         d8   p\        P                  RYP                  TP                  T4        Rp?R# Rp?ii ; i)a  Restore a rewritten file's previous owner (POSIX, root writer only): atomic replace makes the
file owned by the writer's euid, so a root CLI write (e.g. ``docker exec``) against the
unprivileged gateway's store would flip jobs.json to root:root 0600 and lock the ticker out.NÚposixÚgeteuidÚgetegidu«   Could not restore ownership of %s to uid=%s gid=%s after rewrite: %s â€” if the gateway runs as a different user, its cron ticker may now be locked out (see issue #68483).)	rn  rC  rÏ   Úst_uidÚst_gidÚchownr¹   r­   r®   )rc  rm  rr  rs  ÚeuidrÚ   s   &&    r-   Ú_preserve_file_ownershiprx  L  s½   € ð ‚~œŸ™ GÔ+ÙÜ”b˜) TÓ*€GÜ”b˜) TÓ*€GØ‚˜'š/Ùð
3Ù‹yˆØ�1Œ9˜Ÿ™¨¯©Ð6¸4ÀÃÐ:KÔKÙÜ
�Š�—}‘} f§m¡mÖ4øÜô 3Ü�‰ð0ð —-‘- §¡°÷		3ò 	3ûð3ús   Á	1B) Á<+B) Â)C+Â4,C&Ã&C+c                ó0   € V ^8„  d   QhR\         R\        /# )r(   rc  r)   )r
   r+   )r,   s   "r-   r.   r.   c  s   € ÷ $ñ $¤ð $¬$ñ $r/   c                óü   € \         P                  ! \        \        4      ;_uu_ 4        RV P	                  4       P
                  9   d    RRR4       R#  RRR4       RV P
                  9   #   + '       g   i     L; i)z³True if *path* is under ``<hermes_home>/profiles/<name>/`` (default/custom homes are not).
Checks the resolved path (symlinked parents) and the raw path (symlinked profile homes).ÚprofilesNT)rØ   rÙ   r¹   ÚRuntimeErrorrŠ   Úparts©rc  s   &r-   Ú_is_named_profile_pathr  c  sX   € ô 
×	Ò	œW¤l×	3Õ	3Ø˜Ÿ™›×-Ñ-Ô-Ø÷ 
4Ñ	3Ø-÷ 
4ð ˜Ÿ™Ñ#Ð#÷ 
4×	3ús   ¨!A+Á+A;	c                ó(   € V ^8„  d   QhR\         RR/# )r(   rC   r)   Nr	   )r,   s   "r-   r.   r.   l  s   € ÷ 0ñ 0œtð 0¨ñ 0r/   c                óv   € \        V 4      '       d   V P                  RR7       R# V P                  RRR7       R# )a
  Create a cron directory without resurrecting a deleted profile home: a stale multiplex
scheduler may still hold a deleted profile's path, so named profiles use ``parents=False`` and
fail closed. Default/custom homes keep ``parents=True`` so first-run creation works.T)Úexist_okN)Úparentsr‚  )r  Úmkdir)rC   s   &r-   Ú_ensure_cron_dirr…  l  s0   € ô ˜h×'Ò'Ø�‰ ˆÔ%ÙØ‡N�N˜4¨$€NÖ/r/   c                 óÂ   € \        4       p \        V P                  4       \        V P                  4       \	        V P                  4       \	        V P                  4       R# )z6Ensure cron directories exist with secure permissions.N)r�   r…  rC   rM   re  )Ústores    r-   rÓ   rÓ   v  s>   € äÓ!€EÜ�U—^‘^Ô$Ü�U×%Ñ%Ô&Ü�—‘ÔÜ�× Ñ Ö!r/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# )r(   Úrepeatr)   )r   r   Úint)r,   s   "r-   r.   r.   �  s   € ÷ 0ñ 0¤3ð 0¬8´C­=ñ 0r/   c                ó  € V f   R# \        V \        4      '       d=   V P                  4       P                  4       pVR9   d   R# VR9   d   ^#  \	        V4      p V ^ 8:  d   R# \	        V 4      #   \
         d    \        RT : R24      hi ; i)a5  Coerce a repeat value (int or user-facing string) into ``Optional[int]``:
``'forever'``-family -> None, ``'once'``-family -> 1, numeric -> int, 0/negative -> None,
else ValueError.

The tool schema exposes ``repeat`` as an integer, but agents and users legitimately pass the user-facing
strings ``'forever'``/``'once'`` or numeric strings (``'3'``). Uncoerced strings previously died with
``'<=' not supported between instances of 'str' and 'int'`` at create
(#66824/#64520/#7142/#71987/#95706) and were stored raw by update paths, breaking ``mark_job_run``
later.
NzInvalid repeat value z': use an integer, 'forever', or 'once'.)ÚforeverÚinfiniteÚinfÚnoner  )ÚonceÚoneÚ1x)r  r_   r�   ÚlowerrŠ  rŸ   )r‰  Ú
repeat_strs   & r-   Únormalize_repeat_valuer•  �  s™   € ð ‚~ÙÜ�&œ#×ÒØ—\‘\“^×)Ñ)Ó+ˆ
ØÐCÔCÙØÐ.Ô.Ùð	Ü˜“_ˆFð ˜Q”;ˆ4Ð/¤C¨£KÐ/øô ô 	ÜØ'¨¡zð 2(ð )óð ð	ús   ÁA- Á-B	ÚmÚhÚdi   c                ó0   € V ^8„  d   QhR\         R\        /# )r(   Úsr)   )r_   rŠ  )r,   s   "r-   r.   r.   ¡  s   € ÷ 	<ñ 	<”cð 	<œcñ 	<r/   c                óT  € V P                  4       P                  4       p \        P                  ! RV 4      pV'       g   \	        RV  R24      hVP                  ^4      '       d   \        VP                  ^4      4      M^pV\        VP                  ^4      ^ ,          ,          ,          # )u]   Parse a duration into minutes: "30m" â†’ 30, "2h" â†’ 120, "1d" â†’ 1440, bare "hour" â†’ 60.zD^(\d*)\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days)$zInvalid duration: 'zQ'. Use format like '30m', '2h', '1d', or a bare unit like 'hour' (defaults to 1).)r�   r“  ÚreÚmatchrŸ   ÚgrouprŠ  Ú_DURATION_MULTIPLIERS)rš  r�  r#  s   &  r-   Úparse_durationr   ¡  s‡   € à	�‰‹	�‰Ó€AÜ�HŠHÐ\Ð^_Ó`€EßÜØ! ! ð %:ð :ó;ð 	;ð $)§;¡;¨q§>¢>ŒC�—‘˜A“Ô°q€EØÔ(¨¯©°Q«¸Õ):Õ;Õ;Ð;r/   ÚsundayÚ0ÚsunÚmondayÚ1ÚmonÚtuesdayÚ2ÚtueÚtuesÚ	wednesdayÚ3ÚwedÚwedsÚthursdayÚ4ÚthuÚthurÚthursÚfridayÚ5ÚfriÚsaturdayÚ6ÚsatÚdayÚ*ÚdailyÚeverydayÚweekdayz1-5ÚweekdaysÚweekendz0,6Úweekendsc                óF   € V ^8„  d   QhR\         R\        \        ,          /# )r(   r  r)   )r_   r   Útuple)r,   s   "r-   r.   r.   Á  s   € ÷ ñ œCð ¤H¬U¥Oñ r/   c                ó  € V P                  4       P                  4       P                  RR4      pV'       g   R# VR9   d   R# VR8X  d   R	# \        P                  ! RV4      pV'       g   R# \        VP                  ^4      4      p\        VP                  ^4      ;'       g    ^ 4      pVP                  ^4      pV'       d/   ^Tu;8:  d
   ^8:  g    R#  R# V^,          VR8X  d   ^M^ ,           pV^8”  g   V^;8”  d   R# W43# )
z’Parse ``9am``/``9:30am``/``14:00``/``7`` (bare 24h hour)/``noon``/``midnight`` into a
24-hour ``(hour, minute)`` tuple, or None when unrecognized.Ú r  NÚmidnightz ^(\d{1,2})(?::(\d{2}))?(am|pm)?$Úpm)ÚnoonÚmidday)é   é    )rË  rË  )r�   r“  Úreplacerœ  r�  rŠ  rž  )r  Útr�  ÚhourÚminuteÚmeridiems   &     r-   Ú_parse_clock_timerÑ  Á  sÝ   € ð 	�
‰
‹×ÑÓ×$Ñ$ S¨"Ó-€AßÙØÐÔØˆØˆJ„ØˆÜ�HŠHÐ8¸!Ó<€EßÙÜˆu�{‰{˜1‹~Ó€DÜ�—‘˜Q“×$Ð$ 1Ó%€FØ�{‰{˜1‹~€HßØ�DŽ˜B�Ùð ÙØ�b�y (¨dÔ"2™B¸Õ:ˆØˆb„y�F˜R”KÙØˆ>Ðr/   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# )r(   Úrestr)   )r_   r   )r,   s   "r-   r.   r.   Ú  s   € ÷ !(ñ !(¤ð !(¬´#­ñ !(r/   c                ób  € V P                  4       P                  RR4      P                  4       pV'       g   R# \        P	                  V^ ,          4      p^pVf|   . p\        V4      p\        V4       FD  w  rVVR8X  d   K  \        P	                  V4      pVf   Tp MWt9  g   K3  VP                  V4       KF  	  V'       g   R# RP                  V4      pWR pV'       d   V^ ,          R8X  d
   VR,          pV'       g   R# \        RP                  V4      4      p	V	f   R# V	w  r«V RV
 RV 2# )zµConvert ``<when> [at] <time>`` ("monday 9am", "weekday at 9am", "monday, wednesday at 9am")
to a 5-field cron expr, or None so ``parse_schedule`` can fall back to the interval path.Ú,rÅ  NÚandÚatºé   NNz * * )r“  rÌ  ÚsplitÚ_DAYSPEC_TO_CRON_DOWrz   ÚlenÚ	enumerateÚ_WEEKDAY_TO_CRON_DOWr  ÚjoinrÑ  )rÓ  ÚtokensÚdowÚidxÚdaysÚiÚtokÚmappedÚtime_tokensÚparsedrÎ  rÏ  s   &           r-   Ú_natural_every_to_cronré  Ú  s  € ð �Z‰Z‹\×!Ñ! # sÓ+×1Ñ1Ó3€FßÙä
×
"Ñ
" 6¨!¥9Ó
-€CØ
€CØ
‚{ØˆÜ�&‹kˆÜ Ö'‰FˆAØ�eŒ|ÙÜ)×-Ñ-¨cÓ2ˆFØŠ~Ø�ÙØÖ!Ø—‘˜FÖ#ñ (÷ ÙØ�h‰h�t‹nˆØ˜�,€Kß�{ 1•~¨Ô-Ø! "•oˆßÙÜ˜sŸx™x¨Ó4Ó5€FØ‚~ÙØ�L€DØˆX�Q�t�f˜E # Ð'Ð'r/   c                óv   € V ^8„  d   QhR\         R\         R\         R\         R\        \         \        3,          /# )r(   r<  r;  Úmissing_croniterÚinvalid_labelr)   ©r_   r  r   )r,   s   "r-   r.   r.   þ  s<   € ÷ 
>ñ 
>Ü
ð
>Üð
>Ü/2ð
>ÜCFð
>ä	Œ#Œsˆ(…^ñ
>r/   c           	     ó´   € \        4       '       g   \        V R24      h \        V 4       RRRT R	T/#   \         d   p\        RT RT RT 24      hRp?ii ; i)
zLValidate a cron expression with croniter and build the stored schedule dict.z# Install with: pip install croniterzInvalid z 'ú': Nr^  r:   r<  r;  )r9   rŸ   r1   r¬   )r<  r;  rë  rì  rÚ   s   &&&& r-   Ú_cron_schedulerð  þ  sv   € ô ×ÒÜÐ,Ð-Ð-PÐQÓRÐRðFÜ�Œð �F˜F D¨)°WÐ=Ð=øô ô FÜ˜8 M ?°"°W°I¸SÀÀÐDÓEÐEûðFús    3 ³A¾AÁAc                óR   € V ^8„  d   QhR\         R\        \        \        3,          /# )r(   Úminutesr)   )rŠ  r  r_   r   )r,   s   "r-   r.   r.     s&   € ÷ Tñ T¤ð T¬¬S´#¨X­ñ Tr/   c                 ó   € R RRV RRV  R2/# )r^  r_  rò  r;  úevery r–  rG   ©rò  s   &r-   Ú_interval_schedulerö    s   € Ø�J 	¨7°IÀÈÀyÐPQÐ?RÐSÐSr/   c                óR   € V ^8„  d   QhR\         R\        \         \        3,          /# ©r(   r9  r)   rí  )r,   s   "r-   r.   r.     s&   € ÷ Iñ IœSð I¤T¬#¬s¨(¥^ñ Ir/   c           	     óÐ  € V P                  4       p T pV P                  4       pVP                  R4      pV'       d   V R,          P                  4       MTp\        V4      pVe   V'       d   RMRp\	        WQRV R2R4      # V'       d   \        \        V4      4      # V P                  4       p\        V4      ^8¼  dV   \        ;QJ d#    R	 VR
,           4       F  '       d   K   RM	  RM! R	 VR
,           4       4      '       d   \	        W RR4      # RV 9   g   \        P                  ! RV 4      '       dr    \        P                  ! V 4      pVP                  f%   VP                  \!        4       P                  R7      pRRRVP#                  4       RRVP%                  R4       2/# VP                  R4      '       d†   V R,          P                  4       p
 \        V
4      p\!        4       pTP)                  \*        4      \-        TR7      ,           P)                  TP                  4      pRRRTP#                  4       RRT
 2/# \.        P0                  ! \&        4      ;_uu_ 4        \        \        V 4      4      uuRRR4       #   \&         d   p	\'        RT  RT	 24      hRp	?	ii ; i  \&         d    \'        RT
 R24      hi ; i  + '       g   i     M; i\'        R T R!24      h)"zèParse a schedule string into ``{"kind": "once"|"interval"|"cron", ...}`` with ``run_at`` /
``minutes`` / ``expr``. "30m" and "every 30m" are recurring intervals; "every monday 9am" and
"0 9 * * *" are cron; an ISO timestamp is once.rô  :é   NNNzevery monday 9amzweekdays at 9amzWeekday/time schedules like 'z!' require the 'croniter' package.r9  c              3   óP   "  € T F  p\         P                  ! R V4      x € K  	  R# 5i)z^[A-Za-z\d\*\-,/]+$N)rœ  r�  ©r|   Úps   & r-   r~   Ú!parse_schedule.<locals>.<genexpr>(  s   é € ÐVÉIÀqœrŸxšxÐ(>À×BÐBËIùs   ‚$&:Né   NFTz,Cron expressions require 'croniter' package.zcron expressionÚTz^\d{4}-\d{2}-\d{2}©Útzinfor^  r�  r=  r;  zonce at z%Y-%m-%d %H:%MzInvalid timestamp 'rï  zin :é   NNzInvalid duration 'z*' after 'in '. Use e.g. 'in 30m', 'in 2h'.rõ  zonce in zInvalid schedule 'a  '. Use:
  - Interval: '30m', 'every 30m', 'every 2h' (recurring)
  - One-shot delay: 'in 30m', 'in 2h' (fires once)
  - Weekly/daily: 'every monday 9am', 'weekdays at 9am' (recurring)
  - Cron: '0 9 * * *' (cron expression)
  - Timestamp: '2026-02-03T14:00:00' (one-shot at time))r�   r“  Ú
startswithré  rð  rö  r   rÚ  rÜ  r€   rœ  r�  r   Úfromisoformatr  rÌ  Ú_hermes_nowÚ	isoformatÚstrftimerŸ   Ú
astimezoner   r   rØ   rÙ   )r9  ÚoriginalÚschedule_lowerÚis_everyrÓ  Ú	cron_exprÚexampler}  ÚdtrÚ   Úduration_strrò  r   r=  s   &             r-   Úparse_scheduler    s±  € ð �~‰~Ó€HØ€HØ—^‘^Ó%€Nð ×(Ñ(¨Ó2€Hß#+ˆ8�B�<×ÑÔ°€DÜ& tÓ,€IàÒß(0Ñ$Ð6GˆÜØØ+¨G¨9Ð4UÐVÐXbódð 	d÷ Ü!¤.°Ó"6Ó7Ð7ð �N‰NÓ€EÜ
ˆ5ƒz�Q„Ÿ3›3ÑVÈEÐRTÎIÓVŸ3Ÿ3š3ÑVÈEÐRTÎIÓV×VÒVÜØÐ NÐPaócð 	cð ˆh„œ"Ÿ(š(Ð#8¸(×CÒCð	EÜ×'Ò'¨Ó1ˆBð �y‰yÒ Ø—Z‘Z¤{£}×';Ñ';�ZÓ<�à˜Ø˜"Ÿ,™,›.Ø˜X b§k¡kÐ2BÓ&CÐ%DÐEðð ð × Ñ  ×'Ò'Ø •|×)Ñ)Ó+ˆð	_Ü$ \Ó2ˆGô ‹mˆà—.‘.¤Ó%¬	¸'Ô(BÕB×NÑNÈsÏzÉzÓZˆØ˜ ¨&×*:Ñ*:Ó*<¸iÈ8ÐT`ÐSaÐIbÐcÐcÜ	×	Ò	œZ×	(Õ	(Ü!¤.°Ó":Ó;÷ 
)Ò	(øô! ô 	EÜÐ2°8°*¸CÀ¸sÐCÓDÐDûð	Eûô ô 	_ÜØ$ \ NÐ2\Ð]ó_ð _ð	_ú÷ 
)×	(úô Ø
˜X˜Jð 'Bð 	Cóð s1   Ä7A/J ÇJ( É%KÊJ%ÊJ Ê J%Ê(KËK	c                ó0   € V ^8„  d   QhR\         R\         /# )r(   r  r)   )r   )r,   s   "r-   r.   r.   [  s   € ÷ $ñ $”hð $¤8ñ $r/   c                ó   € \        4       P                  pV P                  fM   V P                  \        P                  ! 4       P                  4       P                  R7      P                  V4      # V P                  V4      # )zÞAware datetime in the configured Hermes timezone. Legacy naive values are read as
*system-local* wall time (what created them) then converted, preserving ordering across
timezone changes and avoiding false not-due results.r  )r  r  rÌ  r   r   r	  )r  Ú	target_tzs   & r-   Ú_ensure_awarer  [  s\   € ô “×$Ñ$€IØ	‡y�yÒØ�z‰z¤§¢£×!:Ñ!:Ó!<×!CÑ!CˆzÓD×OÑOÐPYÓZÐZØ�=‰=˜Ó#Ð#r/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# )r(   ÚlaterÚearlierr)   ©r   rš   )r,   s   "r-   r.   r.   e  s&   € ÷ Mñ MœHð M¬xð M¼Eñ Mr/   c                ó|   € V P                  \        4      VP                  \        4      ,
          P                  4       # )zHReturn elapsed seconds between aware instants, independent of wall time.)r	  r   Útotal_seconds)r  r  s   &&r-   Ú_elapsed_secondsr  e  s,   € à×ÑœSÓ! G×$6Ñ$6´sÓ$;Õ;×JÑJÓLÐLr/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# ©r(   ÚleftÚrightr)   ©r   r+   )r,   s   "r-   r.   r.   j  s!   € ÷ 8ñ 8œð 8¬(ð 8´tñ 8r/   c                óX   € V P                  \        4      VP                  \        4      8„  # )z8Whether *left* is a later absolute instant than *right*.©r	  r   ©r  r   s   &&r-   Ú_instant_afterr%  j  ó!   € à�?‰?œ3Ó %×"2Ñ"2´3Ó"7Ñ7Ð7r/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# r  r!  )r,   s   "r-   r.   r.   o  s!   € ÷ 9ñ 9¤ð 9´ð 9¼dñ 9r/   c                óX   € V P                  \        4      VP                  \        4      8*  # )z>Whether *left* is at or before *right* as an absolute instant.r#  r$  s   &&r-   Ú_instant_at_or_beforer)  o  s!   € à�?‰?œ3Ó 5×#3Ñ#3´CÓ#8Ñ8Ð8r/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# r  r!  )r,   s   "r-   r.   r.   t  s!   € ÷ 8ñ 8œ(ð 8¬8ð 8¼ñ 8r/   c                óX   € V P                  \        4      VP                  \        4      8  # )z;Whether *left* is an earlier absolute instant than *right*.r#  r$  s   &&r-   Ú_instant_beforer,  t  r&  r/   c                ó<   € V ^8„  d   QhR\         R\        R\         /# )r(   r  Úsecondsr)   r  )r,   s   "r-   r.   r.   y  s&   € ÷ Sñ S”xð S¬%ð S´Hñ Sr/   c                ó€   € V P                  \        4      \        VR7      ,           P                  V P                  4      # )zž*dt* plus real *seconds*, in *dt*'s zone. Aware ``+ timedelta`` is wall-clock arithmetic
that drops ``fold``, so inside a fall-back hour it lands an hour off.©r.  )r	  r   r   r  )r  r.  s   &&r-   Ú_seconds_afterr1  y  s,   € ð �M‰Mœ#Ó¤°7Ô!;Õ;×GÑGÈÏ	É	ÓRÐRr/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# ©r(   r#  r)   )r   r   r   )r,   s   "r-   r.   r.     s   € ÷ ñ œð ¤¬Õ 2ñ r/   c                óf   €  \        \        P                  ! V 4      4      #   \         d     R# i ; i)ze``_ensure_aware(datetime.fromisoformat(value))``, or None when *value* is not a parseable ISO
string.N)r  r   r  r¬   ©r#  s   &r-   Ú_parse_awarer6    s/   € ðÜœX×3Ò3°EÓ:Ó;Ð;øÜô Úðús   ‚! ¡0¯0c                ó<   € V ^8„  d   QhR\         R\         R\        /# ©r(   rX  Úcurrentr)   r!  )r,   s   "r-   r.   r.   ˆ  s!   € ÷ 5ñ 5¤hð 5¼ð 5Ädñ 5r/   c                ó€   € V P                   e   VP                   f   R# V P                  4       VP                  4       8g  # )z¸True when a stored aware timestamp uses a different UTC offset. Naive values return False:
they are normalized by ``_ensure_aware`` and intentionally never take the offset-repair path.F)r  Ú	utcoffset©rX  r9  s   &&r-   Ú_timezone_offset_mismatchr=  ˆ  s7   € ð ‡}�}Ò §¡Ò 6ÙØ×ÑÓ ×!2Ñ!2Ó!4Ñ4Ð4r/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# r8  r!  )r,   s   "r-   r.   r.   �  s&   € ÷ Fñ F¬ð F¼Hð FÌñ Fr/   c                óL   € V P                  RR7      VP                  RR7      8„  # )uÿ   True when the stored local wall-clock time has not arrived yet. Cron expresses wall-clock
intent; after a timezone change an old offset can make a future run look due (21:00+10 â†’
13:00+02). Comparing naive wall clocks separates that from a genuine miss.Nr  ©rÌ  r<  s   &&r-   Ú_stored_wall_clock_is_futurerA  �  s%   € ð �>‰> ˆ>Ó&¨¯©À¨Ó)EÑEÐEr/   Úlast_run_atc          	      ó–   € V ^8„  d   QhR\         \        \        3,          R\        R\        \        ,          R\        \        ,          /# )r(   r9  r   rB  r)   )r  r_   r   r   r   )r,   s   "r-   r.   r.   —  s<   € ÷ ñ Ü”3œ�8�nðÜ#+ðÜ=EÄc½]ðäŒc…]ñr/   c               óò   € \        V \        4      '       d   V P                  R4      R8w  g	   V'       d   R# V P                  R4      pV'       d   \        V4      MRpVe   \	        W4      \
        8:  d   V# R# )z”One-shot run time if still eligible: a small grace window covers jobs created just after
their minute; once run, a one-shot is never eligible again.r^  r�  Nr=  )r  r  rz   r6  r  ÚONESHOT_GRACE_SECONDS)r9  r   rB  r=  Ú	run_at_dts   &&$  r-   Ú_recoverable_oneshot_run_atrG  —  s^   € ô
 �h¤×%Ò%¨¯©°fÓ)=ÀÔ)GÏ;ÙØ�\‰\˜(Ó#€Fß(.”˜VÔ$°D€IØÒÔ!1°#Ó!AÔEZÔ!ZØˆÙr/   i   c                ó0   € V ^8„  d   QhR\         R\        /# rø  )r  rŠ  )r,   s   "r-   r.   r.   ©  s   € ÷ Vñ V¤Tð V¬cñ Vr/   c                ó�   € \        V 4      pV'       g   \        # \        \        \        \	        V4      ^,          \
        4      4      # )z©How late a job can be and still catch up rather than fast-forward: half the period, clamped
to [120s, 2h], so daily jobs catch up but frequent jobs fast-forward quickly.)Ú_schedule_cadence_secondsÚ_MIN_GRACE_SECONDSr¢   ÚminrŠ  Ú_MAX_GRACE_SECONDS)r9  Úperiod_secondss   & r-   Ú_compute_grace_secondsrO  ©  s7   € ô /¨xÓ8€NßÜ!Ð!ÜÔ!¤3¤s¨>Ó':¸aÕ'?ÔASÓ#TÓUÐUr/   i,  c                ó<   € V ^8„  d   QhR\         R\        R\        /# )r(   Úlateness_secondsÚgrace_secondsr)   )rš   rŠ  r_   )r,   s   "r-   r.   r.   ¸  s"   € ÷ ñ ´%ð Ìð ÔPSñ r/   c                ó0   € W8”  d   R# V \         8”  d   R# R# )zž``on_time`` (within ticker slack), ``late`` (within the catch-up grace window), or
``catch_up`` (beyond grace; accumulated misses skipped, executed once now).Úcatch_upÚlateÚon_time)Ú _LATE_DISPATCH_TOLERANCE_SECONDS)rQ  rR  s   &&r-   Ú_classify_dispatch_latenessrX  ¸  s   € ð Ô'ÙØÔ:Ô:ÙÙr/   c                óŒ   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          R\        R\        /# )r(   r  r9  r   r)   ©r  r_   r   r   r+   )r,   s   "r-   r.   r.   Ê  s<   € ÷ $3ñ $3Ü	Œc”3ˆh�ð$3Ü#'¬¬S¨¥>ð$3Ü8@ð$3ä	ñ$3r/   c                óæ  € V P                  R4      R8w  d   R# ^ RIHp V! W4      '       d   R# \        \	        V P                  R4      ;'       g    R4      4      '       d   R# \        V P                  R4      V\        4      '       d   R# V P                  R4      pV'       d   \        V4      MR	pVf   R# \        W%4      pV^ 8  d   R# \        V4      p\        V4      pVf   TpWhV,           8„  # )
u}  True when a recurring job (caller-checked) is wedged in a stale persisted error state:
``last_status == "error"``, not running in this process (never re-arm a live run underneath
itself), and ``last_run_at`` older than ``cadence + grace`` (a job merely erroring-and-retrying
on schedule stays fresh and is not flagged).

Condition (all must hold): * it has NOT successfully re-fired within its natural cadence â€” its
``last_run_at`` is older than ``cadence + grace``, so this is not a normal transient-error retry that
will fire on its own soon, it is a job that has been sitting errored for a full period with no recovery;
See #62002.
Úlast_statusr×   F)Úhold_activerx   r  r   rB  N)rz   Úcron.quota_holdr]  r¯   r_   Ú_claim_is_liver   r6  r  rO  rJ  )	r  r9  r   r]  Úlast_runÚlast_run_dtÚage_secondsÚgraceÚcadence_secondss	   &&&      r-   Ú_job_is_stale_error_recurringre  Ê  sÐ   € ð ‡w�wˆ}Ó Ô(ÙÝ+Ù�3×ÒÙÜ#¤C¨¯©°«×(;Ð(;¸Ó$<×=Ò=Ùô �c—g‘g˜lÓ+¨SÔ2H×IÒIÙØ�w‰w�}Ó%€Hß,4”,˜xÔ(¸$€KØÒÙÜ" 3Ó4€KØ�Q„ÙÜ" 8Ó,€EÜ/°Ó9€OØÒàˆØ¨EÕ1Ñ2Ð2r/   c                óh   € V ^8„  d   QhR\         \        \        3,          R\        \        ,          /# rø  )r  r_   r   r   rš   )r,   s   "r-   r.   r.   õ  s&   € ÷ ñ ¬¬S´#¨X­ð ¼8ÄE½?ñ r/   c                óÐ  € \        V \        4      '       g   R# V P                  R4      pVR8X  d/   V P                  R4      p V'       d   \        V4      R,          # R# VR8w  g   \        4       '       g   R# V P                  R4      pV'       g   R# V\        9   d   \        V,          #  \        V\        4       4      pVP                  \        4      pVP                  \        4      V,
          P                  4       pV^ 8”  d   TMRp\        \        4      R8¼  d   \        P                  4        V\        V&   V#   \        \
        3 d     R# i ; i  \         d    Rp LZi ; i)	a  Approximate schedule period in seconds, or None (croniter missing / malformed expr). Cron
results are cached per expr because this runs under ``_jobs_lock`` every tick; the gap can vary
with base time for irregular exprs, acceptable for a staleness *threshold*.Nr^  r_  rò  r$   r:   r<  é   )r  r  rz   rš   r    rŸ   r9   Ú_cron_cadence_cacher1   r  Úget_nextr   r  r¬   rÜ  Úclear)r9  r^  rò  r<  ÚitÚfirstÚgaprô   s   &       r-   rJ  rJ  õ  s4  € ô �h¤×%Ò%ÙØ�<‰<˜Ó€DØˆzÔØ—,‘,˜yÓ)ˆð	ß,3”5˜“> DÕ(Ð=¸Ð=ð ˆv„~Ô-×/Ò/ÙØ�<‰<˜Ó€DßÙØÔ"Ô"Ü" 4Õ(Ð(ðÜ�dœK›MÓ*ˆØ—‘œHÓ%ˆØ�{‰{œ8Ó$ uÕ,×;Ñ;Ó=ˆØ˜aœ‘ Tˆô ÔÓ 3Ô&Ü×!Ñ!Ô#Ø &Ô˜ÑØ€Møô) œ:Ð&ô 	Úð	ûô ô ØŠðús1   ÁD= ÁD= ÁD= Â,AE Ä=EÅEÅE%Å$E%c                ób   € V ^8„  d   QhR\         R\        \         \        3,          R\        RR/# )r(   ÚfilenameÚentryÚrecentr)   N)r_   r  r   r  )r,   s   "r-   r.   r.     s7   € ÷ Fñ F¤sð F´4¼¼S¸µ>ð FÌ4ð FÐTXñ Fr/   c                óº  € VP                  V4       VR\        ) 1  \        4       P                  V ,          p\	        VP
                  4       \        VRRR7      ;_uu_ 4       pVP                  \        P                  ! V4      R,           4       RRR4       R#   + '       g   i     R# ; i  \         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)uÙ   Record ``entry`` in the bounded ``recent`` list and append to ``<cron_dir>/<filename>``
(best effort â€” telemetry must never break a tick). Counters stay module-level ints per
metric because tests reset them by name.NÚarà   rÍ   Ú
zCould not append %s record: %s)r  Ú_TELEMETRY_RECENT_HISTORYr�   rC   r…  ÚparentrÔ   ÚwriteÚjsonÚdumpsr¬   r­   Údebug)rp  rq  rr  rc  Úfhrõ   s   &&&   r-   Ú_append_telemetry_recordr}    s¤   € ð ‡M�M�%ÔØÐ*Ô*Ð*Ð*Ð+ðFÜ"Ó$×-Ñ-°Õ8ˆÜ˜Ÿ™Ô%Ü�$˜ g×.Õ.°"Ø�H‰H”T—Z’Z Ó&¨Õ-Ô.÷ /×.×.Ò.ûäô FÜ�‰Ð5°x×EÒEûðFús6   �AB. Á#-BÂB. ÂB+	Â%B. Â+B. Â.CÂ9CÃCc                óV   € V ^8„  d   QhR\         \        \        3,          R\        RR/# )r(   r  Úprevious_next_runr)   Nr  )r,   s   "r-   r.   r.   &  s/   € ÷ Wñ W¬$¬s´C¨x­.ð WÌSð WÐUYñ Wr/   c           	     óð   € RV P                  R4      RV P                  R4      ;'       g    V P                  R4      RVR\        4       P                  4       /p\        ^,          s\	        RV\
        4       R# )zEPersist a countable, probe-visible signal for one stale-error re-arm.r]   rx   rC  Úprevious_next_run_atÚ
rearmed_atz persisted_error_recoveries.jsonlN)rz   r  r  Ú_persisted_error_recoveriesr}  Ú"_persisted_error_recoveries_recent)r  r  rq  s   && r-   Ú _record_persisted_error_recoveryr…  &  si   € ð 	�#—'‘'˜$“-Ø�—‘˜“×0Ð0 3§7¡7¨4£=ØÐ 1Ø”k“m×-Ñ-Ó/ð	€Eô   1Õ$ÐÜØ*¨EÔ3UöWr/   c                óF   € V ^8„  d   QhR\         \        \        3,          /# r'   r  )r,   s   "r-   r.   r.   4  s   € ÷ ñ ¬D´´c°­Nñ r/   c                 ó0   € R\         R\        \        4      /# )z5Probe-visible snapshot of persisted-error recoveries.Úpersisted_error_recoveriesrr  )rƒ  r  r„  rG   r/   r-   Ú"get_persisted_error_recovery_statsr‰  4  s   € ð 	%Ô&AØ”$Ô9Ó:ðð r/   c                ó^   € V ^8„  d   QhR\         \        \        3,          R\        R\        /# )r(   r9  Únext_run_dtr)   rZ  )r,   s   "r-   r.   r.   <  s*   € ÷ ñ ¬$¬s´C¨x­.ð Äxð ÔTXñ r/   c                ót  € V P                  R4      R8w  d   R# V P                  R4      pV'       d   \        4       '       d	   \        f   R#  \        \        V4      V\	        ^R7      ,           4      P                  \        4      p\        W1,
          P                  4       4      R8  #   \         d     R# i ; i)a±  Whether ``next_run_dt`` is an occurrence of the schedule's current expr (detects a
hand-edited ``schedule.expr`` whose stored ``next_run_at`` came from the old one).
Best-effort: anything uncheckable (non-cron, no expr, no croniter, malformed) reports a
match.

A direct ``jobs.json`` edit can change ``schedule.expr`` while leaving the stored ``next_run_at``
computed under the *old* expression (#93049). The stored instant is stale exactly when it is not an
occurrence of the current expression. Validation is best-effort: anything that cannot be checked
(non-cron kind, missing expr, croniter unavailable, malformed input) reports a match so the fire path
keeps its existing semantics.
r^  r:   Tr<  r0  g      ð?)
rz   r9   r1   r_   r   Úget_prevr   Úabsr  r¬   )r9  r‹  r<  Úprevs   &&  r-   Ú_cron_next_run_matches_exprr�  <  s•   € ð ‡|�|�FÓ˜vÔ%ÙØ�<‰<˜Ó€DßÔ'×)Ò)¬XÒ-=Ùðô œ˜D›	 ;´À1Ô1EÕ#EÓF×OÑOÔPXÓYˆÜ�DÕ&×5Ñ5Ó7Ó8¸3Ñ>Ð>øÜô Úðús   ÁAB( Â(B7Â6B7r�  Útimezone_migrationÚ	expr_editc                ój   € V ^8„  d   QhR\         \        \        3,          R\        R\        R\        /# )r(   r9  Úraw_next_run_dtr‹  r)   ©r  r_   r   r   )r,   s   "r-   r.   r.   ]  s4   € ÷  ñ  Ü”3œ�8�nð Ü/7ð ÜFNð äñ r/   c                óÄ   € \        W4      '       d   \        # VP                  RR7      VP                  RR7      8g  pV'       d   \        W4      '       d   \        # \        # )u  Explain WHY a stored ``next_run_at`` misses the current cron lattice; the causes need
opposite actions. ``expr_edit``: a hand edit changed ``schedule.expr`` â€” re-anchor WITHOUT
firing. ``timezone_migration``: only the offset representation changed (legacy UTC rows
normalized into the profile tz); treating it as an edit would skip a due, never-fired
occurrence. Discriminator: normalization moved the wall clock AND the stored instant's OWN
wall clock is a legal occurrence (when offsets agree a genuine expr edit can never be misread
as a migration).

* ``expr_edit`` â€” a direct ``jobs.json`` edit changed ``schedule.expr`` while leaving ``next_run_at``
computed under the old one (#93049). Upgrading from a UTC-scheduling build to one that honours the
profile timezone leaves legacy rows like ``2026-09-02T04:00:00+00:00`` for ``0 4 * * *``; normalizing to
Europe/Brussels turns that into ``06:00+02``, which the expression excludes. Treating it as a stale edit
re-anchored to tomorrow and silently skipped a due occurrence that had never fired.
Nr  )r�  ÚSTALE_CRON_MATCHrÌ  ÚSTALE_CRON_TIMEZONE_MIGRATIONÚSTALE_CRON_EXPR_EDIT)r9  r”  r‹  Úwall_clock_shifteds   &&& r-   Ú_classify_stale_cron_next_runr›  ]  sW   € ô" # 8×9Ò9ÜÐØ(×0Ñ0¸Ð0Ó=À×ATÑATÐ\`ÐATÓAaÑaÐßÔ9¸(×TÒTÜ,Ð,ÜÐr/   c                ób   € V ^8„  d   QhR\         \        \        3,          R\        R\        RR/# )r(   r  r”  r‹  r)   Nr•  )r,   s   "r-   r.   r.   |  s:   € ÷ Yñ YÜ	Œc”3ˆh�ðYÜ*2ðYÜAIðYà	ñYr/   c                ó€  € RV P                  R4      RV P                  R4      ;'       g    V P                  R4      RV P                  R4      ;'       g    / P                  R4      RVP                  4       RVP                  4       R\        4       P                  4       /p\        ^,          s\	        R	V\
        4       R
# )zBPersist a countable signal for one offset-migration catch-up fire.r]   rx   rC  r<  r9  Ústored_next_run_atÚnormalized_next_run_atÚfired_atz!timezone_migration_catchups.jsonlN)rz   r  r  Ú_timezone_migration_catchupsr}  Ú#_timezone_migration_catchups_recent)r  r”  r‹  rq  s   &&& r-   Ú"_record_timezone_migration_catchupr£  |  s    € ð 	�#—'‘'˜$“-Ø�—‘˜“×0Ð0 3§7¡7¨4£=Ø�—‘˜Ó$×*Ð*¨×/Ñ/°Ó7Ø˜o×7Ñ7Ó9Ø  +×"7Ñ"7Ó"9Ø”K“M×+Ñ+Ó-ð€Eô ! AÕ%Ð ÜØ+¨UÔ4WöYr/   c                óF   € V ^8„  d   QhR\         \        \        3,          /# r'   r  )r,   s   "r-   r.   r.   Ž  s   € ÷ ñ ¬d´3¼°8­nñ r/   c                 ó0   € R\         R\        \        4      /# )z:Probe-visible snapshot of offset-migration catch-up fires.Útimezone_migration_catchupsrr  )r¡  r  r¢  rG   r/   r-   Ú$get_timezone_migration_catchup_statsr§  Ž  s   € ð 	&Ô'CØ”$Ô:Ó;ðð r/   c                óŠ   € V ^8„  d   QhR\         \        \        3,          R\        \        ,          R\        \        ,          /# )r(   r9  rB  r)   ©r  r_   r   r   )r,   s   "r-   r.   r.   –  s4   € ÷ ;ñ ;œt¤C¬ H�~ð ;¼HÄS½Mð ;ÔU]Ô^aÕUbñ ;r/   c                óT  € \        4       p\        V \        4      '       g   R# V P                  R4      pVR8X  d   \	        WVR7      # V'       d   \        V4      MR;'       g    TpVR8X  dg   V P                  R4      pVf   R# VP                  \        4      \        VR7      ,           pVP                  VP                  4      P                  4       # VR8X  EdM   V P                  R	4      pV'       g   R# \        4       '       g(   \        P                  R
V\        P                  4       R# \!        4       ;'       g    VP                  pVP                  V4      P#                  RR7      p	\%        Wy4      p
VP'                  4       pV
P)                  \*        4      p\-        ^4       F\  pR F>  pVP#                  WŽR7      pVP'                  4       V8”  g   K,  VP                  4       u u # 	  V
P)                  \*        4      pK^  	  VP#                  VR7      P                  4       # R# )zSCompute the next run time for a schedule as an ISO string, or None if no more runs.Nr^  r�  ©rB  r_  rò  rõ  r:   r<  zÒCannot compute next run for cron schedule %r: 'croniter' is not installed. croniter is a core dependency as of v0.9.x; reinstall hermes-agent or run 'pip install croniter' in your runtime env (interpreter: %s).r  )r  Úfold)rË  rÙ  )r  r  r  rz   rG  r6  r	  r   r   r  r  r9   r­   r®   ÚsysÚ
executabler   rÌ  r1   Ú	timestamprj  r   Úrange)r9  rB  r   r^  Ú	base_timerò  Únext_runr<  ÚzoneÚ	base_wallrl  Úbase_tsÚ	next_wallÚ_r¬  Ú	candidates   &&              r-   Úcompute_next_runr¹  –  sÈ  € ä
‹-€CÜ�h¤×%Ò%ÙØ�<‰<˜Ó€DØˆv„~Ü*¨8ÀkÔRÐRç.9”˜kÔ*¸t×KÐKÈ€IØˆzÔØ—,‘,˜yÓ)ˆØŠ?Ùà×'Ñ'¬Ó,¬yÀÔ/IÕIˆØ×"Ñ" 9×#3Ñ#3Ó4×>Ñ>Ó@Ð@Øˆv…~Ø�|‰|˜FÓ#ˆßÙÜ×!Ò!Ü�N‰Nð%ð ”c—n‘nô&ñ ô ‹~×1Ð1 ×!1Ñ!1ˆØ×(Ñ(¨Ó.×6Ñ6¸dÐ6ÓCˆ	Ü�dÓ&ˆð ×%Ñ%Ó'ˆØ—K‘K¤Ó)ˆ	Ü�q–ˆAÛ�Ø%×-Ñ-°TÐ-ÓE�	Ø×&Ñ&Ó(¨7Ö2Ø$×.Ñ.Ó0Ô0ñ ð Ÿ™¤HÓ-ŠIñ ð × Ñ ¨Ð Ó-×7Ñ7Ó9Ð9Ùr/   c                ó@   € V ^8„  d   QhR\         R\         R\         RR/# )r(   rC  r  Ú
tmp_prefixr)   Nr^   )r,   s   "r-   r.   r.   Ö  s(   € ÷ ñ œð ¤3ð ´Cð ¸Dñ r/   c                óŽ   €  \        4        \        \        4       P                  V ,          WRR7       R#   \         d     R# i ; i)zaAtomic (never torn) best-effort marker write; failures swallowed so markers never break the
tick.é€  ©r»  ÚmodeN)rÓ   r!   r�   rC   r¬   )rC  r  r»  s   &&&r-   Ú_write_markerrÀ  Ö  s:   € ðÜŒÜÔ-Ó/×8Ñ8¸4Õ?ÀÐch×iøÜô Úðús   ‚15 µAÁAFc                ó(   € V ^8„  d   QhR\         RR/# )r(   Úsuccessr)   Nr*   )r,   s   "r-   r.   r.   à  s   € ÷ Gñ G¤Tð G°dñ Gr/   c                óÜ   € \        R\        P                  ! 4        R\        P                  ! 4        2R4       V '       d,   \        R\	        \        P                  ! 4       4      R4       R# R# )u  Record ticker liveness (+ last-success marker when ``success``) so `cron status` can tell
"alive but failing" from "firing"; scoped per profile store.

The ticker calls this once per loop iteration. ``success=True`` additionally bumps the *last successful
tick* marker. We track two distinct signals so `hermes cron status` can tell a thread that is merely
*alive and looping* (heartbeat fresh, success stale) from one that is actually *firing jobs* (both
fresh) â€” a ticker stuck failing every tick would otherwise keep the plain heartbeat fresh and falsely
report healthy (#32612, #32895).
Resolution uses ``_current_cron_store()`` so the heartbeat is correctly scoped to the active profile's
store â€” critical under multiplex_profiles where each profile needs its own liveness signal (#69377).
r<   rÅ  z.hb_r=   N)rÀ  r3   rn  Úgetpidr_   )rÂ  s   &r-   Úrecord_ticker_heartbeatrÅ  à  sH   € ô Ð$¬¯ª«¨°Q´r·y²y³{°mÐ&DÀfÔMßÜÐ+¬S´·²³Ó-=¸vÖFñ r/   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# ©r(   rC  r)   )r_   r  )r,   s   "r-   r.   r.   ó  s   € ÷ ñ œcð ¤d¬3¥iñ r/   c                 óœ   €  \        4       P                  V ,          P                  R R7      P                  4       #   \         d    . u # i ; i)rÌ   rÍ   )r�   rC   Ú	read_textrÚ  r¬   ©rC  s   &r-   Ú_read_marker_fieldsrË  ó  sF   € ðÜ#Ó%×.Ñ.°Õ5×@Ñ@È+Ð@ÓV×\Ñ\Ó^Ð^øÜô ØŠ	ðús   ‚8; »AÁ
Ac                óF   € V ^8„  d   QhR\         R\        \        ,          /# rÇ  )r_   r   rš   )r,   s   "r-   r.   r.   ú  s   € ÷ ñ œ#ð ¤(¬5¥/ñ r/   c           	     ó¦   €  \        R\        P                  ! 4       \        \        V 4      ^ ,          4      ,
          4      #   \         d     R# i ; i)zYSeconds since the epoch stamp stored in ``<cron_dir>/<name>``; None = missing/unreadable.r%   N)r¢   r3   rš   rË  r¬   rÊ  s   &r-   Ú_epoch_file_agerÎ  ú  sA   € ðÜ�3œŸ	š	›¤eÔ,?ÀÓ,EÀaÕ,HÓ&IÕIÓJÐJøÜô Úðús   ‚>A ÁAÁAc                ó$   € V ^8„  d   QhR\         /# r'   r*   )r,   s   "r-   r.   r.     s   € ÷ ñ ¤tñ r/   c                 ó¦   € \        R4      p  ^ RIHp \        V 4      ^8¬  ;'       d    V! \	        V ^,          4      4      #   \
         d     R# i ; i)zªTrue when the process that wrote this store's ticker heartbeat is still running. A legacy
bare-epoch stamp names no writer and is NOT proof of a live scheduler by itself.r<   )Úpid_exists_stdlibF)rË  Úhermes_cli._subprocess_compatrÑ  rÜ  rŠ  r¬   )ÚfieldsrÑ  s     r-   Úticker_heartbeat_writer_aliverÔ    sN   € ô !Ð!3Ó4€FðÝCÜ�6‹{˜aÑ×EÐEÑ$5´c¸&À½)³nÓ$EÐEøÜô Úðús   �A ¨A ÁAÁAc                ó:   € V ^8„  d   QhR\         \        ,          /# r'   ©r   rš   )r,   s   "r-   r.   r.     s   € ÷ /ñ /¤(¬5¥/ñ /r/   c                 ó   € \        R4      # )u=  Seconds since the ticker loop last iterated; None = missing/unreadable ("cannot determine",
not "dead").

Resolution uses ``_current_cron_store()`` so the heartbeat is correctly scoped to the active profile â€”
critical under multiplex_profiles where ``hermes cron status`` must report per-profile liveness
(#69377).
r<   ©rÎ  rG   r/   r-   Úget_ticker_heartbeat_agerÙ    s   € ô Ð-Ó.Ð.r/   c                ó:   € V ^8„  d   QhR\         \        ,          /# r'   rÖ  )r,   s   "r-   r.   r.     s   € ÷ 2ñ 2¤¬¥ñ 2r/   c                 ó   € \        R4      # )u  Seconds since the ticker last completed a tick WITHOUT raising, or None.

Resolution uses ``_current_cron_store()`` so the heartbeat is correctly scoped to the active profile â€”
critical under multiplex_profiles where ``hermes cron status`` must report per-profile liveness
(#69377).
r=   rØ  rG   r/   r-   Úget_ticker_success_agerÜ    s   € ô Ð0Ó1Ð1r/   c                ó(   € V ^8„  d   QhR\         RR/# )r(   Úmessager)   Nr^   )r,   s   "r-   r.   r.   "  s   € ÷ Xñ X¤ð X¨ñ Xr/   c                ón   € \        R\        P                  ! 4        RV P                  4        R2R4       R# )zdPersist the latest tick failure so `cron status` (another process) can show WHY, not just
staleness.Úticker_last_errorru  z.terr_N)rÀ  r3   r�   )rÞ  s   &r-   Úrecord_ticker_errorrá  "  s+   € ô Ð%¬$¯)ª)«+¨°b¸¿¹»Ð8IÈÐ'LÈhÖWr/   c                ó   € V ^8„  d   QhRR/# rÃ   rG   )r,   s   "r-   r.   r.   (  s   € ÷ Hñ H˜Dñ Hr/   c                 óÖ   € \         P                  ! \        4      ;_uu_ 4        \        4       P                  R,          P                  4        RRR4       R#   + '       g   i     R# ; i)zGRemove the last-tick-error marker after a successful tick. Best-effort.rà  N)rØ   rÙ   r¹   r�   rC   ÚunlinkrG   r/   r-   Úclear_ticker_errorrå  (  s=   € ä	×	Ò	œW×	%Õ	%Ü	Ó	×	'Ñ	'Ð*=Õ	=×EÑEÔG÷ 
&×	%×	%Ò	%ús   £*AÁA(	c                ó:   € V ^8„  d   QhR\         \        ,          /# r'   ©r   r_   )r,   s   "r-   r.   r.   .  s   € ÷ 	0ñ 	0œx¬�}ñ 	0r/   c                 ó"  €  \        4       P                  R,          P                  RR7      p T P	                  4       p\        T4      ^8  d   R# RP                  TR,          4      P                  4       ;'       g    R#   \         d     R# i ; i)z<Return the most recent recorded tick error message, or None.rà  rÌ   rÍ   Nru  rØ  )r�   rC   rÉ  r¬   Ú
splitlinesrÜ  rß  r�   )r£   Úliness     r-   Úget_ticker_last_errorrë  .  s~   € ðÜ"Ó$×-Ñ-Ð0CÕC×NÑNÐXcÐNÓdˆð �N‰NÓ€EÜ
ˆ5ƒz�A„~ÙØ�9‰9�U˜2•YÓ×%Ñ%Ó'×/Ð/¨4Ð/øô ô Úðús   ‚+A? Á?BÂBc                óR   € V ^8„  d   QhR\         R\        \        \        3,          /# ©r(   rL   r)   )r
   rÃ  r   r+   )r,   s   "r-   r.   r.   <  s#   € ÷ 	3ñ 	3¤ð 	3¬¬s´D¨yÕ)9ñ 	3r/   c                ó"  € \        V RRR7      ;_uu_ 4       pVP                  4       pRRR4        \        P                  ! X4      R3#   + '       g   i     L); i  \        P                   d    \        P                  ! XRR7      R3u # i ; i)zÂTolerant jobs.json parse -> ``(data, used_strict_fallback)``: utf-8-sig absorbs a BOM, strict
failure retries with ``strict=False``. IO/fallback errors propagate (caller decides repair vs
bail).ÚrrÌ   rÍ   NF)ÚstrictT)rÔ   Úreadry  ÚloadsÚJSONDecodeError)rL   Úfr£   s   &  r-   Ú_parse_jobs_filerõ  <  sr   € ô 
ˆi˜ {×	3Õ	3°qØ�f‰f‹hˆ÷ 
4ð3Ü�zŠz˜#‹ Ð%Ð%÷ 
4×	3ûô ×Ñô 3Ü�zŠz˜# eÔ,¨dÐ2Ò2ð3ús   —A	±A Á	A	Á/BÂBc                ó\   € V ^8„  d   QhR\         \        \        \        3,          ,          /# r'   ©r  r  r_   r   )r,   s   "r-   r.   r.   H  s#   € ÷ Wñ W”4œœS¤#˜X�Õ'ñ Wr/   c                 óB
  € \        4       P                  p \        4        \        V 4      pV P	                  4       '       g   \        R4       . #  \        V 4      w  r#T'       d   RMRp. pRp\        T\        4      '       Edc   TP                  R. 4      p\        T\        4      '       dö   TP                  4        U	U
u. uF  w  rš\        T
\        4      '       d   K  T	NK!  	  pp	p
T'       dT   TP!                  R	\#        T4      R
 R\#        T4      ^8X  d   RMR RRP%                  \'        \(        T4      4       24       TP                  4        U	U
u. uF>  w  rš\        T
\        4      '       g   K  / T
CRT
P                  R4      ;'       g    T	/CNK@  	  pp	p
RpRpM�\        T\*        4      '       g/   TP!                  R\-        T4      P.                   R24       . pRpRpM<\        T\*        4      '       d   TpRpM!\        R\-        T4      P.                   24      hT Uu. uF  p\        T\        4      '       d   K  TNK  	  ppT'       d§   TP!                  R	\#        T4       R\#        T4      ^8X  d   RMR RRP%                  \1        T Uu0 uF  p\-        T4      P.                  kK  	  up4      4       R24       T Uu. uF  p\        T\        4      '       g   K  TNK  	  ppT;'       g    RpT F‹  pTP                  R4      p\        T\        4      '       g   K,  RT9   g   K5  \-        TR,          4      \2        Jg   TR,          ^ 8  g   K_   \5        \3        TR,          4      ^ 4      TR&   T;'       g    RpK�  	  T'       d•   \=        \>        R^ 4      '       g(   \A        4       ;_uu_ 4        \C        4       uuRRR4       # T F  p\        PE                  R T4       K  	   \G        Y‡;'       d    \I        4       RJ R!7       \        PE                  R"T4       \        T4       T#   \         d+   p\        P                  RT4       \        RT 24      ThRp?i\         d+   p\        P                  RT4       \        RT 24      ThRp?ii ; iu up
p	i u up
p	i u upi u upi u upi   \6        \8        \:        3 d
    ^ TR&    EL]i ; i  + '       g   i     EL; i  \         d'   p\J        PL                  ! TR#T R$2R%4        Rp?LõRp?ii ; i)&zLoad all jobs from storage.NzIOError reading jobs.json: %szFailed to read cron database: z#Failed to auto-repair jobs.json: %sz*Cron database corrupted and unrepairable: zhad invalid control charactersFÚjobsz	Skipping r˜  z non-dict entrÚyÚiesz in id-keyed jobs map: ú, rx   z#id-keyed jobs map flattened to listTz*Replacing invalid jobs.json 'jobs' field (z) with an empty listz%invalid jobs field replaced with listzbare list wrapped as dictz7Cron database corrupted: expected {'jobs': [...]}, got z non-object entrz in jobs.json (types: Ú)znon-object entries droppedr‰  rW  z#invalid repeat.completed normalizedrÊ   z%sr@  zAuto-repaired jobs.json (%s)zjobs.json repair (z) not persistedÚload)'r�   rL   rÓ   Ú_jobs_file_stampÚexistsÚ_record_load_stamprõ  r¹   r­   r×   r|  r¬   r  r  rz   Úitemsr  rÜ  rß  ÚmapÚreprr  ÚtyperN   ÚsortedrŠ  r¢   r    rŸ   ÚOverflowErrorrÏ   rÐ   rÛ   r�   r®   Ú	save_jobsÚ_peek_jobs_unlockedr   Únote_unwritable)rL   Úpre_read_stampÚdataÚ_strict_retryrÚ   ÚrepairÚnotesÚunmergeablerù  r0  ÚvÚskippedÚjÚjunkr  ÚrepÚnoterõ   s                     r-   r�   r�   H  sÞ  € ä#Ó%×/Ñ/€IÜ„Mô & iÓ0€NØ×Ñ×ÒÜ˜4Ô Øˆ	ðTÜ.¨yÓ9Ñˆ÷ 2?Ñ-ÀD€FØ€EØ€KÜ�$œ×ÓØ�x‰x˜ Ó#ˆÜ�dœD×!Ò!ð &*§Z¡Z¤\ÔM¡\™T˜Q¼ÀAÄt×9L—q�q¡\ˆGÑMßØ—‘˜y¬¨W«°aÐ(8¸ÌsÐSZË|Ð_`ÔO`ÉÐfkÐFmð  nEð  FJ÷  FOñ  FOô  PSô  TXð  Zaó  Pbó  Fcð  Edð  eô  fØ>B¿j¹j¼lÔb¹l±d°aÌjÐYZÔ\`×NaÕ1�qÐ1˜$ §¡ d£× 0Ð 0¨qÔ1¹lˆDÑbØ:ˆFØ‰KÜ˜D¤$×'Ò'Ø�L‰LÐEÄdÈ4Ãj×FYÑFYÐEZÐZnÐoÔpØˆDØ<ˆFØˆKøÜ	�Dœ$×	Ò	ØˆØ,‰äØGÌÈTË
×H[ÑH[ÐG\Ð]ó_ð 	_áÓ7‘t�!¤:¨a´×#6�AˆA‘t€DÐ7ßð 	�‰�y¤ T£ Ð+;Ä3ÀtÃ9ÐPQÄ>¹CÐW\Ð;]Ð]sÐtx×t}Ñt}ô  Eñ  aeó  Ffñ  aeð  \]ô  GKð  LMó  GN÷  GWô  GWñ  aeñ  Ffó  gó  uhð  tið  ijð  kô  	lÙÓ7™4�a¤:¨a´×#6—�™4ˆÐ7Ø×7Ð7Ð7ˆÛˆð
 �g‰g�hÓˆÜ�cœ4× Ô  [°CÖ%7Ü�S˜Õ%Ó&¬cÓ1°S¸Õ5EÈÖ5Ið%Ü#&¤s¨3¨{Õ+;Ó'<¸aÓ#@��KÑ ð ×DÐDÐDŠFñ ÷ ÜÔ'¨°!×4Ò4ô —•Ü “{÷ ’ãˆDÜ�N‰N˜4 Ö&ñ ð	dÜ�d×$QÐ$QÔ4GÓ4IÈTÐ4QÕRÜ�N‰NÐ9¸6ÔBô �~Ô&Ø€KøôU ô HÜ�‰Ð4°aÔ8ÜÐ;¸A¸3Ð?Ó@ÀaÐGûÜô TÜ�‰Ð:¸AÔ>ÜÐGÈÀsÐKÓLÐRSÐSûðTüó  Nùó cùò 8ùò
 FfùÚ7øô œz¬=Ð9ô %Ø#$��KÔ ð%ú÷ —�ûô ô 	dÜ×(Ò(¨Ð0BÀ6À(È/Ð.ZÐ\b×cÑcûð	dús¢   ÁP1 Â=RÃRÅR"Å2R"Æ	R"È$R(ÉR(ÊR-ËR2Ë#R2ÍR7Î9
SÏ.S- Ï<(S- Ð1RÐ<%Q!Ñ!RÑ.RÑ/%RÒRÒ7SÓSÓS*	Ó-TÓ8TÔTc                ór   € V ^8„  d   QhR\         \        \        \        \        3,          ,          ,          /# r'   )r   r  r  r_   r   )r,   s   "r-   r.   r.   ¢  s%   € ÷ 4ñ 4œX¤d¬4´´S°­>Õ&:Õ;ñ 4r/   c                 óR  € \        4       P                  p V P                  4       '       g   . #  \        V 4      w  r\        T\        4      '       d-   TP                  R. 4      p\        T\        4      '       d   T# R# \        T\        4      '       d   T# R#   \         d     R# i ; i)u¥   Repair-free read under ``_jobs_lock()``: ``[]`` if missing, ``None`` if corrupt (never
shrink-merge against an unknown baseline). Never saves â€” that would recurse.Nrù  )	r�   rL   r   rõ  r¬   r  r  rz   r  )rL   r  r·  rù  s       r-   r	  r	  ¢  s˜   € ô $Ó%×/Ñ/€IØ×Ñ×ÒØˆ	ðÜ" 9Ó-‰ˆô �$œ×ÒØ�x‰x˜ Ó#ˆÜ! $¬×-Ò-ˆtÐ7°4Ð7Ü˜d¤D×)Ò)ˆ4Ð3¨tÐ3øô ô Úðús   ®B ÂB&Â%B&c                ór   € V ^8„  d   QhR\         R\        \        \        \        \        3,          ,          /# rí  )r
   r   rÃ  rŠ  )r,   s   "r-   r.   r.   ²  s*   € ÷ ñ ¤ð ¬´%¼¼SÄ#¸Õ2FÕ)Gñ r/   c                óŽ   €  V P                  4       pVP                  VP                  VP                  3#   \         d     R# i ; i)zÍShrink-merge fast-path stamp ``(mtime_ns, size, ino)``; ``None`` if unstatable. ``st_ino`` is
included because every writer uses mkstemp+rename, so a same-size write in one mtime quantum
can't false-match.N)ÚstatÚst_mtime_nsÚst_sizeÚst_inor¹   )rL   Ústs   & r-   rÿ  rÿ  ²  s>   € ðØ�^‰^ÓˆØ—‘ §
¡
¨B¯I©IÐ6Ð6øÜô Úðús   ‚25 µAÁAc                ój   € V ^8„  d   QhR\         \        \        \        \        3,          ,          RR/# )r(   Ústampr)   N)r   rÃ  rŠ  )r,   s   "r-   r.   r.   ½  s*   € ÷ 
,ñ 
,œh¤u¬S´#´s¨]Õ';Õ<ð 
,Àñ 
,r/   c                óN   € \        \        R^ 4      '       d   V \        n        R# R# )a¿  Remember jobs.json's stamp for the enclosing _jobs_lock() section (no-op outside one) so the
save path can skip the shrink-merge when disk provably hasn't changed. Capture it BEFORE
reading: a mid-read sibling then mismatches (fail-safe); stamping after would certify an
unseen write.

Stamping after the read would let that sibling's write be certified as "seen" without being in the
loaded payload, wrongly suppressing the recovery. See #80703.
rÊ   N)rÏ   rÐ   rÒ   )r!  s   &r-   r  r  ½  s"   € ô Ô ¨!×,Ò,Ø&+ÔÖ#ñ -r/   c          	      óØ   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        \
        \        ,          ,          R\         \        \        \        3,          ,          /# ©r(   rù  Úremoved_idsr)   ©r  r  r_   r   r   r   )r,   s   "r-   r.   r.   Ê  sG   € ÷ ñ Ü
Œt”Cœ�H�~Õ
ðÜ-5´jÄµoÕ-Fðä	Œ$Œs”Cˆx�.Õñr/   c                óà  € \        \        RR4      pVe%   \        \        4       P                  4      V8X  d   . # \        4       pVf!   \        R\        4       P                   24      hV  Uu0 uFF  p\        V\        4      '       g   K  VP                  R4      '       g   K4  \        VR,          4      kKH  	  ppYQ;'       g    R Uu0 uF  qf'       g   K  \        V4      kK  	  up,          p. pV Fn  p\        V\        4      '       d   VP                  R4      '       g   K2  \        VR,          4      p	W•9  g   KL  VP                  V4       VP                  V	4       Kp  	  V# u upi u upi )zòOn-disk jobs missing from *jobs* and not intentionally removed. Stamp match => nothing
landed, return without parsing; unreadable store => raise RuntimeError (fail closed: writing
over an unknown baseline would silently drop every job in it).rÒ   Nz/Cron database corrupted; refusing to overwrite rx   rG   )rÏ   rÐ   rÿ  r�   rL   r	  r|  r  r  rz   r_   r  Úadd)
rù  r%  r!  Ú	disk_jobsr  Úseenrä  Ú	recoveredÚdisk_jobÚdisk_ids
   &&        r-   Ú_unmerged_disk_jobsr.  Ê  s3  € ô Ô$ l°DÓ9€EØÒÔ-Ô.AÓ.C×.MÑ.MÓNÐRWÔWØˆ	Ü#Ó%€IØÒÜØ=Ô>QÓ>S×>]Ñ>]Ð=^Ð_óað 	aá"&ÓN¡$˜Q¬*°Q¼×*=ŒLÀ!Ç%Á%ÈÇ+ŒLŒC��$•ŽL¡$€DÐNØ×/Ð/¨RÐ/Ó6Ñ/˜³AŒVŒS�ŽVÑ/Ñ6Õ6€DØ&(€IÛˆÜ˜(¤D×)Ò)°·±¸d×1CÒ1CÙÜ�h˜t•nÓ%ˆØÖØ×Ñ˜XÔ&Ø�H‰H�WÖñ ð Ðùò OùÚ6s   Á,E&Â	E&Â"E&Ã	E+ÃE+r%  c          	      óØ   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        \
        \        ,          ,          R\         \        \        \        3,          ,          /# r$  r&  )r,   s   "r-   r.   r.   ä  sG   € ÷ ñ Ü
Œt”Cœ�H�~Õ
ðÜ08¼ÄC½Õ0Iðä	Œ$Œs”Cˆx�.Õñr/   c          
     óÂ   € \        W4      pV'       g   V # \        P                  R\        V4      V Uu. uF  q3P	                  R4      NK  	  up4       W,           # u upi )zÇ*jobs* plus on-disk jobs absent from the payload (under the degraded flock-timeout path a
stale writer would otherwise clobber concurrent creates). Deletes pass ``removed_ids``; never
mutates *jobs*.z™Preserved %d cron job(s) present on disk but missing from the in-memory save payload (concurrent create under degraded lock or stale writer) (#80624): %srx   )r.  r­   r®   rÜ  rz   )rù  r%  r+  r  s   &$  r-   Ú_merge_unexpected_disk_jobsr1  ä  sW   € ô $ DÓ6€IßØˆÜ
‡N�Nð	(ô 	ˆI‹©iÓ8©i¨Ÿ™˜tž©iÑ8ô	:ð
 ÕÐùò 9s   ´Ac                ó>   € V ^8„  d   QhR\         \        ,          RR/# )r(   rc  r)   Nrç  )r,   s   "r-   r.   r.   õ  s   € ÷ ñ œ¤�ð ¨$ñ r/   c                 ó¼   € V eD   \         P                  ! \        4      ;_uu_ 4        \        P                  ! V 4       R R R 4       R # R #   + '       g   i     R # ; ir/  )rØ   rÙ   r¹   rn  rä  r~  s   &r-   Ú_unlink_quietr4  õ  s;   € ØÒÜ× Ò ¤×)Õ)Ü�IŠI�dŒO÷ *Ñ)ñ ß)×)Ð)ús   §A
Á
A	c                ót   € V ^8„  d   QhR\         R\        \        \        \        3,          ,          R\        /# )r(   rL   rù  r)   )r
   r  r  r_   r   )r,   s   "r-   r.   r.   û  s.   € ÷ ñ ¤4ð ¬t´D¼¼c¸µNÕ/Cð Ìñ r/   c           	     óª  € \        V RRR7      w  r# \        P                  ! VRRR7      ;_uu_ 4       p\        P                  ! RVR\        4       P                  4       /V^R	R
7       VP                  4        \        P                  ! VP                  4       4       RRR4       V#   + '       g   i     T# ; i  \         d    \        T4       h i ; i)zdSerialize the store payload to a fsynced temp file beside the resolved *jobs_file*; return its path.z.tmpz.jobs_)ÚsuffixÚprefixÚwrà   rÍ   rù  Ú
updated_atF)ÚindentÚensure_asciiN)r#   rn  Úfdopenry  Údumpr  r  ÚflushÚfsyncr½   ÚBaseExceptionr4  )rL   rù  ÚfdÚtmp_pathrô  s   &&   r-   Ú_stage_jobs_payloadrD  û  sª   € ä! )°FÀ8ÔL�L€Bð	Ü�YŠY�r˜3¨×1Õ1°QÜ�IŠIØ˜˜|¬[«]×-DÑ-DÓ-FÐGØ˜!¨%õ1ð �G‰GŒIÜ�HŠH�Q—X‘X“ZÔ ÷ 2ð €O÷ 2Ö1ð €Oûô ô Ü�hÔØðús)   ’!B: ³A)B&ÂB: Â&B7	Â1B: Â7B: Â:CrÌ  c                ó    € V ^8„  d   QhR\         \        \        \        3,          ,          R\        \
        \        ,          ,          R\        /# ©r(   rù  r%  rÌ  ©r  r  r_   r   r   r   r+   )r,   s   "r-   r.   r.     s:   € ÷ -ñ -Ü
Œt”Cœ�H�~Õ
ð-Ü08¼ÄC½Õ0Ið-äñ-r/   c               ó  € \        4       P                  p\        4        RpW3P                  3 FE  p\        P
                  ! \        4      ;_uu_ 4        \        P                  ! V4      p RRR4        M	  Rp \        \        ^,           4       FÂ  pV'       g   \        WR7      p \        W04      pV'       g,   V\        8  d!   \        W4      '       d   \        V4       RpKU  \        \!        Wc4      4      pRp\#        VP                  4       \%        V4       \'        W44       \)        R4       \*        P,                  ! VP                  4        R# 	  R#   + '       g   i     EK<  ; i  \.         d    \        T4       h i ; i)a  Save all jobs; caller must hold _jobs_lock(). ``removed_ids`` = intentional deletes;
``replace=True`` skips the shrink-merge guard and the corrupt-store refusal (wholesale
rewrite for tests, disaster recovery and load_jobs' auto-repair of unmergeable shapes).N©r%  )r�   rL   rÓ   rw  rØ   rÙ   r¹   rn  r  r°  Ú_SAVE_JOBS_MERGE_ATTEMPTSr1  rD  r.  r4  r
   r    r"   rj  rx  r  r   Únote_writablerA  )	rù  r%  rÌ  rL   Ú_stat_beforeÚproberC  ÚattemptÚreplaceds	   &$$      r-   Ú_save_jobs_unlockedrP    s1  € ô $Ó%×/Ñ/€IÜ„Mà€LØ×-Ñ-Ó.ˆÜ× Ò ¤×)Õ)ÜŸ7š7 5›>ˆLØ÷ *Ñ)ñ /ð €HðÜÔ6¸Õ:Ö;ˆGßÜ2°4ÔQ�Ü*¨9Ó;ˆH÷ ØÔ7Ô7Ü'¨×:Ò:ä˜hÔ'Ø�ÙäœN¨8Ó?Ó@ˆHØˆHÜ˜HŸO™OÔ,Ü˜Ô#Ü$ YÔ=ô ˜tÔ$Ü×&Ò& y×'7Ñ'7Ô8Úó/ <÷ *×)Ñ)ûô> ô Ü�hÔØðús   ÁEÁ:CE- ÅE- ÅE*Å-Fc                ó    € V ^8„  d   QhR\         \        \        \        3,          ,          R\        \
        \        ,          ,          R\        /# rF  rG  )r,   s   "r-   r.   r.   >  s?   € ÷ Lñ LÜ
Œt”Cœ�H�~Õ
ðLÜ08¼ÄC½Õ0IðLäñLr/   c               ó~   € \        4       ;_uu_ 4        \        WVR7       RRR4       R#   + '       g   i     R# ; i)zZSave all jobs under the lock; see ``_save_jobs_unlocked`` for ``removed_ids``/``replace``.)r%  rÌ  N)rÛ   rP  )rù  r%  rÌ  s   &$$r-   r  r  >  s!   € ô
 
��Ü˜DÀ7ÕK÷ 
��Šús   “+«<	c          	      óÚ   € V ^8„  d   QhR\         R\        \        \        \        \         3,          ,          \
        \        \        \         3,          .\         3,          R\         R\         /# )r(   r]   rø   Úmissingr)   )r   r   r  r  r_   rŠ  )r,   s   "r-   r.   r.   J  sR   € ÷ 
ñ 
Üð
Üœt¤D¬¬c¨¥NÕ3´S¼$¼sÄC¸x½.ÐIÌ3ÐNÕOð
ÜZ]ð
äñ
r/   c                óö   € \        4       ;_uu_ 4        \        4       p\        V4       F0  w  rEVP                  R4      V 8X  g   K  V! W4V4      u uuRRR4       # 	  RRR4       V#   + '       g   i     T# ; i)zdRun ``fn(jobs, i, job)`` on the first match under ``_jobs_lock()``; ``fn`` saves. *missing*
if none.rx   N)rÛ   r�   rÝ  rz   )r]   rø   rT  rù  rä  r  s   &&&   r-   Ú	_with_jobrV  J  s_   € ô
 
��Ü‹{ˆÜ –o‰FˆAØ�w‰w�t‹} Ö&Ù˜$ 3Ó'Ñ'÷	 
Šá%÷ 
ð
 €N÷ 
Žð
 €Nús   “.A'Á
A'ÁA'Á'A8	c                óJ   € V ^8„  d   QhR\         \        \        3,          RR/# ©r(   r  r)   Nr  )r,   s   "r-   r.   r.   W  s&   € ÷ Cñ Cœd¤3¬ 8�nð C°ñ Cr/   c                ó.   € V P                  RRRR7       R# )zPRetire *job* in place as a terminal completion (record kept for `cronjob list`).FrW  N)rR  rG  Únext_run_at©Úupdater4  s   &r-   Ú_complete_job_recordr]  W  s   € à‡J�J�u K¸T€JÖBr/   c                óJ   € V ^8„  d   QhR\         \        \        3,          RR/# rX  r  )r,   s   "r-   r.   r.   \  s&   € ÷ Tñ Tœd¤3¬ 8�nð T°ñ Tr/   c                ó0   € V P                  RRRRR7       R# )z8Clear pause markers in place so *job* is runnable again.TrV  N)rR  rG  rN  Úpaused_reasonr[  r4  s   &r-   Ú_activate_job_recordra  \  s   € à‡J�J�t ;¸$Èd€JÖSr/   c                ó\   € V ^8„  d   QhR\         \        ,          R\         \        ,          /# )r(   Úworkdirr)   rç  )r,   s   "r-   r.   r.   a  s"   € ÷ ñ ¤¬¥ð ´(¼3µ-ñ r/   c                ó˜  € V f   R# \        V 4      P                  4       pV'       g   R# \        V4      P                  4       pVP	                  4       '       g   \        RV: R24      hVP                  4       pVP                  4       '       g   \        RV 24      hVP                  4       '       g   \        RV 24      h\        V4      # )a  Workdir -> absolute path, or None when empty. ``~`` expands; relative paths are rejected
(cron runs detached from any cwd); must be an existing dir now but is deliberately NOT
re-checked at run time (scheduler falls back with a warning). ValueError when invalid.Nz+Cron workdir must be an absolute path (got zN). Cron jobs run detached from any shell cwd, so relative paths are ambiguous.zCron workdir does not exist: z!Cron workdir is not a directory: )	r_   r�   r
   r“   r  rŸ   rŠ   r   Úis_dir)rc  r£   ÚexpandedÚresolveds   &   r-   Ú_normalize_workdirrh  a  sÂ   € ð ‚ÙÜ
ˆg‹,×
Ñ
Ó
€CßÙÜ�C‹y×#Ñ#Ó%€HØ×Ñ×!Ò!ÜØ9¸#¹ð AZð [ó\ð 	\ð ×ÑÓ!€HØ�?‰?×ÒÜÐ8¸¸
ÐCÓDÐDØ�?‰?×ÒÜÐ<¸X¸JÐGÓHÐHÜˆx‹=Ðr/   c                ór   € V ^8„  d   QhR\         \        \        ,          \        \        ,          3,          /# r'   )rÃ  r   r_   )r,   s   "r-   r.   r.   w  s%   € ÷ ;ñ ;œœx¬�}¬h´s­mÐ;Õ<ñ ;r/   c                 ó^  € ^ RI Hp  \        4       R,          pVP                  4       '       d	   V ! V4      M/ pVP	                  R4      ;'       g    / p\        V\        4      '       d+   VP	                  R4      ;'       g    VP	                  R4      MTp\        V4      pV'       g   R	# Rp\        P                  ! \        4      ;_uu_ 4        ^ RIHp \        V! RR7      P	                  R4      4      pRRR4       V'       d   VP                  4       V3# RV3#   + '       g   i     L.; i)
a  ``(provider, model)`` the main agent runs on right now (``model.default`` + the provider it
resolves to), for ``pinned=True`` jobs: the lock is a plain per-job pin, so the scheduler needs
no second precedence axis. ``(None, None)`` when nothing is configured (the job stays unpinned).)Úload_user_config_effectivezconfig.yamlÚmodelrX   N)Úresolve_runtime_provider)Ú	requestedÚprovider©NN)Úhermes_cli.config_effectiverk  r   r   rz   r  r  Ú_normalize_job_optional_textrØ   rÙ   r¬   Úhermes_cli.runtime_providerrm  r“  )rk  Úcfg_pathÚcfgÚ	model_cfgrl  ro  rm  s          r-   Ú_main_model_pinrw  w  së   € õ GäÓ  =Õ0€HØ2:·/±/×2CÒ2CÑ
$ XÔ
.È€CØ—‘˜Ó ×&Ð& B€IÜBLÈYÔX\×B]ÒB]ˆI�M‰M˜)Ó$×>Ð>¨	¯©°gÓ(>øÐcl€EÜ(¨Ó/€EßØÐØ€HÜ	×	Ò	œY×	'Õ	'ÝHÜ/Ñ0HÐSWÔ0X×0\Ñ0\Ð]gÓ0hÓiˆ÷ 
(÷ !)ˆH�N‰NÓ°UÐ:Ð:¨d°UÐ:Ð:÷ 
(×	'ús   Ã(DÄD,	Ústrip_trailing_slashc                óR   € V ^8„  d   QhR\         R\        R\        \        ,          /# )r(   r#  rx  r)   )r   r+   r   r_   )r,   s   "r-   r.   r.   ‹  s-   € ÷ Zñ ZÜðZÜ)-ðZäŒc…]ñZr/   c                ó´   € \        V \        4      '       g   R # V'       d    V P                  4       P                  R4      MV P                  4       ;'       g    R # )Nr  )r  r_   r�   Úrstrip)r#  rx  s   &$r-   rr  rr  ‹  s>   € ô �eœS×!Ò!Ùß)=ˆE�K‰K‹M× Ñ  Ô%À5Ç;Á;Ã=×YÐYÐUYÐYr/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# r3  ©r   r   r_   )r,   s   "r-   r.   r.   “  s"   € ÷ Jñ Jœsð J¤x´¥}ñ Jr/   c                 ó   € \        V R R7      # )T)rx  )rr  r5  s   &r-   Ú_normalize_base_urlr  “  s   € Ü'¨ÀDÔIÐIr/   c                ó\   € V ^8„  d   QhR\         R\        \        \        ,          ,          /# )r(   r  r)   ©r   r   r  r_   )r,   s   "r-   r.   r.   —  s'   € ÷ Eñ Eœsð E¤x´´Sµ	Õ':ñ Er/   c                ó´   € V  Uu. uF=  p\        V4      P                  4       '       g   K$  \        V4      P                  4       NK?  	  up;'       g    R# u upi )zBNon-blank stripped items of *items*, or None when nothing remains.N©r_   r�   )r  r  s   & r-   Ú_normalize_str_listr„  —  s:   € á$)Ó<¡E˜q¬S°«V¯\©\¯^ŒNŒC�‹F�L‰LŽN¡EÑ<×DÐDÀÐDùÒ<s
   …!A«Ac                ó\   € V ^8„  d   QhR\         R\        \        \        ,          ,          /# r3  r�  )r,   s   "r-   r.   r.   œ  s#   € ÷ =ñ =¤sð =¬x¼¼S½	Õ/Bñ =r/   c                óî   € V f   R# \        V \        4      '       g   \        R4      hV  Uu. uF=  p\        V4      P	                  4       '       g   K$  \        V4      P	                  4       NK?  	  up# u upi )u  Per-job toolset allowlist. An explicitly-set EMPTY list means 'nothing allowed' and must
persist as [] â€” normalizing it to None would read back as 'no restriction' (#82010). Absent
or None stays None (the unrestricted default); a non-list raises before anything is stored.NzBenabled_toolsets must be a list of toolset names (or [] for none).)r  r  rŸ   r_   r�   )r#  r  s   & r-   Ú_normalize_enabled_toolsetsr‡  œ  sU   € ð ‚}ÙÜ�eœT×"Ò"ÜÐ]Ó^Ð^Ù$)Ó<¡E˜q¬S°«V¯\©\¯^ŒNŒC�‹F�L‰LŽN¡EÑ<Ð<ùÒ<s   ¬!A2ÁA2c                ó\   € V ^8„  d   QhR\         R\        \        \        ,          ,          /# r3  r�  )r,   s   "r-   r.   r.   §  s'   € ÷ Kñ K¤3ð K¬8´D¼µIÕ+>ñ Kr/   c                óz   € \        V \        4      '       d   V .p \        V \        4      '       d   \        V 4      # R# )z8Accept a job id or a list of ids; anything else is None.N)r  r_   r  r„  r5  s   &r-   Ú_normalize_context_fromrŠ  §  s3   € ä�%œ×ÒØ�ˆÜ)3°E¼4×)@Ò)@Ô˜uÓ%ÐJÀdÐJr/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# r3  r}  )r,   s   "r-   r.   r.   ®  s   € ÷ /ñ /¤cð /¬h´s­mñ /r/   c                ó”   € \        V \        \        34      '       d#   RP                  R V  4       4      ;'       g    R# \	        V 4      # )zÚfailure_deliver shares deliver's value grammar; flatten str/list like the tool layer's
_normalize_deliver_param for direct create_job callers. Semantic validation happens at
resolution time via the shared deliver path.rÕ  c              3   ó–   "  € T F?  p\        V4      P                  4       '       g   K$  \        V4      P                  4       x € KA  	  R # 5ir/  rƒ  rü  s   & r-   r~   Ú-_normalize_failure_deliver.<locals>.<genexpr>³  s*   é € ÐH±¨1¼¸Q»¿¹¿œœ˜A›Ÿ™Ÿ˜³ùs
   ‚!A	¨!A	N)r  r  rÃ  rß  rr  r5  s   &r-   Ú_normalize_failure_deliverr�  ®  s=   € ô �%œ$¤˜×'Ò'Ø�x‰xÑH±ÓHÓH×PÐPÈDÐPÜ'¨Ó.Ð.r/   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# r3  r}  )r,   s   "r-   r.   r.   ·  s   € ÷ ñ ¤sð ¬x¼­}ñ r/   c                óÆ   € V f   R# \        V 4      P                  4       P                  4       pV'       g   R# ^ RIHp V! V4      f   \        RV : R24      hVR9   d   R# V# )a  Spelling-only validation via the shared parser (cron knob never stricter/looser than
config.yaml); model capability is deliberately NOT checked (model unknowable at create time,
transports clamp at send time). None for unset, lowercase level, or ValueError.N)Úparse_reasoning_effortzInvalid reasoning_effort zg. Valid levels: none, minimal, low, medium, high, xhigh, max, ultra (empty string clears the override).r�  >   ÚfalseÚdisabled)r_   r�   r“  Úhermes_constantsr’  rŸ   )r#  r  r’  s   &  r-   Ú_normalize_reasoning_effortr–  ·  sl   € ð ‚}ÙÜˆu‹:×ÑÓ×#Ñ#Ó%€DßÙÝ7á˜dÓ#Ò+ÜØ'¨¡yð 12ð 2ó3ð 	3ð Ð$Ô$ÙØ€Kr/   rl  ro  Úbase_urlÚmonitor_scriptÚmonitor_urlÚenabled_toolsetsrc  Úcontext_fromÚfailure_deliverÚinterpreterc                 ó*   € V R9   d   R # \        V 4      # )N>   FNr  )rh  )r  s   &r-   Ú<lambda>rŸ  ß  s   €  Ð&7Ô!7˜ÐRÔ=OÐPQÓ=RÐRr/   Úreasoning_effortc                ó0   € V ^8„  d   QhR\         R\        /# )r(   r=  r)   )r   rŸ   )r,   s   "r-   r.   r.   ç  s   € ÷ Jñ J¤cð J¬jñ Jr/   c                 ó.   € \        R V  R\         R24      # )úRequested one-shot time z is more than z&s in the past and cannot be scheduled.)rŸ   rE  )r=  s   &r-   Ú_oneshot_past_grace_errorr¤  ç  s)   € ÜØ
" 6 (¨.Ü Ð
!Ð!Gð	IóJð Jr/   c                óŒ   € V ^8„  d   QhR\         \        \        3,          R\        R\        R\        R\        \        ,          /# )r(   Úparsed_scheduleÚlabelÚfallback_run_atÚwhatr)   r©  )r,   s   "r-   r.   r.   í  s@   € ÷ ñ Üœ#œs˜(•^ðÜ,/ðÜBEðÜMPðäŒc…]ñr/   c                óÔ   € \        V 4      pV P                  R4      R8X  dG   VfC   V P                  R4      ;'       g    Tp\        P                  RW1V\        4       \        V4      hV# )z�``compute_next_run`` that raises (after a warning log) for a one-shot outside the grace
window, so a ghost job with ``next_run_at=None`` can never be stored.r^  r�  r=  zMRejecting one-shot cron job %s'%s': run_at %s is outside the %ss grace window)r¹  rz   r­   r®   rE  r¤  )r¦  r§  r¨  r©  rZ  r=  s   &&&&  r-   Ú _next_run_or_reject_past_oneshotr«  í  sh   € ô
 # ?Ó3€KØ×Ñ˜6Ó" fÔ,°Ò1DØ ×$Ñ$ XÓ.×AÐA°/ˆÜ�‰Ø[Ø˜Ô!6ô	8ô (¨Ó/Ð/ØÐr/   c          6      óº  € V ^8„  d   QhR\         \        ,          R\        R\         \        ,          R\         \        ,          R\         \        ,          R\         \        \        \        3,          ,          R\         \        ,          R\         \
        \        ,          ,          R	\         \        ,          R
\         \        ,          R\         \        ,          R\         \        ,          R\         \        \
        \        ,          ,          ,          R\         \
        \        ,          ,          R\         \        ,          R\        R\         \        ,          R\         \        ,          R\         \        ,          R\         \        ,          R\         \        ,          R\        R\         \        ,          R\        R\         \        ,          R\        \        \        3,          /# )r(   r-  r9  rC  r‰  ÚdeliverÚoriginr  r  rl  ro  r—  r(  r›  rš  rc  r)  Úattach_to_sessionr˜  r™  r   rœ  rM  r`  Úpinnedr�  r)   )r   r_   rŠ  r  r   r  r+   )r,   s   "r-   r.   r.   ü  s   € ÷ Bñ BÜ”S�MðBäðBô ”3�-ðBô ”S�Mð	Bô
 ”c�]ðBô ”Tœ#œs˜(•^Õ$ðBô ”C�=ðBô ”Tœ#•YÕðBô ”C�=ðBô ”s�mðBô ”s�mðBô ”S�MðBô œ3¤¤c¥�?Õ+ðBô œt¤C�yÕ)ðBô ”c�]ðBô  ð!Bô"  ¤•~ð#Bô$ œS•Mð%Bô& œ#•ð'Bô( œs•mð)Bô* œc•]ð+Bô, ð-Bô. œC•=ð/Bô0 ð1Bô2 œ#•ð3Bô4 
Œ#Œsˆ(…^ñ5Br/   c           	     ó*  € \        V\        4      '       g   \        R4      hVe"   \        V\        4      '       g   \        R4      hVe   V'       g   \        R4      h\	        V4      p\        V4      pVR,          R8X  d   Vf   ^pVf   V'       d   RMRp\        P                  ! 4       P                  R	,          p\        4       P                  4       p\        4       p\        P                  4        UUu/ uF  w  ppVV! VV,          4      bK  	  ppp\        Wg4      p \        V\        4      '       d   TMRp!\        V4      p"\!        VR
,          VR,          VR,          VR,          4       \#        V 4      P%                  4       p#V#'       g'   VR,          '       g   V '       g   \        \&        4      h^ RIHp$ V$! V#VR,          4       T#;'       g@    V '       d
   V ^ ,          MR;'       g%    VR,          '       d
   VR,          MR;'       g    Rp%T;'       g    V%R,          P%                  4       pV'       d#   VR,          '       g   \-        4       w  VR&   VR&   \/        VW!R4      p&/ RVbRVbRV#bRV bRV '       d
   V ^ ,          MRbRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bR
VR
,          bRVR,          bRRbRVR,          bRVbRVP1                  RV4      bRR VR!^ /b/ R"V'       * bR#V'       d   R$MR%bR&V'       d   TMRbR'V'       d%   T;'       g    RP%                  4       ;'       g    R(MRbR)VbR*V'       d   RMT&bR+RbR,RbR-RbR.RbR/RbR0RbR1^ bR2VbRVbR3VR3,          bR4VR4,          bCp'R5V!3R6V"3R7VR7,          3R8VR8,          33 F  w  pp(V(f   K  V(V'V&   K  	  \3        4       ;_uu_ 4        \5        \7        4       V'.,           4       RRR4       V'# u uppi   + '       g   i     T'# ; i)9aã  Create a new cron job and return the stored record.

deliver defaults to "origin" when ``origin`` is given, else "local"; repeat None = forever.
script: stdout is injected as prompt context, or with ``no_agent=True`` IS the job (stdout
delivered verbatim, requires ``script``). context_from: job id(s) whose latest output is
injected. workdir: absolute cwd for tools/scripts. monitor_script/monitor_url: cheap monitor
source run FIRST each tick; unchanged output suppresses the agent run (mutually exclusive,
incompatible with ``no_agent``). reasoning_effort: per-job pin; capability NOT validated.
interpreter: absolute/``~`` Python for ``.py`` script/monitor_script, validated at run time
(a venv can be rebuilt or moved after creation).zpaused must be a boolean.Nzpaused_reason must be a string.z#paused_reason requires paused=True.r^  r�  r®  ÚlocalºNrÊ  Nr˜  r™  r)  r(  )Úcheck_gateway_lifecyclerD  rE  rl  ro  r  rx   rC  r-  r  r  r—  Úmonitor_stater›  r9  r8  r;  r‰  ÚtimesrW  rR  rG  rM  rV  rN  r`  z+Created paused; awaiting operator approval.Ú
created_atrZ  rB  r\  Ú
last_errorÚlast_delivery_errorÚlast_delivery_unverifiedÚlast_failureÚfailure_streakr­  rš  rc  r¯  r   rœ  r�  )r  r+   rŸ   r_   r  r•  rê   Úuuid4rí   r  r  ÚlocalsÚ_CREATE_FIELD_NORMALIZERSr  r  r–  r   r'  r�   ÚEMPTY_PAYLOAD_ERRORÚcron.lifecycle_guardr´  rw  r«  rz   rÛ   r  r�   ))r-  r9  rC  r‰  r­  r®  r  r  rl  ro  r—  r(  r›  rš  rc  r)  r¯  r˜  r™  r   rœ  rM  r`  r°  r�  r¦  r]   r   r£   r>  Únormrô  Únormalized_skillsÚnormalized_attachÚnormalized_reasoning_effortÚprompt_textr´  rI  rZ  r  r#  s)   &&&&&&&&&&&&&&&&&&&&&&&&&                r-   Ú
create_jobrÇ  ü  s»  € ôJ �fœd×#Ò#ÜÐ4Ó5Ð5ØÒ ¬°MÄ3×)GÒ)GÜÐ:Ó;Ð;ØÒ ¯ÜÐ>Ó?Ð?Ü$ XÓ.€Oô $ FÓ+€FØ�vÕ &Ô(¨Vª^ØˆØ‚ß$‘(¨'ˆÜ�ZŠZ‹\×Ñ˜cÕ"€FÜ
‹-×
!Ñ
!Ó
#€Cä
‹(€CÜ.G×.MÑ.MÔ.OÔPÑ.O¡  dˆ‰d�3�s•8‹nÒ	Ñ.O€AÑPÜ-¨eÓ<ÐÜ-7Ð8IÌ4×-PÒ-PÑ)ÐVZÐÜ"=Ð>NÓ"OÐä! !Ð$4Õ"5°q¸Õ7GÈÈ:ÍÐXYÐZbÕXcÔdÜ" 6Ó*×0Ñ0Ó2€Kß˜q Ÿ{œ{×3DÜÔ,Ó-Ð-å<Ù˜K¨¨8­Ô5ð 	÷ 	ð 	ß$5Ð˜aÖ ¸4÷	ð 	à˜ZŸ=œ=ˆAˆhŽK¨d÷	ð 	ð ð	 ð ×,Ð,�< Õ$×*Ñ*Ó,€Dß�a˜—j”jÜ$3Ó$5Ñ!ˆˆ*‰�q˜‘zÜ2°?ÀDÐTVÓW€Kð%Øˆfð%à�ð%ð 	�+ð%ð 	Ð#ð	%ð
 	×):Ð" 1Ö%Àð%ð 	��7•ð%ð 	�A�j•Mð%ð 	�A�j•Mð%ð 	�!�H•+ð%ð 	�A�j•Mð%ð 	˜!Ð,Õ-ð%ð 	�q˜Õ'ð%ð 	˜ð%ð 	˜˜.Õ)ð%ð 	�Oð%ð  	˜O×/Ñ/°	¸8ÓDð!%ð" 	�7˜F K°Ð3ñ#%ð$ 	�v”:ð%%ð& 	ŸV‘¨ð'%ð( 	ŸF‘S¨ð)%ð* 	×ms˜=×.Ð.¨B×5Ñ5Ó7×hÐhÐ;høÐy}ð+%ð, 	�cð-%ð. 	Ÿv‘t¨;ð/%ð0 	�tð1%ð2 	�tð3%ð4 	�dð5%ð6 	˜tð7%ð: 	# Dð;%ð> 	˜ð?%ð@ 	˜!ðA%ðB 	�7ðC%ðD 	�&ðE%ðF 	˜AÐ0Õ1ðG%ðH 	�1�Y•<ñI%€CðT 
Ð/Ð0Ð3EÐGbÐ2cØ	˜AÐ/Õ0Ð1°MÀ1À]ÕCSÐ3Tó‰
ˆˆUð ÔØˆC�‹Hñô 
��Ü”)“+  Õ%Ô&÷ 
à€JùóU 	Q÷P 
Žà€Jús   Ã7O;ÏPÐP	c                óh   € V ^8„  d   QhR\         R\        \        \         \        3,          ,          /# rs   ©r_   r   r  r   )r,   s   "r-   r.   r.   �  s+   € ÷ Cñ C”Cð CœH¤T¬#¬s¨(¥^Õ4ñ Cr/   c                ób   a € \        V 3R l\        4        4       R4      pVe   \        V4      # R# )zGet a job by ID.c              3   óF   <"  € T F  qR ,          S8X  g   K  Vx € K  	  R# 5irw   rG   )r|   r  r]   s   & €r-   r~   Úget_job.<locals>.<genexpr>ƒ  s   øé € Ð<™;�a¨D­'°VÑ*;—’›;ùó   ƒ!—
!N)r  r�   rJ  )r]   r  s   f r-   Úget_jobrÎ  �  s,   ø€ ä
Ô<œ9œ;Ó<¸dÓ
C€CØ),ªÔ  Ó%ÐB¸dÐBr/   c                   óB   a a€ ] tR tRt oRtV3R lV 3R lltRtVtV ;t# )ÚAmbiguousJobReferencei‡  z1Raised when a job name matches more than one job.c                óL   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          /# )r(   ÚrefÚmatches)r_   r  r  r   )r,   rD   s   "€r-   r.   Ú"AmbiguousJobReference.__annotate__Š  s(   ø€ ÷ (ñ (™Cð (©$©t±C¹°H­~Õ*>ñ (r/   c           	     ó”   <€ Wn         W n        R P                  R V 4       4      p\        SV `  RV R\        V4       RV R24       R# )rü  c              3   ó2   "  € T F  qR ,          x € K  	  R# 5irw   rG   )r|   r–  s   & r-   r~   Ú1AmbiguousJobReference.__init__.<locals>.<genexpr>�  s   é € Ð1© A˜$Ÿš«ùs   ‚z
Job name 'u   ' is ambiguous â€” matches z jobs: z. Use the job ID instead.N)rÒ  rÓ  rß  Úsuperrf   rÜ  )re   rÒ  rÓ  ÚidsÚ	__class__s   &&& €r-   rf   ÚAmbiguousJobReference.__init__Š  sQ   ø€ ØŒØŒØ�i‰iÑ1©Ó1Ó1ˆÜ‰ÑØ˜˜Ð8¼¸W»¸ÀgÈcÈUð S&ð 'ö	(r/   )rÓ  rÒ  )	rN   rO   rP   rQ   rh   rf   rT   rU   Ú__classcell__)rÚ  rD   s   @@r-   rÐ  rÐ  ‡  s   ù‡ € Ù;÷(÷ (ó (r/   rÐ  c                óh   € V ^8„  d   QhR\         R\        \        \         \        3,          ,          /# )r(   rÒ  r)   rÉ  )r,   s   "r-   r.   r.   “  s'   € ÷ 2ñ 2œð 2¤¬$¬s´C¨x­.Õ!9ñ 2r/   c           	     óÌ  a € S '       g   R# \        4       p\        V 3R lV 4       R4      pVe   \        V4      # S P                  4       pV Uu. uF4  qDP	                  R4      ;'       g    RP                  4       V8X  g   K2  VNK6  	  ppV'       g   R# \        V4      ^8”  d&   \        S V Uu. uF  p\        V4      NK  	  up4      h\        V^ ,          4      # u upi u upi )z¢Resolve an ID or name to a job record. Exact ID wins, then case-insensitive name; an
ambiguous name raises AmbiguousJobReference rather than silently picking one.Nc              3   óF   <"  € T F  qR ,          S8X  g   K  Vx € K  	  R# 5irw   rG   )r|   r  rÒ  s   & €r-   r~   Ú"resolve_job_ref.<locals>.<genexpr>™  s   øé € Ð4™T˜ t¥W°¡^—!’!›TùrÍ  rC  r  )r�   r  rJ  r“  rz   rÜ  rÐ  )rÒ  rù  Úby_idÚ	ref_lowerr  Úname_matchess   f     r-   Úresolve_job_refrä  “  sÄ   ø€ ÷ ÙÜ‹;€DÜÔ4™TÓ4°dÓ;€EØÒÜ$ UÓ+Ð+Ø—	‘	“€IÙ#ÓR™t˜!¯©¨f«×(;Ð(;¸×'BÑ'BÓ'DÈ	Ñ'Q—A�A™t€LÐRßÙÜ
ˆ<Ó˜1ÔÜ# CÉLÓ)YÉLÀqÔ*?ÀÖ*BÉLÑ)YÓZÐZÜ  ¨a¥Ó1Ð1ùò Sùò *Zs   ÁCÁ+CÂCÂ/C!
c                óh   € V ^8„  d   QhR\         R\        \        \        \        3,          ,          /# )r(   Úinclude_disabledr)   )r+   r  r  r_   r   )r,   s   "r-   r.   r.   ¥  s'   € ÷ ñ ¤ð ´´d¼3Ä¸8µnÕ1Eñ r/   c           	     ó¨  € \        4        Uu. uF  p\        V4      NK  	  ppV '       g+   V Uu. uF  qP                  RR4      '       g   K  VNK   	  pp ^ RIHp T! V Uu. uF  qDP                  RR4      NK  	  up4      pV F'  pVP                  VP                  RR4      4      VR&   K)  	  V# u upi u upi u upi   \
         d    / p LMi ; i)z2List all jobs, optionally including disabled ones.rR  T)Úlatest_executionsrx   r  Úlatest_execution)r�   rJ  rz   Úcron.executionsrè  r¬   )ræ  r  rù  rè  r  Úlatests   &     r-   Ú	list_jobsrì  ¥  s¾   € ä.7¬kÓ:©k¨Ô! !Ö$©k€DÐ:ßÙÓ:™4�a§5¡5¨°D×#9—�™4ˆÐ:ðÝ5á"ÁÓ#FÁ¸#§G¡G¨D°"Ö$5ÁÑ#FÓGˆó ˆØ"(§*¡*¨S¯W©W°T¸2Ó->Ó"?ˆÐÓñ à€Kùò ;ùâ:ùò $GøÜô ØŠðús4   �B2¯B7ÁB7ÁC Á"B<Á<C Â<C ÃCÃCc                ó„   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          R\        RR/# )r(   r  Úupdatedr]   r)   Nr  )r,   s   "r-   r.   r.   µ  s@   € ÷ Fñ F¤T¬#¬s¨(¥^ð F¼dÄ3ÌÀ8½nð FÔVYð FÐ^bñ Fr/   c                ó  € \        V 4      '       ds   \        V 4      '       g`   VP                  R4      R	9  g)   VP                  R4      RJ g   VP                  R4      e    \        RV P                  RV4       R24      hR# R# R# )
zTA genuinely terminal job cannot be reactivated through update_job (use cron resume).rG  rR  TrZ  Nz#Cannot activate terminal cron job 'rC  z8' through update_job; use cron resume --run-now or --at.>   r×   rW  )r[  r`  rz   rŸ   )r  rî  r]   s   &&&r-   Ú_reject_terminal_activationrð  µ  sŒ   € ô 	˜×ÒÜ)¨#×.Ò.à�K‰K˜Ó Ð(>Ô>Ø�{‰{˜9Ó%¨Ó-Ø�{‰{˜=Ó)Ò5ô Ø1°#·'±'¸&À&Ó2IÐ1Jð KEð EóFð 	Fñ 6ñ	 /ñ 	r/   c                óx   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          RR/# ©r(   r  Úupdatesr)   Nr  )r,   s   "r-   r.   r.   Å  s1   € ÷ ;ñ ;œ4¤¤S �>ð ;´D¼¼c¸µNð ;Àtñ ;r/   c                óò   € RV9  d   R# \        VP                  R4      4      pRV9   d   R# V'       d8   \        V P                  R4      4      '       g   \	        4       w  VR&   VR&   R# R# RRuVR&   VR&   R# )a  ``pinned`` is not stored; it rewrites the per-job pin. ``True`` with no explicit model locks
the main agent's current provider+model onto the job, ``False`` releases both so the job follows
the main model again (an explicit ``model`` in the same update wins over either).r°  Nrl  ro  )r+   rî   rr  rz   rw  )r  ró  r°  s   && r-   Ú_apply_pin_updaterõ  Å  sx   € ð �wÔÙÜ�'—+‘+˜hÓ'Ó(€FØ�'ÔÙßÜ+¨C¯G©G°GÓ,<×=Ò=Ü4CÓ4EÑ1ˆG�JÑ ¨Ó!1ñ >ð 15°dÐ-ˆ�
Ñ˜W WÓ-r/   c                óx   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          RR/# rò  r  )r,   s   "r-   r.   r.   Õ  s6   € ÷ _ñ _¤¤S¬# X¥ð _¼¼cÄ3¸h½ð _ÈDñ _r/   c                óº  € \         P                  4        F  w  r#W!9   g   K  V! W,          4      W&   K  	  RV9   dŸ   VR,          pV P                  R4      ;'       g    / P                  R^ 4      p\        V\        4      '       dA   \	        V4      p\        VP                  R4      4      VR&   VP                  RV4       WAR&   R# R\        V4      RV/VR&   R# R# )z­Normalize updates in place like create_job; invalid values raise BEFORE the merge. ``repeat``
accepts the stored dict or a bare value (coerced, completed counter preserved).r‰  rW  r¶  N)Ú_UPDATE_FIELD_NORMALIZERSr  rz   r  r  r•  rã   )r  ró  r>  rÂ  Ú_rprW  s   &&    r-   Ú_normalize_job_updatesrú  Õ  sÅ   € ô /×4Ñ4Ö6‰	ˆØŽ>Ù ¥Ó-ˆG‹Lñ 7ð �7ÔØ�hÕˆØ—W‘W˜XÓ&×,Ð,¨"×1Ñ1°+¸qÓAˆ	Ü�cœ4× Ò Ü�s“)ˆCÜ1°#·'±'¸'Ó2BÓCˆC�‰LØ�N‰N˜;¨	Ô2Ø #�HÓà!(Ô*@ÀÓ*EÀ{ÐT]Ð ^ˆG�HÓñ r/   c                óx   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          RR/# rò  r  )r,   s   "r-   r.   r.   ç  s4   € ÷ ñ Ü	Œc”3ˆh�ðÜ"&¤s¬C x¥.ðà	ñr/   c                óú  € RV9  g   RV9   d   R# VR,          p\        V\        4      '       d   \        V4      pW!R&   V P                  R4      ;'       g    / P                  R4      pVP                  R4      pW48X  d   R# \	        V P                  R4      ;'       g    / 4      pVP                  R4      pVR8X  d   Vf   ^VR&   MVR8w  d   VR8X  d   V^8X  d   RVR&   MR# VP                  R^ 4       WQR&   R# )aÅ  Re-derive the ``repeat`` default when a schedule update flips the kind.

``create_job`` derives it from the schedule kind (once -> 1, recurring -> forever); the update
path must honour the same contract, otherwise a one-shot turned recurring keeps its ``times=1``
budget and retires after one fire, while a recurring job turned one-shot never completes. An
explicit ``repeat`` in the same update wins; a same-kind schedule edit leaves ``repeat`` alone.
r9  r‰  Nr^  r¶  r�  rW  )r  r_   r  rz   r  rã   )r  ró  Únew_scheduleÚold_kindÚnew_kindr‰  r¶  s   &&     r-   Ú$_rederive_repeat_for_schedule_changer   ç  sð   € ð ˜Ô  H°Ô$7ÙØ˜:Õ&€LÜ�,¤×$Ò$Ü% lÓ3ˆØ*�
ÑØ—‘˜
Ó#×)Ð) r×.Ñ.¨vÓ6€HØ×Ñ Ó'€HØÔÙÜ�#—'‘'˜(Ó#×)Ð) rÓ*€FØ�J‰J�wÓ€EØ�6Ô˜ešmØˆˆwŠØ	�VÔ	 ¨FÔ 2°uÀ´zØˆˆwŠáØ
×Ñ�k 1Ô%ØˆHÓr/   c                ó„   € V ^8„  d   QhR\         \        \        3,          R\         \        \        3,          R\        RR/# )r(   rî  ró  r]   r)   Nr  )r,   s   "r-   r.   r.     s@   € ÷ 
Xñ 
X¤D¬¬c¨¥Nð 
X¼TÄ#ÄsÀ(½^ð 
XÔUXð 
XÐ]añ 
Xr/   c           	     ó:  € V R,          p\        V\        4      '       d   \        V4      pW0R&   VP                  RVP                  RV P                  R4      4      4      V R&   V P                  R4      R8w  d#   \	        W0P                  RV4      VR4      V R&   R	# R	# )
zZParse a string schedule, refresh ``schedule_display`` and (unless paused) ``next_run_at``.r9  r8  r;  rG  rM  rC  zupdate rZ  N)r  r_   r  rz   r«  )rî  ró  r]   Úupdated_schedules   &&& r-   Ú_apply_schedule_updater    sš   € à˜zÕ*ÐÜÐ"¤C×(Ò(Ü)Ð*:Ó;ÐØ.�
ÑØ")§+¡+ØÐ,×0Ñ0°¸G¿K¹KÐHZÓ<[Ó\ó#^€GÐÑà‡{�{�7Ó˜xÔ'Ü!AØŸk™k¨&°&Ó9Ð;KÈYó"Xˆ�Óñ (r/   c                óJ   € V ^8„  d   QhR\         \        \        3,          RR/# )r(   rî  r)   Nr  )r,   s   "r-   r.   r.     s"   € ÷ &ñ &¤D¬¬c¨¥Nð &°tñ &r/   c                ó`  € V P                  RR4      '       d.   V P                  R4      R8X  g   V P                  R4      '       d   R# \        V R,          4      pVfM   V R,          P                  R4      R	8X  d0   V R,          P                  R
R4      p\        RV R\         R24      hWR&   R# )zbAn enabled, unpaused record must never persist without ``next_run_at`` (it would never fire).
    rR  TrG  rM  rZ  Nr9  r^  r�  r=  rB  r£  ú is in the past (grace window: zs) and cannot be scheduled.)rz   r¹  rŸ   rE  )rî  r²  r=  s   &  r-   Ú_fill_missing_next_runr    s¯   € ð �K‰K˜	 4×(Ò(Ø�;‰;�wÓ 8Ô+Ø�;‰;�}×%Ò%áÜ ¨
Õ 3Ó4€HØÒ˜G JÕ/×3Ñ3°FÓ;¸vÔEØ˜Õ$×(Ñ(¨°9Ó=ˆÜØ& v hð /Ü3Ð4Ð4OðQóRð 	Rð &ˆMÓr/   c          	      ó–   € V ^8„  d   QhR\         R\        \         \        3,          R\        \        \         \        3,          ,          /# )r(   r]   ró  r)   )r_   r  r   r   )r,   s   "r-   r.   r.   &  s6   € ÷ '$ñ '$”sð '$¤T¬#¬s¨(¥^ð '$¼ÄÄcÌ3ÀhÅÕ8Pñ '$r/   c                óÆ   a a€ \         P                  S;'       g    / 4      pV'       d'   \        RRP                  \	        V4      4       24      hV V3R lp\        S V4      # )zCUpdate a job by ID, refreshing derived schedule fields when needed.z%Cron job field(s) cannot be updated: rü  c           
      ó`  <€ \        VS4       \        VS4       \        VS4       \        / VCSC4      p\	        W#S4       0 RmP                  S4      '       dq   \        VP                  R 4      ;'       g    RVP                  R4      ;'       g    R\        VP                  R4      4      \        VP                  R4      4      4       \        ;QJ d#    V3R l\         4       F  '       g   K   RM	  RM! V3R l\         4       4      '       d!   \        V4      '       d   \        \        4      hRS9   d   \        VSS4       ^ R	IHp V! V4       0 RmP                  S4      '       d   VP%                  R
R4       \'        V4       \	        W#S4       W0V&   \)        V 4       \+        V4      # )r˜  r™  r)  r(  Nc              3   ó,   <"  € T F	  qS9   x € K  	  R # 5ir/  rG   )r|   r0  ró  s   & €r-   r~   Ú,update_job.<locals>.apply.<locals>.<genexpr>:  s   øé € Ð5¡_ �GŽ|£_ùr2  TFr9  )Úclear_stateÚpending_slot>   r(  r)  r™  r˜  >   rG  rR  r9  rZ  )r   rú  rõ  r!  rð  Úintersectionr   rz   r+   rr  r3  Ú_PAYLOAD_FIELDSr5  rŸ   rÀ  r  r^  r  rî   r  r  rJ  )rù  rä  r  rî  Ú_clear_quota_holdr]   ró  s   &&&  €€r-   ÚapplyÚupdate_job.<locals>.apply-  sN  ø€ Ü,¨S°'Ô:Ü˜s GÔ,Ü˜#˜wÔ'Ü%Ð&8¨Ð&8°Ð&8Ó9ˆÜ# C°&Ô9âB×OÑOÐPW×XÒXÜ)Ø—‘Ð,Ó-×5Ð5°Ø—‘˜MÓ*×2Ð2¨dÜ�W—[‘[ Ó,Ó-Ü,¨W¯[©[¸Ó-BÓCô	E÷
 ‹3Ô5¥_Ó5�3�3Š3Ô5¥_Ó5×5Ò5Ô:NÈw×:WÒ:WÜÔ0Ó1Ð1Ø˜Ô Ü" 7¨G°VÔ<õ IÙ˜gÔ&Ú:×GÑGÈ×PÒPð �K‰K˜¨Ô-Ü˜wÔ'Ü# C°&Ô9ØˆQ‰Ü�$ŒÜ$ WÓ-Ð-r/   )Ú_IMMUTABLE_JOB_FIELDSr  rŸ   rß  r  rV  )r]   ró  Ú
bad_fieldsr  s   ff  r-   Ú
update_jobr  &  sV   ù€ ô '×3Ñ3°G·M°M¸rÓB€JßÜÐ@ÀÇÁÌ6ÐR\ÓK]ÓA^Ð@_Ð`ÓaÐaö.ô@ �V˜UÓ#Ð#r/   c          	      óŠ   € V ^8„  d   QhR\         R\        \         ,          R\        \        \         \        3,          ,          /# )r(   r]   Úreasonr)   rÉ  )r,   s   "r-   r.   r.   P  s2   € ÷ 
ñ 
”cð 
¤8¬C¥=ð 
¼HÄTÌ#ÌsÈ(Å^Õ<Tñ 
r/   c                óŽ   € \        V 4      pV'       g   R# \        VR,          RRRRR\        4       P                  4       RV/4      # )	z:Pause a job without deleting it. Accepts a job ID or name.Nrx   rR  FrG  rM  rN  r`  )rä  r  r  r  )r]   r  r  s   && r-   Ú	pause_jobr  P  sI   € ä
˜&Ó
!€CßÙÜ�c˜$•iØ�5Ø�Ø”[“]×,Ñ,Ó.Ø˜ð	"ó ð r/   c                óh   € V ^8„  d   QhR\         R\        \        \         \        3,          ,          /# rs   rÉ  )r,   s   "r-   r.   r.   ]  s'   € ÷ %ñ %”sð %œx¬¬S´#¨X­Õ7ñ %r/   c                óN  € \        V 4      pV'       g   R# VP                  R4      pV'       d   \        V4      MRpVR,          P                  R4      R9   dP   VeL   \        V\	        4       4      '       d2   Tp\
        P                  RVP                  RVR,          4      V4       M\        VR,          4      pVfM   VR,          P                  R4      R8X  d0   VR,          P                  R	R
4      p\        RV R\         R24      h\        VR,          RRRRRRRRRV/4      # )uÄ  Resume a paused job. Accepts a job ID or name.

A recurring job paused across one of its slots must not lose that slot silently: the stored
``next_run_at`` (already past) survives resume as the due instant, and the ordinary late /
catch-up / ``cron.catch_up_missed`` policy in the due scan decides what happens to it â€” one
fire or a logged skip, never a silent re-anchor past it (#113603). One-shots and future
instants recompute from now as before.
NrZ  r9  r^  z€Job '%s' resumed with occurrence %s that elapsed while paused kept due; the next tick fires it (late/catch-up) or logs the skip.rC  rx   r�  r=  rB  zCannot resume: one-shot time r  zs) and will never fire.rR  TrG  rV  rN  r`  >   r:   r_  )rä  rz   r6  r)  r  r­   Úinfor¹  rŸ   rE  r  )r]   r  Ústored_nextÚ	stored_dtrZ  r=  s   &     r-   Ú
resume_jobr!  ]  s&  € ô ˜&Ó
!€CßÙØ—'‘'˜-Ó(€Kß-8”˜[Ô)¸d€IàˆJ�×Ñ˜FÓ#Ð';Ô;ØÒ!Ü! )¬[«]×;Ò;à!ˆÜ�‰ð>à�G‰G�F˜C �IÓ&¨õ	5ô
 ' s¨:¥Ó7ˆØÒ˜s :�×2Ñ2°6Ó:¸fÔDØ�Z•×$Ñ$ X¨yÓ9ˆÜØ+¨F¨8ð 4Ü3Ð4Ð4KðMóNð 	Nô �c˜$•iØ�4Ø�Ø�TØ˜Ø�{ð"ó ð r/   c          	      óŠ   € V ^8„  d   QhR\         R\        \         ,          R\        \        \         \        3,          ,          /# )r(   r]   Úextra_promptr)   rÉ  )r,   s   "r-   r.   r.   …  s5   € ÷ ñ œð ¬8´C­=ð ÄHÌTÔRUÔWZÐRZÍ^ÕD\ñ r/   c                ó@  € \        V 4      pV'       g   R# \        V4      '       d7   VP                  RV 4      p\        RV RVP                  R4       RV R24      h\	        4       P                  4       p\        VR,          R	R
RRRRRRRTRTRT;'       g    R/4      # )zšSchedule a job for the next tick (ID or name). ``extra_prompt`` is stamped as
``manual_run_prompt`` for that single fire only; ``mark_job_run`` clears it.NrC  zCannot run: job 'z' is rG  z> (terminal). Create a new occurrence with 'hermes cron resume z! --run-now' or '--at <ISO-8601>'.rx   rR  TrV  rN  r`  rZ  Úmanual_run_atÚmanual_run_prompt)rä  r[  rz   rŸ   r  r  r  )r]   r#  r  rC  r%  s   &&   r-   Útrigger_jobr'  …  s¾   € ô ˜&Ó
!€CßÙÜ�s×ÒØ�w‰w�v˜vÓ&ˆÜØ ˜v U¨3¯7©7°7Ó+;Ð*<ð =@Ø@D¸vð F/ð/ó0ð 	0ô  “M×+Ñ+Ó-€MÜ�c˜$•iØ�4Ø�Ø�TØ˜Ø�}à˜Ø˜l×2Ð2¨dð	"ó 	ð 	r/   c                óH   € V ^8„  d   QhR\         R\        R\        R\        /# )r(   r  r   Úttl_secondsr)   )r   r   rš   r+   )r,   s   "r-   r.   r.   ž  s(   € ÷ +ñ +œ#ð +¤Hð +¼5ð +ÄTñ +r/   c                óè   € \        V \        4      '       d   V P                  R4      '       g   R# \        V R,          4      pVe   ^ \	        W4      u;8:  d
   V8  g    R#  R# \        V 4      '       * # )aŒ  True for a well-formed claim aged within ``[0, ttl)`` whose owner is not provably dead:
future-dated (clock/TZ skew) or malformed claims count as stale so they can never wedge a
job, and a same-host owner pid that has exited releases the claim immediately instead of
after the TTL (a killed ``hermes cron run`` otherwise blocks the next manual run for the
full window with "already being fired").r×  F)r  r  rz   r6  r  r   )r  r   r)  Ú
claimed_ats   &&& r-   r_  r_  ž  s`   € ô �eœT×"Ò"¨%¯)©)°D¯/ª/ÙÜ˜e D�kÓ*€JØÒ !Ô'7¸Ó'HÖ"VÈ;Õ"VÙð #WÙÜ# EÓ*Ô*Ð*r/   zTCannot re-arm recurring jobs: re-arm is one-shot-only; use plain resume or cron run.c          	      ót   € V ^8„  d   QhR\         R\        R\        \        \         \        3,          ,          /# )r(   r]   r=  r)   )r_   r   r   r  )r,   s   "r-   r.   r.   ±  s.   € ÷  +ñ  +œ#ð  +¤sð  +¬x¼¼SÄ#¸X½Õ/Gñ  +r/   c                óŒ  aaa€ \        V 4      pV'       g   R# \        S\        4      '       d   SP                  4       o\	        \        S4      4      oSP                  R4      R8w  d   \        \        4      h\        S4      oSf%   \        SP                  R4      ;'       g    S4      hVVV3R lp\        VR,          V4      # )z:Re-arm a completed one-shot as an explicit new occurrence.Nr^  r�  r=  c           	      ó8  <€ \        4       p\        VP                  R 4      V\        4       4      '       d   \	        R4      h\        VP                  R4      V\
        4      '       d   \	        R4      hVP                  R/ 4      P                  R4      R8w  d   \	        \        4      hVP                  R4      ;'       g    / p^ VR&   VP                  SSP                  R	\        S4      4      VR
R
R7       \        V4       SVR&   \        V 4       \        V4      # )Ú	run_claimz-Cannot re-arm one-shot over a live run claim.r   z.Cannot re-arm one-shot over a live fire claim.r9  r^  r�  r‰  rW  r;  N)r9  r8  r‰  r/  r   rZ  )r  r_  rz   r¥   rŸ   r   Ú_REARM_RECURRING_ERRORr\  r_   ra  r  rJ  )rù  Ú_ir  r   r‰  rZ  r¦  r=  s   &&&  €€€r-   r  Úrearm_oneshot.<locals>.apply¿  sø   ø€ Ü‹mˆÜ˜#Ÿ'™' +Ó.°Ô5SÓ5U×VÒVÜÐLÓMÐMÜ˜#Ÿ'™' ,Ó/°Ô6L×MÒMÜÐMÓNÐNØ�7‰7�:˜rÓ"×&Ñ& vÓ.°&Ô8ÜÔ3Ó4Ð4Ø—‘˜Ó"×(Ð( bˆØˆˆ{ÑØ�
‰
Ø$°×7JÑ7JÈ9ÔVYÐZ`ÓVaÓ7bØ T°dð 	ô 	<ô 	˜SÔ!Ø(ˆˆMÑÜ�$ŒÜ$ SÓ)Ð)r/   rx   )rä  r  r   r  r  r_   rz   rŸ   r0  r¹  r¤  rV  )r]   r=  Újob_refr  rZ  r¦  s   &f  @@r-   Úrearm_oneshotr4  ±  s¡   ú€ ä˜fÓ%€GßÙÜ�&œ(×#Ò#Ø×!Ñ!Ó#ˆÜ$¤S¨£[Ó1€OØ×Ñ˜6Ó" fÔ,ÜÔ/Ó0Ð0Ü" ?Ó3€KØÒÜ'¨×(;Ñ(;¸HÓ(E×(OÐ(OÈÓPÐP÷*ô$ �W˜T•] EÓ*Ð*r/   c                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   Ô  s   € ÷ ñ ”sð œtñ r/   c           	     ór  € \        V 4      pV'       g   R# VR,          p\        4       ;_uu_ 4        \        4       p\        V4      pV Uu. uF  qUR,          V8w  g   K  VNK  	  pp\        V4      V8X  d    RRR4       R# \	        V4      p\        W20R7       \        P                  4       pVe   VP                  V8X  d   RVn	        VP                  4       '       d   \        P                  ! V4        ^ RIHp V! V4       \%        4       P&                  P)                  4        R	V 2p	\*        ;_uu_ 4        \,        P/                  V	R4       RRR4        RRR4       R# u upi   \         d    \         P#                  RTRR7        L‰i ; i  + '       g   i     LI; i  + '       g   i     R# ; i)
zRemove a job by ID or name.Frx   NrI  T)Úclear_notepadz*Failed to clear notepad for removed job %sr©   rÞ   )rä  rÛ   r�   rÜ  r  r  rl   rz   r]   rc   r   ÚshutilÚrmtreeÚcron.notepadr7  r¬   r­   r{  r�   rC   rŠ   rá   râ   rî   )
r]   r  Úcanonical_idrù  Úoriginal_lenr  Újob_output_dirro   r7  Ú
_fence_keys
   &         r-   Ú
remove_jobr?  Ô  sX  € ä
˜&Ó
!€CßÙØ�t•9€LÜ	��Ü‹{ˆÜ˜4“yˆÙÓ;™4�a T¥7¨lÑ#:—�™4ˆÐ;Üˆt‹9˜Ô$Ø÷ 
‰ô )¨Ó6ˆÜ�$ NÕ3Ü'×+Ñ+Ó-ˆØÒ &§-¡-°<Ô"?Ø!ˆFŒNØ× Ñ ×"Ò"Ü�MŠM˜.Ô)ð	dÝ2Ù˜,Ô'ô ,Ó-×6Ñ6×>Ñ>Ó@ÐAÀÀLÀ>ÐRˆ
ß$Ö$Ü×!Ñ! *¨dÔ3÷ %à÷/ 
‰ùò <øô ô 	dÜ�L‰LÐEÀ|Ð^bˆLÖcð	dú÷ %×$ú÷+ 
�ˆúsl   ±F%ÁE$ÁE$Á%F%ÂAF%Ã F%Ã7E)Ä5F%Ä:F	Å	F%Å$F%Å)#FÆF%ÆFÆF%ÆF"ÆF%Æ%F6	c                óH   € V ^8„  d   QhR\         R\         R\        R\        /# )r(   r]   r   r#  r)   rt   )r,   s   "r-   r.   r.   ô  s(   € ÷ +ñ +œCð +¬ð +´Dð +¼Tñ +r/   c                ó,   aa€ VV3R lp\        WR4      # )a®  Set/clear a persisted alert-dedup marker (alert exactly once until the condition heals;
survives restarts) and return the PRIOR value. Field: ``preflight_alerted`` (blocked config).

The marker records that the operator was already alerted about this job's condition, so the scheduler
alerts exactly once and stays silent on subsequent ticks until the condition heals (same alert-once
shape as the dead-pin auto-pause in #73506).
c                 ó    <€ \        VP                  S4      4      pS'       d   R VS&   MVP                  SR4       VS8w  d   \        V 4       V# )TN)r+   rz   rî   r  )rù  r1  r  Úpriorr   r#  s   &&& €€r-   r  Ú_set_alert_flag.<locals>.applyü  sB   ø€ Ü�S—W‘W˜U“^Ó$ˆßØˆC�ŠJà�G‰G�E˜4Ô Ø�EŒ>Ü�dŒOØˆr/   F©rV  )r]   r   r#  r  s   &ff r-   Ú_set_alert_flagrF  ô  s   ù€ öô �V EÓ*Ð*r/   c                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   		  s   € ÷ >ñ >¤3ð >¬4ñ >r/   c                ó   € \        V RR4      # )zAMark the job as preflight-alerted; return True if it already was.Úpreflight_alertedT©rF  ©r]   s   &r-   Úmark_preflight_alertedrL  		  s   € ä˜6Ð#6¸Ó=Ð=r/   c                ó(   € V ^8„  d   QhR\         RR/# ©r(   r]   r)   Nr^   )r,   s   "r-   r.   r.   	  s   € ÷ 8ñ 8¤Cð 8¨Dñ 8r/   c                ó    € \        V RR4       R# )z@Clear the preflight alert-dedup marker (config validates again).rI  FNrJ  rK  s   &r-   Úclear_preflight_alertedrP  	  s   € ä�FÐ/°Ö7r/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# )r(   r]   Údetailr)   rt   )r,   s   "r-   r.   r.   	  s!   € ÷ +ñ +¤cð +´3ð +¼4ñ +r/   c                ó(   a€ V3R lp\        WR4      # )u  Durably record (as ``last_fire_error``) that a scheduled fire could not be handed to the
runner â€” written by the dashboard fire webhook when the loopback forward fails. Without it
the miss is invisible (no execution row, last_status only covers started runs); mark_job_run
clears it.c                 ó�   <€ R \        4       P                  4       R\        S;'       g    R4      R,          /VR&   \        V 4       R# )r×  rR  r  :Niô  NÚlast_fire_errorT)r  r  r_   r  )rù  r1  r  rR  s   &&&€r-   r  Ú(note_fire_forward_failure.<locals>.apply	  s?   ø€ à”+“-×)Ñ)Ó+¨X´s¸6¿<¸<ÀRÓ7HÈÕ7Nð"PˆÐÑä�$ŒÙr/   FrE  )r]   rR  r  s   &f r-   Únote_fire_forward_failurerW  	  s   ø€ õ
ô �V EÓ*Ð*r/   c                óÈ   € V ^8„  d   QhR\         \        \        3,          R\        R\        \        ,          R\        \        ,          R\        \        ,          R\        RR/# )	r(   r  rÂ  r×   Údelivery_errorÚstatusr   r)   N)r  r_   r   r+   r   )r,   s   "r-   r.   r.   !	  s[   € ÷ " ñ " Ü	Œc”3ˆh�ð" Ü"&ð" Ü/7¼­}ð" ÜNVÔWZÍmð" ä”S�Mð" ä #ð" ð 
ñ" r/   c                ó€  € WPR&   V P                  RR4       V P                  RR4       \        V\        4      ;'       d    \        VP	                  4       4      pT;'       g    V'       g   RMV'       d   RMRV R&   V'       d   RMTV R	&   V'       d+   V P                  R
R4       V P                  RR4       ^ V R&   MK\        V P                  R4      ;'       g    ^ 4      ^,           V R&   RTRT;'       g    T;'       g    R/V R&   W0R&   RV R&   V P                  RR4       V P                  R4      e   RV R&   R# R# )zYStamp one completed run onto *job*: status fields, failure streak, alert markers, claims.rB  r%  Nr&  r×   Údelivery_failedÚokr\  r¸  rI  rU  r¼  r×  rR  z
run failedr»  r¹  r   r  r/  )rî   r  r_   r+   r�   rŠ  rz   )r  rÂ  r×   rY  rZ  r   r\  s   &&&&&& r-   Ú_record_run_outcomer^  !	  s9  € ð
 ˆÑØ‡G�GˆO˜TÔ"à‡G�GÐ Ô&Ü  ´Ó5×VÐV¼$¸~×?SÑ?SÓ?UÓ:V€OØ÷ Vð Vß‰¿/Ñ%6Ètð ˆÑç '™¨U€CˆÑßð 	�‰Ð# TÔ*Ø�‰Ð! 4Ô(Ø !ˆÐÒô !$ C§G¡GÐ,<Ó$=×$BÐ$BÀÓ CÀaÕ GˆÐÑð  $ S¨(°E×4UÐ4U¸f×>TÐ>TÈÐVˆˆNÑØ!/ÐÑà€CˆÑØ‡G�GˆN˜DÔ!Ø
‡w�wˆ{ÓÒ'ØˆˆKÓñ (r/   Úladder_rungc                ób   € V ^8„  d   QhR\         \        \        3,          R\        R\        RR/# )r(   r  r   r_  r)   Nr+  )r,   s   "r-   r.   r.   F	  s1   € ÷ 0"ñ 0"œD¤¤c �Nð 0"´ð 0"Ädð 0"ÐW[ñ 0"r/   c          
     óÆ  € V P                  R/ 4      P                  R4      pV P                  R4      pV'       d{   VP                  R4      pVRJ;'       d    V^ 8„  pVP                  R^ 4      pV'       g$   VR8X  d   V'       d   V^ 8”  g   V^,          pWtR&   V'       d   Wu8¼  d   \        V 4       R# \        V R,          V4      V R&   V R,          e    V P                  R	4      R
8w  d   RV R	&   R# R# VR9   d[   RV R	&   V P                  R4      '       g   RV R&   \        P	                  RV P                  RV P                  RR4      4      V4       R# \        V 4       R# )aZ  Bump ``repeat.completed`` and recompute ``next_run_at``; retire the record as a terminal
completion when the repeat limit is reached or a one-shot has no further run.

``ladder_rung``: this run re-ran an occurrence that already counted (an unreachable-model
re-run, ``cron.unreachable_retry.is_retry_run``), so ``repeat.completed`` is left as is.r9  r^  r‰  r¶  NrW  r�  rZ  rG  rM  rV  r×   r¸  ztFailed to compute next run for recurring schedule (is the 'croniter' package installed in the gateway's Python env?)zyJob '%s' (%s) could not compute next_run_at; leaving enabled and marking state=error so the job is not silently disabled.rC  rx   r:  >   r:   r_  )rz   r]  r¹  r­   r×   )r  r   r_  r^  r‰  r¶  ÚfiniterW  s   &&$     r-   Ú_advance_after_runrc  F	  sI  € ð �7‰7�:˜rÓ"×&Ñ& vÓ.€Dð �W‰W�XÓ€FßØ—
‘
˜7Ó#ˆØ˜dÐ"×0Ð0 u¨q¡yˆØ—J‘J˜{¨AÓ.ˆ	÷  D¨F¤N·vÀ)ÈaÄ-Ø˜�NˆIØ"+�;Ñß�iÔ(ô ! Ô%Ùä)¨#¨j­/¸3Ó?€CˆÑØ
ˆ=ÕÒ%Ø�7‰7�7Ó˜xÔ'Ø&ˆC�‹Lñ (à	Ð%Ô	%ð ˆˆG‰Ø�w‰w�|×$Ò$ð:ð �Ñô 	�‰ð[à�G‰G�F˜CŸG™G D¨#Ó.Ó/°ö	7ô
 	˜SÖ!r/   Úexpected_fire_ownerÚexecution_idÚmodel_unreachableÚquota_hold_secondsÚrecover_consumed_firec                ó,  € V ^8„  d   QhR\         R\        R\        \         ,          R\        \         ,          R\        \         ,          R\        \         ,          R\        \         ,          R\        R	\        R
\        \        ,          R\        R\        /# )r(   r]   rÂ  r×   rY  rZ  rd  re  rf  r_  rg  rh  r)   )r_   r+   r   rš   )r,   s   "r-   r.   r.   y	  s¦   € ÷ I-ñ I-ÜðI-äðI-ô ”C�=ðI-ô œS•Mð	I-ô
 ”S�MðI-ô "¤#�ðI-ô œ3•-ðI-ô ðI-ô ðI-ô !¤�ðI-ô  ðI-ô 
ñI-r/   c               ó`   a aaaaaaaaa	a
a€ VVVVV VVV	V
VV3R loVV 3R lp\        S V4      # )u†  Mark a job as run: update last_run_at/last_status, bump completed, recompute next_run_at,
and retire the record as a terminal completion when the repeat limit is reached.

``delivery_error`` is separate from the agent error: agent succeeded but delivery failed records
``last_status = "delivery_failed"`` (never "ok") while ``failure_streak`` is left alone. An
explicit ``status`` (e.g. "blocked_config") overrides the derived value. False when the fence
can't be taken, the job is missing, or ``expected_fire_owner`` no longer holds the fire claim.

``model_unreachable``: this failed run never reached the model (transient network/DNS error,
zero API calls). Recurring jobs then get a bounded automatic re-run â€” ``next_run_at`` is pulled
earlier per ``cron.unreachable_retry.RETRY_DELAYS_SECONDS`` â€” instead of waiting a full period
(Cowork-style; see cron/unreachable_retry.py). ``ladder_rung``: this run IS one of those
re-runs (``is_retry_run``); its occurrence already counted, so ``repeat.completed`` is not
bumped again.

``quota_hold_seconds``: the provider said it stays closed for this long (a quota 429 with
``retry after <N>s``). Recurring jobs are parked through the window instead of re-firing into
it on every tick. ``recover_consumed_fire`` lets a scheduled sparse cron recover its
consumed fire when the provider reopens; manual runs retain the natural schedule
(cron/quota_hold.py, #89376).
c                 óÔ  <€ Se   SVP                  R. 4      9   d   R# SeV   VP                  R4      p\        V\        4      '       d   VP                  R4      S8w  d   \        P	                  RS4       R# \        4       P                  4       p\        VSS
S	SV4       Se&   ^ RIH	p . V! SVP                  R4      4      OSNVR&   \        W$SR7       ^ R	IHp ^ R
IHpHp S'       g#   S'       d   \!        V4      '       g
   V! V4       MV! V4       S'       g/   S'       d'   \!        V4      '       g   VP#                  VSSR7       MVP                  V4       \%        V 4       R# )NÚcanonical_completionsTr   r  zMmark_job_run: job_id %s fire claim owner changed; discarding stale completionF)Úreplayable_completions)r_  )Ú
quota_hold)r  Ú
plan_retry)rh  )rz   r  r  r­   r®   r  r  r^  Úcron.scheduler_authorityrm  rc  r:   rn  Úcron.unreachable_retryr  ro  r[  Ú	plan_holdr  )rù  r1  r  r  r   Úlivern  r  ro  rY  r×   re  rd  r]   r_  rf  rg  rh  rZ  rÂ  s   &&&      €€€€€€€€€€€r-   r  Úmark_job_run.<locals>.applyœ	  s*  ø€ ØÒ#¨¸¿¹Ð@WÐY[Ó8\Ô(\ÙØÒ*Ø—G‘G˜LÓ)ˆEÜ˜e¤T×*Ò*¨e¯i©i¸«oÐATÔ.TÜ—‘ØcØôñ Ü‹m×%Ñ%Ó'ˆÜ˜C ¨%°ÀÈÔMØÒ#ÝOØ+j©T°&¸#¿'¹'ÐBYÓ:ZÓ-[Ð+jÐ]iÐ+jˆCÐ'Ñ(Ü˜3°Õ=Ý#ßBç×,´_ÀS×5IÒ5IÙ�s�Oñ ˜Ôß×-´oÀc×6JÒ6JØ× Ñ  Ð&8ÐPeÐ Õfà×"Ñ" 3Ô'Ü�$ŒÙr/   c                  óp   <€ \        SS\        R 7      p V \        J d   \        P                  RS4       R# V # )©rT  z0mark_job_run: job_id %s not found, skipping saveF)rV  Ú_MISSINGr­   r®   )Úfoundr  r]   s    €€r-   ÚlockedÚmark_job_run.<locals>.locked»	  s0   ø€ Ü˜& %´Ô:ˆØ”HÓÜ�N‰NÐMÈvÔVÙØˆr/   ©rú   )r]   rÂ  r×   rY  rZ  rd  re  rf  r_  rg  rh  ry  r  s   fffffdddddd @r-   Úmark_job_runr|  y	  s#   ÿû€ ÷F÷ ö>ô ˜V VÓ,Ð,r/   c                ój   € V ^8„  d   QhR\         \        \        3,          R\        R\        R\        /# )r(   r  r  r©  r)   r+  )r,   s   "r-   r.   r.   Å	  s0   € ÷ ñ ¤4¬¬S¨¥>ð ¼ð ÄCð ÌDñ r/   c                ó¾   €  \        V P                  RR4      V4       R#   \         d2   p\        P	                  RY P                  R4      T4        Rp?R# Rp?ii ; i)zTBest-effort operator-visible trace in the job's output dir; never breaks the caller.rx   r  Tz,Failed to write %s diagnostic for job %r: %sNF)Úsave_job_outputrz   r¬   r­   r{  )r  r  r©  rÚ   s   &&& r-   Ú_write_oneshot_diagnosticr€  Å	  sM   € ðÜ˜Ÿ™  bÓ)¨4Ô0ÙøÜô Ü�‰ÐCÀTÏ7É7ÐSWË=ÐZ[Ô\Ýûðús   ‚   A«&AÁAc                óJ   € V ^8„  d   QhR\         \        \        3,          RR/# rX  r  )r,   s   "r-   r.   r.   Ï	  s"   € ÷  1ñ  1¬$¬s´C¨x­.ð  1¸Tñ  1r/   c                óJ  € V P                  R4      e   R# V P                  R4      ;'       g    / pV P                  R4      ;'       g    / p\        V RV P                  R4       RV P                  R4       R	VP                  R
R4       RVP                  RR4       RVP                  RR4       RVP                  RR4       R\        4       P                  4        R2R4      pV'       d9   \        P                  RV P                  RV P                  RR4      4      4       R# R# )u¥  Trace for a wedged one-shot removal: dispatch was claimed but mark_job_run never ran
(interrupted mid-run); removing it silently would leave no output, error, or record.

A finite one-shot whose dispatch was claimed (``repeat.completed`` >= ``repeat.times``) but which never
reached ``mark_job_run`` (``last_run_at`` is null) was interrupted mid-run â€” scheduler restart, gateway
kill, or a non-Exception escape (#73973). The recovery guards remove such jobs so they stop appearing
due, but a silent removal leaves the user with no output, no error, and no job record. Write a small
diagnostic file into the job's output directory so the removal is observable and debuggable.
rB  Nr‰  r/  z7# Cron job removed without producing output

- job id: rx   ú	
- name: rC  z
- dispatch claimed: rW  r:  r  r¶  z
- run claimed at: r×  rB  z by r  z
- removed at: u   

This one-shot job's dispatch was claimed, but the run never completed (`last_run_at` was never written) â€” the scheduler process was most likely killed or restarted mid-execution. The job has been removed to stop it re-firing; recreate it to run again.
zwedged-oneshotuX   Job '%s': removed without a completed run â€” diagnostic written to its output directory)rz   r€  r  r  r­   r®   )r  r‰  r  Úwrittens   &   r-   Ú _write_wedged_oneshot_diagnosticr…  Ï	  s   € ð ‡w�wˆ}ÓÒ)ÙØ�W‰W�XÓ×$Ð$ "€FØ�G‰G�KÓ ×&Ð& B€EÜ'Øð	Ø—W‘W˜T“]�Oð $Ø—7‘7˜6“?Ð#ð $Ø%Ÿz™z¨+°sÓ;Ð<¸A¸f¿j¹jÈÐRUÓ>VÐ=Wð XØ"ŸY™Y t¨YÓ7Ð8¸¸U¿Y¹YÀtÈYÓ=WÐ<Xð YÜ$›×0Ñ0Ó2Ð3ð 4ð
	ð 	ó€G÷ Ü�‰ð#à�G‰G�F˜CŸG™G D¨#Ó.Ó/ö	1ñ r/   c                óV   € V ^8„  d   QhR\         \        \        3,          R\        RR/# )r(   r  r²  r)   Nr  )r,   s   "r-   r.   r.   ò	  s)   € ÷ ñ ¬$¬s´C¨x­.ð ÄCð ÈDñ r/   c                ó°   € \        V RV P                  R4       RV P                  R4       RV R\         R\        4       P	                  4        R2R	4       R
# )zaTrace for a never-ran one-shot retired outside the grace window (else it would just vanish).
    zL# Cron job removed before firing (run time outside grace window)

- job id: rx   rƒ  rC  z
- scheduled run time: z
- grace window: zs
- removed at: a?  

This one-shot's run time is more than the grace window in the past (scheduler down past the window, host asleep, or jobs.json edited), which is outside the 'will never fire' contract enforced at create/update/resume time. The job was removed without running; recreate it (or use the Run button) to schedule it again.
zmissed-oneshotN)r€  rz   rE  r  r  )r  r²  s   &&r-   Ú _write_missed_oneshot_diagnosticrˆ  ò	  sn   € ô Øð	Ø—W‘W˜T“]�Oð $Ø—7‘7˜6“?Ð#ð $!Ø!) 
ð +Ü0Ð1ð 2Ü$›×0Ñ0Ó2Ð3ð 4ð	ð 	ör/   c                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   
  s   € ÷ 4ñ 4œ3ð 4¤4ñ 4r/   c                ó|   a € V 3R lp\        S V\        R7      pV\        J d   \        P                  RS 4       R# V# )u  Atomically claim a finite one-shot dispatch BEFORE execution: ``repeat.completed`` is bumped
and persisted under the jobs lock so a tick dying mid-execution cannot lose the dispatch
(*at-most-times* instead of *at-least-once*). True if the caller may run the job; False when
the limit is already reached. Only ``kind == "once"`` with ``repeat.times > 0`` is claimed.

Increments ``repeat.completed`` under the cross-process jobs lock and persists the claim immediately, so
that if the tick dies mid-execution (gateway kill, OOM, segfault, hard-timeout) the dispatch is not
lost. This converts finite one-shot jobs from *at-least-once* to *at-most-times* semantics â€” a job that
self-destructs fires at most ``repeat.times`` times instead of infinitely (issue #38758).
c                 ó®  <€ VP                  R 4      ;'       g    / pVP                  R4      pVP                  R/ 4      P                  R4      R8w  g   Ve   V^ 8:  d   R# VP                  R^ 4      pVP                  RVP                  RR	4      4      pWT8¼  d†   VP                  R
4      e0   \        V4       \        V 4       \        P	                  RWeV4       R# V P                  V4       \        V S0R7       \        V4       \        P	                  RWeV4       R# V^,           VR&   \        V 4       \        P                  RWcR,          V4       R# )r‰  r¶  r9  r^  r�  TrW  rC  rx   r:  rB  u>   Job '%s': dispatch limit reached (%d/%d) â€” marking completedFrI  u5   Job '%s': dispatch limit reached (%d/%d) â€” removingz Job '%s': claimed dispatch %d/%d)rz   r]  r  r­   r  rî   r…  r{  )rù  rä  r  r‰  r¶  rW  r§  r]   s   &&&    €r-   r  Úclaim_dispatch.<locals>.apply
  s%  ø€ Ø—‘˜Ó"×(Ð( bˆØ—
‘
˜7Ó#ˆà�7‰7�:˜rÓ"×&Ñ& vÓ.°&Ô8¸EºMÈUÐVWÌZÙØ—J‘J˜{¨AÓ.ˆ	Ø—‘˜ §¡¨¨cÓ 2Ó3ˆØÔØ�w‰w�}Ó%Ò1ô % SÔ)Ü˜$”Ü—‘ØTØ eô-ñ ð �H‰H�QŒKä�d¨¨Õ1Ü,¨SÔ1Ü�K‰KØGÈÐ[`ôbáà'¨!�mˆˆ{ÑÜ�$ŒÜ�‰Ð7¸À{Õ@SÐUZÔ[Ùr/   rv  u|   claim_dispatch: job_id %s not in store â€” proceeding without claim (handed-in job dict; nothing to persist a claim against)T)rV  rw  r­   r{  )r]   r  Úclaimeds   f  r-   Úclaim_dispatchrŽ  
  s@   ø€ õ ôD ˜ ¬xÔ8€GØ”(ÓÜ�‰ðGàô	ñ Ø€Nr/   c                ó€   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        R\        R\        /# )r(   rù  r  rü   r)   ©r  r  r_   r   r+   )r,   s   "r-   r.   r.   =
  s6   € ÷ ñ œœd¤3¬ 8�nÕ-ð ´cð Ì3ð ÔSWñ r/   c                ó®   € \        V\        4      '       d   VP                  R4      V8w  d   R# \        4       P	                  4       VR&   \        V 4       R# )zYCompare-and-refresh a claim's ``at`` stamp; False unless *expected_owner* still holds it.r  Fr×  T)r  r  rz   r  r  r  )rù  r  rü   s   &&&r-   Ú_refresh_claimr’  =
  s?   € ä�eœT×"Ò" e§i¡i°£o¸Ô&GÙÜ“-×)Ñ)Ó+€Eˆ$�KÜˆd„OÙr/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# ©r(   r]   rü   r)   rt   )r,   s   "r-   r.   r.   F
  s!   € ÷ +ñ +¤ð +¼ð +Äñ +r/   c               ó(   a€ V3R lp\        WR4      # )aj  Refresh a one-shot's ``run_claim`` timestamp while its run is alive, so an expired claim
really means the claiming process died. Compare-and-refresh on ``expected_owner`` stops a stale
runner from extending a claim another process has since taken over.

Called periodically from the scheduler's run monitor (#62002) so a legitimately long run keeps its claim
fresh: an expired claim then really does mean "the claiming process died", and neither another process's
tick nor this process's own next tick will re-dispatch or stale-remove the job while the run is in
flight. mark_job_run() clears the claim on completion.
c                 óŠ   <€ VP                  R / 4      P                  R4      R8w  d   R# \        WP                  R4      S4      # )r9  r^  r�  Fr/  )rz   r’  ©rù  r1  r  rü   s   &&&€r-   r  Ú"heartbeat_run_claim.<locals>.applyP
  s:   ø€ Ø�7‰7�:˜rÓ"×&Ñ& vÓ.°&Ô8ÙÜ˜d§G¡G¨KÓ$8¸.ÓIÐIr/   FrE  ©r]   rü   r  s   &d r-   Úheartbeat_run_claimrš  F
  s   ø€ õJô
 �V EÓ*Ð*r/   c                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   X
  s   € ÷ +ñ +œCð +¤Dñ +r/   c                ó    € R p\        WR4      # )aM  Clear a one-shot's ``run_claim`` when dispatch itself fails: such a job never reaches
mark_job_run, so the stale claim would block re-dispatch until the TTL expires.

Calling this on every early-exit path restores the "the job stays due and will fire on the next healthy
tick" invariant that the scheduler comment promises (#86522).
c                 ó  € VP                  R 4      pVP                  R/ 4      P                  R4      R8w  g   VRRR/39   d   R# \        V\        4      '       d   VP                  R4      '       d   RR/MRVR &   \        V 4       R# )r/  r9  r^  r�  NÚoutageTF)rz   r  r  r  )rù  r1  r  r  s   &&& r-   r  Úclear_run_claim.<locals>.apply_
  s|   € Ø—‘˜Ó$ˆØ�7‰7�:˜rÓ"×&Ñ& vÓ.°&Ô8¸EÀdÈXÐW[ÐL\ÐE]Ô<]Ùô 0:¸%Ä×/FÒ/FÈ5Ï9É9ÐU]×K^ÒK^˜H dÑ+ÐdhˆˆKÑÜ�$ŒÙr/   FrE  )r]   r  s   & r-   Úclear_run_claimr   X
  s   € òô �V EÓ*Ð*r/   c                ó$   € V ^8„  d   QhR\         /# r'   ©rŠ  )r,   s   "r-   r.   r.   l
  s   € ÷ ñ ¤#ñ r/   c                óD  € \        V 4      pV'       g   ^ # \        4       ;_uu_ 4        \        4       p\        4       P	                  4       p^ pV Fœ  pVR,          V9  gI   \        V4      '       d   \        V4      '       d'   VP                  R/ 4      P                  R4      R9  d   K[  \        VR,          V4      pV'       g   Kx  WeP                  R4      8w  g   K�  WeR&   V^,          pKž  	  V'       d   \        V4       VuuRRR4       #   + '       g   i     R# ; i)a  Batch form of :func:`advance_next_run`: one load + at most one save for the whole due set;
one-shot/unknown ids are skipped. Returns the count advanced. Persisted once at the end, so a
crash mid-batch re-fires the whole set on restart rather than a prefix (sub-10ms window).rx   r9  r^  rZ  N>   r:   r_  )
rm   rÛ   r�   r  r  r[  r`  rz   r¹  r  )Újob_idsrÙ  rù  r   Úadvancedr  Únew_nexts   &      r-   Úadvance_next_runsr§  l
  sÓ   € ô ˆg‹,€CßÙÜ	��Ü‹{ˆÜ‹m×%Ñ%Ó'ˆØˆÛˆCà�D•	 Ô$Ü# C×(Ò(Ô1JÈ3×1OÒ1OØ—7‘7˜: rÓ*×.Ñ.¨vÓ6Ð>RÔRáÜ'¨¨J­¸Ó=ˆHß‰x˜H¯©°Ó(>Ö>Ø%-�MÑ"Ø˜A•’ñ ÷ Ü�dŒOØ÷# 
��‹ús   ¨BDÃDÃDÃ7DÄD	c                ó0   € V ^8„  d   QhR\         R\        /# rs   rt   )r,   s   "r-   r.   r.   ‡
  s   € ÷ ,ñ ,œSð ,¤Tñ ,r/   c                ó    € \        V .4      ^8¬  # )u  Advance a recurring job's next_run_at BEFORE run_job() so a mid-run crash cannot re-fire it
on restart (at-most-once for recurring jobs â€” one missed run beats a crash-loop burst).
One-shots are left unchanged so they can retry. Returns True if next_run_at was advanced.)r§  rK  s   &r-   Úadvance_next_runrª  ‡
  s   € ô
 ˜f˜XÓ&¨!Ñ+Ð+r/   c                ó$   € V ^8„  d   QhR\         /# r'   r^   )r,   s   "r-   r.   r.   �
  s   € ÷ #ñ #”Sñ #r/   c                 óä   € \         P                  ! RR4      P                  4       p V '       d   V #  ^ RIpVP	                  4       pV R\         P                  ! 4        2#   \
         d    Rp L)i ; i)už   Claim attribution/debugging id (NOT correctness â€” that comes from the file lock and the
fresh-claim check): ``HERMES_MACHINE_ID`` if set, else hostname:pid.ÚHERMES_MACHINE_IDr  NrB  Ú:)rn  Úgetenvr�   ÚsocketÚgethostnamer¬   rÄ  )Úexplicitr°  Úhosts      r-   Ú_machine_idr´  �
  sk   € ô �yŠyÐ,¨bÓ1×7Ñ7Ó9€HßØˆðÛØ×!Ñ!Ó#ˆð ˆV�1”R—Y’Y“[�MÐ"Ð"øô ô ØŠðús   ±A ÁA/Á.A/Úclaim_ttl_secondsÚforceÚmanualÚ
return_jobc                ó˜   € V ^8„  d   QhR\         R\        R\        R\        R\        R\        \        \         \        3,          ,          /# )r(   r]   rµ  r¶  r·  r¸  r)   )r_   rŠ  r+   r  r   )r,   s   "r-   r.   r.   �
  s[   € ÷ BNñ BNÜðBNÜ'*ðBNÜLPðBNäðBNä&*ðBNô 
ŒD””c��NÕñBNr/   c               óB   a aaaaa€ VVVV3R lo\        S VV 3R l4      # )a  Atomically claim a job for one external 'fire' (multi-machine at-most-once); True iff THIS
caller won (``CronScheduler.fire_due``: exactly one of N replicas runs a job). Under the
fence + file lock: reject missing/terminal/paused jobs unless ``force`` (explicit manual
fire, which also resumes the job atomically; external callbacks must leave it false so a
stale callback cannot resurrect a paused job). ``manual`` = off-tick run-now without the
resume: no occurrence stamp, so the still-pending ``next_run_at`` slot is not skipped. Lose if a claim younger than
``claim_ttl_seconds`` exists (the TTL lets another fire reclaim after a crash; mark_job_run
clears the claim). Otherwise stamp ``fire_claim`` and, for recurring jobs, advance
``next_run_at`` so a stale re-delivery cannot re-fire.c                 óÔ  <€ \        V4      '       d   \        V4      '       g   R # S
'       g   \        V4      '       g   R # \        4       p\	        VP                  R4      VS	4      '       d   R # ^ RIHpHp S
;'       g.    S;'       g$    VP                  R4      VP                  R4      8H  pV'       d   RMV! VP                  R4      4      pVe3   \        P                  ! V4      V,
          \        \        R7      8¼  d   RpV'       do   V! W'4      '       da   VP                  R/ 4      P                  R4      R9   d9   \        VR,          VP                  4       4      pV'       d   W‚R&   \        V 4       R # S
'       d   \!        V4       R	VP                  4       R
\#        4        R\$        P&                  ! 4       P(                   2/VR&   VP+                  RR4       VP                  R/ 4      P                  R4      R9   d.   \        VR,          VP                  4       4      pV'       d   W‚R&   \        V 4       S'       d"   \-        \.        P0                  ! V4      VR7      # R# )Fr   ©Úcompleted_occurrenceÚscheduled_instantr%  rZ  Nr0  r9  r^  r×  r  r®  r  )Ú_scheduled_instantT>   r:   r_  )r[  r`  rS  r  r_  rz   Úcron.occurrencesr½  r¾  r   r  r   r   r¹  r  r  ra  r´  rê   r½  rí   rî   r  ÚcopyÚdeepcopy)rù  r1  r  r   r½  r¾  Úmanual_fireÚinstantÚnxtrµ  r¶  r·  r¸  s   &&&      €€€€r-   r  Ú!claim_job_for_fire.<locals>.applyª
  sÐ  ø€ Ü˜3×ÒÔ(AÀ#×(FÒ(FÙ÷ œ_¨S×1Ò1ÙÜ‹mˆÜ˜#Ÿ'™' ,Ó/°Ð6G×HÒHÙßLð
 ×[Ð[˜v×[Ð[¨¯©°Ó)AÀSÇWÁWÈ]ÓE[Ñ)[ˆß%‘$Ñ+<¸S¿W¹WÀ]Ó=SÓ+Tˆð ÒÜ×*Ò*¨7Ó3°cÕ9¼YÔOfÔ=gÔgØˆGßÑ+¨C×9Ò9Ø�w‰w�z 2Ó&×*Ñ*¨6Ó2Ð6JÔJÜ& s¨:¥¸¿¹»ÓH�ßØ),˜Ñ&Ü˜d”OÙßÜ  Ô%ð " 3§=¡=£?°D¼[»]¸OÈ1ÌTÏZÊZË\×M]ÑM]ÐL^Ð:_Ð`ˆˆLÑà�‰� Ô%Ø�7‰7�:˜rÓ"×&Ñ& vÓ.Ð2FÔFÜ" 3 z¥?°C·M±M³OÓDˆCßØ%(�MÑ"Ü�$ŒßGQŒt”D—M’M #Ó&¸7ÔCÐ[ÐW[Ð[r/   c                  ó   <€ \        SS R 4      # ©FrE  )r  r]   s   €€r-   rŸ  Ú$claim_job_for_fire.<locals>.<lambda>ß
  s   ø€ ¬Y°v¸uÀeÔ-Lr/   r{  )r]   rµ  r¶  r·  r¸  r  s   fdddd@r-   Úclaim_job_for_firerÊ  �
  s    ý€ ÷3\ð 3\ôj ˜VÕ%LÓMÐMr/   c                ó<   € V ^8„  d   QhR\         R\         R\        /# r”  rt   )r,   s   "r-   r.   r.   â
  s!   € ÷ 	+ñ 	+¤ð 	+¼ð 	+Äñ 	+r/   c               ó(   a€ V3R lp\        WR4      # )aÌ  Refresh an active ``fire_claim`` without extending another owner's lease: an execution may
outlive the TTL, and the owner check stops a stale runner from refreshing a recovered claim.
Deliberately NOT under ``_under_fire_fence``: the run thread holds the per-job fence across
delivery, and the fence's reentrancy is per thread, so the heartbeat thread would time out and
report a false ownership loss. ``_jobs_lock`` already serializes the compare-and-refresh.c                 ó:   <€ \        WP                  R 4      S4      # )r   )r’  rz   r—  s   &&&€r-   r  Ú#heartbeat_fire_claim.<locals>.applyè
  s   ø€ Ü˜d§G¡G¨LÓ$9¸>ÓJÐJr/   FrE  r™  s   &d r-   Úheartbeat_fire_claimrÏ  â
  s   ø€ õKô �V EÓ*Ð*r/   c                ól   € V ^8„  d   QhR\         R\        R\        \        .\        3,          R\        /# )r(   r>  rX   Úcastr)   )r_   r   r   )r,   s   "r-   r.   r.   ó
  s3   € ÷ ñ œSð ¬3ð ´hÄ¸uÄc¸zÕ6Jð Ìsñ r/   c                óæ   €  ^ RI Hp V! 4       ;'       g    / p\        V\        4      '       d   VP	                  R/ 4      M/ pV! VP	                  W4      4      #   \
         d    T! T4      u # i ; i)zTRead ``cron.<key>`` from config as *cast*, falling back to *default* on any failure.)Úload_configr:   )rf  rÓ  r  r  rz   r¬   )r>  rX   rÑ  rÓ  ru  Úcron_cfgs   &&&   r-   Ú_cron_config_numberrÕ  ó
  sd   € ðÝ1Ù‹m×!Ð!˜rˆÜ*4°S¼$×*?Ò*?�3—7‘7˜6 2Ô&ÀRˆÙ�H—L‘L Ó.Ó/Ð/øÜô Ù�G‹}Òðús   ‚A –AA ÁA0Á/A0c                ó$   € V ^8„  d   QhR\         /# r'   r™   )r,   s   "r-   r.   r.   þ
  s   € ÷ dñ d¬5ñ dr/   c                 ó,   € \        R\        \        4      # )zZ``cron.completed_retention_days``; non-positive disables the sweep (records kept forever).Úcompleted_retention_days)rÕ  Ú COMPLETED_ONESHOT_RETENTION_DAYSrš   rG   r/   r-   Ú!_completed_oneshot_retention_daysrÚ  þ
  s   € äÐ9Ô;[Ô]bÓcÐcr/   c                ó¬   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        R\
        \        \        ,          ,          R\        /# )r(   Úraw_jobsr   r%  r)   )r  r  r_   r   r   r   rm   r+   )r,   s   "r-   r.   r.     sC   € ÷ "ñ "Ü”4œœS˜•>Õ"ð"Ü)1ð"ÜCKÌCÔPSÍHÕCUð"ä	ñ"r/   c               ó*  € \        4       pV^ 8:  d   R# V\        VR7      ,
          pRp\        V 4       EF  p VP                  R4      R8w  d   K  VP                  R4      p\	        V\
        4      '       d   VP                  R4      MRR8w  d   K^  VP                  R	4      p\	        V\        4      '       d   \        V4      MRp	V	e   W”8¼  d   Kž  V P                  V4       R
pVP                  R4      p
Ve#   V
'       d   VP                  \        V
4      4       \        P                  RVP                  RVP                  RR4      4      Wƒ4       EK"  	  V#   \         d.    \        P                  RTP                  RR4      R
R7        EK]  i ; i)a  Prune completed one-shot records past retention (in place; True when anything was removed).
Removed ids go into *removed_ids* so save_jobs's shrink-merge guard allows the delete. Age is
measured from ``last_run_at``; a record without a parseable one is kept (never guess into
deletion).F)rã  rG  rW  r9  r^  Nr�  rB  Trx   zNJob '%s': pruning completed one-shot record (finished %s, retention %.1f days)rC  r:  z/Retention sweep skipped malformed job record %rr©   )rÚ  r   r  rz   r  r  r_   r6  Úremover(  r­   r  r¬   r{  )rÜ  r   r%  Úretention_daysÚcutoffrc   Úrjr9  r`  ra  Úrids   &&$        r-   Ú_sweep_completed_oneshotsrã    s_  € ô 7Ó8€NØ˜ÔÙØ”9 .Ô1Õ1€FØ€GÜ�8�nˆð	eØ�v‰v�g‹ +Ô-ÙØ—v‘v˜jÓ)ˆHÜ(2°8¼T×(BÒ(B�—‘˜VÔ$ÈÐQWÔWÙØ—v‘v˜mÓ,ˆHÜ4>¸xÌ×4MÒ4Mœ, xÔ0ÐSWˆKØÒ" kÔ&;ÙØ�O‰O˜BÔØˆGØ—&‘&˜“,ˆCØÒ&¯3Ø—‘¤ C£Ô)Ü�K‰KØ`Ø—‘�v˜rŸv™v d¨CÓ0Ó1°8÷Mñ! ð, €Nøô ô 	eÜ�L‰LØAÀ2Ç6Á6È$ÐPSÓCTÐ_cð ÷ eð eð	eús*   ¹EÁ?EÂ>EÃ/EÄAEÅ3FÆFc                ó\   € V ^8„  d   QhR\         \        \        \        3,          ,          /# r'   r÷  )r,   s   "r-   r.   r.   *  s    € ÷ 
&ñ 
&”dœ4¤¤S �>Õ*ñ 
&r/   c                 óx   € \        4       ;_uu_ 4        \        4       uuRRR4       #   + '       g   i     R# ; i)uá  Return all jobs due now. A recurring job more than one period stale (gateway down, or a run
overran the interval) has its backlog collapsed â€” next_run_at fast-forwards so nothing
burst-fires â€” but still fires ONCE now (via mark_job_run, consuming one ``repeat.times`` run),
avoiding the perpetual-defer loop for runs longer than interval + grace.

This prevents the perpetual-defer loop (#33315) where a job whose runtime exceeds ``interval + grace``
would be skipped forever.
N)rÛ   Ú_get_due_jobs_lockedrG   r/   r-   Úget_due_jobsrç  *  s   € ô 
��Ü#Ó%÷ 
��‹ús   “
(¨9	c                   óŽ   a € ] tR tRt o RtRt]! ]R7      t]! ]	R7      t
V 3R lR ltV 3R lR ltV 3R	 lR
 ltV 3R ltRtV tR# )Ú_DueScani7  zXMutable state threaded through one due scan: the raw store records plus what to persist.F)Údefault_factoryc                óL   <€ V ^8„  d   QhRS[ RS[S[S[S[ 3,          ,          /# rs   )r   r   r  r_   )r,   rD   s   "€r-   r.   Ú_DueScan.__annotate__C  s,   ø€ ÷ Nñ N™3ð N¡8©D±±c°­NÕ#;ñ Nr/   c                óD   a€ \        V3R  lV P                   4       R4      # )c              3   óF   <"  € T F  qR ,          S8X  g   K  Vx € K  	  R# 5irw   rG   )r|   rá  r]   s   & €r-   r~   Ú _DueScan.find.<locals>.<genexpr>D  s   øé € ÐF¡-˜B°dµ8¸vÑ3E—R’R£-ùrÍ  N)r  rÜ  rd   s   &fr-   ÚfindÚ_DueScan.findC  s   ø€ ÜÔF $§-¢-ÓFÈÓMÐMr/   c                ó*   <€ V ^8„  d   QhRS[ RS[ RR/# )r(   r]   rÓ  r)   N©r   )r,   rD   s   "€r-   r.   rì  F  s"   ø€ ÷ #ñ #™cð #©Sð #°Tñ #r/   c                ód   € V P                  V4      pVe   VP                  V4       RV n        R# R# )zSWrite *fields* onto the raw record for *job_id* and flag a save (no-op if missing).NT)rð  r\  Ú
needs_save)re   r]   rÓ  rá  s   &&, r-   ÚpersistÚ_DueScan.persistF  s-   € à�Y‰Y�vÓˆØŠ>Ø�I‰I�fÔØ"ˆDŽOñ r/   c                ó$   <€ V ^8„  d   QhRS[ RR/# rN  ró  )r,   rD   s   "€r-   r.   rì  M  s   ø€ ÷ #ñ #™Sð # Tñ #r/   c                óÀ   € V P                  V4      pVeI   V P                  P                  V4       V P                  P	                  \        V4      4       RV n        R# R# )z;Drop the raw record for *job_id* as an intentional removal.NT)rð  rÜ  rÞ  rc   r(  r_   rõ  )re   r]   rá  s   && r-   ÚretireÚ_DueScan.retireM  sI   € à�Y‰Y�vÓˆØŠ>Ø�M‰M× Ñ  Ô$Ø�L‰L×ÑœS ›[Ô)Ø"ˆDŽOñ r/   c                ó®   <€ V ^8„  d   Qh/ S[ S[S[S[3,          ,          ;R&   S[;R&   S[;R&   S[S[,          ;R&   S[ S[. R3,          ,          ;R&   # )r(   rÜ  r   rõ  rc   NÚon_saved)r  r  r_   r   r   r+   rm   r   )r,   rD   s   "€r-   r.   rì  7  sd   ø‡ ‚ ñ ‘4™™S˜•>Õ"Ñ"ñ	 ñ
 
�Mñ ñ Ññ ñ ‘�XÑ2ñ ñ ‘8˜B ˜HÕ%Õ&ÑDò r/   )rõ  N)rN   rO   rP   rQ   rh   rõ  r   rm   rc   r  rý  rð  rö  rú  rS   rT   rU   rV   s   @r-   ré  ré  7  sL   ø‡ € ábð €JÙ¨cÔ2€Gñ */¸tÔ)D€H÷Nð N÷#ð #÷#ð #÷- ƒ r/   ré  c                óh   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        /# )r(   rÜ  r)   r�  )r,   s   "r-   r.   r.   V  s'   € ÷ ñ ¬$¬t´C¼°H­~Õ*>ð Ä4ñ r/   c                ó²  € RpV  FÎ  pVP                  R4      '       gF   VP                  RR4      ;'       g'    \        P                  ! 4       P                  R,          VR&   Rp\        VP                  R4      \        4      '       g   / VR&   RpR F=  pVP                  V4      pVf   K  \        V4      e   K)  VP                  VR4       RpK?  	  KÐ  	  V# )	a  Repair malformed store records in place BEFORE the due scan keys off them: a missing ``id``
(older writers used ``job_id``), non-dict ``schedule``, or non-ISO timestamp used to abort the
whole scan before save_jobs(), freezing the scheduler in a fast-forward loop.Frx   r]   Nr³  Tr9  )rZ  rB  )rz   rî   rê   r½  rí   r  r  r6  )rÜ  Úchangedrá  r>  r#  s   &    r-   Ú_normalize_due_scan_recordsr  V  s·   € ð €GÛˆØ�v‰v�d�|Š|Ø—v‘v˜h¨Ó-×FÐF´·²³×1AÑ1AÀ#Õ1FˆBˆt‰HØˆGÜ˜"Ÿ&™& Ó,¬d×3Ò3ØˆBˆz‰NØˆGÛ1ˆCØ—F‘F˜3“KˆEØÔ ¤\°%Ó%8Ô%@Ø—‘�s˜DÔ!Ø’ó	 2ñ ð €Nr/   c                óV   € V ^8„  d   QhR\         \        \        3,          R\        RR/# )r(   r  Úscanr)   N)r  r_   r   ré  )r,   s   "r-   r.   r.   j  s)   € ÷ ñ ¤4¬¬S¨¥>ð ¼ð Àdñ r/   c                óx  € V P                  R4      p\        P                  RV P                  RV4      V4       VP                  V4      pVf   R# VP	                  RRR7       VP                  R4      '       g   VP
                  P                  4       VR&   VP                  R	4      '       g   R
VR	&   RVn        R# )zúSelf-heal enabled=true with pause markers: the operator believes the job is frozen while the
scheduler would still fire it. Force enabled=false so listings are honest; logged loudly since
pause_job sets both fields atomically, so this should be rare.rx   zsJob '%s' (%s) has pause markers while enabled=true; self-disabling so it cannot fire (pause must be authoritative).rC  NFrM  )rR  rG  rN  r`  z+auto-disabled: enabled+paused contradictionT)rz   r­   r×   rð  r\  r   r  rõ  )r  r  Újidrá  s   &&  r-   Ú_self_disable_half_pausedr  j  sŸ   € ð �'‰'�$‹-€CÜ
‡L�Lð	Jà�‰�˜Ó˜cô#ð 
�‰�3‹€BØ	‚zÙØ‡I�I�e 8€IÔ,Ø�6‰6�+×ÒØŸ(™(×,Ñ,Ó.ˆˆ;‰Ø�6‰6�/×"Ò"ØKˆˆ?ÑØ€D†Or/   c                ót   € V ^8„  d   QhR\         \        \        3,          R\        R\        \        ,          /# ©r(   r  r  r)   ©r  r_   r   ré  r   )r,   s   "r-   r.   r.   ~  s-   € ÷ ñ ¤4¬¬S¨¥>ð ¼ð ÄhÌsÅmñ r/   c           
     ó4  € V P                  R/ 4      pVP                  R4      p\        W!P                  V P                  R4      R7      pV'       d   RMRpV'       g5   VR9   d.   \        W!P                  P	                  4       4      pV'       d   TpV'       g   R# \
        P                  RV P                  RV P                  R	R
4      4      WT4       RV/p\        V 4      '       d   RVR&   V P                  V4       VP                  ! V R	,          3/ VB  V# )uW  Recompute and persist a missing ``next_run_at``; None when unrecoverable. One-shots use the
grace window; recurring jobs get here after a direct jobs.json edit bypassed add_job() or a
transient croniter ImportError left them in state 'error' (#127182) â€” re-armed ones go back
to 'scheduled', otherwise they would be silently skipped forever.r9  r^  rB  r«  zone-shotNz4Job '%s' had no next_run_at; recovering %s run at %srC  rx   r:  rZ  rV  rG  >   r:   r_  )
rz   rG  r   r¹  r  r­   r  r`  r\  rö  )r  r  r9  r^  Úrecovered_nextÚrecovery_kindrÓ  s   &&     r-   Ú_recover_missing_next_runr  ~  sê   € ð
 �w‰w�z 2Ó&€HØ�<‰<˜Ó€DÜ0Ø—(‘(¨¯©°Ó(>ô@€Nç"0‘J°d€Mß˜dÐ&:Ô:Ü)¨(·H±H×4FÑ4FÓ4HÓIˆßØ ˆMßÙÜ
‡K�KØ>Ø�‰�˜Ÿ™  cÓ*Ó+¨]ôLð ,¨^Ð<€FÜ  ×%Ò%Ø%ˆˆw‰Ø‡J�JˆvÔØ‡L‚L��T•Ñ%˜fÒ%ØÐr/   c                   ó–   a € ] tR tRt o Rt]V 3R lR l4       t]V 3R lR l4       t]V 3R lR l4       tV 3R	 lR
 lt	V 3R lt
RtV tR# )Ú_DueJobi™  z_One candidate under evaluation: its record, schedule and the stored next_run in raw/aware
form.c                ó6   <€ V ^8„  d   QhRS[ S[S[3,          /# r'   r  )r,   rD   s   "€r-   r.   Ú_DueJob.__annotate__¥  s   ø€ ÷ ,ñ ,™$™s¡C˜x�.ñ ,r/   c                ó:   € V P                   P                  R / 4      # )r9  ©r  rz   ©re   s   &r-   r9  Ú_DueJob.schedule¤  s   € à�x‰x�|‰|˜J¨Ó+Ð+r/   c                ó0   <€ V ^8„  d   QhRS[ S[,          /# r'   rç  )r,   rD   s   "€r-   r.   r  ©  s   ø€ ÷ )ñ )‘h™s•mñ )r/   c                ó8   € V P                   P                  R 4      # )r^  )r9  rz   r  s   &r-   r^  Ú_DueJob.kind¨  s   € à�}‰}× Ñ  Ó(Ð(r/   c                ó    <€ V ^8„  d   QhRS[ /# r'   ró  )r,   rD   s   "€r-   r.   r  ­  s   ø€ ÷ =ñ =‘sñ =r/   c                ón   € V P                   P                  R V P                   P                  RR4      4      # )rC  rx   r:  r  r  s   &r-   r§  Ú_DueJob.label¬  s&   € à�x‰x�|‰|˜F D§H¡H§L¡L°°sÓ$;Ó<Ð<r/   c                ó0   <€ V ^8„  d   QhRS[ S[,          /# r'   rç  )r,   rD   s   "€r-   r.   r  °  s   ø€ ÷ Jñ J¡©¥ñ Jr/   c                ór   € \        V P                  V P                  P                  P	                  4       4      # r/  )r¹  r9  r  r   r  r  s   &r-   Úrecompute_nextÚ_DueJob.recompute_next°  s$   € Ü §¡¨t¯y©y¯}©}×/FÑ/FÓ/HÓIÐIr/   c                ól   <€ V ^8„  d   Qh/ S[ S[S[3,          ;R&   S[;R&   S[;R&   S[;R&   S[;R&   # )r(   r  r  r²  r”  r‹  )r  r_   r   ré  r   )r,   rD   s   "€r-   r.   r  ™  sL   ø‡ ‚ ñ
 
‰c‘3ˆh�Ññ ñ �Nñ ñ �Mñ ñ Ññ ñ Ñò r/   rG   N)rN   rO   rP   rQ   rh   Úpropertyr9  r^  r§  r  rS   rT   rU   rV   s   @r-   r  r  ™  sY   ø‡ € ñð ÷,ó ð,ð ÷)ó ð)ð ÷=ó ð=÷Jð J÷/ ƒ r/   r  c                ó0   € V ^8„  d   QhR\         R\        /# ©r(   r˜  r)   ©r  r+   )r,   s   "r-   r.   r.   ´  s   € ÷ ñ ¤Wð ´ñ r/   c                ó   € V P                   P                  p\        V P                  V4      '       d9   \	        V P
                  V4      '       d   \        V P
                  V4      '       g   R# V P                  4       pV'       g   R# \        P                  RV P                  V P
                  P                  4       VP                  4       V4       V P                   P                  V P                  R,          VR7       R# )aÄ  Repair a cron job whose stored offset no longer matches now's (TZ migration).

next_run_at is an absolute instant but the expr means local wall clock, so a TZ change can make
it look due hours early. If the stored wall clock is still in the future, recompute so we fire
at the intended local time. True when re-anchored (caller skips this tick). TRADE-OFF: a DST
offset change meeting the same conditions SKIPS the pending occurrence; accepted as rare.FzlJob '%s' next_run_at offset changed (%s -> %s). Recomputing cron run to preserve local wall-clock intent: %srx   ©rZ  T)r  r   r)  r‹  r=  r”  rA  r  r­   r  r§  r;  rö  r  )r˜  r   r¦  s   &  r-   Ú_repair_timezone_shifted_cronr'  ´  sµ   € ð �&‰&�*‰*€Cä˜aŸm™m¨S×1Ò1Ü% a×&7Ñ&7¸×=Ò=Ü(¨×):Ñ):¸C×@Ò@áØ×ÑÓ!€HßÙÜ
‡K�Kð	Gà	�‰�×"Ñ"×,Ñ,Ó.°·±³ÀôKð ‡F�F‡N�N�1—5‘5˜•;¨H€NÔ5Ùr/   c                ó0   € V ^8„  d   QhR\         R\        /# r#  )r  r   )r,   s   "r-   r.   r.   Í  s   € ÷ $ñ $¤Gð $´ñ $r/   c                ó  € V P                   P                  pV P                  R9   dD   \        V P                  V4      '       d(   \        V P                  V P                  V4      '       g   V P                  # V P                  R8X  d   VP                  4       pTpM%V P                  4       pV'       d   \        V4      MRpV'       d    Ve   \        W0P                  4      '       g   V P                  # V P                  P                  R4      p\        P                  RV P                  P                  RV4      WB4       \        V P                  V P                   4       W P                  R&   V P                   P#                  WBR7       V# )	aÆ  Re-arm a recurring job wedged in persisted last_status=error; returns the effective
next_run_dt.

Such a job errored, mark_job_run parked next_run_at in the future, and nothing re-dispatched it
(the in-memory stale-claim sweep cannot see it). Interval jobs re-arm to now (always a legal
fire); cron jobs re-arm to the next LEGAL occurrence, since re-arming to now would fire at times
the expression excludes. A correctly-parked cron value is left as-is.
r_  Nrx   uÐ   cron.persisted_error.recovered job='%s' id=%s â€” recurring job wedged in stale last_status=error without re-firing for a full cadence; re-arming next_run_at to %s so it re-dispatches without force-run/resumerC  rZ  r&  )r:   r_  )r  r   r^  r%  r‹  re  r  r9  r  r  r6  r,  rz   r­   r®   r…  r²  rö  )r˜  r   r  Úrecovered_next_dtr  s   &    r-   Ú_rearm_stale_error_recurringr+  Í  s  € ð �&‰&�*‰*€Cà	�‰Ð&Ô&Ü˜1Ÿ=™=¨#×.Ò.Ü)¨!¯%©%°·±¸S×AÒAà�}‰}ÐØ‡v�v�ÔØŸ™›ˆØ03Ñà×)Ñ)Ó+ˆß<JœL¨Ô8ÐPTÐçÐ,Ò8ÜÐ-¯}©}×=Ò=à�}‰}ÐØ
�%‰%�)‰)�D‹/€CÜ
‡N�Nð	cð 	
�‰�	‰	�&˜#Ó ô	5ô
 % Q§U¡U¨A¯J©JÔ7Ø)‡E�Eˆ-ÑØ‡F�F‡N�N�3€NÔ3ØÐr/   c                ó0   € V ^8„  d   QhR\         R\        /# r#  r$  )r,   s   "r-   r.   r.   ô  s   € ÷ #ñ #œGð #¬ñ #r/   c                óÊ  € \        V P                  V P                  V P                  4      pV\        8X  EdB   ^ RIHp ^ RIHp V! V P                  V P                  4      '       g$   V! V P                  V P                  4      '       db   \        P                  RV P                  V P                  P                  R4      V P                  P                  R4      V P                  4       R# V P                  4       p\        P                  RV P                  V P                  V P                  P                  R4      V4       V'       d/   V P                   P#                  V P                  R,          VR7       R	# V\$        8X  dà   \        P'                  R
V P                  V P                  P                  R4      V P                  P                  R4      V P                  V P                  P)                  4       V P                  P+                  4       V P                   P,                  P+                  4       4       \/        V P                  V P                  V P                  4       R# )ag  Stale-schedule guard for a due cron instant; True when re-anchored without firing.

A direct edit of schedule.expr leaves next_run_at on the old lattice, so re-anchor first (from
the current expr, so this converges). Some instants are off the lattice on purpose and
authorize one fire instead: an offset-representation migration that would otherwise swallow
a never-fired occurrence, and the failure-path planners' parked instants (a quota recovery
before a sparse cron's next natural occurrence, an unreachable-model retry rung). All fall
through to fire ONCE (at-most-once holds: completion rewrites the instant).)Úis_recovery_fire)Úis_retry_firez2cron.off_lattice_fire job='%s' id=%s expr=%r at=%srx   r<  Fz‚Job '%s' next_run_at %s does not match its current cron expression %r (direct jobs.json edit?); re-anchoring to %s without firing.r&  Tuû   cron.timezone_migration.catch_up job='%s' id=%s expr=%r stored=%s normalized=%s â€” stored next_run_at carries a pre-migration UTC offset (%s, now %s) and is a legal occurrence at its own wall clock; firing the due run instead of re-anchoring past it.)r›  r9  r”  r‹  r™  r^  r.  rq  r/  r  r²  r­   r  r§  rz   r  r  rö  r˜  r®   r  r;  r   r£  )r˜  Ústale_classr.  r/  r¦  s   &    r-   Ú_reanchor_stale_cronr1  ô  s•  € ô 0°·
±
¸A×<MÑ<MÈqÏ}É}Ó]€KØÔ*Õ*Ý4Ý8Ù˜AŸE™E 1§:¡:×.Ò.±-ÀÇÁÀqÇzÁz×2RÒ2RÜ�K‰KØDØ—‘˜Ÿ™Ÿ™ 4›¨!¯*©*¯.©.¸Ó*@À!Ç*Á*ôNñ Ø×#Ñ#Ó%ˆÜ�‰ð^à�G‰G�Q—Z‘Z §¡§¡°Ó!7¸ô	C÷ Ø�F‰F�N‰N˜1Ÿ5™5 �;°HˆNÔ=ÙØÔ3Ô3Ü�‰ðdð �G‰G�Q—U‘U—Y‘Y˜t“_ a§j¡j§n¡n°VÓ&<¸a¿j¹jØ�M‰M×#Ñ#Ó% q×'8Ñ'8×'BÑ'BÓ'DÀaÇfÁfÇjÁj×FZÑFZÓF\ô	^ô 	+¨1¯5©5°!×2CÑ2CÀQÇ]Á]ÔSÙr/   c                ó<   € V ^8„  d   QhR\         R\        R\        /# )r(   r˜  rc  r)   )r  rŠ  r+   )r,   s   "r-   r.   r.     s!   € ÷ ñ ¤gð ´cð ¼dñ r/   c                ó  € \        V P                  P                  V P                  4      V8:  d   R# V P	                  4       pV'       g   R# V P                  P                  V P                  R,          VR7       \        \        \        P                  ! V4      4      V P                  P                  4      '       dv   \        RRR 4      '       gb   \        P                  RV P                  V P                  W4       V P                  P                   P#                  V P                  3R l4       R# \        P                  R	V P                  V P                  W4       ^ R
IHp V P                  P                   P#                  V4       R# )up  Re-anchor accumulated misses; return whether catch-up was explicitly disabled.

The fast-forward is persisted immediately â€” NOT redundant with advance_next_run/mark_job_run:
it
protects the crash window before mark_job_run and covers the external fire_due path, which never
calls advance_next_run. mark_job_run re-anchors on completion, so the value is provisional.
Frx   r&  Úcatch_up_missedTc                 ó
   € V R J# rÈ  rG   r5  s   &r-   rŸ  Ú0_fast_forward_missed_recurring.<locals>.<lambda>)  s
   € ÈuÐ\aÑOar/   z‚Job '%s' missed its scheduled time (%s, grace=%ds). Skipping missed occurrence because cron.catch_up_missed is false; next run: %sc                 ó   € \        V 4      # r/  r   r4  s   &r-   rŸ  r6  .  ó
   € Ô1CÀCÔ1Hr/   z~Job '%s' missed its scheduled time (%s, grace=%ds). Running now; next run provisionally set to: %s (re-anchored on completion))Úrecord_catch_up_occurrence)r  r  r   r‹  r  rö  r  r%  r  r   r  rÕ  r­   r  r§  r²  rý  r  rÀ  r9  )r˜  rc  r¦  r9  s   &&  r-   Ú_fast_forward_missed_recurringr:    s  € ô ˜Ÿ™Ÿ
™
 A§M¡MÓ2°eÔ;ÙØ×ÑÓ!€HßÙØ‡F�F‡N�N�1—5‘5˜•;¨H€NÔ5Ü”}¤X×%;Ò%;¸HÓ%EÓFÈÏÉÏ
É
×SÒSÜ'Ð(9¸4ÑAa×bÒbÜ�‰ð]à�G‰G�Q—Z‘Z ô	2ð 	
�‰�‰×Ñ¨!¯%©%ÓHÔIÙÜ
‡K�Kð	Uà	�‰�—‘˜Uô.õ <Ø‡F�F‡O�O×ÑÐ5Ô6Ùr/   c                ó0   € V ^8„  d   QhR\         R\        /# r#  r$  )r,   s   "r-   r.   r.   9  s   € ÷ ñ œwð ¬4ñ r/   c                ó2  € \        V P                  P                  V P                  4      \        8:  d   R# \
        P                  ! \        4       P                  V P                  P                  4       \        4      '       d   R# V P                  P                  R4      p\        V\        4      '       d;   VP                  R4      '       d$   V P                  P                  R4      '       g   R# V'       g¡   V P                  P                  R4      '       g€   \        V P                  V P                  4       V P                  P!                  V P                  R,          4       V P                  P"                  P%                  V P                  3R l4       R# )u°  One-shot grace gate; True when the job must not fire this tick.

A one-shot beyond the grace window must never fire (create/update/resume reject such schedules
and recovery never revives them; only the due scan used to dispatch them hours late). With no
claim stamped, retire it with a diagnostic (never silently delete). A claim may mean a run is
still in flight elsewhere â€” skip but keep the record so its mark_job_run can land.Fr/  rž  r   rx   c                 ó   € \        V 4      # r/  r   r4  s   &r-   rŸ  Ú)_retire_expired_oneshot.<locals>.<lambda>L  r8  r/   T)r  r  r   r‹  rE  r   Úoutage_coversr�   rC   r¯  r  rz   r  r  rˆ  r²  rú  rý  r  )r˜  r  s   & r-   Ú_retire_expired_oneshotr@  9  sô   € ô ˜Ÿ™Ÿ
™
 A§M¡MÓ2Ô6KÔKÙä×!Ò!Ô"5Ó"7×"@Ñ"@À!Ç-Á-×BYÑBYÓB[Ô]r×sÒsÙØ�E‰E�I‰I�kÓ"€Eä�%œ×Ò 5§9¡9¨X×#6Ò#6¸q¿u¹u¿y¹yÈ×?VÒ?VÙß�Q—U‘U—Y‘Y˜|×,Ò,Ü(¨¯©°·
±
Ô;Ø	�‰�‰�a—e‘e˜D•kÔ"Ø	�‰�‰×Ñ¨!¯%©%ÓHÔIÙr/   c                ó^   € V ^8„  d   QhR\         \        \        3,          R\        R\        /# r  )r  r_   r   ré  r+   )r,   s   "r-   r.   r.   P  s)   € ÷ 'ñ '¬¬c´3¨h­ð '¼xð 'ÌDñ 'r/   c           	     óH  € V P                  R4      ;'       g    / pVP                  R4      pVP                  R^ 4      pVe   V^ 8:  g   WC8  d   R# V P                  RV P                  RR4      4      p\        V P                  RR4      4      '       d   \        P                  R	WTV4       R
# V P                  R4      e(   \        P	                  RWTW0P                  R4      4       M\        P                  RWTV4       VP                  V R,          4       \        V 4       R
# )uP  One-shot dispatch-limit guard; True when the job must not fire this tick.

A finite one-shot claimed via claim_dispatch() whose tick died before mark_job_run has
completed >= times while still looking due. Remove it instead of re-firing â€” unless THIS
process is still running it (a run outliving the run_claim TTL is slow, not stale).r‰  r¶  rW  FrC  rx   r:  r  ui   Job '%s': dispatch limit reached (%d/%d) but its run is still in flight in this process â€” keeping entryTrB  u  Job '%s': one-shot dispatch limit reached (%d/%d) on a record that already completed a run (last_run_at=%s) â€” removing it WITHOUT firing. This record was re-armed without a budget reset (pre-#93615 store or hand edit); re-run it with 'hermes cron resume <job> --run-now' (#93524).uN   Job '%s': one-shot dispatch limit reached (%d/%d) â€” removing stale due entry)rz   r¯   r­   r  r®   rú  r…  )r  r  r‰  r¶  rW  rC  s   &&    r-   Ú_oneshot_dispatch_limit_reachedrC  P  sþ   € ð �W‰W�XÓ×$Ð$ "€FØ�J‰J�wÓ€EØ—
‘
˜;¨Ó*€IØ‚}˜ œ
 iÔ&7ÙØ�7‰7�6˜3Ÿ7™7 4¨Ó-Ó.€Dô $ C§G¡G¨D°"Ó$5×6Ò6Ü�‰ð(à˜Uô	$ñ Ø
‡w�wˆ}ÓÒ)ô 	�‰ð=ð ˜U§G¡G¨MÓ$:õ	<ô 	�‰Ø\Ø˜Uô	$ð 	‡K�K��D•	Ôä$ SÔ)Ùr/   c                ót   € V ^8„  d   QhR\         \        \        3,          R\        R\        \        ,          /# r  r	  )r,   s   "r-   r.   r.   z  s-   € ÷ ñ ¤¤c¬3 h¥ð ´xð ÄHÌSÅMñ r/   c           	     ó¨  € ^ RI Hp V! WP                  4      pVf   R# \        P	                  RV P                  RV P                  R4      4      V P                  R4      W0P                  R4      4       W0R&   V P                  RR4       VP                  V R,          4      pVe   VP                  RR4       VP                  V R,          VR7       V# )	uZ  Put an occurrence the dispatcher advanced past but never claimed back on the schedule
(#107485); returns the restored ``next_run_at`` or None. Restored ONCE: the stamp is dropped
here, so the slot then meets the ordinary late / fast-forward / ``cron.catch_up_missed``
policy like any other overdue instant â€” never a replay of every missed slot.)Úunclaimed_pending_slotNz˜Job '%s' (%s): occurrence %s was taken off the schedule but never claimed (scheduler stopped before dispatch); restoring it as the due instant (was %s).rC  rx   rZ  r  r&  )	rÀ  rF  r   r­   r®   rz   rî   rð  rö  )r  r  rF  Úslotrá  s   &&   r-   Ú_restore_unclaimed_slotrH  z  s±   € õ
 8á! #§x¡xÓ0€DØ‚|ÙÜ
‡N�Nð	Yà�‰�˜Ÿ™ ›Ó&¨¯©°«°t¿W¹WÀ]Ó=SôUð ˆÑØ‡G�GˆN˜DÔ!Ø	�‰�3�t•9Ó	€BØ	‚~Ø
�‰ˆ~˜tÔ$Ø‡L�L��T•¨€LÔ-Ø€Kr/   c                ój   € V ^8„  d   QhR\         \        \        3,          R\        R\        R\
        /# )r(   r  r  Úrun_claim_ttlr)   )r  r_   r   ré  rš   r+   )r,   s   "r-   r.   r.   ‘  s7   € ÷ Pñ Pœ4¤¤S �>ð P´ð PÌ%ð PÔTXñ Pr/   c                óD  € VP                   pV P                  R/ 4      P                  R4      R8X  d$   \        V P                  R4      W24      '       d   R# \        W4      ;'       g%    V P                  R4      ;'       g    \	        W4      pV'       g   R# \
        P                  ! V4      p\        WWE\        V4      4      pVP                  pVR9   pV P                  R4      V8H  p	^ R	I
Hp
Hp V	'       gM   V
! W4      '       d?   V'       d   VP                  4       MR
pV'       d   VP                  V R,          VR7       R# VR8X  d   V	'       g   \        V4      '       d   R# \!        V4      Vn        \%        VP"                  V4      '       d   R# V	'       d   R
MV! V P                  R4      4      V R&   V	'       g   VR8X  d   \'        V4      '       d   R# \)        VP*                  4      pV	'       g   V'       d   \-        Wm4      '       d   R# VR8X  d‚   \/        V4      '       g   \1        W4      '       d   R# RVP3                  4       R\5        4       /p\7        W6P"                  4      \8        8”  d   RVR&   WàR&   VP                  V R,          VR7       V	'       g   V'       dw   \;        R\7        W6P"                  4      4      pRVRVP3                  4       R\=        V^4      R\?        Wý4      /pVV R&   ^ RI
H p VP                  V R,          VV! WC4      R7       R# )a\  Decide whether one enabled, non-terminal job fires this tick, persisting any repairs.
Ordering matters: recover missing next_run_at, repair timezone shifts, re-arm stale-error
recurring jobs; then once due: re-anchor stale cron instants, fast-forward missed recurring
runs, retire/guard one-shots, and finally stamp the run claim / dispatch record.r9  r^  r�  r/  FrZ  r:   r%  r¼  Nrx   r&  r¿  r×  r  Trž  )r/  r%   Úscheduled_atÚdispatched_atrQ  Úlast_dispatch)Úpending_slot_stamp)rN  r  >   r:   r_  )!r   rz   r_  rH  r  r   r  r  r  r^  rÀ  r½  r¾  r  rö  r'  r+  r‹  r%  r1  rO  r9  r:  r@  rC  r  r´  r  rE  r¢   ÚroundrX  rO  )r  r  rJ  r   r²  r”  r˜  r^  Ú	recurringÚ
manual_runr½  r¾  r¦  rc  r  ÚlatenessÚdispatch_stamprO  s   &&&               r-   Ú_evaluate_due_jobrU  ‘  sƒ  € ð
 �(‰(€Cð
 	�‰�
˜BÓ×#Ñ# FÓ+¨vÔ5Ü˜3Ÿ7™7 ;Ó/°×DÒDáä& sÓ1×sÐs°S·W±W¸]Ó5K×sÐsÔOhÐilÓOs€HßÙÜ×,Ò,¨XÓ6€OÜ�˜8´mÀOÓ6TÓU€AØ�6‰6€DØÐ,Ñ,€Ið —‘˜Ó)¨XÑ5€JßHçÑ.¨s×=Ò=ß)2�1×#Ñ#Ô%¸ˆßØ�L‰L˜˜T�°ˆLÔ9ÙØˆv„~ŸjÔ-JÈ1×-MÒ-MÙÜ0°Ó3€A„MÜ�a—m‘m S×)Ò)Ù÷ )3¡Ñ8IÈ#Ï'É'ÐR_ÓJ`Ó8a€CÐÑç˜$ &œ.Ô-AÀ!×-DÒ-DÙÜ" 1§:¡:Ó.€EßŸ)Ô(FÀq×(PÒ(PÙØˆv„~Ü" 1×%Ò%Ô)HÈ×)SÒ)SÙð
 �s—}‘}“¨¬k«mÐ<ˆÜ˜C§¡Ó/Ô2GÔGØ"ˆE�(‰OØ ˆKÑØ�‰�S˜•Y¨%ˆÔ0÷
 Ÿ)Ü�sÔ,¨S·-±-Ó@ÓAˆð ˜HØ˜SŸ]™]›_Ø¤ h°Ó 2ØÔ/°Ó@ð	
ˆð  .ˆˆOÑõ 	8à�‰Ø��I ^Ù+¨HÓ:ð 	ô 	<ñ r/   c                ó\   € V ^8„  d   QhR\         \        \        \        3,          ,          /# r'   r÷  )r,   s   "r-   r.   r.   ä  s    € ÷ ,ñ ,œd¤4¬¬S¨¥>Õ2ñ ,r/   c                 óÈ  € \        4       p \        V \        4       4      p\        V 4      Vn        \
        P                  ! V 4       Uu. uF  p\        V4      NK  	  pp\        4       p\        WP                  VP                  R7      '       d<   RVn        V Uu. uF(  q!P                  VP                  R4      4      f   K&  VNK*  	  pp. pV F…  p \        V4      '       d   \        V4      '       g   K(  VP                  RR4      '       g   KB  \!        V4      '       d   \#        Wa4       K`  \%        WaV4      '       d   VP'                  V4       K…  K‡  	  VP                  '       d"    \/        YP                  ;'       g    RR7       \2        P6                  ! \9        4       P:                  4       VP<                   F
  pV! 4        K  	  V# u upi u upi   \(         dN    \*        P-                  RTP                  R4      ;'       g    TP                  R4      ;'       g    R4        EKa  i ; i  \0         d%   p\2        P4                  ! TR	R
4       Tu Rp?# Rp?ii ; i)zLInner implementation of get_due_jobs(); must be called with _jobs_lock held.rI  Trx   NrR  z.Skipping malformed cron job %r during due scanrC  r:  zdue-scan repairs not persistedr  )r�   ré  r  r  rõ  rÁ  rÂ  r!  r¥   rã  r   rc   rð  rz   r[  r`  rO  r  rU  r  r¬   r­   Ú	exceptionr  r¹   r   r
  Úend_recovery_windowr�   rC   rý  )	rÜ  r  r  rù  rJ  Úduer  rõ   Úemits	            r-   ræ  ræ  ä  sà  € ä‹{€HÜ�Hœk›mÓ,€DÜ1°(Ó;€D„OÜ,0¯MªM¸(Ô,CÓDÑ,C qÔ Ö"Ñ,C€DÐDä2Ó4€Mô ! ¯8©8ÀÇÁ×NÓNØˆŒÙÓF™4�a§9¡9¨Q¯U©U°4«[Ó#9—�™4ˆÐFà
€CÛˆð	9Ü˜s×#Ò#Ô,EÀc×,JÒ,JÙØ—7‘7˜9 d×+Ò+ÙÜ  ×%Ò%Ü)¨#Ô4ÙÜ  ¨M×:Ò:Ø—
‘
˜3–ñ ;ñ ð& ‡‡€ð	Ü�h¯L©L×,@Ð,@¸DÕAô
 ×$Ò$Ô%8Ó%:×%CÑ%CÔDØ—”ˆÙŽñ à€JùòO Eùò Gøô" ô 	9Ü×ÑØ@Ø—‘˜“×7Ð7 3§7¡7¨4£=×7Ð7°C÷9ð 9ð	9ûô ô 	Ü×(Ò(¨Ð.NÐPVÔWØ�Jûð	úsf   ÁGÂ#GÃGÃ"GÃ5GÄGÄ-#GÅ)H2 ÆH2 Ç2H/È
H/È#H/È.H/È2I!È=IÉI!ÉI!c                ó$   € V ^8„  d   QhR\         /# r'   r¢  )r,   s   "r-   r.   r.     s   € ÷ Sñ Sœ3ñ Sr/   c                 ó,   € \        R\        \        4      # )z>Per-job output-file retention cap (``cron.output_retention``).Úoutput_retention)rÕ  Ú_CRON_OUTPUT_DEFAULT_KEEPrŠ  rG   r/   r-   Ú_cron_output_keepr`    s   € äÐ1Ô3LÌcÓRÐRr/   c                ó<   € V ^8„  d   QhR\         R\        R\        /# )r(   r=  Úkeepr)   )r
   rŠ  )r,   s   "r-   r.   r.   "  s!   € ÷ ñ ¤dð ´#ð ¼#ñ r/   c                óJ  € V^ 8:  d   ^ #  \        R V P                  R4       4       R RR7      p^ pY!R  F  p TP                  4        T^,          pK  	  T#   \         d     ^ # i ; i  \         d-   p\        P                  RTP                  T4        Rp?Kg  Rp?ii ; i)zúRemove the oldest ``*.md`` run-output files beyond *keep*; returns count deleted. Filenames
are timestamps, so a reverse lexical sort is newest-first. Non-positive *keep* disables pruning;
failures are swallowed so they can never break output saving.c              3   óT   "  € T F  qP                  4       '       g   K  Vx € K   	  R # 5ir/  )Úis_file)r|   rô  s   & r-   r~   Ú$_prune_job_output.<locals>.<genexpr>*  s   é € ÐCÑ3�1·y±y·{�QŠQÓ3ùs   ‚(ž
(z*.mdc                 ó   € V P                   # r/  rÊ  )rô  s   &r-   rŸ  Ú#_prune_job_output.<locals>.<lambda>+  s   € ˜!Ÿ&š&r/   T)r>  ÚreverseNz"Failed to prune cron output %s: %s)r  Úglobr¹   rä  r­   r{  rC  )r=  rb  ÚfilesÚdeletedÚstalerõ   s   &&    r-   Ú_prune_job_outputrn  "  s¨   € ð ˆq„yÙðÜÙC˜×+Ñ+¨FÔ3ÓCÙ ¨$ô0ˆð
 €GØ�u“ˆð	PØ�L‰LŒNØ�q�LŠGñ ð €Nøô ô Úðûô ô 	PÜ�L‰LÐ=¸u¿z¹zÈ3×OÒOûð	Pús(   ‹%A ºA+ÁA(Á'A(Á+B"Á6!BÂB"c                ó0   € V ^8„  d   QhR\         R\         /# )r(   r]   r>   r^   )r,   s   "r-   r.   r.   8  s   € ÷ ñ œCð ¬ñ r/   c                óü   € \        4        \        V 4      p\        V4       \        V4       V\	        4       P                  R4       R2,          p\        W1RRR7       \        V4       \        V\        4       4       V# )zSave job output to file.z%Y-%m-%d_%H-%M-%Sz.mdz.output_r½  r¾  )
rÓ   r  r…  re  r  r  r!   rj  rn  r`  )r]   r>   r=  Úoutput_files   &&  r-   r  r  8  sj   € ä„MÜ$ VÓ,€NÜ�^Ô$Ü�ÔØ ¤k£m×&<Ñ&<Ð=PÓ&QÐ%RÐRUÐ#VÕV€KÜ�k°jÀuÕMÜ�Ôä�nÔ&7Ó&9Ô:ØÐr/   c                ó0   € V ^8„  d   QhR\         R\        /# )r(   r£   r)   r%  )r,   s   "r-   r.   r.   H  s   € ÷ %ñ %œcð %¤cñ %r/   c                ó  € \        T ;'       g    R4      P                  4       pV'       g   R#  ^ RIHp V! V4      ;'       g    TpVP                  4       P                  R4      #   \         d    \
        P                  RT RR7        LDi ; i)u  Reduce a job skill reference (possibly an absolute path) to the bare name the curator matches
on, resolving as the scheduler does â€” otherwise a path-referencing job's skill looks
unreferenced and gets archived. Falls back to plain cleanup so a broken import can never lose
a name.r  )Únormalize_skill_lookup_namez8referenced_skill_names: could not normalize skill ref %rTr©   r  )r_   r�   Úagent.skill_utilsrt  r¬   r­   r{  Úlstrip)r£   r#  rt  s   &  r-   Ú_canonical_skill_refrw  H  s~   € ô
 �—	�	�r‹N× Ñ Ó"€EßÙðeÝAÙ+¨EÓ2×;Ð;°eˆð �;‰;‹=×Ñ Ó$Ð$øô ô eÜ�‰ÐOÐQTÐ_cˆÖdðeús   ¯A& ÁA& Á&#BÂBc                ó:   € V ^8„  d   QhR\         \        ,          /# r'   )rm   r_   )r,   s   "r-   r.   r.   X  s   € ÷ ñ ¤¤C¥ñ r/   c            
     ól  €  \        4       p T  UUu0 uFc  p\        T\        4      '       g   K  \        TP                  R4      TP                  R4      4       F  p\        T4      ;p'       g   K  TkK  	  Ke  	  upp#   \         d%    \        P                  RRR7       \	        4       u # i ; iu uppi )a  Skill names referenced by ANY cron job, deliberately including paused/disabled ones (resuming
must still find them); the curator protects these from inactivity archival. Canonicalized as the
scheduler does, so absolute paths are protected too. A corrupt store yields an empty set.z0referenced_skill_names: failed to load cron jobsTr©   r  r  )
r�   r¬   r­   r{  rm   r  r  r  rz   rw  )rù  r  rC  Úcleaneds       r-   Úreferenced_skill_namesr{  X  sª   € ðÜ‹{ˆñ ôáˆCÜ�cœ4× ô 	ô *¨#¯'©'°'Ó*:¸C¿G¹GÀHÓ<MÖNˆDÜ+¨DÓ1Ð1ˆGÓ1÷	 	ˆñ Oñ 	Ùòð øô ô Ü�‰ÐGÐRVˆÔWÜ‹uŠðüós!   ‚
A> ‘B0®=B0Á0
B0Á>,B-Â,B-c                óÂ   € V ^8„  d   QhR\         \        \        \        3,          ,          R\         \        \        ,          ,          R\        \        \        3,          /# )r(   ÚconsolidatedÚprunedr)   )r   r  r_   r  r   )r,   s   "r-   r.   r.   j  sG   € ÷ 2`ñ 2`Üœ4¤¤S �>Õ*ð2`Ü;CÄDÌÅIÕ;Nð2`ä	Œ#Œsˆ(…^ñ2`r/   c                óf  € \        T ;'       g    / 4      p \        T;'       g    . 4      \        V P                  4       4      ,
          pV '       g   V'       g	   R. R^ R^ /# \        4       ;_uu_ 4        \	        4       p. pV EFB  p\        VP                  R4      VP                  R4      4      pV'       g   K8  / p. p. p	V Fl  p
W 9   d2   W
,          pW·V
&   V'       d   W¹9  d   V	P                  V4       K6  K8  K:  W¢9   d   VP                  V
4       KS  W©9  g   K[  V	P                  V
4       Kn  	  V'       g   V'       g   KÂ  W•R&   V	'       d
   V	^ ,          MRVR&   TP                  RVP                  R4      R	VP                  R
4      ;'       g    VP                  R4      R\        V4      R\        V	4      RVRV/4       EKE  	  V'       d+   \        V4       \        P                  R\        V4      4       RVR\        V4      R\        V4      /uuRRR4       #   + '       g   i     R# ; i)aÎ  Rewrite cron job skill references after a curator consolidation pass (a job listing a
consolidated/pruned skill would otherwise run without it). Consolidated names map to their
umbrella target without duplication, pruned names are dropped, ordering is preserved, and the
legacy ``skill`` field is realigned. Returns ``{"rewrites": [{job_id, job_name, before,
after, mapped, dropped}, ...], "jobs_updated": N, "jobs_scanned": M}``. Load/save exceptions
propagate.ÚrewritesÚjobs_updatedÚjobs_scannedr  r  Nr]   rx   Újob_namerC  rm  Úafterræ  Údroppedz2Curator rewrote skill references in %d cron job(s))r  rm   ÚkeysrÛ   r�   r  rz   r  r  r  r­   r  rÜ  )r}  r~  Ú
pruned_setrù  r€  r  Úskills_beforeræ  r…  Ú
new_skillsrC  Útargets   &&          r-   Úrewrite_skill_refsr‹  j  sÆ  € ô ˜×*Ð*¨Ó+€Lä�V—\�\˜rÓ"¤S¨×):Ñ):Ó)<Ó%=Õ=€Jß§
Ø˜B °°>À1ÐEÐEä	��Ü‹{ˆØ)+ˆÜˆCÜ1°#·'±'¸'Ó2BÀCÇGÁGÈHÓDUÓVˆMß ÙØ%'ˆFØ!#ˆGØ$&ˆJÛ%�ØÔ'Ø)Õ/�FØ#)˜4‘Lß &Ô":Ø"×)Ñ)¨&Ö1ñ #;‘vàÔ'Ø—N‘N 4Ö(ØÖ+Ø×%Ñ% dÖ+ñ &÷ §'ÙØ&�‰Mß,6˜: až=¸DˆC�‰LØ�O‰OØ˜#Ÿ'™' $›-Ø˜CŸG™G F›O×<Ð<¨s¯w©w°t«}Øœ$˜}Ó-Øœ˜jÓ)Ø˜&Ø˜7ð÷ ñ+ ÷: Ü�dŒOÜ�K‰KÐLÌcÐRZËmÔ\Ø˜H n´c¸(³mÀ^ÔUXÐY]ÓU^Ð_÷G 
��‹ús-   Á3B%HÄHÄ:HÅAHÆ=HÇAHÈH0	c                ó`  € V ^8„  d   Qh/ ^ \         9   d   \        \        ,          ;R&   ^\         9   d
   \        ;R&   ^\         9   d%   \        \
        \        P                  3,          ;R&   ^\         9   d    \        \        \        ,          ,          ;R&   ^\         9   d    \        \        \        ,          ,          ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d&   \        \
        \        \        ,          3,          ;R&   ^\         9   d
   \        ;R	&   ^	\         9   d
   \        ;R
&   ^
\         9   d-   \        \
        \        \        .\        3,          3,          ;R&   ^\         9   d-   \        \
        \        \        .\        3,          3,          ;R&   # )r(   r2   r5   râ   r…   rl   rƒ  r„  ri  r¡  r¢  r¿  rø  )Ú__conditional_annotations__r   r+   rš   r  r_   rä   rå   r   rA   rZ   rŠ  r  r   r   )r,   s   "r-   r.   r.      sk  € ÷ 1ñ 1÷` $Ò #Œh”t�nÑ #ña1÷f  Ò ”EÑ ñg1÷P 3Ò 2”4œœYŸ_™_Ð,Õ-Ñ 2ñQ1÷~)ò )”j¤¬/Õ!:Õ;ñ )ñ1÷V+ò +œ
¤8Ô,@Õ#AÕBñ +ñW1÷D %Ò $œSÑ $ñE1÷J .Ò -¤DÑ -ñK1÷` 5Ò 4”Tœ#œx¬�Ð.Õ/Ñ 4ña1÷l# &Ò %œcÑ %ñm#1÷n# /Ò .¤TÑ .ño#1÷X6ò œ4¤¤X¬s¨e´S¨jÕ%9Ð 9Õ:ñ ñY61÷t6ò œ4¤¤X¬s¨e´S¨jÕ%9Ð 9Õ:ñ òu61r/   rp  )r  >   r  r-  r(  r  r)  r/  rÈ  )NNNNNNNNNNNNNFNNNNNFNFN)NNN(  r�  rh   rØ   rÁ  Úcontextvarsr   Údataclassesr   r   ry  Úloggingr8  r­  rä   r3   rn  rœ  rê   rµ   r6   r»   r   r   r   r   Úpathlibr
   r•  r   Úcron.constantsr   r   r   Úcron.env_settingsr   Úcron.jobs_invariantsr   Úcron.scheduler_ownershipr   r:   r   Útypingr   r   r   r   r   r   r   r   r   Ú	getLoggerrN   r­   Úhermes_timer   r  r   Ú/hermes_cli.observability.shared_metrics_gatewayr   Úutilsr    r!   r"   r#   r1   r2   r7   r5   r9   rŠ   r‹   r†   r‡   ÚTICKER_HEARTBEAT_FILEÚTICKER_SUCCESS_FILEÚTICKER_INTERVAL_SECONDSrå   rÑ   r²  rÐ   râ   ÚLockrá   rç   rÖ   rˆ   rE  rA   r…   rZ   rl   Úcontextmanagerrq   r‚   r‰   r�   r”   r—   r¡   rž   r¥   r¯   r²   rÁ   rÈ   rÛ   rö   rú   r  Ú	frozensetr  r  r  r!  r'  r  rÀ  r5  r?  rJ  rO  rS  rH  r[  r`  re  rj  rx  r  r…  rÓ   r•  rŸ  r   rÞ  rÛ  rÑ  ré  rð  rö  r  r  r  r%  r)  r,  r1  r6  r=  rA  rG  rK  rM  rO  rW  rX  rƒ  rv  r„  re  ri  rJ  r}  r…  r‰  r�  r—  r˜  r™  r›  r¡  r¢  r£  r§  r¹  rÀ  rÅ  rË  rÎ  rÔ  rÙ  rÜ  rá  rå  rë  rõ  r�   r	  rÿ  r  r.  r1  r4  rD  rJ  rP  r  Úobjectrw  rV  r]  ra  rh  rw  rr  r  r„  r‡  rŠ  r�  r–  r+   r¿  rø  r¤  r«  rÇ  rÎ  ÚLookupErrorrÐ  rä  rì  rð  rõ  rú  r   r  r  r  r  r!  r'  r_  r0  r4  r?  rF  rL  rP  rW  r^  rc  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+  r1  r:  r@  rC  rH  rU  ræ  r_  r`  rn  r  rw  r{  r‹  r.   )r�  s   @r-   Ú<module>r£     s®	  øð÷1ð 1ó Û Ý "ß (Û Û Û Û 
Û Û Û 	Û 	Û ðÛðÛ÷ 8Ó 7Ý Ý ,ß iÑ iÝ .Ý >Ý 9Ý ß U× UÕ Uà	×	Ò	˜8Ó	$€å *Ý $Ý Nß TÓ Tð
 €Ø#€Ó #àÐ ØÐ Ó õñ< Ó×&Ñ&Ó(€
ð ˜Õ€Ø�{Õ"€	ð !Ð#5Õ5Ð ØÐ!6Õ6Ð ð Ð ð —/’/Ó#€Ø—?’?Ó$Ð Ø02Ð Ó 2Ø#Ÿ.š.Ó*Ð Ø"ŸšÓ*Ð ð
 "Ð Ø˜Õ €
ØÐ ñ �$Ô÷Jð Jó ðJñ ?IØ 4ô?)Ð ó )÷ñ ñ FPØ TôF+Ð ó +ð ×Ñô,ó ð,õAñ   ¨)°ZÓ@€õ2ð" ×Ñô*ó ð*õ,ð !%Ð ð
 $)Ð  õ
^õõ09õ
õ*
ð ×Ññ2/ó ð2/ðj ×Ñô2ó ð2õjð ×Ñôó ðñ$ " 4 &Ó)Ð õ	3÷ð õ ÷5ð 5ñ ÓOÓP€ðKð õJõõõ0`õ
Iõ!õ 6õ
õ õõ3õ.$õ0ò"õ0ð: ˜a  b¨#¨tÐ4Ð õ	<ðØˆcðØ˜#ðàˆcðà˜#ðð ˆsðð ˜3ðð !'¨ðð �ð	ð ˜Sð	ð #)¨#ð	ð
 �ðð
 ˜Cðð
 "(¨ðð
 /6°sðð ˆcðð ˜#ðð �ðð ˜CñÐ ð 
ˆ3�˜˜j¨#Øˆu�j %Øˆu�j %ðÐ õõ2!(õH
>õTõIõX$õMõ
8õ
9õ
8õ
Sõõ5õFðØMQ÷ð Ð ØÐ õVð $'Ð  õð $%Ð Ó $àÐ Ø+-Ð "Ó -õ$3ðP 35Ð Ó 4õõDFõWõõð8 Ð Ø 4Ð Ø"Ð õ ð6 %&Ð Ó %Ø,.Ð #Ó .õYõ$÷;ð ;õ@÷Gð Gõ&õõõ/õ2õXõHõ	0õ	3õWõt4õ õ
,õð4ØLP÷õ"õð  Ð ð-ØLPð-à÷-ð`LØLPðLà÷Lñ ‹8€÷
ð 
õC÷
T÷
÷,;ñ(ZØ05÷Zñ Z÷J÷E÷
=÷K÷/÷ñ0 Ð)ÙÐ,ÙÐ#ØÐ*ÙÐ2ÙÐ/ÙÐ3ÙÐ!Ø�ÙÐ+ÙÐ1ÙÐ/ð>Ð ó ñ  Ð3ÙÒRÙÐ2ÙÐ/ÙÐ/ÙÐ3ð>Ð ó ÷J÷÷Bò B÷JCö	(˜Kô 	(÷2÷$ò ÷ F÷ ;÷ _÷$÷@
X÷&÷$'$÷T
ò 
÷%÷Pò ÷2+ñ [ð ÷
 +÷F÷@+÷*>÷
8÷
+÷" ñJ0"ÈU÷ 0"ñ 0"ófI-ð *.ñI-ð #'ñI-ð $ñI-ð ñI-ð +/ñI-ð #(÷I-ò I-÷X÷ 1÷F÷(4÷n÷+÷$+÷(÷6,÷#ñBNØ-CñBNØSXñBNàñBNà-2÷BNñ BN÷J	+ð $%Ñ  ÷ð ÷dð dð
"ØX\÷"ò "÷N
&ð 
&ð ÷#ò #ó ñ#÷<ð ÷(ð ÷(ð ð6 ÷Jò Jó ñJ÷4ð ÷2$ð $÷N#ð #÷Lð ÷>ð ÷.'ð '÷Tð ÷.Pð P÷f,ð ,ðl Ñ ÷Sð S÷
ð ÷,ð ÷ %ð %÷ ð ÷$2`õ 2`øðgj ô ØƒEðûð ô ØƒFðús$   Á_ Á_ ß	_ß_ß	_ ß_ 