Runtime
A control plane that
refuses to guess.
The verifier service is the fleet's source of truth: who is attested, what posture the fleet is in, which artifact every node should be running. Every one of those answers is derived, persisted, and fenced — never assumed.
Posture engine
One worker, coalesced bursts, monotonic generations
Posture recalculation is serialized through a single worker fed by a typed trigger channel. When five sensors fault at once, the worker drains all buffered triggers and recomputes once — no thundering herd, no torn intermediate states.
Cycle-safe DAG traversal
Fleet posture derives from a gray/black two-set traversal over the dependency graph. A cycle locks the
fleet out with CYCLE_DETECTED; a depth blowout locks out with MAX_DEPTH_EXCEEDED.
A locked-out dependency propagates LockedOut upward — never merely Degraded.
Generations survive restarts
The posture generation counter persists to SQLite and is restored at boot, so generation numbers never travel backward across restarts — federation peers depend on that monotonicity, and a conditional-upsert keeps it race-safe on both SQLite and Postgres.
Structured lockout reasons
Fail-closed decisions carry typed reasons — DagLockedOut, PostureCacheStale — not
strings. The cache snapshot is atomic: posture, generation, timestamp, and TTL move together.
High availability
Failover without split brain
Heartbeats detect failure; they never grant authority. Authority is a durable epoch claimed by compare-and-swap, and a revived old primary is fenced — its writes refused — the moment a standby has claimed the next epoch. The whole drill runs as a deterministic test in CI, plus a real two-process drill.
Lease-based failover (≤5 s, EP-03) is opt-in behind KIRRA_HA_LEASE_ENABLED; the epoch CAS remains the sole takeover authority either way.
tests/ha_failover.rstests/ha_two_process_drill.rssrc/lease.rssrc/standby_monitor.rsverification/kani/src/proofs_lease.rs
Persistence
Disk before memory, always
WAL-mode SQLite with a strict ordering invariant: state is saved to disk before it is inserted into memory, so a crash can lose an in-flight request but never invent one. Schema migrations are versioned and refuse to open a database written by a newer binary.
Kill-tested durability
A CI drill SIGKILLs a child process mid-append and asserts the audit chain reopens as a valid, intact prefix — an abrupt power loss can never leave a torn or forked chain.
tests/audit_chain_prefix_on_kill.rstests/audit_row_durability_on_abort.rs
Backend-agnostic contracts
Storage is a family of traits — epoch fence, node store, federation, posture state, OTA campaigns — and the same conformance suites run against SQLite, in-memory, and a live Postgres 16 service container in CI. Corrupt rows decode fail-closed on every backend.
Config that fails at boot
Every KIRRA_* variable lives in one canonical registry; unknown vars warn at startup, and a
malformed value in a migrated var aborts boot rather than silently defaulting at use. The effective
config's SHA-256 digest is committed to the audit chain, so drift is detectable.
Fleet rollout
OTA that halts before it hurts
Governor artifacts roll out through staged campaigns with a hard rule:
advance is fail-closed on fleet posture. If the fleet is anything but Nominal, a campaign
halts — it never rolls. A background sweep auto-halts active campaigns on a confirmed regression
between advances.
Dual-slot A/B installs
Node-side, a device-agnostic installer stages, trials, and health-gates every artifact — automatic
rollback on failed probes — with both app-level slots and the Jetson bootloader's native A/B slots via
nvbootctrl. Verified end-to-end by a two-node rollout harness in CI.
crates/kirra-ota-installertests/two_node_rollout.rscrates/kirra-ota-installer/src/nvbootctrl.rs
Uptane, end to end
An anchored node verifies the full signed metadata set — root-keyed role signatures, freshness, rollback floors — before it downloads anything. A re-served older set (rollback attack) or a stripped set (downgrade-by-omission) is refused. The verifier is just an untrusted carrier.
Attested adoption
Nodes report the digest they're actually running; reports can be Ed25519-signed against the node's registered attestation key, making adoption counts unforgeable. Campaign lifecycle mutations are audit-chained.
Execution model
Supervised loops, declared as data
The runtime's seven supervised loops — watchdogs, monitors, shippers — are declared in a task manifest with dependencies, criticality, and deadlines. Startup order is a topological sort that fails closed on a cycle, a missing dependency, or a duplicate.
| Loop | Cadence | Fail-closed behavior |
|---|---|---|
| Telemetry watchdog | 100 ms sweep | 2 s of sensor silence ⇒ node faulted, posture recomputed |
| Campaign monitor | 1 s sweep | Confirmed posture regression ⇒ active campaigns auto-halt |
| Heartbeat writer | 2 s | Primary silence ⇒ standby promotes via epoch CAS |
| Audit shipper | 5 s | Ship-then-advance cursor; at-least-once to WORM sink |
| Cert-expiry monitor | 1 h census | Lapsed mTLS principal ⇒ warn + audit (auth already fail-closes) |
src/execution_manager.rssrc/telemetry_watchdog.rssrc/campaign_monitor.rssrc/audit_shipper.rssrc/cert_expiry_monitor.rs
READ THE SOURCE
Run it yourself in five minutes.
docker compose up brings up the verifier, dashboard, and an optional HA standby — then kill the primary and watch the fence hold.