test: RED executable-spec for #217 — stop-less verb invocation defaults the regime

walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2
pins the decided contract (#217 decision comment, 2026-07-09): a
walkforward invocation without --stop-length/--stop-k succeeds, each
omitted knob defaulting to the single-sourced regime (length 3, k 2.0),
and produces a campaign document content-identical to the explicit
--stop-length 3 --stop-k 2.0 spelling (registry dedup: exactly one doc).
Currently RED: the verb refuses with the requires-knobs usage error.

refs #217
This commit is contained in:
2026-07-09 16:10:30 +02:00
parent 4acea45519
commit af8564d20b
+95
View File
@@ -1859,6 +1859,101 @@ fn walkforward_dissolves_through_the_campaign_path() {
);
}
/// Property (#217): on the dissolved `aura walkforward --real` path,
/// `--stop-length`/`--stop-k` are OPTIONAL — a stop-less invocation succeeds,
/// defaulting each omitted knob to the single-sourced default regime (length 3,
/// k 2.0), the very regime the campaign member runner already resolves for an
/// unbound regime (`stop_rule_for_regime`). The default is byte-identical to
/// spelling the knobs out: running the same blueprint in the same store first
/// stop-less, then with an explicit `--stop-length 3 --stop-k 2.0`, leaves
/// EXACTLY ONE generated campaign document — the two documents are byte-identical
/// so content-addressed storage collapses them onto one content id — while each
/// invocation still records its own campaign-run line. This aligns walkforward's
/// knob contract with sweep's (which already defaults the stop) without any
/// content-id churn on the explicit spelling. The sibling verbs mc/generalize gain
/// the same independent per-flag defaulting on the GREEN side. Gated on the shared
/// GER40 archive; skips cleanly on a data refusal. (The multi-value stop is still
/// refused — `walkforward_dissolved_refuses_a_multi_value_stop` — the stop stays a
/// single risk regime; only its *presence* becomes optional.)
#[test]
fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
const FROM_MS: &str = "1735689600000";
const TO_MS: &str = "1767225599000";
let cwd = temp_cwd("walkforward-stopless-defaults");
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
// Stop-less: the two knobs are omitted entirely. This is the headline — today it
// is refused at the argv boundary (exit 2, "requires --stop-length --stop-k");
// the feature makes the knobs optional so this run defaults the regime and runs.
let stopless = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
"--from", FROM_MS, "--to", TO_MS,
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
if stopless.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&stopless.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!(
stopless.status.code(),
Some(0),
"a stop-less walkforward must succeed (the stop knobs are optional, each defaulting \
to the single-sourced 3/2.0 regime): {}",
String::from_utf8_lossy(&stopless.stderr)
);
// Explicit 3/2.0: names the very defaults the stop-less run resolves. Same
// blueprint, same axes, same default `--name` ("walkforward"), same window.
let explicit = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
"--stop-length", "3", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
])
.current_dir(&cwd)
.output()
.expect("spawn aura");
assert_eq!(
explicit.status.code(),
Some(0),
"the explicit --stop-length 3 --stop-k 2.0 walkforward must succeed: {}",
String::from_utf8_lossy(&explicit.stderr)
);
// Byte-identity: the default regime IS 3/2.0, so both invocations generate the
// same campaign document and content-addressed storage collapses them onto one
// content id — exactly one campaign document in the store.
let count = |sub: &str| {
std::fs::read_dir(cwd.join("runs").join(sub))
.map(|d| d.count())
.unwrap_or(0)
};
assert_eq!(
count("campaigns"),
1,
"the stop-less default binds the SAME 3/2.0 regime as the explicit run, so the two \
campaign documents are byte-identical and dedup to one content id"
);
let runs_log = std::fs::read_to_string(cwd.join("runs").join("campaign_runs.jsonl"))
.expect("campaign_runs.jsonl exists after two sugar runs");
assert_eq!(
runs_log.lines().count(),
2,
"each invocation still records its own campaign run (a run is an event, a document \
is content)"
);
}
/// Property: `aura generalize` is now thin sugar over the one campaign path — a
/// successful run durably auto-registers exactly one generated process document,
/// one generated campaign document (carrying the `--name` handle and the stop as