facett-confirm/tests/matrix.rs
//! **CONFIRM-GATE STATE MATRIX** — a parameter sweep over the whole
//! guard/phrase space that FEEDS real input and ASSERTS real output per case
//! (the inject-and-assert LAW, not no-crash).
//!
//! The gate's one job is a **safety invariant**: it arms (and only then yields
//! [`Effect::Confirmed`] on a confirm) *iff* the host's guard says the target is
//! allowed AND the operator typed the required phrase. Each case sweeps one
//! `(allow, typed-phrase, override-phrase)` tuple, computes the EXPECTED arm
//! verdict **independently** in the test, drives the real `ConfirmGate`, and
//! asserts the component's own `is_armed()` + the effect it produced match — so
//! a broken arming rule (e.g. dropping the guard or the phrase check) turns a
//! row RED, not a stub always-green.
//!
//! Each case emits ONE `functional_status` row (`facett-confirm`) so the whole
//! guard space is provably covered in the nornir warehouse. Built + run with
//! `--features testmatrix`.
//!
//! Run: `cargo test -p facett-confirm --features testmatrix --test matrix`
use facett_confirm::{Allow, ConfirmGate, Effect, Msg};
/// Gated one-line emit shim (real row emitter with `--features testmatrix`,
/// compiled-out no-op otherwise — same shape as the crate's own wrapper).
#[cfg(feature = "testmatrix")]
fn emit(component: &str, check: &str, ok: bool, detail: &str) {
nornir_testmatrix::functional_status(component, check, ok, detail);
}
#[cfg(not(feature = "testmatrix"))]
fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}
/// One swept case's parameters.
#[derive(Clone, Copy)]
struct Case {
allow: Allow,
/// What the operator types.
typed: &'static str,
/// An optional custom phrase (else phrase == target).
override_phrase: Option<&'static str>,
}
#[test]
fn confirm_gate_guard_matrix() {
let target = "njord-lab";
let allows = [Allow::Yes, Allow::No];
// Typed inputs: exact target, wrong text, empty, and the override phrase.
let typeds = ["njord-lab", "njord", "", "DROP"];
// Phrase modes: default (== target) and an explicit override.
let overrides = [None, Some("DROP")];
let mut cases: Vec<Case> = Vec::new();
for &allow in &allows {
for &typed in &typeds {
for &override_phrase in &overrides {
cases.push(Case { allow, typed, override_phrase });
}
}
}
let mut failures: Vec<String> = Vec::new();
let mut armed_cases = 0usize;
for c in &cases {
// Build the real gate for this case.
let mut g = ConfirmGate::new("Restore", target, c.allow);
if let Some(p) = c.override_phrase {
g = g.require_phrase(p);
}
g.update(Msg::Typed(c.typed.to_string()));
// EXPECTED arm verdict, computed independently of the component: allowed
// AND typed matches the *effective* phrase (override, else target).
let effective_phrase = c.override_phrase.unwrap_or(target);
let expected_arm = c.allow == Allow::Yes && c.typed == effective_phrase;
// The component's own verdict + the effect a confirm actually produces.
let got_arm = g.is_armed();
let effects = g.update(Msg::Confirm);
let confirmed = effects == vec![Effect::Confirmed];
// Cancel must ALWAYS yield exactly one Cancelled effect, regardless of arm.
let cancelled = g.update(Msg::Cancel) == vec![Effect::Cancelled];
let ok = got_arm == expected_arm && confirmed == expected_arm && cancelled;
if got_arm {
armed_cases += 1;
}
if !ok {
failures.push(format!(
"allow={:?} typed={:?} phrase={:?}: expected_arm={expected_arm} got_arm={got_arm} confirmed={confirmed} cancelled={cancelled}",
c.allow, c.typed, effective_phrase
));
}
// EMIT one row per case (warehouse coverage of the guard space).
emit(
"facett-confirm",
"guard_matrix",
ok,
&format!(
"allow={:?} typed={:?} phrase={:?} expected_arm={expected_arm} got_arm={got_arm} confirmed={confirmed} cancelled={cancelled}",
c.allow, c.typed, effective_phrase
),
);
}
eprintln!(
"[confirm matrix] {} cases, {armed_cases} armed, {} failures",
cases.len(),
failures.len()
);
// ── matrix-level oracles ──
assert!(
failures.is_empty(),
"{} of {} guard cases violated the arm invariant:\n{}",
failures.len(),
cases.len(),
failures.join("\n")
);
// The sweep must exercise BOTH arms of the invariant — at least one armed and
// one refused case — or it proves nothing.
assert!(armed_cases > 0, "no case ever armed — the sweep is degenerate");
assert!(armed_cases < cases.len(), "every case armed — the guard is not gating");
emit(
"facett-confirm",
"guard_matrix_summary",
failures.is_empty(),
&format!("cases={} armed={armed_cases} failures={}", cases.len(), failures.len()),
);
}