facett-demo/tests/roster_gate.rs
//! **THE WORKSPACE ROSTER GATE** — the by-construction fix for the blind spot that
//! left **14 workspace crates out of the gallery** (found 2026-08-20).
//!
//! ## The bug this guards
//! The demo's roster claims ("shows EVERY facett component") were gated against the
//! **umbrella `facett` crate's re-export list** — but a component crate that is not
//! re-exported by the umbrella (facett-cicd, facett-holger, facett-deploykey, …) was
//! invisible to every roster check: in the workspace, released, tested, and never on
//! screen. The umbrella was the wrong denominator.
//!
//! ## The gate
//! The denominator is now the **workspace itself**: parse the root `Cargo.toml`
//! members list, and every member crate named `facett-*` must be either
//!
//! 1. **credited by at least one LEGEND row** — via the same
//! [`facett_demo::source_links`] extraction that drives each tab's on-screen
//! `source:` hyperlink (one writer: a row that credits its crate both links it
//! and satisfies this gate), or
//! 2. **named in [`EXCLUDED`]** with a documented reason (engines, support crates,
//! and the deliberate non-tabs — each entry says WHY).
//!
//! A new workspace crate that nobody wires then fails this gate **by name**,
//! instead of shipping unseen. The companion tests prove the gate bites (a fake
//! member is rejected) and that the exclusion list cannot go stale (an EXCLUDED
//! crate that is credited, or that leaves the workspace, is RED too).
use std::collections::BTreeSet;
/// The documented exclusions: `facett-*` workspace members that are deliberately
/// NOT a gallery tab, each with the reason. Everything not in this list must be
/// credited by a LEGEND row.
///
/// Engines and support crates are NOT here when a LEGEND row already credits them
/// (facett-core, facett-arrow, facett-cfd, facett-osm, … are credited by the rows
/// of the tabs built on them — the credit is the honest "where it's used" pointer).
const EXCLUDED: &[(&str, &str)] = &[
(
"facett-confirm",
"an ELM gate (Msg/Effect/update), NOT an `impl Facet` — it cannot be a deck \
tab; the confirm/cancel UX it models is exercised in the `overlay` tab \
through facett-app's Modal (see the crate-doc 'Not shown here' list)",
),
(
"facett-build-thing",
"facett's own BUILD RECIPE (the pip/wheel variant rows the xtask door and the \
forge read) — build declaration, not a UI component",
),
(
"facett-dbexplorer",
"a LIVE-SOURCE explorer: its navigator expands by querying its connections \
(Arrow batches on demand); the demo carries no database to point it at, and a \
canned one would show a picture of a browser, not the browser (2026-09-29, \
already unlisted on main 2450648)",
),
(
"facett-flowsim",
"a flow-simulation OVERLAY that rides another component's DAG edges \
(animated particles) — no standalone data surface to open a tab on; it \
decorates a host graph the way pop_beauty decorates the population views",
),
(
"facett-map3d-flight",
"the AAA planetary flight-sim ENGINE layer (floating origin, 6DOF rig, DEM \
terrain) — driven by the standalone flight_demo / osm_fly_demo bins and \
the native-only osm_fly flow, not a deck Facet",
),
(
"facett-ops",
"the non-build release VERBS (`cargo ops deploy-pages` / publish-pip / …, lane X \
2026-10-01) — tooling, not a UI component",
),
(
"facett-simview",
"the population Simulate viewer over the bench's live /sim/api — it ships as \
facet-wrapped's `sim` bundle group, and the demo has no sim server to point it \
at (the population itself is in the `Sverige` and `Graphs` tabs)",
),
(
"facett-wasm-demo",
"the wasm test-harness SUPPORT crate (a dev-dependency of the leaf crates' \
wasm smoke tests), not a UI component",
),
];
/// Every workspace member, parsed from the root `Cargo.toml`'s `members = [...]`
/// list. A parse that finds nothing is a loud failure, never an empty (vacuously
/// green) roster — LAW 2.
fn workspace_members() -> Vec<String> {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join("Cargo.toml");
let text = std::fs::read_to_string(&root)
.unwrap_or_else(|e| panic!("read the workspace manifest {}: {e}", root.display()));
let start = text.find("members = [").expect("the workspace manifest declares `members = [`");
let rest = &text[start + "members = [".len()..];
let end = rest.find(']').expect("the members list closes");
let members: Vec<String> = rest[..end]
.split('"')
.skip(1)
.step_by(2)
.map(str::to_string)
.collect();
assert!(
members.len() > 50,
"parsed only {} workspace members — the parse is broken, not the workspace",
members.len()
);
members
}
/// Every crate dir credited by at least one LEGEND row — derived through the SAME
/// [`facett_demo::source_links`] extraction that renders each tab's `source:`
/// hyperlink, so there is no second key→crate table to drift.
fn credited_dirs() -> BTreeSet<String> {
let mut out = BTreeSet::new();
for (key, _) in facett_demo::LEGEND {
for (dir, _url) in facett_demo::source_links(key) {
out.insert(dir);
}
}
out
}
/// The gate's pure core: which `facett-*` members are neither credited nor
/// excluded. Split out so [`the_gate_bites_on_an_unwired_member`] can prove it red
/// against an injected fake without touching the real workspace.
fn unwired(members: &[String], credited: &BTreeSet<String>) -> Vec<String> {
let excluded: BTreeSet<&str> = EXCLUDED.iter().map(|(c, _)| *c).collect();
members
.iter()
.filter(|m| m.starts_with("facett-"))
.filter(|m| !credited.contains(m.as_str()) && !excluded.contains(m.as_str()))
.cloned()
.collect()
}
/// **THE GATE**: every `facett-*` workspace member is credited by a LEGEND row or
/// documented in [`EXCLUDED`]. A new crate nobody wires fails here BY NAME.
#[test]
fn every_facett_workspace_member_is_wired_or_excluded() {
let members = workspace_members();
let credited = credited_dirs();
let missing = unwired(&members, &credited);
assert!(
missing.is_empty(),
"workspace crates in NO LEGEND row and NOT in the documented EXCLUDED list — \
the gallery claims to show every component, so wire each as a tab (credit \
its crate in the LEGEND description, `… — facett-xxx::Type`) or add it to \
EXCLUDED in tests/roster_gate.rs with a reason: {missing:?}"
);
}
/// **RED-PROOF** (LAW 2): the gate actually bites — an injected member that no
/// LEGEND row credits and no exclusion names is reported, by name.
#[test]
fn the_gate_bites_on_an_unwired_member() {
let mut members = workspace_members();
members.push("facett-unwired-widget".to_string());
let missing = unwired(&members, &credited_dirs());
assert_eq!(
missing,
vec!["facett-unwired-widget".to_string()],
"the gate must name exactly the injected unwired crate"
);
}
/// The exclusion list cannot go STALE: every `EXCLUDED` entry must (a) still be a
/// workspace member — an entry for a deleted crate is dead weight that would mask a
/// future name reuse — and (b) NOT be credited by any LEGEND row — a crate that got
/// wired keeps its exclusion only by mistake, and the stale reason would then
/// contradict the on-screen source link.
#[test]
fn the_exclusion_list_is_live_and_not_redundant() {
let members: BTreeSet<String> = workspace_members().into_iter().collect();
let credited = credited_dirs();
for (crate_name, reason) in EXCLUDED {
assert!(
members.contains(*crate_name),
"EXCLUDED names `{crate_name}` but it is no longer a workspace member — remove the entry (reason was: {reason})"
);
assert!(
!credited.contains(*crate_name),
"EXCLUDED names `{crate_name}` but a LEGEND row now credits it — it is wired, so remove the stale exclusion"
);
}
}