<!-- nornir:class: design -->
# Skidbladnir RON orchestration + steering modules (2026-07-26, Rickard)
> Make Skidbladnir usable two ways: **(1) declarative RON** that describes a provisioning flow (an
> interpreter runs it), and **(2) direct/custom** library calls (imperative escape hatch). The RON steps
> dispatch to pluggable **steering modules** packaged/verified in **znippy**.
## Two layers (the difference)
- **RON-triggered (declarative):** a `.ron` describes a sequence of ops; an interpreter executes it.
Same vocabulary, different files:
```ron
// A: [ BootOS(backend: Redfish, os: Ubuntu), InstallApp(spec: "korp") ]
// B: [ BootOS(backend: Redfish, os: Talos), Configure(target: Talos, ...) ]
```
Data-driven, reusable, versionable, shareable, no-code. A new scenario = a new RON, not new Rust.
- **Custom (imperative):** call Skidbladnir's Rust API / a steering module directly for flows the RON
vocabulary doesn't cover. Full power, but code.
*One is the vocabulary; the other is the raw language.* Mirrors `.runa.ron`→jera and rel2graph emit-vs-execute.
## Vocabulary + interpreter
Typed **Step** enum over what Skidbladnir/draupnir already own: `BootOS{backend, os}` (Redfish/KVM/OCI),
`Seed{config}` (NoCloud), `InstallApp{spec}`, `Configure{target, params}`, `Federate{registry}`, …
A **RON interpreter** (like `runa_exec`) maps each Step → a steering module call. The library stays the
imperative core; the RON layer is a thin declarative interpreter on top.
## Steering modules (the execution/plugin model)
Each Step dispatches to a **steering module** = a pluggable capability. The seam is a
**`SteeringModule` trait**; modules are registered by name and **packaged in znippy** (blake3-verified,
self-contained); Skidbladnir **"plucks" the needed module from the znippy** at runtime (reuses the
two-znippy root-of-trust → tamper-evident, offline, appliance-friendly).
### Module kinds — START NATIVE, add WASM later (behind the same trait)
- **Native Rust modules (start here):** `RedfishBoot`, `KvmBoot`, `OciDeploy`, `TalosConfig`,
`InstallApp`, `TerraformApply`. Full/direct I/O, pure-Rust, no ABI/WASM friction. In-tree registry.
- **WASM modules (future):** the real win is a **third-party / language-agnostic / hot-load** module
ecosystem — NOT sandboxing (Skidbladnir is trusted operator infra, low value) and NOT I/O (WASM needs
host-import capabilities `http`/`exec`/`file`/`seed` — real complexity for these I/O-heavy modules).
So don't lead with WASM; slot a `wasm` module-kind behind the SAME `SteeringModule` trait when an open
ecosystem is wanted. Same "one seam, swappable backends" pattern as ContainerBackend / Graph trait /
rel2graph emitters / runa steps.
### Terraform = the "support-all-clouds" bridge
Skidbladnir natively drives Redfish/KVM/OCI (draupnir). A **`TerraformApply`** steering module bridges
*cloud* providers it doesn't natively speak — pragmatic (Terraform isn't pure-Rust, but it's the
meet-every-provider escape, like ContainerBackend supporting non-Rust runtimes). znippy carries the tf
binary + configs so the appliance stays self-contained. (Alternative to stay pure-Rust: native cloud SDKs.)
## Reuse / relation
Skidbladnir already has the imperative core (ServiceSpec + 11 init renderers + Redfish/KVM/OCI via
draupnir + airgap + NoCloud seed + federation). This adds the RON vocabulary + interpreter + the
`SteeringModule` seam + znippy packaging on top. Same RON-as-description language as `.runa.ron` (jera)
and rel2graph's `.runa.ron` emitter — the constellation converges on RON for declarative flows.
## Generalization → mikronova/rover (WASM = Python control, even Mars) 🚀
The engine (declarative flow + `SteeringModule` seam + znippy-packaged modules) is **brandable**. Same
core, different product:
- **Skidbladnir (nordisk):** infra provisioning · RON · **native** modules (trusted operator; I/O-heavy;
sandbox low-value) → native-first.
- **mikronova/rover (own repo/branding):** robotics/edge control · description-language-agnostic (RON OR
Python OR …) · **WASM** modules → WASM-first.
**Why WASM flips from optional→essential for the rover:** the very thing that's *friction* for Skidbladnir
— WASM modules can only call **host-granted capabilities**, not raw I/O — becomes the **SAFETY MODEL**:
- The rover host exposes ONLY safe caps (`move_motor`, `read_sensor`, `take_photo`, `drive`). A rogue/buggy
control module **physically cannot** exceed them — can't brick the rover. Capability-gated = safety-critical.
- **Python (or any language) as the control language** — science team writes Python → compile to WASM →
runs sandboxed on the rover. No Rust needed for control authors.
- **znippy-packaged, blake3-verified, self-contained, offline** → deploy a verified control module to gear
with no network + no toolchain — extreme airgap, up to **Mars**.
So: **same `SteeringModule` trait, module-kind DEFAULT flips per branding** (native ↔ wasm). Build the trait
right once (native for Skidbladnir now), and the WASM/Python-control rover is a branding + a wasm module-kind
+ a capability-interface later — not a rewrite. mikronova/rover = its own repo, sharing this engine.
## Companion docs (the as-built reference)
This file is the original *design capture*. The **verified, as-built** reference for what
actually ships lives in two siblings — read these for ground truth:
- **[`ron-schema.md`](ron-schema.md)** — the full RON object model: every top-level object
(`Flow`, `Step`, `BootBackend`, …), its fields, the deserialize path, and an annotated
end-to-end example.
- **[`custom-objects.md`](custom-objects.md)** — the custom-object / custom-code seam: the
`SteeringModule` trait, the `run_flow` dispatch point, the two-phase INFRA→APP lifecycle,
the 11 service backends beyond systemd, and a worked custom-object example.
## Status
**Phase 1 SHIPPED** (nornir `78deafc`): the `SteeringModule` trait + name→module `Registry`
(`native_default`) + the `run_flow` RON interpreter + native modules — `KvmBoot` and
`InstallApp` **REAL**, the rest honest mocks. Verified in `ron-schema.md` / `custom-objects.md`.
**NOT YET IMPLEMENTED** (still design-only, flagged in the companions): znippy-packaged
modules + runtime "pluck from znippy"; the WASM module-kind + capability-import interface;
a CLI runner (`skidbladnir run flow.ron`). The design listed a `TalosConfig` module; the code
ships a generic `Configure` step/module instead.