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

//! **ROBOT-UI: the ONE click, driven — `gunnar` → `installer:gunnar-installer` → ▶ KÖR.**
//!
//! Everything else in this tree tests the ⚒ scene against a STAGED constellation, which
//! is right for a scene that must know no product by name. This file is the other half:
//! the operator's actual gesture, on the REAL checkouts this box holds, ending in a REAL
//! QEMU/OVMF/KVM install whose serial says `INSTALLER: INSTALL-OK` and whose framebuffer
//! reaches the pane as a texture-sized `screen`.
//!
//! It drives the scene through its REAL widgets, resolved through the live AccessKit
//! tree — `type_into("artefakt", …)`, `click_label("sök")`, the row, the column, `BYGG`,
//! `▶ KÖR`. It never calls `update_json`, and it never opens a window: the whole drive is
//! off-screen `egui_kittest` (robot-UI first).
//!
//! `#[ignore]` because it BUILDS and BOOTS: it wants QEMU + OVMF + `/dev/kvm` + a
//! readable kernel, it masters an ISO, and it takes minutes. Run it deliberately:
//!
//! ```text
//! cargo test -p dwarves-gui --features vm-boot --test robot_gunnar_installer \
//!     -- --ignored --nocapture
//! ```
//!
//! gunnar is the DEFAULT product, not the only one: see [`named`] for the three env
//! knobs that point the same gesture at another repo's `[[build.installer]]`.
//!
//! # WHAT THIS PROVES, and the stand-in it used to prove instead
//!
//! **`▶ KÖR` boots the medium `BYGG` composed.** Every leg is asserted against the
//! product, by name:
//!
//! ```text
//! BYGG      ok=true, a named artifact, that file on disk        before ▶ is ever pressed
//! ▶ KÖR     the console names THAT file as the medium           and never says STAND-IN
//! install   korp-boot reads a cmdline carrying korp.entry=<p>   the product, off the ISO
//!           INSTALLER: INSTALL-OK                               the install itself
//! disk      the installed ESP's korp/cmdline carries            APPLIED OUTPUT, read off
//!           korp.entry=<p>                                      the bytes the installer wrote
//! reboot    a SECOND boot off that disk reaches korp-start's    the owner's "verifiera att
//!           `exec argv: ["<p>"` — the last line before fexecve   det går att boota om"
//! screen    the winning framebuffer DIFFERS from every other    the boot logo, per product
//!           product's, byte for byte
//! ```
//!
//! ## The stand-in this replaced, and why the frame gate is what it is
//!
//! Until 2026-08-16 `BootRun::boot_installer` compiled its own UEFI installer stub
//! (`korp-installer-efistub`, carrying an EMBEDDED korp `app.znippy`), mastered its own
//! ISO into `/tmp/dwarves-installer-<cell>-<pid>/`, and booted THAT. The medium at
//! `<repo>/.iso-out/<cell>.iso` was never opened.
//!
//! MEASURED that day, and from a run, not from the code: driving holger, `BYGG` went
//! `FEL` and produced **no artifact at all**, and ▶ still reached
//! `INSTALLER: INSTALL-OK`. gunnar's and holger's captured frames were **md5-identical,
//! all six each** — `c9573786…`, `7d4764e9…`, `76ec6119…`, `0e45ee0e…`, `0ecccb27…`,
//! `32b65f91…` — because it was one image every time.
//!
//! That is why [`the_frame_is_this_products_own`] exists and why it compares against
//! OTHER products' frames rather than measuring ink: an ink count, a coverage ratio and
//! "did we get a frame" were all green on the stand-in. Only "this is not the same
//! picture holger booted" could ever have caught it.
//!
//! Every refusal is reported BY CODE where the launcher speaks one (`dwarves-launch-N`):
//! a robot keys on the code, not the sentence.

use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::{Duration, Instant};

use facett_core::Facet;
use nornir_robotui::egui_kittest::Harness;
use nornir_robotui::{Observable, RobotSession};

use dwarves_gui::build_forge::DeclaredForge;
use dwarves_gui::build_scene::BuildScene;

/// The checkout the operator types.
const REPO: &str = "gunnar";
/// The row the installer variant hangs off (`[[build.installer]].variant`).
const ROW: &str = "gunnar-server";
/// The column label the forge renders for a `[[build.installer]]` variant.
const COLUMN: &str = "installer:gunnar-installer";

/// **The same drive, pointed at another product's installer.**
///
/// The gesture is not gunnar's — it is the operator's, and every declared
/// `[[build.installer]]` renders the same three widgets. Copying this file per
/// product would be 325 lines of twin (LAW 5) that drift the first time the pane
/// changes, so the three names it types are overridable and default to gunnar's:
///
/// ```text
/// DWARVES_ROBOT_REPO=korp \
/// DWARVES_ROBOT_ROW=korp-server \
/// DWARVES_ROBOT_COLUMN=installer:korp-installer \
///   cargo test -p dwarves-gui --features vm-boot --test robot_gunnar_installer \
///   -- --ignored --nocapture
/// ```
///
/// A fourth knob, `DWARVES_ROBOT_PRODUCT`, names the `korp.entry=` the installed disk
/// must start; it defaults to the repo name, which is what all three products declare.
///
/// Nothing else is parameterised. The assertions — a landed column pick, an armed BYGG,
/// the composed medium named on the console, `korp.entry=<product>` on the booted AND on
/// the installed cmdline, `INSTALL-OK`, the reboot's handoff line, a frame no other
/// product drew — are product-independent CLAIMS about a product-specific artifact, and
/// a knob that could weaken one of them would be a knob that turns the proof off.
fn named(var: &str, fallback: &'static str) -> String {
    std::env::var(var)
        .ok()
        .filter(|v| !v.trim().is_empty())
        .unwrap_or_else(|| fallback.to_string())
}

/// The scene as the robot's subject — the SAME `Facet::state_json` the shell exposes.
struct SceneApp(BuildScene);

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

/// **Where the real checkouts are.** `ConstellationBuilds::default_root()` is the
/// PARENT of the current directory, and a `cargo test` runs in the PACKAGE root
/// (`…/dwarves/dwarves-gui`) — so its default lands inside dwarves and discovers
/// nothing at all. Named here rather than worked around: `DWARVES_CONSTELLATION` is the
/// same override the shipped binary reads, and the fallback climbs the two levels a
/// test's cwd costs.
fn constellation_root() -> std::path::PathBuf {
    if let Some(r) = std::env::var_os("DWARVES_CONSTELLATION") {
        return std::path::PathBuf::from(r);
    }
    let here = std::env::current_dir().expect("cwd");
    let up = here
        .parent()
        .and_then(|p| p.parent())
        .map(std::path::Path::to_path_buf);
    up.unwrap_or(here)
}

/// Mount the scene over the REAL constellation, off-screen.
fn session() -> RobotSession<'static, SceneApp> {
    let root = constellation_root();
    eprintln!("[robot] constellation : {}", root.display());
    let forge = DeclaredForge::at(root);
    eprintln!("[robot] boot engine   : {}", forge.boot_engine());
    eprintln!(
        "[robot] declared repos: {}",
        forge.declared_repos().join(" ")
    );
    let scene = BuildScene::new(Box::new(forge));
    let harness = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(1600.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(2);
    robot
}

/// Pump real frames until `done` or the deadline. Returns whether it landed.
fn pump(
    robot: &mut RobotSession<'_, SceneApp>,
    secs: u64,
    what: &str,
    done: impl Fn(&RobotSession<'_, SceneApp>) -> bool,
) -> bool {
    let deadline = Instant::now() + Duration::from_secs(secs);
    let mut last = String::new();
    while Instant::now() < deadline {
        robot.run(1);
        if done(robot) {
            return true;
        }
        let phase = robot
            .state_at("/phase")
            .map(|v| v.to_string())
            .unwrap_or_else(|| "null".to_string());
        if phase != last {
            eprintln!("[robot] {what}: phase {phase}");
            last = phase;
        }
        std::thread::sleep(Duration::from_millis(40));
    }
    false
}

/// One captured frame as a P6 PPM — a raw framebuffer needs no encoder to be proof.
fn ppm_of(s: &dwarves_gui::build_scene::Screen) -> Vec<u8> {
    let mut ppm = format!("P6\n{} {}\n255\n", s.width, s.height).into_bytes();
    ppm.extend(s.rgba.chunks_exact(4).flat_map(|p| [p[0], p[1], p[2]]));
    ppm
}

/// Every line the pane is showing, for a report that names what the guest said.
fn lines(robot: &RobotSession<'_, SceneApp>) -> Vec<String> {
    robot
        .state_at("/lines")
        .and_then(|v| serde_json::from_value::<Vec<String>>(v).ok())
        .unwrap_or_default()
}

/// **The winning frame is THIS product's own** — the guard the stand-in could never
/// have survived, and the only one of the graphical assertions that could not.
///
/// Every drive records its inkiest frame's blake3 under `<tmp>/dwarves-boot-frames/`,
/// keyed by product. This one then reads every OTHER product's recorded hash and
/// refuses to match any of them.
///
/// It is deliberately a CROSS-RUN guard on files rather than an in-process one: the
/// three products are driven as three separate `cargo test` invocations (three env
/// knobs, one file — see [`named`]), so nothing in a single process ever sees two
/// products' screens. A hash a previous run left behind is the only witness there is.
///
/// A first-ever run has nothing to compare against and says so instead of passing
/// quietly — "no other product has been driven on this box" is a different sentence
/// from "the frames differ", and reporting the second when the first is true is how a
/// guard goes blind.
fn the_frame_is_this_products_own(product: &str, ppm: &[u8]) {
    let dir = std::env::temp_dir().join("dwarves-boot-frames");
    let _ = std::fs::create_dir_all(&dir);
    let mine = blake3::hash(ppm).to_hex().to_string();
    eprintln!("[robot] frame hash    : {product} blake3={mine}");

    let mut others: Vec<(String, String)> = Vec::new();
    if let Ok(entries) = std::fs::read_dir(&dir) {
        for e in entries.flatten() {
            let name = e.file_name().to_string_lossy().to_string();
            let Some(other) = name.strip_suffix(".blake3") else {
                continue;
            };
            if other == product {
                continue;
            }
            let Ok(theirs) = std::fs::read_to_string(e.path()) else {
                continue;
            };
            others.push((other.to_string(), theirs.trim().to_string()));
        }
    }
    for (who, hash) in &others {
        eprintln!("[robot] frame vs      : {who} blake3={hash}");
    }
    let verdict = frame_verdict(product, &mine, &others);
    // Recorded BEFORE the verdict is applied: a run that fails this guard must still
    // leave its hash behind, or the next run has nothing to compare against and the
    // guard quietly disarms itself exactly when it has just fired.
    std::fs::write(dir.join(format!("{product}.blake3")), &mine).expect("record the frame hash");
    match verdict {
        Ok(0) => eprintln!(
            "[robot] frame vs      : NOTHING — no other product has been driven on this box \
             yet, so the cross-product frame guard compared 0 screens. Drive a second \
             product to arm it; {} now holds this run's hash.",
            dir.display(),
        ),
        Ok(n) => eprintln!("[robot] frame vs      : {n} other product screen(s), all different"),
        Err(why) => panic!("{why}"),
    }
}

/// The pure half of [`the_frame_is_this_products_own`]: `Ok(n)` when `mine` matches none
/// of the `others`, having compared `n` of them; `Err(why)` naming the product whose
/// screen this one is byte-for-byte.
///
/// Split out so the guard itself can be seen RED — see
/// [`the_frame_guard_fires_on_two_products_with_one_screen`]. A guard that has only ever
/// been observed passing is a guard nobody has tested.
fn frame_verdict(product: &str, mine: &str, others: &[(String, String)]) -> Result<usize, String> {
    for (who, theirs) in others {
        if mine == theirs {
            return Err(format!(
                "the boot screen {product} drew is byte-for-byte the one {who} drew \
                 (blake3 {mine}). That is what a stand-in looks like: one medium booted for \
                 every product, or one logo baked for every product. Neither is {product} \
                 installing."
            ));
        }
    }
    Ok(others.len())
}

/// **RED, seen.** The exact shape 2026-08-16 measured — two products, one screen.
///
/// gunnar's and holger's captured frames were md5-identical, all six each, because ▶
/// booted the same korp-shaped stand-in for both. Those are the values below.
#[test]
fn the_frame_guard_fires_on_two_products_with_one_screen() {
    let one_screen = "32b65f91ac759010ecfcbd4d7ce88b11".to_string();
    let others = vec![("holger".to_string(), one_screen.clone())];
    let err = frame_verdict("gunnar", &one_screen, &others)
        .expect_err("two products showing ONE screen must not pass");
    assert!(err.contains("gunnar") && err.contains("holger"), "{err}");
    assert!(err.contains("byte-for-byte"), "{err}");

    // …and it passes only when they genuinely differ, having actually compared one.
    assert_eq!(frame_verdict("gunnar", "5c2bd9e6", &others), Ok(1));
    // A first-ever run compares nothing, and the count says so rather than the verdict.
    assert_eq!(frame_verdict("gunnar", "5c2bd9e6", &[]), Ok(0));
}

/// **The ssh-forward host port, read off the pane's own console line.** The live-ISO
/// leg prints `[dwarves] ssh forward: host 127.0.0.1:<port> -> guest 2222` the moment
/// the guest starts booting (`dwarves_gui::boot_run::ssh_forward_notice`); this pulls
/// `<port>` out of that line so the drive can open a socket to it.
///
/// `None` when NO such line is present. A boot that never announced a forward has no
/// address to serve through, and saying that is a different fact from guessing
/// [`LIVE_SSH_HOST_PORT`] — a phantom port would turn "the guest never came up" into a
/// connection refused against the wrong thing. Split out pure so it can be seen RED
/// without QEMU — see [`the_ssh_forward_port_parser_refuses_a_console_without_the_line`].
fn parse_ssh_forward_port(lines: &[String]) -> Option<u16> {
    const MARK: &str = "[dwarves] ssh forward: host 127.0.0.1:";
    for l in lines {
        let Some(idx) = l.find(MARK) else { continue };
        let rest = &l[idx + MARK.len()..];
        let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
        if let Ok(p) = digits.parse::<u16>() {
            return Some(p);
        }
    }
    None
}

/// **The listener over the tunnel is THIS product's own SSH server.** gunnar's
/// appliance sshd answers `SSH-2.0-gunnar_<ver>` (its own forge test,
/// `gunnar_appliance_iso`, asserts the same prefix over a forwarded port); a foreign
/// sshd, or a bare TCP echo, or nothing at all, is not the product serving.
///
/// `Ok(())` only when the banner names the product; `Err(why)` quotes what actually
/// answered. Product-parametric so the same guard serves korp/holger, and split out
/// pure so it can be seen RED against a wrong banner without QEMU — see
/// [`the_serving_banner_helper_passes_only_the_products_own_sshd`].
fn banner_names_the_product(banner: &str, product: &str) -> Result<(), String> {
    let want = format!("SSH-2.0-{product}");
    if banner.starts_with(&want) {
        Ok(())
    } else {
        Err(format!(
            "the listener on the ssh forward is not {product}'s own SSH server — it \
             answered {banner:?}, not an {want} banner. Nothing is SERVING through the tunnel."
        ))
    }
}

/// **RED, seen without QEMU.** The parser reads the port the live leg announces, and a
/// console that never announced a forward yields `None` — not a phantom port.
#[test]
fn the_ssh_forward_port_parser_refuses_a_console_without_the_line() {
    // The honest line, base port and rebound port both — the port is the load-bearing
    // field, read out of exactly what `ssh_forward_notice` emits.
    assert_eq!(
        parse_ssh_forward_port(&[
            "[dwarves] booting gunnar-appliance-iso".to_string(),
            "[dwarves] ssh forward: host 127.0.0.1:10022 -> guest 2222".to_string(),
        ]),
        Some(10022),
    );
    assert_eq!(
        parse_ssh_forward_port(&[
            "[dwarves] ssh forward: host 127.0.0.1:10024 -> guest 2222 (10022 was taken)"
                .to_string(),
        ]),
        Some(10024),
        "a rebound forward's port is still read, past the ` (… was taken)` suffix",
    );
    // The failing shape: a boot with no forward line. `None`, never a guessed 10022.
    assert_eq!(
        parse_ssh_forward_port(&["[dwarves] booting, no forward announced here".to_string()]),
        None,
    );
    assert_eq!(parse_ssh_forward_port(&[]), None);
}

/// **RED, seen without QEMU.** Only the product's own banner passes; OpenSSH, an empty
/// read, anything else is refused BY what it said.
#[test]
fn the_serving_banner_helper_passes_only_the_products_own_sshd() {
    banner_names_the_product("SSH-2.0-gunnar_0.1.0", "gunnar").expect("gunnar's own banner passes");
    // A foreign sshd on the forward is NOT gunnar serving — the exact miss the whole
    // proof exists to catch: a listener answered, but not the product's.
    let err = banner_names_the_product("SSH-2.0-OpenSSH_9.6p1", "gunnar")
        .expect_err("a foreign sshd must not pass as gunnar serving");
    assert!(
        err.contains("SSH-2.0-gunnar") && err.contains("OpenSSH"),
        "{err}"
    );
    assert!(err.contains("SERVING"), "{err}");
    // No banner at all — a connect that read zero bytes — is refused too.
    assert!(
        banner_names_the_product("", "gunnar").is_err(),
        "an empty banner must not pass"
    );
    // Product-parametric: the same guard serves another product's own banner.
    banner_names_the_product("SSH-2.0-holger_2.0", "holger").expect("holger's own banner passes");
}

/// One connect-and-read of the SSH identification string off `127.0.0.1:<port>`.
/// `Some(line)` once the guest's sshd answers, `None` while it is not yet up — the
/// drive retries between pumped frames. Mirrors gunnar's own forge-test probe: read the
/// banner, write a probe line back so the guest's protocol loop does not stall.
fn read_ssh_banner(port: u16) -> Option<String> {
    let mut s = TcpStream::connect(("127.0.0.1", port)).ok()?;
    s.set_read_timeout(Some(Duration::from_secs(3))).ok();
    let mut buf = [0u8; 256];
    match s.read(&mut buf) {
        Ok(n) if n > 0 => {
            let line = String::from_utf8_lossy(&buf[..n]).trim().to_string();
            let _ = s.write_all(b"SSH-2.0-probe\r\n");
            Some(line)
        }
        _ => None,
    }
}

/// The launcher's coded refusal, if the pane is showing one.
fn refusal(robot: &RobotSession<'_, SceneApp>) -> Option<String> {
    let hay = format!(
        "{}\n{}\n{}",
        robot
            .state_at("/phase")
            .map(|v| v.to_string())
            .unwrap_or_default(),
        robot
            .state_at("/note")
            .map(|v| v.to_string())
            .unwrap_or_default(),
        lines(robot).join("\n"),
    );
    hay.split(|c: char| !(c.is_ascii_alphanumeric() || c == '-'))
        .find(|w| w.starts_with("dwarves-launch-"))
        .map(str::to_string)
}

#[test]
#[ignore = "builds and BOOTS: needs QEMU + OVMF + /dev/kvm, takes minutes"]
fn a_robot_clicks_gunnar_installer_and_watches_it_install_in_the_pane() {
    let repo = named("DWARVES_ROBOT_REPO", REPO);
    let row = named("DWARVES_ROBOT_ROW", ROW);
    let column = named("DWARVES_ROBOT_COLUMN", COLUMN);
    // The product the installed disk starts — `korp.entry=` in the cell's declaration,
    // which for all three products is the repo's own name. Overridable for a repo whose
    // checkout and product disagree, never derived from anything the guest said (a
    // marker read out of the serial and then asserted against that same serial is the
    // identity value with extra steps).
    let product = named("DWARVES_ROBOT_PRODUCT", "gunnar");
    let product = if std::env::var_os("DWARVES_ROBOT_PRODUCT").is_some() {
        product
    } else {
        repo.clone()
    };
    let (repo, row, column, product) = (
        repo.as_str(),
        row.as_str(),
        column.as_str(),
        product.as_str(),
    );
    eprintln!("[robot] driving       : {repo} → {row} → {column} (product {product})");
    let mut robot = session();

    // RED — nothing is resolved before anything is typed.
    assert_eq!(
        robot
            .state_at("/rows")
            .and_then(|v| v.as_array().map(Vec::len)),
        Some(0)
    );
    assert_eq!(robot.state_at("/can_build"), Some(serde_json::json!(false)));

    // 1 — TYPE the artifact name into the real field, then CLICK the real search button.
    robot
        .type_into("artefakt", repo)
        .expect("the artefakt field is reachable");
    robot
        .click_label("sök")
        .expect("the sök button is reachable");
    robot.run(2);
    let recipe = robot.state_at("/recipe");
    assert_ne!(
        recipe,
        Some(serde_json::Value::Null),
        "the search resolved no recipe for {repo}: {:?}",
        robot.state_at("/error"),
    );
    eprintln!("[robot] recipe        : {recipe:?}");

    // 2 — CLICK the row the installer chains off.
    robot
        .click_label(row)
        .unwrap_or_else(|e| panic!("the {row} row is not clickable: {e}"));
    robot.run(2);
    assert_eq!(robot.state_at("/row"), Some(serde_json::json!(row)));

    // The column strip this row actually renders — printed BEFORE the click, so a
    // missing column names what WAS there instead of only what was not.
    let columns: Vec<String> = robot
        .state_at("/rows")
        .and_then(|v| v.as_array().cloned())
        .unwrap_or_default()
        .iter()
        .find(|r| r["name"] == serde_json::json!(row))
        .and_then(|r| r["targets"].as_array().cloned())
        .unwrap_or_default()
        .iter()
        .map(|t| {
            format!(
                "{}{}",
                t["name"].as_str().unwrap_or("?"),
                if t["declared"] == serde_json::json!(true) {
                    ""
                } else {
                    "(grey)"
                }
            )
        })
        .collect();
    eprintln!("[robot] columns       : {}", columns.join(" "));

    // 3 — CLICK the installer column. A column the recipe does not declare is disabled, so
    //     a landed pick is itself the proof that the repo DECLARES this installer.
    robot
        .click_label(column)
        .unwrap_or_else(|e| panic!("the {column} column is not clickable: {e}"));
    robot.run(2);
    assert_eq!(robot.state_at("/target"), Some(serde_json::json!(column)));
    assert_eq!(
        robot.state_at("/pane"),
        Some(serde_json::json!("console")),
        "an installer variant did not open the Console pane",
    );
    assert_eq!(
        robot.state_at("/can_build"),
        Some(serde_json::json!(true)),
        "BYGG is not armed"
    );

    // 3b — TYPE the cell's declared per-site parameters into their real inputs.
    //
    //      The pane renders `[[build.*.params]]` as editable settings whose accessible
    //      name IS the parameter name, so the robot types into them exactly as an
    //      operator would. gunnar declares none and this loop does nothing; holger
    //      declares four, one of them REQUIRED (`holger_ron`), and without a value BYGG
    //      refuses BY NAME — which is the declaration doing its job, not a blocker to be
    //      worked around. `@<path>` reads the value out of a file, the same door
    //      `--param` speaks.
    //
    //      Semicolon-separated because a value may contain `=` (an `@path` may not, but
    //      a text parameter can) and because a shell-quoted env var is the one channel
    //      a drive has.
    for pair in named("DWARVES_ROBOT_PARAMS", "").split(';') {
        let Some((key, value)) = pair.split_once('=') else {
            continue;
        };
        let (key, value) = (key.trim(), value.trim());
        if key.is_empty() {
            continue;
        }
        robot.type_into(key, value).unwrap_or_else(|e| {
            panic!("the declared parameter input '{key}' is not reachable: {e}")
        });
        eprintln!("[robot] param         : {key}={value}");
    }
    robot.run(2);

    // 4 — CLICK BYGG, and wait for the build to settle. The pane below only exists once
    //     the build produced an outcome, so this is the gate on the ▶ button existing.
    robot
        .click_label("BYGG")
        .expect("the BYGG button is reachable");
    let built = pump(&mut robot, 900, "bygg", |r| {
        r.state_at("/outcome").is_some_and(|v| !v.is_null())
    });
    // `state_json` carries the phase LABEL ("FEL"); the reason lives in the phase itself,
    // and a failure whose reason a driver cannot print is a failure nobody can act on.
    if let Some(dwarves_gui::build_scene::Phase::Failed(why)) = robot.app().0.report().phase.clone()
    {
        eprintln!("[robot] BYGG failed   : {why}");
    }
    eprintln!("[robot] build phase   : {:?}", robot.state_at("/phase"));
    eprintln!("[robot] outcome       : {:?}", robot.state_at("/outcome"));
    eprintln!("[robot] note          : {:?}", robot.state_at("/note"));
    assert!(
        built,
        "the build never settled — no outcome, so no pane and no ▶"
    );

    // **THE BUILD MUST HAVE SUCCEEDED, and it is asserted HERE — before ▶.**
    //
    // "An outcome exists" was the whole gate until 2026-08-16, and a FAILED build
    // produces an outcome too. MEASURED that day, driving holger: BYGG went `FEL`
    // with `required parameter 'holger_ron' … was not supplied`, `ok=false`,
    // `artifact=""` — and the test went GREEN, INSTALL-OK and all, because ▶ KÖR
    // does not boot what BYGG built. It re-masters through `boot_installer`, which
    // fell back to a korp-appliance-lean SCAFFOLD; the serial said
    // `INSTALLER: INSTALL-OK`, the pane got a full-bleed splash, and every
    // assertion below passed over a product that had not been built at all.
    //
    // So the three facts that separate "this product installed" from "something
    // installed" are checked before the pane is ever asked to boot: the build says
    // it succeeded, it NAMES an artifact, and that artifact is on disk. A drive
    // that cannot get past this line is telling the truth about the product.
    let outcome = robot
        .state_at("/outcome")
        .unwrap_or(serde_json::Value::Null);
    assert_eq!(
        outcome["ok"],
        serde_json::json!(true),
        "BYGG did not succeed, so anything ▶ boots afterwards is not this product: {:?}",
        robot.state_at("/phase"),
    );
    let artifact = outcome["artifact"].as_str().unwrap_or_default().to_string();
    assert!(
        !artifact.is_empty(),
        "the build reported ok with NO artifact — there is nothing for ▶ to boot",
    );
    assert!(
        std::path::Path::new(&artifact).is_file(),
        "the build named {artifact} and no such file is on disk",
    );
    eprintln!("[robot] medium        : {artifact}");

    // 5 — CLICK ▶ KÖR. It boots THAT medium: a blank target at the cell's declared size,
    //     the ISO on `-cdrom` AND on read-only virtio (the appliance kernel has no
    //     ATA/AHCI, so the PID-1's CD001 probe would find no `sr0`), gating on INSTALL-OK,
    //     then a SECOND boot off the disk it just wrote.
    robot
        .click_label("▶ KÖR")
        .expect("the console pane's ▶ KÖR button is reachable");
    robot.run(2);
    assert_eq!(
        robot.state_at("/live"),
        Some(serde_json::json!(true)),
        "▶ KÖR did not bring a subject up{}",
        refusal(&robot)
            .map(|c| format!(" — refused {c}"))
            .unwrap_or_default(),
    );

    // 6 — WATCH it install, KEEPING THE FRAME WITH THE MOST INK ON IT.
    //
    // The last frame is the wrong frame. An install is ~1.4 s and the pane reboots
    // straight into the installed disk afterwards, so by the time the serial says
    // INSTALL-OK the framebuffer has already gone black for the second boot's firmware
    // — MEASURED 2026-08-16: 1280×800 captured, 16 non-zero bytes in the whole image,
    // which is a PPM header and nothing else. A black frame is a captured frame, so
    // "did we get a frame" is not the question. The question is whether the guest DREW
    // anything, and the answer is kept as the run goes: every frame is weighed and the
    // inkiest one survives.
    let mut best: Option<dwarves_gui::build_scene::Screen> = None;
    let mut best_ink = 0usize;
    let mut seen: std::collections::BTreeSet<(usize, usize, usize)> =
        std::collections::BTreeSet::new();
    let deadline = Instant::now() + Duration::from_secs(900);
    let mut installed = false;
    // korp-start's last line before `fexecve` replaces the process
    // (`korp-installer/src/bin/korp-start.rs`, argv[0] = the payload's entry). Reaching
    // it off the INSTALLED disk is the whole reboot claim: firmware → korp-boot →
    // vmlinuz → korp-start → payload verified → this product handed control.
    // **`INSTALLER: `-prefixed on purpose.** korp-start emits every line through the
    // installer's serial sink, so the real handoff reads
    // `INSTALLER: exec argv: ["gunnar", "serve", …]`. The pane's OWN narration also
    // names the gate it is waiting for — `[dwarves] … gate `exec argv: ["gunnar"` …` —
    // and it is printed BEFORE the second boot starts. MEASURED 2026-08-16: matching
    // the bare form latched on that narration, so a run whose second boot never
    // happened at all reported a reboot. The prefix is what separates what the GUEST
    // said from what the pane said about it.
    let handoff = format!("INSTALLER: exec argv: [\"{product}");
    let mut rebooted: Option<Instant> = None;
    let mut settled: Option<Instant> = None;
    while Instant::now() < deadline {
        robot.run(1);
        if let Some(s) = robot.app().0.report().screen.clone() {
            let ink = s.rgba.iter().filter(|b| **b != 0).count();
            // EVERY distinct frame is kept, not only the winner: "the guest drew
            // something" is not the same claim as "the guest drew THE SPLASH", and the
            // only way to tell them apart is to look at the frames.
            if seen.insert((s.width, s.height, ink)) {
                let out = std::env::temp_dir()
                    .join(format!("dwarves-{repo}-frame-{:02}.ppm", seen.len()));
                let _ = std::fs::write(&out, ppm_of(&s));
                eprintln!(
                    "[robot] frame         : {}×{} ink {ink} → {}",
                    s.width,
                    s.height,
                    out.display(),
                );
            }
            if ink > best_ink {
                best_ink = ink;
                best = Some(s);
            }
        }
        if !installed && lines(&robot).iter().any(|l| l.contains("INSTALL-OK")) {
            eprintln!("[robot] INSTALL-OK    : latched");
            installed = true;
        }
        if rebooted.is_none() && lines(&robot).iter().any(|l| l.contains(&handoff)) {
            eprintln!("[robot] reboot handoff: latched — {handoff}\"…]");
            rebooted = Some(Instant::now());
        }
        // **The run ends when the SECOND boot has spoken, not three seconds after
        // INSTALL-OK.**
        //
        // MEASURED 2026-08-16, the first run on the real medium: the old three-second
        // grace stopped the watch while the installed disk was still in its firmware,
        // and the drive reported on an install whose reboot it had not waited for. An
        // install that is never booted is bytes on a disk.
        //
        // The pane's own terminal phase is the other exit: `KLAR`/`FEL` means the whole
        // two-phase console resolved, and a frame grace after it lands the final screen.
        if rebooted.is_some_and(|t| t.elapsed() > Duration::from_secs(12)) {
            break;
        }
        let live = robot.state_at("/live") == Some(serde_json::json!(true));
        match (live, settled) {
            (false, None) => settled = Some(Instant::now()),
            (false, Some(t)) if t.elapsed() > Duration::from_secs(10) => break,
            (true, _) => settled = None,
            _ => {}
        }
        // The OTHER exit: the pane's boot has ended (`/live` went false), plus a grace
        // for the last frames to land.
        //
        // It reads `/live` and not `/phase`. MEASURED 2026-08-16: `/phase` is the
        // BUILD's phase — `build_forge::poll` derives it from `(run, outcome)` alone —
        // so it says KLAR the moment BYGG succeeds and keeps saying it for the whole
        // boot. Watching it turned this loop into a ten-second window that started at
        // BYGG: holger's guest reaches INSTALL-OK at 1.9 s and won it, gunnar's lost it
        // once the install guest's graceful shutdown was added, and the drive then
        // reported "never came back up" about a boot it had stopped watching.
        std::thread::sleep(Duration::from_millis(10));
    }

    for l in lines(&robot) {
        eprintln!("[serial] {l}");
    }
    eprintln!("[robot] phase         : {:?}", robot.state_at("/phase"));
    eprintln!("[robot] refusal code  : {:?}", refusal(&robot));

    // The guest's REAL screen, written out of the SAME `Report` the pane draws its
    // texture from — a P6 PPM, because a raw framebuffer needs no encoder to be proof.
    if let Some(s) = &best {
        let out = std::env::temp_dir().join(format!("dwarves-{repo}-installer-screen.ppm"));
        match std::fs::write(&out, ppm_of(s)) {
            Ok(()) => eprintln!(
                "[robot] gfx frame     : {} ({}×{}, {best_ink} non-black bytes)",
                out.display(),
                s.width,
                s.height,
            ),
            Err(e) => eprintln!("[robot] gfx frame FAILED: {e}"),
        }
    }

    let serial = lines(&robot).join("\n");

    // ── TEAR THE GUEST DOWN, BEFORE ANY ASSERTION ───────────────────────────────
    //
    // Ordered here on purpose. An assertion that fires unwinds past everything below
    // it, so a teardown at the end of the test only ever runs on a PASSING run —
    // MEASURED 2026-08-16: four failing drives left four live guests, one each. It
    // also releases the disk before the readback opens it.
    //
    // MEASURED 2026-08-16: three drives left three live `qemu-system-x86_64`
    // processes behind, one per run, each still holding its 64 GiB target image. The
    // reason is not a leak in the pane — it is that tunnr spawns QEMU in its OWN
    // process group (so a killed harness cannot orphan a half-torn-down guest), and a
    // `cargo test` that has finished asserting simply exits while the reboot worker is
    // still lingering on the running guest for framebuffer capture. Nothing ever told
    // it to stop.
    //
    // So the drive presses the pane's own ✕ STOPP — the operator's gesture, not a
    // `pkill` — and pumps long enough for the cancel to travel: cancel → the console
    // pump's kill flag → RebootConsole::kill → the reboot worker's 50 ms poll → the
    // guest. A run that cannot reach the button says so rather than leaving a VM
    // behind quietly.
    if robot.state_at("/live") == Some(serde_json::json!(true)) {
        match robot.click_label("✕ STOPP") {
            Ok(()) => {
                let stopped = pump(&mut robot, 25, "stopp", |r| {
                    r.state_at("/live") == Some(serde_json::json!(false))
                });
                eprintln!("[robot] stopp         : live=false after cancel: {stopped}");
            }
            Err(e) => eprintln!("[robot] stopp         : the ✕ STOPP button is not reachable: {e}"),
        }
    }
    // The worker kills the guest ~50 ms after it sees the flag; a short settle so the
    // process is reaped before this one exits and the group outlives it.
    std::thread::sleep(Duration::from_secs(2));

    // ── THE MEDIUM ──────────────────────────────────────────────────────────────
    // The pane announces which file it is about to boot, before it boots it, and says
    // STAND-IN when there is none. Both halves are asserted: a console that merely
    // FAILED to say STAND-IN could be a console that says nothing.
    assert!(
        serial.contains(&format!("medium: {artifact}")),
        "the console never named {artifact} as the medium it booted:\n{serial}",
    );
    assert!(
        !serial.contains("STAND-IN"),
        "▶ fell back to the korp-installer efistub SCAFFOLD — whatever installed, it was \
         not {product}:\n{serial}",
    );

    // ── THE INSTALL, NAMING THE PRODUCT ─────────────────────────────────────────
    // korp-boot echoes the cmdline it read off the medium's own ESP. `korp.entry=<p>`
    // is what compose_installer_row packed the payload under and what korp-start will
    // resolve — so a medium carrying another product's entry cannot pass here.
    assert!(
        serial.contains(&format!("korp.entry={product}")),
        "the booted medium's cmdline never named korp.entry={product} — this is not \
         {product}'s medium:\n{serial}",
    );
    assert!(
        installed,
        "no INSTALLER: INSTALL-OK on the serial{}",
        refusal(&robot)
            .map(|c| format!(" — refused {c}"))
            .unwrap_or_default(),
    );

    // ── THE DISK, READ BACK ─────────────────────────────────────────────────────
    // **Applied output.** Everything above is the guest talking. This is the bytes the
    // installer actually wrote: the GPT entry array at LBA 2, then `korp/cmdline` out
    // of the ESP's FAT, through `korp_installer::kvmproof` — the same range readers
    // holger's and gunnar's own crown proofs use.
    let disk = dwarves_gui::boot_run::TunnrBootLauncher::installer_disk_path(&cell_of(column));
    assert!(
        disk.is_file(),
        "the installer's target disk is not at {}",
        disk.display()
    );
    let mbr = korp_installer::kvmproof::read_at(&disk, 510, 2);
    assert_eq!(
        (mbr[0], mbr[1]),
        (0x55, 0xAA),
        "no protective MBR on {} — nothing partitioned this disk",
        disk.display(),
    );
    let (esp_first, esp_last) = korp_installer::kvmproof::gpt_entry(&disk, 0);
    let installed_cmdline =
        korp_installer::kvmproof::read_fat_file(&disk, esp_first, esp_last, "korp/cmdline")
            .map(|b| String::from_utf8_lossy(&b).to_string())
            .unwrap_or_else(|| {
                panic!("the installed ESP (p1 LBA {esp_first}..{esp_last}) carries no korp/cmdline")
            });
    eprintln!("[robot] installed cmd : {installed_cmdline}");
    assert!(
        installed_cmdline.contains(&format!("korp.entry={product}")),
        "the INSTALLED disk's cmdline does not start {product}: {installed_cmdline}",
    );

    // ── THE REBOOT ──────────────────────────────────────────────────────────────
    // The owner: "du skall verifiera att det går att boota om". A machine that installed
    // and cannot boot is bytes on a disk, so the second boot is asserted on ITS OWN
    // marker — korp-start's handoff line, which carries argv[0] and therefore names
    // this product and no other.
    assert!(
        rebooted.is_some(),
        "the machine installed but never came back up: no `{handoff}\"…]` off the \
         installed disk. phase {:?}\n{serial}",
        robot.state_at("/phase"),
    );
    // What the handoff line does NOT prove is that the app then RAN — it is printed
    // immediately BEFORE `fexecve`. So the two ways that exec dies are refused by name,
    // the same pair gunnar's and holger's own first-boot proofs refuse
    // (`isoproof::FirstBootProof::refuse_serial`). A payload that cannot be executed
    // (a host-glibc binary with a PT_INTERP no initramfs can resolve, say) says
    // `INSTALLER: FAILED exec` right after the handoff, and a machine that boots and
    // then dies is not a machine that boots.
    for refuse in ["FAILED exec", "Kernel panic"] {
        assert!(
            !serial.contains(refuse),
            "the reboot reached the handoff and then died with {refuse:?}:\n{serial}",
        );
    }
    // **And the product SPOKE.** Everything korp-start prints is prefixed `INSTALLER: `;
    // after `fexecve` the process IS the app and its stdout is unprefixed. So a line
    // after the handoff that is neither korp-start's protocol nor a kernel `[    n.nnn]`
    // stamp can only have come from the installed product itself. That is the difference
    // between "the loader was about to exec" and "the thing on this disk is running",
    // and it is product-neutral: it names no marker any one product owns.
    let spoke: Vec<&str> = serial
        .split('\n')
        .skip_while(|l| !l.contains(&handoff))
        .skip(1)
        .map(str::trim)
        .filter(|l| !l.is_empty() && !l.starts_with("INSTALLER:") && !l.starts_with('['))
        .collect();
    eprintln!(
        "[robot] app said      : {} line(s), first: {:?}",
        spoke.len(),
        spoke.first()
    );
    assert!(
        !spoke.is_empty(),
        "korp-start handed off to {product} and nothing but korp-start and the kernel ever \
         spoke again — the payload was reached but never ran:\n{serial}",
    );

    // ── THE SCREEN ──────────────────────────────────────────────────────────────
    let s = best
        .as_ref()
        .expect("the graphics console never produced a frame for the pane");
    eprintln!(
        "[robot] winning frame : {}×{} {best_ink}/{} bytes inked ({:.0}%)",
        s.width,
        s.height,
        s.rgba.len(),
        best_ink as f64 / s.rgba.len() as f64 * 100.0,
    );
    the_frame_is_this_products_own(product, &ppm_of(s));
}

/// The launch cell's name, out of the column label the operator clicked
/// (`installer:gunnar-installer` → `gunnar-installer`). The pane names its columns
/// `<kind>:<cell>`, so this is the one place the drive has to undo that.
fn cell_of(column: &str) -> String {
    column
        .split_once(':')
        .map_or_else(|| column.to_string(), |(_, c)| c.to_string())
}

/// **The ONE gesture, to a LIVE iso-kind boot** — the drive both iso-kind proofs share.
///
/// Resolves the checkout, picks the row and the iso column, presses BYGG and asserts
/// the composed medium exists on disk BEFORE ▶ (the exact gate the installer drive
/// learned 2026-08-16: a FAILED build has an outcome too), then presses ▶ KÖR and
/// asserts the pane brought a live subject up. Returns the artifact the console will
/// name as the medium.
///
/// Written once because the gesture is identical whether the watch that follows proves
/// the boot chain (the frame + handoff proof) or the product SERVING (the ssh-banner
/// proof) — two claims about the same live guest, not two gestures (LAW 5).
///
/// # THE ANCHOR IS PART OF THE GESTURE, not of one proof's assertions
///
/// It used to be set inside the serving proof only, and that is the whole reason the
/// boot proof was RED on main: an appliance ISO composed with no
/// `KORP_ROOT_SIGNING_KEY` stages no `root.pub`/`app.sig`, `korp.require_signed`
/// defaults CLOSED, and `korp-start` refuses the payload it just verified the blake3
/// of — `signature-over-root: NOT-ENROLLED`. Two proofs of one medium cannot compose
/// it two ways, so the enrolment moved to the ONE place that composes it.
fn drive_iso_variant_to_live(
    robot: &mut RobotSession<'_, SceneApp>,
    repo: &str,
    row: &str,
    column: &str,
) -> String {
    // 0 — DEV-SIGNED, or nothing this drive composes can boot. See the doc above.
    //     Named on the console so it is never a silent default, and so a run that
    //     brought its OWN key says which one composed the medium.
    if std::env::var_os("KORP_ROOT_SIGNING_KEY").is_none() {
        std::env::set_var("KORP_ROOT_SIGNING_KEY", "dev");
    }
    eprintln!(
        "[robot] signing key   : KORP_ROOT_SIGNING_KEY={:?} — the appliance is fail-closed, \
         so an UNENROLLED medium refuses its own payload at korp-start",
        std::env::var("KORP_ROOT_SIGNING_KEY").ok(),
    );

    // 1 — resolve the checkout, pick the row, pick the iso column.
    robot
        .type_into("artefakt", repo)
        .expect("the artefakt field is reachable");
    robot
        .click_label("sök")
        .expect("the sök button is reachable");
    robot.run(2);
    assert_ne!(
        robot.state_at("/recipe"),
        Some(serde_json::Value::Null),
        "the search resolved no recipe for {repo}: {:?}",
        robot.state_at("/error"),
    );
    robot
        .click_label(row)
        .unwrap_or_else(|e| panic!("the {row} row is not clickable: {e}"));
    robot.run(2);
    robot
        .click_label(column)
        .unwrap_or_else(|e| panic!("the {column} column is not clickable: {e}"));
    robot.run(2);
    assert_eq!(robot.state_at("/target"), Some(serde_json::json!(column)));
    assert_eq!(
        robot.state_at("/can_build"),
        Some(serde_json::json!(true)),
        "BYGG is not armed"
    );

    // 2 — BYGG, and the composed medium MUST exist before ▶ is ever pressed.
    robot
        .click_label("BYGG")
        .expect("the BYGG button is reachable");
    let built = pump(robot, 900, "bygg", |r| {
        r.state_at("/outcome").is_some_and(|v| !v.is_null())
    });
    if let Some(dwarves_gui::build_scene::Phase::Failed(why)) = robot.app().0.report().phase.clone()
    {
        eprintln!("[robot] BYGG failed   : {why}");
    }
    assert!(
        built,
        "the build never settled — no outcome, so no pane and no ▶"
    );
    let outcome = robot
        .state_at("/outcome")
        .unwrap_or(serde_json::Value::Null);
    assert_eq!(
        outcome["ok"],
        serde_json::json!(true),
        "BYGG did not succeed, so anything ▶ boots afterwards is not this product: {:?}",
        robot.state_at("/phase"),
    );
    let artifact = outcome["artifact"].as_str().unwrap_or_default().to_string();
    assert!(
        !artifact.is_empty(),
        "the build reported ok with NO artifact — nothing to boot"
    );
    assert!(
        std::path::Path::new(&artifact).is_file(),
        "the build named {artifact} and no such file is on disk",
    );
    eprintln!("[robot] medium        : {artifact}");

    // 3 — ▶ KÖR. The live leg boots THE composed file off -cdrom, GPU framebuffer on.
    robot
        .click_label("▶ KÖR")
        .expect("the console pane's ▶ KÖR button is reachable");
    robot.run(2);
    assert_eq!(
        robot.state_at("/live"),
        Some(serde_json::json!(true)),
        "▶ KÖR did not bring a subject up{}",
        refusal(robot)
            .map(|c| format!(" — refused {c}"))
            .unwrap_or_default(),
    );
    artifact
}

/// **ROBOT-UI: BUILT is not done, ANSWERED is done — `gunnar` → `iso:gunnar-appliance-iso`
/// → ▶ KÖR → the appliance SERVES over the ssh forward.**
///
/// The sibling proof [`a_robot_clicks_gunnar_appliance_iso_and_watches_it_boot_in_the_pane`]
/// ends at korp-start's exec handoff and the framebuffer — that a boot HAPPENED. This
/// one carries the house law one step further (the doctrine
/// `dwarves/crates/dwarves/tests/variant_drive.rs` states verbatim: "BUILT is not done.
/// ANSWERED is done"): it drives the SAME gesture, then opens a TCP socket to the
/// live-ISO ssh forward and asserts the guest returns gunnar's OWN SSH banner —
/// `SSH-2.0-gunnar` — i.e. the product is answering a real request through the tunnel.
/// The gate is the CODE on the wire, never a rendered string.
///
/// The chain proven: BYGG composes the dev-signed medium → ▶ KÖR boots it live off
/// `-cdrom`, GPU framebuffer on, its user netdev carrying `hostfwd=tcp:127.0.0.1:<port>-:2222`
/// → the console announces the forward's host address → the drive reads `<port>` off
/// that line and connects → gunnar's sshd, bound in the guest on `0.0.0.0:2222`,
/// answers `SSH-2.0-gunnar_<ver>`. A boot that reaches the framebuffer but whose sshd
/// never binds (a payload the root-of-trust refused, a selftest that fail-closed) draws
/// a frame and then serves nothing — which is exactly the gap a banner assertion, and
/// only a banner assertion, closes.
///
/// # DEV-SIGNED IS REQUIRED
///
/// The appliance is fail-closed: `require_signed` defaults CLOSED, so an UNSIGNED iso
/// refuses its own payload at selftest and sshd never binds. The SHARED driver
/// [`drive_iso_variant_to_live`] sets `KORP_ROOT_SIGNING_KEY=dev` if the operator did
/// not — it is the composition's input, so it sits with the composition rather than in
/// one of the two proofs — so signature-over-root=PASS and gunnar reaches serving.
/// Prefer to pass it explicitly on the run line (below) so it is never a silent default.
///
/// # RUN IT (on a box with QEMU + OVMF + /dev/kvm — NOT dwarf, which has no qemu)
///
/// ```bash
/// KORP_ROOT_SIGNING_KEY=dev \
///   cargo test -p dwarves-gui --features vm-boot --test robot_gunnar_installer \
///   a_robot_builds_gunnar_and_the_appliance_serves -- --ignored --nocapture
/// ```
///
/// The same env knobs as the boot proof re-point it at another product
/// (`DWARVES_ROBOT_ISO_REPO` / `_ROW` / `_COLUMN`, plus `DWARVES_ROBOT_PRODUCT` for the
/// banner prefix — gunnar answers `SSH-2.0-gunnar`).
#[test]
#[ignore = "builds and BOOTS and SERVES: needs QEMU + OVMF + /dev/kvm and gunnar's sshd answering over the forward, takes minutes"]
fn a_robot_builds_gunnar_and_the_appliance_serves() {
    // The dev-signing default used to be set HERE, which is why the sibling boot proof
    // was RED: the enrolment belongs to the composition, not to one proof's asserts, so
    // it lives in `drive_iso_variant_to_live` now — one writer, both drives.
    let repo = named("DWARVES_ROBOT_ISO_REPO", REPO);
    let row = named("DWARVES_ROBOT_ISO_ROW", ROW);
    let column = named("DWARVES_ROBOT_ISO_COLUMN", "iso:gunnar-appliance-iso");
    let product = if std::env::var_os("DWARVES_ROBOT_PRODUCT").is_some() {
        named("DWARVES_ROBOT_PRODUCT", "gunnar")
    } else {
        repo.clone()
    };
    let (repo, row, column, product) = (
        repo.as_str(),
        row.as_str(),
        column.as_str(),
        product.as_str(),
    );
    eprintln!("[robot] driving       : {repo} → {row} → {column} (product {product})");
    let mut robot = session();

    // 1–3 — the ONE gesture, to a live boot (shared with the boot proof).
    let artifact = drive_iso_variant_to_live(&mut robot, repo, row, column);

    // 4 — READ THE FORWARD PORT OFF THE CONSOLE. The live leg announces
    //     `[dwarves] ssh forward: host 127.0.0.1:<port> -> guest 2222` the moment the
    //     guest starts booting; the address is read off the pane, never guessed. A boot
    //     that never announced a forward has no address to serve through and fails here.
    let mut port: Option<u16> = None;
    let deadline = Instant::now() + Duration::from_secs(60);
    while Instant::now() < deadline && port.is_none() {
        robot.run(1);
        port = parse_ssh_forward_port(&lines(&robot));
        std::thread::sleep(Duration::from_millis(40));
    }
    let port = port.unwrap_or_else(|| {
        panic!(
            "the live leg never announced its ssh forward on the console — no \
             `[dwarves] ssh forward: host 127.0.0.1:<port>` line, so there is no address \
             for the product to serve through:\n{}",
            lines(&robot).join("\n"),
        )
    });
    eprintln!(
        "[robot] ssh forward   : host 127.0.0.1:{port} -> guest {}",
        dwarves_gui::boot_run::LIVE_SSH_GUEST_PORT,
    );

    // 5 — SERVE: connect to the forward and read the guest's SSH banner. The guest is
    //     still booting on the pane's console pump, so frames are pumped between connect
    //     attempts — sshd binds only once the payload was verified and exec'd, which is
    //     the whole serving claim. This is a BOUND LISTENER answering on the wire, not a
    //     marker in a log (a marker can be printed by code that then dies).
    let mut best_ink = 0usize;
    let mut banner: Option<String> = None;
    let deadline = Instant::now() + Duration::from_secs(180);
    while Instant::now() < deadline && banner.is_none() {
        robot.run(1);
        if let Some(s) = robot.app().0.report().screen.clone() {
            best_ink = best_ink.max(s.rgba.iter().filter(|b| **b != 0).count());
        }
        banner = read_ssh_banner(port);
        if banner.is_none() {
            std::thread::sleep(Duration::from_millis(200));
        }
    }
    for l in lines(&robot) {
        eprintln!("[serial] {l}");
    }
    let serial = lines(&robot).join("\n");
    eprintln!("[robot] inkiest frame : {best_ink} non-black bytes");

    // Tear the guest down BEFORE asserting (same reasoning as the sibling drives: a
    // firing assertion unwinds past a teardown placed after it, leaving a live guest).
    if robot.state_at("/live") == Some(serde_json::json!(true)) {
        match robot.click_label("✕ STOPP") {
            Ok(()) => {
                let stopped = pump(&mut robot, 25, "stopp", |r| {
                    r.state_at("/live") == Some(serde_json::json!(false))
                });
                eprintln!("[robot] stopp         : live=false after cancel: {stopped}");
            }
            Err(e) => eprintln!("[robot] stopp         : the ✕ STOPP button is not reachable: {e}"),
        }
    }
    std::thread::sleep(Duration::from_secs(2));

    // ── THE MEDIUM: the console named THE composed file, and no stand-in, no refusal ──
    assert!(
        serial.contains(&format!("medium: {artifact}")),
        "the console never named {artifact} as the medium it booted:\n{serial}",
    );
    assert!(
        !serial.contains("STAND-IN"),
        "▶ fell back to the stand-in — whatever booted, it was not {product}:\n{serial}",
    );
    assert_eq!(
        refusal(&robot),
        None,
        "a composed iso-kind variant was refused before it could serve:\n{serial}",
    );

    // ── ANSWERED: the product is SERVING through the tunnel ──────────────────────
    let banner = banner.unwrap_or_else(|| {
        panic!(
            "no SSH banner on 127.0.0.1:{port} within 180 s — {product} BUILT and BOOTED \
             but never ANSWERED: sshd never bound (a fail-closed selftest, or an unsigned \
             payload the root-of-trust refused, draws a frame and serves nothing). Set \
             KORP_ROOT_SIGNING_KEY=dev.\n{serial}"
        )
    });
    eprintln!("[robot] banner        : {banner:?}");
    banner_names_the_product(&banner, product).unwrap_or_else(|e| panic!("{e}\n{serial}"));
    eprintln!("[robot] SERVED        : {product} answered {banner:?} over 127.0.0.1:{port} — ANSWERED is done");
}

/// **ROBOT-UI: the composed iso-kind variant — `gunnar` → `iso:gunnar-appliance-iso` →
/// ▶ KÖR.** Gap #7 of the dwarves-ui audit, driven end to end through the real widgets.
///
/// REPRODUCED 2026-08-20 before the fix: BYGG composed a real 32 MB self-contained
/// El Torito ESP ISO (app.znippy pinned, kernel blake3'd, ESP mastered) and ▶ KÖR
/// answered `dwarves-launch-5 … no real appliance wired for launch cell
/// 'gunnar-appliance-iso'`, offering the korp-installer efistub STAND-IN. So the drive
/// asserts, in order: BYGG ok + a named artifact on disk, the console naming THAT file
/// as the medium (and never STAND-IN, never a `dwarves-launch-*` refusal), and the
/// serial reaching korp-start's exec-argv handoff — `INSTALLER: exec argv: ["<entry>"`,
/// the last line before `fexecve` — which proves firmware → korp-boot → vmlinuz →
/// korp-start → payload verified, off THE COMPOSED MEDIUM, run-from-RAM (no target
/// disk: this is the live leg, nothing is installed).
///
/// The same three env knobs as the installer drive re-point it
/// (`DWARVES_ROBOT_ISO_REPO` / `_ROW` / `_COLUMN`, plus `DWARVES_ROBOT_PRODUCT` for the
/// `korp.entry=` — gunnar declares `korp.entry=gunnar`).
///
/// No cross-product frame guard here on purpose: the live splash and the installed
/// splash are the same product logo, so recording this run under the product's key
/// would fight the installer drive's record instead of guarding anything.
#[test]
#[ignore = "builds and BOOTS: needs QEMU + OVMF + /dev/kvm, takes minutes"]
fn a_robot_clicks_gunnar_appliance_iso_and_watches_it_boot_in_the_pane() {
    let repo = named("DWARVES_ROBOT_ISO_REPO", REPO);
    let row = named("DWARVES_ROBOT_ISO_ROW", ROW);
    let column = named("DWARVES_ROBOT_ISO_COLUMN", "iso:gunnar-appliance-iso");
    let product = if std::env::var_os("DWARVES_ROBOT_PRODUCT").is_some() {
        named("DWARVES_ROBOT_PRODUCT", "gunnar")
    } else {
        repo.clone()
    };
    let (repo, row, column, product) = (
        repo.as_str(),
        row.as_str(),
        column.as_str(),
        product.as_str(),
    );
    eprintln!("[robot] driving       : {repo} → {row} → {column} (product {product})");
    let mut robot = session();

    // 1–3 — the ONE gesture, to a live boot: resolve → row → iso column → BYGG (medium
    //        on disk before ▶) → ▶ KÖR → a live subject. Shared with the serving proof.
    let artifact = drive_iso_variant_to_live(&mut robot, repo, row, column);

    // 4 — watch until korp-start hands off to the product (the boot's own PASS gate),
    //     keeping the inkiest frame as the honest screen report.
    let handoff = format!("INSTALLER: exec argv: [\"{product}");
    let deadline = Instant::now() + Duration::from_secs(300);
    let mut best_ink = 0usize;
    let mut best: Option<dwarves_gui::build_scene::Screen> = None;
    let mut handed_off = false;
    let mut settled: Option<Instant> = None;
    while Instant::now() < deadline {
        robot.run(1);
        if let Some(s) = robot.app().0.report().screen.clone() {
            let ink = s.rgba.iter().filter(|b| **b != 0).count();
            if ink > best_ink {
                best_ink = ink;
                best = Some(s);
            }
        }
        if !handed_off && lines(&robot).iter().any(|l| l.contains(&handoff)) {
            eprintln!("[robot] live handoff  : latched — {handoff}\"…]");
            handed_off = true;
        }
        // Exit once the handoff has spoken and a frame grace has passed, or once the
        // pane's run ended (live went false) plus the same grace.
        let live = robot.state_at("/live") == Some(serde_json::json!(true));
        match (handed_off || !live, settled) {
            (true, None) => settled = Some(Instant::now()),
            (true, Some(t)) if t.elapsed() > Duration::from_secs(10) => break,
            (false, _) => settled = None,
            _ => {}
        }
        std::thread::sleep(Duration::from_millis(10));
    }
    for l in lines(&robot) {
        eprintln!("[serial] {l}");
    }
    let serial = lines(&robot).join("\n");

    // Tear the guest down BEFORE any assertion (same reasoning as the installer drive).
    if robot.state_at("/live") == Some(serde_json::json!(true)) {
        match robot.click_label("✕ STOPP") {
            Ok(()) => {
                let stopped = pump(&mut robot, 25, "stopp", |r| {
                    r.state_at("/live") == Some(serde_json::json!(false))
                });
                eprintln!("[robot] stopp         : live=false after cancel: {stopped}");
            }
            Err(e) => eprintln!("[robot] stopp         : the ✕ STOPP button is not reachable: {e}"),
        }
    }
    std::thread::sleep(Duration::from_secs(2));

    // ── THE MEDIUM: the console names THE composed file, and no stand-in, no refusal ──
    assert!(
        serial.contains(&format!("medium: {artifact}")),
        "the console never named {artifact} as the medium it booted:\n{serial}",
    );
    assert!(
        !serial.contains("STAND-IN"),
        "▶ fell back to the stand-in — whatever booted, it was not {product}:\n{serial}",
    );
    assert_eq!(
        refusal(&robot),
        None,
        "a composed iso-kind variant was refused — gap #7 is back:\n{serial}",
    );

    // ── THE BOOT: the composed chain ran, off this product's own medium ──
    assert!(
        serial.contains(&format!("korp.entry={product}")),
        "the booted medium's cmdline never named korp.entry={product} — this is not \
         {product}'s medium:\n{serial}",
    );
    assert!(
        handed_off,
        "the medium booted but korp-start never handed off: no `{handoff}\"…]` on the \
         serial. phase {:?}\n{serial}",
        robot.state_at("/phase"),
    );
    for refuse in ["FAILED exec", "Kernel panic"] {
        assert!(
            !serial.contains(refuse),
            "the boot reached the handoff and then died with {refuse:?}:\n{serial}",
        );
    }

    // ── THE SCREEN, reported honestly (captured or not — the gate is the serial) ──
    match &best {
        Some(s) => {
            let out = std::env::temp_dir().join(format!("dwarves-{repo}-appliance-iso-screen.ppm"));
            let _ = std::fs::write(&out, ppm_of(s));
            eprintln!(
                "[robot] gfx frame     : {} ({}×{}, {best_ink} non-black bytes)",
                out.display(),
                s.width,
                s.height,
            );
        }
        None => eprintln!("[robot] gfx frame     : none captured before the run settled"),
    }
}