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

nordisk/edda

crates/nornir-testmatrix/src/appliance.rs

//! **The appliance (product × proof) rollup** — ONE place that answers "which
//! products have a PROVEN identity ceremony, and how far up the ladder?".
//!
//! The suite's appliance work (the wg-appliance ceremony, the container robot,
//! the installer KVM proofs, Secure Boot arming) emits ordinary
//! [`functional_status`](crate::functional_status) rows under a NAMING
//! CONVENTION this module owns:
//!
//! ```text
//!   component = "<product>-appliance"            (the host/server form)
//!            or "<product>-appliance/<form>"     (a specific form: container, iso …)
//!   check     = the proof name (identity_ready, ping_through_tunnel, …)
//! ```
//!
//! [`ApplianceRollup::from_rows`] folds any row set into a grid of
//! [`ProofVerdict`]s keyed `(product, proof)`. Three verdicts, and the third is
//! the load-bearing one:
//!
//! * [`ProofVerdict::Green`] — every matching row passed.
//! * [`ProofVerdict::Red`] — ANY matching row failed. Red wins over green
//!   (a proof that passed on one form and failed on another is NOT proven).
//! * [`ProofVerdict::Absent`] — no row at all. A product that never emitted is
//!   a visible HOLE in the grid, never silent green — "no evidence" and
//!   "proven" must be impossible to confuse (the same law as the gated-tests
//!   guard: silence is not success).
//!
//! The canonical rosters ([`APPLIANCE_PRODUCTS`], [`APPLIANCE_PROOFS`]) pin the
//! grid's SHAPE: the rollup always shows all three products against the full
//! proof ladder, so korp's empty row is on the wall next to holger's green one.
//! Extra products/proofs found in the rows are appended after the canonical
//! ones — the roster floors the grid, it does not cap it.

use std::collections::BTreeMap;

use crate::model::{status, TestResultRow};

/// The products the suite ships as appliances. The grid ALWAYS carries all of
/// them — an unproven product is a row of `Absent`, not a missing row.
///
/// These are the nordisk RUNNABLES, and the roster is a **FLOOR, not a cap**:
/// discovery appends anything else it finds.
///
/// ⚠ The four (five) names are NOT the rows. Each product exists in several
/// runnable FORMS in the Build Thing — server, ui, cli, solo, service,
/// container, iso, installer — and **the forms are the rows**
/// ([`crate::chain::BuildThing`]). This roster only fixes which products must
/// always be REPRESENTED.
///
/// `dwarves` is a product here (it ships `dwarves-server`), not merely the forge
/// that builds the others. `nornir` is last on purpose: it is a real product but
/// mostly embedded in dwarves, so it is the lowest-priority standalone and is
/// expected to sit `Absent` — visible, not missing, and not a wall.
///
/// The LIBRARIES are deliberately NOT rows: facett, edda, tunnr, knut, znippy,
/// skade, Skidbladnir, draupnir, modgunn. They are the MACHINERY the matrix uses
/// — draupnir instantiates, nornir-build-thing enumerates and builds,
/// wg-appliance-core owns the ceremony.
pub const APPLIANCE_PRODUCTS: [&str; 5] = ["gunnar", "holger", "dwarves", "korp", "nornir"];

/// The proof ladder, in ceremony order — each name is a `check` some proof
/// emits (or will emit; an emitter that does not exist yet shows `Absent`,
/// which is exactly the honest answer):
///
/// * `identity_ready` — the server binary ran the ceremony (host form).
/// * `identity_minted` / `identity_survives_replacement` /
///   `identity_survives_upgrade` — the container robot's durability arms.
/// * `tunnel_up_traffic_crossed` — handshake seen + ≥1 packet sealed.
/// * `ping_through_tunnel` — ICMP echo through the tunnel, both directions.
/// * `install_ok` — the installer ISO laid the product onto a disk under KVM.
/// * `secure_boot_armed` — enforcing firmware boots the signed installer and
///   refuses the unsigned/foreign-keyed one.
pub const APPLIANCE_PROOFS: [&str; 8] = [
    "identity_ready",
    "identity_minted",
    "identity_survives_replacement",
    "identity_survives_upgrade",
    "tunnel_up_traffic_crossed",
    "ping_through_tunnel",
    "install_ok",
    "secure_boot_armed",
];

/// The `-appliance` component-name marker the convention hangs on.
const COMPONENT_SUFFIX: &str = "-appliance";

/// One cell of the grid — and, since the chain matrix
/// ([`crate::chain`]), of THAT grid too. ONE verdict vocabulary for the whole
/// suite, deliberately: two enums that mean almost the same thing is exactly how
/// "no evidence" and "proven" start reading alike again.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ProofVerdict {
    /// It RAN and every matching row passed.
    Green,
    /// It RAN and at least one matching row failed — red wins over green.
    Red,
    /// No row matched — it NEVER RAN. No evidence, visibly so. Never green.
    Absent,
    /// **This kind has no such stage.** Not a hole anyone can go fill: a library
    /// was never going to boot off a disk, and a place where no green was ever
    /// possible must not sit in the report looking like unfinished work.
    ///
    /// A product DECLARES it, out loud, per rung: a `skip` row whose message
    /// opens with [`NA_MARKER`](crate::functional::NA_MARKER) — what
    /// [`functional_na`](crate::functional::functional_na) emits — and the
    /// reason travels with it. Nothing infers an `Na`; silence stays
    /// [`Absent`](ProofVerdict::Absent).
    ///
    /// REVISED 2026-09-10: this enum's old note said the (product × proof)
    /// rollup never produces one, on the theory that every rung applies to
    /// every product. It does not: nornir is embedded in dwarves and ships no
    /// installer ISO, so `install_ok` is not a hole in nornir's work — it is a
    /// stage nornir has not got. The alternative was leaving it `Absent`
    /// forever, which is the same dash as "nobody wrote it yet".
    Na,
}

impl ProofVerdict {
    /// The single-glyph cell rendering: `✔` / `✘` / `—` / `·`.
    ///
    /// `—` (absent) and `·` (n/a) MUST stay visually distinct: one is a hole
    /// someone must go fill, the other never was one.
    pub fn glyph(self) -> &'static str {
        match self {
            ProofVerdict::Green => "✔",
            ProofVerdict::Red => "✘",
            ProofVerdict::Absent => "—",
            ProofVerdict::Na => "·",
        }
    }
}

/// Parse a component name against the convention: `holger-appliance` and
/// `holger-appliance/container` both yield `Some("holger")`; anything without
/// the `-appliance` marker (or with an EMPTY product) is `None` — foreign
/// components never leak into the grid.
pub fn appliance_product(component: &str) -> Option<&str> {
    let base = component.split('/').next().unwrap_or(component);
    let product = base.strip_suffix(COMPONENT_SUFFIX)?;
    if product.is_empty() {
        return None;
    }
    Some(product)
}

/// The folded (product × proof) grid.
#[derive(Debug, Clone)]
pub struct ApplianceRollup {
    /// Row order: the canonical products first, then any extras seen in the
    /// rows (sorted). Same floor-not-cap rule for `proofs`.
    pub products: Vec<String>,
    /// Column order: the canonical ladder first, then extras (sorted).
    pub proofs: Vec<String>,
    verdicts: BTreeMap<(String, String), ProofVerdict>,
}

impl ApplianceRollup {
    /// Fold rows into the grid. Rows whose component does not match the
    /// convention are ignored; `pass` folds green, a `skip` that DECLARES
    /// itself inapplicable ([`NA_MARKER`](crate::functional::NA_MARKER)) folds
    /// `Na`, and anything else that matched a proof folds RED (a stalled or
    /// failed proof is not proven).
    ///
    /// Precedence across forms is red > green > n/a: one form failing sinks the
    /// proof, and a form that DID climb the rung outranks another form's claim
    /// that the rung is not there.
    pub fn from_rows<'a, I: IntoIterator<Item = &'a TestResultRow>>(rows: I) -> Self {
        let mut verdicts: BTreeMap<(String, String), ProofVerdict> = BTreeMap::new();
        let mut extra_products: Vec<String> = Vec::new();
        let mut extra_proofs: Vec<String> = Vec::new();
        for row in rows {
            let Some(product) = appliance_product(&row.suite) else {
                continue;
            };
            let proof = row.test_name.as_str();
            let seen = if row.status == status::PASS {
                ProofVerdict::Green
            } else if row.status == status::SKIP
                && row.message.trim_start().starts_with(crate::functional::NA_MARKER)
            {
                ProofVerdict::Na
            } else {
                ProofVerdict::Red
            };
            let key = (product.to_string(), proof.to_string());
            let held = verdicts.entry(key).or_insert(seen);
            *held = match (*held, seen) {
                (ProofVerdict::Red, _) | (_, ProofVerdict::Red) => ProofVerdict::Red,
                (ProofVerdict::Green, _) | (_, ProofVerdict::Green) => ProofVerdict::Green,
                _ => ProofVerdict::Na,
            };
            if !APPLIANCE_PRODUCTS.contains(&product) && !extra_products.iter().any(|p| p == product) {
                extra_products.push(product.to_string());
            }
            if !APPLIANCE_PROOFS.contains(&proof) && !extra_proofs.iter().any(|p| p == proof) {
                extra_proofs.push(proof.to_string());
            }
        }
        extra_products.sort();
        extra_proofs.sort();
        let products = APPLIANCE_PRODUCTS
            .iter()
            .map(|p| p.to_string())
            .chain(extra_products)
            .collect();
        let proofs = APPLIANCE_PROOFS
            .iter()
            .map(|p| p.to_string())
            .chain(extra_proofs)
            .collect();
        Self { products, proofs, verdicts }
    }

    /// The verdict for one cell (any product/proof string — off-grid asks are
    /// `Absent`, the same answer as an on-grid hole).
    pub fn verdict(&self, product: &str, proof: &str) -> ProofVerdict {
        self.verdicts
            .get(&(product.to_string(), proof.to_string()))
            .copied()
            .unwrap_or(ProofVerdict::Absent)
    }

    /// True when every canonical (product × proof) place is `Green` **or a
    /// declared `Na`** — the suite-wide "all appliances fully proven" gate.
    /// Today this is FALSE and the grid shows exactly which places keep it
    /// false.
    ///
    /// An `Na` never holds the gate, for the same reason a needs-real-cloud
    /// preset never holds [`ApplianceBootGrid::kvm_all_green`]: a stage the
    /// product has not got cannot be climbed, so demanding it would make the
    /// gate permanently red for a reason nobody can act on. `Absent` is the
    /// opposite case and still holds it red — that IS actionable.
    pub fn all_green(&self) -> bool {
        APPLIANCE_PRODUCTS.iter().all(|product| {
            APPLIANCE_PROOFS.iter().all(|proof| {
                matches!(self.verdict(product, proof), ProofVerdict::Green | ProofVerdict::Na)
            })
        })
    }

    /// Render the grid as a fixed-width text table — products as rows, proofs
    /// as columns (numbered, with a legend, so the table stays terminal-width).
    pub fn render(&self) -> String {
        let mut out = String::new();
        out.push_str(
            "appliance (product × proof) — ✔ proven · ✘ FAILED · — no evidence · · no such stage\n",
        );
        let width = self.products.iter().map(|p| p.len()).max().unwrap_or(0).max(7);
        out.push_str(&format!("{:width$} ", ""));
        for i in 1..=self.proofs.len() {
            out.push_str(&format!("{i:>3}"));
        }
        out.push('\n');
        for product in &self.products {
            out.push_str(&format!("{product:width$} "));
            for proof in &self.proofs {
                out.push_str(&format!("{:>3}", self.verdict(product, proof).glyph()));
            }
            out.push('\n');
        }
        for (i, proof) in self.proofs.iter().enumerate() {
            out.push_str(&format!("  {:>2} = {proof}\n", i + 1));
        }
        out
    }
}

// ─────────────── appliance-boot: one row per TARGET PRESET ──────────────────

/// The component prefix of the per-machine boot rows: `appliance-boot/<preset>`.
///
/// The `<preset>` word is a name from `nornir_build_thing::target::presets::PRESETS`
/// — the ONE roster of cloud machine shapes (UpCloud, qemu-local, GCE …). This
/// crate never spells those names itself (the dependency runs the other way,
/// see `tests/chain_matrix.rs`): the caller hands the grid a [`BootPreset`]
/// roster built FROM that code (`presets::boot_roster()`), and the rows come
/// from the proof doors (`gunnar-installtest installer --target <preset>`, a
/// real cloud deploy) under this naming convention.
pub const APPLIANCE_BOOT_COMPONENT: &str = "appliance-boot";

/// The proof ladder of one machine shape, in door order — the three legs of
/// `gunnar-installtest installer`: B1 install onto the disk, B2 boot the
/// installed disk alone and ANSWER, B3 power-cut and come back.
pub const APPLIANCE_BOOT_PROOFS: [&str; 3] = ["install_ok", "serves", "ready"];

/// Can this machine shape be measured on the proof box at all?
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BootReach {
    /// QEMU/KVM can mimic it — a hole the installtest fills.
    Kvm,
    /// No QEMU model for a device the guest must drive (or not QEMU at all):
    /// the row is declared and stays `Absent (needs real cloud: …)` until a
    /// real deploy emits under its name. The reason is the preset's own.
    RealCloud(String),
}

/// **One device QEMU had to stand in for**, as the roster's code derives it
/// (`nornir_build_thing::target::presets::MimicAxes::substitutions`).
///
/// A proof row for a preset with a non-empty list proved a SHAPE — the boot
/// order, the firmware handoff, the partition scan, the address plumbing —
/// and NOT the named devices. The row has to say so; see
/// [`BootPreset::unnamed_substitutions`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BootSubstitution {
    /// Which facet: `firmware` | `chipset` | `disk` | `nic` | `medium`.
    pub facet: String,
    /// The device the real machine presents.
    pub missing: String,
    /// What QEMU was asked to present instead.
    pub with: String,
}

impl BootSubstitution {
    /// The exact phrase a row must contain — `nic: hv-netvsc -> virtio-net-pci`.
    /// One writer, so the emitter and the checker cannot spell it differently.
    pub fn label(&self) -> String {
        format!("{}: {} -> {}", self.facet, self.missing, self.with)
    }
}

/// One machine shape the grid MUST show — the floor, handed in from the code
/// that declares it. Never typed out here.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BootPreset {
    /// The `<preset>` word.
    pub name: String,
    pub reach: BootReach,
    /// Where/when the shape was measured, when it was — the roster's own
    /// evidence, quoted in the detail of a row that has no fresh proof yet.
    /// The string says which KIND of measurement it was (`real cloud: …` /
    /// `qemu rig: …`); the roster is the one writer of that word.
    pub measured: Option<String>,
    /// The devices QEMU stands in for on this shape. Empty = a green here
    /// means the machine; non-empty = a green means the shape, and the row
    /// must NAME each one.
    pub substitutions: Vec<BootSubstitution>,
}

/// **The word a row uses to say it ran on the real platform**, not on a mimic.
///
/// The same vocabulary the roster's own evidence uses
/// (`nornir_build_thing::target::presets::MeasuredOn::RealCloud` renders
/// `real cloud: …`), so there is ONE spelling of this claim in the
/// constellation rather than one per emitter.
pub const REAL_DEPLOY_MARKER: &str = "real cloud";

impl BootPreset {
    /// **Which substitutions a passing row failed to account for.**
    ///
    /// The load-bearing guard of this module's second half: a `pass` whose
    /// message does not spell every swapped device is not evidence that the
    /// machine booted, it is evidence that SOMETHING booted.
    ///
    /// Two ways for a row to be honest, because there are two ways to earn a
    /// green on a substituted shape:
    ///
    /// * it NAMES every substitution — a mimic run, and the row says what it
    ///   stood in for; or
    /// * it declares [`REAL_DEPLOY_MARKER`] — it ran on the actual platform, so
    ///   nothing was substituted and the devices really were there.
    ///
    /// A preset with no substitutions is always honest, so nothing at all is
    /// demanded of an exactly-mimicked shape.
    pub fn unnamed_substitutions(&self, message: &str) -> Vec<String> {
        if message.contains(REAL_DEPLOY_MARKER) {
            return Vec::new();
        }
        self.substitutions
            .iter()
            .map(BootSubstitution::label)
            .filter(|label| !message.contains(label.as_str()))
            .collect()
    }
}

/// Parse `appliance-boot/<preset>` → `Some("<preset>")`; anything else (the
/// bare prefix, an empty preset, a foreign component) is `None`.
pub fn appliance_boot_preset(component: &str) -> Option<&str> {
    let rest = component.strip_prefix(APPLIANCE_BOOT_COMPONENT)?.strip_prefix('/')?;
    if rest.is_empty() || rest.contains('/') {
        return None;
    }
    Some(rest)
}

/// The (preset × proof) grid: every declared machine shape against the three
/// boot proofs. Same verdict vocabulary as the rest of the suite
/// ([`ProofVerdict`]); the only twist is WHY a cell is `Absent`, which this
/// grid carries as a detail string so `needs real cloud` and `never ran` are
/// never the same dash.
#[derive(Debug, Clone)]
pub struct ApplianceBootGrid {
    /// Row order: the roster first (its order), then any preset seen only in
    /// rows (sorted) — floor, not cap.
    pub presets: Vec<BootPreset>,
    /// Column order: the canonical ladder, then extras (sorted).
    pub proofs: Vec<String>,
    cells: BTreeMap<(String, String), (ProofVerdict, String)>,
}

impl ApplianceBootGrid {
    /// Fold rows onto the roster. `pass` is green, any other matched status
    /// is red; a roster cell with no row is `Absent` with the reason the
    /// roster gives (`needs real cloud: …` or `no KVM proof emitted yet`).
    pub fn from_rows<'a, I: IntoIterator<Item = &'a TestResultRow>>(roster: Vec<BootPreset>, rows: I) -> Self {
        let mut cells: BTreeMap<(String, String), (ProofVerdict, String)> = BTreeMap::new();
        let mut extra_presets: Vec<String> = Vec::new();
        let mut extra_proofs: Vec<String> = Vec::new();
        for row in rows {
            let Some(preset) = appliance_boot_preset(&row.suite) else { continue };
            let proof = row.test_name.as_str();
            // **A pass must name what QEMU stood in for.** The roster knows
            // which devices this shape substitutes; a `pass` whose message is
            // silent about one of them claims a machine it did not boot. Such
            // a row is folded RED with the missing names, because "green but
            // do not believe it" is not a verdict this grid can render — and a
            // silent green is the one outcome this crate exists to refuse.
            let unnamed = roster
                .iter()
                .find(|p| p.name == preset)
                .map(|p| p.unnamed_substitutions(&row.message))
                .unwrap_or_default();
            let ok = row.status == status::PASS && unnamed.is_empty();
            let message = if row.status == status::PASS && !unnamed.is_empty() {
                format!(
                    "row says pass but does not name what QEMU stood in for: {} — a KVM green on a \
                     substituted shape proves the shape, not the machine, so the row must name every \
                     swap or say `{REAL_DEPLOY_MARKER}` (message was: {:?})",
                    unnamed.join("; "),
                    row.message
                )
            } else {
                row.message.clone()
            };
            let cell = cells
                .entry((preset.to_string(), proof.to_string()))
                .or_insert((ProofVerdict::Green, String::new()));
            if !ok {
                *cell = (ProofVerdict::Red, message);
            } else if cell.0 == ProofVerdict::Green {
                cell.1 = message;
            }
            if !roster.iter().any(|p| p.name == preset) && !extra_presets.iter().any(|p| p == preset) {
                extra_presets.push(preset.to_string());
            }
            if !APPLIANCE_BOOT_PROOFS.contains(&proof) && !extra_proofs.iter().any(|p| p == proof) {
                extra_proofs.push(proof.to_string());
            }
        }
        extra_presets.sort();
        extra_proofs.sort();
        let mut presets = roster;
        presets.extend(extra_presets.into_iter().map(|name| BootPreset {
            name,
            reach: BootReach::Kvm,
            measured: None,
            // An extra preset is one the roster never declared, so nothing here
            // knows what it substitutes. No claim is made either way.
            substitutions: Vec::new(),
        }));
        let proofs: Vec<String> =
            APPLIANCE_BOOT_PROOFS.iter().map(|p| p.to_string()).chain(extra_proofs).collect();
        // Every roster hole gets its reason NOW, so a render never invents one.
        for p in &presets {
            for proof in &proofs {
                cells.entry((p.name.clone(), proof.clone())).or_insert_with(|| {
                    let why = match &p.reach {
                        BootReach::RealCloud(why) => format!("needs real cloud: {why}"),
                        BootReach::Kvm => match &p.measured {
                            Some(m) => format!("no KVM proof emitted this run (roster says measured: {m})"),
                            None => "no KVM proof emitted yet".to_string(),
                        },
                    };
                    (ProofVerdict::Absent, why)
                });
            }
        }
        Self { presets, proofs, cells }
    }

    pub fn verdict(&self, preset: &str, proof: &str) -> ProofVerdict {
        self.cells
            .get(&(preset.to_string(), proof.to_string()))
            .map(|c| c.0)
            .unwrap_or(ProofVerdict::Absent)
    }

    /// The reason behind a cell — the gap for an `Absent`, the message for a
    /// red or green.
    pub fn detail(&self, preset: &str, proof: &str) -> &str {
        self.cells
            .get(&(preset.to_string(), proof.to_string()))
            .map(|c| c.1.as_str())
            .unwrap_or("")
    }

    /// Every cell as an ordinary functional row — `pass` / `fail` / `skip`
    /// with the reason in `message` — so the warehouse carries the holes and
    /// the `needs real cloud` gaps as rows, never as silence.
    pub fn rows(&self) -> Vec<TestResultRow> {
        let mut out = Vec::new();
        for p in &self.presets {
            for proof in &self.proofs {
                let (verdict, detail) = &self.cells[&(p.name.clone(), proof.clone())];
                let st = match verdict {
                    ProofVerdict::Green => status::PASS,
                    ProofVerdict::Red => status::FAIL,
                    ProofVerdict::Absent | ProofVerdict::Na => status::SKIP,
                };
                out.push(TestResultRow {
                    run_id: String::new(),
                    repo: String::new(),
                    suite: format!("{APPLIANCE_BOOT_COMPONENT}/{}", p.name),
                    test_name: proof.clone(),
                    status: st.to_string(),
                    duration_ms: 0.0,
                    ts_micros: crate::harness::now_micros(),
                    message: detail.clone(),
                    aspect: crate::functional::ASPECT_FUNCTIONAL.to_string(),
                    metric: if *verdict == ProofVerdict::Red { 1.0 } else { 0.0 },
                });
            }
        }
        out
    }

    /// True when every KVM-reachable preset is green on every proof. A
    /// `RealCloud` preset never holds the gate — its gap is shown, not scored.
    pub fn kvm_all_green(&self) -> bool {
        self.presets.iter().filter(|p| p.reach == BootReach::Kvm).all(|p| {
            self.proofs.iter().all(|proof| self.verdict(&p.name, proof) == ProofVerdict::Green)
        })
    }

    /// Fixed-width text: presets as rows, proofs as columns, plus one reason
    /// line per preset that is not fully green.
    pub fn render(&self) -> String {
        let mut out = String::new();
        out.push_str("appliance-boot (preset × proof) — ✔ proven · ✘ FAILED · — no evidence\n");
        let width = self.presets.iter().map(|p| p.name.len()).max().unwrap_or(0).max(7);
        out.push_str(&format!("{:width$} ", ""));
        for i in 1..=self.proofs.len() {
            out.push_str(&format!("{i:>3}"));
        }
        out.push_str("  reach\n");
        for p in &self.presets {
            out.push_str(&format!("{:width$} ", p.name));
            for proof in &self.proofs {
                out.push_str(&format!("{:>3}", self.verdict(&p.name, proof).glyph()));
            }
            let reach = match &p.reach {
                BootReach::Kvm => "kvm".to_string(),
                BootReach::RealCloud(why) => format!("needs real cloud: {why}"),
            };
            // The wall says what a green on this row would and would not mean.
            let subs = if p.substitutions.is_empty() {
                String::new()
            } else {
                format!(
                    "  [substitutes {}]",
                    p.substitutions.iter().map(BootSubstitution::label).collect::<Vec<_>>().join("; ")
                )
            };
            out.push_str(&format!("  {reach}{subs}\n"));
        }
        for (i, proof) in self.proofs.iter().enumerate() {
            out.push_str(&format!("  {:>2} = {proof}\n", i + 1));
        }
        out
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::functional::functional_row;

    // RED-BEFORE-GREEN: the hole is the load-bearing verdict — a product with
    // no rows is Absent in EVERY cell, and all_green() refuses.
    #[test]
    fn a_product_that_never_emitted_is_a_visible_hole_not_silent_green() {
        let rows =
            vec![functional_row("fixture-appliance", "identity_ready", true, "pubkey=… minted=false")];
        let grid = ApplianceRollup::from_rows(&rows);
        assert_eq!(grid.verdict("korp", "identity_ready"), ProofVerdict::Absent);
        assert_eq!(grid.verdict("gunnar", "ping_through_tunnel"), ProofVerdict::Absent);
        assert!(!grid.all_green(), "holes must hold the gate red");
        let rendered = grid.render();
        assert!(rendered.contains("korp"), "korp's empty row is ON the wall:\n{rendered}");
        assert!(rendered.contains('—'), "absence renders as —:\n{rendered}");
    }

    /// RED-BEFORE-GREEN: a rung the product HAS NOT GOT is its own verdict.
    ///
    /// The three ways it must not be reported: as a pass (nothing was proven),
    /// as a fail (nothing is broken), or as the same dash as a rung nobody has
    /// written the test for yet — that last one is why `Absent` and `Na` render
    /// with different glyphs.
    #[test]
    fn a_declared_n_a_rung_is_not_a_hole_and_is_not_green() {
        use crate::functional::functional_na_row;
        let rows = vec![
            functional_na_row("fixture-appliance", "install_ok", "ships no installer ISO"),
            functional_row("fixture-appliance", "identity_ready", true, "ceremony ran"),
        ];
        let grid = ApplianceRollup::from_rows(&rows);
        assert_eq!(grid.verdict("fixture", "install_ok"), ProofVerdict::Na);
        assert_eq!(grid.verdict("fixture", "identity_ready"), ProofVerdict::Green);
        assert_eq!(
            grid.verdict("fixture", "secure_boot_armed"),
            ProofVerdict::Absent,
            "an UNDECLARED rung stays a hole — n/a is a claim somebody makes, never a default"
        );
        assert_ne!(
            ProofVerdict::Na.glyph(),
            ProofVerdict::Absent.glyph(),
            "the hole and the missing stage must not read alike"
        );

        // A `skip` that does not declare itself is still red: silence about the
        // reason is the failure mode, not the skip.
        let mut mute = functional_na_row("fixture-appliance", "install_ok", "x");
        mute.message = String::new();
        assert_eq!(ApplianceRollup::from_rows(&[mute]).verdict("fixture", "install_ok"), ProofVerdict::Red);

        // Precedence: a form that really climbed the rung outranks another
        // form's n/a; a red sinks both.
        let mixed = vec![
            functional_na_row("fixture-appliance/container", "ping_through_tunnel", "no tunnel in this form"),
            functional_row("fixture-appliance", "ping_through_tunnel", true, "8 bytes back"),
        ];
        assert_eq!(ApplianceRollup::from_rows(&mixed).verdict("fixture", "ping_through_tunnel"), ProofVerdict::Green);
        let sunk = vec![
            functional_na_row("fixture-appliance/container", "ping_through_tunnel", "no tunnel in this form"),
            functional_row("fixture-appliance", "ping_through_tunnel", false, "timed out"),
        ];
        assert_eq!(ApplianceRollup::from_rows(&sunk).verdict("fixture", "ping_through_tunnel"), ProofVerdict::Red);
    }

    #[test]
    fn red_wins_over_green_across_forms() {
        // Same proof, two forms: host passed, container FAILED → the fold is red.
        let rows = vec![
            functional_row("fixture-appliance", "identity_ready", true, "host ok"),
            functional_row("fixture-appliance/container", "identity_ready", false, "container broke"),
        ];
        let grid = ApplianceRollup::from_rows(&rows);
        assert_eq!(grid.verdict("fixture", "identity_ready"), ProofVerdict::Red);
    }

    // The three FORM SHAPES a product's emitters use — the bare product (the
    // host form), a `/container` form and an `/iso-kvm` form — against the full
    // ladder, so the fold is pinned for every rung at once.
    //
    // REFRESHED 2026-08-15: the ISO form is `iso-kvm`, NOT `iso`. holger stopped
    // emitting a bare `…-appliance/iso` when the kvm/metal split landed — a
    // green earned inside a faked machine must never sit where a real-hardware
    // green would (`crate::chain::Form`). The fold itself is form-blind
    // (`appliance_product` splits on `/`), which is exactly why a stale fixture
    // here could go unnoticed — so both forms are pinned.
    //
    // REFRESHED 2026-09-10: the fixture product used to be spelled `holger`,
    // with a comment saying it quoted holger's real strings. It did — and the
    // inventory then read these eight sample rows as holger's EVIDENCE and
    // reported the grid 8/8 green while holger's own repo emitted one rung.
    // A product's vocabulary is pinned where the product emits it, in holger's
    // own tests; the instrument's fixture names no product at all.
    #[test]
    fn the_form_vocabulary_folds_onto_the_canonical_grid() {
        let rows = vec![
            functional_row("fixture-appliance", "identity_ready", true, ""),
            functional_row("fixture-appliance/container", "identity_minted", true, ""),
            functional_row("fixture-appliance/container", "identity_survives_replacement", true, ""),
            functional_row("fixture-appliance/container", "identity_survives_upgrade", true, ""),
            functional_row("fixture-appliance/container", "tunnel_up_traffic_crossed", true, ""),
            functional_row("fixture-appliance/container", "ping_through_tunnel", true, ""),
            functional_row("fixture-appliance/iso-kvm", "install_ok", true, ""),
            functional_row("fixture-appliance/iso-kvm", "secure_boot_armed", true, ""),
        ];
        // Every form reads as the product, and a form that is not a form (the
        // bare product) does too.
        for c in ["fixture-appliance", "fixture-appliance/container", "fixture-appliance/iso-kvm"] {
            assert_eq!(appliance_product(c), Some("fixture"), "{c} must fold onto fixture");
        }
        let grid = ApplianceRollup::from_rows(&rows);
        for proof in APPLIANCE_PROOFS {
            assert_eq!(
                grid.verdict("fixture", proof),
                ProofVerdict::Green,
                "{proof} must fold onto the canonical column"
            );
        }
        // No stray columns: every proof emitted here IS canonical.
        assert_eq!(grid.proofs.len(), APPLIANCE_PROOFS.len());
    }

    #[test]
    fn foreign_components_never_leak_into_the_grid() {
        let rows = vec![
            functional_row("holger-mvp", "build", true, "a build tier, not an appliance"),
            functional_row("appliance", "identity_ready", true, "no product prefix"),
            functional_row("-appliance", "identity_ready", true, "EMPTY product"),
        ];
        let grid = ApplianceRollup::from_rows(&rows);
        assert_eq!(grid.products.len(), APPLIANCE_PRODUCTS.len(), "only the canonical roster");
        for product in APPLIANCE_PRODUCTS {
            for proof in APPLIANCE_PROOFS {
                assert_eq!(grid.verdict(product, proof), ProofVerdict::Absent);
            }
        }
    }

    #[test]
    fn extra_products_and_proofs_floor_not_cap() {
        let rows = vec![functional_row("skrymt-appliance", "quantum_ready", false, "a fourth product")];
        let grid = ApplianceRollup::from_rows(&rows);
        assert!(grid.products.iter().any(|p| p == "skrymt"), "extras appended");
        assert!(grid.proofs.iter().any(|p| p == "quantum_ready"));
        assert_eq!(grid.verdict("skrymt", "quantum_ready"), ProofVerdict::Red);
        // …and the canonical roster still leads the row order, nornir last.
        assert_eq!(&grid.products[..5], &["gunnar", "holger", "dwarves", "korp", "nornir"]);
    }

    #[test]
    fn product_parsing_handles_forms_and_refuses_nonmatches() {
        assert_eq!(appliance_product("holger-appliance"), Some("holger"));
        assert_eq!(appliance_product("holger-appliance/container"), Some("holger"));
        assert_eq!(appliance_product("korp-appliance/iso"), Some("korp"));
        assert_eq!(appliance_product("holger-mvp"), None);
        assert_eq!(appliance_product("-appliance"), None);
        assert_eq!(appliance_product("appliance"), None);
    }

    // ── appliance-boot ──

    fn roster() -> Vec<BootPreset> {
        vec![
            BootPreset { name: "qemu-local".into(), reach: BootReach::Kvm, measured: Some("qemu rig: before 2026-08-28".into()), substitutions: vec![] },
            BootPreset { name: "upcloud".into(), reach: BootReach::Kvm, measured: Some("real cloud: 2026-08-29 se-sto1".into()), substitutions: vec![] },
            BootPreset { name: "hetzner-cloud".into(), reach: BootReach::Kvm, measured: None, substitutions: vec![] },
            BootPreset {
                name: "aws-nitro".into(),
                reach: BootReach::RealCloud("ENA has no QEMU model".into()),
                measured: None,
                substitutions: vec![BootSubstitution {
                    facet: "nic".into(),
                    missing: "ena".into(),
                    with: "virtio-net-pci".into(),
                }],
            },
        ]
    }

    /// A shape with substitutions, shaped exactly like the real `azure-gen2`
    /// row: Hyper-V synthetic SCSI and synthetic NIC, both stood in for.
    fn azure_like() -> Vec<BootPreset> {
        vec![BootPreset {
            name: "azure-gen2".into(),
            reach: BootReach::Kvm,
            measured: None,
            substitutions: vec![
                BootSubstitution { facet: "disk".into(), missing: "hv-scsi".into(), with: "virtio-scsi-pci".into() },
                BootSubstitution { facet: "nic".into(), missing: "hv-netvsc".into(), with: "virtio-net-pci".into() },
            ],
        }]
    }

    /// **RED: a `pass` that does not say what QEMU stood in for is not a pass.**
    ///
    /// The whole point of carrying substitutions on the row. A KVM green for
    /// `azure-gen2` proves the boot order, the firmware handoff, the partition
    /// scan and the root mount — it does NOT prove `hv_netvsc` or `hv_storvsc`,
    /// because neither was there. A row that stays silent about that is
    /// claiming a machine it never booted, so the grid folds it RED and names
    /// what is missing.
    #[test]
    fn a_pass_that_does_not_name_its_substitutions_is_red() {
        // Silent pass → RED, and the detail names the missing swap.
        let silent = vec![functional_row("appliance-boot/azure-gen2", "install_ok", true, "B1 install 2002 ms INSTALL-OK")];
        let grid = ApplianceBootGrid::from_rows(azure_like(), &silent);
        assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Red, "a silent green must not stand");
        let why = grid.detail("azure-gen2", "install_ok");
        assert!(why.contains("nic: hv-netvsc -> virtio-net-pci"), "the refusal must NAME the swap: {why}");
        assert!(why.contains("disk: hv-scsi -> virtio-scsi-pci"), "both swaps, not just the first: {why}");
        assert!(!grid.kvm_all_green());

        // HALF-honest is still red: naming one swap and hiding the other is the
        // same lie, smaller.
        let half = vec![functional_row("appliance-boot/azure-gen2", "install_ok", true, "INSTALL-OK; disk: hv-scsi -> virtio-scsi-pci")];
        let grid = ApplianceBootGrid::from_rows(azure_like(), &half);
        assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Red);
        let why = grid.detail("azure-gen2", "install_ok");
        assert!(why.contains("nic: hv-netvsc -> virtio-net-pci") && !why.contains("row says pass but does not name what QEMU stood in for: disk"), "{why}");

        // GREEN, same verdict word, once the row tells the truth — proof the
        // red above is the guard firing and not the fold being broken.
        let honest = vec![functional_row(
            "appliance-boot/azure-gen2",
            "install_ok",
            true,
            "B1 install 2002 ms INSTALL-OK (shape only — disk: hv-scsi -> virtio-scsi-pci; nic: hv-netvsc -> virtio-net-pci)",
        )];
        let grid = ApplianceBootGrid::from_rows(azure_like(), &honest);
        assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Green);

        // GREEN the other way: a row that ran on the REAL platform substituted
        // nothing, and says so. This is the escape hatch a real Azure deploy
        // takes — and it must be a claim the row makes out loud, not a default.
        let deployed = vec![functional_row("appliance-boot/azure-gen2", "install_ok", true, "real cloud: Standard_D2s_v5, westeurope")];
        let grid = ApplianceBootGrid::from_rows(azure_like(), &deployed);
        assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Green);

        // And a preset that substitutes NOTHING is asked for nothing: an
        // exactly-mimicked shape's bare `pass` stays green.
        let bare = vec![functional_row("appliance-boot/upcloud", "install_ok", true, "SeaBIOS + -serial none")];
        let grid = ApplianceBootGrid::from_rows(roster(), &bare);
        assert_eq!(grid.verdict("upcloud", "install_ok"), ProofVerdict::Green);

        // A FAIL is still a fail, and keeps its own message — the substitution
        // guard must not overwrite a genuine red's reason.
        let failed = vec![functional_row("appliance-boot/azure-gen2", "serves", false, "B2 never answered in 180 s")];
        let grid = ApplianceBootGrid::from_rows(azure_like(), &failed);
        assert_eq!(grid.verdict("azure-gen2", "serves"), ProofVerdict::Red);
        assert_eq!(grid.detail("azure-gen2", "serves"), "B2 never answered in 180 s");
    }

    /// The wall says, per row, what a green there would and would not mean.
    #[test]
    fn the_wall_names_the_substituted_devices() {
        let rendered = ApplianceBootGrid::from_rows(roster(), &[]).render();
        assert!(rendered.contains("[substitutes nic: ena -> virtio-net-pci]"), "{rendered}");
        // A row with nothing to substitute says nothing — no noise.
        let upcloud_line = rendered.lines().find(|l| l.starts_with("upcloud")).unwrap();
        assert!(!upcloud_line.contains("substitutes"), "{upcloud_line}");
    }

    // RED-BEFORE-GREEN: an empty row set is a grid full of NAMED holes — the
    // real-cloud gap and the never-ran hole are different reasons on the same
    // dash, and neither is green.
    #[test]
    fn appliance_boot_holes_carry_their_reason_and_never_read_green() {
        let grid = ApplianceBootGrid::from_rows(roster(), &[]);
        assert_eq!(grid.presets.len(), 4);
        assert_eq!(grid.proofs, APPLIANCE_BOOT_PROOFS.map(String::from).to_vec());
        for p in ["qemu-local", "upcloud", "hetzner-cloud", "aws-nitro"] {
            for proof in APPLIANCE_BOOT_PROOFS {
                assert_eq!(grid.verdict(p, proof), ProofVerdict::Absent, "{p}/{proof}");
            }
        }
        assert!(grid.detail("aws-nitro", "install_ok").starts_with("needs real cloud: ENA"));
        assert_eq!(grid.detail("hetzner-cloud", "install_ok"), "no KVM proof emitted yet");
        assert!(grid.detail("upcloud", "serves").contains("roster says measured: real cloud: 2026-08-29"));
        assert!(!grid.kvm_all_green());
        let rows = grid.rows();
        assert_eq!(rows.len(), 4 * 3);
        assert!(rows.iter().all(|r| r.status == status::SKIP), "absent emits `skip`, never `pass`");
        assert!(rows.iter().all(|r| appliance_boot_preset(&r.suite).is_some()));
        let rendered = grid.render();
        assert!(rendered.contains("aws-nitro") && rendered.contains("needs real cloud"), "{rendered}");
    }

    #[test]
    fn appliance_boot_folds_the_door_rows_and_red_wins() {
        let rows = vec![
            functional_row("appliance-boot/upcloud", "install_ok", true, "B1 1.5 s"),
            functional_row("appliance-boot/upcloud", "serves", false, "B2 never answered in 180 s"),
            functional_row("appliance-boot/qemu-local", "install_ok", true, ""),
            functional_row("appliance-boot/qemu-local", "serves", true, ""),
            functional_row("appliance-boot/qemu-local", "ready", true, ""),
            functional_row("appliance-boot/qemu-local", "ready", false, "one red arm"),
            // A real deploy CAN land a green on a needs-real-cloud row — and it
            // says so, which is what excuses it from naming substitutions: on
            // the real platform nothing was stood in for.
            functional_row("appliance-boot/aws-nitro", "install_ok", true, "real cloud: imported AMI booted"),
            // Foreign components never leak.
            functional_row("fixture-appliance/iso-kvm", "install_ok", true, ""),
            functional_row("appliance-boot", "install_ok", true, "no preset"),
            functional_row("appliance-boot/", "install_ok", true, "empty preset"),
        ];
        let grid = ApplianceBootGrid::from_rows(roster(), &rows);
        assert_eq!(grid.verdict("upcloud", "install_ok"), ProofVerdict::Green);
        assert_eq!(grid.verdict("upcloud", "serves"), ProofVerdict::Red);
        assert_eq!(grid.detail("upcloud", "serves"), "B2 never answered in 180 s");
        assert_eq!(grid.verdict("upcloud", "ready"), ProofVerdict::Absent);
        assert_eq!(grid.verdict("qemu-local", "ready"), ProofVerdict::Red, "red wins over green");
        assert_eq!(grid.verdict("aws-nitro", "install_ok"), ProofVerdict::Green);
        assert_eq!(grid.verdict("aws-nitro", "serves"), ProofVerdict::Absent);
        assert_eq!(grid.presets.len(), 4, "no stray rows from foreign components");
        assert!(!grid.kvm_all_green());
    }

    #[test]
    fn appliance_boot_extras_floor_not_cap_and_the_gate_is_kvm_only() {
        let rows = vec![functional_row("appliance-boot/hetzner-cloud", "vnc_screendump", true, "banner seen")];
        let grid = ApplianceBootGrid::from_rows(roster(), &rows);
        assert_eq!(grid.proofs.len(), 4, "extra proof appended");
        let mut all_kvm_green = Vec::new();
        for p in roster().into_iter().filter(|p| p.reach == BootReach::Kvm) {
            for proof in grid.proofs.clone() {
                all_kvm_green.push(functional_row(&format!("appliance-boot/{}", p.name), &proof, true, ""));
            }
        }
        let grid = ApplianceBootGrid::from_rows(roster(), &all_kvm_green);
        assert!(grid.kvm_all_green(), "a needs-real-cloud preset never holds the KVM gate:\n{}", grid.render());
        assert_eq!(grid.verdict("aws-nitro", "install_ok"), ProofVerdict::Absent);
        // A preset seen only in rows is appended after the roster.
        let stray = vec![functional_row("appliance-boot/moon-base", "install_ok", true, "")];
        let grid = ApplianceBootGrid::from_rows(roster(), &stray);
        assert_eq!(grid.presets.last().unwrap().name, "moon-base");
    }

    #[test]
    fn appliance_boot_component_parsing() {
        assert_eq!(appliance_boot_preset("appliance-boot/upcloud"), Some("upcloud"));
        assert_eq!(appliance_boot_preset("appliance-boot/"), None);
        assert_eq!(appliance_boot_preset("appliance-boot"), None);
        assert_eq!(appliance_boot_preset("appliance-boot/a/b"), None);
        assert_eq!(appliance_boot_preset("holger-appliance/iso-kvm"), None);
    }
}