§
    øžyj>  ã                  óz   — d Z ddlmZ ddlZddlmZmZ ddlmZ  G d„ de¦  «        Z	dd
„Z
 G d„ de	¦  «        ZdS )u"  CronScheduler provider interface (Axis B â€” the trigger).

âš ï¸� EXPERIMENTAL â€” this interface is validated by exactly ONE consumer (the
built-in) until an external provider (Chronos, Phase 4) shakes it out. Until
then the module path, method signatures, and start() kwargs MAY change without
a deprecation cycle. Once a second provider validates the shape it becomes
stable. Any growth MUST be additive (new optional method with a default), never
a changed signature on start() or a new abstractmethod.

A CronScheduler decides *when* a due job fires. It does NOT decide what firing
means: execution + delivery stay in cron.scheduler.run_job / _deliver_result,
shared by all providers. Providers must never reimplement agent construction or
delivery.

The built-in InProcessCronScheduler runs the historical 60s daemon-thread
ticker. Alternative providers (e.g. Chronos, a NAS-mediated managed-cron
provider for scale-to-zero deployments) live under plugins/cron_providers/<name>/ and are
selected via the `cron.provider` config key (empty = built-in).
é    )ÚannotationsN)ÚABCÚabstractmethod)ÚAnyc                  óœ   — e Zd ZdZeedd„¦   «         ¦   «         Zdd„Zedddd	œdd„¦   «         Zd d„Z	d d„Z
d!d„Zd"d„Zdddœd#d„Zd d„ZdS )$ÚCronScheduleru«  Axis-B trigger provider. Decides WHEN a due cron job fires.

    Required surface is intentionally minimal: ``name`` + ``start``. ``stop``
    and ``is_available`` carry safe defaults. The three Phase-4 hooks
    (``on_jobs_changed`` / ``fire_due`` / ``reconcile``) are added later as
    NON-abstract methods so the built-in keeps satisfying the ABC without
    overriding them â€” see ``test_abc_growth_stays_additive``.
    ÚreturnÚstrc                ó   — dS )z,Short identifier, e.g. 'builtin', 'chronos'.N© ©Úselfs    ú=/home/ragecks/.hermes/hermes-agent/cron/scheduler_provider.pyÚnamezCronScheduler.name%   ó   € € € ó    Úboolc                ó   — dS )a)  Whether this provider can run in the current environment.

        MUST NOT make network calls. The built-in is always available; an
        external provider checks for configured endpoint/credentials. When a
        named provider returns False, the resolver falls back to the built-in.
        Tr   r   s    r   Úis_availablezCronScheduler.is_available*   s	   € ð ˆtr   Né<   )ÚadaptersÚloopÚintervalÚ
stop_eventúthreading.Eventr   r   r   r   ÚintÚNonec               ó   — dS )aP  Begin firing due jobs.

        For the built-in this BLOCKS in the 60s loop until stop_event is set
        (it is run inside a daemon thread by the caller, exactly as today).
        An external provider may register a schedule/webhook and return
        immediately; in that case it must still honor stop_event for teardown.
        Nr   )r   r   r   r   r   s        r   ÚstartzCronScheduler.start3   r   r   c                ó   — dS )zÂOptional eager teardown hook. Default no-op; setting the stop_event
        is the primary stop signal. Override for providers holding external
        resources (queue consumers, HTTP servers).Nr   r   s    r   ÚstopzCronScheduler.stopD   ó	   € ð ˆtr   c                ó   — dS )a  Called after a successful store mutation (create/update/remove/
        pause/resume). External providers reconcile their registry here (e.g.
        Chronos re-provisions/cancels the affected one-shot via NAS).
        Built-in: no-op (it re-reads jobs.json on every tick).Nr   r   s    r   Úon_jobs_changedzCronScheduler.on_jobs_changedN   s	   € ð
 ˆtr   Újobúdict[str, Any]c                ó   — dS )aR  Register the first external trigger for one newly persisted job.

        The built-in provider reads the local store on every tick, so its
        default is a no-op. External providers override this when creating a
        job requires a remote registration before callers can honestly report
        that the job is scheduled.
        Nr   )r   r%   s     r   Úregister_jobzCronScheduler.register_jobU   s	   € ð ˆtr   c                ó"   — ddl m}  |¦   «         S )z@Run profile-local attempt recovery for every provider lifecycle.r   )Úrecover_interrupted_executions)Úcron.executionsr*   )r   r*   s     r   Úrecover_interruptedz!CronScheduler.recover_interrupted_   s#   € àBÐBÐBÐBÐBÐBà-Ð-Ñ/Ô/Ð/r   ©r   r   Újob_idc               ó´   — ddl m}m} ddlm} ddlm}  ||¦  «        sdS  ||¦  «        }|€dS  ||| j        ¬¦  «        d         |d	<    ||||¬
¦  «        S )aL  Run a single job NOW via the shared orchestrator. Called by the
        inbound fire webhook when an external scheduler signals a job is due.

        The default claims the job with a store-level compare-and-set
        (multi-machine at-most-once), then runs it via the shared
        ``run_one_job`` body. Built-in never calls this (it has its own tick
        loop); an external provider routes its inbound fire here.

        Returns True if THIS caller claimed and ran the job, False if the claim
        was lost (another machine/retry won it) or the job no longer exists.
        r   )Úclaim_job_for_fireÚget_job)Úcreate_execution)Úrun_one_jobFN)ÚsourceÚidÚexecution_idr-   )Ú	cron.jobsr0   r1   r+   r2   Úcron.schedulerr3   r   )	r   r.   r   r   r0   r1   r2   r3   r%   s	            r   Úfire_duezCronScheduler.fire_duee   s§   € ð 	:Ð9Ð9Ð9Ð9Ð9Ð9Ð9Ø4Ð4Ð4Ð4Ð4Ð4Ø.Ð.Ð.Ð.Ð.Ð.à!Ð! &Ñ)Ô)ð 	Ø�5Øˆg�f‰oŒoˆØˆ;Ø�5Ø.Ð.¨v¸d¼iÐHÑHÔHÈÔNˆˆNÑØˆ{˜3¨¸Ð=Ñ=Ô=Ð=r   c                ó   — dS )z¨Converge the external registry toward jobs.json (the desired state):
        arm missing one-shots, cancel orphaned ones, re-arm changed times.
        Built-in: no-op.Nr   r   s    r   Ú	reconcilezCronScheduler.reconcile}   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   r   r	   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úpropertyr   r   r   r   r!   r$   r(   r,   r9   r;   r   r   r   r   r      s  € € € € € ðð ð Øð;ð ;ð ;ñ „^ñ „Xð;ðð ð ð ð ð
 ØØðð ð ð ð ñ „^ðð ð ð ð ðð ð ð ðð ð ð ð0ð 0ð 0ð 0ð 8<Èð >ð >ð >ð >ð >ð >ð0ð ð ð ð ð r   r   r	   ú'CronScheduler'c                 ó|  — ddl } |                      d¦  «        }d}	 ddlm}m}  | |¦   «         ddd¬¦  «        pd                     ¦   «         }n# t          $ r Y nw xY w|r|d	v rt          ¦   «         S 	 dd
lm	}  ||¦  «        }|€$| 
                    d|¦  «         t          ¦   «         S |                     ¦   «         s$| 
                    d|¦  «         t          ¦   «         S |                     d|j        ¦  «         |S # t          $ r/}| 
                    d||¦  «         t          ¦   «         cY d}~S d}~ww xY w)u1  Return the active cron scheduler provider.

    Reads ``cron.provider`` from config. Empty/absent â†’ built-in. A named
    provider that is missing, fails to load, or reports ``is_available() ==
    False`` falls back to the built-in with a warning â€” cron must never be left
    without a trigger.
    r   Núcron.scheduler_providerÚ )Úcfg_getÚload_configÚcronÚprovider)Údefault)Úbuiltinz
in-processÚ	inprocess)Úload_cron_schedulerz3cron.provider '%s' not found; using built-in tickerz7cron.provider '%s' not available; using built-in tickerz!Using cron scheduler provider: %sz=Failed to load cron.provider '%s' (%s); using built-in ticker)ÚloggingÚ	getLoggerÚhermes_cli.configrF   rG   ÚstripÚ	ExceptionÚInProcessCronSchedulerÚplugins.cron_providersrM   Úwarningr   Úinfor   )rN   Úloggerr   rF   rG   rM   rI   Úes           r   Úresolve_cron_schedulerrY   „   s°  € ð €N€N€Nà×ÒÐ8Ñ9Ô9€Fà€DðØ:Ð:Ð:Ð:Ð:Ð:Ð:Ð:Ø�˜˜™œ v¨zÀ2ÐFÑFÔFÐLÈ"×SÒSÑUÔUˆˆøÝð ð ð Øˆðøøøð ð (�4ÐAÐAÐAÝ%Ñ'Ô'Ð'ð(Ø>Ð>Ð>Ð>Ð>Ð>Ø&Ð& tÑ,Ô,ˆØÐØ�NŠNÐPÐRVÑWÔWÐWÝ)Ñ+Ô+Ð+Ø×$Ò$Ñ&Ô&ð 	,Ø�NŠNÐTÐVZÑ[Ô[Ð[Ý)Ñ+Ô+Ð+Ø�ŠÐ7¸¼ÑGÔGÐGØˆøÝð (ð (ð (Ø�ŠØKÈTÐSTñ	
ô 	
ð 	
õ &Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'øøøøð	(øøøs;   �3A Á
AÁAÁ66D Â-7D Ã%D Ä
D;Ä$D6Ä0D;Ä6D;c                  óP   — e Zd ZdZedd„¦   «         Zddddddœd„Zddddd	œd
„ZdS )rS   aš  Default provider: the historical in-process 60s ticker.

    ``start()`` blocks in the tick loop until ``stop_event`` is set, identical
    to the pre-refactor ``_start_cron_ticker`` core loop. The caller runs it in
    a daemon thread. ``can_dispatch`` is an optional synchronous gate supplied
    by GatewayRunner during external drain; skipped ticks leave due jobs intact
    for the next allowed tick.
    r	   r
   c                ó   — dS )NrK   r   r   s    r   r   zInProcessCronScheduler.name¶   s   € àˆyr   Nr   )r   r   r   Úcan_dispatchÚprofile_homesc               óî  — dd l }ddlm} ddlm}	m}
m} |                     d¦  «        }|                     d|¦  «         |r|  	                    ||||||¬¦  «         d S |  
                    ¦   «         }|r|                     d|¦  «          |¦   «          |                     ¦   «         sÌd}	 |�  |¦   «         s|                     d	¦  «         n |d||d|¬
¦  «         d}nQ# t          $ rD}|                     d|d¬¦  «          |
t!          |¦  «        j        › d|› �¦  «         Y d }~nd }~ww xY w ||¬¦  «         |r
 |	¦   «          |                     |¦  «         |                     ¦   «         ¯Êd S d S )Nr   ©Útick)Úclear_ticker_errorÚrecord_ticker_errorÚrecord_ticker_heartbeatrD   z0In-process cron scheduler started (interval=%ds))r]   r   r   r   r\   z=Marked %d interrupted cron execution(s) unknown after restartFú7Cron dispatch paused while gateway drains existing work©Úverboser   r   Úsyncr\   TúCron tick error: %s©Úexc_infoú: ©Úsuccess)rN   r8   r`   r7   ra   rb   rc   rO   rV   Ú_start_multiplexr,   rU   Úis_setÚdebugÚBaseExceptionÚerrorÚtyper=   Úwait)r   r   r   r   r   r\   r]   rN   Ú	cron_tickra   rb   rc   rW   Ú	recoveredÚokrX   s                   r   r   zInProcessCronScheduler.startº   s\  € ð 	ˆˆˆØ4Ð4Ð4Ð4Ð4Ð4ð	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð ×"Ò"Ð#<Ñ=Ô=ˆØ�ŠÐFÈÑQÔQÐQð ð 		Ø×!Ò!ØØ+Ø!ØØ!Ø)ð "ñ ô ð ð ˆFð ×,Ò,Ñ.Ô.ˆ	Øð 	Ø�NŠNØOØñô ð ð 	 ÐÑ!Ô!Ð!Ø×#Ò#Ñ%Ô%ð $	&ØˆBð@ØÐ+°L°L±N´NÐ+Ø—L’LÐ!ZÑ[Ô[Ð[Ð[à�IØ %Ø!)Ø!Ø"Ø%1ðñ ô ð ð ��øÝ ð @ð @ð @ð —’Ð2°AÀ�ÑEÔEÐEð $Ð#¥t¨A¡w¤wÔ'7Ð$>Ð$>¸1Ð$>Ð$>Ñ?Ô?Ð?Ð?Ð?Ð?Ð?Ð?øøøøð@øøøð& $Ð#¨BÐ/Ñ/Ô/Ð/Øð %Ø"Ð"Ñ$Ô$Ð$Ø�OŠO˜HÑ%Ô%Ð%ðI ×#Ò#Ñ%Ô%ð $	&ð $	&ð $	&ð $	&ð $	&s   Â,4C! Ã!
D/Ã+:D*Ä*D/)r   r   r   r\   c          	     óê  — ddl }ddlm} ddlm}	m}
m}m} ddlm	}m
} |                     d¦  «        }|                     dt          |¦  «        d„ |D ¦   «         ¦  «         |D ]¯}t          |t          ¦  «        r|d	         n|} |t!          |¦  «        ¦  «        }	  ||¦  «        5  |                      ¦   «         }|r|                     d
||¦  «          |¦   «          ddd¦  «         n# 1 swxY w Y    ||¦  «         Œ #  ||¦  «         w xY w|                     ¦   «         �sÊd}	 |�  |¦   «         s|                     d¦  «         n‹|D ]ˆ}t          |t          ¦  «        r|d	         n|} |t!          |¦  «        ¦  «        }	  ||¦  «        5   |d||d|¬¦  «         ddd¦  «         n# 1 swxY w Y    ||¦  «         Œy#  ||¦  «         w xY wd}d}nH# t*          $ r;}|                     d|d¬¦  «         t/          |¦  «        j        › d|› �}Y d}~nd}~ww xY w|D ]ž}t          |t          ¦  «        r|d	         n|} |t!          |¦  «        ¦  «        }	  ||¦  «        5   ||¬¦  «         |r |	¦   «          n|r |
|¦  «         ddd¦  «         n# 1 swxY w Y    ||¦  «         Œ�#  ||¦  «         w xY w|                     |¦  «         |                     ¦   «         �¯ÈdS dS )u´  Tick every served profile's cron store when multiplex_profiles is on.

        Each profile uses ``set_hermes_home_override()`` + ``use_cron_store()``
        to scope its tick, heartbeat, recovery, lock file, config/.env, and
        agent execution to that profile's home â€” mirroring how
        ``_profile_runtime_scope`` scopes the multiplexed inbound path and
        ``web_server.py`` scopes per-profile cron API calls.
        r   Nr_   )ra   rb   rc   Úuse_cron_store)Úset_hermes_home_overrideÚreset_hermes_home_overriderD   z6Multiplex cron scheduler started for %d profile(s): %sc                óL   — g | ]!}t          |t          ¦  «        r|d          n|‘Œ"S )r   )Ú
isinstanceÚtuple)Ú.0Úps     r   ú
<listcomp>z;InProcessCronScheduler._start_multiplex.<locals>.<listcomp>1  s/   € ÐHÐHÐH°Q•Z ¥5Ñ)Ô)Ð0ˆQˆqŒTˆT¨qÐHÐHÐHr   é   z9Marked %d interrupted cron execution(s) for profile at %sFrd   re   Trh   ri   rk   rl   )rN   r8   r`   r7   ra   rb   rc   ry   Úhermes_constantsrz   r{   rO   rV   Úlenr}   r~   r
   r,   rU   ro   rp   rq   rr   rs   r=   rt   )r   r   r]   r   r   r   r\   rN   ru   ra   rb   rc   ry   rz   r{   rW   ÚentryÚhomeÚ
home_tokenrv   rw   Ú_tick_errorrX   s                          r   rn   z'InProcessCronScheduler._start_multiplex  sû  € ð$ 	ˆˆˆØ4Ð4Ð4Ð4Ð4Ð4ð	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	ZÐYÐYÐYÐYÐYÐYÐYà×"Ò"Ð#<Ñ=Ô=ˆØ�ŠØDÝ�ÑÔØHÐH¸-ÐHÑHÔHñ	
ô 	
ð 	
ð #ð 	7ð 	7ˆEÝ)¨%µÑ7Ô7ÐB�5˜”8�8¸UˆDØ1Ð1µ#°d±)´)Ñ<Ô<ˆJð7Ø#�^ DÑ)Ô)ð .ð .Ø $× 8Ò 8Ñ :Ô :�IØ ð ØŸšØWØ%Ø ñô ð ð
 ,Ð+Ñ-Ô-Ð-ð.ð .ð .ñ .ô .ð .ð .ð .ð .ð .ð .øøøð .ð .ð .ð .ð +Ð*¨:Ñ6Ô6Ð6Ð6øÐ*Ð*¨:Ñ6Ô6Ð6Ð6øøøà×#Ò#Ñ%Ô%ñ *	&ØˆBð#ØÐ+°L°L±N´NÐ+Ø—L’LÐ!ZÑ[Ô[Ð[Ð[à!.ð Cð C˜Ý+5°e½UÑ+CÔ+CÐN˜u Qœx˜xÈ˜Ø%=Ð%=½cÀ$¹i¼iÑ%HÔ%H˜
ð
CØ!/ °Ñ!5Ô!5ð "ð "Ø ) 	Ø,1Ø-5Ø)-Ø).Ø1=ð!"ñ !"ô !"ð !"ð"ð "ð "ñ "ô "ð "ð "ð "ð "ð "ð "øøøð "ð "ð "ð "ð 7Ð6°zÑBÔBÐBÐBøÐ6Ð6°zÑBÔBÐBÐBøøøØ�ð
 #��øõ	 !ð 9ð 9ð 9Ø—’Ð2°AÀ�ÑEÔEÐEÝ!% a¡¤Ô!1Ð8Ð8°QÐ8Ð8������øøøøð9øøøð 'ð ;ð ;�Ý#-¨eµUÑ#;Ô#;ÐF�u˜Q”x�xÀ�Ø5Ð5µc¸$±i´iÑ@Ô@�
ð;Ø'˜¨Ñ-Ô-ð =ð =Ø/Ð/¸Ð;Ñ;Ô;Ð;ð ð =Ø.Ð.Ñ0Ô0Ð0Ð0Ø(ð =Ø/Ð/°Ñ<Ô<Ð<ð=ð =ð =ñ =ô =ð =ð =ð =ð =ð =ð =øøøð =ð =ð =ð =ð /Ð.¨zÑ:Ô:Ð:Ð:øÐ.Ð.¨zÑ:Ô:Ð:Ð:øøøØ�OŠO˜HÑ%Ô%Ð%ðU ×#Ò#Ñ%Ô%ñ *	&ð *	&ð *	&ð *	&ð *	&sµ   ÂDÂ)8C-Ã!DÃ-C1	Ã1DÃ4C1	Ã5DÄDÄ,AG Æ
G	ÆF2Æ&G	Æ2F6	Æ6G	Æ9F6	Æ:G	Æ=G Ç	GÇG Ç
H#Ç(1HÈH#É"J7É-'J ÊJ7Ê J$	Ê$J7Ê'J$	Ê(J7Ê7Kr<   )r=   r>   r?   r@   rA   r   r   rn   r   r   r   rS   rS   ¬   sœ   € € € € € ðð ð ðð ð ñ „Xðð ØØØØðU&ð U&ð U&ð U&ð U&ðx ØØØð^&ð ^&ð ^&ð ^&ð ^&ð ^&ð ^&r   rS   )r	   rB   )r@   Ú
__future__r   Ú	threadingÚabcr   r   Útypingr   r   rY   rS   r   r   r   ú<module>r�      sß   ððð ð& #Ð "Ð "Ð "Ð "Ð "à Ð Ð Ð Ø #Ð #Ð #Ð #Ð #Ð #Ð #Ð #Ø Ð Ð Ð Ð Ð ðfð fð fð fð f�Cñ fô fð fðR%(ð %(ð %(ð %(ðPC&ð C&ð C&ð C&ð C&˜]ñ C&ô C&ð C&ð C&ð C&r   