test(cli): weld-gone proof + new refusals for the generic verbs (#220 slice 3)

Task 5 of the verb-axis-generalization plan — the cycle's point, pinned:

- walkforward_campaign_runs_an_arbitrary_blueprint and
  mc_campaign_runs_an_arbitrary_blueprint drive r_breakout_open.json
  (wrapped channel_hi/channel_lo axes) over the real archive;
  generalize_campaign_runs_an_arbitrary_blueprint grades
  r_meanrev_open.json across two instruments. All three invocations
  were structurally impossible before this cycle (exit 2).
- New refusals: campaign mode without --axis (wf/mc), mc --real +
  --seeds mix, raw-form axis name before data access, unknown axis as
  a named Knob fault — each mirroring sweep's shipped contract.
- Final acceptance gates ran green in the loop: full workspace suite,
  clippy -D warnings, cargo doc warning-free, zero too_many_arguments
  allows in verb_sugar.rs, no RGrid in the workspace, no r_sma_open
  embed at the three verb dispatch sites (the aura graph default embed
  survives by design).

closes #220
closes #214
This commit is contained in:
2026-07-09 15:17:51 +02:00
parent e7efe1f765
commit b849bb76b9
+251
View File
@@ -3572,6 +3572,257 @@ fn mc_dissolved_seed_changes_the_bootstrap_draw_not_the_pooled_series() {
);
}
/// #220 weld-gone proof: the walkforward campaign path runs an arbitrary user
/// blueprint with axis names the CLI has never heard of — the r-sma weld
/// (hardcoded fast/slow) is structurally gone. Gated on the GER40 archive;
/// skips cleanly when absent.
#[test]
fn walkforward_campaign_runs_an_arbitrary_blueprint() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
const FROM_MS: &str = "1735689600000";
const TO_MS: &str = "1767225599000";
let cwd = temp_cwd("walkforward-arbitrary-blueprint");
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=20,40",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--stop-length", "3", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
if out.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
"exit 1 must be a data refusal, got: {stderr}"
);
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(
out.status.code(),
Some(0),
"exit: {:?} stderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
let grade_line = stdout
.lines()
.find(|l| l.starts_with("{\"walkforward\":"))
.unwrap_or_else(|| panic!("the walkforward summary line: {stdout}"));
let v: serde_json::Value = serde_json::from_str(grade_line).expect("summary line parses as JSON");
let wf = &v["walkforward"];
assert!(wf["windows"].as_u64().is_some_and(|w| w >= 1), "at least one window: {grade_line}");
// param_stability rows: channel_hi, channel_lo, stop_length, stop_k.
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps.len(), 4, "two channel axes + the stop pair: {grade_line}");
}
/// #220 weld-gone proof for mc: the R-bootstrap pipeline runs the same
/// arbitrary breakout blueprint. Gated on the GER40 archive.
#[test]
fn mc_campaign_runs_an_arbitrary_blueprint() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
const FROM_MS: &str = "1735689600000";
const TO_MS: &str = "1767225599000";
let cwd = temp_cwd("mc-arbitrary-blueprint");
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=20,40",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--stop-length", "3", "--stop-k", "2.0",
"--block-len", "5", "--resamples", "100", "--seed", "7",
"--from", FROM_MS, "--to", TO_MS,
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
if out.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
"exit 1 must be a data refusal, got: {stderr}"
);
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(
out.status.code(),
Some(0),
"exit: {:?} stderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
let grade_line = stdout
.lines()
.find(|l| l.starts_with("{\"mc_r_bootstrap\":"))
.unwrap_or_else(|| panic!("the mc_r_bootstrap grade line: {stdout}"));
let v: serde_json::Value = serde_json::from_str(grade_line).expect("grade line parses as JSON");
assert_eq!(v["mc_r_bootstrap"]["n_resamples"].as_u64(), Some(100), "resamples flow: {grade_line}");
assert!(v["mc_r_bootstrap"]["e_r"]["mean"].is_number(), "bootstrap E[R] present: {grade_line}");
}
/// #220 weld-gone proof for generalize: one mean-reversion candidate graded
/// across two instruments — three axes with names the CLI has never heard of,
/// including an F64 axis. Gated on the GER40/USDJPY Sept-2024 archive.
#[test]
fn generalize_campaign_runs_an_arbitrary_blueprint() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let cwd = temp_cwd("generalize-arbitrary-blueprint");
let fixture = format!("{}/examples/r_meanrev_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"generalize", &fixture, "--real", "GER40,USDJPY",
"--axis", "r_meanrev_signal.mean_window.length=20",
"--axis", "r_meanrev_signal.var_window.length=20",
"--axis", "r_meanrev_signal.band.factor=2.0",
"--stop-length", "3", "--stop-k", "2.0",
"--from", GER40_SEPT2024_FROM_MS, "--to", GER40_SEPT2024_TO_MS,
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
if out.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
"exit 1 must be a data refusal, got: {stderr}"
);
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(
out.status.code(),
Some(0),
"exit: {:?} stderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
assert!(stdout.contains("\"generalize\":"), "aggregate object: {stdout}");
assert!(stdout.contains("\"n_instruments\":2"), "two instruments graded: {stdout}");
assert!(stdout.contains("\"worst_case\":"), "worst-case floor present: {stdout}");
}
/// #220: the walkforward campaign path requires >= 1 --axis (exit 2, data-free).
#[test]
fn walkforward_campaign_refuses_missing_axis() {
let cwd = temp_cwd("walkforward-campaign-no-axis");
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["walkforward", &fixture, "--real", "GER40", "--stop-length", "14", "--stop-k", "2.0"])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "missing --axis is a usage refusal");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("requires >= 1 --axis"), "axis-arity refusal: {stderr}");
}
/// #220: mc's two modes are disjoint — --seeds (synthetic seed family) with
/// --real (campaign pipeline) is a cross-mode usage error (exit 2, data-free).
#[test]
fn mc_campaign_refuses_seeds_with_real() {
let cwd = temp_cwd("mc-campaign-seeds-mix");
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40", "--seeds", "3",
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
"--stop-length", "14", "--stop-k", "2.0",
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "--seeds with --real is a cross-mode usage refusal");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("Usage: aura mc"), "usage refusal: {stderr}");
}
/// #220: a raw-form axis name is refused before any data access on the migrated
/// verbs, exactly as sweep's shipped refusal — echoing what the user typed, with
/// no store litter (mirrors aura_sweep_real_blueprint_refuses_a_raw_form_axis_name_before_data_access).
#[test]
fn walkforward_campaign_refuses_a_raw_form_axis_name() {
let cwd = temp_cwd("walkforward-campaign-raw-axis");
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "fast.length=3,5", "--axis", "sma_signal.slow.length=12,20",
"--stop-length", "14", "--stop-k", "2.0",
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "a raw-form axis name is refused before any data access");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("axis \"fast.length\""), "echoes the raw name verbatim: {stderr}");
assert!(!cwd.join("runs").exists(), "no store litter on the refusal path");
}
/// #220: an unknown axis name is refused at the dispatch boundary on the
/// migrated verbs (mirrors aura_sweep_rejects_an_unknown_axis).
#[test]
fn mc_campaign_refuses_an_unknown_axis() {
let cwd = temp_cwd("mc-campaign-unknown-axis");
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.nope.length=1",
"--stop-length", "14", "--stop-k", "2.0",
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "an unknown axis is refused at the dispatch boundary");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("axis \"r_breakout_signal.nope.length\""),
"names the offending axis: {stderr}"
);
}
/// #220: the mc campaign path also requires >= 1 --axis before any archive
/// access (exit 2, data-free) — the same arity gate walkforward enforces
/// (`walkforward_campaign_refuses_missing_axis`) and generalize enforces
/// (`generalize_refuses_no_axis`), but which Task 5 left untested for mc: the
/// `axes.is_empty()` check lives in `dispatch_mc`'s `--real` arm alone, so a
/// regression dropping it would otherwise surface only once real data hits the
/// R-bootstrap pipeline, not at argv-parse time.
#[test]
fn mc_campaign_refuses_missing_axis() {
let cwd = temp_cwd("mc-campaign-no-axis");
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["mc", &fixture, "--real", "GER40", "--stop-length", "14", "--stop-k", "2.0"])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "missing --axis is a usage refusal");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("Usage: aura mc"), "usage refusal: {stderr}");
assert!(!cwd.join("runs").exists(), "no store litter on the refusal path");
}
/// `aura run` on the shipped closed example reproduces the built-in r-sma grade
/// (#159): the example is the proven data successor to `--harness r-sma`. Survives
/// the Cut-1b builder deletion (depends only on the example + generic run path).