pub struct Manifest {Show 17 fields
pub id: String,
pub version: String,
pub description: Option<String>,
pub execute: Execute,
pub require_approval: bool,
pub inventory: Option<InventoryHint>,
pub emit: Option<EmitConfig>,
pub check: Option<CheckHint>,
pub collect: Option<CollectHint>,
pub aggregate: Option<Vec<AggregateWidget>>,
pub staleness: Staleness,
pub client: Option<ClientHint>,
pub tags: Vec<String>,
pub origin: Option<RepoOrigin>,
pub finalize: Option<FinalizeSpec>,
pub feed: Vec<FeedSpec>,
pub tier: Option<Tier>,
}Expand description
YAML job manifest (= registered “what to run”, v0.18.0+).
Owns only script-intrinsic fields. Who (target), how to
phase fanout (rollout), and when to stagger start
(jitter) all moved to the Schedule / exec request side — same
script can now be fired against different targets / rollouts
without copying the script body.
#492: these types are READ fleet-wide (agents decode them from
BUCKET_JOBS / BUCKET_SCHEDULES and inside live Commands), so they
must tolerate unknown fields — deny_unknown_fields here made a
gradually-upgrading fleet’s OLD agents reject the whole object
the moment a newer backend added any field. Operator typo
protection (the old reason for the attribute) lives at the WRITE
boundaries instead: kanade job/schedule create and the backend
POST extractor parse via crate::strict, which rejects unknown
keys with their full paths. The wire rule: new fields always get
#[serde(default)] (+ skip_serializing_if while old readers
may still be strict).
Fields§
§id: String§version: String§description: Option<String>§execute: Execute§require_approval: bool§inventory: Option<InventoryHint>Opt-in marker that this job produces a JSON inventory fact
payload on stdout. When present, the backend’s results
projector parses ExecResult.stdout as JSON and upserts an
inventory_facts row keyed by (pc_id, manifest.id). The
display sub-config drives the SPA’s Inventory page render.
emit: Option<EmitConfig>Issue #246: opt-in marker that this job emits per-line
observability events on stdout (one JSON ObsEvent per
newline). When present, the agent — after the script exits
successfully — parses each non-empty stdout line as an
ObsEvent, publishes it on obs.<pc_id> via the
obs_outbox, and (intentionally) omits the stdout from
the ExecResult so the timeline data doesn’t double up
in execution_results.stdout (which would multiply rows
by ~50/day/PC of noise).
Distinct from inventory: (single JSON object → projector
upsert) — events are append-only timeline points consumed
by the dedicated obs_events table.
check: Option<CheckHint>#290: opt-in marker that this job is an operator-defined
health check whose result feeds the Client App’s Health
tab over KLP (StateSnapshot.checks). The script prints a
free-form JSON object on stdout (like any inventory job); the
agent reads the CheckHint::status_field value dynamically
into a crate::ipc::state::Check named check.name.
Cadence / windows / conditions come from
the job’s Schedule (exactly like inventory) — there is
deliberately no interval here. Composes with inventory: and
collect: (#821): each reads its own #KANADE-<KIND>-fenced
stdout block, so one job can drive a check, project inventory
facts, and collect files in a single run. Only emit: (NDJSON
stdout) is incompatible. A check-only job may skip the fence
(whole stdout is the JSON); a multi-hint job fences each block.
collect: Option<CollectHint>#219: opt-in marker that this job COLLECTS files into a bundle.
The script does the collection work and prints a single JSON
object on stdout carrying a files array of paths (the field
name is CollectHint::files_field, default "files"); the
agent — after the script exits successfully — zips those files,
uploads the archive to the OBJECT_COLLECTIONS Object Store
bucket (key <pc_id>/<job_id>/<timestamp>.zip), and records the
key in crate::wire::ExecResult::collect_object. The operator
downloads bundles from the SPA Collect page.
Like inventory: / check: this reads a JSON object from stdout.
#821: it reads its own #KANADE-COLLECT-BEGIN/END-fenced block,
so it composes with inventory: / check: (and a user
message) on one stdout — only emit: (NDJSON) is incompatible
(enforced in Manifest::validate). A collect-only job may skip
the fence. It also composes with client: — a collect: +
client: job lets an end user trigger a collection from the
Client App (the same-host agent runs it).
aggregate: Option<Vec<AggregateWidget>>#720: opt-in declarative aggregation over obs_events that drives
the SPA Analytics page. Unlike the other hints this one never
touches stdout and is never delivered to the agent — it’s a pure
read spec the backend reads from BUCKET_JOBS at query time and
turns into json_extract aggregation SQL. Each entry is one widget
(a dashboard: tab groups them); scope: selects per-PC vs
fleet-wide rollup. Because it consumes nothing at run time it
composes with every other hint (typically paired with emit:,
which produces the events it reads). See AggregateWidget.
New field ⇒ #492 wire rule (default + skip_serializing_if).
staleness: Stalenessv0.26: Layer 2 staleness policy (SPEC.md §2.6.2). Controls
what the agent does at fire time when it can’t verify the
script_current / script_status KV values are fresh —
especially relevant for runs_on: agent schedules where
the agent may fire from cache while offline. Defaults to
Staleness::Cached (silently use cached values), which
matches every pre-v0.26 Manifest.
client: Option<ClientHint>#291: opt-in marker that this job is offered to end users
in the Client App’s job tabs over KLP (jobs.list →
jobs.execute). Parallel to inventory / check /
emit: the block’s mere presence is the opt-in, and it
groups the end-user presentation fields (name / category /
icon) that only make sense for a user-facing job. None
(the default) ⇒ an operator-only job — inventory, checks,
scheduled maintenance — that never surfaces in the catalog.
The agent re-reads this at every jobs.list / jobs.execute
(SPEC §2.1), so removing the block takes a job out of a
running client on its next action.
Free-form operator taxonomy for the Jobs catalog. Purely a
SPA-side organisational aid — agents / scheduler / projector
never read it — so it carries no runtime semantics and any
string is allowed (security, weekly, windows, …). Jobs
cross-cut (a check-bitlocker is at once a health-check, a
security control, and Windows-specific), which is why this is
a multi-valued list rather than the single closed-enum
ClientHint::category (whose values are the end-user Client
App’s tabs, a different concern). The operator Jobs page groups
rows by id-prefix for free; tags add the orthogonal filter axis
prefixes can’t express.
Empty by default (the overwhelming majority of jobs), and a
new field, so it follows the #492 wire rule: serde(default)
plus skip_serializing_if keep gradually-upgrading old readers
from tripping over its absence / presence.
origin: Option<RepoOrigin>GitOps provenance (#678) — see RepoOrigin. Stamped by
kanade job create when the source YAML lives inside a Git work
tree, so the SPA can render the job read-only and point edits
back at the repo instead of letting a ClickOps edit silently
diverge from Git (SPEC design principle #3: 設定駆動 YAML + Git).
None for SPA-born jobs and for manifests applied from outside
any Git repo. Purely informational: agents / scheduler /
projector never read it, and it survives script_file: inlining
(it’s orthogonal to the exactly-one-of script-source rule). New
field ⇒ #492 wire rule (default + skip_serializing_if).
finalize: Option<FinalizeSpec>Job-generic post-step hook. When set, the agent runs this script
AFTER the main execute: script exits cleanly (and, for a
collect: job, after the bundle finishes uploading), so the
operator can delete / move / notify based on what the step
produced. Best-effort: a finalize failure is logged but never
fails the run — the upload (if any) already succeeded.
For collect: jobs the agent injects the environment variable
KANADE_COLLECT_RESULT — a JSON object
{ "ok": true, "bundles": [ { "key", "uploaded", "files": [...] } ] }
— so the hook acts on exactly the files that were bundled and
uploaded (e.g. deletes only the uploaded ones). Composes with
every hint. New field ⇒ #492 wire rule (default +
skip_serializing_if).
feed: Vec<FeedSpec>#vuln-roadmap: declarative external-data feeds. Each entry fetches
global reference data (a vulnerability catalog, an EOL table, a license
roster) and projects it into the shared feeds table keyed
(feed_id, item_id) — fleet-wide, with no pc_id, unlike the per-PC
inventory ExplodeSpec. The job’s script (run on the trusted
controller tier) fetches + shapes the data and prints the array under
each spec’s field inside a
#KANADE-FEED-BEGIN/END fence; the projector replaces that feed’s rows
wholesale. A non-empty feed: implies tier: controller (the
dispatch guard treats it as such), so an external fetch never lands on
an employee endpoint. Composes with the other fenced hints. New field ⇒
#492 wire rule (default + skip_serializing_if). See FeedSpec.
tier: Option<Tier>Execution tier (#vuln-roadmap). None / endpoint (default) ⇒ the
job dispatches to the targeted fleet agents like any job. controller
⇒ it may run ONLY on trusted infra hosts — the backend constrains
dispatch to members of the operator-configured controller_group
(server_settings KV), and refuses to run anywhere if that group is
unset (fail-safe). This keeps feed: (external-fetch) and future
privileged hints off employee endpoints. The feed: hint implies
controller; it can also be set explicitly. New field ⇒ #492 wire
rule (default + skip_serializing_if).
Implementations§
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Manifest
impl<'de> Deserialize<'de> for Manifest
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 Manifest
impl JsonSchema for Manifest
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 moreSource§impl StrictSchema for Manifest
Manifest has no #[serde(flatten)] field, so serde_ignored
already catches every top-level typo — the default (None) is
correct.
impl StrictSchema for Manifest
Manifest has no #[serde(flatten)] field, so serde_ignored
already catches every top-level typo — the default (None) is
correct.
Source§fn strict_top_level_keys() -> Option<&'static [&'static str]>
fn strict_top_level_keys() -> Option<&'static [&'static str]>
None when the
type has no flattened field. See the trait docs.