test(cli): RED — mc --real refuses a short window instead of fitting the roller (#239)

The dissolved mc/walkforward dispatches stamp the fixed 90/30/30-day
roller into the generated process document regardless of the resolved
campaign window; a window under 120 days dead-ends at the executor with
no remedy reachable from the verb surface.

refs #239
This commit is contained in:
2026-07-11 16:24:33 +02:00
parent 2be6cf8860
commit a081c14532
+105
View File
@@ -4702,6 +4702,111 @@ fn mc_dissolves_through_the_campaign_path() {
);
}
/// Property (#239): a real-data `aura mc --real` invocation over a window SHORTER
/// than the fixed 90-day-in-sample / 30-day-out-of-sample roller runs the R-bootstrap
/// to completion by fitting the injected walk-forward roller down to the campaign
/// window, instead of refusing late at the executor with "walk_forward windows do not
/// fit the campaign window" (exit 1, no remedy reachable from the mc surface). The
/// short window is DERIVED from the live archive — probe GER40's full span via a
/// no-window single-cell sweep, then take the last ~30 days — the same live-archive
/// derivation `generalize_without_explicit_window_resolves_the_intersection_of_all_symbols`
/// uses, so the window stays short as the archive grows and always holds recent bars.
/// The leading sweep stage runs first over the short window; the refusal today fires at
/// stage 1 (the walk_forward roller), so this bites only when the window genuinely has
/// data. Gated on the GER40 archive; skips cleanly on a data-less host.
#[test]
fn mc_real_fits_the_injected_roller_to_a_short_window() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (cwd, _g) = fresh_project();
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
// Probe GER40's full archive window: a single-cell, no-window sweep records
// `campaign_window_ms(full_window)` into its generated campaign document, in the
// same Unix-ms currency `--from`/`--to` speak (the generalize no-window test's
// derivation).
let full_window = {
let name = "probe-ger40-span";
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"sweep", &fixture, "--real", "GER40",
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
"--name", name,
])
.current_dir(cwd)
.output()
.expect("spawn aura sweep probe");
assert_eq!(
out.status.code(), Some(0),
"the full-archive window-probe sweep must run to exit 0: {}",
String::from_utf8_lossy(&out.stderr)
);
let dir = cwd.join("runs").join("campaigns");
let mut resolved = None;
for entry in std::fs::read_dir(&dir).expect("campaigns dir exists after a run") {
let path = entry.expect("readable dir entry").path();
let doc: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&path).expect("read campaign doc"))
.expect("campaign doc parses as JSON");
if doc["name"].as_str() == Some(name) {
let w = &doc["data"]["windows"][0];
resolved = Some((
w["from_ms"].as_i64().expect("from_ms is an integer"),
w["to_ms"].as_i64().expect("to_ms is an integer"),
));
break;
}
}
resolved.expect("the probe campaign document records GER40's full window")
};
// The last ~30 days of the archive: far shorter than the fixed 90+30-day roller,
// so the injected walk_forward cannot fit unless it scales down to the window.
const THIRTY_DAYS_MS: i64 = 30 * 86_400_000;
let sub_from = full_window.1 - THIRTY_DAYS_MS;
let sub_to = full_window.1;
assert!(
sub_from > full_window.0,
"the derived ~30-day sub-window must sit inside the archive span {full_window:?}"
);
let sub_from_s = sub_from.to_string();
let sub_to_s = sub_to.to_string();
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40",
"--axis", "sma_signal.fast.length=3,5", "--axis", "sma_signal.slow.length=12,20",
"--stop-length", "14", "--stop-k", "2.0",
"--block-len", "5", "--resamples", "100", "--seed", "42",
"--from", &sub_from_s, "--to", &sub_to_s,
])
.current_dir(cwd)
.output()
.expect("spawn aura mc");
let stderr = String::from_utf8_lossy(&out.stderr);
// Defensive: a genuine data refusal (never the late window-fit refusal this test
// pins) still means "no usable data on this host" — skip, don't fail.
if out.status.code() == Some(1)
&& (stderr.contains("no local data") || stderr.contains("no recorded geometry"))
{
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(
out.status.code(), Some(0),
"a short real window must fit the injected roller and run to completion, \
not refuse late; stderr: {stderr}"
);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
assert!(
stdout.lines().any(|l| l.starts_with("{\"mc_r_bootstrap\":")),
"the mc_r_bootstrap grade line must be emitted for the fitted short window: {stdout}"
);
}
/// Fork A: the stop is a single risk regime on the dissolved mc path — a multi-value
/// `--stop-length`/`--stop-k` is a usage refusal (exit 2), not a swept axis. Mirrors
/// `walkforward_dissolved_refuses_a_multi_value_stop`. NOT gated — the refusal is pure