
    TdjR<                    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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/thesage/.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 )zOptional 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   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_interruptedU   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.jobsr,   r-   r'   r.   cron.schedulerr/   r   )	r   r*   r   r   r,   r-   r.   r/   jobs	            r   fire_duezCronScheduler.fire_due[   s     	:9999999444444......!!&)) 	5gfoo;5..vdiHHHNN{3====r   c                    dS )zConverge 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.reconciles   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(   r6   r8   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.configrC   rD   strip	ExceptionInProcessCronSchedulerplugins.cron_providersrJ   warningr   infor   )rK   loggerr   rC   rD   rJ   rF   es           r   resolve_cron_schedulerrV   z   s    NNN899FD::::::::vz2FFFL"SSUU     (4AAA%'''(>>>>>>&&t,,NNPRVWWW)+++$$&& 	,NNTVZ[[[)+++7GGG ( ( (KTST	
 	
 	
 &''''''''	(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 )rP   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 )NrH   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|            |             |                                sd}	 |  |            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_heartbeatrA   z0In-process cron scheduler started (interval=%ds))rZ   r   r   r   rY   z=Marked %d interrupted cron execution(s) unknown after restartF7Cron dispatch paused while gateway drains existing workverboser   r   syncrY   TCron tick error: %sexc_info: success)rK   r4   r]   r3   r^   r_   r`   rL   rS   _start_multiplexr(   rR   is_setdebugBaseExceptionerrortyper:   wait)r   r   r   r   r   rY   rZ   rK   	cron_tickr^   r_   r`   rT   	recoveredokrU   s                   r   r   zInProcessCronScheduler.start   s\    	444444	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ""#<==FQQQ  		!!+!!) "    F ,,..	 	NNO   	 !!!##%% $	&B@+LLNN+LL!Z[[[[I %!)!"%1      @ @ @ 2AEEE $#tAww'7$>$>1$>$>????????@& $#B//// %""$$$OOH%%%I ##%% $	& $	& $	& $	& $	&s   ,4C! !
D/+:D**D/)r   r   r   rY   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|                                sd}	 |  |            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\   )r^   r_   r`   use_cron_store)set_hermes_home_overridereset_hermes_home_overriderA   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>'  s/    HHHQZ5))0QqTTqHHHr      z9Marked %d interrupted cron execution(s) for profile at %sFra   rb   Tre   rf   rh   ri   )rK   r4   r]   r3   r^   r_   r`   rv   hermes_constantsrw   rx   rL   rS   lenrz   r{   r
   r(   rR   rl   rm   rn   ro   rp   r:   rq   )r   r   rZ   r   r   r   rY   rK   rr   r^   r_   r`   rv   rw   rx   rT   entryhome
home_tokenrs   rt   _tick_errorrU   s                          r   rk   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 	7E)%77B588UD11#d))<<J7#^D)) . . $ 8 8 : :I  W%   
 ,+---. . . . . . . . . . . . . . . +*:6666**:6666##%% *	&B#+LLNN+LL!Z[[[[!. C C+5eU+C+CNuQxx%=%=c$ii%H%H

C!/!5!5 " " )	,1-5)-).1=!" !" !" !"" " " " " " " " " " " " " " " 76zBBBB66zBBBB
 #	 ! 9 9 92AEEE!%a!188Q889 ' ; ;#-eU#;#;FuQxx55c$ii@@
;'-- = =//;;;;  =..0000( =//<<<= = = = = = = = = = = = = = = /.z::::..z::::OOH%%%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r9   )r:   r;   r<   r=   r>   r   r   rk   r   r   r   rP   rP      s             X U& U& U& U& U&x ^& ^& ^& ^& ^& ^& ^&r   rP   )r	   r?   )r=   
__future__r   	threadingabcr   r   typingr   r   rV   rP   r   r   r   <module>r      s    & # " " " " "     # # # # # # # #      \ \ \ \ \C \ \ \~%( %( %( %(PC& C& C& C& C&] C& C& C& C& C&r   