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

nordisk/edda

crates/nornir-build-thing/README.md

nornir-build-thing

The reusable, dependency-light core of nornir's build-thing (task #36): declarative cargo build variants + a content-addressed build-provenance attestation (SLSA-style build-info). Extracted as a leaf crate — mirroring nornir-testmatrix — so a leaf repo's xtask can build a declared variant and capture provenance without nornir's iceberg / arrow / eframe stack.

What a repo declares

A product ships several distinct executables from one source tree. The driving example is holger's three split exes — embedded, minimal, full — each a distinct (package × bin × features × profile × target) point, declared as data in a [build] / [[build.variant]] TOML table (in nornir.toml or a standalone .nornir/build-variants.toml):

[build]
offers = ["embedded", "minimal", "full", "served", "gui"]
exclusive = [["embedded", "minimal", "full"]]   # a build is exactly one fatness

[[build.variant]]
name = "full"
package = "holger-full"
bin = "holger-full"
features = ["full", "served", "gui"]
profile = "release"

The headline call

use nornir_build_thing::{BuildVariantSet, build_variant};

let set = BuildVariantSet::discover(repo_root)?.unwrap_or_default();
let outcome = build_variant(&set, "full", repo_root)?;   // validate → cargo build → capture
println!("{}", outcome.attestation.digest_hex());

build_variant validates the chosen variant against the set's rules, renders the exact cargo build argv, runs it (with --message-format=json so the produced executables are content-hashed), and returns a BuildOutcome carrying the artifacts plus an unsealed provenance Attestation.

Declared parameters and fixtures

[[build.installer.params]] / [[build.iso.params]] declare a cell's install-time inputs (a site name on the cmdline, a hub.conf staged onto the medium); the CLI and the dwarves pane supply them as --param name=value / name=@path. A cell may also declare a fixture the forge mints itself — [build.installer.fixtures] hub_conf = "wg-hub" — filled with --param hub_conf=@fixture; the record says the value came from that fixture. Minting is injected through params::FixtureSource / InstallerComposer::fixtures() (korp-installer's wgfixture is the one implementation), so this crate pulls no WireGuard. See params.rs.

Provenance

A BuildProvenance record captures exactly what a build produced:

record.attest() folds the record to canonical bytes and a SHA-256 digest; a ProvenanceSealer binds a detached seal to that digest. The real, cryptographic sealer is znippy's detached-CMS signer — wired by nornir behind its build-provenance feature (nornir::build::seal); this crate stays dep-light and never fabricates a signature.

Modules

module what
variant the descriptor, validator, and cargo materializer
provenance BuildProvenance, Attestation, the ProvenanceSealer seam, build_variant → BuildOutcome
surface the data-driven UI surface (BuildSurface) a viz tab / CLI --json / MCP tool renders
sink the BuildProvenanceSink warehouse seam + dep-light NullSink / JsonFileSink
elm the build-thing UI component (facett Elm pure core: Model / Msg / update)
kernel_trim the measured kernel trim: kernel what-if <row> --drop CONFIG_X / candidates (static half, over the composed row kernel Kconfig graph), kernel coverage (the DEV gcov probe kernel through the row's suite), kernel veto / vetoes (one veto per run, each with a receipt) — see below
syscall_join the join of measured syscalls with kernel options: kernel needs <row>, and the measured-call veto inside what-if / candidates (see below)
measure the measure step: measure syscalls <row> [--check] runs a row's programs under the EXTERNAL heimdall tool and writes seccomp profiles beside the row (see below)

No heavy deps: VCS state is read via the git CLI and host facts via std + /proc, so there is no gix dependency.

The measured kernel trim: coverage and vetoes

kernel_trim answers what a row's kernel COULD drop (the static half: what-if, candidates, the inverse check). Two more verbs turn a candidate into a row veto, on evidence, one at a time:

The measure step and the licence boundary

measure syscalls <row> [--check] (module measure, Heimdall tooling plan phase 1) runs every program of a row under Heimdall's syscall probe over the runs in .nornir/profiles/<row>/measure.toml, and writes per program <arch>-<libc>.{probe.json,json,oci.json,rs} beside the row plus the evidence measure.json. --check fails on drift, naming the call and the test that made it.

This crate is MIT OR Apache-2.0; Heimdall is AGPL-3.0-only. This crate never links a heimdall crate. It runs the heimdall binary (cargo install nordisk-heimdall-cli --version 0.2.0 --locked) as a separate process and reads the JSON files it writes as data. The row pins the tool by version and sha256; a missing tool, another version or another build is refused in one line that names the install command. The generated .rs names heimdall_seccomp types: only code that already links Heimdall includes it; everything else reads the .json / .oci.json.

The join: measured syscalls and kernel options

Module syscall_join (Heimdall tooling plan step 3) joins what the measure step recorded with the composed row kernel's OWN source tree. Nothing is hand-written:

Limits, stated: Makefile ifdef blocks and #if … || … / #elif branches give no gate, and code paths inside a call (a bpf command that needs CONFIG_CGROUP_BPF) are not split out: the join answers "is the call there at all". Programs that run only inside the booted appliance are not measured yet and are named (vm_later).