Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/Skidbladnir

.nornir/unfold-reasoning.md

unfold — reasoning

Class: reasoning. Why the unfold layer is shaped this way. The descriptive shape is in unfold-design.md.

Why split every backend into pure builders + a drive

Provisioning talks to external systems — a BMC, a libvirt daemon, the terraform binary, qemu — none of which exist in a unit test or a CI box. If the only way to exercise a backend were to run it, none of it would be tested. So each backend is split: the request bodies, config files, and argv are pure functions (testable, deterministic, no tool needed), and unfold() is the thin drive that executes them. This is the same "verbs as data" discipline as the install tree — the plan is an inspectable artifact; running it is a separate, gated step.

Why honest feature stubs instead of always linking

Redfish drags a blocking reqwest client; Terraform shells a binary; both are useless (and unwanted weight) in a lean airgap client that only installs software locally. So they live behind cargo features, and the no-feature build's unfold() returns a clear "needs the redfish/terraform feature" error. We reject the alternative — always compiling them and failing at runtime with an opaque "connection refused" — because an honest, named error at the seam is far better than a mystery failure deep in a network stack the operator did not know was compiled in.

Why a hand-rolled libvirt RPC and not virsh

Shelling virsh would re-introduce exactly the shell-out dependency the whole project exists to remove, and would require virsh installed on the orchestrating host. Instead libvirt is spoken directly: a small pure-Rust XDR/RPC client over TCP:16509 that frames connect_open, domain_define_xml, domain_create, domain_destroy, domain_undefine. The cost is that the procedure numbers are transcribed from libvirt's remote_protocol.x and are only proven against a real daemon in an #[ignore]d heavy test — a deliberate trade: the pure XDR framing is unit-tested exhaustively, the live round-trip is opt-in because it needs a real libvirt host.

Why KVM is "build the argv, bail if you can't boot"

The KVM backend is a local test convenience, not a production provisioner. It always builds the real (tunnr-identical) qemu argv so the argv itself is testable, but if /dev/kvm or qemu is absent it bails honestly rather than faking a boot. This mirrors the repo-wide law: a stub that cannot do the real thing says so, loudly, instead of returning a green lie.

Why teardown is a first-class verb

Every drivable backend has a teardown (KvmTarget stop, LibvirtTarget::teardown, terraform::destroy) so a provisioned target can be cleanly reclaimed — essential for the install matrix (f9), which boots and destroys guests per cell. Provisioning without a symmetric teardown would leak VMs and cloud resources across matrix runs.