dwarves-gui/tests/robot_two_doors.rs
//! **ROBOT-UI: one road, two doors** — the line the ⚒ node menu shows is the line the
//! CLI door judges, and both reach the same decision (laws B and C across the doors),
//! on the constellation's REAL declaration: the installer that consumes a donor kernel
//! with `kernel.*` opts.
//!
//! The claim, driven end to end without compiling anything:
//!
//! 1. ⚒ resolves the real recipe, picks the installer, selects its `kärna` node and
//! CLICKS one kernel symbol off — through the tree's judge (the same `try_toggle`
//! the CLI's `--opt kernel.<symbol>=off` goes through);
//! 2. the menu now shows the flag the click equals, `--opt kernel.<symbol>=off`
//! (law C), and the flags ⚒ would hand BYGG are exactly the flags on screen;
//! 3. those flags, fed VERBATIM to the CLI door's judge
//! (`dwarves::buildthing::resolve_kernel_axis` — what `dwarves buildthing <repo>
//! --variant <installer> --opt …` resolves before it spawns anything), reach the
//! SAME decision fragment the model reaches for the pane's state
//! (`SettingsTree::kernel_decision`) — the same bytes, either door (law E's
//! fragment-sha follows).
//!
//! SKIPS, saying so, when the real declaration is not on this disk: a fixture that
//! invented a donor would prove the fixture. A real BUILD is not run here — that is a
//! kernel compile — but the decision that build would run from is what is compared.
#![cfg(not(target_arch = "wasm32"))]
use std::collections::BTreeMap;
use facett_core::Facet;
use nornir_build_thing::tree::{SettingsTree, 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)
}
}
#[test]
fn the_flags_on_the_node_menu_reach_the_same_decision_through_the_cli_door() {
// ── the REAL declaration: an installer with a kernel donor edge ─────────────
// The constellation root: `DWARVES_CONSTELLATION` when set, else the directory this checkout
// sits in (the test's cwd is the crate, whose parent is the repo, not the root).
let root = std::env::var_os("DWARVES_CONSTELLATION")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
let root = root.canonicalize().unwrap_or(root);
let builds = ConstellationBuilds::discover(&root);
let Some((repo, installer, donor)) = builds.repos.iter().find_map(|r| {
r.set.installers.iter().find_map(|i| {
i.kernel_donor.as_ref().map(|d| {
(
r.repo.clone(),
i.name.clone(),
(d.repo.clone(), d.kernel.clone()),
)
})
})
}) else {
eprintln!(
"SKIP: no checkout under {} declares an installer with a kernel_donor",
root.display()
);
return;
};
if builds.repo(&donor.0).is_none() {
eprintln!(
"SKIP: the donor `{}` is not checked out beside `{repo}`",
donor.0
);
return;
}
eprintln!(
"real declaration: {repo} · {installer} → {}/{}",
donor.0, donor.1
);
let repo_root = builds
.repo(&repo)
.map(|r| r.root.clone())
.expect("the repo is on disk");
let set = builds
.repo(&repo)
.map(|r| r.set.clone())
.expect("its declaration");
// ── DOOR 1: ⚒ ─────────────────────────────────────────────────────────────
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);
robot
.type_into("artefakt", &repo)
.expect("the artefakt field");
robot.click_label("sök").expect("sök");
robot.run(2);
// The installer is a delivery column of the row it packs: select its node by id
// (the headless twin of the canvas click), which picks row + column.
robot.harness_mut().state_mut().0.select_node(&installer);
robot.run(2);
assert_eq!(
robot.state_at("/menu/variant"),
Some(json!(installer)),
"the installer is the menu's subject"
);
// Open the `kärna` node: its menu lists the symbols.
let kid = format!("{installer}/{KERNEL_PATH}");
robot.harness_mut().state_mut().0.select_node(&kid);
robot.run(2);
assert_eq!(robot.state_at("/menu/node"), Some(json!(KERNEL_PATH)));
let k = robot
.state_at("/tree/roots")
.and_then(|r| r.as_array().cloned())
.and_then(|roots| roots.into_iter().find(|n| n["path"] == json!(KERNEL_PATH)))
.expect("the kärna node on the installer");
assert_eq!(
k["edge"],
json!(format!("{}/{} : kernel", donor.0, donor.1)),
"the donor edge, typed"
);
// A symbol that is ON and not locked — one the judge will let go.
let symbol = k["children"]
.as_array()
.unwrap()
.iter()
.find(|c| {
c["kind"] == json!("feature")
&& c["value"] == json!("true")
&& c["locked_by"].as_array().is_some_and(|l| l.is_empty())
})
.cloned()
.expect("an unlocked symbol in the pick");
let path = symbol["path"].as_str().unwrap().to_string();
let label = symbol["label"].as_str().unwrap().to_string();
robot
.click_label(&label)
.unwrap_or_else(|e| panic!("the symbol `{label}` is a checkbox in the kärna menu: {e}"));
robot.run(3);
// LAW C: the flag beside the node is the one the click equals.
let want_flag = format!("--opt {path}=off");
let k = robot
.state_at("/tree/roots")
.and_then(|r| r.as_array().cloned())
.and_then(|roots| roots.into_iter().find(|n| n["path"] == json!(KERNEL_PATH)))
.unwrap();
let after = k["children"]
.as_array()
.unwrap()
.iter()
.find(|c| c["path"] == json!(path))
.cloned()
.unwrap();
assert_eq!(
after["flag"],
json!(want_flag),
"the flag follows the click: {after} — menu note: {:?}",
robot.state_at("/menu/note")
);
assert_eq!(after["value"], json!("false"));
// …and what BYGG would hand the build: the same string, off the settings map.
assert_eq!(
robot.state_at(&format!("/settings/{path}")),
Some(json!("false")),
"the click landed in the settings BYGG carries"
);
// ── DOOR 2: the CLI's judge, fed the pane's flag verbatim ──────────────────
let opt = want_flag.trim_start_matches("--opt ").to_string();
let (decision, rest) = dwarves::buildthing::resolve_kernel_axis(
&set,
&repo_root,
Some(&installer),
&[opt.clone()],
)
.unwrap_or_else(|e| panic!("the CLI door refused the pane's own flag `{opt}`: {e:#}"));
assert!(
rest.is_empty(),
"nothing but the kernel opt was passed: {rest:?}"
);
let decision = decision.expect("the installer consumes a kernel, so the opt decides one");
let option = path.strip_prefix("kernel.").unwrap();
assert!(
decision
.clicked_off
.iter()
.any(|s| nornir_build_thing::kernel_features::option_name(s) == option),
"the CLI's decision vetoes the symbol the pane clicked: {:?}",
decision.clicked_off
);
// ── ONE MODEL: the tree's own decision for the pane's state is the CLI's ───
// Derived the way both doors derive it: the donor's kernel from the donor's OWN
// checkout (the same lookup `DeclaredForge::derived_tree` and the CLI's donor
// resolution make), not a default preset.
let inst = set
.installers
.iter()
.find(|i| i.name == installer)
.expect("the installer");
let donor_kernel = builds
.repo(&donor.0)
.and_then(|r| r.set.kernels.iter().find(|k| k.name == donor.1).cloned());
let tree = SettingsTree::of_installer(inst, donor_kernel.as_ref());
let mut state: BTreeMap<String, bool> = BTreeMap::new();
state.insert(path.clone(), false);
let vals: BTreeMap<String, String> = BTreeMap::new();
let model = tree
.kernel_decision(KERNEL_PATH, &state, &vals)
.expect("the model's decision for the pane's state");
assert_eq!(
model.fragment, decision.fragment,
"the same decision fragment through both doors — law B/C across the doors"
);
assert_eq!(model.preset, decision.preset);
eprintln!(
"both doors: {} · preset {} · off {:?}",
want_flag,
decision.preset.as_str(),
decision.clicked_off
);
}
/// **The CHAIN the pane plans is the chain the prompt plans** — wave 3, laws B/C
/// across the doors, on the constellation's real declaration.
///
/// | claim | RED when |
/// |---|---|
/// | ⚒'s dry pass names the same steps, in the same order, as `plan_build_steps` | the pane plans its own DAG (two orderings, the wave-3 finding) |
/// | every row carries the PLANNER's own sentence, the one `--list --build` prints | the console writes its own prose about a plan it did not make |
/// | the decision fragment the CLI door resolves is IN the row | the fragment stops at the door and the pane shows a plan for another choice |
/// | the dry pass starts nothing: every row `köad`, no receipt, no clock | a dry pass that builds, or a console that looks finished |
///
/// Driven through `plan_edge`, the DRY twin of `BYGG <kant>`: the same planner, the
/// pin asked what it WOULD do, nothing spawned. A robot must be able to compare the
/// doors without starting a kernel compile, which is why that twin exists.
///
/// SKIPS by name when the real declaration is not on this disk.
#[test]
fn the_chain_the_pane_plans_is_the_chain_the_prompt_plans() {
let root = std::env::var_os("DWARVES_CONSTELLATION")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
let root = root.canonicalize().unwrap_or(root);
let builds = ConstellationBuilds::discover(&root);
let Some((repo, installer, donor)) = builds.repos.iter().find_map(|r| {
r.set.installers.iter().find_map(|i| {
i.kernel_donor.as_ref().map(|d| {
(
r.repo.clone(),
i.name.clone(),
(d.repo.clone(), d.kernel.clone()),
)
})
})
}) else {
eprintln!(
"SKIP: no checkout under {} declares an installer with a kernel_donor",
root.display()
);
return;
};
if builds.repo(&donor.0).is_none() {
eprintln!(
"SKIP: the donor `{}` is not checked out beside `{repo}`",
donor.0
);
return;
}
let repo_root = builds
.repo(&repo)
.map(|r| r.root.clone())
.expect("the repo is on disk");
let set = builds
.repo(&repo)
.map(|r| r.set.clone())
.expect("its declaration");
// ── DOOR 1: ⚒'s dry pass ──────────────────────────────────────────────────
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);
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(&installer);
robot.run(2);
assert_eq!(robot.state_at("/menu/variant"), Some(json!(installer)));
robot
.harness_mut()
.state_mut()
.0
.update_json(&format!(r#"{{"plan_edge":"{KERNEL_PATH}"}}"#));
robot.run(3);
let steps = robot
.state_at("/steps")
.filter(|v| !v.is_null())
.unwrap_or_else(|| {
panic!(
"the dry pass planned nothing — menu note: {:?}",
robot.state_at("/menu/note")
)
});
let rows = steps["steps"].as_array().expect("rows").clone();
// Nothing was started: the dry pass reads.
for r in &rows {
assert_eq!(
r["state"],
json!("queued"),
"the dry pass starts nothing: {r}"
);
}
assert_eq!(
steps["receipt"],
Value::Null,
"no verb fired ⇒ no BYGG receipt"
);
assert_eq!(steps["elapsed_ms"], json!(0), "no clock on a plan");
// The consuming line, off the screen: the flags the consumer row carries ARE
// what ⚒ would hand the build (law C), so they are what door 2 is fed.
let line: Vec<String> = rows
.last()
.and_then(|r| r["flags"].as_array().cloned())
.unwrap_or_default()
.iter()
.filter_map(|f| {
f.as_str()
.and_then(|f| f.strip_prefix("--opt "))
.map(str::to_string)
})
.collect();
// ── DOOR 2: the prompt's planner, fed the same line ───────────────────────
let builds_flag = [KERNEL_PATH.to_string()];
let opts = dwarves::buildthing::expand_opts_for(&set, Some(&installer), &line, &[])
.unwrap_or_else(|e| panic!("the CLI door refused the pane's own line {line:?}: {e:#}"));
let (decision, _) =
dwarves::buildthing::resolve_kernel_axis(&set, &repo_root, Some(&installer), &opts)
.unwrap_or_else(|e| panic!("the CLI door refused the kernel half of {opts:?}: {e:#}"));
let planned = dwarves::build_steps::plan_build_steps(
&set,
&repo_root,
Some(&installer),
&builds_flag,
&opts,
)
.unwrap_or_else(|e| panic!("the prompt's planner refused: {e:#}"));
let lines = dwarves::build_steps::describe_plan(&planned, decision.as_ref())
.expect("the dry pass describes itself");
// ONE ORDER: the same coordinates, in the same order, the consumer last.
let pane: Vec<String> = rows
.iter()
.map(|r| r["coordinate"].as_str().unwrap_or_default().to_string())
.collect();
let mut prompt: Vec<String> = planned.iter().map(|s| s.coordinate().to_string()).collect();
prompt.push(format!("{repo}/{installer}"));
assert_eq!(
pane, prompt,
"one DAG, two doors — the pane must not plan its own"
);
assert!(
!planned.is_empty(),
"the real declaration has a kernel edge, so there is a donor step"
);
// ONE SENTENCE: the planner's own lines for a step are the row's, verbatim.
// Not "the row says something about the step" — the row IS the prompt's dry pass,
// because `describe_plan` wrote both.
for (i, s) in planned.iter().enumerate() {
let got = rows[i]["plan"].as_str().unwrap_or_default();
let head = format!("step {}/{} ", i + 1, planned.len());
assert!(
got.starts_with(&head),
"row {i} opens with the planner's own position: {got:?}"
);
let mine: Vec<&String> = {
let from = lines
.iter()
.position(|l| l.starts_with(&head))
.expect("the planner wrote this step");
std::iter::once(&lines[from])
.chain(
lines[from + 1..]
.iter()
.take_while(|l| !l.starts_with("step ")),
)
.collect()
};
assert!(mine.len() >= 1);
for l in &mine {
assert!(
got.contains(l.trim()),
"the row must carry the prompt's line verbatim.\n row: {got}\n prompt: {}",
l.trim()
);
}
assert!(got.contains(s.coordinate()));
}
// THE CHOICE TRAVELS: the preset the CLI door resolved is the preset the row's
// routed `--opt` line carries, and every symbol the door vetoed is spelled off
// on it. The fragment SHA itself only appears on a plan whose donor has a BUILT
// pin to compare against (`StepNeed::ByPin`); with nothing built, what the two
// doors can be held to is the choice, and they are.
if let Some(d) = &decision {
let all: String = rows
.iter()
.filter_map(|r| r["plan"].as_str())
.collect::<Vec<_>>()
.join(" ");
assert!(
all.contains(&format!("--opt kernel.preset={}", d.preset.as_str())),
"the resolved preset `{}` must be on the routed line the pane shows: {all}",
d.preset.as_str()
);
for sym in &d.clicked_off {
let opt = nornir_build_thing::kernel_features::option_name(sym);
assert!(
all.contains(&format!("--opt kernel.{opt}=off")),
"the veto on {sym} must ride the donor's own line: {all}"
);
}
}
eprintln!("both doors planned: {prompt:?}");
}