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

nordisk/dwarves

dwarves-gui/tests/robot_build_scene.rs

//! **The scene's guards.** Every one of them is watched failing here, in the same
//! test, against the input that makes it fail — a guard that has only ever been seen
//! green is a guard whose subject you are guessing at.
//!
//! The headline guard is [`the_scene_source_names_no_product`]: the scene's own source,
//! tokenised, may not contain a single product name. A product name in that file means
//! somebody put an `if` back, and the whole design is that there are no such `if`s.

use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};

use dwarves_gui::build_scene::{
    Act, Axis, BuildScene, Forge, Menu, Outcome, Phase, Proof, Query, Report, Row, Setting, Target,
};
use facett_core::Facet;
use nornir_build_thing::contract::{Coordinate, PaneKind, Receipt, RECEIPT_VERSION};
use nornir_build_thing::receipt_proof::ReceiptVerdict;
use nornir_build_thing::variant::ArtifactKind;
use nornir_robotui::RobotSession;

// ── the product-name guard ────────────────────────────────────────────────────

/// **Every product this constellation builds**, plus the three framework repos the
/// scene links against (`nornir`, `facett`, `edda`) and the forge's own name.
///
/// The framework three are deliberately IN the list rather than exempted wholesale:
/// they are allowed to appear in a `use` line and nowhere else, which is what
/// [`scan`] enforces. That way "the scene imports the contract crate" stays legal and
/// "the scene mentions the contract crate halfway down a render function" does not.
const PRODUCTS: [&str; 24] = [
    "bifrost",
    "draupnir",
    "dwarves",
    "edda",
    "facett",
    "garmr",
    "gunnar",
    "heimdal",
    "heimdall",
    "holger",
    "jera",
    "knut",
    "korp",
    "mimir",
    "modgunn",
    "njord",
    "nornir",
    "ordning",
    "saga",
    "skade",
    "skidbladnir",
    "tunnr",
    "znippy",
    "weston",
];

/// The scene, as it ships.
const SCENE: &str = include_str!("../src/build_scene.rs");

/// Every product name in `src`, in order of appearance.
///
/// The rule, stated so it can be argued with:
///
/// * everything from the file's own `#[cfg(test)]` line onward is not read. The subject
///   is the SHIPPED scene; the scene's unit tests have to model a REAL build report, and
///   a fixture with an invented variant name is a fixture that proves nothing about the
///   thing it stands in for. This is the one exemption that is neither a declaration nor
///   the shipped surface, and it is a TAIL exemption — the line above `#[cfg(test)]` is
///   still the scene;
/// * lines whose first non-space characters are `use ` are removed next. That is the
///   one place a dependency crate is named, and it is a declaration, not a decision;
/// * what is left is split on every character that is not a letter or a digit — so `_`
///   and `-` both split. `korp_server`, `korp-server` and `"korp"` all yield the token
///   `korp`, and `nornir_build_thing` outside a `use` line yields `nornir`;
/// * a token matches if it EQUALS a product name, case-insensitively.
///
/// Comments are NOT exempt, deliberately: `RED 2` below pins `"// korp."` as a finding,
/// because prose that reasons about one product by name is how a special case gets
/// argued back in.
fn scan(src: &str) -> Vec<String> {
    // The TAIL exemption. `position` on the trimmed line, so an indented `#[cfg(test)]`
    // inside a nested module does not cut the file short at the wrong place.
    let shipped = src
        .lines()
        .position(|l| l.trim_end() == "#[cfg(test)]")
        .map_or(usize::MAX, |i| i);
    let body: String = src
        .lines()
        .take(shipped)
        .filter(|l| !l.trim_start().starts_with("use "))
        .collect::<Vec<_>>()
        .join("\n");
    body.split(|c: char| !c.is_ascii_alphanumeric())
        .map(str::to_ascii_lowercase)
        .filter(|t| PRODUCTS.contains(&t.as_str()))
        .collect()
}

/// **The measure of done.** Zero product names in the scene — and the check watched
/// failing on four separate inputs first, because a scanner that finds nothing is
/// exactly what a broken scanner looks like.
#[test]
fn the_scene_source_names_no_product() {
    // RED 1 — the shape this whole design exists to prevent: an `if` over a name.
    assert_eq!(
        scan("if row == \"korp\" { draw_the_special_pane() }"),
        vec!["korp"],
        "the scanner cannot see a product name in an if, so it can never report red"
    );
    // RED 2 — the separators. A product name is usually half of a longer name.
    for spelling in [
        "\"korp-server\"",
        "let korp_server = 1;",
        "// korp.",
        "(korp)",
    ] {
        assert_eq!(
            scan(spelling),
            vec!["korp"],
            "the scanner missed the product name in {spelling:?}"
        );
    }
    // RED 3 — the `use`-line exemption is exactly one line wide. Naming the contract
    // crate anywhere else is a finding.
    assert!(
        scan("use nornir_build_thing::contract::PaneKind;").is_empty(),
        "an import is a declaration, not a decision, and must stay legal"
    );
    assert_eq!(
        scan("    let k = nornir_build_thing::variant::ArtifactKind::Exe;"),
        vec!["nornir"],
        "the exemption leaked past the use line, so the scanner is blind below it"
    );
    // RED 4 — the `#[cfg(test)]` TAIL exemption, in both directions at once. The scene's
    // own unit tests may model a real report (`gunnar-appliance-iso`, `korp-falkordb`);
    // the line ABOVE the tail may not.
    assert_eq!(
        scan("let _ = \"korp\";\n#[cfg(test)]\nmod t { fn f() { let _ = \"holger\"; } }"),
        vec!["korp"],
        "the tail exemption swallowed the line above `#[cfg(test)]` (or does not exempt \
         the tail at all) — it is a tail, not a file-wide amnesty"
    );
    // RED 5 — the real file, plus one line, injected ABOVE the tail so the exemption
    // cannot be what makes the guard quiet. This is the guard pointed at the actual
    // subject, so a green below cannot be an accident of the file being unreadable.
    let with_a_name = SCENE.replacen(
        "\n#[cfg(test)]",
        "\nfn special() { let _ = \"holger-ui\"; }\n#[cfg(test)]",
        1,
    );
    assert_ne!(
        with_a_name, SCENE,
        "the injection point `#[cfg(test)]` is gone from the scene"
    );
    assert_eq!(
        scan(&with_a_name),
        vec!["holger"],
        "the guard does not fire on the REAL scene source plus one product name"
    );
    assert!(!SCENE.is_empty(), "the scene source did not load");
    assert!(
        SCENE.lines().any(|l| l.trim_end() == "#[cfg(test)]"),
        "the tail exemption is aimed at a line the scene does not have — it would be \
         exempting nothing, or everything"
    );

    // GREEN — and it means something, because the four above were red.
    let found = scan(SCENE);
    assert!(
        found.is_empty(),
        "the scene names {} product(s): {found:?} — that is an `if` somebody put back",
        found.len()
    );
}

// ── the kind-dispatch guard ───────────────────────────────────────────────────

/// The ⚒ tree declaration, held to the same rule: it names the five leaves and their
/// shape, and a kind reaching into it would be the same defect one file later.
const TREE: &str = include_str!("../src/build_thing_scene.rs");

/// Every `ArtifactKind::` path in the SHIPPED half of `src`, with its line — the
/// scanner behind [`the_scene_dispatches_no_pane_on_a_kind`].
///
/// The same tail rule as [`scan`]: everything from the file's own top-level
/// `#[cfg(test)]` line onward is not read, and a line whose first non-space characters
/// are `use ` is a declaration, not a decision. ONE deliberate difference: comment
/// lines (`//`, `///`, `//!`) are exempt here and are NOT in the product guard. The
/// product guard reads prose because prose reasoning about one product is how a
/// special case gets argued back in; this guard hunts EXECUTABLE dispatch, and the
/// module doc's opening job is precisely to STATE the one legal mapping
/// (`ArtifactKind::Exe -> PaneKind::Exec`, …) so a reader knows where it lives — a
/// `match` in a comment compiles to nothing.
fn kind_dispatch(src: &str) -> Vec<String> {
    let shipped = src
        .lines()
        .position(|l| l.trim_end() == "#[cfg(test)]")
        .map_or(usize::MAX, |i| i);
    src.lines()
        .take(shipped)
        .enumerate()
        .filter(|(_, l)| {
            let t = l.trim_start();
            !t.starts_with("use ") && !t.starts_with("//")
        })
        .filter(|(_, l)| l.contains("ArtifactKind::"))
        .map(|(i, l)| format!("line {}: {}", i + 1, l.trim()))
        .collect()
}

/// **The scene dispatches no pane on a KIND.** `ArtifactKind::` must not appear in the
/// shipped half of `build_scene.rs` or `build_thing_scene.rs` — the only kind→pane
/// mapping the design allows is the declared `PaneKind` switch in edda
/// (`PaneKind::for_artifact`, reached through `Target::pane()`), which the scene calls
/// and never re-implements.
///
/// # Why this guard exists as a SIBLING of the product-name guard
///
/// [`the_scene_source_names_no_product`] scans 24 PRODUCT names, and that left it
/// blind to exactly one shape of special case: UI keyed on a KIND. That is how the
/// kernel configurator slipped past it — `kernel` is a kind, not a product, so a pane
/// wired to `ArtifactKind::Kernel` would read as clean to the product guard while
/// being the same `if` in different clothes. `d253db6` is the record: the kernel leaf
/// was offered to fifteen recipes that build no kernel, and no guard went red. The
/// doctrine (edda's `PaneKind` doc) says the scene "renders what a recipe declares" —
/// a KIND branch is a product branch one abstraction up, and this pins the class.
///
/// The scene is clean today (measured 2026-08-26: the only `ArtifactKind` in shipped
/// code is the `Row.kind` FIELD TYPE, which is data riding through, not a decision) —
/// this guard keeps it that way.
#[test]
fn the_scene_dispatches_no_pane_on_a_kind() {
    // RED 1 — the shape this exists to catch: a pane picked by matching a kind.
    let dispatch = "match t.kind { ArtifactKind::Kernel => self.draw_kernel_pane(ui), _ => {} }";
    assert_eq!(
        kind_dispatch(dispatch),
        vec![format!("line 1: {dispatch}")],
        "the scanner cannot see a kind-keyed match, so it can never report red"
    );
    // RED 2 — an `if` and a bare variant expression are the same decision.
    for spelling in [
        "if t.kind == ArtifactKind::Kernel { special(ui); }",
        "let want = ArtifactKind::Iso;",
    ] {
        assert_eq!(
            kind_dispatch(spelling).len(),
            1,
            "the scanner missed the kind path in {spelling:?}"
        );
    }
    // The two exemptions, exactly as wide as stated.
    assert!(
        kind_dispatch("use nornir_build_thing::variant::ArtifactKind;").is_empty(),
        "an import is a declaration, not a decision, and must stay legal"
    );
    assert!(
        kind_dispatch("//! ArtifactKind::Exe -> PaneKind::Exec   argv + stdout").is_empty(),
        "the module doc STATES the declared switch so a reader can find it — prose \
         compiles to nothing, and forbidding it would strip the map from the file"
    );
    // RED 3 — the tail rule, both directions at once: the scene's unit tests may build
    // a `Row` with a real kind, the line above the tail may not.
    assert_eq!(
        kind_dispatch(
            "let k = ArtifactKind::Vm;
#[cfg(test)]
mod t { const K: ArtifactKind = ArtifactKind::Iso; }"
        ),
        vec!["line 1: let k = ArtifactKind::Vm;".to_string()],
        "the tail exemption swallowed the line above `#[cfg(test)]`, or exempts nothing"
    );
    // RED 4 — the real file plus one dispatch, injected ABOVE the tail, so a green on
    // the real file cannot be an accident of the file being unreadable.
    let with_a_kind = SCENE.replacen(
        "\n#[cfg(test)]",
        "\nfn special(t: &Target) -> bool { t.kind == ArtifactKind::Kernel }\n#[cfg(test)]",
        1,
    );
    assert_ne!(
        with_a_kind, SCENE,
        "the injection point `#[cfg(test)]` is gone from the scene"
    );
    assert_eq!(
        kind_dispatch(&with_a_kind).len(),
        1,
        "the guard does not fire on the REAL scene source plus one kind dispatch"
    );

    // GREEN — and it means something, because the four above were red.
    for (name, src) in [("build_scene.rs", SCENE), ("build_thing_scene.rs", TREE)] {
        let found = kind_dispatch(src);
        assert!(
            found.is_empty(),
            "{name} dispatches on a KIND in its shipped half: {found:?} — the kernel \
             pane went through the product-name guard exactly this way, because a kind \
             is not a product name; the one legal mapping is edda's PaneKind switch"
        );
    }
}

// ── a forge that knows nothing, for a scene that knows nothing ────────────────

/// The rows the fake declares. Deliberately not any product's names: a fixture that
/// spelled a real atom would make the guard above pass for the wrong reason.
const ALPHA: &str = "alpha";
const BETA: &str = "beta";

/// A forge that records what it was asked and answers from a script.
#[derive(Default)]
struct Fake {
    /// Every [`Act`] the scene sent, in order.
    acts: Arc<Mutex<Vec<Act>>>,
    /// What [`Forge::poll`] returns.
    report: Arc<Mutex<Report>>,
    /// Queries that must fail, and why.
    refuse: Option<String>,
}

impl Fake {
    fn new() -> Self {
        Self::default()
    }

    /// A row whose single column produces `kind`.
    fn row(name: &str, kind: ArtifactKind, target: &str) -> Row {
        Row {
            name: name.to_string(),
            badges: vec!["ORIGINAL".to_string()],
            targets: vec![
                Target {
                    name: target.to_string(),
                    kind,
                    pane: None,
                    declared: true,
                    variant: None,
                    settings: vec![
                        Setting::open("gpu", "false"),
                        Setting::fixed("base", "scratch", "declared by the recipe"),
                    ],
                },
                Target {
                    name: "rpm".to_string(),
                    kind: ArtifactKind::Exe,
                    pane: None,
                    declared: false,
                    variant: None,
                    settings: vec![],
                },
            ],
        }
    }
}

impl Forge for Fake {
    /// No manifests behind this fake, so no picture — which is a different answer
    /// from a failed one and says so. The axis edge itself is measured in
    /// `robot_build_scene_ui.rs`, against a real crate graph on disk.
    fn axis(&mut self, _pick: &dwarves_gui::build_scene::AxisPick) -> Axis {
        Axis::Absent
    }

    fn resolve(&mut self, query: &Query) -> Result<Menu, String> {
        if let Some(why) = &self.refuse {
            return Err(why.clone());
        }
        let how = match query {
            Query::Artifact(_) => "löst ur namnet",
            Query::At(_) => "angiven koordinat",
        };
        Ok(Menu {
            coordinate: Coordinate::parse("a-build-thing:0.3.1:a91f4c2e").unwrap(),
            how: how.to_string(),
            rows: vec![
                Fake::row(ALPHA, ArtifactKind::Exe, "exe"),
                Fake::row(BETA, ArtifactKind::Container, "container"),
            ],
        })
    }

    fn act(&mut self, act: Act) {
        self.acts.lock().unwrap().push(act);
    }

    fn poll(&mut self) -> Report {
        self.report.lock().unwrap().clone()
    }
}

/// The landed-record sentence, in `BuildRecordReceipt::line`'s own shape.
const WAREHOUSE_LANDED: &str =
    "build records: 1 provenance + 3 build_info row(s) landed in `nordisk` at \
     http://127.0.0.1:7878";

/// A finished build, so the pane below has something to act on.
fn built() -> Report {
    Report {
        phase: Some(Phase::Done),
        elapsed_ms: 12_400,
        outcome: Some(Outcome {
            ok: true,
            artifact: "target/release/alpha".to_string(),
            size_bytes: Some(38_200_000),
            sha: Some("7c1a2b3c4d5e".to_string()),
            receipt: Some(Receipt {
                receipt_version: RECEIPT_VERSION,
                build_thing: Coordinate::parse("a-build-thing:0.3.1:a91f4c2e").unwrap(),
                atom: ALPHA.to_string(),
                target: "exe".to_string(),
                settings: BTreeMap::from([("gpu".to_string(), "false".to_string())]),
                inputs: BTreeMap::new(),
            }),
            // The receipt says `gpu = false`; the binary agrees. Both halves are
            // present here so the pane's `kvitto` and `bevis` rows have something
            // to render, and so `proven` in the robot JSON is exercised as `true`
            // by a case that could also be `false`.
            proof: Some(Proof {
                verdict: ReceiptVerdict::Proven,
                lines: vec!["gpu = false and the binary links no GPU stack ✓".to_string()],
            }),
            elapsed_ms: 12_400,
            ports: vec![5901],
            note: None,
            // A fixture-filled per-site value, as the forge spells it — the
            // pane must say so beside the receipt, never silently.
            applied: vec![
                "hub_conf ← fixture wg-hub (wg-hub pub=k endpoint=10.0.2.2:1 key=/s/k.key)"
                    .to_string(),
            ],
            // The build's provenance record LANDED. Worded exactly as
            // `BuildRecordReceipt::line` words it, so this fixture cannot drift
            // into a sentence the real writer never produces.
            warehouse_line: WAREHOUSE_LANDED.to_string(),
            warehouse_red: false,
            warehouse_landed: true,
            features: None,
        }),
        live: false,
        lines: vec!["USAGE: …".to_string()],
        screen: None,
        note: None,
        menu: None,
    }
}

/// Wire a scene onto a fake, keeping the fake's two shared slots.
fn scene() -> (BuildScene, Arc<Mutex<Vec<Act>>>, Arc<Mutex<Report>>) {
    let f = Fake::new();
    let (acts, report) = (f.acts.clone(), f.report.clone());
    (BuildScene::new(Box::new(f)), acts, report)
}

/// Drive one message through the headless seam.
fn drive(s: &mut BuildScene, msg: &str) {
    s.update_json(msg);
}

// ── the flow ──────────────────────────────────────────────────────────────────

/// Steps 1→5 in order, and the two refusals in the middle of them.
///
/// Both directions are asserted, so the "can build" oracle is watched red (a row with
/// no column) before it is trusted green (a row and a column).
#[test]
fn the_flow_needs_one_row_and_one_column_before_it_can_build() {
    let (mut s, acts, _r) = scene();

    // RED — nothing typed.
    drive(&mut s, r#"{"resolve":true}"#);
    assert!(
        s.state_json()["error"].as_str().is_some(),
        "an empty artifact field resolved to something"
    );
    assert!(s.state_json()["rows"].as_array().unwrap().is_empty());

    // Step 1+2 — a name, and the matrix.
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    let st = s.state_json();
    assert_eq!(st["recipe"], "a-build-thing:0.3.1:a91f4c2e");
    assert_eq!(
        st["rows"].as_array().unwrap().len(),
        2,
        "the whole home matrix"
    );
    // The typed name matched a row, so that row is picked — a name matching itself.
    assert_eq!(st["row"], ALPHA);

    // RED — a row alone cannot build, and pressing BYGG must reach nothing.
    assert_eq!(st["target"], serde_json::Value::Null);
    assert_eq!(
        st["can_build"], false,
        "a row with no column armed the build"
    );
    drive(&mut s, r#"{"build":true}"#);
    assert!(
        acts.lock().unwrap().is_empty(),
        "BYGG fired with no column picked"
    );

    // RED — a column the recipe does not declare cannot be picked.
    drive(&mut s, r#"{"pick_target":"rpm"}"#);
    assert_eq!(
        s.state_json()["target"],
        serde_json::Value::Null,
        "an undeclared column was selectable"
    );

    // Step 4+5.
    drive(&mut s, r#"{"pick_target":"exe"}"#);
    let st = s.state_json();
    assert_eq!(st["target"], "exe");
    assert_eq!(
        st["can_build"], true,
        "a row AND a column still would not build"
    );
    drive(&mut s, r#"{"build":true}"#);
    let sent = acts.lock().unwrap();
    assert_eq!(sent.len(), 1, "BYGG did not reach the forge exactly once");
    match &sent[0] {
        Act::Build {
            row,
            target,
            settings,
        } => {
            assert_eq!(row, ALPHA);
            assert_eq!(target, "exe");
            // Every declared setting travels, including the one the recipe fixed —
            // a setting missing here is a setting missing from the receipt.
            assert_eq!(settings.get("gpu").map(String::as_str), Some("false"));
            assert_eq!(settings.get("base").map(String::as_str), Some("scratch"));
        }
        other => panic!("BYGG sent {other:?} instead of a build"),
    }
}

/// Step 3 is single-selection, and it invalidates everything downstream.
///
/// Watched red by asserting the SAME expressions before the second pick, when they
/// are all populated: without the clear in `pick_row` the three assertions below
/// would still see the previous row's column, outcome and pane.
#[test]
fn picking_a_second_row_clears_the_column_the_outcome_and_the_pane() {
    let (mut s, _acts, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_target":"exe"}"#);
    *report.lock().unwrap() = built();
    drive(&mut s, r#"{"poll":true}"#);

    // RED — the state the assertions below must NOT find after the second pick.
    let st = s.state_json();
    assert_eq!(st["target"], "exe");
    assert_eq!(st["pane"], "exec");
    assert!(
        !st["outcome"].is_null(),
        "the fixture never produced an outcome"
    );

    drive(&mut s, &format!(r#"{{"pick_row":"{BETA}"}}"#));
    let st = s.state_json();
    assert_eq!(st["row"], BETA, "the second row was not picked");
    assert!(
        st["target"].is_null(),
        "the previous row's column survived the pick"
    );
    assert!(
        st["pane"].is_null(),
        "a pane is open for a row nobody is looking at"
    );
    assert!(
        st["outcome"].is_null(),
        "the previous row's outcome survived the pick"
    );
    assert_eq!(st["can_build"], false);
}

/// **The whole switch**, asserted through the scene rather than through the contract:
/// what the operator sees for each kind is what [`PaneKind::for_artifact`] says.
///
/// Red by construction — the four expectations differ, so a switch collapsed to any
/// single arm fails at least three of them.
#[test]
fn every_artifact_kind_opens_the_pane_the_design_names() {
    let cases = [
        (ArtifactKind::Exe, "exec"),
        (ArtifactKind::Container, "log"),
        (ArtifactKind::Service, "log"),
        (ArtifactKind::Iso, "console"),
        (ArtifactKind::Vm, "console"),
        (ArtifactKind::Lib, "none"),
        (ArtifactKind::Wasm, "none"),
    ];
    // The expectations are not all the same value, so no constant answer passes.
    let distinct: std::collections::BTreeSet<&str> = cases.iter().map(|(_, p)| *p).collect();
    assert_eq!(distinct.len(), 4, "the case table lost its discrimination");

    for (kind, want) in cases {
        let mut f = Fake::new();
        f.report = Arc::new(Mutex::new(built()));
        let mut s = BuildScene::new(Box::new(FixedKind { kind, inner: f }));
        drive(&mut s, r#"{"artifact":"x"}"#);
        drive(&mut s, r#"{"resolve":true}"#);
        drive(&mut s, r#"{"pick_row":"only"}"#);
        drive(&mut s, r#"{"pick_target":"the-target"}"#);
        assert_eq!(
            s.state_json()["pane"],
            want,
            "{kind:?} opened the wrong pane"
        );
    }
}

/// A forge whose one row's one column produces exactly the kind under test.
struct FixedKind {
    kind: ArtifactKind,
    inner: Fake,
}

impl Forge for FixedKind {
    fn axis(&mut self, pick: &dwarves_gui::build_scene::AxisPick) -> Axis {
        self.inner.axis(pick)
    }

    fn resolve(&mut self, _query: &Query) -> Result<Menu, String> {
        Ok(Menu {
            coordinate: Coordinate::parse("a-build-thing:0.1.0").unwrap(),
            how: "test".to_string(),
            rows: vec![Row {
                name: "only".to_string(),
                badges: vec![],
                targets: vec![Target {
                    name: "the-target".to_string(),
                    kind: self.kind,
                    pane: None,
                    declared: true,
                    variant: None,
                    settings: vec![],
                }],
            }],
        })
    }
    fn act(&mut self, act: Act) {
        self.inner.act(act);
    }
    fn poll(&mut self) -> Report {
        self.inner.poll()
    }
}

/// A recipe may override the pane its kind would get — and the override is the only
/// thing that can move it, so the default is still the interface.
#[test]
fn a_recipe_can_override_the_pane_its_kind_would_get() {
    let t = Target {
        name: "t".to_string(),
        kind: ArtifactKind::Exe,
        pane: None,
        declared: true,
        variant: None,
        settings: vec![],
    };
    // RED — the default, so the override below cannot pass by coincidence.
    assert_eq!(t.pane(), PaneKind::Exec);
    let overridden = Target {
        pane: Some(PaneKind::None),
        ..t
    };
    assert_eq!(
        overridden.pane(),
        PaneKind::None,
        "the recipe's override was ignored"
    );
}

/// **Two entry points, one state.** The scene downstream of the resolve cannot tell
/// which door was used, because there is only one thing to tell.
#[test]
fn both_entry_points_land_in_the_same_state() {
    let by_name = {
        let (mut s, _a, _r) = scene();
        drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
        drive(&mut s, r#"{"resolve":true}"#);
        drive(&mut s, r#"{"pick_target":"exe"}"#);
        s.state_json()
    };
    let by_coordinate = {
        let (mut s, _a, _r) = scene();
        drive(&mut s, r#"{"coordinate":"a-build-thing:0.3.1:a91f4c2e"}"#);
        drive(&mut s, r#"{"resolve_coordinate":true}"#);
        drive(&mut s, &format!(r#"{{"pick_row":"{ALPHA}"}}"#));
        drive(&mut s, r#"{"pick_target":"exe"}"#);
        s.state_json()
    };
    for key in ["recipe", "rows", "row", "target", "pane", "can_build"] {
        assert_eq!(
            by_name[key], by_coordinate[key],
            "the two doors disagree about {key}"
        );
    }
    // …and `how` is the ONE thing that differs, which is why it is shown.
    assert_ne!(by_name["how"], by_coordinate["how"]);

    // RED — a coordinate that cannot resolve to exactly one recipe is refused, not
    // defaulted. `Coordinate::parse` owns that rule; the scene must surface it.
    let (mut s, _a, _r) = scene();
    drive(&mut s, r#"{"coordinate":"a-build-thing"}"#);
    drive(&mut s, r#"{"resolve_coordinate":true}"#);
    let st = s.state_json();
    assert!(
        st["error"]
            .as_str()
            .is_some_and(|e| e.contains("a-build-thing")),
        "a versionless coordinate resolved to something: {:?}",
        st["error"]
    );
    assert!(st["recipe"].is_null());
}

/// A pane verb before there is anything to act on reaches the forge not at all.
///
/// Watched red the only honest way: the same verb, after a build, DOES reach it.
#[test]
fn a_pane_verb_reaches_the_forge_only_after_a_build() {
    let (mut s, acts, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_target":"exe"}"#);

    drive(&mut s, r#"{"argv":"--help"}"#);
    drive(&mut s, r#"{"run":true}"#);
    drive(&mut s, r#"{"start":true}"#);
    drive(&mut s, r#"{"stop":true}"#);
    drive(&mut s, r#"{"reboot":true}"#);
    assert!(
        acts.lock().unwrap().is_empty(),
        "a pane verb ran against a build that never happened"
    );

    *report.lock().unwrap() = built();
    drive(&mut s, r#"{"poll":true}"#);
    drive(&mut s, r#"{"run":true}"#);
    let sent = acts.lock().unwrap();
    assert_eq!(
        sent.len(),
        1,
        "the same verb did not reach the forge after a build"
    );
    assert_eq!(sent[0], Act::Run("--help".to_string()));
}

/// **A Console pane shows BOTH consoles at once**, and the graphical one says it has no
/// frame rather than drawing a placeholder that could be read as the guest's screen.
///
/// The two-at-once rule is the refinement of 2026-08-06: during an install the serial
/// says what is HAPPENING and the graphical says what the USER SEES, and a toggle forces
/// a choice between them in the one situation that needs both. Watched red against the
/// two panes that are NOT consoles, which list none — a constant `["serial","graphical"]`
/// would fail there.
#[test]
fn a_console_pane_shows_both_consoles_and_an_absent_frame_says_so() {
    let (mut s, _a, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);

    // RED — before a column is picked there is no pane, so there are no consoles.
    assert_eq!(s.state_json()["consoles"], serde_json::json!([]));

    // RED — an Exec pane is not a console pane, so it lists none either.
    drive(&mut s, r#"{"pick_target":"exe"}"#);
    *report.lock().unwrap() = built();
    drive(&mut s, r#"{"poll":true}"#);
    let st = s.state_json();
    assert_eq!(st["pane"], "exec");
    assert_eq!(
        st["consoles"],
        serde_json::json!([]),
        "an exe pane grew a console"
    );

    // A Console pane lists BOTH, in the order the design names them.
    let mut s = console_scene(&report);
    drive(&mut s, r#"{"poll":true}"#);
    let st = s.state_json();
    assert_eq!(st["pane"], "console");
    assert_eq!(
        st["consoles"],
        serde_json::json!(["serial", "graphical"]),
        "the console pane is showing one console, so it is still a toggle"
    );

    // The graphical half is honest when the forge has produced no frame.
    assert!(
        st["screen"].is_null(),
        "a frame appeared out of a forge that has none"
    );
    // …and the serial half is up at the same time, with the lines the forge has.
    assert_eq!(
        st["line_count"], 1,
        "the serial half is empty while the frame is absent"
    );

    // RED — with a frame, the same field reports its real dimensions, and the serial
    // lines are STILL there: both halves are live together, not one instead of the other.
    let mut r = built();
    r.screen = Some(dwarves_gui::build_scene::Screen {
        width: 4,
        height: 2,
        rgba: vec![0; 4 * 2 * 4],
    });
    r.lines = vec!["installer stage 1".to_string(), "partitioning".to_string()];
    *report.lock().unwrap() = r;
    drive(&mut s, r#"{"poll":true}"#);
    let st = s.state_json();
    assert_eq!(st["screen"]["width"], 4);
    assert_eq!(st["screen"]["bytes"], 32);
    assert_eq!(
        st["line_count"], 2,
        "the serial half went dark when a frame arrived"
    );
    assert_eq!(st["consoles"], serde_json::json!(["serial", "graphical"]));

    // There is no message that can turn one of them off — a toggle would have one.
    drive(&mut s, r#"{"console":"graphical"}"#);
    assert_eq!(
        s.state_json()["consoles"],
        serde_json::json!(["serial", "graphical"]),
        "a console selector survived, so the pane can still be put into one mode"
    );
}

/// A scene sitting on a picked Iso column — the Console pane's subject.
fn console_scene(report: &Arc<Mutex<Report>>) -> BuildScene {
    let mut f = Fake::new();
    f.report = report.clone();
    let mut s = BuildScene::new(Box::new(FixedKind {
        kind: ArtifactKind::Iso,
        inner: f,
    }));
    drive(&mut s, r#"{"artifact":"x"}"#);
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_row":"only"}"#);
    drive(&mut s, r#"{"pick_target":"the-target"}"#);
    s
}

/// A resolve that fails is shown verbatim, and leaves no half-built matrix behind.
#[test]
fn a_failed_resolve_is_shown_and_clears_the_matrix() {
    let (mut s, _a, _r) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    assert_eq!(s.state_json()["rows"].as_array().unwrap().len(), 2);

    let mut f = Fake::new();
    f.refuse = Some("inget recept deklarerar den artefakten".to_string());
    let mut s = BuildScene::new(Box::new(f));
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    let st = s.state_json();
    assert_eq!(st["error"], "inget recept deklarerar den artefakten");
    assert!(st["rows"].as_array().unwrap().is_empty());
    assert!(st["recipe"].is_null());
    assert_eq!(st["can_build"], false);
}

/// The receipt is rendered as the sketches show it: the recipe, then every resolved
/// setting. A build with no receipt SAYS there is none.
#[test]
fn the_receipt_line_names_the_recipe_and_every_resolved_setting() {
    let r = built().outcome.unwrap().receipt.unwrap();
    let line = dwarves_gui::build_scene::receipt_line(&r);
    assert!(line.starts_with("a-build-thing:0.3.1 a91f4c2e"), "{line}");
    assert!(
        line.contains("gpu=false"),
        "a resolved setting is missing from {line}"
    );

    // A REAL recipe hash is a full sha256, and the line shows it short: 64
    // characters would push every resolved setting off the row, which is the part
    // an operator reads. The record is untouched — only the render shortens.
    let mut long = r.clone();
    long.build_thing.hash =
        Some("e484a4c6dfcbeaa18d1bac7915750420c817bc23dd322b7da954a818b904d78f".to_string());
    let line = dwarves_gui::build_scene::receipt_line(&long);
    assert!(
        line.contains("e484a4c6…"),
        "the hash was not shortened: {line}"
    );
    assert!(
        !line.contains("904d78f"),
        "the whole 64-character digest is on the line: {line}"
    );
    assert!(line.contains("gpu=false"), "the settings fell off: {line}");
    assert_eq!(
        long.build_thing.hash.as_deref().map(str::len),
        Some(64),
        "the render mutated the record"
    );

    // RED — an outcome without a receipt is reported as such, never as an empty one.
    let (mut s, _a, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_target":"exe"}"#);
    let mut b = built();
    b.outcome.as_mut().unwrap().receipt = None;
    *report.lock().unwrap() = b;
    drive(&mut s, r#"{"poll":true}"#);
    assert_eq!(s.state_json()["outcome"]["has_receipt"], false);
    // …and the APPLIED per-site record still says where a value came from: the
    // fixture line is its own row, independent of the receipt.
    let applied = s.state_json()["outcome"]["applied"].clone();
    assert_eq!(
        applied,
        serde_json::json!([
            "hub_conf ← fixture wg-hub (wg-hub pub=k endpoint=10.0.2.2:1 key=/s/k.key)"
        ]),
        "the fixture-filled value is named in the result: {applied}"
    );
}

/// **Only `Proven` passes, and the pane says which of the other five it is.**
///
/// This is the whole reason [`Proof`] carries the verdict rather than a boolean.
/// `NotMeasured` is a stripped binary — gunnar's `[profile.release]` sets
/// `strip = true`, so it is the ordinary case for a shipped artifact, not an
/// exotic one — and folding it into a pass would make the third level green for
/// ever on precisely the artifacts that ship.
///
/// Watched red by asserting the false side over every non-passing variant, not
/// by asserting the true side over one.
#[test]
fn every_verdict_except_proven_reads_as_not_proven_and_has_its_own_word() {
    use ReceiptVerdict::{
        Disproven, MalformedReceipt, NoReceipt, NotMeasured, NothingProvable, Proven,
    };
    let of = |v| Proof {
        verdict: v,
        lines: vec![],
    };

    assert!(of(Proven).holds(), "the one passing verdict does not pass");
    for v in [
        NotMeasured,
        NoReceipt,
        MalformedReceipt,
        NothingProvable,
        Disproven,
    ] {
        assert!(
            !of(v).holds(),
            "{v:?} reads as proven — an unchecked claim is not a verified one"
        );
    }

    // Six distinct words: an operator has to be able to tell "the binary
    // contradicts its receipt" from "nobody could read the binary".
    let words: std::collections::BTreeSet<&str> = [
        Proven,
        Disproven,
        NoReceipt,
        MalformedReceipt,
        NothingProvable,
        NotMeasured,
    ]
    .into_iter()
    .map(|v| of(v).label())
    .collect();
    assert_eq!(words.len(), 6, "two verdicts share a word: {words:?}");

    // And the robot JSON carries it, with `proven` false on a build that finished
    // green. A green build whose receipt is unproven is exactly the state that
    // must be visible.
    let (mut s, _a, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_target":"exe"}"#);
    let mut b = built();
    b.outcome.as_mut().unwrap().proof = Some(of(NotMeasured));
    *report.lock().unwrap() = b;
    drive(&mut s, r#"{"poll":true}"#);
    let st = s.state_json();
    assert_eq!(st["outcome"]["ok"], true, "the build itself failed");
    assert_eq!(st["outcome"]["proven"], false);
    assert_eq!(st["outcome"]["proof"], of(NotMeasured).label());

    // The passing case, so the assertion above is not passing because the field
    // is always false.
    let mut b = built();
    b.outcome.as_mut().unwrap().proof = Some(of(Proven));
    *report.lock().unwrap() = b;
    drive(&mut s, r#"{"poll":true}"#);
    assert_eq!(s.state_json()["outcome"]["proven"], true);
}

// ── the pane must ASK to be drawn again ───────────────────────────────────────

/// **The pane asks egui for the next frame while the work is somewhere else.**
///
/// egui draws on demand. Every slow thing this pane shows — the build, the boot, the
/// guest's serial and framebuffer, the dependency graph — happens off the ui thread
/// and arrives through [`Forge::poll`]/[`Forge::axis`], so unless the pane asks for
/// another frame there is nothing to make one happen.
///
/// **It did not ask, and it shipped that way.** MEASURED 2026-08-16 driving the real
/// `dwarves-ui-gpu` binary on a real X server: BYGG on `gunnar /
/// installer:gunnar-installer` left the window frozen at `BYGGER 0.3s` for the whole
/// 237-second build. Moving the mouse — any event at all — repainted it and the
/// finished run appeared at once. The build was never broken; nobody had asked for a
/// frame.
///
/// **No `egui_kittest` test in this tree could ever have caught it**, which is why
/// this one is written against the raw [`egui::Context`] instead. `Harness::run()`
/// pumps frames unconditionally, so a harness that always redraws is structurally
/// blind to a pane that never asks to be redrawn (LAW 2: the guard was green about
/// something else).
///
/// The oracle is the APPLIED output — `ViewportOutput::repaint_delay`, the number
/// eframe's event loop actually waits on — not a call count and not a state
/// round-trip. `Duration::MAX` is egui's "do not wake up".
///
/// RED-WHEN-BROKEN in both directions, and both are needed: drop the
/// `request_repaint_after` and the working case goes `MAX`; ask unconditionally
/// (a plain `request_repaint()` every frame) and the settled case stops being `MAX`
/// and the pane spins the CPU forever on an idle screen.
#[test]
fn the_pane_asks_for_frames_while_work_is_in_flight() {
    /// The delay eframe would sleep for once the pane has SETTLED.
    ///
    /// Settled, and not the first pass, because egui itself asks for an immediate
    /// second pass on a fresh [`egui::Context`] — fonts, layout, the tessellator all
    /// want one — so pass 1 reports `0ns` for every input and would make this whole
    /// test green about egui's warm-up instead of about the pane. Measured 2026-08-16
    /// while writing it: the idle case came back `0ns` on the first pass and
    /// `Duration::MAX` from the third onward.
    fn repaint_delay(s: &mut BuildScene) -> std::time::Duration {
        let ctx = egui::Context::default();
        let mut delay = std::time::Duration::ZERO;
        for _ in 0..4 {
            let mut out = ctx.run_ui(egui::RawInput::default(), |ui| s.ui(ui));
            // egui 0.36 debug-asserts on a FullOutput dropped with unapplied texture
            // deltas; nothing is uploaded headless, so they are cleared (2026-10-03).
            out.textures_delta.clear();
            delay = out
                .viewport_output
                .get(&egui::ViewportId::ROOT)
                .expect("the root viewport is always in the output")
                .repaint_delay;
        }
        delay
    }

    let (mut s, _acts, report) = scene();
    drive(&mut s, &format!(r#"{{"artifact":"{ALPHA}"}}"#));
    drive(&mut s, r#"{"resolve":true}"#);
    drive(&mut s, r#"{"pick_target":"exe"}"#);

    // RED FIRST — a settled pane must NOT ask, or the fix is "repaint forever" and
    // the assertion below would pass on a pane that burns a core doing nothing.
    *report.lock().unwrap() = built();
    assert_eq!(
        repaint_delay(&mut s),
        std::time::Duration::MAX,
        "a finished build still asks egui to wake up — this pane never sleeps",
    );

    // A build in flight: the pane must ask, and soon enough to read as a timer.
    *report.lock().unwrap() = Report {
        phase: Some(Phase::Building),
        elapsed_ms: 300,
        live: true,
        ..Report::default()
    };
    let d = repaint_delay(&mut s);
    assert!(
        d <= std::time::Duration::from_millis(250),
        "a build in flight asked for the next frame in {d:?} — the operator watches a \
         still picture until they happen to move the mouse",
    );

    // A live boot with no `Phase::Building` — the console leg, which is the one that
    // streams serial lines and framebuffers and therefore needs it most.
    *report.lock().unwrap() = Report {
        phase: Some(Phase::Done),
        live: true,
        lines: vec!["INSTALLER: INSTALL-OK".to_string()],
        ..Report::default()
    };
    let d = repaint_delay(&mut s);
    assert!(
        d <= std::time::Duration::from_millis(250),
        "a LIVE subject asked for the next frame in {d:?} — the serial and the \
         graphical console would only advance when something else woke the window",
    );
}

// ── where the build record landed ─────────────────────────────────────────────

/// The pane, wrapped so the robot can read its observable state.
struct SceneApp(BuildScene);

impl nornir_robotui::Observable for SceneApp {
    fn state_json(&self) -> serde_json::Value {
        Facet::state_json(&self.0)
    }
}

/// A `Report` whose outcome is `built()`'s with the three warehouse fields set,
/// so each of the three landings is the SAME build in every other respect. Two
/// states that differ in more than the thing under test cannot tell you which
/// difference the pane reacted to.
fn landed_as(line: &str, red: bool, landed: bool) -> Report {
    let mut r = built();
    let o = r.outcome.as_mut().expect("built() has an outcome");
    o.warehouse_line = line.to_string();
    o.warehouse_red = red;
    o.warehouse_landed = landed;
    r
}

/// Drive the real pane to a finished build and hand back a live robot over it.
fn drawn(report: Report) -> RobotSession<'static, SceneApp> {
    let f = Fake::new();
    *f.report.lock().unwrap() = report;
    let mut scene = BuildScene::new(Box::new(f));
    // Steps 1→4 through the headless seam; step 5's OUTCOME is already in the
    // fake's report, which is what puts the result head on screen.
    scene.update_json(r#"{"artifact":"alpha"}"#);
    scene.update_json(r#"{"resolve":true}"#);
    scene.update_json(&format!(r#"{{"pick_row":"{ALPHA}"}}"#));
    scene.update_json(r#"{"pick_target":"exe"}"#);
    let harness = nornir_robotui::egui_kittest::Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(1400.0, 1400.0))
        .build_ui_state(
            |ui, app: &mut SceneApp| Facet::ui(&mut app.0, ui),
            SceneApp(scene),
        );
    let mut robot = RobotSession::new(harness, "dwarves-gui");
    robot.run(3);
    robot
}

/// The three sentences, as the record writer words them. Kept here so the three
/// states are compared against each OTHER as well as against the screen.
const NOT_SUBMITTED: &str = "build records: NOT SUBMITTED — no nornir server configured \
                             (set NORNIR_SERVER); this build filed no provenance";
const ZERO_ROWS: &str = "⚠ build records: ZERO ROWS LANDED at http://127.0.0.1:7878 \
                         (build_provenance=0, build_info=0/3 offered) — dwarves-buildthing-10";

/// **The register row is LAID OUT on the real pane, and it says which of the three
/// landings this build had.**
///
/// ── WHAT WAS WRONG ────────────────────────────────────────────────────────────
///
/// The ⚒ pane never read the build's warehouse receipt at all: `Outcome::from_report`
/// took the report and dropped it, and `grep warehouse` over `build_scene.rs` and
/// `build_forge.rs` answered with NOTHING. So a build that filed its provenance and a
/// build that filed nowhere rendered identically — which is the four-times-empty
/// records disease seen from the producing end. The receipt type's own doc says a
/// submit "must never be invisible"; on this pane it was.
///
/// ── WHY THIS GUARD IS TWO-SIDED ───────────────────────────────────────────────
///
/// Each state asserts its own sentence is on screen **with area** AND that the other
/// two are not. A pane that drew a constant string, or drew the row unconditionally
/// from stale state, passes the first half and fails the second. Asserting only "the
/// register row exists" would be green against a pane that always says the same thing,
/// which is precisely the failure being fixed.
#[test]
fn where_the_build_record_landed_is_drawn_on_the_real_pane() {
    let states: [(&str, bool, bool); 3] = [
        (WAREHOUSE_LANDED, false, true),
        (ZERO_ROWS, true, false),
        (NOT_SUBMITTED, false, false),
    ];
    let all: Vec<&str> = states.iter().map(|(l, _, _)| *l).collect();

    for (line, red, landed) in states {
        let robot = drawn(landed_as(line, red, landed));

        // MINE — laid out, with area. A widget with a zero rect is not on screen.
        let rect = robot.label_visibility(line).unwrap_or_else(|e| {
            panic!(
                "the register row is not on the pane for state (red={red}, landed={landed}): \
                 {e}\nthe pane says: {}",
                robot.state()
            )
        });
        assert!(
            rect.area() > 0.0,
            "the register row is laid out at zero area for (red={red}, landed={landed}): {rect:?}"
        );

        // NOT THE OTHERS — the row is a report, not a decoration.
        for other in all.iter().filter(|o| **o != line) {
            assert!(
                robot.label_visibility(other).is_err(),
                "the pane drew a landing it did not have: showing {other:?} while the \
                 build's own landing was {line:?}"
            );
        }

        // And the same three facts are readable headlessly, so a driver need not
        // scrape pixels to know where a build filed.
        let o = robot
            .state_at("/outcome")
            .expect("the outcome is observable");
        assert_eq!(
            o["warehouse_line"], line,
            "the observable line disagrees with the screen"
        );
        assert_eq!(o["warehouse_red"], red);
        assert_eq!(o["warehouse_landed"], landed);
    }
}