
    `gjI%                    l    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y)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y)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``.
    c                     y)z,Short identifier, e.g. 'builtin', 'chronos'.N selfs    J/root/.hermes/venv/lib/python3.12/site-packages/cron/scheduler_provider.pynamezCronScheduler.name%           c                     y)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     r   N<   )adaptersloopintervalc                    y)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   
stop_eventr   r   r   s        r   startzCronScheduler.start3   r   r   c                     y)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        r   c                     y)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    
 r   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-//r   r   r   c                   ddl m}m} ddlm} ddlm}  ||      sy ||      }|y ||| 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_jobF)sourceidexecution_idr#   )	cron.jobsr%   r&   r!   r'   cron.schedulerr(   r   )	r   job_idr   r   r%   r&   r'   r(   jobs	            r   fire_duezCronScheduler.fire_due[   sR     	:4.!&)fo;.vdiiHNN3==r   c                     y)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   returnstr)r4   bool)
r   zthreading.Eventr   r   r   r   r   intr4   None)r4   r8   )r4   r7   )r.   r5   r   r   r   r   r4   r6   )__name__
__module____qualname____doc__propertyr   r   r   r   r   r   r"   r0   r2   r
   r   r   r   r      s     ;  ; 
 # 	
   
  0 8< >0r   r   c                    ddl } | j                  d      }d}	 ddlm}m}  | |       ddd      xs dj                         }|r|d	v r
t               S 	 dd
lm	}  ||      }||j                  d|       t               S |j                         s|j                  d|       t               S |j                  d|j                         |S # t        $ r Y w xY w# t        $ r'}|j                  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.configrA   rB   strip	ExceptionInProcessCronSchedulerplugins.cron_providersrH   warningr   infor   )rI   loggerr   rA   rB   rH   rD   es           r   resolve_cron_schedulerrT   z   s    89FD:vz2FL"SSU 4AA%''(>&t,NNPRVW)++$$&NNTVZ[)++7G!  "  (KTST	
 &''	(s;   +C +C +C -C 	CC	D
#D?D
D
c                  4    e Zd ZdZedd       ZddddddZy)rN   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.
    c                     y)NrF   r
   r   s    r   r   zInProcessCronScheduler.name   s    r   Nr   )r   r   r   can_dispatchc                  dd l }ddlm} ddlm} |j                  d      }	|	j                  d|       | j                         }
|
r|	j                  d|
        |        |j                         sYd}	 | |       s|	j                  d       n |d||d|	       d
} ||       |j                  |       |j                         sXy y # t        $ r}|	j                  d|d
       Y d }~Od }~ww xY w)Nr   )tick)record_ticker_heartbeatr?   z0In-process cron scheduler started (interval=%ds)z=Marked %d interrupted cron execution(s) unknown after restartFz7Cron dispatch paused while gateway drains existing work)verboser   r   syncrW   TzCron tick error: %s)exc_info)success)rI   r-   rY   r,   rZ   rJ   rQ   r"   rP   is_setdebugBaseExceptionerrorwait)r   r   r   r   r   rW   rI   	cron_tickrZ   rR   	recoveredokrS   s                r   r   zInProcessCronScheduler.start   s    45""#<=FQ,,.	NNO 	 !##%BF+LNLL!Z[ %!)!"%1  $B/OOH%7 ##% ! F 2AEEFs   2*C	 		C0C++C0r3   )r9   r:   r;   r<   r=   r   r   r
   r   r   rN   rN      s,       -1tbW[ +&r   rN   )r4   z'CronScheduler')r<   
__future__r   	threadingabcr   r   typingr   r   rT   rN   r
   r   r   <module>rk      s9   & #  # \C \~%(P9&] 9&r   