From bd6ddf9efe1c1ce4d8faa6428bbad5fe25db5a5c Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 24 Jul 2026 00:08:56 +0200 Subject: [PATCH] test(cli): RED -- synthetic member panics are contained like the real path Milestone-fieldtest bug (source-blind run, 2026-07-24): a member fault on the synthetic walkforward/sweep path escapes as an uncaught panic (exit 101, no class marker, internal source path leaked) while the real path contains the identical fault via the #272 boundary as aura: warning: + exit 3. Cause: the family builders in aura-runner/src/family.rs run members bare, without the catch_unwind containment that lives only in aura-campaign/src/exec.rs. Two RED e2es pin the derived contract (decision log on #278): no exit 101, a warning-marked fault line, no panic text or crates/ path on stderr, exit 3 per the C14 partition. refs #278 --- crates/aura-cli/tests/cli_run.rs | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index f90d925..7d5b084 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -4839,6 +4839,77 @@ fn aura_walkforward_with_trades_emits_no_zero_trade_note() { let _ = std::fs::remove_dir_all(&cwd); } +/// E2E (#278 decision: same fault class, same verb, same surface => same +/// treatment): a member fault on the SYNTHETIC walk-forward path is CONTAINED +/// exactly like the real/campaign path contains it (#272) — it surfaces as an +/// `aura: warning: ` class-marked stderr line naming the member fault and the +/// process takes the deliberate failed-cells exit 3 (C14 partition, +/// `exit_on_campaign_result` precedent). It never escapes as an uncaught Rust +/// panic: no exit 101, no `panicked at`, no internal `crates/` source path on +/// the consumer's stderr. +#[test] +fn aura_walkforward_synthetic_member_panic_is_contained() { + let cwd = temp_cwd("blueprint-walkforward-member-panic-contained"); + let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); + // length=0 poisons the member: `Sma::new` asserts "SMA length must be >= 1" + // during member compile — the same fault class the real path records as + // "a member panicked: …" and survives. + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=0", "--name", "wfpanic"]) + .current_dir(&cwd) + .output() + .expect("spawn aura walkforward (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"), + "no raw panic report reaches the consumer: {stderr}" + ); + assert!( + !stderr.contains("crates/"), + "no internal source path leaks to the consumer: {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 (#278 decision, sweep twin): the synthetic `aura sweep` shares the +/// same bare-member seam (`run_blueprint_member` inside the family builders' +/// sweep closures, no #272 fault boundary), so the same poison must be +/// contained the same way — warning + exit 3, no panic escape. +#[test] +fn aura_sweep_synthetic_member_panic_is_contained() { + let cwd = temp_cwd("blueprint-sweep-member-panic-contained"); + let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args(["sweep", &bp, "--axis", "sma_signal.fast.length=0", "--name", "swpanic"]) + .current_dir(&cwd) + .output() + .expect("spawn aura sweep (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 /// equal-length config over the shared GER40 archive emits the same /// zero-trade note. Gated on the archive; skips cleanly on a data refusal.