pub struct Schedule {
pub id: String,
pub when: When,
pub job_id: String,
pub plan: FanoutPlan,
pub active: Active,
pub constraints: Constraints,
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.
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 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 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