test: red executable-spec for #247 — sweep-boundary bind errors render as prose

Both blueprint-sweep bind faults (wrong-kind axis value; an open param
no axis covers) must print prose in the unknown-axis sibling's register
— axis name, kind/knob specifics, a --list-axes pointer — never the raw
Debug structs KindMismatch{..}/MissingKnob(..). Exit stays 2. Fails on
the Debug leakage present today, not on scaffolding.

refs #247
This commit is contained in:
2026-07-13 22:35:03 +02:00
parent be9f1cef8c
commit ce12e7f976
+78
View File
@@ -2181,6 +2181,84 @@ fn aura_sweep_real_blueprint_refuses_a_raw_form_axis_name_before_data_access() {
let _ = std::fs::remove_dir_all(&dir);
}
/// Property (#247): every sweep-verb boundary `BindError` renders as prose in
/// the same register as its unknown-axis sibling
/// (`aura: axis <name>: names no param of this blueprint … — see `aura sweep
/// <bp> --list-axes``) — never a raw Rust Debug struct. A wrong-kind axis value
/// and an uncovered open knob are the two variants that today leak
/// `KindMismatch { knob: …, expected: …, got: … }` and
/// `MissingKnob("…")` verbatim to stderr; both must instead name the axis, carry
/// the specifics (expected vs supplied kind / the unbound knob), and point at
/// `--list-axes`, with the exit code staying 2. Data-free: both faults are
/// caught at the axis-resolution boundary before any member runs, so the test
/// needs no archive and no project (hostless synthetic sweep).
#[test]
fn sweep_boundary_bind_errors_render_as_prose_not_debug_structs() {
let dir = temp_cwd("sweep-bind-error-prose");
// (a) KindMismatch — an axis re-opens the bound I64 `fast.length` (#246) but
// supplies F64 values: the specifics are the expected vs supplied kind.
let closed = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let km = Command::new(BIN)
.args(["sweep", &closed, "--axis", "sma_signal.fast.length=2.5,3.5"])
.current_dir(&dir)
.output()
.expect("spawn aura sweep (kind-mismatched axis)");
assert_eq!(
km.status.code(),
Some(2),
"a kind-mismatched axis value stays a usage refusal: status={:?}",
km.status
);
let km_err = String::from_utf8_lossy(&km.stderr).into_owned();
assert!(
!km_err.contains("KindMismatch"),
"must not leak the raw Rust Debug struct name: {km_err}"
);
assert!(
km_err.contains("sma_signal.fast.length"),
"the prose names the offending axis exactly as typed: {km_err}"
);
assert!(
km_err.contains("I64") && km_err.contains("F64"),
"the prose carries the expected vs supplied kind specifics: {km_err}"
);
assert!(
km_err.contains("--list-axes"),
"the prose points at the discovery surface, like the unknown-axis sibling: {km_err}"
);
// (b) MissingKnob — the open fixture leaves `slow.length` unbound when only
// `fast.length` is swept: the specific is the uncovered open knob.
let open = format!("{}/tests/fixtures/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let mk = Command::new(BIN)
.args(["sweep", &open, "--axis", "sma_signal.fast.length=2,4"])
.current_dir(&dir)
.output()
.expect("spawn aura sweep (uncovered open knob)");
assert_eq!(
mk.status.code(),
Some(2),
"an uncovered open knob stays a usage refusal: status={:?}",
mk.status
);
let mk_err = String::from_utf8_lossy(&mk.stderr).into_owned();
assert!(
!mk_err.contains("MissingKnob"),
"must not leak the raw Rust Debug struct name: {mk_err}"
);
assert!(
mk_err.contains("sma_signal.slow.length"),
"the prose names the unbound knob no axis supplies: {mk_err}"
);
assert!(
mk_err.contains("--list-axes"),
"the prose points at the discovery surface, like the unknown-axis sibling: {mk_err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
/// Property (#210 c0110 fieldtest, "refused dissolved sweeps still register
/// their generated campaign document" — store litter, probe P3): binding only
/// a SUBSET of the blueprint's open axes (here: `fast.length`, leaving