pub struct Schedule {
pub id: String,
pub when: When,
pub job_id: String,
pub plan: FanoutPlan,
pub active: Active,
pub constraints: Constraints,
pub on_failure: OnFailure,
pub tz: ScheduleTz,
pub starting_deadline: Option<String>,
pub runs_on: RunsOn,
pub enabled: bool,
}Expand description
Periodic schedule (spec §2.4.3). v0.18.0 carries the fanout plan
(target + optional rollout + optional jitter) inline; the
referenced job (job_id → [BUCKET_JOBS]) supplies only the
script body. Two schedules of the same job can target different
groups on different cadences without copying the manifest.
#418 Phase 1: the cadence is the single When field. The old
cron × mode × cooldown × auto_disable_when_done quartet
is gone (no back-compat — pre-Phase-1 KV blobs fail to parse and
are warn-skipped; re-schedule create to upgrade them). The
engine underneath is unchanged: Schedule::lowered maps when
onto the same (cron, ExecMode, cooldown) trio the scheduler and
decide_fire always ran on.
Fields§
§id: String§when: WhenWhen to fire — a reconcile cadence (per_pc / per_target)
or a calendar time trigger (at / days). See When.
singleton_map: serde_yaml 0.9 renders externally-tagged
enums as !per_pc YAML tags by default; this keeps the
operator-facing map shape (when: { per_pc: once }). JSON
output is identical either way, and the schemars schema
(external tagging = oneOf of single-key objects) already
matches the singleton-map wire shape.
job_id: StringKey into crate::kv::BUCKET_JOBS. Must equal a registered
Manifest’s id.
plan: FanoutPlanWho + how-to-phase + when-to-stagger. The Manifest doesn’t carry these any more — same job + different fanout = different schedule.
active: ActiveOptional validity window. Outside [from, until) the
schedule is dormant — still registered, still visible, but
every tick is skipped (deleted ≠ dormant: a campaign that
ended stays inspectable and can be re-armed by editing the
window). Checked at tick time on both the backend scheduler
and the agent’s local scheduler.
constraints: Constraints#418 Phase 3: operational constraints gating when within an
active period a fire may happen. Currently just window
(a maintenance time-of-day window); future require
(env gates) and max_concurrent land in the same namespace.
Evaluated in the schedule’s tz like the other wall-clock
fields. Checked at tick time on both schedulers.
on_failure: OnFailure#418 Phase 4: what to do after a fire’s script comes back
failed. Currently just retry (fixed-backoff in-process
re-run); future notify / disable join the same namespace.
Applied fire-side in handle_command (the retry policy is
lowered onto every Command this schedule produces), so it
covers both runs_on locations.
tz: ScheduleTz#418 Phase 2: the timezone this schedule’s wall-clock fields
are evaluated in — both the calendar at firing time AND the
active.{from,until} window bounds. local (default) = the
running host’s TZ (the agent’s for runs_on: agent, the
backend server’s otherwise); utc for TZ-independent
schedules. Reconcile shapes (per_pc/per_target) ignore it
for firing (poll cron runs every minute regardless) but still
honor it for the active window.
starting_deadline: Option<String>v0.22: optional humantime window after a cron tick during
which the Command is still considered “live”. The scheduler
computes tick_at + starting_deadline and stamps it onto
each Command as deadline_at; agents skip Commands they
receive after that absolute time. None (default) = no
deadline, meaning a Command queued in the broker / stream
during agent downtime runs whenever the agent reconnects —
good for kitting / inventory / cleanup. Set this for
time-of-day notifications, lunch reminders, etc., where
“fire 3 hours late” would be wrong.
runs_on: RunsOnv0.23: where does the cron tick happen? Backend (default,
historical) = backend’s scheduler fires Commands via NATS;
agents passively receive. Agent = each targeted agent runs
its own internal cron and fires locally, so the schedule
keeps ticking even when the broker is unreachable (laptop on
the train, broker maintenance window, full WAN outage). The
two locations are mutually exclusive — when Agent, the
backend scheduler stays out and just keeps the definition in
KV for agents to read.
enabled: boolImplementations§
Source§impl Schedule
impl Schedule
Sourcepub fn bad_window(&self) -> Option<String>
pub fn bad_window(&self) -> Option<String>
The error message if this schedule’s constraints.window is
set but unparseable, else None. The scheduler logs this at
register time so a fail-closed (never-firing) schedule from a
hand-edited KV blob is diagnosable (gemini #452 review).
Sourcepub fn calendar_outside_window(&self) -> bool
pub fn calendar_outside_window(&self) -> bool
True when this is a calendar schedule whose fire time can
never fall inside its constraints.window — the cron fires,
the window check rejects it, and (firing only at that
time-of-day) it effectively never runs. An easy misconfig to
set up by accident; the scheduler warns at register time
(claude #452 review). Reconcile shapes poll every minute, so
they always catch the window opening and aren’t affected.
Sourcepub fn preview_fires(
&self,
now: DateTime<Utc>,
count: usize,
) -> Vec<DateTime<Utc>>
pub fn preview_fires( &self, now: DateTime<Utc>, count: usize, ) -> Vec<DateTime<Utc>>
Up to count future instants this schedule will fire, as
absolute UTC, strictly after now — the dry-run / preview
surface (#418 “ドライラン / プレビュー”). Only calendar
schedules have discrete fire times; reconcile shapes
(per_pc/per_target) poll every minute gated by cooldown, so
they return an empty vec and the caller describes the cadence
instead. Occurrences outside the active.{from,until} window or
the constraints.window are skipped, so the list reflects
when the schedule will ACTUALLY run, not the raw cron ticks.
Evaluated in the schedule’s tz, exactly like the scheduler’s
Job::new_async_tz, and with the same croner config the
scheduler / Schedule::validate use, so a preview can never
disagree with a real fire. A schedule that can never fire (a
calendar time wholly outside its window, a past one-shot,
enabled: false is not considered here — callers gate on
enabled separately) yields an empty vec.
Sourcepub fn lowered(&self) -> Lowered
pub fn lowered(&self) -> Lowered
Lower the operator-facing when onto the engine vocabulary.
Single seam shared by the backend scheduler and the agent’s
local scheduler so the two can never drift.
Sourcepub fn next_calendar_fire(&self, now: DateTime<Utc>) -> Option<DateTime<Utc>>
pub fn next_calendar_fire(&self, now: DateTime<Utc>) -> Option<DateTime<Utc>>
The next absolute (UTC) time this schedule fires, or None when
it has no discrete upcoming fire to preview.
Used by the KLP maintenance.list preview (“what’s about to
happen on my PC”, SPEC §2.1). Returns None for:
- reconcile shapes (
per_pc/per_target) — they lower to the every-minutePOLL_CRONand re-converge state continuously, so “next fire” is always ~60s away and means nothing to a user previewing upcoming maintenance; - a calendar schedule whose lowered cron won’t parse (a
hand-edited KV blob that slipped past
Schedule::validate); - a cron with no future occurrence.
The wall-clock fire is evaluated in the schedule’s own tz
(matching the live tick’s Job::new_async_tz) then normalised
to UTC for the wire. inclusive = false: strictly the next
fire after now, never one matching the current instant.
Sourcepub fn validate(&self) -> Result<(), String>
pub fn validate(&self) -> Result<(), String>
Cross-field semantic checks that don’t fit pure serde derive
— the Manifest::validate counterpart (#418 decision F;
pre-Phase-1 a broken schedule was accepted at create time
and silently warn-skipped at tick time). Run at every create
site: kanade schedule create (client-side) and
POST /api/schedules. The job_id-exists check lives in the
API handler instead — it needs the JOBS KV.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Schedule
impl<'de> Deserialize<'de> for Schedule
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl JsonSchema for Schedule
impl JsonSchema for Schedule
Source§fn schema_id() -> Cow<'static, str>
fn schema_id() -> Cow<'static, str>
Source§fn json_schema(generator: &mut SchemaGenerator) -> Schema
fn json_schema(generator: &mut SchemaGenerator) -> Schema
Source§fn inline_schema() -> bool
fn inline_schema() -> bool
$ref keyword. Read more