tests/testmatrix_rows.rs
//! **Test-matrix verdict rows for Skíðblaðnir's real surfaces** — this is
//! Skidbladnir's entry into nornir's constellation-wide introspection-coverage
//! gate (nornir #32). Every check asserts on the *actual* return value AND records
//! the verdict as one functional-status row.
//!
//! This follows the ordning-core / korp-collectors reference pattern. With
//! `--features testmatrix` each emit becomes a real
//! `nornir_testmatrix::functional_status` row (nornir's matrix reads them back);
//! without the feature `skidbladnir::functional_status` is a compiled-out
//! `#[inline]` no-op (no nornir dep), so this file is a plain assert-only test in
//! the default build. The `assert!` is the gate; the emit is the observation
//! (nornir's `assert_emit!` doctrine).
//!
//! HONEST STATUS — green is only for what genuinely works offline and is asserted
//! here. Surfaces that need a live host (systemd D-Bus, Redfish BMC), shell out
//! (Terraform `apply`), are unverified against a real peer (the hand-rolled
//! libvirt RPC), need a hypervisor host (KVM boot), or are declared-but-unrendered
//! (the remaining non-systemd/OpenRC/SysV init renderers) are emitted as PARTIAL rows
//! (ok=false) with an honest detail — never a fake green.
use std::path::{Path, PathBuf};
use skidbladnir::archive::{Archive, TarZstd};
use skidbladnir::install::{self, InstallOp};
use skidbladnir::layout::Layout;
use skidbladnir::product::ComponentSpec;
use skidbladnir::service::{self, Backend};
/// Assert on the real value AND record a GREEN matrix row under the component
/// `skidbladnir/<surface>`. Mirrors nornir's `assert_emit!` — the `assert!` gates
/// the build, the emit is the observation. Only for what genuinely works.
macro_rules! assert_emit {
($surface:expr, $check:expr, $ok:expr, $($detail:tt)+) => {{
let ok: bool = $ok;
let detail = format!($($detail)+);
skidbladnir::functional_status(
concat!("skidbladnir/", $surface),
$check,
ok,
&detail,
);
assert!(ok, "{}::{} — {}", $surface, $check, detail);
}};
}
/// Record a PARTIAL/scaffold row (ok=false) WITHOUT failing the test — the surface
/// exists but cannot be honestly greened in the offline gate (needs a live host,
/// shells out, is an unverified hand-rolled wire path, or is declared-not-rendered).
/// The row IS the observation: "present, but not green, and we say so."
macro_rules! emit_partial {
($surface:expr, $check:expr, $($detail:tt)+) => {{
let detail = format!($($detail)+);
skidbladnir::functional_status(
concat!("skidbladnir/", $surface),
$check,
false,
&detail,
);
}};
}
// ───────────────────────────── fixtures ─────────────────────────────
/// A tiny product archive fixture: a staging tree with the product binary at its
/// `bundle_binary` path (+ a payload file) packed via the default `TarZstd` engine.
fn product_bundle(spec: &ComponentSpec) -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().unwrap();
let staging = dir.path().join("staging");
let bin = staging.join(&spec.bundle_binary);
std::fs::create_dir_all(bin.parent().unwrap()).unwrap();
std::fs::write(&bin, b"#!/fake product binary bytes\n").unwrap();
std::fs::write(staging.join("README"), b"product tree\n").unwrap();
let bundle = dir.path().join("bundle.tar.zst");
TarZstd::new().pack(&staging, "20250505", &bundle).unwrap();
(dir, bundle)
}
fn layout_in(tmp: &Path, version: &str) -> Layout {
Layout {
data_dir: tmp.join("data"),
log_dir: tmp.join("log"),
software_dir: tmp.join("software"),
version: version.into(),
}
}
// ───────────────────────────── install / apply ─────────────────────────────
#[test]
fn install_product_is_honest_and_dbus_routed() {
let spec = ComponentSpec::holger();
let (_guard, bundle) = product_bundle(&spec);
let tmp = tempfile::tempdir().unwrap();
let layout = layout_in(tmp.path(), "20250505");
let root = tmp.path().join("root");
let out = install::install_product(
&spec,
&layout,
&install::UpgradeSource::bundle(&bundle).unwrap(),
&TarZstd::new(),
Backend::SystemdSystem,
&root,
)
.unwrap();
// The lifecycle is pure data: op, the three provisioned dirs, a minted identity,
// written unit files, and the ordered [enable, start] verbs — all assertable
// offline without running a single privileged command.
assert_emit!(
"install",
"install_product_yields_enable_then_start_and_identity",
out.op == InstallOp::Install
&& out.dirs.len() == 3
&& out.identity.is_some()
&& !out.files_written.is_empty()
&& out.verbs.len() == 2
&& out.verbs[0].contains(&"enable".to_string())
&& out.verbs[1].contains(&"start".to_string()),
"op={:?} dirs={} identity={} files={} verbs={:?}",
out.op,
out.dirs.len(),
out.identity.is_some(),
out.files_written.len(),
out.verbs
);
// The current symlink now resolves to the installed generation.
assert_emit!(
"install",
"activate_points_current_at_installed_generation",
layout.active_version().as_deref() == Some("20250505"),
"active={:?}",
layout.active_version()
);
// TRANSPORT (partial): `install::apply` routes the systemd backends' verbs
// through the pure-Rust zbus D-Bus controller (org.freedesktop.systemd1), NOT a
// `systemctl` subprocess. It needs a live system/session bus + privilege, so the
// dispatch is not exercised in the offline gate — honestly partial, not green.
emit_partial!(
"install",
"apply_routes_systemd_verbs_over_dbus",
"apply() dispatches enable/start/stop over zbus org.freedesktop.systemd1; \
needs a live bus + root, not exercised offline"
);
}
// ───────────────────────────── rollback ─────────────────────────────
#[test]
fn rollback_product_swaps_back_a_generation() {
let spec = ComponentSpec::holger();
let (_guard, bundle) = product_bundle(&spec);
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("root");
// Gen A (install) then Gen B (update) — B is active, A is retained.
let a = layout_in(tmp.path(), "20250505");
install::install_product(
&spec,
&a,
&install::UpgradeSource::bundle(&bundle).unwrap(),
&TarZstd::new(),
Backend::SystemdSystem,
&root,
)
.unwrap();
let b = Layout {
version: "20250606".into(),
..a.clone()
};
install::update_product(
&spec,
&b,
&install::UpgradeSource::bundle(&bundle).unwrap(),
&TarZstd::new(),
Backend::SystemdSystem,
&root,
)
.unwrap();
assert_eq!(b.active_version().as_deref(), Some("20250606"));
let out = install::rollback_product(&spec, &b, Backend::SystemdSystem, &root).unwrap();
// Rollback finds the prior generation, swaps `current` back atomically, and
// returns a restart (stop-then-start) — all honest data.
assert_emit!(
"rollback",
"rollback_product_restores_prior_generation",
out.op == InstallOp::Rollback
&& out.version == "20250505"
&& b.active_version().as_deref() == Some("20250505")
&& out.verbs.len() == 2
&& out.verbs[0].contains(&"stop".to_string())
&& out.verbs[1].contains(&"start".to_string()),
"op={:?} version={} active={:?} verbs={:?}",
out.op,
out.version,
b.active_version(),
out.verbs
);
}
// ───────────────────────────── layout ─────────────────────────────
#[test]
fn layout_previous_generation_and_activate_version() {
let tmp = tempfile::tempdir().unwrap();
let sw = tmp.path().join("software");
let a = Layout {
data_dir: tmp.path().join("data"),
log_dir: tmp.path().join("log"),
software_dir: sw.clone(),
version: "20250505".into(),
};
// Each generation must hold real software: `prepare_product` alone leaves the
// empty husk a failed install leaves behind, and `activate`/`previous_generation`
// now refuse to promote one. This test is about the SWAP, so give it real gens.
a.prepare_product("holger").unwrap();
std::fs::write(a.product_software("holger").join("holger"), b"a").unwrap();
a.activate().unwrap();
let b = Layout {
version: "20250606".into(),
..a.clone()
};
b.prepare_product("holger").unwrap();
std::fs::write(b.product_software("holger").join("holger"), b"b").unwrap();
b.activate().unwrap();
assert_emit!(
"layout",
"previous_generation_picks_older_retained_gen",
b.previous_generation("holger").as_deref() == Some("20250505")
&& b.previous_generation("ghost").is_none(),
"prev(holger)={:?} prev(ghost)={:?}",
b.previous_generation("holger"),
b.previous_generation("ghost")
);
b.activate_version("20250505").unwrap();
assert_emit!(
"layout",
"activate_version_swaps_current_and_guards_missing",
b.active_version().as_deref() == Some("20250505")
&& b.activate_version("20991231").is_err()
&& b.active_version().as_deref() == Some("20250505"),
"after swap-back active={:?}, missing-gen refused",
b.active_version()
);
}
// ───────────────────── deploy backends: renderers ─────────────────────
#[test]
fn service_renderers_systemd_and_openrc_are_real() {
let tmp = tempfile::tempdir().unwrap();
let layout = layout_in(tmp.path(), "20250505");
let spec = ComponentSpec::holger();
// systemd (system scope): real unit + systemctl enable/start verbs.
let sysd = service::renderer_for(Backend::SystemdSystem).expect("systemd renderer exists");
let r = sysd.render(&spec.to_service_spec(&layout, Backend::SystemdSystem)).unwrap();
assert_emit!(
"backend-systemd",
"systemd_renderer_emits_unit_and_verbs",
r.files.len() == 1
&& r.files[0].contents.contains("[Service]")
&& r.files[0].contents.contains("ExecStart=")
&& r.enable.contains(&"enable".to_string())
&& r.start.contains(&"start".to_string()),
"unit_path={} enable={:?} start={:?}",
r.files[0].path,
r.enable,
r.start
);
// OpenRC: real `#!/sbin/openrc-run` script + rc verbs.
let openrc = service::renderer_for(Backend::OpenRc).expect("openrc renderer exists");
let ro = openrc.render(&spec.to_service_spec(&layout, Backend::OpenRc)).unwrap();
assert_emit!(
"backend-openrc",
"openrc_renderer_emits_openrc_run_script",
!ro.files.is_empty()
&& ro.files[0].contents.starts_with("#!/sbin/openrc-run")
&& ro.files[0].contents.contains("command=")
&& !ro.enable.is_empty()
&& !ro.start.is_empty(),
"script_path={} enable={:?}",
ro.files[0].path,
ro.enable
);
// SysV (the universal fallback): real LSB `/etc/init.d/<name>` script with an
// init-info header, `start-stop-daemon` verbs + the script-driven lifecycle.
let sysv = service::renderer_for(Backend::SysV).expect("sysv renderer exists");
let rv = sysv.render(&spec.to_service_spec(&layout, Backend::SysV)).unwrap();
assert_emit!(
"backend-sysv",
"sysv_renderer_emits_lsb_initd_script",
!rv.files.is_empty()
&& rv.files[0].contents.starts_with("#!/bin/sh")
&& rv.files[0].contents.contains("### BEGIN INIT INFO")
&& rv.files[0].contents.contains("start-stop-daemon --start")
&& !rv.enable.is_empty()
&& !rv.start.is_empty(),
"script_path={} enable={:?}",
rv.files[0].path,
rv.enable
);
// runit: a real service DIRECTORY — `/etc/sv/<name>/run` execs under runsv, plus
// the `ln`-into-runsvdir enable verb and `sv up` start verb.
let runit = service::renderer_for(Backend::Runit).expect("runit renderer exists");
let rr = runit.render(&spec.to_service_spec(&layout, Backend::Runit)).unwrap();
let run = rr.file_ending("/run").expect("a runit run script");
assert_emit!(
"backend-runit",
"runit_renderer_emits_runsv_service_dir",
run.starts_with("#!/bin/sh")
&& run.contains("exec ")
&& rr
.files
.iter()
.any(|f| f.path.ends_with("/run") && f.path.contains("/sv/"))
&& rr.enable.first().map(|s| s == "ln").unwrap_or(false)
&& rr.start.first().map(|s| s == "sv").unwrap_or(false),
"run_path={} enable={:?} start={:?}",
rr.files[0].path,
rr.enable,
rr.start
);
// s6-rc: a real compiled source-definition DIR — `type = longrun`, a `run` script
// that chainloads under s6-supervise, plus the `s6-rc-bundle-update` enable verb and
// `s6-rc … change` start verb.
let s6rc = service::renderer_for(Backend::S6Rc).expect("s6-rc renderer exists");
let rs = s6rc.render(&spec.to_service_spec(&layout, Backend::S6Rc)).unwrap();
let s6run = rs.file_ending("/run").expect("an s6-rc run script");
assert_emit!(
"backend-s6rc",
"s6rc_renderer_emits_source_definition_dir",
rs.file_ending("/type")
.map(|t| t == "longrun\n")
.unwrap_or(false)
&& s6run.starts_with("#!/bin/sh")
&& s6run.contains("exec ")
&& rs
.files
.iter()
.any(|f| f.path.ends_with("/run") && f.path.contains("/s6-rc/source/"))
&& rs
.enable
.first()
.map(|s| s == "s6-rc-bundle-update")
.unwrap_or(false)
&& rs.start.first().map(|s| s == "s6-rc").unwrap_or(false),
"run_path={} enable={:?} start={:?}",
s6run,
rs.enable,
rs.start
);
// OCI: a real `podman run` argv — `-d --name`, `--restart`, and the
// `--entrypoint <exec>` … image split, plus the `podman stop` verb.
let oci = service::renderer_for(Backend::Oci).expect("oci renderer exists");
let ro = oci.render(&spec.to_service_spec(&layout, Backend::Oci)).unwrap();
assert_emit!(
"backend-oci",
"oci_renderer_emits_podman_run_argv",
ro.start.first().map(|s| s == "podman").unwrap_or(false)
&& ro.start.iter().any(|s| s == "run")
&& ro.start.iter().any(|s| s == "--entrypoint")
&& ro.start.iter().any(|s| s.starts_with("--restart="))
&& ro.stop.first().map(|s| s == "podman").unwrap_or(false),
"start={:?} stop={:?}",
ro.start,
ro.stop
);
// Quadlet: a real systemd `.container` unit — `[Container] Image=`/`Exec=`, a
// `[Install] WantedBy=` target, and the `systemctl daemon-reload` (enable) +
// `systemctl start <name>.service` verbs the `podman-systemd` generator needs.
let quadlet = service::renderer_for(Backend::Quadlet).expect("quadlet renderer exists");
let rq = quadlet.render(&spec.to_service_spec(&layout, Backend::Quadlet)).unwrap();
let cunit = rq.file_ending(".container").expect("a .container unit");
assert_emit!(
"backend-quadlet",
"quadlet_renderer_emits_container_unit_and_verbs",
cunit.contains("[Container]")
&& cunit.contains("Image=")
&& cunit.contains("Exec=")
&& cunit.contains("WantedBy=")
&& rq.enable == vec!["systemctl".to_string(), "daemon-reload".to_string()]
&& rq.start.iter().any(|s| s == "start")
&& rq
.start
.last()
.map(|s| s.ends_with(".service"))
.unwrap_or(false),
"unit_path={} enable={:?} start={:?}",
rq.files[0].path,
rq.enable,
rq.start
);
// Windows service (SCM): a real WiX `.wxs` service fragment (via the shared MSI
// seam) + `sc.exe` create/start/query/stop verbs — the 11th and final backend.
let win = service::renderer_for(Backend::WindowsService).expect("windows renderer exists");
let rw = win.render(&spec.to_service_spec(&layout, Backend::WindowsService)).unwrap();
let wxs = rw.file_ending(".wxs").expect("a .wxs service fragment");
assert_emit!(
"backend-windows",
"windows_renderer_emits_wix_fragment_and_sc_verbs",
wxs.contains("<ServiceInstall")
&& wxs.contains("Start=\"auto\"")
&& wxs.contains("<ServiceControl")
&& rw.enable.first().map(|s| s == "sc.exe").unwrap_or(false)
&& rw.enable.iter().any(|s| s == "create")
&& rw.start
== vec![
"sc.exe".to_string(),
"start".to_string(),
"holger".to_string()
],
"wxs_path={} enable={:?} start={:?}",
rw.files[0].path,
rw.enable,
rw.start
);
}
#[test]
fn every_declared_backend_renders_full_coverage() {
// The coverage axis is now COMPLETE: every backend `Backend::all()` declares
// resolves to a real renderer whose `backend()` matches its lookup key — 11/11,
// no honest-`None` implementation debt remains.
for b in Backend::all() {
let r = service::renderer_for(*b);
assert!(
r.is_some(),
"{} must have a renderer (full coverage)",
b.slug()
);
assert_eq!(
r.unwrap().backend(),
*b,
"{}: renderer reports a different backend",
b.slug()
);
}
let implemented = Backend::all()
.iter()
.filter(|b| service::renderer_for(**b).is_some())
.count();
assert_emit!(
"backend-init-renderers",
"renderer_coverage_is_complete",
Backend::all().len() == 11 && implemented == 11,
"{}/{} backends have a renderer (systemd-system, systemd-user, openrc, sysv, runit, s6-rc, dinit, kubernetes, oci, quadlet, windows-service)",
implemented,
Backend::all().len()
);
}
#[test]
fn common_infra_bringup_is_wave_ordered_and_renders() {
use skidbladnir::product::{common_infra, common_infra_waves};
// TIER-0.5 keystone: the app-agnostic common-infra set brings up in dependency
// WAVES via the ONE wave engine, and each infra spec renders to real k8s manifests
// through the ONE k8s renderer (the pure renderer half; live apply is deferred).
let waves = common_infra_waves().expect("no dependency cycle");
let tmp = tempfile::tempdir().unwrap();
let layout = layout_in(tmp.path(), "20250505");
// Render the OpenBao infra spec → a StatefulSet (stateful) in its own namespace.
let bao = common_infra()
.into_iter()
.find(|c| c.name == "openbao")
.unwrap();
let r = service::renderer_for(Backend::Kubernetes)
.unwrap()
.render(&bao.to_service_spec(&layout, Backend::Kubernetes)).unwrap();
let ss = r
.file_ending("openbao-statefulset.yaml")
.expect("an OpenBao StatefulSet");
assert_emit!(
"tier0-common-infra",
"bringup_waves_ordered_and_specs_render_k8s",
waves
== vec![
vec!["cert-manager".to_string(), "ingress-nginx".to_string()],
vec!["holger-registry".to_string(), "openbao".to_string()],
]
&& ss.contains("kind: \"StatefulSet\"")
&& ss.contains("namespace: \"openbao\"")
&& ss.contains("image: \"ghcr.io/openbao/openbao:2.0.3\""),
"waves={:?} openbao_manifest_bytes={}",
waves,
ss.len()
);
}
#[test]
fn ingress_nginx_renders_the_full_production_object_set() {
use skidbladnir::infra::ingress_nginx_objects;
// TIER-0.5 renderer depth: ingress-nginx renders its COMPLETE production object
// graph (not an MVP single Deployment) — SA + RBAC (Cluster/Role + both bindings)
// + ConfigMap + Deployment + LoadBalancer Service + IngressClass, all pure/offline.
let objs = ingress_nginx_objects();
let has = |kind: &str| {
objs.iter()
.any(|f| f.contents.contains(&format!("kind: {kind}\n")))
};
let all_kinds = [
"ServiceAccount",
"ConfigMap",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Deployment",
"Service",
"IngressClass",
// admission webhook set
"ValidatingWebhookConfiguration",
"Job",
];
let deploy = objs
.iter()
.find(|f| f.path.ends_with("-deployment.yaml"))
.map(|f| f.contents.as_str())
.unwrap_or("");
let vwc = objs
.iter()
.find(|f| {
f.path
.ends_with("admission-validatingwebhookconfiguration.yaml")
})
.map(|f| f.contents.as_str())
.unwrap_or("");
assert_emit!(
"tier0-ingress-nginx",
"full_production_object_set_with_admission_renders",
objs.len() == 18
&& all_kinds.iter().all(|k| has(k))
&& deploy.contains("serviceAccountName: ingress-nginx")
&& objs
.iter()
.find(|f| f.path.ends_with("ingress-nginx-service.yaml"))
.unwrap()
.contents
.contains("type: LoadBalancer")
&& vwc.contains("name: ingress-nginx-controller-admission"),
"objects={} kinds_present={}",
objs.len(),
all_kinds.iter().filter(|k| has(k)).count()
);
}
#[test]
fn cert_manager_renders_its_full_three_component_graph() {
use skidbladnir::infra::cert_manager_objects;
// TIER-0.5 renderer depth (symmetric with ingress-nginx): cert-manager renders its
// three-component production graph (controller + webhook + cainjector) with full
// RBAC + webhook configs + CRD registrations (schemas DEFERRED), pure/offline.
let objs = cert_manager_objects();
let n = |slug: &str| {
objs.iter()
.find(|f| f.path.ends_with(&format!("cert-manager-{slug}.yaml")))
.map(|f| f.contents.as_str())
.unwrap_or("")
};
let three_deploys_run_as_their_sa = [
("controller", "cert-manager"),
("webhook", "cert-manager-webhook"),
("cainjector", "cert-manager-cainjector"),
]
.iter()
.all(|(c, sa)| n(&format!("{c}-deployment")).contains(&format!("serviceAccountName: {sa}")));
let crds_deferred = [
"certificates",
"issuers",
"clusterissuers",
"orders",
"challenges",
"certificaterequests",
]
.iter()
.all(|p| n(&format!("crd-{p}")).contains("x-kubernetes-preserve-unknown-fields: true"));
assert_emit!(
"tier0-cert-manager",
"full_three_component_graph_renders_crds_deferred",
objs.len() == 28
&& three_deploys_run_as_their_sa
&& crds_deferred
&& n("webhook-validatingwebhookconfiguration")
.contains("kind: ValidatingWebhookConfiguration")
&& n("webhook-mutatingwebhookconfiguration")
.contains("kind: MutatingWebhookConfiguration"),
"objects={} deploys_bound={} crds_deferred={}",
objs.len(),
three_deploys_run_as_their_sa,
crds_deferred
);
}
/// The ONE declared `HealthProbe` that drives the host `Lifecycle::health()` also
/// projects onto the k8s render as `readinessProbe` + `livenessProbe` (one knob,
/// two views — L5). A `ComponentSpec` with an HTTP health check, rendered for
/// `Backend::Kubernetes`, must carry an `httpGet` probe on its declared port.
#[test]
fn k8s_render_projects_health_probe_onto_readiness_and_liveness() {
use skidbladnir::product::HealthCheck;
use skidbladnir::service::{HealthProbe, ServiceSpec, Socket};
// Direct neutral-spec path (Tcp): both probes render tcpSocket on the port.
let tcp = ServiceSpec::new("web", "w", "/unused")
.image("img:1")
.namespace("ns")
.socket(Socket::tcp(7070))
.health(HealthProbe::Tcp(7070));
let tcp_dep = service::renderer_for(Backend::Kubernetes)
.unwrap()
.render(&tcp).unwrap()
.file_ending("web-deployment.yaml")
.unwrap()
.to_string();
// ComponentSpec path (Http): the AIRGAP6 HealthCheck flows through to the k8s probe.
let comp = ComponentSpec::new("api", "linux-x86_64/api", "apiuser")
.with_health(HealthCheck::Http("/healthz".into()))
.with_image("img:1")
.with_port(8080);
let comp_spec = comp.to_service_spec(
&layout_in(Path::new("/srv"), "20250505"),
Backend::Kubernetes,
);
let comp_dep = service::renderer_for(Backend::Kubernetes)
.unwrap()
.render(&comp_spec).unwrap()
.file_ending("api-deployment.yaml")
.unwrap()
.to_string();
assert_emit!(
"k8s-health-probe",
"health_probe_projects_to_readiness_and_liveness",
tcp_dep.matches("tcpSocket:").count() == 2
&& tcp_dep.contains("port: 7070")
&& comp_dep.matches("httpGet:").count() == 2
&& comp_dep.contains("path: \"/healthz\"")
&& comp_dep.contains("port: 8080"),
"tcp_sockets={} comp_httpget={} comp_has_path={}",
tcp_dep.matches("tcpSocket:").count(),
comp_dep.matches("httpGet:").count(),
comp_dep.contains("path: \"/healthz\"")
);
}
// ───────────────── deploy backends: KVM / Redfish / Terraform / Libvirt ─────────────────
#[test]
fn kvm_qemu_argv_is_real_boot_is_partial() {
use skidbladnir::unfold::kvm::KvmTarget;
let t = KvmTarget::new("skid-test", "/img/appliance.qcow2");
let argv = t.qemu_argv();
assert_emit!(
"backend-kvm",
"qemu_argv_is_q35_kvm_launch",
argv.windows(2)
.any(|w| w[0] == "-machine" && w[1] == "q35,accel=kvm:tcg")
&& argv.iter().any(|a| a.contains("/img/appliance.qcow2")),
"{} argv tokens, q35/accel=kvm:tcg present",
argv.len()
);
// The actual boot (and the live multi-OS matrix runner) needs a qemu/KVM host
// and is a `#[ignore]`d heavy test — the argv is the real artifact, the spawn is
// not exercised in the gate.
emit_partial!(
"backend-kvm",
"kvm_boot_and_matrix_runner_need_host",
"qemu_argv is the tested artifact; boot()/matrix runner need a qemu/KVM host (heavy #[ignore] live test)"
);
}
/// The jera routing seam (feature `jera`): the `machine` module's container/VM
/// spawns route through jera (edda), the ONE job/container/VM manager, instead of raw
/// `podman`/`qemu`. A `MachineSpec` classifies into a `jera::JobPolicy` and a container
/// spec maps onto a `jera::container::ContainerSpec` → a VALID `draupnir::BootSpec`
/// (asserted daemon-free). The AIRGAP live-route is LANDED: `NetMode::Isolated` maps
/// onto jera `NetMode::None` → draupnir `--network none` (egress isolation reaches the
/// live knob); bind mounts carry too. The remaining honest gap (an arbitrary named
/// host-network; the VM kernel-less disk-boot) keeps the direct fallback for that case.
#[cfg(feature = "jera")]
#[test]
fn machine_spawn_maps_onto_a_jera_job_spec() {
use skidbladnir::machine::{ImageRef, MachineSpec, NetMode};
// A NAT container classifies as a jera Container job and maps to a valid spec.
let c = MachineSpec::container("holger-c", "git.example.com/nordisk/holger:latest");
let js = c
.to_jera_container_spec()
.expect("container maps to a jera ContainerSpec");
let bootspec_ok = {
let d = js.to_draupnir();
d.validate().is_ok() && format!("{d:?}").contains("git.example.com/nordisk/holger:latest")
};
assert_emit!(
"machine-jera",
"container_spawn_maps_to_jera_job_spec_and_valid_draupnir_bootspec",
c.job_policy().as_str() == "container"
&& js.image == "git.example.com/nordisk/holger:latest"
&& bootspec_ok
&& c.jera_routable(),
"policy={} image={} routable={}",
c.job_policy().as_str(),
js.image,
c.jera_routable()
);
// A VM disk classifies as a jera Vm job (routes through jera's vm seam) and maps
// onto jera's VM payload (jera::vm::BootSpec) + the serde agent VmBoot carrying
// name/disk/size — the mirror of the container spec-mapper.
let v = MachineSpec::vm("holger-vm", "/img/base.qcow2").resources(4, 4096);
let vm = v.to_jera_vm_spec().expect("vm maps onto a jera BootSpec");
let payload = v
.to_jera_vm_payload()
.expect("vm maps onto the agent VmBoot");
assert_emit!(
"machine-jera",
"vm_disk_classifies_as_jera_vm_job_and_maps_onto_the_jera_vm_payload",
v.job_policy().as_str() == "vm"
&& v.to_jera_container_spec().is_err()
&& matches!(v.image, ImageRef::Path(_))
&& vm.disk == "/img/base.qcow2"
&& vm.mem_mb == 4096
&& vm.cores == 4
&& payload.image == "/img/base.qcow2"
&& payload.mem_mb == 4096,
"vm disk -> JobPolicy::Vm, refused by container seam, maps onto jera BootSpec disk={} + VmBoot",
vm.disk
);
// AIRGAP LIVE-ROUTE (LANDED): an airgap container now routes through jera —
// `NetMode::Isolated` maps onto jera `NetMode::None`, threaded onward onto
// draupnir's `HostConfig.network_mode = "none"` (a real `--network none` egress
// isolated run). The net=None knob is reached; a bind mount likewise carries.
let iso = MachineSpec::container("c", "alpine:3").net(NetMode::Isolated);
let iso_js = iso
.to_jera_container_spec()
.expect("airgap container maps to a jera spec");
let iso_none = format!("{:?}", iso_js.net) == "None"; // jera::container::NetMode::None
let iso_draupnir_none = format!("{:?}", iso_js.to_draupnir()).contains("net: None");
assert_emit!(
"machine-jera",
"airgap_container_routes_through_jera_with_network_none",
iso.jera_routable() && iso_none && iso_draupnir_none && iso.jera_unsupported().is_empty(),
"airgap routable={} jera_net={:?} draupnir_network_none={} (Skidbladnir→jera→draupnir --network none)",
iso.jera_routable(),
iso_js.net,
iso_draupnir_none
);
// Remaining HONEST gap: an arbitrary NAMED host-network — jera's NetMode is
// Default/None/Host/Bridge, not an arbitrary name — keeps the direct podman path.
let named = MachineSpec::container("c", "alpine:3").net(NetMode::Named("br0".into()));
emit_partial!(
"machine-jera",
"named_network_container_awaits_jera_named_net_knob",
"named-net container gap={:?} routable={} (jera has no arbitrary named net) — direct podman fallback kept",
named.jera_unsupported(),
named.jera_routable()
);
// HONEST gap (VM): a Skidbladnir VM is a kernel-less bootloader-in-disk boot, which
// jera/draupnir's kernel-required VM payload cannot express — so `disk-boot` is
// always in the gap and NO vm is routable through jera today; the direct qemu path
// stays. Airgap net + a cloud-init seed stack further gaps (tripwire flips when jera
// grows a kernel-less disk boot + net + cloud-init).
let airvm = MachineSpec::vm("v", "/img/base.qcow2")
.net(NetMode::Isolated)
.provision(skidbladnir::machine::Provision::CloudInit {
user_data: "#cloud-config\n".into(),
meta_data: None,
});
emit_partial!(
"machine-jera",
"vm_awaits_jera_kernelless_disk_boot_net_and_cloud_init_knobs",
"vm gap={:?} routable={} (jera VM payload has no bootloader-in-disk/net/cloud-init) — live route deferred, direct qemu fallback kept",
airvm.jera_vm_unsupported(),
airvm.jera_vm_routable()
);
}
#[test]
fn redfish_bodies_and_paths_are_real_rest_is_partial() {
use skidbladnir::unfold::redfish::{PowerAction, power_body, reset_action_path, system_path};
let body = power_body(PowerAction::On);
assert_emit!(
"backend-redfish",
"reset_body_and_paths_are_canonical_redfish",
body["ResetType"] == "On"
&& system_path("1") == "/redfish/v1/Systems/1"
&& reset_action_path("1") == "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset",
"power_body={} system_path={}",
body,
system_path("1")
);
// The live REST calls (feature `redfish`) drive a real BMC over HTTPS — not
// reachable in the offline gate.
emit_partial!(
"backend-redfish",
"redfish_rest_client_needs_live_bmc",
"body/path builders are pure+tested; RedfishClient power/boot/virtual-media need a live BMC (feature `redfish`)"
);
}
#[test]
fn terraform_hcl_renders_but_apply_is_shellout() {
use skidbladnir::unfold::terraform::{TerraformTarget, render_config, render_resource};
let t = TerraformTarget::new("fleet", "holger").count(3);
let hcl = render_config(&t);
// HCL RENDER is pure + verifiable → green.
assert_emit!(
"backend-terraform",
"hcl_render_is_valid_nornir_component",
hcl.contains("required_providers")
&& hcl.contains("nordisk/nornir")
&& render_resource(&t).contains("resource \"nornir_component\" \"holger\"")
&& hcl.contains("count = 3"),
"rendered {} bytes of HCL",
hcl.len()
);
// The APPLY is a `terraform` binary shellout (feature `terraform`, std::process)
// — NOT green: it needs the terraform CLI + a real provider/backend.
emit_partial!(
"backend-terraform",
"terraform_apply_is_binary_shellout",
"render is pure; unfold() shells `terraform init/plan/apply` (feature `terraform`) — needs the CLI, not verified in-gate"
);
}
#[test]
fn libvirt_domain_xml_renders_but_rpc_is_unverified() {
use skidbladnir::unfold::libvirt::LibvirtTarget;
let t = LibvirtTarget::new("bmc-host", "skid-vm", "/img/disk.qcow2").resources(4096, 4);
let xml = t.domain_xml();
// The domain XML + connect URI are pure render artifacts → green.
assert_emit!(
"backend-libvirt",
"domain_xml_and_uri_render",
xml.contains("<domain")
&& xml.contains("skid-vm")
&& xml.contains("/img/disk.qcow2")
&& t.connect_uri() == "qemu+tcp://bmc-host/system",
"{} bytes of domain XML, uri={}",
xml.len(),
t.connect_uri()
);
// The hand-rolled libvirt RPC (define/create/destroy over the raw wire protocol)
// is UNVERIFIED against a real libvirtd — honestly partial, never green.
emit_partial!(
"backend-libvirt",
"handrolled_rpc_unverified_against_libvirtd",
"domain XML is a tested artifact; the hand-rolled DOMAIN_DEFINE_XML/create RPC wire path is unverified against a live libvirtd"
);
}
// ───────────────────────────── PKI (rcgen X.509) ─────────────────────────────
#[cfg(feature = "pki")]
#[test]
fn pki_issues_and_verifies_real_x509() {
use skidbladnir::pki::{MachineCa, verify_leaf};
// Deterministic CA from a fixed seed → reproducible, still a real ed25519 key.
let ca = MachineCa::from_seed("host14", [7u8; 32]);
// issue_leaf returns (LeafCert, leaf_private_key_pem); the CERT PEM is on the leaf.
let (leaf, key_pem) = ca
.issue_leaf("holger", &["holger.host14.x14.local".to_string()])
.unwrap();
assert_emit!(
"pki",
"rcgen_issues_x509_leaf_that_verifies_under_ca",
leaf.cert_pem.starts_with("-----BEGIN CERTIFICATE-----")
&& key_pem.contains("PRIVATE KEY")
&& !leaf.cert_der.is_empty()
&& verify_leaf(&leaf, &ca.verifying_key()),
"leaf CN={} sans={:?} der={} bytes, verifies under CA",
leaf.subject_cn,
leaf.sans,
leaf.cert_der.len()
);
// Tampering: a leaf must NOT verify under a DIFFERENT CA's key.
let other = MachineCa::from_seed("host99", [9u8; 32]);
assert_emit!(
"pki",
"leaf_rejected_under_wrong_ca_key",
!verify_leaf(&leaf, &other.verifying_key()),
"leaf issued by host14 CA is rejected under host99 CA key"
);
}
// ───────────────────────────── secret-env hardening ─────────────────────────────
#[test]
fn secret_env_stays_out_of_the_world_readable_unit() {
use skidbladnir::systemd::{SystemdServiceSpec, env_file, systemd_unit};
// SECURITY: a token set via `with_secret_env` is written ONLY into the (0600)
// env file — never inline in the world-readable `/etc/systemd/system` unit.
// A missing env_file for a spec that carries secrets is a hard validation error.
let spec = SystemdServiceSpec::new(
"nornir",
"nornir server",
"/usr/local/bin/nornir-server",
"nornir",
"/home/nornir",
)
.with_env("NORNIR_SERVER_ADDR", "127.0.0.1:7878")
.with_secret_env("NORNIR_SERVER_TOKEN", "s3cr3t")
.with_env_file("/etc/nornir/nornir.env");
let unit = systemd_unit(&spec);
// And a secret with NO env_file is refused before anything is written.
let leaky = SystemdServiceSpec::new("svc", "d", "/bin/svc", "svc", "/tmp")
.with_secret_env("TOKEN", "abc");
assert_emit!(
"secret-env-hardening",
"secrets_never_inline_in_world_readable_unit",
!unit.contains("s3cr3t")
&& !unit.contains("Environment=NORNIR_SERVER_TOKEN")
&& unit.contains("EnvironmentFile=/etc/nornir/nornir.env")
&& env_file(&spec).contains("NORNIR_SERVER_TOKEN=s3cr3t")
&& spec.validate().is_ok()
&& leaky.validate().is_err(),
"unit_bytes={} secret_in_file={} secretless_leak_rejected={}",
unit.len(),
env_file(&spec).contains("NORNIR_SERVER_TOKEN=s3cr3t"),
leaky.validate().is_err()
);
}
// ───────────────────────────── nornir_spec drop-in ─────────────────────────────
// ───────────────── IMPLANTED lifecycle-surface emits: red-when-broken ─────────────────
/// RED-WHEN-BROKEN (the law): the lifecycle SURFACES emit `functional_status`
/// straight from their runtime path — not just from this test's re-derivation. Drive
/// three surfaces into their genuine failure mode and assert the IMPLANTED emit the
/// production code left behind reads as RED with real detail. Because the emit is only
/// compiled when `--features testmatrix` is on (and only then is the read-back buffer
/// present), this proof is gated on that feature; the default build compiles the emits
/// out entirely, so a green tree here means "the surfaces are wired, and broken input
/// makes them go red" — the exact tripwire nornir's matrix ingests.
#[cfg(feature = "testmatrix")]
#[test]
fn lifecycle_surfaces_emit_red_when_broken() {
use skidbladnir::archive::{Archive, TarZstd, load_manifest, manifest_path};
use skidbladnir::fetch::verify_on_receipt;
use skidbladnir::orchestrate::start_waves;
use skidbladnir::product::Product;
use skidbladnir::systemd::{Scope, SystemdServiceSpec, install_systemd_scoped};
// Clear any rows parallel tests already parked in the process-global buffer so
// the drain below sees a clean slate; we still filter by surface+check to be safe.
let _ = skidbladnir::drain_functional_rows();
// 1) FETCH — tamper the sidecar manifest so verify-on-receipt fails; the surface
// must return not-ok AND have emitted a RED `verify_on_receipt` row.
let dir = tempfile::tempdir().unwrap();
let staging = dir.path().join("stage");
std::fs::create_dir_all(staging.join("linux-x86_64")).unwrap();
std::fs::write(staging.join("linux-x86_64/holger"), b"holger-bytes").unwrap();
let bundle = dir.path().join("lake.tar.zst");
let eng = TarZstd::new();
eng.pack(&staging, "20250505", &bundle).unwrap();
let mut m = load_manifest(&bundle).unwrap();
m.entries[0].sha256 = "ff".repeat(32); // corrupt the recorded digest
std::fs::write(manifest_path(&bundle), serde_json::to_vec(&m).unwrap()).unwrap();
let report = verify_on_receipt(&bundle, &eng, None, None).unwrap();
assert!(!report.is_ok(), "tampered bundle must fail the gate");
// 2) START — a dependency cycle is an install that never converges; start_waves
// must Err AND have emitted a RED `start_waves_ordered` row.
let cycle = [
Product {
name: "a".into(),
depends_on: vec!["b".into()],
user: "a".into(),
ports: vec![],
},
Product {
name: "b".into(),
depends_on: vec!["a".into()],
user: "b".into(),
ports: vec![],
},
];
assert!(start_waves(&cycle).is_err(), "a cycle cannot be ordered");
// 3) SYSTEMD — a spec that carries a secret but no private env-file would leak it
// into the world-readable unit; install refuses it BEFORE any privilege (so this
// is reachable without root) AND emits a RED `install_unit` row.
let leaky = SystemdServiceSpec::new("svc", "d", "/bin/svc", "svc", "/tmp")
.with_secret_env("TOKEN", "s3cr3t");
assert!(
install_systemd_scoped(&leaky, Scope::System).is_err(),
"a secret with no env-file must be refused"
);
// Now read back the rows the SURFACES themselves left behind and assert each went RED.
let rows = skidbladnir::drain_functional_rows();
let red = |suite: &str, check: &str| -> String {
let hits: Vec<_> = rows
.iter()
.filter(|r| r.suite == suite && r.test_name == check && r.status == "fail")
.collect();
assert_eq!(
hits.len(),
1,
"exactly one RED `{suite}::{check}` row (got {}): {:?}",
hits.len(),
rows.iter()
.map(|r| (&r.suite, &r.test_name, &r.status))
.collect::<Vec<_>>()
);
assert!(!hits[0].message.is_empty(), "RED row carries real detail");
hits[0].message.clone()
};
let fetch_detail = red("skidbladnir/fetch", "verify_on_receipt");
assert!(
fetch_detail.contains("bad_entries=") || fetch_detail.contains("manifest_root_ok="),
"fetch RED names the failing check: {fetch_detail}"
);
let start_detail = red("skidbladnir/start", "start_waves_ordered");
assert!(
start_detail.contains("cycle"),
"start RED names the cycle: {start_detail}"
);
let systemd_detail = red("skidbladnir/systemd", "install_unit");
assert!(
systemd_detail.contains("refused"),
"systemd RED says refused: {systemd_detail}"
);
}
#[test]
fn nornir_spec_dropin_renders_a_real_unit() {
use skidbladnir::systemd::{nornir_spec, systemd_unit};
let spec = nornir_spec("/opt/nornir/nornir-server", "0.0.0.0:8443");
let unit = systemd_unit(&spec);
assert_emit!(
"nornir-spec-dropin",
"nornir_spec_renders_installable_unit",
unit.contains("[Unit]")
&& unit.contains("[Service]")
&& unit.contains("ExecStart=")
&& unit.contains("/opt/nornir/nornir-server"),
"rendered {} bytes; unit_file={}",
unit.len(),
spec.unit_file_name()
);
}