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

nordisk/dwarves

dwarves-gui/src/build_forge.rs

//! **The forge behind the scene** — the side of [`crate::build_scene::Forge`] that is
//! allowed to know what a product is.
//!
//! Everything here is a translation, not a decision. The declarations already say
//! which atoms a repo ships, which suites each one is in, which delivery cells chain
//! off which atom, and what kind each of those produces; this file turns that into the
//! [`Menu`] the scene renders and hands the verbs back to the engines that already
//! exist ([`BuildRun`] for a build, [`BootRun`] for a container log or a guest
//! console). No build logic is re-implemented here.
//!
//! # The matrix is the declaration, unchanged
//!
//! Rows are the `[[build.variant]]` tables **in declaration order**. Columns are the
//! delivery targets those same declarations name, in first-appearance order across the
//! repo, and a row that does not declare a column gets it greyed rather than dropped —
//! that is what makes it a matrix instead of five different lists. Nothing is merged,
//! renamed, sorted or invented: a column exists here exactly when some declaration in
//! that repo produced it, and a suite badge says exactly what `suites = [...]` says.
//!
//! **Every row is an ASSEMBLY, so its switches are its own.** A column strip is a map
//! and therefore shows cells this row lacks, greyed; a settings list is the argv a
//! build will run with, so it shows only what THIS row resolved. See [`settings_of`]
//! for why the repo's feature space is not a switch list.
//!
//! # The receipt, read back out of what was built
//!
//! [`DeclaredForge::settle`] hands the finished artifact to
//! [`audit_artifact`], which lifts `.nordisk.buildthing` out of it with an object
//! parser and measures every claim in it against the binary's own symbol table.
//! Both halves of the pane's answer come from that one call:
//!
//! ```text
//! kvitto   what the build SAID it did       the receipt, from the section
//! bevis    what the binary SHOWS it did     the claims, from the symbol table
//! ```
//!
//! Nothing is executed to obtain either, which is why it works on a cross-built
//! musl binary this host could not run, on a stripped one, and on a hostile one.
//!
//! **A medium is audited through the program it carries.** An installer ISO has no
//! symbol table and no receipt section, so asking it those two questions can only
//! ever answer NOT MEASURED. [`audit_first_readable`] names the subject rule: the
//! audit is about the first listed artifact it can read, and
//! `assemble_cell::compose_installer_row` lists the appliance binary beside the ISO
//! for exactly that reason. The artifact the operator is SHOWN stays the medium.
//!
//! # What is honestly missing
//!
//! The [`Coordinate`] in the menu is still derived from the declaration:
//! `<repo>-build-thing` at the repo's own version, with **no hash** — a coordinate
//! typed before a first build has nothing to pin to, and inventing a hash here
//! would put a number in the receipt that means nothing. The receipt read back out
//! of a finished artifact carries the real one, because the recipe hashed its own
//! sources while it compiled.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use dwarves::build_thing::{code, BuildRun, ConstellationBuilds, RepoBuilds};
use dwarves::feature_axis_source::{feature_axis, AxisRequest};
use nornir_build_thing::contract::{Coordinate, Receipt};
use nornir_build_thing::kernel::{HardeningProfile, Preset};
use nornir_build_thing::receipt_proof::{audit_artifact, ReceiptVerdict};
use nornir_build_thing::variant::{ArtifactKind, BuildVariant};

use crate::boot_run::{
    ArtifactExecLauncher, BootLauncher, BootRun, ContainerLauncher, ExecSandbox, HwConfig,
};
use crate::build_scene::{
    Act, Axis, AxisPick, BuildScene, Choice, Forge, Governed, Leaf, Menu, Outcome, Phase, Proof,
    Query, Report, Row, Screen, Setting, Target,
};
use crate::kernel_scene::{KconfigWhy, KernelScene, PRESETS};

/// How often the kernel leaf asks whether a build rewrote `kernel.why.toml`.
const KERNEL_WHY_RECHECK: std::time::Duration = std::time::Duration::from_secs(2);

/// The settings key the kernel preset rides under — the ONE spelling shared by the
/// picker the operator turns, the leaf that redraws itself from it, and the override
/// that reaches the build. Three call sites and one string.
const PRESET_KEY: &str = "preset";

/// The suffix a recipe crate's name carries. Only used to turn a typed coordinate back
/// into the checkout it addresses.
const RECIPE_SUFFIX: &str = "-build-thing";

/// The forge the shipped scene runs on.
pub struct DeclaredForge {
    /// Every side-by-side checkout that declares a build space.
    builds: ConstellationBuilds,
    /// Which checkout the last resolve landed on — the build verbs need its root.
    repo: Option<String>,
    /// The cell each column maps back to, keyed by `<row>\u{1f}<column>`. Built during
    /// [`Self::menu_for`] so a build never has to re-derive what the operator picked.
    cells: BTreeMap<String, Cell>,
    /// The build in flight, or the last one that finished.
    run: Option<BuildRun>,
    /// **The pane's ONE running subject** — a container log, a guest console, or a
    /// sandboxed exe run. At most one, ever: [`Self::take_live`] is the only way it is
    /// replaced, and it tears the previous one down first. Concurrent containers are the
    /// thing this field exists to prevent.
    live: Option<BootRun>,
    /// The last thing the pane was asked to bring up, so ↻ can bring it up again.
    last_launch: Option<Cell>,
    /// What the finished build produced.
    outcome: Option<Outcome>,
    /// The honest reason the pane cannot do what it offers.
    note: Option<String>,
    /// Where a boot goes when the console asks for one.
    boots: Arc<dyn BootLauncher>,
    /// Which engine [`Self::boots`] is — shown, so an operator can tell WHICH install
    /// they are watching rather than infer it from the serial.
    boot_engine: &'static str,
    /// Where a container run goes.
    containers: Arc<dyn ContainerLauncher>,
    /// The dependency axis being derived, or the one that was. At most one: the
    /// scene only ever looks at the picked row, and a second worker would be work
    /// nobody is waiting for.
    axis_work: Option<AxisWork>,
    /// **The kconfig configurator, as a leaf of ⚒.**
    ///
    /// Built LAZILY, on the first kernel cell an operator picks, and never before:
    /// `KernelScene::new` merges the committed fragments and lays out a 3D cloud, and
    /// this forge is constructed at shell startup for every operator whether or not
    /// they will ever open a kernel row. Seven panes were moved off the UI thread for
    /// putting work on the frame that draws them (`warehouse_nav` held **3.246 s**);
    /// paying a layout at startup for a tab nobody opened is the same bill in a
    /// different pocket. There is exactly one, reused across picks — `set_preset` is
    /// idempotent, so a frame that changes nothing rebuilds nothing.
    kernel: Option<KernelScene>,
    /// **Which built kernel's `kernel.why.toml` the configurator is showing** —
    /// `(output dir, the index's mtime)`, and when it was last looked at.
    ///
    /// The kconfig SOLVER (edda `kconfig_graph`) writes its chains beside every
    /// kernel it builds; the leaf reads that file so a click can say what else
    /// leaves the `.config` by Kconfig's own edges. Read when the picked kernel
    /// changes or the file does — never every frame: the mtime is asked at most
    /// once per [`KERNEL_WHY_RECHECK`], and a frame that changes nothing reads
    /// nothing.
    kernel_why: Option<(PathBuf, Option<std::time::SystemTime>, std::time::Instant)>,
    /// **The recipe crate's wasm build in flight** (gap #9). Started by
    /// [`Self::take_code_road`] on resolve when the cache misses; its lines are the
    /// step console's until BYGG runs something else; its landing swaps the resolved
    /// repo's set for the CODE's and re-answers the matrix through `Report::menu`.
    code_build: Option<code::CodeBuild>,
    /// The loaded module for the resolved repo — the instance the tree's
    /// `options(path)` / `try_toggle` / `actions(kind)` will be asked of.
    code: Option<code::LoadedCode>,
    /// The landed wasm build's lines, kept on screen until the next resolve or build.
    code_log: Vec<String>,
    /// How the last resolve found its repo ("hela receptet", "löst ur namnet", …) —
    /// kept so the matrix re-answered on landing wears the same first half.
    how: Option<String>,
    /// **The chained build in flight** — a `BYGG <kant>` running the edge DAG
    /// through the CLI's own `run_build_chain_watched` on a worker thread. At most
    /// one: two chains would be two containers compiling one kernel, and the second
    /// would file its receipt over the first's.
    chain: Option<crate::build_steps::ChainRun>,
    /// **The resolved declaration's own refusal at load** (`dwarves-buildthing-4`),
    /// when it has one. Set by [`Self::menu_for`] through the CLI door's own gate;
    /// every build verb refuses by it, because acting on a declaration the prompt
    /// refuses is the drift this field exists to make impossible.
    invalid: Option<String>,
}

/// **One row's dependency axis, derived off the frame.**
///
/// MEASURED on this constellation, 2026-08-06, debug build, one repo's lockfile
/// universe of 1557 packages with a three-feature Φ: **19.6 s** before a single
/// minimal cause is hunted (the manifest scan plus the two resolves that settle the
/// ⊥ wall), then roughly **2 s per removable crate** on top — 61.9 s at 20 of that
/// repo's 29 removable crates. There is no cap that makes this a per-frame call, so
/// it is not one: the work runs on its own thread and the scene is told
/// [`Axis::Working`] until it lands.
///
/// One plain thread, not a fan-out: this is a single job, and concurrency in this
/// constellation is `gatling` (LAW 3 — rayon is forbidden).
struct AxisWork {
    /// Which SELECTION it belongs to — row, column and toggles. A finished axis
    /// for a selection nobody is looking at any more is dropped rather than
    /// shown, and that now includes a selection whose opt-outs have changed: the
    /// same row with WireGuard unticked is a picture of a different artifact.
    pick: AxisPick,
    /// `None` once the answer has been taken off it.
    rx: Option<std::sync::mpsc::Receiver<Axis>>,
    /// The answer, kept so the repeat calls cost nothing.
    done: Option<Axis>,
}

/// What one column of one row actually builds.
#[derive(Debug, Clone, PartialEq, Eq)]
struct Cell {
    /// The declared handle the engines take (`--variant` / `--image` / `--iso`).
    name: String,
    /// What it produces.
    kind: ArtifactKind,
    /// Site values this cell DECLARES (`[[build.*.params]]`). Empty for every
    /// cargo axis and every cell that declares none — a product that declares no
    /// parameters renders no inputs.
    params: Vec<nornir_build_thing::params::ParamSpec>,
    /// The fixtures this cell DECLARES for its params (`[build.*.fixtures]`,
    /// name → kind). Carried verbatim; the scene offers the affordance only
    /// where a name is in here.
    fixtures: BTreeMap<String, nornir_build_thing::params::FixtureKind>,
    /// The OPT-OUTS this cell DECLARES (`[[build.installer.options]]`) — the
    /// capabilities a customer may leave out of the packed binary. Empty for
    /// every cargo axis and every cell that declares none, which renders no
    /// checkboxes at all.
    options: Vec<nornir_build_thing::options::OptionSpec>,
}

impl DeclaredForge {
    /// Read the constellation the forge stands in.
    ///
    /// **Says out loud which root it landed on**, once, at startup. MEASURED
    /// 2026-08-22: the ui was launched with `cwd = ~/git` and no
    /// `$DWARVES_CONSTELLATION`, so `default_root()` took the PARENT —
    /// `~` — which declares no build space at all. The roster was
    /// empty, `sök` found nothing, and the log said NOTHING about where it had
    /// looked. A root this pane resolves silently is a root nobody can debug.
    #[must_use]
    pub fn discover() -> Self {
        let root = ConstellationBuilds::default_root();
        let me = Self::at(root);
        eprintln!(
            "build thing: konstellationsrot {} — {} repo(n) deklarerar ett byggrum{}",
            me.builds.root.display(),
            me.builds.repos.len(),
            if me.builds.repos.is_empty() {
                " (sätt $DWARVES_CONSTELLATION)"
            } else {
                ""
            }
        );
        me
    }

    /// Read a specific constellation root.
    #[must_use]
    pub fn at(root: PathBuf) -> Self {
        // ONE read of the selector, ONE decision: the launcher and the name it is
        // reported under come out of the same arm (see [`boot_engine`]).
        let (boots, boot_engine_label) =
            boot_engine(std::env::var("DWARVES_INSTALLER_ENGINE").ok().as_deref());
        Self {
            builds: ConstellationBuilds::discover(&root),
            repo: None,
            cells: BTreeMap::new(),
            run: None,
            live: None,
            last_launch: None,
            outcome: None,
            note: None,
            boots,
            boot_engine: boot_engine_label,
            containers: Arc::new(crate::boot_run::DraupnirContainerLauncher),
            axis_work: None,
            kernel: None,
            kernel_why: None,
            code_build: None,
            code: None,
            code_log: Vec::new(),
            how: None,
            chain: None,
            invalid: None,
        }
    }

    /// **Which road the resolved repo's matrix came down**, as the pane shows it —
    /// [`dwarves::build_thing::BuildRoad::line`], or the in-flight sentence while the
    /// recipe crate is still building. A robot reads it off `how`.
    #[must_use]
    pub fn road_line(&self) -> String {
        self.repo
            .as_deref()
            .map(|r| self.road_line_for(r))
            .unwrap_or_default()
    }

    /// [`Self::road_line`] for a named checkout — `menu_for` asks BEFORE it records the
    /// resolve, so the answer cannot come off `self.repo`.
    fn road_line_for(&self, repo_name: &str) -> String {
        if let Some(b) = self.code_build.as_ref().filter(|b| b.repo() == repo_name) {
            return format!(
                "deklarationen (toml) tills `{}` byggt till wasm — se konsolen",
                b.package()
            );
        }
        self.builds
            .repo(repo_name)
            .map(|r| r.road.line())
            .unwrap_or_default()
    }

    /// The loaded module for the resolved repo, when the code road answered.
    #[must_use]
    pub fn code(&self) -> Option<&code::LoadedCode> {
        self.code.as_ref()
    }

    /// **Take the code road for the repo a resolve landed on** (gap #9): the cached
    /// module answers now; a cache miss starts the wasm build on a worker and the
    /// table answers until it lands; no crate, or the wrong shape, is refused by name
    /// and the table answers with the refusal on it. Never blocks on cargo.
    fn take_code_road(&mut self, repo_name: &str) {
        self.code_build = None;
        self.code = None;
        self.code_log.clear();
        let Some(i) = self.builds.repos.iter().position(|r| r.repo == repo_name) else {
            return;
        };
        let root = self.builds.repos[i].root.clone();
        match code::take(&root, repo_name) {
            code::Road::Ready(loaded) => {
                self.builds.repos[i] = self.builds.repos[i].with_code(&loaded);
                self.code = Some(loaded);
            }
            code::Road::Building(b) => self.code_build = Some(b),
            code::Road::Refused(why) => {
                self.builds.repos[i] = self.builds.repos[i].refused_code(why);
            }
        }
    }

    /// [`Self::take_code_road`] on a fresh resolve — unless the SAME repo's wasm build is
    /// already in flight, in which case its landing is the answer being waited for.
    fn take_code_road_unless_building(&mut self, repo_name: &str) {
        if self
            .code_build
            .as_ref()
            .is_some_and(|b| b.repo() == repo_name)
        {
            return;
        }
        self.take_code_road(repo_name);
    }

    /// **Land the wasm build, if it has.** Swaps the repo's set for the CODE's (or
    /// records the refusal by name) and re-answers the matrix for the scene to swap in.
    fn settle_code(&mut self) -> Option<Menu> {
        let b = self.code_build.as_mut()?;
        b.poll();
        if !b.is_done() {
            return None;
        }
        let mut b = self.code_build.take()?;
        let repo_name = b.repo().to_string();
        self.code_log = std::mem::take(&mut b.log);
        let i = self.builds.repos.iter().position(|r| r.repo == repo_name)?;
        match b.take_result()? {
            Ok(loaded) => {
                self.builds.repos[i] = self.builds.repos[i].with_code(&loaded);
                self.code = Some(loaded);
            }
            Err(why) => {
                self.builds.repos[i] = self.builds.repos[i].refused_code(why.clone());
                self.note = Some(why);
            }
        }
        if self.repo.as_deref() != Some(repo_name.as_str()) {
            return None;
        }
        let how = self.how.clone().unwrap_or_default();
        Some(self.menu_for(&repo_name, how))
    }

    /// Which boot engine the Console pane's ▶ KÖR will use.
    #[must_use]
    pub fn boot_engine(&self) -> &'static str {
        self.boot_engine
    }

    /// Every checkout that declares a build space, in discovery order. The scene never
    /// asks for this — it is the roster a robot prints so a failure names what WAS there.
    #[must_use]
    pub fn declared_repos(&self) -> Vec<String> {
        self.builds.repos.iter().map(|r| r.repo.clone()).collect()
    }

    /// **Every `[[build.kernel]]` this constellation declares** — `(checkout, kernel
    /// name, declared preset)`, in discovery order.
    ///
    /// The scene never asks for this either. It exists so a test can run against the
    /// ONE live declaration on this disk rather than only against a staged tempdir, and
    /// name what it found: a green measured on a fixture says nothing about the
    /// declaration an operator will actually pick.
    #[must_use]
    pub fn kernel_declarations(&self) -> Vec<(String, String, String)> {
        self.builds
            .repos
            .iter()
            .flat_map(|r| {
                r.set
                    .kernels
                    .iter()
                    .map(|k| (r.repo.clone(), k.name.clone(), k.preset.clone()))
            })
            .collect()
    }

    /// **Tear down whatever is running, and return the slot empty.**
    ///
    /// Every path that starts something goes through here first, so there is never a
    /// second container alive beside the first — not on a rebuild, not on a re-run, not
    /// on a row or column change, not on a fresh resolve.
    fn take_live(&mut self) {
        if let Some(b) = self.live.take() {
            b.cancel();
        }
    }

    /// The checkout the last resolve landed on.
    fn current(&self) -> Option<&RepoBuilds> {
        self.builds.repo(self.repo.as_deref()?)
    }

    /// `Some(read)` when the picked kernel's `kernel.why.toml` must be (re)read —
    /// another kernel, or the file changed since — and `None` when what the pane
    /// holds is still the answer. The file's mtime is asked at most once per
    /// [`KERNEL_WHY_RECHECK`].
    fn kernel_why_changed(
        &mut self,
        out: PathBuf,
        kernel_name: &str,
    ) -> Option<Result<Option<KconfigWhy>, String>> {
        let now = std::time::Instant::now();
        if let Some((dir, _, at)) = &self.kernel_why {
            if *dir == out && now.duration_since(*at) < KERNEL_WHY_RECHECK {
                return None;
            }
        }
        let mtime = std::fs::metadata(out.join(nornir_build_thing::kernel_requires::WHY_FILE))
            .and_then(|m| m.modified())
            .ok();
        let same = matches!(&self.kernel_why, Some((dir, m, _)) if *dir == out && *m == mtime);
        self.kernel_why = Some((out.clone(), mtime, now));
        if same {
            return None;
        }
        Some(KconfigWhy::read(&out, kernel_name))
    }

    /// The cell behind a picked `(row, column)`.
    fn cell(&self, row: &str, target: &str) -> Option<&Cell> {
        self.cells.get(&key(row, target))
    }

    // ── resolution ────────────────────────────────────────────────────────────

    /// Which checkouts declare something answering to `name` — the repo itself, one of
    /// its variants, or a variant's package/bin/member.
    ///
    /// # A checkout named exactly what you typed is not ambiguous
    ///
    /// This machine holds a dozen worktrees of the same repo side by side, and every one
    /// of them declares the same variants. Without the exact-name rule, typing the
    /// product's own name is ambiguous with its own worktrees and resolves to nothing at
    /// all — measured 2026-08-06 on this disk: two of the three products the design names
    /// were unreachable that way. So a directory whose name IS the query wins outright;
    /// anything else that merely mentions the name stays a candidate, and two of THOSE is
    /// still a refusal with both named.
    fn candidates(&self, name: &str) -> Vec<&RepoBuilds> {
        let exact: Vec<&RepoBuilds> = self
            .builds
            .repos
            .iter()
            .filter(|r| r.repo == name)
            .collect();
        if exact.len() == 1 {
            return exact;
        }
        self.builds
            .repos
            .iter()
            .filter(|r| {
                r.repo == name
                    // What this repo DECLARES — the enumeration over all six
                    // collections, not just `variants`. A kind declared only
                    // through its sugar table (`[[build.kernel]]`, and the same
                    // for the others) lands in its own collection, so matching
                    // `variants` alone made a valid declaration unresolvable:
                    // the CLI listed it and the pane answered "no declaration
                    // names it". MEASURED 2026-08-22 with a `[[build.kernel]]`
                    // fixture: zero rows.
                    || r.set.declared_names().contains(&name)
                    || r.set.variants.iter().any(|v| {
                        v.package.as_deref() == Some(name)
                            || v.bin.as_deref() == Some(name)
                            || v.member.as_deref() == Some(name)
                    })
            })
            .collect()
    }

    /// Build the [`Menu`] for one checkout, recording the cell behind every column.
    ///
    /// The column strip is the union of the delivery targets the repo's own
    /// declarations name, in first-appearance order; a row that does not declare one
    /// carries it greyed. Order and spelling both come from the file.
    fn menu_for(&mut self, repo_name: &str, how: String) -> Menu {
        // The road is part of the answer: a matrix read off the table says so, and
        // says why the code did not answer (gap #9 — never a silent second path).
        self.how = Some(how.clone());
        let mut how = format!("{how} · {}", self.road_line_for(repo_name));
        // **THE DOOR VALIDATES WHAT IT IS ABOUT TO ACT ON** — wave 3, lane W7.
        //
        // ⚒ and the prompt have shared their LOADER since gap #9: both take
        // `build_thing::code::take`, both fall back to the toml BY NAME through
        // `BuildRoad`. What they did not share was the judgement. `read_through_code`
        // has refused a set that will not validate since `35729c2`
        // (`dwarves-buildthing-4`); this pane loaded the same set down the same road
        // and asked nothing, so a declaration the prompt refuses by name rendered
        // here as a matrix — the silent second path law B forbids, arriving through
        // agreement about loading rather than disagreement about it.
        //
        // `refuse_unvalidated` is that door's own gate, lifted out of it. Whichever
        // road the set came down is judged here, before a verb is drawn.
        self.invalid = None;
        if let Some(r) = self.builds.repo(repo_name) {
            if let Err(e) = dwarves::buildthing::refuse_unvalidated(r) {
                let why = format!("{e:#}");
                eprintln!("[dwarves] deklarationen vägrad vid inläsning: {why}");
                how.push_str(&format!(" · ⚠ VÄGRAD VID INLÄSNING: {why}"));
                self.note = Some(why.clone());
                self.invalid = Some(why);
            }
        }
        let repo = self
            .builds
            .repo(repo_name)
            .expect("caller resolved this repo");
        let set = repo.set.clone();
        let root = repo.root.clone();

        // Pass 1 — every column any row declares, in first-appearance order.
        let mut columns: Vec<(String, ArtifactKind)> = Vec::new();
        let mut per_row: Vec<(String, Vec<(String, Cell)>)> = Vec::new();
        for v in &set.variants {
            let mut mine: Vec<(String, Cell)> = Vec::new();
            let own = (
                v.kind.as_str().to_string(),
                Cell {
                    name: v.name.clone(),
                    kind: v.kind,
                    params: Vec::new(),
                    fixtures: BTreeMap::new(),
                    options: Vec::new(),
                },
            );
            mine.push(own);
            for (label, cell) in chained(&set, &v.name) {
                mine.push((label, cell));
            }
            for (label, cell) in &mine {
                if !columns.iter().any(|(c, _)| c == label) {
                    columns.push((label.clone(), cell.kind));
                }
            }
            per_row.push((v.name.clone(), mine));
        }

        // Pass 1b — the SUGAR rows. `[[build.kernel]]` is documented as
        // "permanent sugar" for `kind = "kernel"`, exactly like the iso and
        // installer tables, and a sugar table carries its handle in its own NAME
        // rather than hanging off a cargo variant (a kernel is built from a
        // pinned tarball, so there is no row for it to chain onto). Without this
        // the two spellings of ONE declaration disagreed: the long way got a
        // clickable cell and the documented way got nothing at all.
        for k in &set.kernels {
            if per_row.iter().any(|(n, _)| n == &k.name) {
                continue;
            }
            let label = ArtifactKind::Kernel.as_str().to_string();
            let cell = Cell {
                name: k.name.clone(),
                kind: ArtifactKind::Kernel,
                params: Vec::new(),
                fixtures: BTreeMap::new(),
                options: Vec::new(),
            };
            if !columns.iter().any(|(c, _)| c == &label) {
                columns.push((label.clone(), ArtifactKind::Kernel));
            }
            per_row.push((k.name.clone(), vec![(label, cell)]));
        }

        // Pass 2 — every row gets every column; the ones it did not declare are greyed.
        self.cells.clear();
        let mut rows = Vec::new();
        for (row_name, declared) in per_row {
            // `None` for a sugar row: there is no cargo variant behind it, and
            // the badges/settings a variant would have carried come from the
            // declaration itself instead.
            let v = set.variants.iter().find(|v| v.name == row_name);
            let targets = columns
                .iter()
                .map(|(label, kind)| {
                    let mine = declared.iter().find(|(l, _)| l == label);
                    if let Some((_, cell)) = mine {
                        self.cells.insert(key(&row_name, label), cell.clone());
                    }
                    Target {
                        name: label.clone(),
                        kind: mine.map_or(*kind, |(_, c)| c.kind),
                        pane: None,
                        declared: mine.is_some(),
                        variant: mine.map(|(_, c)| c.name.clone()).filter(|n| n != &row_name),
                        settings: mine
                            .map(|(_, c)| {
                                let mut out = match v {
                                    Some(v) => settings_of(v, c),
                                    None => {
                                        vec![Setting::fixed("kind", c.kind.as_str(), "deklarerad")]
                                    }
                                };
                                out.extend(kernel_settings(&set, c));
                                out.extend(donor_settings(&set, c));
                                out
                            })
                            .unwrap_or_default(),
                    }
                })
                .collect();
            let badges = match v {
                Some(v) => badges_of(v),
                None => Vec::new(),
            };
            rows.push(Row {
                name: row_name,
                badges,
                targets,
            });
        }

        self.repo = Some(repo_name.to_string());
        Menu {
            coordinate: coordinate_of(repo_name, &root),
            how,
            rows,
        }
    }

    // ── the dependency axis ───────────────────────────────────────────────────

    /// **The picked row's feature↔dependency axis**, derived off the frame.
    ///
    /// Cheap on every call after the first for a given row: the worker is started
    /// once, its answer is kept, and a row change throws the previous one away
    /// rather than showing a picture of a different assembly.
    ///
    /// # Φ is the features THIS ROW BUILDS WITH
    ///
    /// Not the repo's space, and not the row's open switches either. The picture
    /// answers "what are the features of this build holding up, and what would
    /// dropping one cost" — so the clickable universe is exactly the set the build
    /// will enable. A feature the row leaves open is OFF until somebody turns it on;
    /// putting it on the axis would draw a package as present because of a feature
    /// this build does not have, which is a graph that is wrong rather than partial.
    fn axis_for(&mut self, pick: &AxisPick) -> Axis {
        if self.axis_work.as_ref().is_some_and(|w| &w.pick != pick) {
            // A worker for a selection nobody is looking at any more. Dropping
            // the receiver does not stop the thread, but nothing waits on it and
            // its result is never shown.
            self.axis_work = None;
        }
        if self.axis_work.is_none() {
            self.axis_work = Some(self.start_axis(pick));
        }
        let work = self.axis_work.as_mut().expect("just set");
        if let Some(done) = &work.done {
            return done.clone();
        }
        match work.rx.as_ref().map(std::sync::mpsc::Receiver::try_recv) {
            Some(Ok(axis)) => {
                work.rx = None;
                work.done = Some(axis.clone());
                axis
            }
            // The sender was dropped without answering — a panicked worker. Said
            // out loud: a silent fall back to "no axis" would read exactly like a
            // repo that has none.
            Some(Err(std::sync::mpsc::TryRecvError::Disconnected)) => {
                let why = "beroendegrafen kunde inte räknas ut: arbetstråden dog".to_string();
                work.rx = None;
                work.done = Some(Axis::Failed(why.clone()));
                Axis::Failed(why)
            }
            _ => Axis::Working,
        }
    }

    /// Start the worker for one selection, or record outright why there will be
    /// no axis.
    ///
    /// **The picture is of the artifact the SELECTION produces**, not of the row
    /// label. Two corrections live here, and both were defects:
    ///
    /// * an installer/iso column packs a variant, and it is THAT variant's parts
    ///   the picture is about. Keyed on the row alone, an installer row (which
    ///   declares no features of its own) settled on `bygger utan features` and
    ///   drew nothing, while the app row beside it drew — the intermittency
    ///   The owner saw as "the picture sometimes flickers in".
    /// * the features are the RESOLVED ones. A picture derived from the declared
    ///   set shows the WireGuard parts of a build that is about to be made
    ///   without them, which is a picture of an artifact that will not exist.
    fn start_axis(&self, pick: &AxisPick) -> AxisWork {
        let settled = |a: Axis| AxisWork {
            pick: pick.clone(),
            rx: None,
            done: Some(a),
        };

        let Some(repo) = self.current() else {
            return settled(Axis::Absent);
        };
        // Which variant this selection actually builds: the column's packed
        // appliance when a delivery column is picked, else the row itself.
        let cell = pick.target.as_deref().and_then(|t| self.cell(&pick.row, t));
        let subject = cell
            .and_then(|c| self.packed_variant(&repo.set, c))
            .unwrap_or_else(|| pick.row.clone());
        let Some(v) = repo.set.variants.iter().find(|v| v.name == subject) else {
            // A row with no cargo variant behind it and no column that names
            // one. Not a failure: there is genuinely nothing to draw.
            return settled(Axis::Absent);
        };
        let package = v.package.clone().unwrap_or_else(|| repo.repo.clone());
        // The RESOLVED feature set — the declaration minus every opt-out the
        // operator has unticked, through the same `nornir_build_thing::options`
        // resolution the CLI door and the forge use. A second subtraction rule
        // here would be a second answer to "what is in this build".
        let declared = cell.map(|c| c.options.as_slice()).unwrap_or(&[]);
        let params = cell.map(|c| c.params.as_slice()).unwrap_or(&[]);
        let features: Vec<String> =
            match nornir_build_thing::options::resolve_options(declared, params, &pick.toggles) {
                Ok(r) => r.app_features(v),
                // A recipe whose options do not validate is the recipe's problem
                // and is refused loudly at build time; the picture falls back to
                // the declaration rather than inventing a set.
                Err(_) => v.features.clone(),
            };
        let phi: std::collections::BTreeSet<String> = features.into_iter().collect();
        if phi.is_empty() {
            return settled(Axis::Failed(format!(
                "{subject} bygger utan features, så det finns ingen axel att klicka på"
            )));
        }

        let mut req = AxisRequest::new(repo.repo.clone(), repo.root.clone()).package(package);
        req.checkout_root = Some(self.builds.root.clone());
        req.features = Some(phi);
        // ── LAZY ON CLICK ────────────────────────────────────────────────────
        //
        // The minimal-cause hunt is the entire cost of this picture: MEASURED at
        // 10-16 s per crate over 98 removable crates, which at the old default
        // budget of 200 is 17-27 MINUTES. The manifest scan everyone suspected is
        // 6-30 s of that — under 2%.
        //
        // So nothing is hunted until it is asked for. The press lands in seconds
        // with the whole graph, the ⊥ wall and the clickable features on it, and
        // each crate the operator clicks buys exactly its own hunt. `pick.focus`
        // is what those clicks accumulate to; a fresh pick has none, and
        // `max_analyses` follows it exactly, so the budget can never quietly
        // spend itself on a crate nobody asked about.
        req.focus = pick.focus.iter().cloned().collect();
        req.max_analyses = pick.focus.len();

        let (tx, rx) = std::sync::mpsc::channel();
        std::thread::spawn(move || {
            let answer = match feature_axis(&req, None) {
                Ok(a) => Axis::Ready(Box::new(a)),
                Err(e) => Axis::Failed(e.to_string()),
            };
            // The scene may have moved on; nobody is owed an error for that.
            let _ = tx.send(answer);
        });
        AxisWork {
            pick: pick.clone(),
            rx: Some(rx),
            done: None,
        }
    }

    /// The cargo variant a delivery column PACKS — an installer's appliance, an
    /// iso's `from_variant`, a container layer's. `None` for a plain cargo
    /// column, whose subject is its own row.
    fn packed_variant(
        &self,
        set: &nornir_build_thing::variant::BuildVariantSet,
        cell: &Cell,
    ) -> Option<String> {
        match cell.kind {
            ArtifactKind::Installer => set
                .installers
                .iter()
                .find(|i| i.name == cell.name)
                .map(|i| i.variant.clone())
                .filter(|v| !v.is_empty()),
            ArtifactKind::Iso => set
                .isos
                .iter()
                .find(|i| i.name == cell.name)
                .and_then(|i| i.from_variant.clone()),
            ArtifactKind::Container => set
                .containers
                .iter()
                .find(|c| c.name == cell.name)
                .and_then(|c| c.layers.iter().find_map(|l| l.from_variant.clone())),
            ArtifactKind::Vm => set
                .vms
                .iter()
                .find(|v| v.name == cell.name)
                .and_then(|v| v.from_variant.clone()),
            _ => None,
        }
    }

    // ── the verbs ─────────────────────────────────────────────────────────────

    /// Step 5: hand the picked cell to the one build worker there is.
    fn start_build(&mut self, row: &str, target: &str, settings: &BTreeMap<String, String>) {
        self.outcome = None;
        self.note = None;
        self.take_live();
        // The load-time refusal is the prompt's; this door does not get to act past it.
        if let Some(why) = self.invalid.clone() {
            self.note = Some(format!("deklarationen vägras vid inläsning: {why}"));
            return;
        }
        let Some(cell) = self.cell(row, target).cloned() else {
            self.note = Some(format!("{row} · {target} är inte deklarerad"));
            return;
        };
        let Some(repo) = self.current().cloned() else {
            return;
        };
        // **THE PICKED PRESET IS THE BUILT PRESET.** The override lands on the
        // declaration this build reads, not on an argv beside it, because that is the
        // only place a kernel's contents are decided — see `kernel_override`.
        let repo = match kernel_override(&repo, &cell, settings) {
            Some((overridden, line)) => {
                eprintln!("[dwarves] {line}");
                overridden
            }
            None => repo,
        };
        let params = declared_param_values(&cell, settings);
        let mut opts = declared_option_values(&cell, settings);
        // The kernel vetoes the operator clicked in the graph ride as the `--opt
        // kernel.<symbol>=off` the CLI door speaks — the flag on screen IS the flag the
        // build gets (law C). `kernel_override` carries the preset the same way.
        opts.extend(
            settings
                .iter()
                .filter(|(k, _)| k.starts_with("kernel."))
                .map(|(k, v)| format!("{k}={}", if v == "true" { "on" } else { "off" })),
        );
        if !params.is_empty() {
            eprintln!("[dwarves] {} params: {}", cell.name, params.join(" "));
        }
        if !opts.is_empty() {
            eprintln!("[dwarves] {} opts: {}", cell.name, opts.join(" "));
        }
        // The OPEN cargo features the operator ticked ride as `--features` — the flag
        // the node shows is the flag the build gets (law C), the same choice
        // `dwarves buildthing --features` and the forge's BuildThing RPC carry.
        let cargo = declared_cargo_features(&repo, &cell.name, settings);
        if !cargo.is_empty() {
            eprintln!("[dwarves] {} cargo: {}", cell.name, cargo.argv().join(" "));
        }
        self.last_launch = Some(cell.clone());
        self.run = Some(BuildRun::spawn_with_choice(
            &repo, &cell.name, params, opts, cargo,
        ));
    }

    /// Bring the pane's subject up: a container run, or a guest boot.
    fn launch(&mut self) {
        let (Some(cell), Some(repo)) = (self.last_launch.clone(), self.current().cloned()) else {
            return;
        };
        let root = repo.root;
        self.take_live();
        self.live = Some(match cell.kind {
            ArtifactKind::Container => {
                BootRun::spawn_container(&cell.name, root, self.containers.clone())
            }
            // **▶ boots what BYGG built.** The medium goes in HERE, from the finished
            // build's own outcome, because this is the only place that knows both the
            // cell and the artifact. Until 2026-08-16 the installer arm fell through to
            // `spawn_hw` below, which takes no artifact, and the launcher re-mastered a
            // korp-shaped scaffold into its own temp root — so a holger BYGG that FAILED
            // and produced nothing still reached `INSTALL-OK`, and all three products'
            // captured framebuffers came back md5-identical.
            ArtifactKind::Installer => BootRun::spawn_installer(
                &cell.name,
                root,
                self.boots.clone(),
                HwConfig::default(),
                self.composed_medium(),
            ),
            // An iso-kind variant boots the medium BYGG composed the same way the
            // installer does — read off the SAME `Outcome` the pane shows (gap #7:
            // until 2026-08-20 this arm carried no medium, so a composed
            // gunnar-appliance-iso was refused as unwired and offered the stand-in).
            ArtifactKind::Iso => BootRun::spawn_iso(
                &cell.name,
                root,
                self.boots.clone(),
                HwConfig::default(),
                self.composed_medium(),
            ),
            _ => BootRun::spawn_hw(&cell.name, root, self.boots.clone(), HwConfig::default()),
        });
    }

    /// **The medium the last successful build composed**, or `None`.
    ///
    /// Read off the same [`Outcome`] the pane SHOWS, so the operator's eye and the boot
    /// cannot disagree about which file this is. Three things must hold and each is
    /// checked, because each has been false in a real run: the build succeeded, it named
    /// an artifact, and the artifact is on disk. A failed build's outcome carries
    /// `ok=false` and an empty artifact — and a ▶ that boots anything after that is not
    /// booting this product.
    fn composed_medium(&self) -> Option<PathBuf> {
        let o = self.outcome.as_ref()?;
        if !o.ok || o.artifact.trim().is_empty() {
            return None;
        }
        let p = PathBuf::from(&o.artifact);
        p.is_file().then_some(p)
    }

    /// **Run the built artifact INSIDE A CONTAINER**, streaming its output into the pane.
    ///
    /// Not a child process against the host. The artifact is bind-mounted read-only into
    /// a fixed base image with no network, and the container runs the in-container path —
    /// so the same build behaves the same way on every machine and cannot write over
    /// anything outside itself.
    ///
    /// It goes through [`BootRun::spawn_container`] because that is already the one
    /// console contract there is: one worker, one [`crate::boot_run::pump`], one log
    /// window, one kill path. An Exec run is a container run with a different launcher,
    /// not a second mechanism.
    fn run_artifact(&mut self, argv: &str) {
        let Some(path) = self.outcome.as_ref().map(|o| PathBuf::from(&o.artifact)) else {
            return;
        };
        if !path.is_file() {
            self.note = Some(format!("{} finns inte på disk", path.display()));
            return;
        }
        let Some(repo) = self.current().cloned() else {
            return;
        };
        let sandbox = ExecSandbox::new(path, argv.split_whitespace().map(str::to_string).collect());
        // The sandbox is SHOWN, not implied: an operator reading stdout has to be able to
        // see that it came out of a container and which one.
        self.note = Some(sandbox.banner());
        let label = self
            .last_launch
            .as_ref()
            .map_or("artefakt", |c| c.name.as_str())
            .to_string();
        self.take_live();
        self.live = Some(BootRun::spawn_container(
            &label,
            repo.root,
            Arc::new(ArtifactExecLauncher { sandbox }),
        ));
    }

    /// Fold the finished build into an [`Outcome`]. Reads only what the report holds:
    /// a build that produced nothing shows nothing, never a plausible path.
    fn settle(&mut self) {
        let Some(run) = self.run.as_mut() else { return };
        run.poll();
        let Some(report) = run.report.clone() else {
            return;
        };
        if self.outcome.is_some() {
            return;
        }
        let ports = self
            .last_launch
            .as_ref()
            .and_then(|c| self.current().map(|r| (c.name.clone(), r.set.clone())))
            .map(|(name, set)| {
                set.containers
                    .iter()
                    .find(|c| c.name == name)
                    .map(|c| c.expose.clone())
                    .unwrap_or_default()
            })
            .unwrap_or_default();
        // **Read the receipt back out of what was just built, and check it.** One
        // `audit_artifact` does both: it lifts `.nordisk.buildthing` out with the
        // object parser and measures every claim in it against the binary's own
        // symbol table. Nothing is executed — which is why this works on the musl
        // cross build this host would not run, on a stripped one, and on a hostile
        // one.
        let paths: Vec<&Path> = report.artifacts.iter().map(|(p, _)| p.as_path()).collect();
        let (receipt, proof) = match audit_first_readable(&paths) {
            Some((r, p)) => (r, Some(p)),
            None => (None, None),
        };
        self.outcome = Some(Outcome::from_report(&report, ports, receipt, proof));
        if !report.errors.is_empty() {
            self.note = Some(report.errors.join("\n"));
        }
    }
}

/// **Which of a build's artifacts the receipt audit is ABOUT.**
///
/// For every cargo cell there is one artifact and this is the identity function.
/// It exists for the kinds that produce a MEDIUM.
///
/// An installer ISO is not an object file and never will be. Pointing
/// [`audit_artifact`] at `gunnar-installer.iso` answers `NOT MEASURED — Unknown
/// file magic` — MEASURED on oden 2026-08-16, and the sentence is *true*. It is
/// simply about the wrong subject: an ISO carries no `.nordisk.buildthing` section
/// and no symbol table, so there is nothing there to agree or disagree with a
/// receipt, and no amount of work on the audit will ever change that.
///
/// The receipt that matters for an installer is the one compiled into the program
/// it LAYS DOWN. `assemble_cell::compose_installer_row` lists that binary as this
/// cell's second artifact for exactly this reason, so the subject is simply **the
/// first listed artifact the audit can read**, and the artifact the operator is
/// shown stays the medium.
///
/// # Why this is not a way of making an ISO pass
///
/// It never suppresses a verdict. A list whose artifacts are all unreadable still
/// answers on the FIRST one, so a medium shipped without its program still says
/// NOT MEASURED — which is what a `[[build.iso]]` live-appliance cell (one
/// artifact, the medium) still answers today. Only a build that genuinely produced
/// a readable program gets a verdict, and that verdict is free to come out
/// `Disproven`.
fn audit_first_readable(artifacts: &[&Path]) -> Option<(Option<Receipt>, Proof)> {
    let mut fallback = None;
    for p in artifacts {
        let (receipt, proof) = audit(p);
        if proof.verdict != ReceiptVerdict::NotMeasured {
            return Some((receipt, proof));
        }
        fallback.get_or_insert((receipt, proof));
    }
    fallback
}

/// Read an artifact's receipt and measure its claims against the artifact.
///
/// A free function rather than a method because it touches nothing but the path:
/// that is what lets [`the_audit_reports_every_outcome_it_has_a_name_for`] point it
/// at files it built itself and watch every verdict come out, including the ones
/// that must never read as a pass.
///
/// [`the_audit_reports_every_outcome_it_has_a_name_for`]: tests::the_audit_reports_every_outcome_it_has_a_name_for
fn audit(artifact: &Path) -> (Option<Receipt>, Proof) {
    let a = audit_artifact(artifact);
    let proof = Proof {
        verdict: a.verdict(),
        lines: a.lines(),
    };
    (a.receipt.clone(), proof)
}

/// What [`DeclaredForge::plan_chain`] hands back: the repo's builds, the planned
/// steps, the `--opt` line, the params and the kernel decision.
type PlannedChain = (
    RepoBuilds,
    Vec<dwarves::build_steps::BuildStep>,
    Vec<String>,
    Vec<String>,
    Option<nornir_build_thing::kernel_features::KernelDecision>,
);

impl DeclaredForge {
    /// **ONE derivation for both readers** — `tree()` and `try_toggle()` must look at the
    /// same tree, or the judge refuses a node the pane drew (measured 2026-08-26 on the
    /// real declaration: the pane derived the donor's kernel across the constellation,
    /// the judge derived it without the donor, and `kernel.pci` was "not a setting").
    /// The picked preset lands on the declaration, exactly as `kernel_override` puts it
    /// on the build's; the donor of an edge is looked up in ITS checkout.
    fn derived_tree(
        &self,
        variant: &str,
        preset: Option<Preset>,
    ) -> Option<nornir_build_thing::tree::SettingsTree> {
        use nornir_build_thing::tree::SettingsTree;
        let mut set = self.current()?.set.clone();
        if let Some(p) = preset {
            for k in &mut set.kernels {
                k.preset = p.as_str().to_string();
            }
        }
        if let Some(inst) = set.installers.iter().find(|i| i.name == variant) {
            if let Some(d) = &inst.kernel_donor {
                let mut donor = self
                    .builds
                    .repo(&d.repo)
                    .and_then(|r| r.set.kernels.iter().find(|k| k.name == d.kernel).cloned());
                if let (Some(k), Some(p)) = (donor.as_mut(), preset) {
                    k.preset = p.as_str().to_string();
                }
                return Some(SettingsTree::of_installer(inst, donor.as_ref()));
            }
        }
        SettingsTree::of_set(&set, variant)
    }

    /// **The chain a `BYGG <kant>` means, planned through the CLI door's own
    /// functions and nothing else.**
    ///
    /// Four calls, in the order `build_cells` makes them, because a second ordering
    /// is a second product:
    ///
    /// 1. `refuse_build_flags` — the grammar's judge. A target that is no edge is
    ///    refused with the same words on both doors (law B).
    /// 2. `expand_opts_for` — the `--opt` line, expanded and spelling-checked.
    /// 3. `resolve_kernel_axis` — the kernel half of the line judged against the
    ///    donor BEFORE anything spawns; the decision the pin is asked about.
    /// 4. `plan_build_steps` — the edge DAG (a consumer of edda's `BuildDag::derive`
    ///    since `7881fd7`, so the donor's donor is a step), then `describe_plan`,
    ///    whose sentences ARE the prompt's dry pass.
    ///
    /// The `--opt` line comes off the TREE's flags, not off a second reading of the
    /// settings: the flag on screen is the flag the build gets (law C), and the CLI
    /// line the node menu shows is spelled from the same list.
    fn plan_chain(&mut self, plan: &crate::build_steps::StepPlan) -> Result<PlannedChain, String> {
        if let Some(why) = &self.invalid {
            return Err(format!("deklarationen vägras vid inläsning: {why}"));
        }
        let Some(repo) = self.current().cloned() else {
            return Err("inget recept upplöst — sök en rot först".to_string());
        };
        let variant = plan.variant.as_str();
        let builds = std::slice::from_ref(&plan.edge_path);
        dwarves::buildthing::refuse_build_flags(&repo.set, &repo.repo, Some(variant), builds)
            .map_err(|e| format!("{e:#}"))?;
        // Every `--opt` the tree carries, donor's and consumer's alike: routing onto
        // the steps is the planner's job (the path prefix is the brace) and doing it
        // here as well would be the second implementation of one rule.
        //
        // **IN THE TREE'S ORDER, and deduped by first appearance.** The consuming
        // step's flags are the whole line in declaration order, so they lead. Sorting
        // them — which a `BTreeSet` here did for one commit — reorders the line the
        // kernel grammar applies IN ORDER, cascade and contract refusals included:
        // the same set of flags in another order is not guaranteed to be the same
        // decision, and the prompt would have been handed the declaration's order
        // while ⚒ handed an alphabetical one. Caught by
        // `robot_two_doors::the_chain_the_pane_plans_is_the_chain_the_prompt_plans`,
        // which compares the two doors' plan lines verbatim.
        // `StepPlan::routed_opts` is that rule's ONE writer — it is also what spells the
        // CLI line printed under the button, which is the only way the two can be
        // guaranteed to name the same build.
        let line = plan.routed_opts();
        // The consumer's `--param` line, off the same declared-parameter reader
        // `BYGG` and the printed line both use. A donor step carries none; its routed
        // `--opt` line is all it takes from this screen.
        let params = Forge::declared_params(self, variant, &plan.settings);
        let opts = dwarves::buildthing::expand_opts_for(&repo.set, Some(variant), &line, &params)
            .map_err(|e| format!("{e:#}"))?;
        let (decision, _) =
            dwarves::buildthing::resolve_kernel_axis(&repo.set, &repo.root, Some(variant), &opts)
                .map_err(|e| format!("{e:#}"))?;
        let steps = dwarves::build_steps::plan_build_steps(
            &repo.set,
            &repo.root,
            Some(variant),
            builds,
            &opts,
        )
        .map_err(|e| format!("{e:#}"))?;
        Ok((repo, steps, opts, params, decision))
    }

    /// The planned chain as the console's own plan — the steps with their
    /// coordinates, typs and routed flags, each wearing the planner's own line.
    fn staged(
        plan: &crate::build_steps::StepPlan,
        steps: &[dwarves::build_steps::BuildStep],
        lines: &[String],
    ) -> crate::build_steps::StepPlan {
        let planned: Vec<(String, ArtifactKind, Vec<String>)> = steps
            .iter()
            .map(|s| {
                (
                    s.coordinate().to_string(),
                    s.edge.typ,
                    s.opts.iter().map(|o| format!("--opt {o}")).collect(),
                )
            })
            .collect();
        let consumer_typ = plan.steps.last().map_or(ArtifactKind::Exe, |s| s.typ);
        let consumer_flags = plan
            .steps
            .last()
            .map(|s| s.flags.clone())
            .unwrap_or_default();
        crate::build_steps::StepPlan::from_planned(
            plan.product.as_deref(),
            &plan.variant,
            consumer_typ,
            &plan.edge_path,
            &plan.coordinate,
            consumer_flags,
            &planned,
            lines,
            plan.cli.clone(),
            plan.params.clone(),
        )
    }
}

impl Forge for DeclaredForge {
    fn axis(&mut self, pick: &AxisPick) -> Axis {
        self.axis_for(pick)
    }

    fn resolve(&mut self, query: &Query) -> Result<Menu, String> {
        self.run = None;
        self.outcome = None;
        self.note = None;
        self.axis_work = None;
        self.take_live();
        match query {
            Query::Artifact(name) => {
                let hits: Vec<String> = self
                    .candidates(name)
                    .iter()
                    .map(|r| r.repo.clone())
                    .collect();
                match hits.as_slice() {
                    // ── AN EMPTY ROSTER IS ITS OWN ANSWER, NOT A MISS ────────
                    //
                    // "ingen deklaration nämner gunnar" is true of a root that
                    // holds 40 checkouts and true of a root that holds none,
                    // and only one of those is the operator's fault. MEASURED
                    // 2026-08-22: launched from ~/git with no
                    // $DWARVES_CONSTELLATION, `default_root()` returns the
                    // PARENT (~), the roster is empty, and every
                    // search reads as "that artifact does not exist here".
                    [] if self.builds.repos.is_empty() => Err(format!(
                        "0 repon deklarerar ett byggrum under {} — sätt \
                         $DWARVES_CONSTELLATION till katalogen som håller \
                         utcheckningarna sida vid sida",
                        self.builds.root.display()
                    )),
                    [] => Err(format!(
                        "ingen deklaration under {} nämner {name:?} ({} repo(n) \
                         deklarerar ett byggrum där)",
                        self.builds.root.display(),
                        self.builds.repos.len()
                    )),
                    [one] => {
                        let how = if one == name {
                            "hela receptet".to_string()
                        } else {
                            "löst ur namnet".to_string()
                        };
                        let one = one.clone();
                        self.take_code_road_unless_building(&one);
                        Ok(self.menu_for(&one, how))
                    }
                    many => Err(format!(
                        "{name:?} deklareras av flera utcheckningar: {} — skriv namnet på \
                         den du menar",
                        many.join(", ")
                    )),
                }
            }
            Query::At(c) => {
                let repo = c
                    .krate
                    .strip_suffix(RECIPE_SUFFIX)
                    .unwrap_or(&c.krate)
                    .to_string();
                if self.builds.repo(&repo).is_none() {
                    if self.builds.repos.is_empty() {
                        return Err(format!(
                            "0 repon deklarerar ett byggrum under {} — sätt \
                             $DWARVES_CONSTELLATION till katalogen som håller \
                             utcheckningarna sida vid sida",
                            self.builds.root.display()
                        ));
                    }
                    return Err(format!("ingen utcheckning heter {repo:?}"));
                }
                self.take_code_road_unless_building(&repo);
                Ok(self.menu_for(&repo, format!("angiven koordinat {c}")))
            }
        }
    }

    /// **The kconfig configurator, hosted by ⚒.**
    ///
    /// Three answers, and every one of them is a sentence:
    ///
    /// * a kernel cell whose `[[build.kernel]]` is there ⇒ the graph, its preset
    ///   mirrored from the settings map so the leaf and the picker cannot disagree;
    /// * a kernel cell whose recipe is MISSING ⇒ the same named refusal
    ///   [`kernel_settings`] gives, for the same reason: a kernel is two declarations
    ///   that must agree by name, and a blank is indistinguishable from a typo;
    /// * anything else ⇒ a refusal that says what this constellation actually
    ///   declares. **Exactly one repo declares a `[[build.kernel]]` today**, so a
    ///   leaf that rendered an empty pane on the other fifteen would be a blank for
    ///   almost every operator who opens the tab. The names are read off the
    ///   discovered checkouts rather than typed here, so the sentence cannot rot the
    ///   day a second repo declares one.
    fn leaf(&mut self, row: &str, target: &str, settings: &BTreeMap<String, String>) -> Leaf<'_> {
        let Some(cell) = self.cell(row, target).cloned() else {
            return Leaf::None;
        };
        if cell.kind != ArtifactKind::Kernel {
            // An INSTALLER that consumes a donor kernel gets the sharper sentence:
            // the cell HAS a kernel — a declared, pin-verified one — it just is not
            // configured here. Naming the donor turns "this is not a kernel" into
            // "the choice is made in the donor's configurator", which is the actual
            // answer to the question the operator opened this leaf with.
            if cell.kind == ArtifactKind::Installer {
                if let Some(d) = self
                    .current()
                    .and_then(|r| r.set.installers.iter().find(|i| i.name == cell.name))
                    .and_then(|i| i.kernel_donor.clone())
                {
                    return Leaf::Refused(format!(
                        "`{row} · {target}` bygger ingen kärna SJÄLV — den KONSUMERAR \
                         donatorkärnan `{}/{}` ([build.installer.kernel_donor]). Valet \
                         görs i `{}`s konfigurator; BYGG här verifierar den byggda \
                         kärnan mot sin pin och vägrar en obyggd eller avvikande",
                        d.repo, d.kernel, d.repo
                    ));
                }
            }
            let declaring: Vec<&str> = self
                .builds
                .repos
                .iter()
                .filter(|r| !r.set.kernels.is_empty())
                .map(|r| r.repo.as_str())
                .collect();
            let who = if declaring.is_empty() {
                "ingen utcheckning i konstellationen deklarerar en".to_string()
            } else {
                format!("deklareras av: {}", declaring.join(", "))
            };
            return Leaf::Refused(format!(
                "`{row} · {target}` bygger ingen kärna — det här bladet konfigurerar en \
                 [[build.kernel]], och {who}"
            ));
        }
        let Some(set) = self.current().map(|r| r.set.clone()) else {
            return Leaf::None;
        };
        let Some(k) = set.kernels.iter().find(|k| k.name == cell.name).cloned() else {
            let declared = if set.kernels.is_empty() {
                "inga alls".to_string()
            } else {
                set.kernels
                    .iter()
                    .map(|k| k.name.as_str())
                    .collect::<Vec<_>>()
                    .join(", ")
            };
            return Leaf::Refused(format!(
                "INGEN DEKLARATION för `{}` — en [[build.kernel]] med det namnet saknas; \
                 deklarerade: {declared}",
                cell.name
            ));
        };
        // The kconfig solver's answer for THIS kernel, if a build left one: decided
        // before the pane is borrowed, applied after.
        let why = self
            .current()
            .map(|r| nornir_build_thing::kernel::kernel_output_dir(&r.root, &k))
            .and_then(|out| self.kernel_why_changed(out, &k.name));
        // Lazy: the first kernel cell an operator picks pays for the layout, and a
        // shell nobody opens this tab in pays nothing.
        let pane = self.kernel.get_or_insert_with(KernelScene::embedded);
        if let Some(read) = why {
            pane.set_why_read(read);
        }
        // ── ONE VALUE, ONE CONTROL ───────────────────────────────────────────
        // The settings map is the source of truth (it is what rides into
        // `Act::Build`), so the leaf is re-pointed from it every frame; the
        // embedded pane draws no preset strip of its own. `set_preset` and
        // `set_profile` both return early on no change, so a settled frame does no
        // work — the graph must never be relaid on the frame that draws it.
        let want = settings
            .get(PRESET_KEY)
            .and_then(|v| Preset::parse(v.trim()))
            .or_else(|| Preset::parse(&k.preset));
        if let Some(p) = want {
            pane.set_preset(p);
        }
        if let Some(p) = HardeningProfile::parse(&k.profile) {
            pane.set_profile(p);
        }
        // …and because the settings map IS the control, the pane's own strip stays off.
        pane.set_chrome(false);
        Leaf::Pane(pane)
    }

    /// **The configurator with nothing picked** — the question the retired ⚙ Kernel tab
    /// was the only home for: *"what is in a `slim` kernel?"*, asked before an operator
    /// knows which row they want.
    ///
    /// The SAME instance [`leaf`](Forge::leaf) hands back, with its own preset strip
    /// turned ON because no host control is pointing at it this frame. Two instances
    /// would be two presets and the operator would pick in one and build the other; one
    /// instance in two mutually exclusive states is one preset with one control on it.
    ///
    /// ── BUT IT IS THE RECIPE THAT DECIDES, NOT THE TAB ──────────────────────────
    ///
    /// This used to hand back the configurator UNCONDITIONALLY, and that is wrong for
    /// the same reason the ⚙ tab was wrong: *"varför skall kernel dyka upp jämt? det
    /// beror ju på receptet"* (owner, 2026-08-26). A recipe that builds a library
    /// declares no kernel and must not offer to configure one — the standing question
    /// *"what is in a `slim` kernel"* is only standing for a recipe that HAS one.
    ///
    /// MEASURED across the constellation on the day this was written: of 16 checkouts
    /// declaring a build space, exactly **one** (`tunnr`) declares a `[[build.kernel]]`.
    /// So the unconditional pane was the wrong answer for 15 recipes out of 16, and the
    /// one it was right for was not the one being looked at.
    ///
    /// Three answers, because there are three states and the seam already has the words
    /// for them — collapsing any two is what put a blank pane on screen:
    ///
    /// * **no recipe picked** → [`Leaf::None`]. The scene says "välj en rad och en
    ///   kolumn"; a configurator here would be pointing at nothing.
    /// * **a recipe that declares no kernel** → [`Leaf::Refused`], NAMING the recipe.
    ///   Not `None`: "this library has no kernel" and "you have picked nothing" are
    ///   different facts, and a leaf that renders identically for both is the defect.
    /// * **a recipe that declares one** → the configurator, chrome on, as before.
    fn browse(&mut self) -> Leaf<'_> {
        // Answer from the DECLARATION before borrowing the pane: `self.kernel` is
        // borrowed mutably for the rest of the function, and the roster read has to
        // finish first.
        let Some(repo) = self.repo.clone() else {
            return Leaf::None;
        };
        let declared: Vec<String> = match self.current() {
            Some(r) => r.set.kernels.iter().map(|k| k.name.clone()).collect(),
            None => return Leaf::None,
        };
        if declared.is_empty() {
            // A donor-consuming installer is the third state: the recipe builds no
            // kernel, but one of its media BOOTS a declared one. The refusal names
            // it, because "this repo has no kernel" is a false answer for a repo
            // whose installer ships tunnr's.
            if let Some((iname, d)) = self.current().and_then(|r| {
                r.set
                    .installers
                    .iter()
                    .find_map(|i| i.kernel_donor.clone().map(|d| (i.name.clone(), d)))
            }) {
                return Leaf::Refused(format!(
                    "`{repo}` deklarerar ingen [[build.kernel]] — men `{iname}` \
                     KONSUMERAR donatorkärnan `{}/{}`. Valet görs i `{}`s \
                     konfigurator; BYGG på `{iname}` verifierar den byggda kärnan \
                     mot sin pin",
                    d.repo, d.kernel, d.repo
                ));
            }
            return Leaf::Refused(format!(
                "`{repo}` deklarerar ingen [[build.kernel]] — det här bladet \
                 konfigurerar en kärna, och det här receptet bygger ingen"
            ));
        }
        let pane = self.kernel.get_or_insert_with(KernelScene::embedded);
        // ONE control on the preset, browsing or not: the settings tree's `kernel.preset`
        // val is it (gap #6 — the canvas is the `kärna` node's DETAIL, and a strip of
        // its own beside the tree's would be two widgets with one accessible name).
        pane.set_chrome(false);
        Leaf::Pane(pane)
    }

    /// The kernel leaf's PRESENCE: does the resolved recipe have a KERNEL ANSWER —
    /// a `[[build.kernel]]` of its own, or an installer that binds a donor's kernel
    /// (`[build.installer.kernel_donor]`, the gunnar → tunnr/tunnr-kernel road)?
    ///
    /// The same declaration read [`browse`](Forge::browse) opens with, but
    /// side-effect-free: `browse` lazily builds the configurator and flips its chrome,
    /// which a per-frame presence ask must never do. The donor case answers `true`
    /// because the leaf's surface for it — the donor, NAMED, and where the choice is
    /// made — IS that recipe's kernel answer: hiding it would tell a gunnar operator
    /// their medium boots no kernel, which is false. A recipe with neither (a plain
    /// library) answers `false`, and no recipe answers `false` — those two are the
    /// states with no kernel answer at all, and the leaf is ABSENT rather than a frame
    /// full of refusal.
    fn kernel_declared(&self) -> bool {
        self.repo.is_some()
            && self.current().is_some_and(|r| {
                !r.set.kernels.is_empty()
                    || r.set.installers.iter().any(|i| i.kernel_donor.is_some())
            })
    }

    /// **The settings tree for this selection** — edda's `SettingsTree::of_set` over the
    /// resolved declaration (the CODE's set when the wasm answered, the table's else),
    /// with the operator's preset folded into the kernel declaration first, because the
    /// preset decides which `kernel.<symbol>` features exist at all.
    ///
    /// The donor of an installer's edge is looked up ACROSS the constellation (`tunnr`'s
    /// `tunnr-kernel` for gunnar's installer): the edge's coordinate is `repo/variant`,
    /// and the repo is another checkout. `of_set` alone looks only in its own set and
    /// would derive the donor's symbols from a default preset.
    ///
    /// Nothing picked ⇒ the standing question's tree: the recipe's own kernel, else the
    /// installer that carries the edge, else an empty tree. No recipe ⇒ `None`.
    fn tree(
        &mut self,
        row: Option<&str>,
        target: Option<&str>,
        settings: &BTreeMap<String, String>,
    ) -> Option<nornir_build_thing::tree::SettingsTree> {
        use nornir_build_thing::tree::SettingsTree;
        let repo = self.current()?.clone();
        // ── THE CODE ANSWERS FIRST (stage D): `options(variant, "")` over wasm ──────
        // The loaded build thing's own tree, when the code road is open and the
        // operator has not moved the preset off the declaration — `OptionsQuery`
        // carries no values yet, so a moved preset (which changes WHICH symbols
        // exist) is derived locally from the same declaration until edda's query
        // grows a `vals` map. Said here so the two roads cannot be confused.
        let preset_moved = settings.get(PRESET_KEY).is_some_and(|v| {
            let want = v.trim();
            !repo.set.kernels.iter().any(|k| k.preset == want) && Preset::parse(want).is_some()
        });
        if let (Some(r), Some(t), false) = (row, target, preset_moved) {
            if let Some(name) = self.cell(r, t).map(|c| c.name.clone()) {
                if let Some(code) = self.code.as_mut() {
                    match code.handle.options(&name, "") {
                        Ok(Some(tree)) => return Some(tree),
                        Ok(None) => {}
                        Err(e) => eprintln!(
                            "[dwarves] kod · options({name}): {e:#} — härleder ur deklarationen"
                        ),
                    }
                }
            }
        }
        let set = repo.set.clone();
        let preset = settings
            .get(PRESET_KEY)
            .and_then(|v| Preset::parse(v.trim()));
        let variant: String = match (row, target) {
            (Some(r), Some(t)) => self.cell(r, t)?.name.clone(),
            _ => {
                // The standing question: the variant that carries the `kärna` feature.
                if let Some(k) = set.kernels.first() {
                    k.name.clone()
                } else if let Some(i) = set.installers.iter().find(|i| i.kernel_donor.is_some()) {
                    i.name.clone()
                } else {
                    return Some(SettingsTree {
                        variant: String::new(),
                        typ: ArtifactKind::Exe,
                        roots: Vec::new(),
                    });
                }
            }
        };
        self.derived_tree(&variant, preset)
    }

    /// **The judge** — the loaded build thing's `try_toggle` over wasm when the code
    /// road is open (a donor's build thing must answer for its own kernel), the derived
    /// tree's otherwise. One judge per click, never two.
    fn try_toggle(
        &mut self,
        req: &nornir_build_thing::tree::ToggleRequest,
    ) -> nornir_build_thing::tree::ToggleDecision {
        if let Some(code) = self.code.as_mut() {
            match code.handle.try_toggle(req) {
                Ok(d) => return d,
                Err(e) => eprintln!(
                    "[dwarves] kod · try_toggle({}): {e:#} — dömer ur deklarationen",
                    req.path
                ),
            }
        }
        // The request's variant, derived the SAME way `tree()` derives it — donor and
        // preset included (the preset rides in `req.vals`).
        let preset = req
            .vals
            .get("kernel.preset")
            .or_else(|| req.vals.get("preset"))
            .and_then(|v| Preset::parse(v.trim()));
        let derived = self.derived_tree(&req.variant, preset);
        match derived {
            Some(t) => t.try_toggle(req),
            None => nornir_build_thing::tree::ToggleDecision {
                path: req.path.clone(),
                on: req.on,
                verdict: nornir_build_thing::plugin::Verdict::refuse(format!(
                    "ingen variant `{}` i det upplösta receptet",
                    req.variant
                )),
                cascade: Vec::new(),
                refuses: Vec::new(),
                activates: Vec::new(),
            },
        }
    }

    /// **The verbs** — the loaded build thing's `actions(kind)` over wasm when the code
    /// road is open (an impl may refine: UNPACK), edda's table otherwise.
    fn actions(&mut self, kind: ArtifactKind) -> Vec<nornir_build_thing::verbs::Action> {
        if let Some(code) = self.code.as_mut() {
            match code.handle.actions(kind) {
                Ok(a) => return a,
                Err(e) => eprintln!(
                    "[dwarves] kod · actions({}): {e:#} — tabellen svarar",
                    kind.as_str()
                ),
            }
        }
        nornir_build_thing::verbs::actions_for(kind)
    }

    /// The product word: the resolved repo's name — `dwarves buildthing <repo>`.
    fn product(&self) -> Option<String> {
        self.current().map(|r| r.repo.clone())
    }

    /// The DECLARED parameters among the site values, off the cell's own declaration —
    /// the ONE reader both the sent plan and the printed CLI line take their `--param`
    /// line from.
    fn declared_params(
        &mut self,
        variant: &str,
        settings: &BTreeMap<String, String>,
    ) -> Vec<String> {
        self.cells
            .values()
            .find(|c| c.name == variant)
            .map(|c| declared_param_values(c, settings))
            .unwrap_or_default()
    }

    /// **The dry pass** — the chain planned, the pin asked, nothing spawned.
    fn plan_edge(
        &mut self,
        plan: &crate::build_steps::StepPlan,
    ) -> Result<crate::build_steps::StepPlan, String> {
        let (_repo, steps, _opts, _params, decision) = self.plan_chain(plan)?;
        let lines = dwarves::build_steps::describe_plan(&steps, decision.as_ref())
            .map_err(|e| format!("{e:#}"))?;
        for l in &lines {
            eprintln!("[dwarves] plan · {l}");
        }
        Ok(Self::staged(plan, &steps, &lines))
    }

    /// **`BYGG <kant>` — the chain, run.**
    ///
    /// Planned by [`plan_edge`](Forge::plan_edge)'s own road (one planner), then
    /// handed to `dwarves::build_steps::run_build_chain_watched` on a worker thread:
    /// the same registry, the same runa, the same `assemble_cell` the prompt's
    /// `--build` reaches. This door is a second READER of that run, never a second
    /// runner — which is the whole of law B here, and the reason the wave-3 stub
    /// (`missing_runner()` on both arms, naming a `BuildRun::spawn_chain` that has
    /// never existed) could not simply be replaced with a build of its own.
    ///
    /// Answers the plan it started — the DAG, not the click's two steps.
    fn build_edge(
        &mut self,
        plan: &crate::build_steps::StepPlan,
    ) -> Result<crate::build_steps::StepPlan, String> {
        let (repo, steps, opts, params, decision) = self.plan_chain(plan)?;
        let lines = dwarves::build_steps::describe_plan(&steps, decision.as_ref())
            .map_err(|e| format!("{e:#}"))?;
        let staged = Self::staged(plan, &steps, &lines);
        // The run's rows go to the same warehouse a `dwarves run` files under, so the
        // pipeline DAG viewer draws this chain like any other run.
        let warehouse = dwarves::store::warehouse_from(
            std::env::var("DWARVES_WAREHOUSE").ok().as_deref(),
            std::env::var("HOME").ok().as_deref(),
            dwarves::store::is_system_scope(),
        );
        let variant = plan.variant.clone();
        let refusal_on = steps
            .first()
            .map_or_else(|| plan.coordinate.clone(), |s| s.coordinate().to_string());
        self.chain = Some(crate::build_steps::ChainRun::spawn(
            refusal_on,
            move |watch| {
                dwarves::build_steps::run_build_chain_watched(
                    &repo,
                    &repo.repo,
                    &variant,
                    steps,
                    decision.as_ref(),
                    &opts,
                    &params,
                    Some(&warehouse),
                    Some(watch),
                )
                .map(|_| ())
                .map_err(|e| format!("{e:#}"))
            },
        ));
        Ok(staged)
    }

    /// **The chain's steps as the runner has reported them so far** — drained once a
    /// frame, never blocking. A chain that refused before its first step comes back
    /// as that step refused by name, which is [`ChainRun::poll`]'s own doing.
    fn steps(&mut self) -> Option<Vec<crate::build_steps::StepReport>> {
        let reports = self.chain.as_mut()?.poll();
        if let Some(why) = self
            .chain
            .as_mut()
            .and_then(crate::build_steps::ChainRun::take_note)
        {
            self.note = Some(format!("BYGG <kant>: {why}"));
        }
        Some(reports)
    }

    fn act(&mut self, act: Act) {
        match act {
            Act::Build {
                row,
                target,
                settings,
            } => self.start_build(&row, &target, &settings),
            Act::Run(argv) => self.run_artifact(&argv),
            Act::Start => self.launch(),
            Act::Stop => self.take_live(),
            Act::Reboot => {
                self.take_live();
                self.launch();
            }
        }
    }

    fn poll(&mut self) -> Report {
        self.settle();
        let menu = self.settle_code();
        let (phase, elapsed) = match (&self.run, &self.outcome) {
            (None, _) => (None, 0),
            (Some(r), None) => (Some(Phase::Building), r.elapsed_ms()),
            (Some(r), Some(o)) => (
                Some(if o.ok {
                    Phase::Done
                } else {
                    Phase::Failed(
                        r.report
                            .as_ref()
                            .map_or_else(|| "bygget misslyckades".to_string(), |x| x.headline()),
                    )
                }),
                r.elapsed_ms(),
            ),
        };
        let (mut live, mut screen) = (false, None);
        let lines = if let Some(b) = &self.live {
            live = b.is_running();
            screen = b.frame().map(|f| Screen {
                width: f.width,
                height: f.height,
                rgba: f.rgba,
            });
            b.log()
        } else if let Some(r) = &self.run {
            r.log.clone()
        } else if let Some(b) = &self.code_build {
            // The recipe crate's wasm build IS a build; the console shows it.
            b.log.clone()
        } else {
            self.code_log.clone()
        };
        Report {
            phase,
            elapsed_ms: elapsed,
            outcome: self.outcome.clone(),
            live,
            lines,
            screen,
            note: self.note.clone(),
            menu,
        }
    }
}

// ── declaration → data ────────────────────────────────────────────────────────

/// **Which boot engine a Console pane drives**, from `$DWARVES_INSTALLER_ENGINE` —
/// the launcher AND the name it goes by, out of ONE match arm.
///
/// Carried over from the pane this scene replaced, because without it the real RAID
/// installer engine is not merely unrecorded but UNREACHABLE from the UI — the scene
/// would always take the default single-disk path and no operator could ask for the
/// other one. Unset, or any other value, is the default: the shipping UI never changes
/// engine silently.
///
/// **Why one function and not two (L5, and the guard that could not go red).** This
/// used to be a `boot_launcher` returning the `Arc` and a separate `boot_engine_name`
/// returning the string — two independent copies of the same three-arm match, and the
/// test watched the STRING one. MEASURED 2026-08-23: renaming only `boot_launcher`'s
/// arm to `Some("edda_raid")` makes `DWARVES_INSTALLER_ENGINE=edda-raid` fall through
/// to the default launcher — the RAID engine unreachable from the UI, which is the one
/// defect the test's own doc says it exists to catch — and the test still reported
/// `ok`. A name that mirrors a decision is not the decision. Now the name is produced
/// BY the arm that produces the launcher, so asserting the name asserts the arm, and
/// the two can no longer disagree. The `$DWARVES_INSTALLER_ENGINE` read is also done
/// once at the call site rather than twice, which is the same fault one level up.
fn boot_engine(engine: Option<&str>) -> (Arc<dyn BootLauncher>, &'static str) {
    match engine {
        Some("edda-raid") => (
            Arc::new(crate::boot_run::EddaRaidInstallerLauncher::default()),
            "edda-raid",
        ),
        _ => (Arc::new(crate::boot_run::TunnrBootLauncher), "tunnr"),
    }
}

/// The map key for one cell of the matrix. A unit separator, so a column label
/// containing a colon cannot collide with a row name.
fn key(row: &str, target: &str) -> String {
    format!("{row}\u{1f}{target}")
}

/// The delivery cells that chain off `variant`, labelled `<kind>:<cell>`.
///
/// A repo may declare two containers off one atom, so the cell name has to be part of
/// the label; the kind leads so the column strip reads as an axis.
fn chained(
    set: &nornir_build_thing::variant::BuildVariantSet,
    variant: &str,
) -> Vec<(String, Cell)> {
    let mut out = Vec::new();
    for c in &set.containers {
        if c.layers
            .iter()
            .any(|l| l.from_variant.as_deref() == Some(variant))
        {
            out.push((
                format!("container:{}", c.name),
                Cell {
                    name: c.name.clone(),
                    kind: ArtifactKind::Container,
                    params: Vec::new(),
                    fixtures: BTreeMap::new(),
                    options: Vec::new(),
                },
            ));
        }
    }
    for v in &set.vms {
        if v.from_variant.as_deref() == Some(variant) {
            out.push((
                format!("vm:{}", v.name),
                Cell {
                    name: v.name.clone(),
                    kind: ArtifactKind::Vm,
                    params: Vec::new(),
                    fixtures: BTreeMap::new(),
                    options: Vec::new(),
                },
            ));
        }
    }
    for i in &set.isos {
        if i.from_variant.as_deref() == Some(variant) {
            out.push((
                format!("iso:{}", i.name),
                Cell {
                    name: i.name.clone(),
                    kind: ArtifactKind::Iso,
                    params: i.params.clone(),
                    fixtures: i.fixtures.clone(),
                    options: i.options.clone(),
                },
            ));
        }
    }
    for i in &set.installers {
        // WHICH ROW AN INSTALLER HANGS OFF. `installer_variant` is the cargo build that
        // produces the PID-1 riding the ISO; `variant` is the appliance the installer
        // LAYS DOWN. Matching only the former made the column unreachable for every
        // installer actually declared in this constellation — gunnar's and holger's both
        // name `variant` and leave `installer_variant` unset (it is `#[serde(default)]`,
        // so it is the empty string, which equals no row), so the installer was declared,
        // listed by the CLI, and had no clickable column in the matrix at all. MEASURED
        // 2026-08-16: `installer:gunnar-installer` resolved to no widget.
        //
        // A declared `installer_variant` still wins outright — korp's shape is unchanged
        // — and the fallback is taken ONLY when the field is absent, so one installer never
        // lands on two rows.
        let hangs_off = if i.installer_variant.is_empty() {
            i.variant == variant
        } else {
            i.installer_variant == variant
        };
        if hangs_off {
            out.push((
                format!("installer:{}", i.name),
                Cell {
                    name: i.name.clone(),
                    // `Installer`, NOT `Iso`. edda added the variant precisely because
                    // "until this variant existed the attestation spelled both `iso`, so
                    // an auditor could not tell them apart" — a live ISO *is* the
                    // appliance, an installer ISO *produces* one. Spelling it `Iso` here
                    // re-created in the pane the exact conflation the kind was added to end.
                    kind: ArtifactKind::Installer,
                    // The site values this cell DECLARES. Carried verbatim so the scene
                    // renders whatever the recipe says and nothing it invented.
                    params: i.params.clone(),
                    // …and the fixtures it declares for them, the same way.
                    fixtures: i.fixtures.clone(),
                    // …and the OPT-OUTS. These decide the rest of the block:
                    // which of the parameters above are asked for at all, and
                    // which cargo features the packed app binary is built with.
                    options: i.options.clone(),
                },
            ));
        }
    }
    out
}

/// The marks a row carries, straight out of the declaration and in its order:
/// the suites it ships in, its role, whether it is the default for that role, and how
/// it links to its siblings. Nothing is derived and nothing is added.
fn badges_of(v: &BuildVariant) -> Vec<String> {
    let mut out: Vec<String> = v.suites.clone();
    if let Some(role) = &v.role {
        out.push(format!("{role:?}").to_lowercase());
    }
    if v.is_default {
        out.push("default".to_string());
    }
    out
}

/// The settings one cell resolves: the cargo axes the declaration fixed, plus the
/// switches **that row** declares open.
///
/// Every value shown is the one the build will use. A setting the declaration pinned
/// is shown fixed, with the reason; a feature the row declares `open` is shown as a
/// toggle the operator may flip.
///
/// # Why the row's `open`, and not the repo's `offers`
///
/// Every row of this matrix is an ASSEMBLY, and the two are not interchangeable:
/// `offers` is the SPACE the repo's declarations may draw from — the set the
/// declaration validator rejects a stray feature against — while `open` is what one
/// particular assembly leaves switchable. Reading the space as a switch list put a
/// toggle for every feature ANY row could use on EVERY row: a GPU checkbox on a
/// server that has only ever had a CPU path.
///
/// Greying it there instead of dropping it is not the fix. A greyed column is honest
/// because the column strip is a MAP — every delivery target the repo has, so an
/// operator can see the one this row lacks. A setting is not a map of anything; it is
/// the argv this build will run with. A control the recipe never declared has no
/// business in that list in either state, which is the same reason no row grows an
/// `rpm` column nobody declared.
/// The declared kernel facts a `kernel` cell carries — the ones that decide what
/// comes out. `nuked` and `slim` differ by ~86 KiB and by what an appliance can
/// do at all, so a cell that showed the KIND and hid the PRESET would be showing
/// the half that decides nothing. Empty for every other kind, and empty for a
/// `kind = "kernel"` row with no matching declaration (which is legal: the
/// variant row is a leaf, and the sugar may live in another suite's file).
/// **The kernel a donor-consuming installer cell shows for itself** — the fact
/// row that makes the cell's kernel VISIBLE in ⚒.
///
/// An installer with a `[build.installer.kernel_donor]` boots a kernel some
/// OTHER checkout declares, and until this row existed the pane said nothing
/// about it: the operator saw dhcp/wireguard toggles and site params while the
/// medium's kernel — the one thing the cell cannot toggle — was decided off
/// screen. One fixed row names the donor and where the choice is made; the
/// choice itself is the DONOR's configurator (the same two-step the CLI walks:
/// build the kernel where it is declared, then BYGG here consumes the built,
/// pin-verified artifact — `assemble_cell::installer_kernel` refuses a stale or
/// missing one by name).
fn donor_settings(set: &nornir_build_thing::variant::BuildVariantSet, cell: &Cell) -> Vec<Setting> {
    if cell.kind != ArtifactKind::Installer {
        return Vec::new();
    }
    let Some(d) = set
        .installers
        .iter()
        .find(|i| i.name == cell.name)
        .and_then(|i| i.kernel_donor.as_ref())
    else {
        return Vec::new();
    };
    vec![Setting::fixed(
        "kärna",
        format!("{}/{}", d.repo, d.kernel),
        format!(
            "donator — valet görs i `{}`s konfigurator; BYGG verifierar mot dess pin",
            d.repo
        ),
    )]
}

fn kernel_settings(
    set: &nornir_build_thing::variant::BuildVariantSet,
    cell: &Cell,
) -> Vec<Setting> {
    if cell.kind != ArtifactKind::Kernel {
        return Vec::new();
    }
    let Some(k) = set.kernels.iter().find(|k| k.name == cell.name) else {
        // **A KERNEL CELL WITH NO KERNEL DECLARATION SAYS SO.** Returning an empty Vec
        // here rendered a cell headed "Kernel" with nothing under it and no reason —
        // indistinguishable from a kernel that simply declares no settings, and from a
        // pane that failed to load.
        //
        // It is reachable by a plain typo, because a kernel recipe is TWO declarations
        // that must agree by name: a `[[build.kernel]]` row and a
        // `[[build.variant]] kind = "kernel"` row pointing at it. Misspell either and
        // the matrix still shows the column — the variant is what puts it there — while
        // the recipe it names is never found.
        //
        // Naming the miss costs one row and turns an unexplained blank into the actual
        // answer. The declared names are listed because "not found" without "here is
        // what IS declared" sends the reader to grep for something they cannot spell.
        let declared = if set.kernels.is_empty() {
            "inga alls".to_string()
        } else {
            set.kernels
                .iter()
                .map(|k| k.name.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        };
        return vec![Setting::fixed(
            "kernel",
            format!("INGEN DEKLARATION för `{}`", cell.name),
            format!("en [[build.kernel]] med det namnet saknas — deklarerade: {declared}"),
        )];
    };
    // ── THE PRESET IS A CONTROL NOW, NOT A LABEL ─────────────────────────────
    //
    // It was `Setting::fixed(…, "deklarerad")` — four true words the operator could
    // read and not act on, with the machine that produces them eleven tabs away in ⚙
    // Kernel. The owner went looking for the configurator in ⚒ three separate times;
    // three times the answer was "it is in the Kernel tab, by design". Build Thing is
    // where you choose what gets built, so the choice is here.
    //
    // Open, therefore it lands in `Act::Build`'s settings map like every other open
    // setting — and `kernel_override` below turns that into the declaration the build
    // actually runs from. A picker that changed the picture and not the build would be
    // strictly worse than the label it replaced.
    //
    // The BASE rides in the chosen entry's note rather than in a row of its own,
    // because it is derived from the preset and a separate row would freeze at the
    // DECLARED preset's base the moment the operator picked another one. Unless the
    // declaration pins `base` explicitly — then the preset does NOT decide it, every
    // note says so, and the pinned row is kept below.
    let choices: Vec<Choice> = PRESETS
        .iter()
        .map(|p| {
            let note = match &k.base {
                Some(b) => format!("bas {b} (pinnad i deklarationen — följer inte preset)"),
                None => format!("bas {}", p.base()),
            };
            Choice::new(p.as_str(), note)
        })
        .collect();
    let mut out = vec![
        Setting::choice(PRESET_KEY, k.preset.clone(), choices),
        Setting::fixed("version", k.version.clone(), "deklarerad"),
    ];
    if let Some(b) = &k.base {
        out.push(Setting::fixed("base", b.clone(), "pinnad i deklarationen"));
    }
    if !k.profile.is_empty() {
        out.push(Setting::fixed("profile", k.profile.clone(), "deklarerad"));
    }
    out
}

/// **The declaration the build ACTUALLY runs from**, once the operator's preset
/// pick is folded into it — or `None` when nothing was picked that changes it.
///
/// This is the whole point of moving the configurator into ⚒. `BuildRun::spawn_*`
/// clones `repo.set` and builds from that clone, and a kernel row is assembled from
/// its `[[build.kernel]]` declaration — so overriding the preset ON THE SET is
/// exactly, and only, overriding what gets built. Nothing downstream needs a new
/// parameter: the change is in the declaration the worker reads.
///
/// Returns `None` — leaving the declared build untouched — when the cell is not a
/// kernel, when nothing was picked, when the pick equals the declaration, or when
/// the pick is not a preset this kind knows. That last one is a refusal by
/// omission on purpose: `Preset::parse` is the same gate `KernelBuild::validate`
/// uses, and inventing a preset here would produce a build that fails deep inside
/// a toolchain container instead of a picker that never offered the word.
fn kernel_override(
    repo: &RepoBuilds,
    cell: &Cell,
    settings: &BTreeMap<String, String>,
) -> Option<(RepoBuilds, String)> {
    if cell.kind != ArtifactKind::Kernel {
        return None;
    }
    let want = Preset::parse(settings.get(PRESET_KEY)?.trim())?;
    let i = repo.set.kernels.iter().position(|k| k.name == cell.name)?;
    let had = repo.set.kernels[i].preset.clone();
    if had == want.as_str() {
        return None;
    }
    let mut repo = repo.clone();
    repo.set.kernels[i].preset = want.as_str().to_string();
    let line = format!(
        "{} · preset {had} → {} (vald i ⚒, bas {})",
        cell.name,
        want.as_str(),
        repo.set.kernels[i].base(),
    );
    Some((repo, line))
}

fn settings_of(v: &BuildVariant, cell: &Cell) -> Vec<Setting> {
    let mut out = vec![Setting::fixed("kind", cell.kind.as_str(), "deklarerad")];
    out.push(Setting::fixed(
        "profile",
        format!("{:?}", v.profile).to_lowercase(),
        "deklarerad",
    ));
    if let Some(t) = &v.target {
        out.push(Setting::fixed("target", t.clone(), "deklarerad"));
    }
    if let Some(m) = &v.member {
        out.push(Setting::fixed("member", m.clone(), "deklarerad"));
    }
    for f in &v.features {
        out.push(Setting::fixed(f.clone(), "true", "i atomen"));
    }
    for f in &v.open {
        // A feature the row both enables and opens is refused by the declaration
        // validator; the guard here is so a set built in code rather than parsed
        // cannot render one key twice.
        if !v.features.contains(f) {
            out.push(Setting::open(f.clone(), "false"));
        }
    }
    // ── THE OPT-OUTS, AT THE TOP OF THE INPUTS BLOCK ─────────────────────────
    //
    // Before the values, because they decide which values are asked for at all:
    // untick DHCP and the static address appears; untick WireGuard and the hub
    // declaration goes away. An operator who typed a hub.conf and then unticked
    // WireGuard would otherwise be looking at a field whose value the door
    // refuses BY NAME.
    //
    // These are NOT the row's `open` switches above. An `open` feature flips a
    // cargo feature and nothing else; an option ALSO governs parameters and
    // bakes a cmdline selector, and its default is the recipe's (ON, for an
    // opt-out) rather than the `false` an open switch starts at.
    for o in &cell.options {
        out.push(Setting::option(o));
    }
    // ── THE SITE VALUES THIS CELL DECLARES ───────────────────────────────────
    //
    // In the declaration's own order, carrying its own label/kind/required. This is
    // the whole of the pane's per-site input: the scene knows no product by name, so
    // holger's four (site · hub_conf · holger_ron · underlay) and korp's two appear
    // because those cells DECLARE them, and a product that declares none renders no
    // inputs at all.
    //
    // The same `ParamSpec` list the CLI door reads for `--param`, so the pane and
    // `dwarves buildthing --installer` cannot drift into asking for different things.
    // The sharper SHAPE an option gives its conditional parameters. The rows
    // themselves stay at a spelling every published loader knows (`kind =
    // "text"`), because `ParamSpec` is deny_unknown_fields and `ParamKind` is a
    // closed enum in the 0.1.7 reader a parity mirror loads through; the real
    // shape (`ipv4`, `ipv4-cidr`) is declared inside the options table, which
    // that reader skips whole. Applying it here is what makes the pane refuse
    // exactly what the door refuses — `ResolvedOptions::effective_specs` does
    // the same to the same map on the other side.
    let sharpened: BTreeMap<&str, nornir_build_thing::params::ParamKind> = cell
        .options
        .iter()
        .flat_map(|o| o.param_kinds.iter().map(|(n, k)| (n.as_str(), *k)))
        .collect();
    for p in &cell.params {
        let p = match sharpened.get(p.name.as_str()) {
            Some(k) => {
                let mut p = p.clone();
                p.kind = *k;
                p
            }
            None => p.clone(),
        };
        let p = &p;
        let mut s = Setting::param(p, cell.fixtures.get(&p.name).copied());
        // Which toggle, if any, this parameter answers to. The rule is the
        // recipe's — `params_when_on` / `params_when_off` — read once, HERE, so
        // the pane cannot drift from the CLI door and the forge, which resolve
        // the same declaration through `nornir_build_thing::options`.
        s.governed = governing_option(&cell.options, &p.name);
        out.push(s);
    }
    out
}

/// The option that governs `param`, and the state in which it applies.
///
/// `None` for a parameter no option names, which is every parameter of every
/// recipe written before the opt-out axis existed — those are always shown.
/// A parameter named by TWO options is refused at declaration time by
/// `nornir_build_thing::options::validate_options`, so the first hit is the
/// only hit.
fn governing_option(
    options: &[nornir_build_thing::options::OptionSpec],
    param: &str,
) -> Option<Governed> {
    for o in options {
        if o.params_when_on.iter().any(|p| p == param) {
            return Some(Governed {
                option: o.name.clone(),
                shown_when: true,
            });
        }
        if o.params_when_off.iter().any(|p| p == param) {
            return Some(Governed {
                option: o.name.clone(),
                shown_when: false,
            });
        }
    }
    None
}

/// **What the operator typed into the cell's DECLARED parameter inputs**, as the
/// `name=value` argv `run_installer` already speaks.
///
/// Only the cell's own `[[build.*.params]]` names are read, in the declaration's order:
/// a settings map also carries the cargo axis (`kind`, `profile`, `target`, every open
/// feature), and forwarding those as parameters would offer a cell values it never
/// declared. An empty value is DROPPED rather than sent as `name=`, so a required
/// parameter left blank still produces the declaration's own refusal — naming the
/// parameter — instead of a validation error about an empty string.
///
/// Until 2026-08-16 this function did not exist and `Act::Build`'s `settings` were
/// discarded at the door: holger's ⚒ cell declares a REQUIRED `holger_ron`, the pane
/// rendered the input, and BYGG failed with `required parameter 'holger_ron' … was not
/// supplied` no matter what was typed into it.
fn declared_param_values(cell: &Cell, settings: &BTreeMap<String, String>) -> Vec<String> {
    let toggles = declared_option_states(cell, settings);
    cell.params
        .iter()
        .filter_map(|p| {
            // A parameter the toggles REFUSE is not forwarded, whatever is left
            // in the map. The operator may have typed a hub.conf and then
            // unticked WireGuard; sending it would be refused BY NAME at the
            // door, and the refusal would name a field that is no longer on the
            // screen.
            let g = governing_option(&cell.options, &p.name);
            if let Some(g) = &g {
                if toggles.get(&g.option).copied().unwrap_or(true) != g.shown_when {
                    return None;
                }
            }
            let v = settings.get(&p.name)?.trim();
            (!v.is_empty()).then(|| format!("{}={v}", p.name))
        })
        .collect()
}

/// The state of every opt-out this cell declares, as the operator left it —
/// falling back to the DECLARATION's default for a toggle nobody touched, never
/// to `false`. An opt-out defaulted to off would silently ship an appliance
/// stripped of capabilities nobody asked to remove.
fn declared_option_states(
    cell: &Cell,
    settings: &BTreeMap<String, String>,
) -> BTreeMap<String, bool> {
    cell.options
        .iter()
        .map(|o| {
            let on = match settings.get(&o.name).map(String::as_str) {
                Some("true") => true,
                Some("false") => false,
                _ => o.default,
            };
            (o.name.clone(), on)
        })
        .collect()
}

/// **The toggles as the `--opt name=on|off` argv the door already speaks.**
///
/// EVERY declared option is sent, including the ones still at their default:
/// the door would resolve an omitted toggle to the same default, but sending it
/// makes the build RECORD say the operator was shown the choice. `from_default`
/// on the applied record keeps the two apart.
/// **The cargo features ⚒ sends** — the cargo variant's OPEN features whose box is
/// ticked in `settings` (declared-on ones are in the variant already). Empty for a
/// row that is no cargo variant.
pub fn declared_cargo_features(
    repo: &RepoBuilds,
    variant: &str,
    settings: &BTreeMap<String, String>,
) -> nornir_build_thing::CargoFeatures {
    let mut cf = nornir_build_thing::CargoFeatures::default();
    if let Some(v) = repo.set.get(variant).filter(|v| v.kind.is_cargo()) {
        cf.add_features(v.open.iter().filter(|f| settings.get(f.as_str()).is_some_and(|x| x == "true")));
    }
    cf
}

fn declared_option_values(cell: &Cell, settings: &BTreeMap<String, String>) -> Vec<String> {
    declared_option_states(cell, settings)
        .into_iter()
        .map(|(name, on)| format!("{name}={}", if on { "on" } else { "off" }))
        .collect()
}

/// The coordinate a checkout's recipe would carry.
///
/// `hash` stays `None` on purpose: it is the hash of the recipe that ACTUALLY RAN, and
/// nothing has run yet. A number here would be a number in the receipt that no rebuild
/// could ever match.
fn coordinate_of(repo: &str, root: &Path) -> Coordinate {
    Coordinate {
        krate: format!("{repo}{RECIPE_SUFFIX}"),
        // `?` when the checkout declares no version: a virtual workspace with no
        // `[workspace.package]` has none to read, and `0.0.0` there would be a number a
        // rebuild could try to match. `?` cannot be mistaken for one.
        version: manifest_version(root).unwrap_or_else(|| "?".to_string()),
        hash: None,
    }
}

/// The `version` a checkout's root manifest declares, from `[workspace.package]` or
/// `[package]` — the FIRST `version = "…"` inside either table.
///
/// A line scan rather than a TOML parse, because the only question is which of two
/// named tables a key sits in, and a dependency on a parser to answer it would be the
/// larger change. `None` when there is no manifest, no such table, or no key: never a
/// guess.
fn manifest_version(root: &Path) -> Option<String> {
    let text = std::fs::read_to_string(root.join("Cargo.toml")).ok()?;
    let mut inside = false;
    for line in text.lines() {
        let l = line.trim();
        if l.starts_with('[') {
            inside = matches!(l, "[workspace.package]" | "[package]");
            continue;
        }
        if !inside {
            continue;
        }
        if let Some(rest) = l.strip_prefix("version") {
            let rest = rest.trim_start().strip_prefix('=')?.trim();
            let v = rest.trim_matches(|c| c == '"' || c == '\'');
            if !v.is_empty() && !v.starts_with('{') {
                return Some(v.to_string());
            }
        }
    }
    None
}

/// The shipped ⚒ Build Thing scene, over the constellation this forge stands in.
#[must_use]
pub fn build_scene_pane() -> BuildScene {
    BuildScene::new(Box::new(DeclaredForge::discover()))
}

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

    /// The constellation on this disk, if there is one — the tests below are about
    /// REAL declarations, so they skip rather than lie when there is nothing to read.
    fn forge() -> Option<DeclaredForge> {
        let f = DeclaredForge::discover();
        (!f.builds.repos.is_empty()).then_some(f)
    }

    /// **RED-when-broken: every outcome the audit has a name for comes out of a REAL
    /// file, and a MEDIUM is audited through the program it carries.**
    ///
    /// `audit`'s own doc has named this test since the receipt work landed and the
    /// test did not exist — a doc-level phantom guard, found on oden 2026-08-16 while
    /// answering why `gunnar-installer.iso` reports `EJ MÄTT`. It is written now
    /// because that question has an answer that must not rot: the ISO arm below is
    /// the exact shape the real medium has, and the two-artifact arm is the exact
    /// shape `assemble_cell::compose_installer_row` now produces.
    #[test]
    fn the_audit_reports_every_outcome_it_has_a_name_for() {
        let dir = std::env::temp_dir().join(format!("dwarves-audit-{}", std::process::id()));
        std::fs::create_dir_all(&dir).expect("a scratch dir for files this test writes itself");

        // ── NOT MEASURED — a MEDIUM. ─────────────────────────────────────────────
        // Not a hypothetical file: ISO-9660 puts `CD001` at 0x8001 and carries no
        // object-file magic anywhere, which is why `gunnar-installer.iso` answers
        // `Unknown file magic` and why no work on the audit will ever change that.
        let iso = dir.join("medium.iso");
        let mut image = vec![0u8; 0x8800];
        image[0x8001..0x8006].copy_from_slice(b"CD001");
        std::fs::write(&iso, &image).expect("write the medium");
        let (_, medium) = audit(&iso);
        assert_eq!(
            medium.verdict,
            ReceiptVerdict::NotMeasured,
            "an ISO has no symbol table, so it can only be NOT MEASURED: {:?}",
            medium.lines,
        );
        assert!(!medium.holds(), "and NOT MEASURED is never a pass");

        // A path that is not there at all lands in the same non-passing outcome
        // rather than in an early return that looks like silence.
        let (_, absent) = audit(&dir.join("was-never-built"));
        assert_eq!(absent.verdict, ReceiptVerdict::NotMeasured);

        // ── the PROGRAM — a real object file this box built. ─────────────────────
        // The running test binary: `object` parses it, it has a symbol table, and
        // `build.rs` put a `.nordisk.buildthing` section in it. Whatever verdict that
        // receipt earns, it is a MEASURED one — which is the whole difference.
        let me = std::env::current_exe().expect("the running test binary");
        let (_, program) = audit(&me);
        assert_ne!(
            program.verdict,
            ReceiptVerdict::NotMeasured,
            "the test binary must be readable as an object file: {:?}",
            program.lines,
        );

        // ── the SUBJECT. ─────────────────────────────────────────────────────────
        // A medium ALONE is all the audit has, and it still says so — this is the
        // arm that keeps the change below from being a way of making an ISO pass.
        let (_, alone) = audit_first_readable(&[iso.as_path()]).expect("one artifact, one answer");
        assert_eq!(alone.verdict, ReceiptVerdict::NotMeasured);

        // Listed BESIDE the program it carries, the answer is about the program.
        let (_, carried) = audit_first_readable(&[iso.as_path(), me.as_path()])
            .expect("two artifacts, one answer");
        assert_eq!(
            carried.verdict, program.verdict,
            "an installer is audited through the appliance it lays down, not through \
             the medium: {:?}",
            carried.lines,
        );
        let subject = me
            .file_name()
            .expect("a file name")
            .to_string_lossy()
            .into_owned();
        assert!(
            carried.lines.first().is_some_and(|l| l.contains(&subject)),
            "and the audit names the subject it actually read, so nobody reads the \
             verdict as being about the ISO: {:?}",
            carried.lines,
        );

        // Nothing produced, nothing claimed — never a verdict over an empty list.
        assert!(audit_first_readable(&[]).is_none());

        let _ = std::fs::remove_dir_all(&dir);
    }

    /// **RED-when-broken: an installer cell's DECLARED parameters become the pane's
    /// inputs — label, kind and required-ness straight out of the declaration.**
    ///
    /// This is the founding rule made testable. A hardcoded "WireGuard hub key" text
    /// box would pass any test that only asked "is there an input"; what must hold is
    /// that the inputs ARE the declaration, so a cell declaring different parameters
    /// renders different fields and a cell declaring none renders nothing.
    ///
    /// Driven through the private `settings_of`/`chained` against a synthetic set, so
    /// it needs no checkout and no 64 GiB ISO.
    /// **RED-when-broken: gunnar's REAL declaration must put BOTH delivery columns
    /// on the row that declares them — the live ISO and the installer.**
    ///
    /// Reads the actual `gunnar/.nornir/build-variants.toml` off this box rather
    /// than a fixture, because the defect being guarded is a mismatch between what
    /// the file says and what the matrix resolves. Skips by name (never silently)
    /// when the checkout is absent, so it cannot go quietly green elsewhere.
    #[test]
    fn gunnars_installer_and_iso_are_both_reachable_columns() {
        let root_buf = std::path::PathBuf::from(std::env::var_os("HOME").unwrap_or_default())
            .join("git/gunnar");
        let root = root_buf.as_path();
        if !root.join(".nornir/build-variants.toml").exists() {
            eprintln!(
                "SKIP: no gunnar checkout at {} — this guard needs the real file",
                root.display()
            );
            return;
        }
        let set = nornir_build_thing::variant::BuildVariantSet::discover(root)
            .expect("gunnar's declaration parses")
            .expect("gunnar declares a build space");

        // Every column any row declares — the same union pass 1 builds.
        let mut seen: Vec<String> = Vec::new();
        for v in &set.variants {
            seen.push(v.kind.as_str().to_string());
            for (label, _) in chained(&set, &v.name) {
                seen.push(label);
            }
        }
        seen.sort();
        seen.dedup();

        assert!(
            seen.iter().any(|c| c == "iso:gunnar-appliance-iso"),
            "the live-ISO column must exist; got {seen:?}"
        );
        assert!(
            seen.iter().any(|c| c == "installer:gunnar-installer"),
            "THE INSTALLER COLUMN MUST EXIST — an installer that is declared, listed by \
             the CLI and has no column is a capability the operator cannot reach; got {seen:?}"
        );

        // And they must be DISTINCT columns: conflating them re-creates the exact
        // ambiguity ArtifactKind::Installer was added to end.
        let iso_row: Vec<&str> = set
            .variants
            .iter()
            .filter(|v| {
                chained(&set, &v.name)
                    .iter()
                    .any(|(l, _)| l == "iso:gunnar-appliance-iso")
            })
            .map(|v| v.name.as_str())
            .collect();
        let inst_row: Vec<&str> = set
            .variants
            .iter()
            .filter(|v| {
                chained(&set, &v.name)
                    .iter()
                    .any(|(l, _)| l == "installer:gunnar-installer")
            })
            .map(|v| v.name.as_str())
            .collect();
        assert!(!iso_row.is_empty(), "the live ISO must hang off some row");
        assert!(!inst_row.is_empty(), "the installer must hang off some row");
        eprintln!("live ISO hangs off {iso_row:?}; installer hangs off {inst_row:?}");
    }

    /// Prints the matrix EXACTLY as the pane resolves it for gunnar — every column
    /// of every row with its declared/greyed state. A diagnostic, not a gate.
    #[test]
    fn print_gunnar_matrix() {
        let root =
            std::path::PathBuf::from(std::env::var_os("HOME").unwrap_or_default()).join("git");
        if !root.join("gunnar/.nornir/build-variants.toml").exists() {
            eprintln!("SKIP: no gunnar checkout");
            return;
        }
        let mut f = DeclaredForge::at(root);
        let menu = f.menu_for("gunnar", "probe".to_string());
        for row in &menu.rows {
            let lit: Vec<&str> = row
                .targets
                .iter()
                .filter(|t| t.declared)
                .map(|t| t.name.as_str())
                .collect();
            eprintln!("ROW {:<24} LIT: {:?}", row.name, lit);
        }
        let all: Vec<&str> = menu.rows[0]
            .targets
            .iter()
            .map(|t| t.name.as_str())
            .collect();
        eprintln!("ALL COLUMNS ({}): {:?}", all.len(), all);
    }

    #[test]
    fn a_cells_declared_params_become_the_panes_inputs() {
        use nornir_build_thing::params::{ParamKind, ParamSpec};

        let spec = |name: &str, label: &str, kind, required| ParamSpec {
            name: name.into(),
            label: label.into(),
            kind,
            required,
            boot_param: format!("korp.{name}"),
            staged_file: String::new(),
            ..Default::default()
        };
        let cell = Cell {
            name: "korp-installer".into(),
            kind: ArtifactKind::Installer,
            params: vec![
                spec("site", "Site name", ParamKind::Text, false),
                spec("hub_pubkey", "Hub public key", ParamKind::Base64Key, true),
            ],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        let v = BuildVariant {
            name: "korp-server".into(),
            ..Default::default()
        };
        let out = settings_of(&v, &cell);

        let site = out
            .iter()
            .find(|s| s.key == "site")
            .expect("the declared `site` renders");
        let hub = out
            .iter()
            .find(|s| s.key == "hub_pubkey")
            .expect("the declared `hub_pubkey` renders");

        // The DECLARATION's words, not the UI's.
        let d = site
            .declared
            .as_ref()
            .expect("a declared param carries its shape");
        assert_eq!(d.label, "Site name");
        assert!(!d.required);
        let d = hub
            .declared
            .as_ref()
            .expect("a declared param carries its shape");
        assert_eq!(d.label, "Hub public key");
        assert!(
            d.required,
            "the declaration said required, so the pane must refuse without it"
        );
        assert_eq!(d.kind, ParamKind::Base64Key);

        // Open by construction — a site value the operator types.
        assert!(
            !site.fixed && !hub.fixed,
            "declared params are the operator's to fill"
        );
        assert!(
            hub.value.is_empty(),
            "and start empty, never pre-filled with a guess"
        );

        // A cell that declares NOTHING renders no inputs at all.
        let bare = Cell {
            name: "korp-server".into(),
            kind: ArtifactKind::Exe,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        let none = settings_of(&v, &bare);
        assert!(
            none.iter().all(|s| s.declared.is_none()),
            "a product declaring no parameters renders no inputs: {none:?}",
        );
    }

    /// **The fixture affordance is DATA-DRIVEN**: a param whose cell declares a
    /// fixture for it carries the fixture's label into the scene; one that does
    /// not carries `None` — and `@fixture` typed there is refused by label (the
    /// red). A staged text file renders multi-line; a cmdline token does not.
    #[test]
    fn a_declared_fixture_reaches_the_panes_setting_and_only_there() {
        use nornir_build_thing::params::{FixtureKind, ParamKind, ParamSpec, FIXTURE_VALUE};
        let hub_conf = ParamSpec {
            name: "hub_conf".into(),
            label: "Hub declaration".into(),
            kind: ParamKind::Text,
            required: false,
            boot_param: String::new(),
            staged_file: "hub.conf".into(),
            ..Default::default()
        };
        let site = ParamSpec {
            name: "site".into(),
            label: "Site name".into(),
            kind: ParamKind::Text,
            required: false,
            boot_param: "korp.site".into(),
            staged_file: String::new(),
            ..Default::default()
        };
        let cell = Cell {
            name: "x-installer".into(),
            kind: ArtifactKind::Installer,
            params: vec![site, hub_conf],
            fixtures: BTreeMap::from([("hub_conf".to_string(), FixtureKind::WgHub)]),
            options: vec![],
        };
        let v = BuildVariant {
            name: "x-server".into(),
            ..Default::default()
        };
        let out = settings_of(&v, &cell);
        let hub = out
            .iter()
            .find(|s| s.key == "hub_conf")
            .unwrap()
            .declared
            .clone()
            .unwrap();
        let site = out
            .iter()
            .find(|s| s.key == "site")
            .unwrap()
            .declared
            .clone()
            .unwrap();
        assert_eq!(
            hub.fixture.as_deref(),
            Some("wg-hub"),
            "the declared fixture, by its label"
        );
        assert!(hub.multiline, "a staged text file is a multi-line value");
        assert_eq!(site.fixture, None, "no fixture was declared for site");
        assert!(!site.multiline, "a cmdline token is one line");
        hub.check(FIXTURE_VALUE)
            .expect("@fixture is legal where a fixture is declared");
        let red = site.check(FIXTURE_VALUE).unwrap_err();
        assert!(
            red.contains("ingen fixtur") && red.contains("Site name"),
            "{red}"
        );
        // And the argv the build is given carries the same word the CLI speaks.
        let chosen = BTreeMap::from([
            ("hub_conf".to_string(), FIXTURE_VALUE.to_string()),
            ("site".to_string(), "stockholm-7".to_string()),
        ]);
        assert_eq!(
            declared_param_values(&cell, &chosen),
            vec![
                "site=stockholm-7".to_string(),
                format!("hub_conf={FIXTURE_VALUE}")
            ]
        );
    }

    /// **RED-when-broken: the declared KIND is what validates, and the refusal names
    /// the field.** A `Base64Key` that accepted any text would let a mistyped hub key
    /// reach a 64 GiB ISO build and fail at boot instead of at the keystroke.
    #[test]
    fn a_declared_kind_validates_and_refuses_by_label() {
        use crate::build_scene::DeclaredParam;
        use nornir_build_thing::params::ParamKind;

        let key = DeclaredParam {
            label: "Hub public key".into(),
            kind: ParamKind::Base64Key,
            required: true,
            fixture: None,
            multiline: false,
        };
        assert!(key
            .check("HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=")
            .is_ok());
        for bad in [
            "not-a-key",
            "HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw",
            "",
            "  ",
        ] {
            let err = key.check(bad).expect_err(bad);
            assert!(
                err.contains("Hub public key"),
                "the refusal names the field: {err}"
            );
        }
        // Empty is "required", never "malformed" — different sentences for different faults.
        assert_eq!(key.check("").unwrap_err(), "Hub public key is required");

        let opt = DeclaredParam {
            label: "Site".into(),
            kind: ParamKind::Text,
            required: false,
            fixture: None,
            multiline: false,
        };
        assert!(opt.check("").is_ok(), "an optional blank is fine");

        let ep = DeclaredParam {
            label: "Endpoint".into(),
            kind: ParamKind::Endpoint,
            required: false,
            fixture: None,
            multiline: false,
        };
        assert!(ep.check("192.0.2.7:51820").is_ok());
        assert!(ep.check("192.0.2.7").is_err());

        let cidr = DeclaredParam {
            label: "Allowed".into(),
            kind: ParamKind::CidrList,
            required: false,
            fixture: None,
            multiline: false,
        };
        assert!(cidr.check("10.77.0.0/16, 10.99.0.1/32").is_ok());
        assert!(cidr.check("10.77.0.0/33").is_err());
        assert!(cidr.check("10.77.0.0").is_err());
    }

    /// **A KERNEL COLUMN WITH NO KERNEL RECIPE RENDERED A BLANK AND SAID NOTHING.**
    ///
    /// A kernel is TWO declarations that must agree by name: a `[[build.kernel]]` row
    /// carrying preset/version/profile, and a `[[build.variant]] kind = "kernel"` row
    /// that puts a column in the matrix. The variant is what makes the column appear, so
    /// misspell the pairing and the operator gets a column headed with the kernel's name
    /// and NOTHING under it — indistinguishable from a kernel that declares no settings,
    /// and from a pane that failed to load.
    ///
    /// That is not a hypothetical typo: `tunnr` is the only repo in the constellation
    /// that declares a kernel at all, so this pairing has exactly one live example and
    /// nothing has ever exercised getting it wrong.
    ///
    /// RED WHEN REVERTED: put the bare `return Vec::new();` back and the assertion below
    /// gets an empty vec.
    #[test]
    fn a_kernel_column_whose_recipe_is_missing_names_the_miss() {
        let dir = tempfile::tempdir().expect("tmp");
        // The variant declares a kernel column called `orten-kernel`; the recipe below
        // is called `orten-krnel`. One transposition, and they no longer meet.
        std::fs::write(
            dir.path().join("build-variants.toml"),
            r#"
[build]
[[build.kernel]]
name = "orten-krnel"
version = "6.12.104"
preset = "nuked"

[[build.variant]]
name = "orten-kernel"
suites = ["ORIGINAL"]
member = "orten"
package = "orten"
kind = "kernel"
linkage = "not-applicable"
"#,
        )
        .expect("write");
        let set = nornir_build_thing::variant::BuildVariantSet::load_file(
            &dir.path().join("build-variants.toml"),
        )
        .expect("load");

        let cell = Cell {
            name: "orten-kernel".into(),
            kind: ArtifactKind::Kernel,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        let out = kernel_settings(&set, &cell);
        assert!(
            !out.is_empty(),
            "a kernel column whose recipe is missing rendered NOTHING — the operator sees \
             a column named `orten-kernel` with no settings and no reason, which reads as \
             'this kernel has nothing to configure' rather than 'the recipe is misspelt'"
        );
        let text = format!("{out:?}");
        assert!(
            text.contains("orten-kernel"),
            "the miss must name the column that is missing its recipe: {text}"
        );
        assert!(
            text.contains("orten-krnel"),
            "and it must list what IS declared — 'not found' without the real names sends \
             the reader off to grep for a string they cannot spell: {text}"
        );
    }

    /// **THE GUARD ON THE GUARD.** A kernel column whose recipe IS present must render
    /// the recipe, not the refusal — otherwise the test above is satisfied by a function
    /// that always complains.
    #[test]
    fn a_kernel_column_with_its_recipe_renders_the_recipe() {
        let dir = tempfile::tempdir().expect("tmp");
        std::fs::write(
            dir.path().join("build-variants.toml"),
            r#"
[build]
[[build.kernel]]
name = "orten-kernel"
version = "6.12.104"
preset = "nuked"

[[build.variant]]
name = "orten-kernel"
suites = ["ORIGINAL"]
member = "orten"
package = "orten"
kind = "kernel"
linkage = "not-applicable"
"#,
        )
        .expect("write");
        let set = nornir_build_thing::variant::BuildVariantSet::load_file(
            &dir.path().join("build-variants.toml"),
        )
        .expect("load");
        let cell = Cell {
            name: "orten-kernel".into(),
            kind: ArtifactKind::Kernel,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        let text = format!("{:?}", kernel_settings(&set, &cell));
        assert!(
            text.contains("6.12.104"),
            "the declared version reaches the pane: {text}"
        );
        assert!(text.contains("nuked"), "and the declared preset: {text}");
        assert!(
            !text.contains("INGEN DEKLARATION"),
            "a recipe that IS declared must not be reported as missing: {text}"
        );
    }

    /// **The donor-consuming installer is VISIBLE, and both kernel leaves name the
    /// donor.** RED-FIRST 2026-08-26, the acceptance question in user terms:
    /// *"I search gunnar in ⚒ — do I get a buildthing with a KERNEL choice?"*
    /// Before this, the answer was a bare refusal ("`gunnar` deklarerar ingen
    /// [[build.kernel]]") and an installer settings block that said dhcp/wireguard
    /// and NOTHING about the one thing the medium boots. Now the cell's settings
    /// carry the donor as a fact row, and both the repo-level browse and the
    /// cell-level leaf answer with WHERE the choice is made instead of "no".
    #[test]
    fn a_donor_consuming_installer_shows_its_kernel_and_both_leaves_name_the_donor() {
        let dir = tempfile::tempdir().expect("tmp");
        let root = dir.path().join("consumer");
        std::fs::create_dir_all(root.join(".nornir")).expect("mkdir");
        std::fs::write(
            root.join("Cargo.toml"),
            "[package]\nname = \"consumer\"\nversion = \"1.0.0\"\nedition = \"2021\"\n",
        )
        .expect("manifest");
        std::fs::write(
            root.join(".nornir").join("build-variants.toml"),
            r#"
[build]
[[build.variant]]
name = "c-installer"
suites = ["ORIGINAL"]
kind = "installer"
role = "server"
default = false

[[build.installer]]
name = "c-installer"
variant = "c-app"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[[build.variant]]
name = "c-app"
suites = ["ORIGINAL"]
kind = "exe"
role = "server"
default = true
"#,
        )
        .expect("declaration");

        let mut forge = DeclaredForge::at(dir.path().to_path_buf());
        assert!(
            forge.builds.repos.iter().any(|r| r.repo == "consumer"),
            "the fixture repo must be discovered: {:?}",
            forge.declared_repos()
        );
        forge.repo = Some("consumer".to_string());

        // 1. THE FACT ROW: the installer cell's settings say which kernel it boots.
        let set = forge.current().expect("picked").set.clone();
        let cell = Cell {
            name: "c-installer".into(),
            kind: ArtifactKind::Installer,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        let rows = donor_settings(&set, &cell);
        assert_eq!(rows.len(), 1, "one donor row for a donor-declaring cell");
        // …and a cell with no donor renders NO row — the fact never appears undeclared.
        let plain = Cell {
            name: "not-declared".into(),
            ..cell.clone()
        };
        assert!(donor_settings(&set, &plain).is_empty());

        // 2. THE REPO-LEVEL LEAF (browse): the refusal names the donor, not just "no".
        match forge.browse() {
            Leaf::Refused(t) => {
                assert!(t.contains("tunnr/tunnr-kernel"), "{t}");
                assert!(
                    t.contains("c-installer"),
                    "the consuming cell is named: {t}"
                );
            }
            _ => panic!("a kernel-less repo answers Refused from browse()"),
        }

        // 3. THE CELL-LEVEL LEAF: same donor, sharper sentence.
        forge.cells.insert(key("c-installer", "installer"), cell);
        match forge.leaf("c-installer", "installer", &BTreeMap::new()) {
            Leaf::Refused(t) => {
                assert!(t.contains("tunnr/tunnr-kernel"), "{t}");
                assert!(
                    t.contains("pin"),
                    "the pin verification is what BYGG buys: {t}"
                );
            }
            _ => panic!("an installer cell answers Refused from leaf()"),
        }
    }

    /// A staged checkout with ONE `[[build.kernel]]`, read the way the forge reads it.
    /// `RepoBuilds::read` (not a hand-built struct) because the subject of the two
    /// tests below is the declaration the BUILD WORKER receives, and the worker
    /// receives `repo.set`.
    fn staged_kernel_repo(preset: &str) -> (tempfile::TempDir, RepoBuilds) {
        let dir = tempfile::tempdir().expect("tmp");
        let root = dir.path().join("orten");
        std::fs::create_dir_all(root.join(".nornir")).expect("mkdir");
        std::fs::write(
            root.join("Cargo.toml"),
            "[package]\nname = \"orten\"\nversion = \"1.0.0\"\nedition = \"2021\"\n",
        )
        .expect("manifest");
        std::fs::write(
            root.join(".nornir").join("build-variants.toml"),
            format!(
                r#"
[build]
[[build.kernel]]
name = "orten-kernel"
version = "6.12.104"
tarball_sha256 = "949d166a1263bd0cf0c5aab44eeb57869615099e7c48bf88d0e4adb77379b999"
preset = "{preset}"

[[build.variant]]
name = "orten-kernel"
suites = ["ORIGINAL"]
member = "orten"
package = "orten"
kind = "kernel"
linkage = "not-applicable"
"#
            ),
        )
        .expect("declaration");
        let repo = RepoBuilds::read("orten", &root);
        assert!(repo.declared, "the fixture must parse: {:?}", repo.error);
        (dir, repo)
    }

    fn kernel_cell() -> Cell {
        Cell {
            name: "orten-kernel".into(),
            kind: ArtifactKind::Kernel,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        }
    }

    /// **A PRESET PICKED IN ⚒ CHANGES WHAT ⚒ WOULD BUILD.** This is the claim the
    /// whole move rests on, measured rather than asserted.
    ///
    /// It is measured on the DECLARATION the build worker receives, because that is
    /// where a kernel's contents are decided: `BuildRun::spawn_with_selection` clones
    /// `repo.set` and hands the clone to the assembler, which resolves the
    /// `[[build.kernel]]` row by name. So the honest question is not "did a string
    /// reach a UI field" but "is the declaration the worker gets a different kernel",
    /// and the kind answers that itself — `KernelBuild::content_id` is the identity a
    /// rebuild has to match, and `base()` is the make target the preset resolves to.
    ///
    /// RED BEFORE GREEN: with `preset` still `Setting::fixed` and no `kernel_override`,
    /// the operator's pick was discarded at the door and both halves below are the
    /// declared `nuked` — the picker was a knob that turned nothing.
    #[test]
    fn a_preset_picked_in_the_forge_is_the_preset_the_build_runs() {
        let (_dir, repo) = staged_kernel_repo("nuked");
        let cell = kernel_cell();
        let declared = repo.set.kernels[0].clone();
        assert_eq!(declared.preset, "nuked");
        assert_eq!(
            declared.base(),
            "tinyconfig",
            "the declared preset's own base"
        );

        // THE PICK, exactly as `Act::Build` carries it off the settings block.
        let mut chose = BTreeMap::new();
        chose.insert(PRESET_KEY.to_string(), "full".to_string());
        let (built, line) = kernel_override(&repo, &cell, &chose)
            .expect("a pick that differs from the declaration must reach the build");

        let after = &built.set.kernels[0];
        assert_eq!(
            after.preset, "full",
            "the WORKER's declaration carries the pick"
        );
        assert_eq!(
            after.base(),
            "defconfig",
            "and the derived base follows it — `full` starts from defconfig, `nuked` from \
             tinyconfig, which is the difference between the two machines"
        );
        assert_ne!(
            after.content_id(),
            declared.content_id(),
            "the kind's OWN identity for this build must differ; an override that produced \
             the same content id would be a rebuild of the same kernel"
        );
        assert!(
            line.contains("nuked") && line.contains("full"),
            "the log names both: {line}"
        );

        // …and the declaration ON DISK is untouched. The override is this run's, not
        // an edit to the repo — a pane that rewrote a committed declaration to build
        // something once would be a far worse bug than the one it fixed.
        assert_eq!(
            repo.set.kernels[0].preset, "nuked",
            "the source declaration is not edited"
        );
    }

    /// **THE GUARD ON THE GUARD.** The override must be silent about everything it is
    /// not: a pick equal to the declaration, a word that is not a preset, and any cell
    /// that is not a kernel. Without these, the test above is satisfied by a function
    /// that clones the repo unconditionally and calls that a change.
    #[test]
    fn the_override_stays_out_of_the_way_of_everything_it_does_not_decide() {
        let (_dir, repo) = staged_kernel_repo("nuked");
        let cell = kernel_cell();

        assert!(
            kernel_override(&repo, &cell, &BTreeMap::new()).is_none(),
            "nothing picked ⇒ the declaration stands"
        );

        let same: BTreeMap<String, String> = [(PRESET_KEY.to_string(), "nuked".to_string())]
            .into_iter()
            .collect();
        assert!(
            kernel_override(&repo, &cell, &same).is_none(),
            "a pick equal to the declaration is not an override, and must not be logged as one"
        );

        let junk: BTreeMap<String, String> = [(PRESET_KEY.to_string(), "slimm".to_string())]
            .into_iter()
            .collect();
        assert!(
            kernel_override(&repo, &cell, &junk).is_none(),
            "an unknown preset is refused HERE, by the same `Preset::parse` gate the kind's \
             own validate uses — forwarding it would fail deep inside a toolchain container"
        );

        let exe = Cell {
            kind: ArtifactKind::Exe,
            ..kernel_cell()
        };
        let pick: BTreeMap<String, String> = [(PRESET_KEY.to_string(), "full".to_string())]
            .into_iter()
            .collect();
        assert!(
            kernel_override(&repo, &exe, &pick).is_none(),
            "a cargo cell has no kernel declaration to override, whatever is in the map"
        );
    }

    /// **The picker offers every preset the kind has, and no others.**
    ///
    /// The list is the kind's own [`crate::kernel_scene::PRESETS`], not a list spelled
    /// here — a picker with its own copy of the vocabulary is a picker that offers a
    /// word the door refuses the day the kind grows a fifth preset.
    #[test]
    fn the_preset_setting_is_a_control_with_the_kinds_whole_vocabulary() {
        let (_dir, repo) = staged_kernel_repo("nuked");
        let out = kernel_settings(&repo.set, &kernel_cell());
        let preset = out
            .iter()
            .find(|s| s.key == PRESET_KEY)
            .expect("the kernel cell resolves a `preset` setting");
        assert!(
            !preset.fixed,
            "the preset was `Setting::fixed` — a label. It is the choice the tab exists to \
             offer, and a read-only one is why the configurator had to be looked for in \
             another tab three times"
        );
        assert_eq!(
            preset.value, "nuked",
            "seeded from the declaration, not from a default"
        );
        let words: Vec<&str> = preset.choices.iter().map(|c| c.value.as_str()).collect();
        assert_eq!(
            words,
            PRESETS
                .iter()
                .map(nornir_build_thing::kernel::Preset::as_str)
                .collect::<Vec<_>>(),
            "the vocabulary is the kind's"
        );
        // Every choice carries the base it resolves to — the consequence beside the
        // pick, so a `base` row that would freeze at the declared preset is not needed.
        assert!(
            preset.choices.iter().all(|c| c.note.contains("bas ")),
            "each choice must say what it resolves to: {:?}",
            preset.choices
        );
        assert!(
            !out.iter().any(|s| s.key == "base"),
            "with no pinned `base` in the declaration there must be no `base` ROW — it would \
             read as the declared preset's base forever while the operator picked another"
        );
    }

    /// **The dependency picture's SUBJECT: an installer column is about the
    /// appliance it packs, not about itself.**
    ///
    /// An installer variant declares no cargo features — the thing it builds is
    /// the appliance. Derived from its own row, the picture settled on
    /// `bygger utan features` and drew nothing, while the app row beside it drew
    /// fine; that intermittency is what read as "the picture sometimes flickers
    /// in". Both arms here, because a mapping that returned `None` for
    /// everything would look identical on the plain-exe arm.
    ///
    /// ⚠ THIS TEST DID NOT RUN. Its doc block and its `#[test]` were separated from
    /// it by a whole other test that landed in between (the doc comment above went in
    /// UNDER the attribute), so the attribute attached to the kernel test — which then
    /// carried two of them — and this function was a plain private fn inside
    /// `mod tests` that nothing ever called. Found 2026-08-26 while moving the kernel
    /// configurator into ⚒; the attribute is back where it belongs.
    #[test]
    fn a_delivery_column_pictures_the_variant_it_packs() {
        let dir = tempfile::tempdir().expect("tempdir");
        std::fs::write(
            dir.path().join("build-variants.toml"),
            r#"
[[build.variant]]
name = "orten-server"
package = "orten"
bin = "orten"
kind = "exe"
features = ["server"]

[[build.installer]]
name = "orten-installer"
variant = "orten-server"

[[build.iso]]
name = "orten-live"
from_variant = "orten-server"
staging = "iso-root"
"#,
        )
        .expect("write");
        let set = nornir_build_thing::variant::BuildVariantSet::load_file(
            &dir.path().join("build-variants.toml"),
        )
        .expect("load");
        let forge = DeclaredForge::at(dir.path().to_path_buf());

        let installer = Cell {
            name: "orten-installer".into(),
            kind: ArtifactKind::Installer,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        assert_eq!(
            forge.packed_variant(&set, &installer),
            Some("orten-server".to_string()),
            "an installer column does not know which appliance it packs, so its picture is              derived from a row that declares no features and draws nothing"
        );

        let iso = Cell {
            name: "orten-live".into(),
            kind: ArtifactKind::Iso,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        assert_eq!(
            forge.packed_variant(&set, &iso),
            Some("orten-server".to_string())
        );

        // The arm that keeps the two above honest: a plain cargo column packs
        // nothing, and its picture is of its own row.
        let exe = Cell {
            name: "orten-server".into(),
            kind: ArtifactKind::Exe,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        assert_eq!(
            forge.packed_variant(&set, &exe),
            None,
            "a cargo column claimed to pack something, so every picture would be redirected"
        );
    }

    /// **RED-when-broken: an installer column is `Installer`, never `Iso`.**
    ///
    /// edda added the kind precisely because "until this variant existed the
    /// attestation spelled both `iso`, so an auditor could not tell them apart" — a
    /// live ISO IS the appliance, an installer ISO PRODUCES one.
    #[test]
    fn an_installer_column_is_not_spelled_iso() {
        let cell = Cell {
            name: "korp-installer".into(),
            kind: ArtifactKind::Installer,
            params: vec![],
            fixtures: BTreeMap::new(),
            options: vec![],
        };
        assert_ne!(
            cell.kind,
            ArtifactKind::Iso,
            "the two kinds must stay distinguishable"
        );
        assert_eq!(
            settings_of(
                &BuildVariant {
                    name: "x".into(),
                    ..Default::default()
                },
                &cell
            )
            .iter()
            .find(|s| s.key == "kind")
            .map(|s| s.value.as_str()),
            Some(ArtifactKind::Installer.as_str()),
            "and the pane's own `kind` setting says so",
        );
    }

    /// Every declared checkout resolves to a matrix, and the matrix is the file.
    ///
    /// Watched red: `assert!(rows > 0)` alone would pass on a resolver that returned
    /// one row for everything, so the row NAMES are compared against the declaration
    /// itself, in order.
    #[test]
    fn every_declared_checkout_renders_its_own_matrix_in_declaration_order() {
        let Some(mut f) = forge() else { return };
        let names: Vec<String> = f.builds.repos.iter().map(|r| r.repo.clone()).collect();
        let mut checked = 0;
        for repo in names {
            let declared: Vec<String> = f
                .builds
                .repo(&repo)
                .map(|r| r.set.variants.iter().map(|v| v.name.clone()).collect())
                .unwrap_or_default();
            if declared.is_empty() {
                continue;
            }
            let menu = f
                .resolve(&Query::Artifact(repo.clone()))
                .expect("a declared repo");
            let rows: Vec<String> = menu.rows.iter().map(|r| r.name.clone()).collect();
            assert_eq!(
                rows, declared,
                "{repo}: the scene reordered or dropped rows the declaration lists"
            );
            assert_eq!(menu.coordinate.krate, format!("{repo}{RECIPE_SUFFIX}"));
            assert!(
                menu.coordinate.hash.is_none(),
                "{repo}: a hash was invented"
            );
            checked += 1;
        }
        assert!(checked > 0, "no checkout on this disk declares a variant");
    }

    /// Suite badges are copied, not computed.
    ///
    /// Red probe: the badge list is compared to `suites` as a PREFIX, so a resolver
    /// that dropped, sorted or invented a suite fails — including one that helpfully
    /// added the base suite to a row that does not declare it.
    #[test]
    fn a_rows_suite_badges_are_exactly_what_the_declaration_says() {
        let Some(mut f) = forge() else { return };
        let names: Vec<String> = f.builds.repos.iter().map(|r| r.repo.clone()).collect();
        let mut seen_any = 0;
        for repo in names {
            let declared: Vec<(String, Vec<String>)> = f
                .builds
                .repo(&repo)
                .map(|r| {
                    r.set
                        .variants
                        .iter()
                        .map(|v| (v.name.clone(), v.suites.clone()))
                        .collect()
                })
                .unwrap_or_default();
            if declared.is_empty() {
                continue;
            }
            let menu = f
                .resolve(&Query::Artifact(repo.clone()))
                .expect("a declared repo");
            for (row, suites) in declared {
                let badges = &menu
                    .rows
                    .iter()
                    .find(|r| r.name == row)
                    .unwrap_or_else(|| panic!("{repo}: row {row} vanished"))
                    .badges;
                assert!(
                    badges.starts_with(&suites),
                    "{repo}/{row}: badges {badges:?} are not the declared suites {suites:?}"
                );
                seen_any += usize::from(!suites.is_empty());
            }
        }
        assert!(seen_any > 0, "no checkout on this disk declares a suite");
    }

    /// A column exists exactly when a declaration produced it — and a row that does
    /// not declare one carries it greyed rather than dropped.
    #[test]
    fn the_column_strip_is_the_same_on_every_row_and_only_some_cells_are_declared() {
        let Some(mut f) = forge() else { return };
        let names: Vec<String> = f.builds.repos.iter().map(|r| r.repo.clone()).collect();
        let mut wide = 0;
        for repo in names {
            let Ok(menu) = f.resolve(&Query::Artifact(repo.clone())) else {
                continue;
            };
            let Some(first) = menu.rows.first() else {
                continue;
            };
            let strip: Vec<&str> = first.targets.iter().map(|t| t.name.as_str()).collect();
            for r in &menu.rows {
                let mine: Vec<&str> = r.targets.iter().map(|t| t.name.as_str()).collect();
                assert_eq!(
                    mine, strip,
                    "{repo}/{}: a row has its own column strip",
                    r.name
                );
                assert!(
                    r.targets.iter().any(|t| t.declared),
                    "{repo}/{}: every column greyed — the row builds nothing",
                    r.name
                );
            }
            if strip.len() > 1 {
                wide += 1;
                // RED — a strip where EVERY cell is declared would mean the greying is
                // never exercised. At least one repo must have a genuinely sparse row.
                let sparse = menu
                    .rows
                    .iter()
                    .any(|r| r.targets.iter().any(|t| !t.declared));
                assert!(
                    sparse,
                    "{repo}: a multi-column matrix with no undeclared cell"
                );
            }
        }
        assert!(
            wide > 0,
            "no checkout on this disk declares more than one column"
        );
    }

    /// A name two recipes both declare is refused, with both named. Silently picking
    /// one is how a build comes out of the wrong checkout.
    #[test]
    fn an_ambiguous_name_is_refused_and_names_the_candidates() {
        let Some(mut f) = forge() else { return };
        // Find a name more than one checkout declares, if the disk has one.
        let mut counts: BTreeMap<String, usize> = BTreeMap::new();
        for r in &f.builds.repos {
            for v in &r.set.variants {
                *counts.entry(v.name.clone()).or_default() += 1;
            }
        }
        let Some((name, _)) = counts.iter().find(|(_, n)| **n > 1) else {
            return;
        };
        let name = name.clone();
        let err = f
            .resolve(&Query::Artifact(name.clone()))
            .expect_err("ambiguous");
        assert!(
            err.contains(&name),
            "the refusal does not name what was asked: {err}"
        );
        assert!(
            err.contains(','),
            "the refusal does not list the candidates: {err}"
        );
    }

    /// A coordinate addresses the same matrix the name does — the second door onto one
    /// state, proven against a real declaration rather than a fixture.
    #[test]
    fn a_coordinate_reaches_the_same_matrix_the_name_does() {
        let Some(mut f) = forge() else { return };
        let Some(repo) = f
            .builds
            .repos
            .iter()
            .find(|r| !r.set.variants.is_empty())
            .map(|r| r.repo.clone())
        else {
            return;
        };
        let by_name = f.resolve(&Query::Artifact(repo.clone())).expect("by name");
        let coord = Coordinate {
            krate: format!("{repo}{RECIPE_SUFFIX}"),
            version: by_name.coordinate.version.clone(),
            hash: None,
        };
        let by_coord = f.resolve(&Query::At(coord)).expect("by coordinate");
        assert_eq!(
            by_name.rows, by_coord.rows,
            "the two doors disagree about {repo}"
        );
        assert_ne!(by_name.how, by_coord.how, "the doors are indistinguishable");

        // RED — a coordinate naming no checkout is refused, not defaulted.
        let bad = Coordinate {
            krate: "ingen-sadan-utcheckning".to_string(),
            version: "0.1.0".to_string(),
            hash: None,
        };
        assert!(
            f.resolve(&Query::At(bad)).is_err(),
            "an unknown coordinate resolved"
        );
    }
}

#[cfg(test)]
mod engine_tests {
    use super::*;

    /// **The RAID installer engine is still reachable.**
    ///
    /// It was wired into the retired pane's constructor and would have been lost with it;
    /// unreachable is worse than unrecorded, because no operator could ask for it at all.
    ///
    /// Watched red in both directions: the default must NOT be the RAID engine and the
    /// hook must NOT be the default, so a selector stuck on either answer fails one of
    /// the two.
    ///
    /// **This test was BLIND until 2026-08-23** and this note is the reason it is written
    /// the way it is. It asserted a `boot_engine_name` that held a SECOND copy of the
    /// selector match; renaming only the launcher's arm to `Some("edda_raid")` made
    /// `DWARVES_INSTALLER_ENGINE=edda-raid` fall through to the default launcher — the
    /// exact "unreachable from the UI" defect above — and the test still reported `ok`.
    /// It now reads the name out of the SAME call that builds the launcher
    /// ([`boot_engine`]), so the string under assertion is the arm that ran. That
    /// sabotage is now RED, measured.
    #[test]
    fn the_installer_engine_hook_is_opt_in_and_the_default_is_untouched() {
        // The name asserted is the one the picked arm handed back WITH its launcher —
        // never a mirror computed somewhere else.
        let name = |e: Option<&str>| boot_engine(e).1;

        assert_eq!(name(None), "tunnr", "the default engine changed silently");
        assert_eq!(name(Some("")), "tunnr");
        assert_eq!(name(Some("something-else")), "tunnr");
        assert_eq!(
            name(Some("edda-raid")),
            "edda-raid",
            "the RAID engine cannot be asked for, so it is unreachable from the UI"
        );
        // RED — the two answers must differ, or neither assertion above discriminates.
        assert_ne!(name(None), name(Some("edda-raid")));

        // …and the SCENE really carries the arm's answer: the field the pane reports is
        // fed by the same call, so a selector that stopped reaching the launcher shows up
        // here too rather than only in a helper nobody dispatches through.
        let scene = DeclaredForge::at(std::env::temp_dir().join("dwarves-engine-probe"));
        assert_eq!(
            scene.boot_engine(),
            boot_engine(std::env::var("DWARVES_INSTALLER_ENGINE").ok().as_deref()).1,
            "the pane reports an engine the constructor did not pick"
        );
    }
}