test(cli): RED -- mc synthetic member panic is contained like its siblings

Orchestrator probe after 51096a3: blueprint_mc_family is the one
remaining family builder running members bare — a closed poison
blueprint (SMA length bound 0) through aura mc --seeds 2 exits 101
with a raw panic. RED e2e pins the shared contract: no 101, an
aura: warning: fault line, no panic text or crates/ path, exit 3.
Single-seed escape confirms the bare seam (not the parallelism) is
the cause. refs #278
This commit is contained in:
2026-07-24 00:47:05 +02:00
parent 51096a3212
commit 59f547e313
+62
View File
@@ -4910,6 +4910,68 @@ fn aura_sweep_synthetic_member_panic_is_contained() {
let _ = std::fs::remove_dir_all(&cwd); let _ = std::fs::remove_dir_all(&cwd);
} }
/// E2E (#278 decision, mc twin): the synthetic `aura mc` shares the same
/// bare-member seam as its sweep/walkforward siblings (`run_blueprint_member`
/// inside `blueprint_mc_family`'s per-seed draw closure, no #272 fault
/// boundary), so the same member-compile fault class must be contained the
/// same way — one `aura: warning: ` line naming the fault, no raw panic text
/// or internal `crates/` path on stderr, deliberate exit 3 (C14), never 101.
/// mc takes only CLOSED blueprints, so the poison is BOUND (`length=0` on the
/// SMA add-op) rather than a poison axis: the test builds its minimal 4-op
/// poison blueprint inline via `aura graph build`. Two seeds so the parallel
/// draws join before the lowest-seed fault resolves (the shipped sweep
/// contract's thread-order-independent shape, C1).
#[test]
fn aura_mc_synthetic_member_panic_is_contained() {
let cwd = temp_cwd("blueprint-mc-member-panic-contained");
// Minimal poison: each op is load-bearing (source: the close role; add+bind:
// the length=0 that trips `Sma::new`'s "SMA length must be >= 1" assert at
// member compile; feed: wiring so build validation passes; expose: the bias
// contract). Bound length keeps the blueprint CLOSED, as `aura mc` requires.
let ops = r#"[
{"op":"source","role":"close","kind":"F64"},
{"op":"add","type":"SMA","name":"sma","bind":{"length":{"I64":0}}},
{"op":"feed","role":"close","into":["sma.series"]},
{"op":"expose","from":"sma.value","as":"bias"}
]"#;
let ops_path = cwd.join("poison.ops.json");
std::fs::write(&ops_path, ops).expect("write poison op-list");
let bp_path = cwd.join("poison.bp.json");
let build = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["graph", "build"])
.stdin(std::fs::File::open(&ops_path).expect("open poison op-list"))
.current_dir(&cwd)
.output()
.expect("spawn aura graph build (poison blueprint)");
assert_eq!(
build.status.code(),
Some(0),
"poison blueprint builds (the fault is a member-COMPILE fault, not a build refusal): {}",
String::from_utf8_lossy(&build.stderr)
);
std::fs::write(&bp_path, &build.stdout).expect("write poison blueprint");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["mc", bp_path.to_str().expect("utf-8 path"), "--seeds", "2"])
.current_dir(&cwd)
.output()
.expect("spawn aura mc (poison member)");
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert!(
stderr.lines().any(|l| l.contains("aura: warning: ") && l.contains("SMA length must be >= 1")),
"the member fault surfaces as one class-marked warning naming the fault: {stderr}"
);
assert!(
!stderr.contains("panicked at") && !stderr.contains("crates/"),
"no raw panic report or internal source path leaks: {stderr}"
);
assert_eq!(
out.status.code(),
Some(3),
"a contained member fault is the deliberate failed-cells exit 3, never 101: {stderr}"
);
let _ = std::fs::remove_dir_all(&cwd);
}
/// E2E (#313, Path 1 — the real/sugar walk-forward): the degenerate /// E2E (#313, Path 1 — the real/sugar walk-forward): the degenerate
/// equal-length config over the shared GER40 archive emits the same /// equal-length config over the shared GER40 archive emits the same
/// zero-trade note. Gated on the archive; skips cleanly on a data refusal. /// zero-trade note. Gated on the archive; skips cleanly on a data refusal.