pub struct Schedule {Show 13 fields
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,
pub tags: Vec<String>,
pub origin: Option<RepoOrigin>,
}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 operational constraints gating when within an active
period a fire may happen: a maintenance window, a fleet
max_concurrent cap, and skip_dates (holiday exclusion). The
wall-clock ones are evaluated in the schedule’s tz; future
require (env gates) lands in the same namespace. Checked at
tick time on both schedulers (and surfaced by preview).
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: boolFree-form operator taxonomy for the Schedules page — the
schedule-side mirror of Manifest.tags (added in #640; a plain
code ref rather than an intra-doc link, since that field isn’t
on this branch until #640 merges). Purely a SPA-side
organisational aid (search / filter chips alongside the
id-prefix grouping); the scheduler never reads it, so any
string is allowed and it carries no firing semantics. A
schedule’s own tags are independent of its job’s: the same job
may back a weekly maintenance schedule and a canary rollout
schedule. Empty by default and skip_serializing_if-elided per
the #492 gradual-upgrade wire rule.
origin: Option<RepoOrigin>GitOps provenance (#695) — see RepoOrigin. Stamped by
kanade schedule create when the source YAML lives inside a Git
work tree, so the SPA renders the schedule read-only and points
edits back at the repo (SPEC design principle #3: 設定駆動 YAML +
Git), parity with a job’s Manifest::origin. None for
SPA-born schedules and ones applied from outside any repo. Purely
informational — the scheduler never reads it. New field ⇒ #492
wire rule (default + skip_serializing_if).
Implementations§
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 is_event(&self) -> bool
pub fn is_event(&self) -> bool
True when this schedule fires from an OS event (when: { on })
rather than a clock — the agent skips tokio-cron registration
for these and drives them from boot / session-change instead.
Sourcepub fn event_triggers(&self) -> &[OnTrigger]
pub fn event_triggers(&self) -> &[OnTrigger]
The OS event triggers this schedule listens for, or &[] when it
is not an event schedule.
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