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_pane_builds_the_medium.rs

//! **DOOR 1, RUN FOR REAL: the ⚒ pane's `BYGG <kant>` button composes the same medium
//! the prompt and the CI door compose.**
//!
//! # Why this file exists
//!
//! On 2026-08-27 the prompt door (`dwarves buildthing`) and the CI door
//! (`dwarves run <file>.runa.ron`) were proven to produce the SAME medium, byte for
//! byte on the ISO-9660 half. The PANE's equality was *inferred* — the three doors
//! share `BuildRun::spawn_recording_with_selection`, so the argument went, therefore
//! the pane must produce the same thing. Nobody had ever pressed the button and
//! measured the result. `robot_two_doors` drives the pane to a real PLAN and stops
//! there, on purpose, because comparing plans must not cost a kernel compile.
//!
//! An inference is not a measurement. This test presses the real button.
//!
//! # What is compared, and what deliberately is NOT
//!
//! **NOT the ISO's sha256.** The nondeterminism was measured the same day at exactly
//! **39 bytes out of 16 754 688**, all of them inside FAT 8.3 directory entries in the
//! ESP — creation-time tenths-of-a-second. The ISO-9660 half is byte-identical. So the
//! oracle here is:
//!
//! * the composed line the assembler prints — the app size, the **znippy pin**, the
//!   kernel size and its **pinned blake3** — which names the exact payload and the
//!   exact kernel that went in;
//! * the console's own **receipt fragment** per step, which is the kernel decision the
//!   chain ran from;
//! * a byte-level diff against the reference medium: the differing bytes must be few
//!   and must all lie in the ESP, never in the ISO-9660 half.
//!
//! # Ignored by default
//!
//! This composes a real appliance ISO. `cargo test -p dwarves-gui --test
//! robot_pane_builds_the_medium -- --ignored --nocapture` runs it. It SKIPS by name
//! when the declaration or the reference medium is not on this disk — a fixture that
//! invented a donor would prove the fixture.

#![cfg(not(target_arch = "wasm32"))]

use std::path::{Path, PathBuf};

use facett_core::Facet;
use nornir_build_thing::tree::KERNEL_PATH;
use nornir_robotui::egui_kittest::Harness;
use nornir_robotui::{Observable, RobotSession};
use serde_json::{json, Value};

use dwarves::build_thing::ConstellationBuilds;
use dwarves_gui::build_forge::DeclaredForge;
use dwarves_gui::build_scene::BuildScene;

struct SceneApp(BuildScene);
impl Observable for SceneApp {
    fn state_json(&self) -> Value {
        Facet::state_json(&self.0)
    }
}

/// The medium the OTHER two doors produced, for the byte comparison. Overridable so the
/// path is not baked into the test; absent ⇒ the byte leg SKIPS by name and the composed
/// line + fragment legs still run.
fn reference_medium() -> Option<PathBuf> {
    let p = std::env::var_os("DWARVES_REFERENCE_ISO")
        .map(PathBuf::from)
        .unwrap_or_else(|| {
            PathBuf::from(std::env::var_os("HOME").unwrap_or_default())
                .join("scratch/l4-CLIDOOR-gunnar-appliance-iso.iso")
        });
    p.is_file().then_some(p)
}

/// The constellation the pane resolves against.
fn constellation() -> PathBuf {
    let root = std::env::var_os("DWARVES_CONSTELLATION")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
    root.canonicalize().unwrap_or(root)
}

/// Every differing byte offset between two files, and their lengths.
fn byte_diff(a: &Path, b: &Path) -> (usize, usize, Vec<usize>) {
    let x = std::fs::read(a).expect("read the pane's medium");
    let y = std::fs::read(b).expect("read the reference medium");
    let diffs: Vec<usize> = x
        .iter()
        .zip(y.iter())
        .enumerate()
        .filter_map(|(i, (p, q))| (p != q).then_some(i))
        .collect();
    (x.len(), y.len(), diffs)
}

#[test]
#[ignore = "composes a REAL appliance ISO through the ⚒ pane — minutes, and it writes .iso-out"]
fn the_pane_button_composes_the_same_medium_as_the_other_two_doors() {
    let root = constellation();
    let builds = ConstellationBuilds::discover(&root);
    // The ISO cell that consumes a donor kernel — the one the other two doors built.
    let Some((repo, iso_name, repo_root)) = builds.repos.iter().find_map(|r| {
        r.set
            .isos
            .iter()
            .find(|i| i.kernel_donor.is_some())
            .map(|i| (r.repo.clone(), i.name.clone(), r.root.clone()))
    }) else {
        eprintln!(
            "SKIP: no checkout under {} declares an iso with a kernel_donor",
            root.display()
        );
        return;
    };
    eprintln!(
        "[pane] real declaration: {repo} · {iso_name} · root {}",
        repo_root.display()
    );

    // ── the pane, on the REAL forge ────────────────────────────────────────────
    let scene = BuildScene::new(Box::new(DeclaredForge::at(root.clone())));
    let harness = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(2400.0, 1600.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(2);

    // REAL WIDGETS: type the product into the real field, press the real search button.
    robot
        .type_into("artefakt", &repo)
        .expect("the artefakt field");
    robot.click_label("sök").expect("sök");
    robot.run(2);
    robot.harness_mut().state_mut().0.select_node(&iso_name);
    robot.run(2);
    assert_eq!(
        robot.state_at("/menu/variant"),
        Some(json!(iso_name)),
        "the iso cell is the menu's subject"
    );

    // The `kärna` node is where the consumption edge — and its verb — lives.
    robot
        .harness_mut()
        .state_mut()
        .0
        .select_node(&format!("{iso_name}/{KERNEL_PATH}"));
    robot.run(2);
    assert_eq!(
        robot.state_at("/menu/node"),
        Some(json!(KERNEL_PATH)),
        "the kärna node is open"
    );

    // ★ THE LINE UNDER THE BUTTON, BEFORE THE CLICK. It must reproduce the click — the
    // whole point of `StepPlan::respell_cli`. Captured here so the report can quote what
    // the operator saw beside the button that produced the medium below.
    let printed = robot
        .state_at("/menu/build_verbs")
        .and_then(|v| v.as_array().cloned())
        .and_then(|v| v.into_iter().find(|b| b["path"] == json!(KERNEL_PATH)))
        .and_then(|b| b["cli"].as_str().map(str::to_string))
        .expect("the kärna node offers a BYGG verb with a CLI line");
    eprintln!("[pane] the line printed under the button: {printed}");
    assert!(
        printed.starts_with(&format!(
            "dwarves buildthing {repo} --variant {iso_name} --build {KERNEL_PATH}"
        )),
        "the printed line is not the click's own command: {printed}"
    );

    // ★ THE PIN, PICKED THROUGH THE PANE'S OWN CONTROL.
    //
    // The other two doors passed no `--opt` at all and took the donor's PINNED kernel
    // (blake3 642b8c7c158e1208, preset `nuked`). The pane spells its whole default state
    // explicitly, and its `Preset` val defaults to `normal`, so the planner refuses the
    // pin BY NAME: *"the pin was built from preset 'nuked' … the choice here merges to
    // preset 'normal'"*. That is a real difference between the doors and it is recorded
    // in the report; here the robot does what an operator would do to reach the pinned
    // medium — open the real `Preset` combo and pick `nuked`.
    if let Some(want) = std::env::var("DWARVES_PANE_PRESET")
        .ok()
        .filter(|s| !s.is_empty())
    {
        // ⚠ NOT through the combo, and the reason is a finding of its own: egui's
        // ComboBox puts the current VALUE on the button and the caption beside it, so
        // "Preset" is a caption ("in the AccessKit tree but NOTHING carrying it is
        // pressable"), and its value `normal` names MORE THAN ONE pressable widget in
        // this pane — the shared label→node oracle refuses to guess, correctly. A drive
        // cannot reach this control today. So the preset is set through the pane's own
        // declared `set_setting` seam, and the BUILD is still pressed as a real widget.
        // ⚠ THE SETTINGS MAP IS KEYED BY THE NODE'S `key`, NOT ITS `path`.
        //
        // This leg used to write `set_setting {"kernel.preset": …}` and then assert
        // `/settings/kernel.preset` came back equal. It ALWAYS passed and it proved
        // NOTHING: `update_json` drops whatever string it is handed straight into
        // `chosen`, and `state_at` read back the very key it had just written — while
        // `settings_now()` builds the map the build actually carries from each
        // setting's `key` (`preset`), so the invented `kernel.preset` entry was read by
        // nobody. MEASURED on the run that exposed it: the assertion said `nuked` and
        // the step the pane really ran carried `--opt kernel.preset=normal`.
        //
        // A state round-trip through the thing under test is the identity value of LAW 2.
        // The oracle is the APPLIED OUTPUT: the routed `--opt` line the plan sends.
        Facet::update_json(
            &mut robot.harness_mut().state_mut().0,
            &json!({"set_setting": {"preset": want}}).to_string(),
        );
        robot.run(3);
        let printed_now = robot
            .state_at("/menu/build_verbs")
            .and_then(|v| v.as_array().cloned())
            .and_then(|v| v.into_iter().find(|b| b["path"] == json!(KERNEL_PATH)))
            .and_then(|b| b["cli"].as_str().map(str::to_string))
            .expect("the kärna node still offers a BYGG verb");
        assert!(
            printed_now.contains(&format!("--opt {KERNEL_PATH}.preset={want}")),
            "the preset choice never reached the line the button sends — it is still: {printed_now}"
        );
    }
    if std::env::var_os("DWARVES_PANE_DUMP").is_some() {
        let k = robot
            .state_at("/tree/roots")
            .and_then(|r| r.as_array().cloned())
            .and_then(|rs| rs.into_iter().find(|n| n["path"] == json!(KERNEL_PATH)))
            .unwrap();
        for c in k["children"].as_array().unwrap().iter().take(4) {
            eprintln!("[dump] {}", serde_json::to_string(c).unwrap());
        }
        return;
    }
    let out = repo_root.join(".iso-out").join(format!("{iso_name}.iso"));
    let before = std::fs::metadata(&out).ok().and_then(|m| m.modified().ok());

    // ★ THE REAL BUTTON. `bygg_edge_at` is the headless twin and would prove the twin;
    // this presses the widget a human presses, resolved through the shared
    // label→node oracle (so "visible" and "clickable" cannot disagree).
    robot
        .click_label("BYGG kärnan")
        .expect("the BYGG kärnan button on the kärna node");
    robot.run(2);

    // ── drive the frame loop until the console settles ────────────────────────
    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60 * 60);
    let outcome = loop {
        robot.run(1);
        if let Some(o) = robot
            .state_at("/steps/receipt/outcome")
            .and_then(|v| v.as_str().map(str::to_string))
        {
            if o != "running" {
                break o;
            }
        }
        assert!(
            std::time::Instant::now() < deadline,
            "the chain never settled: {}",
            robot
                .state_at("/steps")
                .map(|v| v.to_string())
                .unwrap_or_default()
        );
        std::thread::sleep(std::time::Duration::from_millis(200));
    };
    let steps = robot.state_at("/steps").expect("the console");
    eprintln!(
        "[pane] console: {}",
        serde_json::to_string_pretty(&steps).unwrap_or_default()
    );
    assert_eq!(outcome, "done", "the pane's own chain refused: {steps}");

    // ── the medium ────────────────────────────────────────────────────────────
    let meta = std::fs::metadata(&out).unwrap_or_else(|e| {
        panic!(
            "the pane reported `done` and wrote no medium at {}: {e}",
            out.display()
        )
    });
    assert!(
        before.is_none_or(|b| meta.modified().is_ok_and(|m| m > b)),
        "{} was not rewritten — a `done` over a stale artifact proves nothing",
        out.display()
    );
    eprintln!("[pane] medium: {} · {} B", out.display(), meta.len());

    // ★ THE FRAGMENT. Every settled step names the decision it ran from; a chain that
    // ran a different kernel choice than the other doors would differ here first.
    // ⚠ The console's state has NO `plan` key and its `state` is a WORD, not an object:
    // `StepConsole::state_json` spells `{product, variant, cli, steps: [...], receipt}`
    // and `Step::json` puts the pin under `detail` — `{sha, fragment}` when a step is
    // Done, `{key}` when it was Cached. Reading `["plan"]["steps"]` and `["state"]["key"]`
    // yielded an empty vector, so this leg would have failed on `is_empty()` at the END
    // of a half-hour compose, and any softer spelling of it would have passed while
    // comparing nothing at all. Read off the shape the pane really publishes.
    let fragments: Vec<String> = steps["steps"]
        .as_array()
        .map(|ss| {
            ss.iter()
                .filter_map(|s| {
                    let d = &s["detail"];
                    d["fragment"]
                        .as_str()
                        .or_else(|| d["key"].as_str())
                        .or_else(|| d["sha"].as_str())
                        .map(|f| format!("{}={f}", s["coordinate"].as_str().unwrap_or("?")))
                })
                .collect()
        })
        .unwrap_or_default();
    assert!(
        !fragments.is_empty(),
        "no step named a fragment or a pin — the console says nothing to compare: {steps}"
    );
    eprintln!("[pane] fragments/pins: {fragments:?}");

    // ── the byte comparison against the other doors' medium ───────────────────
    let Some(reference) = reference_medium() else {
        eprintln!("SKIP (byte leg): no reference medium — set DWARVES_REFERENCE_ISO");
        return;
    };
    let (mine, theirs, diffs) = byte_diff(&out, &reference);
    eprintln!(
        "[pane] {mine} B vs reference {theirs} B · {} differing bytes",
        diffs.len()
    );
    assert_eq!(
        mine, theirs,
        "the two doors produced media of different LENGTH"
    );
    // The ISO-9660 half must be identical: the first differing byte cannot be in it.
    // The ESP is a partition inside the image; every known difference is a FAT 8.3
    // directory entry's creation-time tenths, which live far past the primary volume
    // descriptor at 0x8000 and the path tables that follow it.
    assert!(
        diffs.len() <= 64,
        "{} bytes differ between the pane's medium and the reference — that is not \
         creation-time jitter, the doors built different things (first at {:#x})",
        diffs.len(),
        diffs.first().copied().unwrap_or(0)
    );
    if let Some(&first) = diffs.first() {
        assert!(
            first > 0x10000,
            "the FIRST differing byte is at {first:#x}, inside the ISO-9660 half — that \
             half is supposed to be byte-identical across the doors"
        );
    }
}