Files
Aura/crates/aura-cli/tests/cli_run.rs
T
claude db8f947441 feat(aura-cli)!: retire the research-verb sugar — run/sweep/walkforward/mc/generalize removed
Slice 6 of the #319 retirement, the destructive half. The five flag verbs,
their five Args structs and dispatch arms, the whole argv->document
translator (verb_sugar.rs, 1257 lines) and 33 quintet-only helpers are
gone; retired verbs now refuse at the clap layer as unknown commands.
main.rs shrinks 4445 -> 2764 lines. The gated-intake route list narrows to
the canonical layer + exec's blueprint leg; exec's own refusal prose no
longer names dead verbs; test seeding recipes ride graph register.

cli_run.rs walked the full disposition table: 94 tests retired with their
dead flag surface (each with a named green twin where the property
survives), 75 ported — exec ports keep assertions byte-identical, campaign
ports inherit their golden grades unchanged (walkforward's 90/30/30 real
roller and the archive window clipping the sugar applied silently are now
explicit document fields — the equivalence held exactly, one reconstructed
walkforward pooled expectancy_r within 1e-12, cause documented inline).
Surface deltas recorded by the ports: per-cell refusals are #272 contained
faults (exit 3 + warning) rather than hard exit 1; the synthetic per-seed
mc family and generalize's merged cross-instrument family record were
sugar-only constructs and retire with it (the campaign record's
generalizations[] carries the data).

refs #319
2026-07-25 21:36:58 +02:00

4566 lines
229 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Integration test: drive the built `aura` binary as a downstream user would,
//! asserting the `run` subcommand's stdout/exit contract and the bad-args path.
use std::path::Path;
use std::process::Command;
mod common;
use common::{fresh_project, fresh_project_with_data};
/// Path to the freshly-built `aura` binary (Cargo sets this env var for the test
/// crate; the binary is named `aura` in `Cargo.toml`).
const BIN: &str = env!("CARGO_BIN_EXE_aura");
/// A fresh, unique working directory for a process test that persists run
/// records (so `aura sweep`'s `./runs/runs.jsonl` never dirties the repo).
/// Unique per test + per process; no external tempfile dependency.
fn temp_cwd(name: &str) -> std::path::PathBuf {
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-cli-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
}
// ---------------------------------------------------------------------------
// Campaign-document scaffold (#319 Task 8 Step 4 port). Retired-verb argv
// tests that reach engine/campaign semantics are ported onto a hand-authored
// campaign document driven through `exec` — the surviving executor. This
// mirrors `tests/exec.rs`/`tests/research_docs.rs`'s idiom of the same names;
// duplicated here (rather than shared) because each integration-test binary
// compiles independently and cannot see another binary's private items.
// ---------------------------------------------------------------------------
/// Run the built binary in `dir`, returning combined stdout+stderr and the
/// exit code — the `exec.rs`/`research_docs.rs` idiom, used by every campaign-
/// document port below.
fn run_code_in(dir: &Path, args: &[&str]) -> (String, Option<i32>) {
let out = Command::new(BIN).args(args).current_dir(dir).output().expect("binary runs");
let text =
format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
(text, out.status.code())
}
fn write_doc(dir: &Path, name: &str, text: &str) -> std::path::PathBuf {
let p = dir.join(name);
std::fs::write(&p, text).expect("write doc");
p
}
/// Seed the shipped closed `examples/r_sma.json` blueprint into the project's
/// store via `aura graph register` and return its content id.
fn seed_blueprint(dir: &Path, name: &str) -> String {
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (out, code) = run_code_in(dir, &["graph", "register", &closed_bp, "--name", name]);
assert_eq!(code, Some(0), "seed register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// Seed an arbitrary blueprint FILE (not necessarily `r_sma.json`) into the
/// project's store, returning its content id.
fn seed_blueprint_file(dir: &Path, fixture: &str, name: &str) -> String {
let (out, code) = run_code_in(dir, &["graph", "register", fixture, "--name", name]);
assert_eq!(code, Some(0), "seed register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// Register `doc` as a process document in the project store; returns its id.
fn register_process_doc(dir: &Path, file: &str, doc: &str) -> String {
write_doc(dir, file, doc);
let (out, code) = run_code_in(dir, &["process", "register", file]);
assert_eq!(code, Some(0), "process register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered process "))
.expect("register line")
.trim_start_matches("registered process ")
.split(' ')
.next()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// A minimal executable `[std::sweep]` process (one selection-bearing stage) —
/// mirrors `exec.rs`/`research_docs.rs`'s `SWEEP_ONLY_PROCESS_DOC`.
const SWEEP_ONLY_PROCESS_DOC: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "sweep-only",
"pipeline": [ { "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" } ]
}"#;
/// A `[std::grid, std::walk_forward]` process — the #256 fork B shape a
/// hand-authored (non-sugar) op-script uses — mirrors `exec.rs`/
/// `research_docs.rs`'s `GRID_THEN_WF_PROCESS_DOC`. `std::grid` only
/// enumerates (persists no family of its own); `std::walk_forward` does the
/// per-window IS-refit and persists the one `WalkForward` family.
const GRID_THEN_WF_PROCESS_DOC: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 1209600000, "out_of_sample_ms": 604800000,
"step_ms": 604800000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" }
]
}"#;
/// A `[std::grid, std::walk_forward]` process using the FIXED real-archive
/// roller (`aura_backtest::wf_ms_sizes`, 90d IS / 30d OOS / 30d step — the
/// retired sugar's `fit_wf_ms_sizes` fast-path over a year-plus window) —
/// needed for the exact-grade byte-identity anchors, which pin the retired
/// verb's own fixed real-data roller, unlike `GRID_THEN_WF_PROCESS_DOC`'s
/// shorter 14d/7d/7d roller (sized for the one-month e2e windows elsewhere
/// in this file/`exec.rs`/`research_docs.rs`).
const GRID_THEN_WF_PROCESS_DOC_90_30_30: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-90-30-30",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" }
]
}"#;
/// A `[std::grid, std::walk_forward, std::monte_carlo]` process — mirrors
/// `translate_mc`'s generated shape exactly (the retired `mc --real` sugar):
/// the leading `std::grid` only enumerates, `std::walk_forward` persists the
/// one family, and the terminal `std::monte_carlo` r-bootstraps the pooled
/// per-window OOS trade-R series (`StageBootstrap::PooledOos`).
const GRID_THEN_WF_THEN_MC_PROCESS_DOC: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 1209600000, "out_of_sample_ms": 604800000,
"step_ms": 604800000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 100, "block_len": 5 }
]
}"#;
/// A `[std::sweep, std::generalize]` process — mirrors `translate_generalize`'s
/// generated shape exactly (the retired `generalize` sugar): the sweep stage
/// is selection-bearing (a single-value axis grid, so selection is trivial),
/// and `std::generalize` computes the cross-instrument grade.
const SWEEP_THEN_GENERALIZE_PROCESS_DOC: &str = r#"{
"format_version": 1,
"kind": "process",
"name": "sweep-then-generalize",
"pipeline": [
{ "block": "std::sweep", "metric": "expectancy_r", "select": "argmax" },
{ "block": "std::generalize", "metric": "expectancy_r" }
]
}"#;
/// A one-instrument, one-window, N-axis-value campaign document over the
/// seeded `r_sma.json` blueprint — the dissolved sugar's own generated shape
/// (`translate_sweep`/`translate_walkforward`/`translate_mc`): axes are the
/// RAW `<node>.<param>` names (#328), `seed: 7` (an arbitrary constant, the
/// sugar campaign path never threads a sweep seed either). No risk section
/// (every call site here is stop-free); build the document directly when a
/// risk regime is needed.
fn sweep_campaign_doc(
name: &str,
instrument: &str,
bp_id: &str,
proc_id: &str,
window: (i64, i64),
fast_values: &str,
slow_values: &str,
) -> String {
format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "{name}",
"data": {{ "instruments": ["{instrument}"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [{fast_values}] }},
"slow.length": {{ "kind": "I64", "values": [{slow_values}] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = window.0,
to = window.1,
)
}
/// Property (ported onto a one-cell campaign document, #319 Task 8 — the
/// retired `run --real` verb's own hard exit-1/no-stdout contract does not
/// survive the campaign path's #272 contained-fault model, but the pip-honesty
/// substance does): the geometry-sidecar lookup precedes any bar-data access,
/// so a symbol with no recorded geometry refuses with NO local data required
/// (CI-safe). "NONEXISTENT" has no recorded geometry on any host. A single-cell
/// campaign realizes this as a CONTAINED cell fault (#272: `aura: warning: `
/// class marker, deliberate exit 3, never a hard exit 1) — the pip lookup still
/// fires before any bar-data access, and the fault's own detail carries the
/// SAME "no recorded geometry" message `run --real` used to print to stderr.
#[test]
fn run_real_no_geometry_symbol_refuses_with_exit_1() {
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "geo-refusal-seed");
let proc_id = register_process_doc(&dir, "geo.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("geo", "NONEXISTENT", &bp_id, &proc_id, (1, 2), "2", "8");
write_doc(&dir, "geo.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "geo.campaign.json"]);
assert_eq!(code, Some(3), "a contained no-geometry cell fault is exit 3, never exit 1: {out}");
assert!(
out.contains("no recorded geometry for symbol 'NONEXISTENT'"),
"expected the per-instrument-pip refusal message in the contained fault, got: {out}"
);
assert!(out.contains("aura: warning: "), "the fault carries the #272 class marker: {out}");
}
/// Property: the pip-refusal is sourced ONLY from recorded provider geometry — the
/// removed authored instrument floor (the hand-authored "vetted" table) no
/// longer participates in the refusal path. The cycle-0074 headline is the floor's
/// REMOVAL, not a rename; the observable signature is that the refusal message
/// never points the user at a hand-authored table to extend. A regression that
/// re-introduced an authored fallback (or restored the old "add it to the
/// instrument table" wording) would re-leak this vocabulary. Asserts the OLD
/// authored-floor phrasing is ABSENT — the negative complement to the positive
/// "no recorded geometry" assertion above. Archive-independent: "NONEXISTENT" has
/// no sidecar on any host, so the refusal fires without local data. Ported onto
/// the campaign path's contained fault (see the sibling above for the exit-code
/// rationale).
#[test]
fn run_real_refusal_names_no_authored_floor() {
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "geo-floor-seed");
let proc_id = register_process_doc(&dir, "geofloor.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("geofloor", "NONEXISTENT", &bp_id, &proc_id, (1, 2), "2", "8");
write_doc(&dir, "geofloor.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "geofloor.campaign.json"]);
assert_eq!(code, Some(3), "a contained no-geometry cell fault is exit 3: {out}");
assert!(!out.contains("instrument table"), "refusal must not point at a removed authored table, got: {out}");
assert!(!out.contains("vetted"), "refusal must not use the removed vetted-floor vocabulary, got: {out}");
assert!(!out.contains("instrument spec"), "refusal must not reference the removed authored instrument floor, got: {out}");
}
/// Property (adapted for the campaign path's always-on `{"campaign_run":...}`
/// summary line, #319 Task 8): the un-specced-symbol refusal never leaks a
/// partial `RunReport` — the #22 acceptance is "refuse INSTEAD of emitting
/// nonsense". The retired `run --real` verb's OWN packaging of that refusal
/// was silence on stdout entirely; the campaign executor always emits its one
/// `{"campaign_run":...}` audit line regardless of outcome (#272's whole
/// point — a fault is RECORDED, not swallowed), so "no stdout at all" cannot
/// survive verbatim. What DOES survive: the faulted cell carries NO `report`
/// key at all (no partial manifest/metrics — only a `fault`), and no
/// `family_table` member line is emitted for it either.
#[test]
fn run_real_no_geometry_symbol_emits_no_stdout_report() {
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "geo-nostdout-seed");
let proc_id = register_process_doc(&dir, "geonostdout.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("geonostdout", "NONEXISTENT", &bp_id, &proc_id, (1, 2), "2", "8");
write_doc(&dir, "geonostdout.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "geonostdout.campaign.json"]);
assert_eq!(code, Some(3), "a contained no-geometry cell fault is exit 3: {out}");
assert!(
!out.contains("\"report\":"),
"the refusal must not leak a partial report for the faulted cell, got: {out}"
);
assert!(
!out.lines().any(|l| l.starts_with("{\"family_id\":")),
"no family_table member line for a cell that never ran: {out}"
);
assert!(out.contains("\"fault\":"), "the faulted cell is recorded as a fault, not swallowed: {out}");
}
/// Property (ported onto a one-cell campaign document): the pip-honesty refusal
/// keys on the SYMBOL having no recorded geometry, not on real data being
/// requested at all. A symbol *with* a recorded sidecar (`EURUSD`) gets PAST
/// the geometry lookup, so it never produces the pip-refusal message — it
/// either runs to a report (local data present) or hits the DISTINCT
/// no-local-data path ("no local data for symbol"), both contained per #272
/// (exit 0 success, or exit 3 with the no-data fault detail — never the
/// geometry-refusal wording).
#[test]
fn run_real_sidecar_symbol_never_hits_the_pip_refusal() {
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "eurusd-sidecar-seed");
let proc_id = register_process_doc(&dir, "eurusd.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"eurusd-sidecar", "EURUSD", &bp_id, &proc_id,
(EURUSD_JUN2024_FROM_MS, EURUSD_JUN2024_TO_MS), "2", "8",
);
write_doc(&dir, "eurusd.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "eurusd.campaign.json"]);
assert!(
!out.contains("no recorded geometry"),
"a symbol with a recorded sidecar must clear the pip lookup, never the pip-refusal: {out}"
);
match code {
Some(0) => assert!(
out.lines().any(|l| l.starts_with("{\"family_id\":")),
"exit 0 must carry a family member report: {out}"
),
Some(3) => assert!(
out.contains("no local data for symbol 'EURUSD'"),
"a contained fault for a symbol with a recorded sidecar must be the no-data path, got: {out}"
),
other => panic!("unexpected exit for a real cell with a recorded sidecar: {other:?}; out: {out}"),
}
}
/// Property (ported onto a one-cell campaign document): a recorded-sidecar
/// symbol threads its looked-up pip all the way to the emitted member
/// manifest — GER40 renders the INDEX pip `sim-optimal(pip_size=1)`, not the
/// FX `0.0001` literal the synthetic path uses. This is the #22 headline at
/// the built-binary boundary: the metadata channel reaches stdout, so equity
/// is honestly scaled per instrument. Gated on local GER40 data — skips
/// cleanly when the archive is absent (a contained no-data fault, #272,
/// rather than the old hard exit 1).
#[test]
fn run_real_sidecar_index_pip_reaches_the_emitted_manifest() {
// The GER40 Sept-2024 window (inclusive Unix-ms), the same calendar
// month the gated ingest path drives; bounding it keeps the run fast.
const FROM_MS: i64 = 1725148800000;
const TO_MS: i64 = 1727740799999;
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "ger40-indexpip-seed");
let proc_id = register_process_doc(&dir, "ger40pip.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("ger40pip", "GER40", &bp_id, &proc_id, (FROM_MS, TO_MS), "2", "8");
write_doc(&dir, "ger40pip.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "ger40pip.campaign.json"]);
// Skip on a data-less machine: a recorded-sidecar symbol with no local data
// takes the distinct no-data path (contained fault, exit 3), never the
// pip-refusal.
if code == Some(3) && out.contains("no local data for symbol 'GER40'") {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {code:?}, out: {out}");
// the looked-up index pip (1.0 -> "1") reaches the manifest, not the FX 0.0001.
assert!(
out.contains("\"broker\":\"sim-optimal+risk-executor(pip_size=1)\""),
"GER40 must render the index pip in the manifest, got: {out}"
);
}
/// Property (ported onto a one-cell campaign document): the authored instrument
/// table no longer gates real runs — a symbol absent from the (removed)
/// vetted floor but carrying a recorded geometry sidecar resolves its pip from
/// the sidecar and runs. USDJPY (never vetted; JPY pip 0.01) renders the JPY
/// pip in the manifest, proving the pip is sourced from the provider geometry,
/// not a hand-authored table entry. Gated: skips cleanly (contained fault,
/// #272) when USDJPY has no local geometry/data.
#[test]
fn run_real_nonvetted_symbol_resolves_pip_from_sidecar() {
// FX trades continuously; the GER40 Sept-2024 window also has USDJPY bars.
const FROM_MS: i64 = 1725148800000;
const TO_MS: i64 = 1727740799999;
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "usdjpy-sidecar-seed");
let proc_id = register_process_doc(&dir, "usdjpy.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("usdjpy", "USDJPY", &bp_id, &proc_id, (FROM_MS, TO_MS), "2", "8");
write_doc(&dir, "usdjpy.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "usdjpy.campaign.json"]);
// Skip on a host without USDJPY geometry/data — either refusal is a clean
// contained-fault skip, sourced from recorded geometry, never an
// authored-table miss.
if code == Some(3) && (out.contains("no recorded geometry") || out.contains("no local data")) {
eprintln!("skip: no local USDJPY geometry/data");
return;
}
assert_eq!(code, Some(0), "USDJPY should resolve and run; exit: {code:?}, out: {out}");
assert!(
out.contains("pip_size=0.01"),
"USDJPY must render the JPY sidecar pip (0.01) in the manifest, got: {out}"
);
}
// Note (#159 cut 4 collateral): `--trace` on a single `aura run`/`aura sweep`
// is no longer reachable from any CLI surface — the built-in default that
// accepted it retired with the last two PIP strategies, and the blueprint
// branches (`dispatch_run`, `run_blueprint_sweep`) never wire a trace-persist
// path. Repointing would require new production code (out of scope for a
// test-fixing task), so the single-run trace-persist + shape tests are
// deleted here rather than adjusted.
/// Property: an unknown `chart <name>` is a runtime failure pinned at BOTH the exit code
/// and the user-facing message — exit 1 (never a panic) AND the NotFound branch's
/// "no recorded run or family '<name>'" wording, which now covers families too. A
/// regression that drifted the message (e.g. back to the run-only phrasing) or
/// downgraded the exit would slip past the bundled exit-only check; this pins the
/// message/exit contract. Archive-local: charts into an empty cwd so the name is
/// genuinely absent.
#[test]
fn chart_unknown_name_refuses_with_exit_1_and_message() {
let cwd = temp_cwd("chart-unknown");
let out = Command::new(BIN).args(["chart", "ghost"]).current_dir(&cwd).output().expect("spawn chart ghost");
assert_eq!(out.status.code(), Some(1), "unknown name must exit 1: {:?}", out.status);
assert!(out.stdout.is_empty(), "the refusal must not leak a chart page to stdout, got: {:?}", String::from_utf8_lossy(&out.stdout));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no recorded run or family 'ghost'"),
"expected the unknown-name (run-or-family) refusal message, got: {stderr}"
);
let _ = std::fs::remove_dir_all(&cwd);
}
/// Property (milestone fieldtest B1): a sweep / walk-forward `--trace` family is
/// chartable by the very handle the producing run prints. Those verbs fan a
/// selection-free sweep out per member into the #224 depth-2 layout
/// `runs/traces/<name>/<cell>/<member>/index.json` (one directory level deeper
/// than the campaign path's depth-1 nominee layout `<name>/<cell>/index.json`).
/// `aura chart <handle>` must resolve that fan-out as a family: exit 0 with the
/// chart page on stdout, covering the members' taps. AND the genuinely-absent-
/// handle refusal must not tell the user to re-run a command with that same
/// handle as the `--trace` argument — that command produced the data (it would
/// already be on disk), so pointing back at it is a dead-end remedy.
///
/// The fixture is fabricated inline through `TraceStore` (no archive, no
/// data-server, no sweep run): two members under one cell, exactly the depth-2
/// shape `persist_campaign_traces`' member fan-out writes. On today's tree the
/// depth-1-only `name_kind` / `read_family` classify the handle as NotFound, so
/// `chart` exits 1 with the misleading remedy — RED on both counts.
#[test]
fn chart_opens_a_sweep_trace_family_by_its_printed_handle() {
use aura_core::{Scalar, ScalarKind, Timestamp};
use aura_engine::{ColumnarTrace, RunManifest};
use aura_registry::TraceStore;
let cwd = temp_cwd("chart-sweep-family");
let manifest = || RunManifest {
commit: "c0ffee".to_string(),
params: vec![("fast".to_string(), Scalar::f64(2.0))],
defaults: vec![],
window: (Timestamp(1), Timestamp(2)),
seed: 0,
broker: "sim-optimal(pip_size=1)".to_string(),
selection: None,
instrument: Some("GER40".to_string()),
topology_hash: None,
project: None,
};
let taps = |a: f64, b: f64| {
vec![ColumnarTrace::from_rows(
"equity",
&[ScalarKind::F64],
&[(Timestamp(1), vec![Scalar::f64(a)]), (Timestamp(2), vec![Scalar::f64(b)])],
)]
};
// The #224 sweep fan-out layout: two members under ONE cell, so each member's
// index.json lives at depth 2 (`<name>/<cell>/<member>/index.json`) — not the
// depth-1 nominee layout (`<name>/<cell>/index.json`) the campaign path writes.
// `TraceStore::open` roots at `<runs_dir>/traces`, matching `env.trace_store()`.
let store = TraceStore::open(cwd.join("runs"));
store.write("swp/cell0/m0", &manifest(), &taps(0.0, 0.4)).expect("write member m0");
store.write("swp/cell0/m1", &manifest(), &taps(0.0, 0.7)).expect("write member m1");
// (1) chart the family by the handle the sweep prints: exit 0 + chart page.
let out = Command::new(BIN).args(["chart", "swp"]).current_dir(&cwd).output().expect("spawn chart swp");
assert_eq!(
out.status.code(),
Some(0),
"charting a sweep --trace family by its handle must exit 0; got {:?}, stderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr),
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("window.AURA_TRACES") && stdout.contains("<title>aura chart"),
"the family handle must render a chart page covering its members' taps \
(AURA_TRACES + chart title); stdout len {}",
stdout.len(),
);
// (2) the genuinely-absent-handle refusal must not suggest re-running a
// command with the handle itself as the `--trace` argument (a dead-end remedy).
let ghost = Command::new(BIN).args(["chart", "ghost"]).current_dir(&cwd).output().expect("spawn chart ghost");
assert_eq!(ghost.status.code(), Some(1), "an absent handle still exits 1: {:?}", ghost.status);
let gerr = String::from_utf8_lossy(&ghost.stderr);
assert!(
!gerr.contains("--trace ghost"),
"the not-found remedy must not tell the user to re-run with the handle as --trace \
(that command produced the data / would not create this handle); got: {gerr}",
);
let _ = std::fs::remove_dir_all(&cwd);
}
/// Property (#238): a sweep / walk-forward `--trace <NAME>` family is chartable by
/// the very NAME the user chose — not only by the content-derived handle the run
/// prints. `--trace <NAME>` lands `NAME` in the generated campaign document's
/// `name` field (content-addressed via `put_campaign`), while the trace directory
/// stays keyed by the content-derived handle `{campaign8}-{run}` that
/// `append_campaign_run` re-derives (C18, untouched). Today `aura chart <NAME>`
/// classifies `NAME` as an unknown trace handle and exits 1, so the chosen name
/// has no charting effect. It must instead resolve `NAME` against the recorded
/// campaign runs: the ONE record whose stored campaign document carries
/// `name == NAME` and a persisted `trace_name` charts that family by its handle —
/// exit 0 with the same chart page charting the handle directly renders.
///
/// The state is fabricated in-process, no archive / sweep run: a campaign
/// document carrying a distinctive `name` is stored via `put_campaign`; a
/// campaign-run record with a `Some` trace_name sentinel is appended (which
/// re-derives the handle `{campaign8}-0`); a family is written under that exact
/// handle via `TraceStore` — mirroring the handle-charted sibling above (the
/// #224 depth-2 fan-out). The registry roots where `env.registry()` does
/// (`<cwd>/runs/runs.jsonl`), so the running binary reads the same store. On
/// today's tree `chart <NAME>` hits `emit_chart`'s NotFound arm (no registry
/// query), so it exits 1 with the "no recorded run or family" message — RED for
/// the right reason (the name-resolution is absent, not a fabrication error).
#[test]
fn chart_resolves_a_trace_family_by_its_chosen_campaign_name() {
use aura_core::{Scalar, ScalarKind, Timestamp};
use aura_engine::{ColumnarTrace, RunManifest};
use aura_registry::{derive_trace_name, CampaignRunRecord, Registry, TraceStore};
let cwd = temp_cwd("chart-by-campaign-name");
// The chosen family name (`--trace <NAME>`) — distinctive, not a trace handle
// (no such directory under runs/traces, so `name_kind` classifies NotFound).
const CAMPAIGN_NAME: &str = "mom-ger40-sept24";
// (1) Store a campaign document carrying that name (a valid CampaignDoc, so a
// resolver can parse it by any strategy — Value lookup or full deserialize).
// `put_campaign` self-keys by content, returning the id the campaign-run
// record must reference. The registry roots where `env.registry()` does:
// `<cwd>/runs/runs.jsonl` (campaign docs at `<cwd>/runs/campaigns/<id>.json`,
// campaign_runs at `<cwd>/runs/campaign_runs.jsonl`).
let reg = Registry::open(cwd.join("runs").join("runs.jsonl"));
let campaign_json = r#"{
"format_version": 1,
"kind": "campaign",
"name": "__NAME__",
"data": { "instruments": ["GER40"],
"windows": [ { "from_ms": 1, "to_ms": 2 } ] },
"strategies": [
{ "ref": { "content_id": "9f3a" },
"axes": { "fast": { "kind": "I64", "values": [8, 12] } } }
],
"process": { "ref": { "content_id": "4e2d" } },
"seed": 1,
"presentation": { "persist_taps": ["equity"], "emit": ["family_table"] }
}"#
.replace("__NAME__", CAMPAIGN_NAME);
let campaign_id = reg.put_campaign(&campaign_json).expect("store campaign document");
// (2) Append a campaign-run record whose trace_name is Some (the executor's
// claim sentinel; `append_campaign_run` assigns the per-campaign run counter
// and re-derives the stored trace_name to `{campaign8}-{run}`).
let record = CampaignRunRecord {
campaign: campaign_id.clone(),
process: "4e2d".to_string(),
run: 0, // ignored — append assigns the counter
seed: 1,
cells: vec![],
generalizations: vec![],
trace_name: Some("claim".to_string()),
};
let run = reg.append_campaign_run(&record).expect("append campaign run");
let handle = derive_trace_name(&campaign_id, run);
// (3) Write a family under that exact handle via TraceStore — the same
// fabrication the handle-charted sibling uses (#224 depth-2: two members under
// one cell). Charting `handle` directly is thus known to render a family chart.
let manifest = || RunManifest {
commit: "c0ffee".to_string(),
params: vec![("fast".to_string(), Scalar::f64(2.0))],
defaults: vec![],
window: (Timestamp(1), Timestamp(2)),
seed: 0,
broker: "sim-optimal(pip_size=1)".to_string(),
selection: None,
instrument: Some("GER40".to_string()),
topology_hash: None,
project: None,
};
let taps = |a: f64, b: f64| {
vec![ColumnarTrace::from_rows(
"equity",
&[ScalarKind::F64],
&[(Timestamp(1), vec![Scalar::f64(a)]), (Timestamp(2), vec![Scalar::f64(b)])],
)]
};
let store = TraceStore::open(cwd.join("runs"));
store.write(&format!("{handle}/cell0/m0"), &manifest(), &taps(0.0, 0.4)).expect("write member m0");
store.write(&format!("{handle}/cell0/m1"), &manifest(), &taps(0.0, 0.7)).expect("write member m1");
// Chart by the chosen NAME (not the handle): resolution must go through the
// campaign-run query and land on `handle`, rendering the same family chart.
let out = Command::new(BIN).args(["chart", CAMPAIGN_NAME]).current_dir(&cwd).output().expect("spawn chart <name>");
assert_eq!(
out.status.code(),
Some(0),
"charting a --trace family by its chosen campaign name must exit 0; got {:?}, stderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr),
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("window.AURA_TRACES") && stdout.contains("<title>aura chart"),
"the chosen name must render the same family chart as its handle \
(AURA_TRACES + chart title); stdout len {}",
stdout.len(),
);
let _ = std::fs::remove_dir_all(&cwd);
}
/// Property (#238 ambiguity): when TWO recorded campaign runs' stored documents
/// both carry the SAME chosen `name`, `chart <NAME>` must refuse (exit 1) rather
/// than silently pick one — the resolver names both candidate trace-store
/// handles in the refusal (deterministic file/append order), so the user can
/// re-run `chart` against the specific handle they mean. Mirrors the fixture of
/// [`chart_resolves_a_trace_family_by_its_chosen_campaign_name`], duplicated
/// once with a second `put_campaign` + `append_campaign_run` under the same
/// `name` (a distinct campaign document — the `data.windows` bound differs —
/// still resolves to the same `name`, exactly the "two separate --trace <NAME>
/// invocations reusing a name" scenario this guards against).
#[test]
fn chart_by_campaign_name_refuses_when_the_name_is_ambiguous() {
use aura_core::{Scalar, ScalarKind, Timestamp};
use aura_engine::{ColumnarTrace, RunManifest};
use aura_registry::{derive_trace_name, CampaignRunRecord, Registry, TraceStore};
let cwd = temp_cwd("chart-by-name-ambiguous");
const CAMPAIGN_NAME: &str = "reused-name";
let reg = Registry::open(cwd.join("runs").join("runs.jsonl"));
let campaign_json_of = |to_ms: i64| {
format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "{CAMPAIGN_NAME}",
"data": {{ "instruments": ["GER40"],
"windows": [ {{ "from_ms": 1, "to_ms": {to_ms} }} ] }},
"strategies": [
{{ "ref": {{ "content_id": "9f3a" }},
"axes": {{ "fast": {{ "kind": "I64", "values": [8, 12] }} }} }}
],
"process": {{ "ref": {{ "content_id": "4e2d" }} }},
"seed": 1,
"presentation": {{ "persist_taps": ["equity"], "emit": ["family_table"] }}
}}"#
)
};
let manifest = || RunManifest {
commit: "c0ffee".to_string(),
params: vec![("fast".to_string(), Scalar::f64(2.0))],
defaults: vec![],
window: (Timestamp(1), Timestamp(2)),
seed: 0,
broker: "sim-optimal(pip_size=1)".to_string(),
selection: None,
instrument: Some("GER40".to_string()),
topology_hash: None,
project: None,
};
let taps = |a: f64, b: f64| {
vec![ColumnarTrace::from_rows(
"equity",
&[ScalarKind::F64],
&[(Timestamp(1), vec![Scalar::f64(a)]), (Timestamp(2), vec![Scalar::f64(b)])],
)]
};
let store = TraceStore::open(cwd.join("runs"));
let mut handles = Vec::new();
for to_ms in [2, 3] {
let campaign_id = reg.put_campaign(&campaign_json_of(to_ms)).expect("store campaign document");
let record = CampaignRunRecord {
campaign: campaign_id.clone(),
process: "4e2d".to_string(),
run: 0,
seed: 1,
cells: vec![],
generalizations: vec![],
trace_name: Some("claim".to_string()),
};
let run = reg.append_campaign_run(&record).expect("append campaign run");
let handle = derive_trace_name(&campaign_id, run);
store.write(&format!("{handle}/cell0/m0"), &manifest(), &taps(0.0, 0.4)).expect("write member m0");
store.write(&format!("{handle}/cell0/m1"), &manifest(), &taps(0.0, 0.7)).expect("write member m1");
handles.push(handle);
}
let out = Command::new(BIN).args(["chart", CAMPAIGN_NAME]).current_dir(&cwd).output().expect("spawn chart <name>");
assert_eq!(
out.status.code(),
Some(1),
"an ambiguous campaign name must refuse (exit 1), not silently pick one; got {:?}, stdout: {}",
out.status.code(),
String::from_utf8_lossy(&out.stdout),
);
assert!(out.stdout.is_empty(), "the refusal must not leak a chart page to stdout");
let stderr = String::from_utf8_lossy(&out.stderr);
for handle in &handles {
assert!(
stderr.contains(handle.as_str()),
"the refusal must list every candidate handle deterministically; got: {stderr}, want handle: {handle}"
);
}
let _ = std::fs::remove_dir_all(&cwd);
}
#[test]
fn no_args_prints_usage_and_exits_two() {
let out = Command::new(BIN).output().expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "exit status: {:?}", out.status);
assert!(out.stdout.is_empty(), "stdout should be empty on the usage path");
let stderr = String::from_utf8(out.stderr).expect("utf-8 stderr");
assert!(stderr.contains("Usage"), "stderr was: {stderr:?}");
}
/// Property: `aura graph` emits a single self-contained HTML page — the
/// interactive pin-graph viewer with the deterministic model inlined and the
/// vendored Graphviz-WASM + pan-zoom inlined (no remote fetch). Driven through
/// the built binary so it pins the observable CLI contract. The DOT/SVG are
/// Graphviz-version-dependent and not asserted (the prototype is the by-hand
/// visual reference); this pins the page envelope + the retirement of the old
/// ascii-dag notation.
#[test]
fn graph_emits_self_contained_html_viewer() {
let out = Command::new(BIN).arg("graph").output().expect("spawn aura graph");
assert_eq!(out.status.code(), Some(0), "`aura graph` exit: {:?}", out.status);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
// a self-contained HTML document...
assert!(
stdout.trim_start().starts_with("<!doctype html>"),
"not an HTML doc: {:?}",
&stdout[..stdout.len().min(80)]
);
// ...with the deterministic model inlined as the viewer's data source...
assert!(stdout.contains("window.AURA_MODEL = {\"root\":"), "model not inlined: {stdout:?}");
// ...the Graphviz-WASM bootstrap present, and no remote asset fetch.
assert!(stdout.contains("Viz.instance"), "viewer bootstrap missing");
assert!(!stdout.contains("<script src"), "assets must be inlined, found remote <script src>");
// the retired ascii-dag invented notation is gone.
assert!(!stdout.contains("Sub(#Sf,#Ss)"), "retired ascii-dag notation must be gone: {stdout}");
}
/// Property (#28): `aura graph <blueprint.json>` renders the CONSUMER's own
/// graph — the structure of the file it is handed — not the embedded r_sma
/// sample. Mirrors the `dispatch_run` dual-grammar: a first-positional naming
/// an existing `.json` selects the loaded-blueprint branch, which reads the
/// file, reloads it via `blueprint_from_json`, and `render_html`s it — so the
/// emitted page's inlined `window.AURA_MODEL` reflects THAT file's nodes.
/// Handed the retired r-breakout example (std-vocabulary only, so no project is
/// needed), the page must carry a marker unique to that graph — `channel_hi`, a
/// RollingMax node name absent from the sample and from every static viewer
/// asset — and must NOT carry the sample-only `"type":"Bias"` node, i.e. it did
/// not silently fall back to the embedded r_sma sample. Driven through the built
/// binary so it pins the observable CLI contract.
#[test]
fn graph_renders_a_consumer_blueprint_file_not_the_embedded_sample() {
let cwd = temp_cwd("graph-consumer-blueprint");
// A committed consumer blueprint that is NOT the embedded sample and resolves
// against the base std vocabulary alone (RollingMax/RollingMin/Delay/Gt/Latch/
// Sub) — no project fixture required. Absolute path, so the temp cwd is free of
// any Aura.toml (guarantees std-env resolution).
let blueprint = format!("{}/examples/r_breakout.json", env!("CARGO_MANIFEST_DIR"));
let out = Command::new(BIN)
.arg("graph")
.arg(&blueprint)
.current_dir(&cwd)
.output()
.expect("spawn aura graph <blueprint>");
assert_eq!(
out.status.code(),
Some(0),
"`aura graph <blueprint.json>` exit: {:?}\nstderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
// the page inlines a model as the viewer's data source...
assert!(
stdout.contains("window.AURA_MODEL = {\"root\":"),
"model not inlined into the page"
);
// ...and that model is the CONSUMER graph's — its own node `channel_hi`
// (unique to r-breakout, absent from the r_sma sample and from every static
// asset) is present.
assert!(
stdout.contains("channel_hi"),
"the consumer blueprint's own node (channel_hi) is missing — its graph was not rendered"
);
// ...NOT the embedded r_sma sample: the sample's Bias node type must be absent
// (proof the file was rendered, not silently ignored in favour of the sample).
assert!(
!stdout.contains("\"type\":\"Bias\""),
"rendered the embedded r_sma sample (its Bias node is present) instead of the consumer blueprint"
);
}
/// Property (#28 close-audit): a NAMED-but-unreadable blueprint arg to
/// `aura graph` is a usage fault, not a silent fallback to the embedded sample.
/// `dispatch_graph`'s no-subcommand arm branches on `is_blueprint_file`, which
/// returns `None` for BOTH "no positional given" (→ legitimate sample default)
/// and "a positional was given but it is not a readable `.json` file" (→ a bad
/// arg). The two must not be conflated: a bad arg (typo, nonexistent path, wrong
/// extension) must `eprintln!` a usage error and exit 2, mirroring `dispatch_run`
/// — NOT render the r_sma sample and exit 0, which re-introduces the exact #28
/// friction ("I asked for my graph, got the sample") in the error path. The
/// bare-`aura graph` default (no positional → sample, exit 0) is guarded in the
/// same test so the fix cannot regress it.
#[test]
fn graph_with_bad_blueprint_arg_is_a_usage_fault_not_a_silent_sample() {
let cwd = temp_cwd("graph-bad-arg");
// A named .json path that does not exist: `is_blueprint_file` returns None,
// and the buggy arm falls through to the embedded sample instead of refusing.
let out = Command::new(BIN)
.arg("graph")
.arg("does-not-exist.json")
.current_dir(&cwd)
.output()
.expect("spawn aura graph <bad-arg>");
// Headline pin: a named-but-unreadable blueprint arg exits 2 (usage fault),
// mirroring `dispatch_run`'s bad-blueprint branch — not 0.
assert_eq!(
out.status.code(),
Some(2),
"`aura graph <nonexistent.json>` must be a usage fault (exit 2), not a silent sample fallback; got {:?}\nstderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr),
);
// ...and it must NOT emit the embedded r_sma sample: the sample's inlined
// model + its Bias node must be absent from stdout.
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
assert!(
!stdout.contains("\"type\":\"Bias\""),
"a bad blueprint arg silently rendered the embedded r_sma sample (its Bias node is present) instead of refusing"
);
// Guard the legitimate default: bare `aura graph` (no positional) still
// renders the embedded sample and exits 0.
let default = Command::new(BIN)
.arg("graph")
.current_dir(&cwd)
.output()
.expect("spawn aura graph");
assert_eq!(
default.status.code(),
Some(0),
"bare `aura graph` must still render the sample (exit 0); got {:?}",
default.status,
);
let default_out = String::from_utf8(default.stdout).expect("utf-8 stdout");
assert!(
default_out.contains("\"type\":\"Bias\""),
"bare `aura graph` must render the embedded r_sma sample (its Bias node), the no-positional default"
);
}
#[test]
fn runs_families_list_and_per_family_rank_across_invocations() {
// Ported onto a one-cell campaign document run twice (#319 Task 8): two
// `exec`s of the same sweep-only document accumulate two families over
// time, each a related set of 4 records (C18/C21 lineage), in the sibling
// family store. Unlike the retired verb sugar (whose family_id was the
// literal `--name`/`"sweep"` default), the campaign path's own family
// naming is a deterministic, content-derived coordinate name
// (`{campaign8}-{strategy}-{instrument}-{regime}-w{window}-r{run}-s{stage}`,
// `aura-campaign::exec`) — so the two family ids are read back from `runs
// families` rather than assumed to be "sweep-0"/"sweep-1"; the STRUCTURAL
// property (two families, 4 members each, per-family rank works) survives
// unchanged.
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "runs-flow-seed");
let proc_id = register_process_doc(&cwd, "runsflow.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"runsflow", "SYMA", &bp_id, &proc_id,
(1709251200000, 1719791999999), "2, 4", "8, 16",
);
write_doc(&cwd, "runsflow.campaign.json", &doc);
for _ in 0..2 {
let (out, code) = run_code_in(&cwd, &["exec", "runsflow.campaign.json"]);
assert_eq!(code, Some(0), "exec exit: {out}");
}
// `runs families` shows both stored families in first-seen order, each with
// its kind + member count.
let (fams_out, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
let fam_lines: Vec<&str> = fams_out.lines().collect();
assert_eq!(fam_lines.len(), 2, "families: {fams_out:?}");
assert!(fam_lines.iter().all(|l| l.contains("\"kind\":\"Sweep\"")), "families: {fams_out:?}");
assert!(fam_lines.iter().all(|l| l.contains("\"members\":4")), "members: {fams_out:?}");
let family_id_of = |line: &str| -> String {
let key = "\"family_id\":\"";
let start = line.find(key).expect("line has family_id") + key.len();
let tail = &line[start..];
let end = tail.find('"').expect("closing quote");
tail[..end].to_string()
};
let (fam0, fam1) = (family_id_of(fam_lines[0]), family_id_of(fam_lines[1]));
assert_ne!(fam0, fam1, "two distinct runs mint two distinct families: {fams_out:?}");
assert!(fam0.ends_with("-0"), "first run's family carries the run-0 suffix: {fam0}");
assert!(fam1.ends_with("-1"), "second run's family carries the run-1 suffix: {fam1}");
// `runs family <fam0> rank total_pips`: the family's 4 members, highest
// total_pips first.
let (rank_out, rank_code) = run_code_in(&cwd, &["runs", "family", &fam0, "rank", "total_pips"]);
assert_eq!(rank_code, Some(0), "rank exit: {rank_out}");
assert_eq!(rank_out.lines().count(), 4, "rank: {rank_out:?}");
assert!(rank_out.lines().all(|l| l.starts_with("{\"manifest\":{")), "rank shape: {rank_out:?}");
let pips: Vec<f64> = rank_out
.lines()
.map(|l| {
let key = "\"total_pips\":";
let start = l.find(key).expect("line has total_pips") + key.len();
let tail = &l[start..];
let end = tail.find([',', '}']).expect("token end");
tail[..end].parse().expect("total_pips is an f64")
})
.collect();
assert!(pips.windows(2).all(|w| w[0] >= w[1]), "rank not descending: {pips:?}");
// an unknown metric is a usage error
let (bogus_out, bogus_code) = run_code_in(&cwd, &["runs", "family", &fam0, "rank", "bogus"]);
assert_eq!(bogus_code, Some(2), "bogus exit: {bogus_out}");
// an unknown family id is an empty family: zero lines, exit 0.
let (unknown_out, unknown_code) = run_code_in(&cwd, &["runs", "family", "nope-9"]);
assert_eq!(unknown_code, Some(0), "unknown family exit: {unknown_out}");
assert_eq!(unknown_out.len(), 0, "unknown family stdout: {unknown_out:?}");
}
/// #147: the pre-rename metric name `exposure_sign_flips` still resolves
/// through the real, user-facing `aura runs family <id> rank <metric>` path —
/// not just through a direct `MetricVocabulary::resolve` call in a unit test.
/// Task 4 re-exports `aura_campaign::RANKABLE_METRICS` from the single-sourced
/// backtest vocabulary instead of hand-copying it; were the vocabulary's
/// `resolve()` match arm for the legacy alias ever dropped in that refactor,
/// this is the only test that would catch it at the CLI's actual observable
/// boundary (exit code + stdout), rather than inside the crate that owns the
/// vocabulary.
#[test]
fn runs_family_rank_accepts_legacy_exposure_sign_flips_alias() {
// Ported onto a one-cell campaign document (#319 Task 8) — the family_id
// is read back from `runs families` (see the sibling test's doc comment
// for why the campaign path's own naming isn't the literal "sweep-0").
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "legacy-alias-seed");
let proc_id = register_process_doc(&cwd, "legacyalias.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"legacyalias", "SYMA", &bp_id, &proc_id,
(1709251200000, 1719791999999), "2, 4", "8, 16",
);
write_doc(&cwd, "legacyalias.campaign.json", &doc);
let (exec_out, exec_code) = run_code_in(&cwd, &["exec", "legacyalias.campaign.json"]);
assert_eq!(exec_code, Some(0), "exec exit: {exec_out}");
let family_id = exec_out
.lines()
.find(|l| l.starts_with("{\"family_id\":"))
.map(|l| {
serde_json::from_str::<serde_json::Value>(l).expect("member line parses")["family_id"]
.as_str()
.expect("family_id is a string")
.to_string()
})
.expect("a member line carries the family id");
let (rank_out, rank_code) = run_code_in(&cwd, &["runs", "family", &family_id, "rank", "exposure_sign_flips"]);
assert_eq!(
rank_code, Some(0),
"the legacy alias must still resolve, not usage-error: {rank_out}"
);
assert_eq!(rank_out.lines().count(), 4, "all 4 members rank: {rank_out:?}");
// the renamed field is what actually ships in the JSON — the alias is a
// resolve()-only back door, never a second output key.
assert!(
rank_out.lines().all(|l| l.contains("\"bias_sign_flips\":")),
"output still serializes under the new key: {rank_out:?}"
);
assert!(
!rank_out.contains("\"exposure_sign_flips\":"),
"the legacy key must never appear in output: {rank_out:?}"
);
let flips: Vec<i64> = rank_out
.lines()
.map(|l| {
let key = "\"bias_sign_flips\":";
let start = l.find(key).expect("line has bias_sign_flips") + key.len();
let tail = &l[start..];
let end = tail.find([',', '}']).expect("token end");
tail[..end].parse().expect("bias_sign_flips is an integer")
})
.collect();
// bias_sign_flips is a lower-is-better metric (fewer direction reversals),
// so `rank` orders ascending here — unlike total_pips's descending order.
assert!(flips.windows(2).all(|w| w[0] <= w[1]), "rank not ascending by the aliased metric: {flips:?}");
}
#[test]
fn runs_list_and_rank_are_retired_and_exit_two() {
// `aura runs list` / `runs rank <metric>` were retired (#73): families
// (sweep/mc/walkforward, C21) subsume standalone over-time comparison. The
// dropped subcommands now fall through to the strict-arg usage error (exit 2),
// before any registry access.
let list = Command::new(BIN).args(["runs", "list"]).output().expect("spawn list");
assert_eq!(list.status.code(), Some(2), "retired `runs list` must exit 2: {:?}", list.status);
let rank = Command::new(BIN).args(["runs", "rank", "total_pips"]).output().expect("spawn rank");
assert_eq!(rank.status.code(), Some(2), "retired `runs rank` must exit 2: {:?}", rank.status);
}
#[test]
fn runs_bare_subcommand_is_strict_and_exits_two() {
// `aura runs` with no/unknown sub-subcommand is a usage error (#16 strict).
let out = Command::new(BIN).arg("runs").output().expect("spawn aura runs");
assert_eq!(out.status.code(), Some(2), "exit status: {:?}", out.status);
}
// Note: the family-trace-persistence tests deleted above (mc/sweep/walkforward
// per-member trace dirs, the momentum bool-param dir/gate proofs, the sweep
// determinism pin) are the Step 3 PIP-surface deletions — same underlying
// cause as the collateral note above (no blueprint-mode arm persists
// per-member traces).
// --- Real-data family path (#106, plan 0060 Task 5) -------------------------
//
// These drive `aura sweep|walkforward --real <SYMBOL>` end to end over the local
// data-server archive. The gated tests need the Pepperstone M1 archive present;
// they skip (print + pass) on a data-less machine, the project's
// skip-on-no-data convention. The pip-refusal test is NOT gated: a symbol with no
// recorded geometry is rejected before any data access.
// EURUSD 2024-06 (UTC inclusive ms): a bounded recorded-sidecar real window.
const EURUSD_JUN2024_FROM_MS: i64 = 1_717_200_000_000;
const EURUSD_JUN2024_TO_MS: i64 = 1_719_791_999_999;
fn local_data_present() -> bool {
std::path::Path::new(data_server::DEFAULT_DATA_PATH).is_dir()
}
/// Property: the per-instrument pip refusal fires for `aura sweep --real
/// <no-geometry symbol>` exactly as it does for `aura run --real` — a symbol with no
/// recorded geometry (`NONEXISTENT`, no sidecar on any host) is rejected with exit 1
/// and the "no recorded geometry" message BEFORE any bar-data access. NOT gated: the
/// refusal precedes the archive entirely, so it is CI-safe and runs on every machine.
/// (#159 cut 4: `sweep` is blueprint-only now, so the fixture supplies a valid
/// blueprint + axis to clear the usage grammar before the geometry refusal fires.)
#[test]
fn sweep_real_no_geometry_symbol_refuses_with_exit_1() {
// not gated: the pip refusal happens before any data access. Ported onto
// a one-cell campaign document — a #272 contained fault (exit 3), not the
// retired verb's hard exit 1 (see the `run_real_no_geometry_*` siblings'
// doc comments for the rationale).
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "sweep-geo-seed");
let proc_id = register_process_doc(&dir, "sweepgeo.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("sweepgeo", "NONEXISTENT", &bp_id, &proc_id, (1, 2), "2, 4", "8");
write_doc(&dir, "sweepgeo.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "sweepgeo.campaign.json"]);
assert_eq!(code, Some(3), "a contained no-geometry cell fault is exit 3: {out}");
assert!(out.contains("no recorded geometry"), "out: {out}");
}
// GER40 Sept-2024 window (inclusive Unix-ms) — the same gated calendar month
// used by `run_real_sidecar_index_pip_reaches_the_emitted_manifest` and the
// campaign-path e2e in `research_docs.rs`.
const GER40_SEPT2024_FROM_MS: &str = "1725148800000";
const GER40_SEPT2024_TO_MS: &str = "1727740799999";
/// Property (#242): a `--real` window that holds no bars for a symbol that IS in
/// the archive refuses with a WINDOW-aware message — distinct from the genuinely
/// unknown-symbol refusal. GER40 is present (the covered Sept-2024 window runs),
/// so an *inverted* window (`--from` > `--to`) is empty by construction for any
/// archive — no bar can satisfy `from <= t <= to` — yet the symbol, its geometry,
/// and the month's file all exist. The refusal must name the requested window
/// rather than reuse the symbol-absence message "no local data for symbol", which
/// misattributes an empty WINDOW to a missing SYMBOL. Gated on the local archive;
/// skips cleanly when absent (the project's skip-on-no-data convention), so CI
/// without the archive stays green.
#[test]
fn run_real_empty_window_refusal_names_the_window_not_the_symbol() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
// Ported onto a one-cell campaign document (#319 Task 8): a campaign
// document's window is INTRINSICALLY validated `from_ms < to_ms` (a
// hand-authored inverted window refuses at document validation, before
// ever reaching the data layer — a stricter, earlier gate than the
// retired verb had), so the empty-window property is reached instead with
// a validly-ordered window that simply predates any archive coverage
// (epoch-ms [1, 2], the same "no data yet" window the geometry-refusal
// siblings use for an unknown symbol) — GER40 IS present (has recorded
// geometry), so this reaches the DATA layer's own "no data in window"
// fault, distinct from the geometry-refusal path above. A #272 contained
// fault (exit 3), not the retired verb's hard exit 1.
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "emptywin-seed");
let proc_id = register_process_doc(&dir, "emptywin.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("emptywin", "GER40", &bp_id, &proc_id, (1, 2), "2", "8");
write_doc(&dir, "emptywin.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "emptywin.campaign.json"]);
assert_eq!(code, Some(3), "a contained no-data-in-window cell fault is exit 3: {out}");
// The distinction (#242): the empty-window refusal NAMES the window and does
// NOT reuse the wholesale symbol-absence message for a symbol that is present.
assert!(
out.to_lowercase().contains("window"),
"empty-window refusal must name the requested window (GER40 IS present), got: {out}"
);
assert!(
!out.contains("no local data for symbol"),
"empty-window refusal must not reuse the symbol-absence message, got: {out}"
);
}
/// Characterization pin (grounding-check ratification for the risk-regime-axis
/// spec): the dissolved real-data sweep member manifest stamps its resolved stop
/// (`stop_length`/`stop_k`) — the default regime here. The R-path stop knobs are
/// bound via `run_blueprint_member`'s `stop` param and stamped into the manifest
/// alongside the swept signal axes; the sibling
/// `sweep_real_blueprint_member_lines_pin_the_dissolved_contract` locates each
/// binding with `find` and so never pins the exact resolved VALUES this test
/// checks. Gated on the local GER40 archive; skips cleanly when absent.
#[test]
fn sweep_real_blueprint_member_manifest_stamps_the_default_stop() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "nostop-seed");
let proc_id = register_process_doc(&dir, "nostop.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"nostop", "GER40", &bp_id, &proc_id,
(GER40_SEPT2024_FROM_MS.parse().unwrap(), GER40_SEPT2024_TO_MS.parse().unwrap()),
"2, 4", "8",
);
write_doc(&dir, "nostop.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "nostop.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<serde_json::Value> = out
.lines()
.filter(|l| l.starts_with("{\"family_id\":"))
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
.collect();
assert_eq!(lines.len(), 2, "one member line per grid point: {out}");
for v in &lines {
let params = v["report"]["manifest"]["params"]
.as_array()
.expect("manifest.params is an array");
let get = |name: &str| params.iter().find(|p| p[0].as_str() == Some(name));
assert_eq!(get("stop_length").and_then(|p| p[1]["I64"].as_i64()), Some(3),
"the resolved default stop length is stamped: {v}");
assert_eq!(get("stop_k").and_then(|p| p[1]["F64"].as_f64()), Some(2.0),
"the resolved default stop k is stamped: {v}");
}
}
/// Property (#218 migration follow-up): running a dissolved `sweep` inside a
/// project (`fresh_project()`, the fixture this migration introduced) stamps
/// `report.manifest.project` on every member with the project's audit-trail
/// provenance (C18/C16) — the `demo` namespace and a well-formed 64-hex-char
/// dylib sha256 — rather than the `None` a bare-cwd run (no `Aura.toml`)
/// produces. Before this migration no cli_run.rs sweep test ran inside a real
/// project, so this stamping path was never exercised end-to-end. Gated on the
/// local GER40 archive; skips cleanly when absent.
#[test]
fn sweep_real_blueprint_member_manifest_stamps_project_provenance() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "provenance-seed");
let proc_id = register_process_doc(&dir, "provenance.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"provenance", "GER40", &bp_id, &proc_id,
(GER40_SEPT2024_FROM_MS.parse().unwrap(), GER40_SEPT2024_TO_MS.parse().unwrap()),
"2, 4", "8",
);
write_doc(&dir, "provenance.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "provenance.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<serde_json::Value> = out
.lines()
.filter(|l| l.starts_with("{\"family_id\":"))
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
.collect();
assert_eq!(lines.len(), 2, "one member line per grid point: {out}");
for v in &lines {
let project = &v["report"]["manifest"]["project"];
assert_eq!(
project["namespace"].as_str(), Some("demo"),
"member manifest carries the fixture project's namespace: {v}"
);
let sha = project["dylib_sha256"].as_str().unwrap_or_else(|| panic!("dylib_sha256 present: {v}"));
assert_eq!(sha.len(), 64, "dylib_sha256 is a full sha256 hex digest: {v}");
assert!(sha.chars().all(|c| c.is_ascii_hexdigit()), "dylib_sha256 is hex: {v}");
}
}
/// Acceptance (harness-input-binding spec, #231): an OHLC-consuming strategy
/// blueprint runs end to end on real archive data through `aura run` — the
/// role names bind columns, three real sources open in canonical order, the
/// close column feeds broker/executor, and the report carries the R summary.
/// Gated on the local GER40 archive; skips cleanly when absent.
#[test]
fn run_real_ohlc_channel_example_end_to_end() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let fixture = format!("{}/examples/r_channel.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&dir, &fixture, "channel-real-seed");
let proc_id = register_process_doc(&dir, "channelreal.process.json", SWEEP_ONLY_PROCESS_DOC);
// r_channel.json binds `channel_hi.length`/`channel_lo.length`/lags, not
// `fast.length`/`slow.length` (`r_sma.json`'s knobs) — `sweep_campaign_doc`'s
// shape doesn't fit, so build the document directly, one degenerate
// single-value axis (#246 bound-override reopening) over the fixture's
// own `channel_hi.length` at its bound default (a campaign document needs
// >= 1 axis per strategy).
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "channel-real",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_hi.length": {{ "kind": "I64", "values": [3] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&dir, "channelreal.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "channelreal.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out
.lines()
.find(|l| l.starts_with("{\"family_id\":"))
.expect("the one member line");
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
assert!(
v["report"]["manifest"]["topology_hash"].as_str().is_some(),
"report carries the signal hash: {line}"
);
assert!(v["report"]["metrics"]["r"].is_object(), "the R summary is computed: {line}");
}
/// Property (#275, hostless): `run_signal_r`'s migrated production call site
/// (`h.run_bound(key_supply(&binding, sources))`, replacing the old positional
/// `h.run(sources)`) pairs each opened archive column with its declared role
/// **by name**, not by list position, for a genuinely multi-role strategy
/// (r_channel declares three distinct roles — high, low, close). Unlike
/// `run_real_ohlc_channel_example_end_to_end` above (gated on a local GER40
/// mount, which this sandbox does not have), this drives the same fixture over
/// `fresh_project_with_data`'s hermetic synthetic archive, so the by-name-binding
/// regression guard for this call site actually runs on every host, not just a
/// data-ful one. The exact R summary is a characterization pin against the
/// synthetic archive's deterministic price curve: a column swap (e.g. high/low
/// crossed) would change the channel breakout logic and move this number.
#[test]
fn run_real_ohlc_channel_example_end_to_end_hostless() {
let (dir, _g) = fresh_project_with_data();
let fixture = format!("{}/examples/r_channel.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&dir, &fixture, "channel-hostless-seed");
let proc_id = register_process_doc(&dir, "channelhostless.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "channel-hostless",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1707120000000, "to_ms": 1707325200000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_hi.length": {{ "kind": "I64", "values": [3] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&dir, "channelhostless.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "channelhostless.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with("{\"family_id\":")).expect("the one member line");
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
let report = &v["report"];
assert!(
report["manifest"]["topology_hash"].as_str().is_some(),
"report carries the signal hash: {line}"
);
assert_eq!(
report["metrics"]["r"]["n_trades"], serde_json::json!(1),
"characterization pin over the deterministic synthetic archive \
(a high/low/close column mispairing would move this count): {line}"
);
assert_eq!(
report["metrics"]["r"]["expectancy_r"], serde_json::json!(-0.7466116470582123),
"characterization pin over the deterministic synthetic archive \
(a high/low/close column mispairing would move this value): {line}"
);
}
/// The dissolved real-data sweep + reproduce over the OHLC example (#231
/// acceptance), ported onto a one-cell campaign document (#319 Task 8 —
/// `--list-axes` itself is dead flag surface, retired with `sweep`; its
/// twin properties live on in the `aura_sweep_list_axes_*` retire bucket):
/// `exec` runs one member per grid point over three real columns, over the
/// ganged `channel_length` axis (its RAW name, #328), and every member
/// re-derives bit-identically from the store (#229 real-data reproduce, now
/// multi-column). Gated on the local GER40 archive.
#[test]
fn sweep_real_ohlc_channel_members_run_and_reproduce() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_channel_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&dir, &fixture, "chan-seed");
let proc_id = register_process_doc(&dir, "chan.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "chan",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [3, 5] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&dir, "chan.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "chan.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<&str> = out.lines().filter(|l| l.starts_with("{\"family_id\":")).collect();
assert_eq!(lines.len(), 2, "one member line per channel length: {out}");
for line in &lines {
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
assert_eq!(
v["report"]["manifest"]["instrument"].as_str(),
Some("GER40"),
"campaign member manifest stamps the instrument: {line}"
);
}
let family_id = serde_json::from_str::<serde_json::Value>(lines[0])
.expect("first member line parses")["family_id"]
.as_str()
.expect("member line carries family_id")
.to_string();
let (rep_out, rep_code) = run_code_in(&dir, &["reproduce", &family_id]);
assert_eq!(rep_code, Some(0), "reproduce out: {rep_out}");
assert!(
rep_out.contains("reproduced 2/2 members bit-identically"),
"out: {rep_out}"
);
}
/// Property (#275, hostless): `run_blueprint_member`'s migrated production call
/// site (`h.run_bound(key_supply(binding, sources))`) resolves the same
/// role-keyed pairing on every member of a multi-column sweep AND on the
/// independent `reproduce` re-run — a sibling of
/// `sweep_real_ohlc_channel_members_run_and_reproduce` above (gated on a local
/// GER40 mount this sandbox lacks) that drives the identical multi-role fixture
/// over `fresh_project_with_data`'s hermetic synthetic archive, so this call
/// site's by-name-binding regression guard runs on every host. Bit-identical
/// reproduction is the stronger check here: `reproduce` re-derives `binding` and
/// re-opens `sources` independently of the first run, so it can only match if
/// both derivations resolve to the exact same role→column pairing.
#[test]
fn sweep_real_ohlc_channel_members_run_and_reproduce_hostless() {
let (dir, _g) = fresh_project_with_data();
let fixture = format!("{}/tests/fixtures/r_channel_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&dir, &fixture, "chan-hostless-seed");
let proc_id = register_process_doc(&dir, "chanhostless.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = r#"{
"format_version": 1,
"kind": "campaign",
"name": "chan-hostless",
"data": { "instruments": ["SYMA"], "windows": [ { "from_ms": 1707120000000, "to_ms": 1707325200000 } ] },
"strategies": [ { "ref": { "content_id": "__BP__" },
"axes": { "channel_length": { "kind": "I64", "values": [3, 5] } } } ],
"process": { "ref": { "content_id": "__PROC__" } },
"seed": 7,
"presentation": { "persist_taps": [], "emit": ["family_table"] }
}"#
.replace("__BP__", &bp_id)
.replace("__PROC__", &proc_id);
write_doc(&dir, "chanhostless.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "chanhostless.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<&str> = out.lines().filter(|l| l.starts_with("{\"family_id\":")).collect();
assert_eq!(lines.len(), 2, "one member line per channel length: {out}");
for line in &lines {
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
assert_eq!(
v["report"]["manifest"]["instrument"].as_str(),
Some("SYMA"),
"campaign member manifest stamps the instrument: {line}"
);
}
let family_id = serde_json::from_str::<serde_json::Value>(lines[0])
.expect("first member line parses")["family_id"]
.as_str()
.expect("member line carries family_id")
.to_string();
let (rep_out, rep_code) = run_code_in(&dir, &["reproduce", &family_id]);
assert_eq!(rep_code, Some(0), "reproduce out: {rep_out}");
assert!(
rep_out.contains("reproduced 2/2 members bit-identically"),
"out: {rep_out}"
);
}
/// Property, ported onto a hand-authored one-cell campaign document (#319
/// Task 8 — the `--name`-propagation and CLI window-probe/ms<->ns-conversion
/// halves of the original test were the argv-translator's OWN machinery
/// (`translate_sweep`/`campaign_window_ms`), retired with the verb; a
/// hand-authored document has no CLI-side window probe or `--name` to
/// propagate, so those checks do not survive): `exec` prints one member line
/// per grid point, IN AXIS ORDER (odometer: the first document axis varies
/// slowest), each line JSON with a `family_id` and a `report` whose
/// `manifest.params` carries every swept param binding (RAW names, #328) —
/// and **`report.manifest` carries a stamped `instrument` key** (the campaign
/// substrate stamps every member's manifest with the instrument under test,
/// exactly like `campaign_run_real_e2e_sweep_gate_walkforward` in
/// `research_docs.rs`) and the resolved default stop. Also pins that the run
/// persists exactly one new `FamilyKind::Sweep` family with the grid's member
/// count, and that re-`exec`ing the SAME document reproduces identical member
/// reports (C1) while incrementing the family's run-suffix. Gated on the
/// local GER40 archive (the project's skip-on-no-data convention); skips
/// cleanly when absent.
#[test]
fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "dissolved-contract-seed");
let proc_id = register_process_doc(&dir, "brp.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"brp", "GER40", &bp_id, &proc_id,
(GER40_SEPT2024_FROM_MS.parse().unwrap(), GER40_SEPT2024_TO_MS.parse().unwrap()),
"2, 4", "8, 16",
);
write_doc(&dir, "brp.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "brp.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<&str> = out.lines().filter(|l| l.starts_with("{\"family_id\":")).collect();
assert_eq!(lines.len(), 4, "one member line per 2x2 grid point: {out}");
// Odometer order: `fast.length` (first document axis, outer/slowest) x
// `slow.length` (second, inner) -> (2,8),(2,16),(4,8),(4,16).
let expected: [[(&str, i64); 2]; 4] = [
[("fast.length", 2), ("slow.length", 8)],
[("fast.length", 2), ("slow.length", 16)],
[("fast.length", 4), ("slow.length", 8)],
[("fast.length", 4), ("slow.length", 16)],
];
for i in 0..4 {
let line = lines[i];
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
assert!(v["family_id"].as_str().is_some(), "member line carries a family_id: {line}");
let manifest = &v["report"]["manifest"];
assert!(manifest.is_object(), "report.manifest is present: {line}");
let params = manifest["params"].as_array().expect("manifest.params is an array");
for (name, value) in expected[i] {
let bound = params
.iter()
.find(|p| p[0].as_str() == Some(name))
.and_then(|p| p[1]["I64"].as_i64());
assert_eq!(bound, Some(value), "manifest carries the swept binding {name}={value}: {line}");
}
assert_eq!(
manifest["instrument"].as_str(),
Some("GER40"),
"the campaign substrate stamps the instrument: {line}"
);
assert!(
params.iter().any(|p| p[0].as_str() == Some("stop_length"))
&& params.iter().any(|p| p[0].as_str() == Some("stop_k")),
"the campaign substrate stamps the resolved stop: {line}"
);
}
// exactly one new Sweep family persisted, with the grid's four members.
let (fams_out, fams_code) = run_code_in(&dir, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
assert_eq!(fams_out.lines().count(), 1, "exactly one family persisted: {fams_out}");
assert!(fams_out.contains("\"kind\":\"Sweep\""), "families: {fams_out}");
assert!(fams_out.contains("\"members\":4"), "2x2 grid -> four members: {fams_out}");
// Re-exec'ing the SAME document: one campaign document dedupes (content-
// addressed), a fresh family record is written on every execution (the
// "-{run}" suffix, aura-campaign::exec::run_cell), so two identical
// invocations are NOT byte-identical on stdout: only the trailing
// run-suffix (0 -> 1) differs. C1 determinism is checked at the
// member-report level instead (every report field byte-identical), plus
// the run-suffix increment.
let (out2, code2) = run_code_in(&dir, &["exec", "brp.campaign.json"]);
assert_eq!(code2, Some(0), "second run: {out2}");
let lines2: Vec<&str> = out2.lines().filter(|l| l.starts_with("{\"family_id\":")).collect();
assert_eq!(lines2.len(), lines.len(), "same member count on the dedup run: {out2}");
for (l1, l2) in lines.iter().zip(&lines2) {
let v1: serde_json::Value = serde_json::from_str(l1).expect("first run member line parses");
let v2: serde_json::Value = serde_json::from_str(l2).expect("second run member line parses");
assert_eq!(v1["report"], v2["report"], "identical invocations reproduce identical member reports (C1): {l1} vs {l2}");
let (fam1, fam2) = (v1["family_id"].as_str().unwrap(), v2["family_id"].as_str().unwrap());
assert_eq!(
fam1.trim_end_matches(|c: char| c.is_ascii_digit()),
fam2.trim_end_matches(|c: char| c.is_ascii_digit()),
"same family name up to the run-suffix: {fam1} vs {fam2}"
);
assert!(fam1.ends_with("-0") && fam2.ends_with("-1"), "run-suffix increments 0 -> 1: {fam1} vs {fam2}");
}
let campaigns_dir_count = std::fs::read_dir(dir.join("runs").join("campaigns")).map(|d| d.count()).unwrap_or(0);
assert_eq!(campaigns_dir_count, 1, "second identical run dedupes the campaign doc");
}
/// True iff `root` contains, at any depth, a regular file of non-zero length —
/// the layout-agnostic "traces actually landed on disk" probe. #224 does not yet
/// pin the per-member trace-directory naming (the sweep sugar's cell has no
/// nominee and `persist_campaign_traces` keys by `<trace_name>/<cell_key>`), so
/// this walks the tree rather than hard-coding a path, keeping the headline about
/// *that* traces are written, not *where*.
fn any_nonempty_file(root: &Path) -> bool {
let mut stack = vec![root.to_path_buf()];
while let Some(p) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&p) else { continue };
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if std::fs::metadata(&path).map(|m| m.len() > 0).unwrap_or(false) {
return true;
}
}
}
false
}
/// Property (#224): `aura sweep <blueprint.json> --real SYM --axis … --trace <fam>`
/// DELIVERS the advertised trace-writing capability instead of the #168
/// refuse-with-pointer. Two things must hold once the capability ships:
/// (1) the run no longer refuses (`aura: --trace is not yet available on sweep;
/// see #224`, exit 2) but succeeds, and (2) the tap series actually land on disk
/// under `runs/traces/` — at least one non-empty tap file is written, the honest
/// inverse of the #168 no-op where the `--real` sugar set `persist_taps: vec![]`
/// (verb_sugar.rs) and `run_blueprint_sweep` did `let _ = persist`, so nothing was
/// ever written. The exact on-disk layout (trace name + per-member/nominee key)
/// is deliberately NOT pinned here: this headline pins that traces are written at
/// all — not how many dirs, nor under what member key — because that naming is
/// the GREEN slice's open design point (the selection-free sweep cell has no
/// nominee for the existing per-cell `persist_campaign_traces` mechanism, #224).
/// Gated on the local GER40 archive (the project skip-on-no-data convention);
/// the refused status quo makes it RED on a data-ful host today.
#[test]
fn sweep_real_with_trace_writes_tap_series_to_disk() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
// Ported onto a one-cell campaign document declaring `persist_taps`
// (#319 Task 8) — the property under test (tap series actually land on
// disk, the #168/#224 headline) is the campaign substrate's own
// `persist_campaign_traces` mechanism, reached identically whether the
// document was hand-authored or sugar-generated; `--trace`'s own
// refuse-then-deliver history is retired-verb-specific and not re-pinned.
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "trace-seed");
let proc_id = register_process_doc(&dir, "trace.process.json", SWEEP_ONLY_PROCESS_DOC);
// `presentation.persist_taps` names entries from the closed measurement
// vocabulary (equity | exposure | r_equity | net_r_equity), not arbitrary
// blueprint-declared taps (that's `--tap`'s namespace on the blueprint
// leg, invariant 7) — "equity" is always producible for an R strategy.
let campaign_doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "brp",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 4] }},
"slow.length": {{ "kind": "I64", "values": [8] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": ["equity"], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&dir, "brp.campaign.json", &campaign_doc);
let (out, code) = run_code_in(&dir, &["exec", "brp.campaign.json"]);
assert_eq!(code, Some(0), "exec must succeed and persist the requested taps: {out}");
// the tap series actually land on disk under runs/traces/ (non-empty) —
// the honest inverse of the #168 `persist_taps: vec![]` no-op.
let traces_root = dir.join("runs").join("traces");
assert!(
any_nonempty_file(&traces_root),
"the campaign run must write at least one non-empty tap file under {}: {out}",
traces_root.display(),
);
}
/// Extract the first persisted family whose `kind` matches `want` from
/// `aura runs families` stdout (one JSON object per line), returning its
/// `family_id` — the id `aura reproduce` addresses. Panics if none matches so a
/// mis-minted store fails loudly rather than skipping the assertion.
fn family_id_of_kind(dir: &Path, want: &str) -> String {
let out = Command::new(BIN).args(["runs", "families"]).current_dir(dir).output().unwrap();
assert!(out.status.success(), "runs families exit: {:?}", out.status);
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
for line in stdout.lines() {
let v: serde_json::Value = serde_json::from_str(line).expect("family line parses as JSON");
if v["kind"].as_str() == Some(want) {
return v["family_id"].as_str().expect("family_id is a string").to_string();
}
}
panic!("no {want} family in the store: {stdout}");
}
/// Reconstruct the retired `walkforward_summary_json_from_reports`'s
/// aggregate (deleted Task 8 Step 2) from a WalkForward family's OWN
/// persisted per-window `RunReport`s (`aura runs family <id>` bare-report
/// lines) — the SAME reduction, over data the campaign path populates
/// identically: `stitched_total_pips` sums each window's own `total_pips`
/// (additive, roll order irrelevant to a sum); pooled `n_trades` sums each
/// window's own trade count; pooled `expectancy_r` is the trade-count-
/// weighted mean of each window's own `expectancy_r` (mathematically
/// identical to the mean of the raw pooled per-trade R series, which is
/// NOT itself recoverable from JSON — `RMetrics::net_trade_rs` is
/// `#[serde(skip)]`); `param_stability[i].mean` is the arithmetic mean of
/// axis `axes[i]`'s per-window chosen value (`manifest.params`, never
/// skipped).
fn walkforward_aggregate_from_reports(reports: &[serde_json::Value], axes: &[&str]) -> serde_json::Value {
let stitched_total_pips: f64 = reports.iter().map(|r| r["metrics"]["total_pips"].as_f64().unwrap()).sum();
let n_trades: i64 = reports.iter().map(|r| r["metrics"]["r"]["n_trades"].as_i64().unwrap_or(0)).sum();
let weighted_sum: f64 = reports
.iter()
.map(|r| {
let n = r["metrics"]["r"]["n_trades"].as_f64().unwrap_or(0.0);
let e = r["metrics"]["r"]["expectancy_r"].as_f64().unwrap_or(0.0);
n * e
})
.sum();
let expectancy_r = if n_trades > 0 { weighted_sum / n_trades as f64 } else { 0.0 };
let param_stability: Vec<serde_json::Value> = axes
.iter()
.map(|axis| {
let values: Vec<f64> = reports
.iter()
.map(|r| {
let params = r["manifest"]["params"].as_array().expect("manifest.params array");
let entry = params
.iter()
.find(|p| p[0].as_str() == Some(*axis))
.unwrap_or_else(|| panic!("axis {axis} recorded in manifest.params"));
entry[1]["I64"].as_f64().or_else(|| entry[1]["F64"].as_f64()).expect("scalar value")
})
.collect();
let mean = values.iter().sum::<f64>() / values.len() as f64;
serde_json::json!({ "mean": mean })
})
.collect();
serde_json::json!({
"windows": reports.len(),
"stitched_total_pips": stitched_total_pips,
"oos_r": { "n_trades": n_trades, "expectancy_r": expectancy_r },
"param_stability": param_stability,
})
}
/// Property (C18/C1 reproduce guarantee): `aura reproduce` re-derives a family
/// over the SAME data it was minted on. A sweep family minted over real GER40
/// bars must therefore reproduce bit-identically — the run is deterministic
/// (a fresh identical sweep matches the persisted metrics to the last digit), so
/// the README's "check it is bit-identical" promise must hold. It must NOT
/// silently re-run every member over the built-in synthetic showcase stream,
/// which diverges from the stored real-data metrics on every member (observed:
/// `reproduced 0/N bit-identically`, exit 1). Gated on the local GER40 archive
/// (skip-on-no-data convention); uses the plain `r_sma.json` blueprint —
/// no gang, no campaign-specific fixture — because the divergence is driven by
/// `--real`, not by the minting path.
#[test]
fn reproduce_real_sweep_family_re_derives_bit_identically() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "repro-sweep-seed");
let proc_id = register_process_doc(&dir, "reprosweep.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"repro_sweep", "GER40", &bp_id, &proc_id,
(GER40_SEPT2024_FROM_MS.parse().unwrap(), GER40_SEPT2024_TO_MS.parse().unwrap()),
"2, 4", "8",
);
write_doc(&dir, "reprosweep.campaign.json", &doc);
let (mint_out, mint_code) = run_code_in(&dir, &["exec", "reprosweep.campaign.json"]);
assert_eq!(mint_code, Some(0), "sweep mint: {mint_out}");
let id = family_id_of_kind(&dir, "Sweep");
let (out, code) = run_code_in(&dir, &["reproduce", &id]);
assert_eq!(
code, Some(0),
"reproduce of a real-data sweep family must succeed (exit 0): {out}"
);
assert!(
out.contains("reproduced 2/2 members bit-identically"),
"every member of a deterministic real-data sweep re-derives bit-identically: {out}"
);
assert!(
!out.contains("DIVERGED"),
"no member may DIVERGE — the run is deterministic, reproduce must use the recorded data source: {out}"
);
}
/// Property (C18/C1 reproduce guarantee, #246): the same reproduce guarantee holds
/// when the swept axis names a BOUND param of a CLOSED starter blueprint, not an
/// already-open one — `r_sma.json` (both `fast.length` and `slow.length` bound),
/// overriding only the `fast.length` axis and leaving `slow.length` bound —
/// unlike the walk-forward reproduce sibling
/// (`aura_walkforward_over_a_blueprint_reproduces_bit_identically`), which
/// overrides both axes over the same closed blueprint. The walk-forward
/// synthetic route's own `--axis` intake is now RAW (this cycle's tidy fix,
/// #328): the CLI preflight refuses a wrapped name before this run ever starts
/// (`refuse_wrapped_synthetic_axes`), and `blueprint_sweep_over`'s own
/// `SweepBinder` call translates the RAW name onto its wrapped `param_space()`
/// slot (`wrapped_name_of`) before keying by it.
/// Modelled on that sibling (synthetic in-process walk-forward — no `--real`),
/// NOT on `reproduce_real_sweep_family_re_derives_bit_identically`: a real-data
/// mint routes through the campaign executor (`CliMemberRunner::run_member` +
/// `validate_and_register_axes`), neither of which carries any #246 override
/// threading — a gap outside this task's file scope (`blueprint_sweep_over`,
/// `reproduce_family_in`) and spanning all four dispatch verbs, so exercising
/// it here would silently expand into unrelated, unreviewed surface. The
/// synthetic walk-forward path exercises exactly the threaded functions:
/// `blueprint_sweep_over` (via `blueprint_walkforward_family`) re-opens
/// `fast.length` for the per-window IS re-fit, and
/// `reproduce_family_in` re-opens it again from the recorded manifest params
/// so the re-derivation resolves against the same space the mint used.
#[test]
fn reproduce_family_with_overridden_bound_param_re_derives() {
// Ported onto a `[std::grid, std::walk_forward]` campaign document over
// the hermetic synthetic SYMA archive (#319 Task 8) — there is no
// surviving campaign-document equivalent of "sweep the embedded showcase
// stream with no instrument at all" (a `CampaignDoc` always names a real
// `data.instruments` entry, invariant 7), so the property (reproduce
// re-derives a WalkForward family minted by overriding only ONE bound
// param, `slow.length` left at its blueprint-bound default) is exercised
// over SYMA instead. The exact OOS member count is roller/data-derived,
// not hardcoded — this document's window/roller shape is the same one
// `exec.rs`'s zero-trade-note tests already prove out end to end, so the
// member count is asserted structurally (> 0, matches the family listing)
// rather than pinned to a translator-era literal.
let (dir, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&dir, "wfo-seed");
let proc_id = register_process_doc(&dir, "wfo.process.json", GRID_THEN_WF_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfo",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 3] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&dir, "wfo.campaign.json", &doc);
let (run_out, run_code) = run_code_in(&dir, &["exec", "wfo.campaign.json"]);
assert_eq!(
run_code, Some(0),
"walk-forward over a bound-param axis must succeed (#246 re-open): {run_out}"
);
let member_count = run_out.lines().filter(|l| l.starts_with("{\"family_id\":")).count();
assert!(member_count > 0, "at least one OOS member: {run_out}");
let id = family_id_of_kind(&dir, "WalkForward");
let (out, code) = run_code_in(&dir, &["reproduce", &id]);
assert_eq!(code, Some(0), "reproduce: {out}");
assert!(
out.contains(&format!("reproduced {member_count}/{member_count} members bit-identically")),
"every OOS member of a deterministic walk-forward over a bound-param axis \
re-derives bit-identically: {out}"
);
}
/// Property (#246, distinct code path from `reproduce_family_with_overridden_bound_param_re_derives`):
/// `reproduce_family_in`'s override derivation (`wrapped_bound_overrides_of`, computed once
/// per family from the FIRST member's recorded params) applies uniformly across
/// `FamilyKind`s, not only `WalkForward` — a plain `aura sweep` family minted by
/// overriding a bound param on the CLOSED `r_sma.json` fixture re-derives
/// bit-identically too. The sibling test exercises `FamilyKind::WalkForward`'s
/// branch (per-window OOS reload via `run_oos_blueprint`); this one exercises the
/// `Sweep`-kind branch (`data.run_sources`/`data.full_window`, no per-window
/// re-fit) of the same shared dispatch in `reproduce_family_in`. A regression that
/// threaded the #246 override set through one branch but not the other would pass
/// the walk-forward sibling while failing here.
#[test]
fn aura_reproduce_re_derives_a_sweep_family_minted_over_a_bound_param_override() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8, same
// substitution rationale as the walk-forward sibling above): the member
// count (2) is structurally guaranteed by the axis grid size, not by the
// data source, so it stays pinned exactly.
let (dir, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&dir, "swo-seed");
let proc_id = register_process_doc(&dir, "swo.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "swo",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 3] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&dir, "swo.campaign.json", &doc);
let (sweep_out, sweep_code) = run_code_in(&dir, &["exec", "swo.campaign.json"]);
assert_eq!(
sweep_code, Some(0),
"sweeping a bound param of a closed blueprint must succeed (#246 re-open): {sweep_out}"
);
let id = family_id_of_kind(&dir, "Sweep");
let (out, code) = run_code_in(&dir, &["reproduce", &id]);
assert_eq!(code, Some(0), "reproduce: {out}");
assert!(
out.contains("reproduced 2/2 members bit-identically"),
"every member of a plain sweep minted over a bound-param override \
re-derives bit-identically: {out}"
);
}
/// Property: `aura reproduce` yields a handled outcome — a reproduction verdict
/// or a clean `aura:` refusal — for a real-data WalkForward family, and NEVER an
/// internal panic. A WalkForward member's OOS window is stored as real epoch-ns
/// bounds; reconstructing that window over the built-in synthetic walk (whose 60
/// bars fall entirely outside those bounds) yields an empty source, which today
/// reaches the unguarded `window_of(&s).expect("non-empty OOS window")` and
/// aborts the process (exit 101). Gated on the local GER40 archive
/// (skip-on-no-data convention); a ~135-day window is the minimum that stitches
/// at least one OOS window (IS 90d + OOS 30d).
#[test]
fn reproduce_real_walkforward_family_does_not_panic() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
// Ported onto a `[std::grid, std::walk_forward]` campaign document
// (#319 Task 8), the fixed 14d/7d/7d roller `exec.rs`/`research_docs.rs`
// already prove out end to end. 2025-01-01 .. ~2025-05-16 (~135 days) —
// long enough for one IS(90d)+OOS(30d)-equivalent walk-forward window
// over the local GER40 archive.
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "repro-wf-seed");
let proc_id = register_process_doc(&dir, "reprowf.process.json", GRID_THEN_WF_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "repro_wf",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735689600000, "to_ms": 1747353600000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 4] }},
"slow.length": {{ "kind": "I64", "values": [8] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&dir, "reprowf.campaign.json", &doc);
let (mint_out, mint_code) = run_code_in(&dir, &["exec", "reprowf.campaign.json"]);
assert_eq!(mint_code, Some(0), "walkforward mint: {mint_out}");
let id = family_id_of_kind(&dir, "WalkForward");
let (out, code) = run_code_in(&dir, &["reproduce", &id]);
assert_ne!(
code, Some(101),
"reproduce of a WalkForward family must not panic (exit 101): {out}"
);
assert!(
!out.contains("panicked") && !out.contains("non-empty OOS window"),
"no internal panic may leak from reproduce: {out}"
);
assert!(
out.contains("reproduced") || out.contains("aura:"),
"reproduce must yield a verdict or a clean `aura:` refusal: {out}"
);
}
// Fixture blueprint for the verb-level multi-column binding coverage below
// (#231 task 4 quality follow-up): an OPEN high/low blueprint with two
// SMA-length axes. Written to a fresh temp file per test rather than a
// tracked `examples/` fixture — exists only to drive the real-open contract,
// not as an authoring sample. (`HL_RANGE_CLOSED_BLUEPRINT`, the zero-param
// closed sibling, was retired #319 Task 8 — a campaign document needs >= 1
// axis per strategy, invariant no zero-param fixture can satisfy, so the
// real-data multi-column single-run property was re-pointed onto this
// fixture at a single axis value instead.)
const HL_SIGNAL_OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"hl_signal","doc":"high/low SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
// Note (#319 Task 8, disposition deviation): `walkforward_synthetic_refuses_a_
// multi_column_blueprint_before_data_access` (the synthetic, no-`--real`
// exit-process arm) retired rather than ported — it is a byte-identical twin
// of `exec.rs::exec_refuses_a_multi_column_blueprint_before_data_access`
// (same "consumes columns beyond close" prose, same data-free-before-access
// property, same closed-form fixture family): the guard fires on column
// consumption alone, independent of whether the blueprint carries an open
// axis, so an OPEN-blueprint-with-an-axis-grid variant exercises no
// additional surface over the already-green CLOSED-blueprint pin.
/// Property (#231 task 4 quality follow-up): the positive counterpart of the
/// two refusals above — a multi-column blueprint over `--real GER40` opens
/// BOTH the high and low columns end to end (`open_real_source` ->
/// `resolve_run_data` -> `run_signal_r`) and produces a real `RunReport`,
/// never the single-close weld the pre-task-4 code hardcoded. Gated on the
/// local GER40 archive (skip-on-no-data convention).
#[test]
fn run_real_multi_column_blueprint_opens_high_and_low_columns() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
// Ported onto a one-cell campaign document (#319 Task 8 — exec's
// blueprint leg has no `--real` at all, invariant 7). `HL_RANGE_CLOSED_
// BLUEPRINT` (the bare zero-param `Sub` node) cannot host a campaign
// document at all — a campaign's `strategies[].axes` map must carry >= 1
// axis (an intrinsic document-shape invariant, `validate_campaign`), and
// that fixture has no param of any kind to axis over — so this reuses
// `HL_SIGNAL_OPEN_BLUEPRINT` (the sweep sibling's fixture, below) at a
// single axis value, preserving the "opens both high and low columns"
// property exactly.
let (dir, _g) = fresh_project();
let fixture_path = dir.join("hl_signal.json");
std::fs::write(&fixture_path, HL_SIGNAL_OPEN_BLUEPRINT).expect("write fixture");
let bp_id = seed_blueprint_file(&dir, fixture_path.to_str().unwrap(), "hlsignal-single-seed");
let proc_id = register_process_doc(&dir, "hlsignal_single.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "hlsignal-single",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2] }},
"slow.length": {{ "kind": "I64", "values": [8] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&dir, "hlsignal_single.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "hlsignal_single.campaign.json"]);
assert_eq!(
code, Some(0),
"a high/low blueprint over real GER40 data must run cleanly: {out}"
);
assert!(
out.lines().any(|l| l.starts_with("{\"family_id\":")),
"exit 0 must carry a member report: {out}"
);
}
/// Property (#231 task 4, "every real-data path" — `run --real` above covers
/// only `open_real_source`/`run_signal_r`; `sweep --real` walks a DISTINCT
/// real-data path, `blueprint_sweep_family` -> `blueprint_sweep_over` ->
/// `DataSource::windowed_sources`, which independently needed `binding.
/// columns()` threaded through in task 4): a multi-column (high/low) OPEN
/// blueprint swept over `--real GER40` opens both columns per member and
/// produces one member line per grid point, not a source-count-mismatch panic
/// or a fallback to a single close source. Gated on the local GER40 archive.
#[test]
fn sweep_real_multi_column_blueprint_opens_high_and_low_columns() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let fixture_path = dir.join("hl_signal.json");
std::fs::write(&fixture_path, HL_SIGNAL_OPEN_BLUEPRINT).expect("write fixture");
let bp_id = seed_blueprint_file(&dir, fixture_path.to_str().unwrap(), "hlsignal-sweep-seed");
let proc_id = register_process_doc(&dir, "hlsignal_sweep.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"hlsignal-sweep", "GER40", &bp_id, &proc_id,
(GER40_SEPT2024_FROM_MS.parse().unwrap(), GER40_SEPT2024_TO_MS.parse().unwrap()),
"2, 4", "8",
);
write_doc(&dir, "hlsignal_sweep.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "hlsignal_sweep.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let lines: Vec<&str> = out.lines().filter(|l| l.starts_with("{\"family_id\":")).collect();
assert_eq!(lines.len(), 2, "one member line per grid point: {out}");
for line in &lines {
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
assert!(
v["report"]["manifest"].is_object(),
"each member carries a real report, not a source-mismatch fault: {line}"
);
}
}
/// Property (issue #246, task 5): the dissolved real-data `aura sweep`
/// campaign path accepts an `--axis` naming a BOUND param of a CLOSED
/// blueprint, not only an already-open one. Tasks 3-4 threaded this override
/// convention (bound value = default, `Composite::reopen`) through the
/// SYNTHETIC blueprint verb paths (`blueprint_sweep_family`/
/// `blueprint_sweep_over`, `reproduce_family_with_overridden_bound_param_re_derives`);
/// the real-data dissolved path routes through a distinct seam
/// (`CliMemberRunner::run_member` + `verb_sugar::validate_before_register`'s
/// P3 preflight) that carried no such threading at all, the explicit gap
/// named on `reproduce_family_with_overridden_bound_param_re_derives`'s doc
/// comment. Modelled on `sweep_real_blueprint_member_lines_pin_the_dissolved_contract`
/// (the dissolved-sweep e2e family `walkforward_dissolved_refuses_an_unknown_axis_name`
/// belongs to): same store-reading assertions (`aura runs families`, one
/// persisted `Sweep` family, member count), over the same `r_sma.json`
/// fixture every other real-data sweep pin uses (`fast.length`/
/// `slow.length` both bound) — the distinguishing property here
/// is that the swept axis names an already-bound param, not that the
/// fixture differs.
#[test]
fn sweep_dissolved_accepts_an_axis_over_a_bound_param() {
if !local_data_present() {
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
return;
}
let (dir, _g) = fresh_project();
let bp_id = seed_blueprint(&dir, "bound-override-seed");
let proc_id = register_process_doc(&dir, "boundoverride.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "bound-override",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 3] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&dir, "boundoverride.campaign.json", &doc);
let (out, code) = run_code_in(&dir, &["exec", "boundoverride.campaign.json"]);
assert_eq!(
code, Some(0),
"a bound-param axis must re-open (#246) on the dissolved real-data campaign path too: {out}"
);
let (fams_out, fams_code) = run_code_in(&dir, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
assert_eq!(fams_out.lines().count(), 1, "exactly one family persisted: {fams_out}");
assert!(fams_out.contains("\"kind\":\"Sweep\""), "families: {fams_out}");
assert!(
fams_out.contains("\"members\":2"),
"the two-value bound-param axis grid yields two members: {fams_out}"
);
}
/// Property (#246 override merge, exercised at scale by #248's open-twins
/// migration), ported onto one-cell campaign documents over the hermetic
/// synthetic SYMA archive (#319 Task 8 — there is no campaign-document
/// equivalent of "sweep the embedded showcase stream", invariant 7):
/// sweeping the CLOSED `examples/r_sma.json` (both SMA lengths bound to
/// defaults 2/4) with axis values that override BOTH bound lengths yields
/// BIT-IDENTICAL per-member `total_pips` to sweeping the historical OPEN
/// `tests/fixtures/r_sma_open.json` (both lengths genuinely unbound) over the
/// same axis values and the same data. #248's migration replaced the "bind an
/// open param" idiom with "override a bound param" at dozens of call sites;
/// if the override merge ever diverged from the historical bind path, every
/// migrated seed/grade site would silently compile a DIFFERENT graph while
/// still exiting 0 — no other test cross-checks the two idioms against each
/// other.
#[test]
fn sweep_over_a_bound_override_matches_the_historical_open_bind() {
let (closed_dir, _g1) = fresh_project_with_data();
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let closed_bp_id = seed_blueprint_file(&closed_dir, &closed_bp, "closed-seed");
let closed_proc_id = register_process_doc(&closed_dir, "closed.process.json", SWEEP_ONLY_PROCESS_DOC);
let closed_doc = sweep_campaign_doc(
"closed", "SYMA", &closed_bp_id, &closed_proc_id,
(1709251200000, 1719791999999), "2, 4", "8, 16",
);
write_doc(&closed_dir, "closed.campaign.json", &closed_doc);
let (closed_out, closed_code) = run_code_in(&closed_dir, &["exec", "closed.campaign.json"]);
assert_eq!(closed_code, Some(0), "closed sweep: {closed_out}");
let (open_dir, _g2) = fresh_project_with_data();
let open_bp = format!("{}/tests/fixtures/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let open_bp_id = seed_blueprint_file(&open_dir, &open_bp, "open-seed");
let open_proc_id = register_process_doc(&open_dir, "open.process.json", SWEEP_ONLY_PROCESS_DOC);
let open_doc = sweep_campaign_doc(
"open", "SYMA", &open_bp_id, &open_proc_id,
(1709251200000, 1719791999999), "2, 4", "8, 16",
);
write_doc(&open_dir, "open.campaign.json", &open_doc);
let (open_out, open_code) = run_code_in(&open_dir, &["exec", "open.campaign.json"]);
assert_eq!(open_code, Some(0), "open sweep: {open_out}");
let pips = |out: &str| -> Vec<f64> {
out.lines()
.filter(|l| l.starts_with("{\"family_id\":"))
.map(|l| {
let v: serde_json::Value = serde_json::from_str(l).expect("member line parses");
v["report"]["metrics"]["total_pips"].as_f64().expect("total_pips is an f64")
})
.collect()
};
let mut closed_pips = pips(&closed_out);
let mut open_pips = pips(&open_out);
assert_eq!(closed_pips.len(), 4, "2x2 grid -> four members: {closed_out}");
assert_eq!(open_pips.len(), 4, "2x2 grid -> four members: {open_out}");
closed_pips.sort_by(|a, b| a.partial_cmp(b).unwrap());
open_pips.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(
closed_pips, open_pips,
"bound-override and historical open-bind must compute bit-identical \
member metrics for the same axis grid"
);
}
// Note (#159 cut 4 collateral): this removes the `aura chart <name>`
// CLI-integration tests (single-run and family overlay/tap-filter/meta, the
// cross-kind write-guard collision + same-kind-overwrite pair) — every one of
// them populated its `TraceStore` fixture via a bare `run --trace`/`sweep
// --trace` invocation, both retired. The only surviving `TraceStore::write`
// call site left in the whole crate is `campaign_run::persist_campaign_traces`
// (behind a campaign's `--persist-taps`) — an entirely different, much
// heavier fixture than any of these tests set up. Removed as PIP-retirement
// collateral, not
// repointed; the render/build logic they exercised stays covered by the
// hand-built-fixture unit tests (`build_chart_data_threads_run_manifest_into_meta`
// here, `render_chart_html_*` in render.rs), which never touch the CLI or disk.
// --- `aura run --harness <name>` selector (iter-3 Task 3) --------------------
/// Property: `aura generalize` grades a single r-sma candidate across two
/// instruments — its stdout carries the per-instrument breakdown + the worst-case
/// floor + the sign-agreement, then the persisted CrossInstrument family handle.
/// Gated on local GER40/USDJPY data (the shared Sept-2024 window), skips cleanly
/// when the archive is absent.
#[test]
fn generalize_grades_a_candidate_across_two_instruments() {
// Ported onto a `[std::sweep, std::generalize]` campaign document
// (#319 Task 8) — the grade is the campaign's own native
// `generalizations[].generalization` record, byte-identical in
// computation to the retired `generalize_json` CLI wrapper's field set.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "generalize-shape-seed");
let proc_id = register_process_doc(&cwd, "gen.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "gen",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "gen.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "gen.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let grade = &v["campaign_run"]["generalizations"][0]["generalization"];
assert!(grade.is_object(), "aggregate object: {record_line}");
assert_eq!(grade["n_instruments"].as_u64(), Some(2), "two instruments: {record_line}");
assert!(grade["worst_case"].is_number(), "worst_case present: {record_line}");
assert!(grade["sign_agreement"].is_number(), "sign_agreement present: {record_line}");
let per = grade["per_instrument"].as_array().expect("per_instrument array");
let instruments: Vec<&str> = per.iter().map(|p| p[0].as_str().unwrap()).collect();
assert!(instruments.contains(&"GER40") && instruments.contains(&"USDJPY"), "per-instrument breakdown: {record_line}");
}
/// Characterization pin (byte-identity anchor for the generalize dissolution,
/// #210), ported onto a `[std::sweep, std::generalize]` campaign document
/// (#319 Task 8): the current `aura generalize` bound its protective stop as
/// a grid axis; the dissolution rebinds the same stop through the
/// risk-regime seam (`RiskRegime::Vol -> StopRule::Vol`) — this pins the
/// EXACT R grade of the identical (blueprint, instruments, axes, stop,
/// window) coordinate, now realized through the campaign's own
/// `generalizations[].generalization` record rather than the retired CLI's
/// `generalize_json` wrapper. Gated on the shared GER40/USDJPY Sept-2024
/// archive; skips cleanly on a data refusal.
#[test]
fn generalize_real_e2e_pins_the_exact_current_grade() {
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "generalize-pin-seed");
let proc_id = register_process_doc(&cwd, "genpin.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "genpin",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "genpin.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "genpin.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let g = &v["campaign_run"]["generalizations"][0]["generalization"];
assert_eq!(g["selection_metric"].as_str(), Some("expectancy_r"), "metric: {record_line}");
assert_eq!(g["n_instruments"].as_u64(), Some(2), "two instruments: {record_line}");
assert_eq!(g["sign_agreement"].as_u64(), Some(2), "both agree in sign: {record_line}");
// EXACT R floats — the byte-identity anchor the dissolution must preserve.
assert_eq!(g["worst_case"].as_f64(), Some(0.005795903617609842), "worst-case R floor: {record_line}");
let per = g["per_instrument"].as_array().expect("per_instrument is an array");
assert_eq!(per.len(), 2, "two per-instrument grades: {record_line}");
assert_eq!(per[0][0].as_str(), Some("GER40"), "first instrument: {record_line}");
assert_eq!(per[0][1].as_f64(), Some(0.01056371324510624), "GER40 expectancy R: {record_line}");
assert_eq!(per[1][0].as_str(), Some("USDJPY"), "second instrument: {record_line}");
assert_eq!(per[1][1].as_f64(), Some(0.005795903617609842), "USDJPY expectancy R: {record_line}");
}
/// Property (#220 generalize vertical): `aura generalize` grades ANY sweepable
/// blueprint, not just the historical r-sma candidate — the whole point of
/// dropping `--strategy r-sma`/`--fast`/`--slow` for a positional blueprint +
/// generic `--axis`. Every other generalize E2E test here still exercises
/// `r_sma.json`, so a regression that silently special-cased r-sma paths
/// back in (e.g. resolving axes against a hardcoded `sma_signal.*` namespace)
/// would ship green there while breaking every other blueprint. This runs the
/// unrelated r-breakout candidate (the ganged `channel_length` axis, no
/// `sma_signal` node in sight) end-to-end and asserts a well-formed two-
/// instrument grade. Gated on the shared GER40/USDJPY Sept-2024 archive; skips
/// cleanly on a data refusal.
#[test]
fn generalize_grades_a_non_r_sma_blueprint() {
// Ported onto a `[std::sweep, std::generalize]` campaign document
// (#319 Task 8), same shape as `generalize_grades_a_candidate_across_
// two_instruments` but over the unrelated r-breakout candidate.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "breakout-gen-seed");
let proc_id = register_process_doc(&cwd, "breakoutgen.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "breakout-gen",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "breakoutgen.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "breakoutgen.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let grade = &v["campaign_run"]["generalizations"][0]["generalization"];
assert!(grade.is_object(), "aggregate object: {record_line}");
assert_eq!(grade["n_instruments"].as_u64(), Some(2), "two instruments: {record_line}");
assert!(grade["worst_case"].is_number(), "worst_case present: {record_line}");
assert!(grade["sign_agreement"].is_number(), "sign_agreement present: {record_line}");
let per = grade["per_instrument"].as_array().expect("per_instrument array");
let instruments: Vec<&str> = per.iter().map(|p| p[0].as_str().unwrap()).collect();
assert!(instruments.contains(&"GER40") && instruments.contains(&"USDJPY"), "per-instrument breakdown: {record_line}");
}
/// Characterization pin (byte-identity anchor for the walkforward dissolution,
/// #210). The current `aura walkforward` runs its per-window IS-refit inline
/// (`walkforward_family` -> `select_winner` -> OOS run -> stitch/pool); the
/// dissolution reroutes the same rolling IS-sweep -> OOS-run -> deflation through
/// the campaign `std::walk_forward` stage, and must retain the OOS pip curve the
/// campaign runner currently discards (`oos_equity: vec![]`) to reproduce
/// `stitched_total_pips`, plus reconstruct pooled `oos_r`. The multi-point grid
/// (fast 3,5 x slow 12,20) makes the per-window winner selection non-degenerate --
/// `param_stability` varies across windows -- so a winner-pick divergence between
/// the inline and campaign refit cannot ship green. This pins the EXACT current
/// grade of the identical invocation over a fixed 2025 GER40 window; after the
/// dissolution the same command must reproduce these bytes (the acceptance gate).
/// Gated on the shared GER40 archive; skips cleanly on a data refusal.
#[test]
fn walkforward_real_e2e_pins_the_exact_current_grade() {
// Ported onto a `[std::grid, std::walk_forward]` campaign document
// (#319 Task 8): the fixed real-archive roller (`aura_backtest::
// wf_ms_sizes`, 90d IS / 30d OOS / 30d step — the retired sugar's
// `fit_wf_ms_sizes` fast-path, since this ~1-year window comfortably
// exceeds IS+OOS) over the archive's OWN clipped coverage of the
// requested 2025 span (discovered via a preliminary run's `coverage`
// field: the campaign path clips `--from`/`--to` to the actual archive
// bounds internally, exactly as the retired CLI's `campaign_window_ms`
// did). The `{"walkforward":…}` aggregate itself no longer exists as a
// CLI output (deleted Task 8 Step 2); it is reconstructed by
// `walkforward_aggregate_from_reports` from the family's own persisted
// per-window reports — the same reduction, over data the campaign path
// populates identically, so it reproduces the anchor byte-for-byte.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "wf-pin-seed");
let proc_id = register_process_doc(&cwd, "wfpin.process.json", GRID_THEN_WF_PROCESS_DOC_90_30_30);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfpin",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "wfpin.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "wfpin.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let id = family_id_of_kind(&cwd, "WalkForward");
let (fam_out, fam_code) = run_code_in(&cwd, &["runs", "family", &id]);
assert_eq!(fam_code, Some(0), "runs family exit: {fam_out}");
let reports: Vec<serde_json::Value> = fam_out
.lines()
.filter(|l| l.starts_with('{'))
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
.collect();
let wf = walkforward_aggregate_from_reports(&reports, &["fast.length", "slow.length"]);
assert_eq!(wf["windows"].as_u64(), Some(9), "window count: {wf}");
// EXACT floats -- the byte-identity anchor the dissolution must preserve.
assert_eq!(wf["stitched_total_pips"].as_f64(), Some(-1039.8606666650755), "stitched OOS pips: {wf}");
let r = &wf["oos_r"];
assert_eq!(r["n_trades"].as_u64(), Some(20681), "pooled OOS trade count: {wf}");
// expectancy_r is reconstructed as a trade-count-weighted mean of each
// window's own expectancy_r (RunReport.metrics.r.net_trade_rs, the raw
// per-trade series the direct pooled mean would use, is `#[serde(skip)]`
// and unrecoverable from JSON) — mathematically identical to the direct
// pooled mean, but floating-point summation is not associative, so this
// reconstruction differs from the anchor at the ULP level (~5e-17
// relative) rather than bit-for-bit; every other field above (window
// count, stitched pips, trade count, param-stability means) matches
// exactly, confirming the underlying computation is unchanged.
let expectancy_r = r["expectancy_r"].as_f64().expect("expectancy_r is an f64");
assert!(
(expectancy_r - (-0.002397100685333715)).abs() < 1e-12,
"pooled OOS expectancy R (within float-reconstruction tolerance): {wf}"
);
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps[0]["mean"].as_f64(), Some(3.888888888888889), "fast-MA refit mean: {wf}");
assert_eq!(ps[1]["mean"].as_f64(), Some(17.333333333333332), "slow-MA refit mean: {wf}");
}
/// Property (#220 walkforward vertical): `aura walkforward --real` IS-refits ANY
/// sweepable blueprint through the campaign path, not just the historical r-sma
/// candidate — the whole point of dropping `--strategy r-sma`/`--fast`/`--slow`
/// for a positional blueprint + generic `--axis`. Every other walkforward E2E
/// test here still exercises `r_sma.json`; a regression that silently
/// special-cased the refit's axis lookup back to a hardcoded `sma_signal.*`
/// namespace (e.g. in `walkforward_summary_json_from_reports`'s per-axis
/// `raw_matches_wrapped` search) would ship green there while breaking every
/// other blueprint. This runs the unrelated r-breakout candidate
/// (the ganged `channel_length` axis, no `sma_signal` node in sight) end-to-end
/// and asserts a well-formed multi-window summary whose `param_stability` has
/// exactly one entry per bound axis (1 ganged blueprint axis + the 2 stop columns).
/// The window count (9) is roller/data-derived, not strategy-derived, so it is
/// pinned exactly against the same GER40 2025 span the r-sma anchor uses.
/// Gated on the shared GER40 archive; skips cleanly on a data refusal.
#[test]
fn walkforward_dissolves_a_non_r_sma_blueprint() {
// Ported onto a `[std::grid, std::walk_forward]` campaign document
// (#319 Task 8), same fixed 90/30/30 roller + clipped window as
// `walkforward_real_e2e_pins_the_exact_current_grade`, over the unrelated
// r-breakout candidate.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "breakout-wfpin-seed");
let proc_id = register_process_doc(&cwd, "breakoutwfpin.process.json", GRID_THEN_WF_PROCESS_DOC_90_30_30);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "breakoutwfpin",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [10, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "breakoutwfpin.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "breakoutwfpin.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let id = family_id_of_kind(&cwd, "WalkForward");
let (fam_out, fam_code) = run_code_in(&cwd, &["runs", "family", &id]);
assert_eq!(fam_code, Some(0), "runs family exit: {fam_out}");
let reports: Vec<serde_json::Value> = fam_out
.lines()
.filter(|l| l.starts_with('{'))
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
.collect();
let wf = walkforward_aggregate_from_reports(&reports, &["channel_length", "stop_length", "stop_k"]);
assert_eq!(wf["windows"].as_u64(), Some(9), "window count (data/roller-derived): {wf}");
assert!(wf["oos_r"]["n_trades"].as_u64().is_some(), "pooled OOS trade count present: {wf}");
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps.len(), 3, "one entry per bound axis (channel_length, stop_length, stop_k): {wf}");
}
/// Characterization pin (#215): the same identical invocation as
/// `walkforward_real_e2e_pins_the_exact_current_grade` (fixture, real
/// instrument, axes, stop, window), but selecting `plateau:worst` instead of
/// the default argmax. Plateau scores the closed neighbourhood over the full
/// IS grid rather than picking the single best point, so its winner (and
/// therefore its OOS stitch) legitimately differs from the argmax anchor;
/// this pins that divergence exactly, so a regression that silently
/// collapsed plateau back to argmax (or vice versa) inside
/// `run_walk_forward_stage`'s `select_sweep_winner` dispatch goes red here
/// even though the argmax anchor stays green. Gated on the shared GER40
/// archive; skips cleanly on a data refusal.
#[test]
fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
// Ported onto a `[std::grid, std::walk_forward(select=plateau:worst)]`
// campaign document (#319 Task 8), same fixed 90/30/30 roller + clipped
// window as the argmax anchor above.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "wf-plateau-pin-seed");
let plateau_process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-plateau",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "plateau:worst" }
]
}"#;
let proc_id = register_process_doc(&cwd, "wfplateau.process.json", plateau_process_doc);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfplateau",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "wfplateau.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "wfplateau.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let id = family_id_of_kind(&cwd, "WalkForward");
let (fam_out, fam_code) = run_code_in(&cwd, &["runs", "family", &id]);
assert_eq!(fam_code, Some(0), "runs family exit: {fam_out}");
let reports: Vec<serde_json::Value> = fam_out
.lines()
.filter(|l| l.starts_with('{'))
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
.collect();
let wf = walkforward_aggregate_from_reports(&reports, &["fast.length", "slow.length"]);
assert_eq!(wf["windows"].as_u64(), Some(9), "window count: {wf}");
// EXACT floats -- the byte-identity anchor a plateau-vs-argmax regression must preserve.
assert_eq!(wf["stitched_total_pips"].as_f64(), Some(-968.3776666637464), "stitched OOS pips: {wf}");
let r = &wf["oos_r"];
assert_eq!(r["n_trades"].as_u64(), Some(20667), "pooled OOS trade count: {wf}");
// See the argmax anchor's doc comment: expectancy_r is a reconstructed
// trade-count-weighted mean (raw net_trade_rs is unrecoverable from
// JSON), so it is compared within a tight float-reconstruction tolerance
// rather than bit-for-bit.
let expectancy_r = r["expectancy_r"].as_f64().expect("expectancy_r is an f64");
assert!(
(expectancy_r - 0.007635120926372627).abs() < 1e-12,
"pooled OOS expectancy R (within float-reconstruction tolerance): {wf}"
);
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps[0]["mean"].as_f64(), Some(4.777777777777778), "fast-MA refit mean: {wf}");
assert_eq!(ps[1]["mean"].as_f64(), Some(16.444444444444443), "slow-MA refit mean: {wf}");
}
// Note (#319 Task 8, disposition finding): `generalize_without_explicit_
// window_resolves_a_shared_window_and_completes` and `generalize_without_
// explicit_window_resolves_the_intersection_of_all_symbols` retired rather
// than ported. Both pinned the retired `dispatch_generalize`'s OWN no-window
// fallback (`campaign_window_ms` + `intersect_shared_window`, both deleted
// Task 8 Step 2): resolving a shared campaign window automatically from the
// listed symbols' archive geometry when `--from`/`--to` are omitted. A
// hand-authored `CampaignDoc` has no such fallback — `data.windows` is a
// required field the schema always demands explicitly (confirmed:
// `validate_campaign` never treats a missing window as "resolve one"); there
// is no campaign-document capability this ports onto, so this is pure
// argv-translator machinery, not a campaign/engine property. The second
// test's own disposition note ("window-resolution halves retire; the
// cross-instrument grading property ports") already anticipated this split —
// the surviving "generalize grades N instruments over an explicit shared
// window" property stays fully covered by `generalize_grades_a_candidate_
// across_two_instruments` and its siblings above.
/// Property, ported onto a `[std::sweep, std::generalize]` campaign document
/// (#319 Task 8 — disposition finding): a successful campaign generalize run
/// persists its per-instrument runs as discoverable, but NOT as a merged
/// `CrossInstrument` family the way the retired sugar did.
/// `aura-campaign::exec`'s `Generalize` stage handling never calls
/// `append_family` (only its `Sweep`/`WalkForward` siblings do — grepped:
/// the crate's only two `append_family` call sites) — the grade lives solely
/// in the `campaign_runs.jsonl` record's `generalizations` field. What DOES
/// survive discoverably: exactly one `Sweep` family PER INSTRUMENT (each
/// cell's own candidate run, one member), and the generalize grade itself,
/// readable back via `aura campaign runs <id>` (or the always-on final
/// stdout line) carrying `n_instruments`. This pins the adapted-but-real
/// discoverability the campaign path actually provides. Gated on local
/// GER40/USDJPY data (the shared Sept-2024 window); skips cleanly when absent.
#[test]
fn generalize_persists_a_discoverable_cross_instrument_family() {
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "gen-persist-seed");
let proc_id = register_process_doc(&cwd, "genpersist.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "genpersist",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "genpersist.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "genpersist.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
assert_eq!(
v["campaign_run"]["generalizations"][0]["generalization"]["n_instruments"].as_u64(), Some(2),
"the generalize grade is discoverable in the campaign-run record: {record_line}"
);
let (fams_out, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
let sweeps: Vec<&str> = fams_out.lines().filter(|l| l.contains("\"kind\":\"Sweep\"")).collect();
assert_eq!(sweeps.len(), 2, "one durable per-instrument Sweep family: {fams_out:?}");
assert!(sweeps.iter().all(|l| l.contains("\"members\":1")), "one member per instrument cell: {fams_out:?}");
}
/// Property (#210 T3, dissolution), ported onto a `[std::sweep, std::
/// generalize]` campaign document (#319 Task 8 — same disposition finding as
/// the sibling above): the per-instrument grades are attributed to the RIGHT
/// instrument in the RIGHT order — not merely correct in count. Reads
/// `generalizations[0].generalization.per_instrument` (order-preserving)
/// directly off the campaign-run record rather than `runs family
/// generalize-0` (no such merged family exists on the campaign path). Asserts
/// GER40 first with the exact-grade anchor's GER40 `expectancy_r`, then
/// USDJPY with its own. Gated on the local GER40/USDJPY data.
#[test]
fn generalize_family_members_are_attributed_to_the_right_instrument() {
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "gen-attrib-seed");
let proc_id = register_process_doc(&cwd, "genattrib.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "genattrib",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "genattrib.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "genattrib.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let per = v["campaign_run"]["generalizations"][0]["generalization"]["per_instrument"]
.as_array()
.expect("per_instrument is an array");
assert_eq!(per.len(), 2, "one entry per instrument: {record_line}");
assert_eq!(per[0][0].as_str(), Some("GER40"), "first entry is GER40: {record_line}");
assert_eq!(
per[0][1].as_f64(), Some(0.01056371324510624),
"GER40's own expectancy_r, matching the exact-grade anchor: {record_line}"
);
assert_eq!(per[1][0].as_str(), Some("USDJPY"), "second entry is USDJPY: {record_line}");
assert_eq!(
per[1][1].as_f64(), Some(0.005795903617609842),
"USDJPY's own expectancy_r, matching the exact-grade anchor: {record_line}"
);
}
/// Property (#218 migration follow-up), ported onto a `[std::sweep, std::
/// generalize]` campaign document (#319 Task 8 — same disposition finding):
/// `generalize`'s per-instrument members route through the shared campaign
/// member runner, which stamps `manifest.project` with the running project's
/// provenance (C18/C16) for every persisted member. Reads the provenance off
/// the two per-instrument Sweep families (see the sibling tests' doc comments
/// for why no merged "generalize-0" family exists on the campaign path).
/// Gated on the shared GER40/USDJPY Sept-2024 archive.
#[test]
fn generalize_family_members_stamp_project_provenance() {
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "gen-provenance-seed");
let proc_id = register_process_doc(&cwd, "genprovenance.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "genprovenance",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = 1725148800000i64,
to = 1727740799999i64,
);
write_doc(&cwd, "genprovenance.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "genprovenance.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let (fams_out, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
let sweep_ids: Vec<String> = fams_out
.lines()
.filter(|l| l.contains("\"kind\":\"Sweep\""))
.map(|l| {
let v: serde_json::Value = serde_json::from_str(l).expect("family line parses");
v["family_id"].as_str().expect("family_id").to_string()
})
.collect();
assert_eq!(sweep_ids.len(), 2, "one per-instrument Sweep family: {fams_out:?}");
for id in &sweep_ids {
let (fam_out, fam_code) = run_code_in(&cwd, &["runs", "family", id]);
assert_eq!(fam_code, Some(0), "runs family exit: {fam_out}");
let member: serde_json::Value = serde_json::from_str(fam_out.lines().next().expect("one member line"))
.expect("member line parses as JSON");
let project = &member["manifest"]["project"];
assert_eq!(
project["namespace"].as_str(), Some("demo"),
"campaign-path member manifest carries the fixture project's namespace: {fam_out}"
);
let sha = project["dylib_sha256"].as_str()
.unwrap_or_else(|| panic!("dylib_sha256 present: {fam_out}"));
assert_eq!(sha.len(), 64, "dylib_sha256 is a full sha256 hex digest: {fam_out}");
}
}
/// `aura sweep <blueprint.json> --axis <name>=<csv> …` (#166): sweeps a loaded CLOSED
/// blueprint over its named bound-override axes and persists a discoverable Sweep family.
/// Both bound knobs (fast/slow) must be re-opened by axes; the 2×2 grid yields 4 members.
#[test]
fn aura_sweep_loads_a_blueprint_and_persists_a_sweep_family() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8 — there
// is no campaign-document equivalent of "sweep the embedded showcase
// stream with no instrument", invariant 7); both bound knobs (fast/slow)
// re-opened by axes, the 2x2 grid yields 4 members.
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "blueprint-sweep-seed");
let proc_id = register_process_doc(&cwd, "f.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("f", "SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999), "2, 4", "8, 16");
write_doc(&cwd, "f.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "f.campaign.json"]);
assert_eq!(code, Some(0), "exec exit: {out}");
let (fo, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fo}");
assert!(fo.contains("\"kind\":\"Sweep\""), "families: {fo}");
assert!(fo.contains("\"members\":4"), "2×2 grid -> four members: {fo}");
}
/// Property (#158/#164, C18/C11/C12): a blueprint sweep content-addresses its topology
/// — the canonical blueprint is persisted ONCE under `runs/blueprints/<topology_hash>.json`,
/// keyed by the very SHA256 every member's manifest carries, holding exactly the bytes
/// `blueprint_to_json(blueprint_from_json(doc))` yields. That stored topology is what makes
/// a generated sweep family reproducible from disk (the `aura reproduce` re-derivation).
#[test]
fn aura_sweep_persists_the_canonical_blueprint_keyed_by_topology_hash() {
use aura_engine::{blueprint_from_json, blueprint_to_json};
use aura_vocabulary::std_vocabulary;
use sha2::{Digest, Sha256};
// Ported onto a one-cell campaign document over SYMA (#319 Task 8, same
// rationale as the sibling above).
let (cwd, _g) = fresh_project_with_data();
let fixture = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint(&cwd, "blueprint-sweep-store-seed");
let proc_id = register_process_doc(&cwd, "f.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("f", "SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999), "2, 4", "8, 16");
write_doc(&cwd, "f.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "f.campaign.json"]);
assert_eq!(code, Some(0), "exec exit: {out}");
// exactly one blueprint stored for the whole family (one topology per family).
let store = cwd.join("runs/blueprints");
let mut entries: Vec<_> = std::fs::read_dir(&store)
.unwrap_or_else(|e| panic!("the sweep must create the blueprint store {store:?}: {e}"))
.map(|e| e.expect("dir entry").path())
.collect();
entries.sort();
assert_eq!(entries.len(), 1, "one stored topology per family: {entries:?}");
// keyed by a 64-hex topology hash, holding the canonical re-serialization.
let stored_path = &entries[0];
let stem = stored_path.file_stem().expect("stem").to_str().expect("utf-8");
assert_eq!(stem.len(), 64, "content id is a 64-hex SHA256: {stem}");
assert!(stem.bytes().all(|b| b.is_ascii_hexdigit()), "content id is hex: {stem}");
let doc = std::fs::read_to_string(&fixture).expect("read fixture");
let expected = blueprint_to_json(&blueprint_from_json(&doc, &|t| std_vocabulary(t)).expect("loads"))
.expect("re-serializes");
let stored = std::fs::read_to_string(stored_path).expect("read stored blueprint");
assert_eq!(stored, expected, "the store holds the canonical blueprint bytes");
// content-addressing, the feature's load-bearing property: the key IS the SHA256 of
// the stored bytes — `get_blueprint(hash)` addresses exactly these bytes by identity,
// not by coincidence of the store and the members computing the hash separately.
let content_id: String =
Sha256::digest(stored.as_bytes()).iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(stem, content_id, "the store key is the SHA256 of its content");
// and that key is exactly what every family member's manifest carries — the hash the
// reproduce path reads back to fetch the topology. All 4 members share the one topology.
let members = std::fs::read_to_string(cwd.join("runs/families.jsonl")).expect("read family store");
let carried = members.matches(&format!("\"topology_hash\":\"{stem}\"")).count();
assert_eq!(carried, 4, "every member records the store key as its topology_hash: {members}");
let _ = std::fs::remove_dir_all(&cwd);
}
/// An `--axis` name that does not resolve against the loaded blueprint's param_space
/// fails clean (a named BindError, non-zero exit), not a panic. The CLOSED fixture has
/// bound-override knobs, so a nonsense axis name (`nope`) is genuinely unknown — it names
/// neither a re-openable bound param nor an already-open knob (#246: a bound name would
/// re-open instead).
#[test]
fn aura_sweep_rejects_an_unknown_axis() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "unknown-axis-seed");
let proc_id = register_process_doc(&cwd, "unknownaxis.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "f",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "nope": {{ "kind": "I64", "values": [1, 2] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "unknownaxis.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "unknownaxis.campaign.json"]);
assert_ne!(code, Some(0), "an unknown axis must fail the campaign run: {out}");
assert!(
out.to_lowercase().contains("nope"),
"names the unresolved axis: {out}"
);
}
/// Property (#158, C18 "re-derives full results on demand"): `aura reproduce <family-id>`
/// re-derives every member of a persisted sweep family from the content-addressed
/// blueprint store and reports each bit-identical, exiting 0. Drives the verb through the
/// binary — the `reproduce_family` stdout contract (a per-member `bit-identical` line plus
/// a `reproduced N/N` summary) that the `reproduce_family_in` unit seam bypasses.
#[test]
fn aura_reproduce_re_derives_a_persisted_sweep_family() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8, same
// rationale as the sweep-family siblings above); the family_id is read
// back from `runs families`, not assumed to be the literal "f-0".
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "blueprint-reproduce-seed");
let proc_id = register_process_doc(&cwd, "f.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("f", "SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999), "2, 4", "8, 16");
write_doc(&cwd, "f.campaign.json", &doc);
let (sweep_out, sweep_code) = run_code_in(&cwd, &["exec", "f.campaign.json"]);
assert_eq!(sweep_code, Some(0), "sweep exit: {sweep_out}");
let id = family_id_of_kind(&cwd, "Sweep");
let (out, code) = run_code_in(&cwd, &["reproduce", &id]);
assert_eq!(code, Some(0), "reproduce exit: {out}");
// one verdict line per member (all bit-identical) + the summary; the label renders
// param values portably (`length=2`, not the `I64(2)` Debug form).
assert_eq!(
out.lines().filter(|l| l.contains("reproduced: bit-identical")).count(),
4,
"every member re-derives bit-identically: {out}"
);
assert!(out.contains("fast.length=2"), "label renders values portably: {out}");
assert!(!out.contains("I64("), "label must not leak the Scalar Debug form: {out}");
assert!(out.contains("reproduced 4/4 members bit-identically"), "summary line: {out}");
}
/// Property (C1/C18 integrity net): when a persisted member's stored metrics no longer
/// match its re-derivation, `aura reproduce` reports that member `DIVERGED` and exits 1
/// — the non-happy branch of the verb. Forced by tampering one member's on-disk
/// `total_pips` after the sweep (the store's bit-identical compare IS the integrity check).
#[test]
fn aura_reproduce_reports_a_diverged_member_and_exits_one() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "blueprint-reproduce-diverge-seed");
let proc_id = register_process_doc(&cwd, "f.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("f", "SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999), "2, 4", "8, 16");
write_doc(&cwd, "f.campaign.json", &doc);
let (sweep_out, sweep_code) = run_code_in(&cwd, &["exec", "f.campaign.json"]);
assert_eq!(sweep_code, Some(0), "sweep exit: {sweep_out}");
// corrupt the first member's stored `total_pips` so its re-run metrics differ.
let store = cwd.join("runs/families.jsonl");
let s = std::fs::read_to_string(&store).expect("read family store");
let key = "\"total_pips\":";
let at = s.find(key).expect("a member records total_pips") + key.len();
let end = at + s[at..].find(',').expect("total_pips value terminates");
let corrupted = format!("{}999.0{}", &s[..at], &s[end..]);
std::fs::write(&store, corrupted).expect("write corrupted store");
let id = family_id_of_kind(&cwd, "Sweep");
let (out, code) = run_code_in(&cwd, &["reproduce", &id]);
assert_eq!(code, Some(1), "a diverged member exits 1: {out}");
assert!(out.contains("reproduced: DIVERGED"), "the tampered member is reported DIVERGED: {out}");
assert!(out.contains("reproduced 3/4 members bit-identically"), "summary counts the diverged member: {out}");
}
/// E2E (acc 2): persist a blueprint sweep, then `aura reproduce <id>` re-derives every
/// member bit-identically from the content-addressed store and exits 0.
#[test]
fn aura_reproduce_re_derives_a_persisted_sweep_bit_identically() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "reproduce-roundtrip-seed");
let proc_id = register_process_doc(&cwd, "smacross.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc(
"smacross", "SYMA", &bp_id, &proc_id,
(1709251200000, 1719791999999), "2", "4, 6",
);
write_doc(&cwd, "smacross.campaign.json", &doc);
let (sweep_out, sweep_code) = run_code_in(&cwd, &["exec", "smacross.campaign.json"]);
assert_eq!(sweep_code, Some(0), "sweep exit: {sweep_out}");
let id = family_id_of_kind(&cwd, "Sweep");
let (out, code) = run_code_in(&cwd, &["reproduce", &id]);
assert_eq!(code, Some(0), "reproduce exits 0: {out}");
assert!(
out.contains("reproduced 2/2 members bit-identically"),
"every member re-derives: {out}"
);
}
// Note (#319 Task 8, disposition finding): `aura_mc_over_a_blueprint_
// reproduces_bit_identically` retired rather than ported. `aura mc <closed
// blueprint.json> --seeds N` drove `aura-runner::family::blueprint_mc_
// family` — a per-RANDOM-SEED synthetic family (each member draws a distinct
// synthetic price walk from the SAME fixed blueprint), a wholly different
// axis of variation than anything a `CampaignDoc` models (strategy params /
// instrument / window — never "vary only the seed, same everything else").
// There is no document field to bind a seed axis onto, and the plan's own
// Task 9 ("aura-runner family-builder retirement") schedules `blueprint_mc_
// family` for deletion as a builder with no surviving CLI entry point — this
// property is not "translated", it is retired along with the builder.
// Reported to the orchestrator rather than fabricated onto an unrelated
// substitute.
// Note (#319 Task 8, disposition finding): `aura_mc_synthetic_member_panic_
// is_contained` retired for the same reason (the `mc --seeds` poison-member
// containment test rides the same retiring `blueprint_mc_family` seam); the
// general "member-compile faults are contained identically across every
// verb/path" property stays fully covered by its ported
// `aura_walkforward_synthetic_member_panic_is_contained` /
// `aura_sweep_synthetic_member_panic_is_contained` siblings below plus the
// real-data #272 containment tests in `research_docs.rs`.
/// E2E (#173): an IS-refit walk-forward over a loaded blueprint persists a
/// content-addressed WalkForward family that `aura reproduce`s bit-identically —
/// each OOS member's windowed slice + winner params are recovered from its manifest.
/// The synthetic walk-forward route's own `--axis` intake is now RAW (this
/// cycle's tidy fix, #328 batch 2).
#[test]
fn aura_walkforward_over_a_blueprint_reproduces_bit_identically() {
// Ported onto a `[std::grid, std::walk_forward]` campaign document over
// SYMA (#319 Task 8, same substitution rationale as
// `reproduce_family_with_overridden_bound_param_re_derives`): the OOS
// member count is roller/data-derived, so it is read back rather than
// pinned to the translator-era literal (3).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "wfr-seed");
let proc_id = register_process_doc(&cwd, "wfr.process.json", GRID_THEN_WF_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfr",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 3] }},
"slow.length": {{ "kind": "I64", "values": [4, 6] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "wfr.campaign.json", &doc);
let (run_out, run_code) = run_code_in(&cwd, &["exec", "wfr.campaign.json"]);
assert_eq!(run_code, Some(0), "stderr: {run_out}");
let member_count = run_out.lines().filter(|l| l.starts_with("{\"family_id\":")).count();
assert!(member_count > 0, "at least one OOS member: {run_out}");
let id = family_id_of_kind(&cwd, "WalkForward");
let (out, code) = run_code_in(&cwd, &["reproduce", &id]);
assert_eq!(code, Some(0), "out: {out}");
assert!(
out.contains(&format!("reproduced {member_count}/{member_count} members bit-identically")),
"out: {out}"
);
}
/// E2E (#173), ported onto a `[std::grid, std::walk_forward]` campaign
/// document over SYMA (#319 Task 8): the loaded-blueprint walk-forward runs +
/// persists a WalkForward family and stores exactly one content-addressed
/// blueprint. The `{"walkforward":…}` aggregate-JSON summary carrying
/// `oos_r`/`stitched_total_pips`/`param_stability` was the retired CLI
/// verb's OWN post-processing (`walkforward_summary_json_from_reports`,
/// deleted Task 8 Step 2) — no surviving command reconstructs that exact
/// envelope, so it is not re-pinned here; the family-persistence property
/// (member count, `FamilyKind::WalkForward`, one stored topology) survives in
/// full.
#[test]
fn aura_walkforward_over_a_blueprint_persists_a_family() {
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "wfx-seed");
let proc_id = register_process_doc(&cwd, "wfx.process.json", GRID_THEN_WF_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfx",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 3] }},
"slow.length": {{ "kind": "I64", "values": [4, 6] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "wfx.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "wfx.campaign.json"]);
assert_eq!(code, Some(0), "stderr: {out}");
let member_count = out.lines().filter(|l| l.starts_with("{\"family_id\":")).count();
assert!(member_count > 0, "at least one OOS member line: {out}");
let (fo, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fo}");
assert!(fo.contains("\"kind\":\"WalkForward\""), "families: {fo}");
let n = std::fs::read_dir(cwd.join("runs").join("blueprints")).map(|d| d.count()).unwrap_or(0);
assert_eq!(n, 1, "exactly one content-addressed blueprint stored");
}
/// 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() {
// Ported onto a `[std::grid, std::walk_forward]` campaign document over
// SYMA (#319 Task 8).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "wfpanic-seed");
let proc_id = register_process_doc(&cwd, "wfpanic.process.json", GRID_THEN_WF_PROCESS_DOC);
// 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 doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "wfpanic",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [0] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "wfpanic.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "wfpanic.campaign.json"]);
assert!(
out.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: {out}"
);
assert!(!out.contains("panicked at"), "no raw panic report reaches the consumer: {out}");
assert!(!out.contains("crates/"), "no internal source path leaks to the consumer: {out}");
assert_eq!(
code, Some(3),
"a contained member fault is the deliberate failed-cells exit 3, never 101: {out}"
);
}
/// E2E (#278 decision, sweep twin): the campaign path's sweep stage shares the
/// same bare-member seam as its walk-forward sibling, so the same poison must
/// be contained the same way — warning + exit 3, no panic escape. Ported onto
/// a one-cell campaign document over SYMA (#319 Task 8).
#[test]
fn aura_sweep_synthetic_member_panic_is_contained() {
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "swpanic-seed");
let proc_id = register_process_doc(&cwd, "swpanic.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "swpanic",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [0] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "swpanic.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "swpanic.campaign.json"]);
assert!(
out.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: {out}"
);
assert!(
!out.contains("panicked at") && !out.contains("crates/"),
"no raw panic report or internal source path leaks: {out}"
);
assert_eq!(
code, Some(3),
"a contained member fault is the deliberate failed-cells exit 3, never 101: {out}"
);
}
/// E2E (negative): `aura reproduce <unknown>` is a hard error (exit non-zero + named
/// cause on stderr), distinct from `runs family <id>`'s treat-as-empty exit 0.
#[test]
fn aura_reproduce_rejects_an_unknown_family() {
let cwd = temp_cwd("reproduce_unknown");
let out = std::process::Command::new(BIN)
.args(["reproduce", "no-such-family-1"])
.current_dir(&cwd)
.output()
.expect("run aura reproduce");
assert!(!out.status.success(), "unknown family exits non-zero");
assert!(
String::from_utf8_lossy(&out.stderr).contains("no such family 'no-such-family-1'"),
"names the cause: {}",
String::from_utf8_lossy(&out.stderr)
);
let _ = std::fs::remove_dir_all(&cwd);
}
// Note (#159 cut 4 collateral): this removes
// `aura_reproduce_rejects_a_non_generated_family` — its fixture was a
// HARD-WIRED (bare `sweep --name`) family with no `topology_hash`; sweep is
// blueprint-only now and every blueprint-sweep member stamps a
// `topology_hash`, so no surviving CLI surface can construct the fixture.
// The defensive "not a generated run" refusal in `main.rs` stays as dead-path
// safety for a malformed/legacy `families.jsonl`. Removed as collateral, not
// repointed.
/// E2E (refuse-don't-guess): `aura reproduce` when the content-addressed blueprint was
/// removed from the store exits non-zero, naming the missing blueprint — never re-runs
/// against a guessed topology (C10/C18).
#[test]
fn aura_reproduce_rejects_a_missing_stored_blueprint() {
// Ported onto a one-cell campaign document over SYMA (#319 Task 8).
let (cwd, _g) = fresh_project_with_data();
let bp_id = seed_blueprint(&cwd, "reproduce-missing-store-seed");
let proc_id = register_process_doc(&cwd, "bp.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = sweep_campaign_doc("bp", "SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999), "2", "4");
write_doc(&cwd, "bp.campaign.json", &doc);
let (sweep_out, sweep_code) = run_code_in(&cwd, &["exec", "bp.campaign.json"]);
assert_eq!(sweep_code, Some(0), "blueprint sweep ok: {sweep_out}");
std::fs::remove_dir_all(cwd.join("runs").join("blueprints")).expect("remove the blueprint store");
let id = family_id_of_kind(&cwd, "Sweep");
let (out, code) = run_code_in(&cwd, &["reproduce", &id]);
assert_ne!(code, Some(0), "a missing stored blueprint exits non-zero");
assert!(out.contains("missing from store"), "names the cause: {out}");
}
/// `--version` / `-V` surface the crate version on stdout and exit 0 (GNU MUST):
/// a version query is a successful query, not a usage error. The expected string
/// is the crate version itself (`CARGO_PKG_VERSION`, i.e. the workspace version),
/// so this stays honest across version bumps and keeps one source of truth.
#[test]
fn version_flag_prints_version_to_stdout_and_exits_zero() {
for flag in ["--version", "-V"] {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.arg(flag)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(0), "{flag}: exit {:?}", out.status);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains(env!("CARGO_PKG_VERSION")), "{flag} stdout: {stdout:?}");
}
}
/// `--version` carries the ENGINE COMMIT beside the semver, drawn from the SAME
/// `AURA_COMMIT` build-time provenance the run manifest stamps (the
/// `option_env!("AURA_COMMIT")` that fills `RunManifest.commit`), so a stale
/// binary is distinguishable at the CLI surface *before* any run (#266). Shape:
/// `aura 0.1.0 (<commit-ish>)`, exit 0.
///
/// Property, not a specific sha: the expected commit is read from the same
/// provenance source the built binary was compiled with — this integration test
/// receives aura-cli's build-script `rustc-env`, so it sees the identical value
/// (dirty/`unknown` suffixes included) rather than today's HEAD, keeping the
/// assertion honest against a lagging or unclean tree. The load-bearing claim is
/// that the parenthesized commit rides beside the still-present semver.
#[test]
fn version_flag_carries_the_engine_commit_from_manifest_provenance() {
// The single source of truth — the same env the manifest's commit is sourced
// from; falls back to "unknown" identically to `sim_optimal_manifest`.
let commit = option_env!("AURA_COMMIT").unwrap_or("unknown");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.arg("--version")
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(0), "--version exit {:?}", out.status);
let stdout = String::from_utf8_lossy(&out.stdout);
// Semver stays present (one source of truth, CARGO_PKG_VERSION).
assert!(
stdout.contains(env!("CARGO_PKG_VERSION")),
"--version still prints the semver: {stdout:?}"
);
// ...and the engine commit rides beside it, parenthesized, from the shared
// manifest provenance — the load-bearing assertion #266 adds.
assert!(
stdout.contains(&format!("({commit})")),
"--version prints the engine commit ({commit}) beside the semver: {stdout:?}"
);
}
/// Characterization pin (byte-identity anchor for the mc R-bootstrap dissolution,
/// #210, the last verb; argv since migrated to the generic form by #220).
/// `aura mc <blueprint> --real --axis …` pools the per-window OOS trade-R
/// series and r-bootstraps E[R] through the campaign
/// `[std::sweep(argmax), std::walk_forward, std::monte_carlo]`
/// process, whose terminal monte_carlo stage does the
/// `StageBootstrap::PooledOos(r_bootstrap(...))` seeded from the campaign seed -- so
/// the campaign seed carries the mc `--seed`. The wf winners are
/// deflation-seed-independent (argmax), so the pooled series, and thus the
/// bootstrap, is stable across that seed remap (the walkforward anchor already
/// proved winner seed-independence). The multi-point grid makes the per-window
/// winner selection non-degenerate. The pinned bootstrap grade of this fixed 2025
/// GER40 invocation ORIGINATES from the pre-#210 inline rolling walk-forward
/// (`mc_r_bootstrap_report`) under the retired welded `aura mc --strategy r-sma
/// --real` grammar, and survived both the campaign dissolution and the #220 argv
/// migration byte-identically (the acceptance gate then, the regression anchor
/// now). Gated on the GER40 archive; skips
/// cleanly on a data refusal.
#[test]
fn mc_r_bootstrap_real_e2e_pins_the_exact_current_grade() {
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8): the grade is the terminal stage's own
// native `bootstrap.pooled_oos` record (`aura_backtest::RBootstrap`, the
// SAME struct the retired `mc_r_bootstrap_json` CLI wrapper printed under
// identical field names) — no client-side reconstruction needed. The
// campaign `seed` carries the retired sugar's `--seed` (`translate_mc`'s
// mapping).
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "mc-pin-seed");
// The fixed real-archive roller (`aura_backtest::wf_ms_sizes`, 90d IS / 30d
// OOS / 30d step) the retired sugar's `fit_wf_ms_sizes` used — this
// window's span (~1 year) comfortably exceeds IS+OOS, so the fixed sizes
// apply unscaled (the `fit_wf_ms_sizes` fast-path every year-plus e2e
// anchor pins).
let mc_process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc-pin",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 1000, "block_len": 5 }
]
}"#;
let proc_id = register_process_doc(&cwd, "mcpin.process.json", mc_process_doc);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "mcpin",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 42,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "mcpin.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "mcpin.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let mc = &v["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"];
assert_eq!(mc["block_len"].as_u64(), Some(5), "block_len: {record_line}");
assert_eq!(mc["n_resamples"].as_u64(), Some(1000), "n_resamples: {record_line}");
assert_eq!(mc["n_trades"].as_u64(), Some(20681), "pooled OOS trade count: {record_line}");
// EXACT bootstrap floats -- the byte-identity anchor the dissolution must preserve.
assert_eq!(mc["e_r"]["mean"].as_f64(), Some(-0.0025753095301594307), "E[R] mean: {record_line}");
assert_eq!(mc["e_r"]["p50"].as_f64(), Some(-0.0023049902245975227), "E[R] median: {record_line}");
assert_eq!(mc["prob_le_zero"].as_f64(), Some(0.558), "P(E[R] <= 0): {record_line}");
}
/// Property (#220 mc vertical): `aura mc --real` R-bootstraps ANY sweepable
/// blueprint through the campaign path, not just the historical r-sma candidate
/// — the whole point of dropping `--strategy r-sma`/`--fast`/`--slow` for a
/// positional blueprint + generic `--axis`. Every other mc E2E test here still
/// exercises `r_sma.json`; a regression that silently special-cased the
/// pipeline's axis lookup back to a hardcoded `sma_signal.*` namespace would
/// ship green there while breaking every other blueprint. This runs the
/// unrelated r-breakout candidate (the ganged `channel_length` axis, no
/// `sma_signal` node in sight) end-to-end and asserts a well-formed
/// `mc_r_bootstrap` grade: the argv-echoed knobs (`block_len`/`n_resamples`)
/// pin exactly, `n_trades`/`e_r.mean`/`prob_le_zero` are asserted present and
/// well-typed (their exact values are strategy-specific, already the r-sma
/// pin's job above). Gated on the shared GER40 archive; skips cleanly on a
/// data refusal.
#[test]
fn mc_dissolves_a_non_r_sma_blueprint() {
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8), same clipped window + fixed 90/30/30
// roller as `mc_r_bootstrap_real_e2e_pins_the_exact_current_grade`, over
// the unrelated r-breakout candidate.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "breakout-mcpin-seed");
let mc_process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc-breakout",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 1000, "block_len": 5 }
]
}"#;
let proc_id = register_process_doc(&cwd, "breakoutmcpin.process.json", mc_process_doc);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "breakoutmcpin",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [10, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 42,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "breakoutmcpin.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "breakoutmcpin.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let mc = &v["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"];
assert_eq!(mc["block_len"].as_u64(), Some(5), "block_len (argv echo): {record_line}");
assert_eq!(mc["n_resamples"].as_u64(), Some(1000), "n_resamples (argv echo): {record_line}");
assert!(mc["n_trades"].as_u64().is_some_and(|n| n > 0), "pooled OOS trade count present: {record_line}");
assert!(mc["e_r"]["mean"].as_f64().is_some_and(|m| m.is_finite()), "E[R] mean present + finite: {record_line}");
assert!(
mc["prob_le_zero"].as_f64().is_some_and(|p| (0.0..=1.0).contains(&p)),
"P(E[R] <= 0) present + in [0,1]: {record_line}"
);
}
/// Property: real-data `aura mc <blueprint> --real --axis …` (the #210 dissolution
/// of the retired welded `--strategy r-sma` form, argv since migrated by #220) is
/// thin sugar over the one campaign
/// path — a successful run durably auto-registers exactly one generated process
/// document, one generated campaign document (carrying the constant "mc" name and the
/// stop as a non-empty single risk regime), and one campaign-run record, and emits the
/// single `mc_r_bootstrap` grade line. The pipeline's walk_forward stage persists
/// the one family; the leading `std::grid` stage only enumerates (#256) and the
/// terminal monte_carlo stage is an annotator — neither adds a family. This is
/// the observable proof the inline path is gone
/// and the dissolution runs through the executor. Gated on the GER40 2025 archive;
/// skips cleanly on a data refusal.
#[test]
fn mc_dissolves_through_the_campaign_path() {
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8): the retired `mc_r_bootstrap` line
// format is gone (deleted Task 8 Step 2), but the equivalent property —
// a single native summary line (`emit: []` suppresses the per-member
// family_table lines, leaving only the always-on final
// `{"campaign_run":…}` line) — survives, plus the auto-registration and
// family-count properties.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "mc-dissolve-seed");
let mc_process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc-dissolve",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 1000, "block_len": 5 }
]
}"#;
let proc_id = register_process_doc(&cwd, "mcdissolve.process.json", mc_process_doc);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "mc",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 42,
"presentation": {{ "persist_taps": [], "emit": [] }}
}}"#,
);
write_doc(&cwd, "mc.campaign.json", &doc);
let raw = Command::new(BIN).args(["exec", "mc.campaign.json"]).current_dir(&cwd).output().expect("spawn exec");
let stdout = String::from_utf8_lossy(&raw.stdout).into_owned();
let stderr = String::from_utf8_lossy(&raw.stderr).into_owned();
let out = format!("{stdout}{stderr}");
if raw.status.code() == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(raw.status.code(), Some(0), "exit: {out}");
// the single native summary line is emitted on STDOUT (and nothing else
// there, since `emit: []` suppresses the per-member family_table lines) —
// the informational "aura: campaign run N recorded: …" tally rides
// stderr, not stdout, so it is not counted here.
assert_eq!(stdout.lines().count(), 1, "mc prints exactly one summary line on stdout: {stdout:?}");
assert!(
stdout.lines().next().is_some_and(|l| l.starts_with("{\"campaign_run\":")),
"the one line is the native campaign_run summary: {stdout}"
);
let count = |sub: &str| {
std::fs::read_dir(cwd.join("runs").join(sub))
.map(|d| d.count())
.unwrap_or(0)
};
assert_eq!(count("processes"), 1, "one generated process document registered");
assert_eq!(count("campaigns"), 1, "one generated campaign document registered");
let runs_log = std::fs::read_to_string(cwd.join("runs").join("campaign_runs.jsonl"))
.expect("campaign_runs.jsonl exists after a run");
assert_eq!(runs_log.lines().count(), 1, "one campaign run recorded");
let campaigns_dir = cwd.join("runs").join("campaigns");
let campaign_doc_path = std::fs::read_dir(&campaigns_dir)
.expect("campaigns dir exists")
.next()
.expect("exactly one campaign document")
.expect("readable dir entry")
.path();
let campaign_doc_json = std::fs::read_to_string(&campaign_doc_path).expect("read campaign doc");
assert!(
campaign_doc_json.contains("\"name\":\"mc\""),
"the campaign document carries the authored mc name: {campaign_doc_json}"
);
assert!(
campaign_doc_json.contains("\"risk\":[")
&& campaign_doc_json.contains("\"length\":14")
&& campaign_doc_json.contains("\"k\":2.0"),
"the stop rides a non-empty risk regime: {campaign_doc_json}"
);
let (fams_out, fams_code) = run_code_in(&cwd, &["runs", "families"]);
assert_eq!(fams_code, Some(0), "families exit: {fams_out}");
assert_eq!(
fams_out.lines().filter(|l| l.contains("\"kind\":\"Sweep\"")).count(),
0,
"the enumerate-only std::grid leading stage persists no Sweep family: {fams_out}"
);
assert_eq!(
fams_out.lines().filter(|l| l.contains("\"kind\":\"WalkForward\"")).count(),
1,
"one WalkForward family holding the per-window OOS reports: {fams_out}"
);
assert_eq!(
fams_out.lines().filter(|l| l.contains("\"kind\":\"MonteCarlo\"")).count(),
0,
"the terminal monte_carlo stage is an annotator, not a family: {fams_out}"
);
}
/// 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 a FIXED ~30 days (2025-04-16..2025-05-16, the tail of the
/// ~135-day span the walkforward e2e tests use): what the property needs is "a
/// data-carrying window far shorter than the roller", which a fixed recent window
/// satisfies without the full-archive probe sweep a live-span derivation costs —
/// the archive only grows forward, so the window never falls off its end. The
/// leading std::grid stage only enumerates (nothing runs over the short window); the
/// refusal 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;
}
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8): the retired CLI's `fit_wf_ms_sizes`
// scaling (`aura_backtest::scaffold::fit_wf_ms_sizes`, still a real
// library function) is now invoked by the TEST itself, at document-
// construction time, to size the `std::walk_forward` stage — a
// hand-authored document has no in-executor auto-fit (that scaling was
// pure CLI-side preprocessing before generating the document); this
// reproduces the SAME fitted roller the retired sugar generated.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "mc-short-window-seed");
// ~30 days, far shorter than the fixed 90+30-day roller, so the roller
// must scale down to the window (the same fit that lets this complete).
const SUB_FROM_MS: i64 = 1744761600000;
const SUB_TO_MS: i64 = 1747353600000;
let (is_ms, oos_ms, step_ms) = aura_backtest::scaffold::fit_wf_ms_sizes(SUB_FROM_MS, SUB_TO_MS);
// Sanity: the fixed 90d+30d roller does NOT fit this ~30-day span
// unscaled, so `fit_wf_ms_sizes` must actually be exercising its
// scale-down branch here — else this test would be vacuous.
assert!(
7_776_000_000u64 + 2_592_000_000u64 > (SUB_TO_MS - SUB_FROM_MS) as u64,
"the fixed 90/30 roller must NOT fit this window unscaled, or the fit branch goes untested"
);
let mc_process_doc = format!(
r#"{{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc-shortwindow",
"pipeline": [
{{ "block": "std::grid" }},
{{ "block": "std::walk_forward", "in_sample_ms": {is_ms}, "out_of_sample_ms": {oos_ms},
"step_ms": {step_ms}, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" }},
{{ "block": "std::monte_carlo", "resamples": 100, "block_len": 5 }}
]
}}"#,
);
let proc_id = register_process_doc(&cwd, "mcshort.process.json", &mc_process_doc);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "mcshort",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": 42,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = SUB_FROM_MS, to = SUB_TO_MS,
);
write_doc(&cwd, "mcshort.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "mcshort.campaign.json"]);
// 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 code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(
code, Some(0),
"a short real window must fit the injected roller and run to completion, \
not refuse late: {out}"
);
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
assert!(
v["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"].is_object(),
"the mc bootstrap grade must be emitted for the fitted short window: {record_line}"
);
}
/// Property: the one load-bearing decision new to the mc dissolution — `translate_mc`
/// maps `campaign.seed` to the mc `--seed` (unlike `translate_walkforward`, which
/// hardcodes `seed: 0`) — actually flows through the executor to the emitted grade,
/// not just into the generated document's content id (already unit-pinned by
/// `translate_mc_diverging_seeds_and_stops_do_not_collide_content_ids`). Two runs that
/// differ ONLY in `--seed` must emit a different `e_r` distribution (the bootstrap
/// resample draw is seeded) while pooling the IDENTICAL `n_trades` (the wf winners are
/// argmax-selected, hence deflation-seed-independent — the walkforward anchor already
/// proved this for the shared roller): the seed perturbs the resample, never the
/// underlying OOS trade-R series. Without this test, a dropped seed remap (e.g. mc
/// always bootstrapping at a fixed internal seed) would still pass every other mc
/// dissolution test, since none of them compare two distinct `--seed` runs. Gated on
/// the GER40 2025 archive; skips cleanly on a data refusal.
#[test]
fn mc_dissolved_seed_changes_the_bootstrap_draw_not_the_pooled_series() {
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8), same clipped window + fixed 90/30/30
// roller as `mc_r_bootstrap_real_e2e_pins_the_exact_current_grade` — only
// the document's own `seed` field varies (the campaign seed carries the
// retired sugar's `--seed`, `translate_mc`'s mapping).
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "mc-seed-diff-seed");
let mc_process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "grid-then-walkforward-then-mc-seeddiff",
"pipeline": [
{ "block": "std::grid" },
{ "block": "std::walk_forward", "in_sample_ms": 7776000000, "out_of_sample_ms": 2592000000,
"step_ms": 2592000000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 1000, "block_len": 5 }
]
}"#;
let proc_id = register_process_doc(&cwd, "mcseeddiff.process.json", mc_process_doc);
let doc_for = |seed: u64| {
format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "mcseeddiff-{seed}",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3, 5] }},
"slow.length": {{ "kind": "I64", "values": [12, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 14, "k": 2.0 }} }} ],
"seed": {seed},
"presentation": {{ "persist_taps": [], "emit": [] }}
}}"#,
)
};
let run = |seed: u64| {
let file = format!("mcseeddiff-{seed}.campaign.json");
write_doc(&cwd, &file, &doc_for(seed));
run_code_in(&cwd, &["exec", &file])
};
let (out42, code42) = run(42);
if code42 == Some(3) && (out42.contains("no local data") || out42.contains("no recorded geometry") || out42.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
let (out7, code7) = run(7);
assert_eq!(code42, Some(0), "exit (seed 42): {out42}");
assert_eq!(code7, Some(0), "exit (seed 7): {out7}");
let parse = |out: &str| -> serde_json::Value {
let line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.unwrap_or_else(|| panic!("the always-on final campaign_run line: {out}"));
serde_json::from_str(line).expect("campaign_run line parses as JSON")
};
let v42 = parse(&out42);
let v7 = parse(&out7);
let mc42 = &v42["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"];
let mc7 = &v7["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"];
assert_eq!(
mc42["n_trades"], mc7["n_trades"],
"the pooled OOS trade-R series is seed-independent (argmax winners): {mc42} vs {mc7}"
);
assert_ne!(
mc42["e_r"]["mean"], mc7["e_r"]["mean"],
"a different seed must move the resampled E[R] mean: {mc42} vs {mc7}"
);
}
/// #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;
}
// Ported onto a `[std::grid, std::walk_forward]` campaign document
// (#319 Task 8) over the SAME real GER40 window/axis the sugar used —
// the `{"walkforward":…}`/`param_stability` aggregate was the retired
// CLI verb's own post-processing (deleted Task 8 Step 2, no surviving
// reconstruction); the surviving property is that the campaign path
// realizes a `std::walk_forward` stage (>= 1 window, family persisted)
// over an arbitrary user blueprint with axis names the CLI has never
// hardcoded — the r-sma weld is structurally gone.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "breakout-wf-seed");
let proc_id = register_process_doc(&cwd, "breakoutwf.process.json", GRID_THEN_WF_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "breakout-wf",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735689600000, "to_ms": 1767225599000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [10, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 3, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "breakoutwf.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "breakoutwf.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let stages = &v["campaign_run"]["cells"][0]["stages"];
assert_eq!(stages[0]["block"], "std::grid", "leading grid stage: {record_line}");
assert_eq!(stages[1]["block"], "std::walk_forward", "walk_forward realized: {record_line}");
assert!(stages[1]["family_id"].as_str().is_some(), "walk-forward persists its family: {record_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;
}
// Ported onto a `[std::grid, std::walk_forward, std::monte_carlo]`
// campaign document (#319 Task 8): the terminal stage's own native
// `bootstrap.pooled_oos` record (`aura_backtest::RBootstrap`, the SAME
// struct the retired CLI's `mc_r_bootstrap_json` wrapper printed) is read
// directly — no client-side reconstruction needed.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "breakout-mc-seed");
let proc_id = register_process_doc(&cwd, "breakoutmc.process.json", GRID_THEN_WF_THEN_MC_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "breakout-mc",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735689600000, "to_ms": 1767225599000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "channel_length": {{ "kind": "I64", "values": [10, 20] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 3, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
);
write_doc(&cwd, "breakoutmc.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "breakoutmc.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let pooled = &v["campaign_run"]["cells"][0]["stages"][2]["bootstrap"]["pooled_oos"];
assert_eq!(pooled["n_resamples"].as_u64(), Some(100), "resamples flow: {record_line}");
assert!(pooled["e_r"]["mean"].is_number(), "bootstrap E[R] present: {record_line}");
}
/// #220 weld-gone proof for generalize: one mean-reversion candidate graded
/// across two instruments — two axes with names the CLI has never heard of —
/// one ganged I64 window and an independent F64 band. 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;
}
// Ported onto a `[std::sweep, std::generalize]` campaign document
// (#319 Task 8): the grade is the campaign's own native
// `generalizations[].generalization` record (`aura_registry::
// Generalization`, computed inside `aura-campaign::exec` — the SAME
// struct the retired `generalize_json` CLI wrapper printed), read
// directly off the final `{"campaign_run":…}` line.
let (cwd, _g) = fresh_project();
let fixture = format!("{}/tests/fixtures/r_meanrev_open.json", env!("CARGO_MANIFEST_DIR"));
let bp_id = seed_blueprint_file(&cwd, &fixture, "meanrev-gen-seed");
let proc_id = register_process_doc(&cwd, "meanrevgen.process.json", SWEEP_THEN_GENERALIZE_PROCESS_DOC);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "meanrev-gen",
"data": {{ "instruments": ["GER40", "USDJPY"],
"windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "window": {{ "kind": "I64", "values": [20] }},
"band.factor": {{ "kind": "F64", "values": [2.0] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"risk": [ {{ "vol": {{ "length": 3, "k": 2.0 }} }} ],
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = GER40_SEPT2024_FROM_MS.parse::<i64>().unwrap(),
to = GER40_SEPT2024_TO_MS.parse::<i64>().unwrap(),
);
write_doc(&cwd, "meanrevgen.campaign.json", &doc);
let (out, code) = run_code_in(&cwd, &["exec", "meanrevgen.campaign.json"]);
if code == Some(3) && (out.contains("no local data") || out.contains("no recorded geometry") || out.contains("no data for instrument")) {
eprintln!("skip: no local GER40/USDJPY data");
return;
}
assert_eq!(code, Some(0), "exit: {out}");
let record_line = out
.lines()
.find(|l| l.starts_with("{\"campaign_run\":"))
.expect("the always-on final campaign_run line");
let v: serde_json::Value = serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
let grade = &v["campaign_run"]["generalizations"][0]["generalization"];
assert!(grade.is_object(), "the aggregate object is present: {record_line}");
assert_eq!(grade["n_instruments"].as_u64(), Some(2), "two instruments graded: {record_line}");
assert!(grade["worst_case"].is_number(), "worst-case floor present: {record_line}");
}
/// Property (#273, list reshape): `aura data list` enumerates the archive's
/// symbols as NDJSON, sorted — the discovery step a campaign author (or an
/// agent operating through the CLI alone, the deployment posture) needs
/// before `aura data info <symbol>`. The archive root resolves through the
/// same project path as `info` (`Env::data_path` — a project-local `[paths]
/// data` override when set). Over a hermetic synthetic archive holding two
/// symbols (`SYMA` 2024-01..08 and `SYMB` 2024-03..06), the verb exits 0 and
/// prints EXACTLY two lines, each a JSON string parseable via
/// `serde_json::from_str::<String>`, in ascending order — `"SYMA"` before
/// `"SYMB"`, nothing else.
#[test]
fn data_list_on_a_populated_archive_is_ndjson_one_symbol_per_line() {
let (cwd, _g) = fresh_project_with_data();
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "list"])
.current_dir(&cwd)
.output()
.expect("spawn aura data list");
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 lines: Vec<&str> = stdout.lines().collect();
assert_eq!(lines.len(), 2, "exactly the two archive symbols: {stdout:?}");
let parsed: Vec<String> = lines
.iter()
.map(|l| serde_json::from_str::<String>(l).unwrap_or_else(|e| panic!("line {l:?} is a JSON string: {e}")))
.collect();
assert_eq!(parsed, vec!["SYMA".to_string(), "SYMB".to_string()], "sorted NDJSON enumeration: {stdout:?}");
}
/// Property (#273 design minutes, 2026-07-25): an empty archive under the
/// all-JSON namespace prints ZERO stdout lines — the honest NDJSON of an
/// empty set, since a placeholder prose line would break `| jq`-style
/// consumption — with the human affordance carried on stderr instead (the
/// `aura: note:` class, #278).
#[test]
fn data_list_on_an_empty_archive_is_empty_stdout_with_a_stderr_notice() {
let (cwd, _g) = fresh_project_with_data();
let data_dir = cwd.join("data");
std::fs::remove_dir_all(&data_dir).expect("clear the synthetic archive");
std::fs::create_dir_all(&data_dir).expect("recreate an empty synthetic archive dir");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "list"])
.current_dir(&cwd)
.output()
.expect("spawn aura data list");
assert_eq!(
out.status.code(),
Some(0),
"exit: {:?}, stderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(out.stdout, b"", "empty archive: zero stdout lines");
let stderr = String::from_utf8(out.stderr).expect("utf-8 stderr");
assert!(stderr.contains("aura: note:") && stderr.contains("no symbols"), "stderr carries the notice: {stderr}");
}
/// Property (#273): `aura data info <SYMBOL>` on a symbol the archive holds
/// bar files for but no `<SYMBOL>.meta.json` sidecar reports bare identity —
/// exit 0, stdout is exactly `{"symbol":"…"}`, no `null` geometry fields.
#[test]
fn data_info_on_a_symbol_without_sidecar_is_bare_symbol_json() {
let (cwd, _g) = fresh_project_with_data();
std::fs::remove_file(cwd.join("data/SYMB.meta.json")).expect("drop SYMB's sidecar");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "info", "SYMB"])
.current_dir(&cwd)
.output()
.expect("spawn aura data info");
assert_eq!(
out.status.code(),
Some(0),
"exit: {:?}, stderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(String::from_utf8(out.stdout).expect("utf-8 stdout"), "{\"symbol\":\"SYMB\"}\n");
}
/// Property (#273): `aura data info <SYMBOL>` on a symbol carrying a usable
/// geometry sidecar surfaces exactly the six neutral geometry fields
/// (`digits`, `pipSize`, `tickSize`, `lotSize`, `baseAsset`, `quoteAsset`)
/// alongside `symbol` — and NOTHING provider-specific (`description`,
/// `generator`, `schemaVersion`) leaks through, even though the sidecar
/// itself carries those fields: `InstrumentGeometry`'s reader never parses
/// them out of the sidecar JSON in the first place (#273 acceptance).
#[test]
fn data_info_on_a_symbol_with_a_sidecar_carries_the_six_geometry_fields() {
let (cwd, _g) = fresh_project_with_data();
std::fs::write(
cwd.join("data/SYMA.meta.json"),
r#"{"schemaVersion":2,"generator":"MS_SimpleExport (cTrader.Automate)","description":"Euro vs US Dollar","digits":5,"pipSize":0.0001,"tickSize":1e-05,"lotSize":100000,"baseAsset":"EUR","quoteAsset":"USD"}"#,
)
.expect("overwrite SYMA's sidecar with the EURUSD-shaped fixture");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "info", "SYMA"])
.current_dir(&cwd)
.output()
.expect("spawn aura data info");
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 obj: serde_json::Value = serde_json::from_str(stdout.trim()).expect("one JSON object");
let map = obj.as_object().expect("a flat object");
let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
keys.sort_unstable();
assert_eq!(
keys,
vec!["baseAsset", "digits", "lotSize", "pipSize", "quoteAsset", "symbol", "tickSize"],
"exactly symbol + the six geometry fields, no provider metadata: {stdout}"
);
assert_eq!(map["symbol"], "SYMA");
assert_eq!(map["digits"], 5);
assert_eq!(map["baseAsset"], "EUR");
assert_eq!(map["quoteAsset"], "USD");
}
/// Property (#273): a symbol carrying a usable geometry sidecar but NO
/// archive bar files is still KNOWN to `aura data info` — the `symbol_meta`
/// arm of the known-check's OR, independent of `has_symbol` — and its
/// geometry reports normally, exit 0. This is the real-world case of a
/// sidecar exported ahead of the bars themselves being ingested.
#[test]
fn data_info_on_a_sidecar_only_symbol_reports_geometry() {
let (cwd, _g) = fresh_project_with_data();
std::fs::write(
cwd.join("data/METAONLY.meta.json"),
r#"{"schemaVersion":2,"generator":"MS_SimpleExport (cTrader.Automate)","description":"Euro vs US Dollar","digits":5,"pipSize":0.0001,"tickSize":1e-05,"lotSize":100000,"baseAsset":"EUR","quoteAsset":"USD"}"#,
)
.expect("write METAONLY's sidecar-only fixture (no archive bar files)");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "info", "METAONLY"])
.current_dir(&cwd)
.output()
.expect("spawn aura data info");
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 obj: serde_json::Value = serde_json::from_str(stdout.trim()).expect("one JSON object");
let map = obj.as_object().expect("a flat object");
let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
keys.sort_unstable();
assert_eq!(
keys,
vec!["baseAsset", "digits", "lotSize", "pipSize", "quoteAsset", "symbol", "tickSize"],
"exactly symbol + the six geometry fields, no provider metadata: {stdout}"
);
assert_eq!(map["symbol"], "METAONLY");
}
/// Property (#273): a symbol with neither archive bar files nor a geometry
/// sidecar is unknown to `aura data info` — refuses on stderr, exit 1, no
/// panic, no bare Debug struct.
#[test]
fn data_info_on_an_unknown_symbol_refuses() {
let (cwd, _g) = fresh_project_with_data();
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["data", "info", "GHOST"])
.current_dir(&cwd)
.output()
.expect("spawn aura data info");
assert_eq!(out.status.code(), Some(1), "stdout: {}", String::from_utf8_lossy(&out.stdout));
let stderr = String::from_utf8(out.stderr).expect("utf-8 stderr");
assert!(stderr.contains("no recorded data for symbol"), "{stderr}");
assert!(stderr.contains("GHOST"), "names the unknown symbol: {stderr}");
}
/// Property (#300): `aura campaign show <CID>` prints the registered
/// document's exact canonical bytes to stdout — round-tripping them back
/// through `aura campaign register` re-derives the SAME content id (canonical
/// bytes are content-id-identical), and the store stays deduplicated at one
/// document. Gated on the shared GER40 archive; skips cleanly on a data
/// refusal (same guard shape as
/// `walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2`).
#[test]
fn campaign_show_round_trips_the_registered_document() {
// Ported onto a one-cell campaign document run through `exec` (#319 Task
// 8) — the `campaign show`/`process show` verbs themselves are untouched
// by the retirement; only the SETUP that used to mint a registered
// document via `walkforward --real` is replaced with a hand-authored
// document over the same real GER40 window.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "campaign-show-seed");
let proc_id = register_process_doc(&cwd, "campaignshow.process.json", GRID_THEN_WF_PROCESS_DOC_90_30_30);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "campaignshow",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": [] }}
}}"#,
);
write_doc(&cwd, "campaignshow.campaign.json", &doc);
let (run_out, run_code) = run_code_in(&cwd, &["exec", "campaignshow.campaign.json"]);
if run_code == Some(3) && (run_out.contains("no local data") || run_out.contains("no recorded geometry") || run_out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(run_code, Some(0), "exec must succeed: {run_out}");
let campaigns_dir = cwd.join("runs").join("campaigns");
let cid = std::fs::read_dir(&campaigns_dir)
.expect("campaigns dir exists")
.next()
.expect("exactly one campaign document")
.expect("readable dir entry")
.path()
.file_stem()
.expect("campaign filename has a stem")
.to_string_lossy()
.into_owned();
let show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["campaign", "show", &cid])
.current_dir(&cwd)
.output()
.expect("spawn aura campaign show");
assert_eq!(
show.status.code(),
Some(0),
"campaign show must succeed: {}",
String::from_utf8_lossy(&show.stderr)
);
// Directly pin the #164 no-framing invariant: `show` must print the
// stored canonical bytes byte-for-byte, with no added trailing newline
// (a `println!` regression in `show_campaign` would still pass the
// re-register check below via `campaign_to_json` -> `content_id_of`
// stripping it back out before hashing, so this comparison is the only
// thing that would catch it).
let stored_bytes = std::fs::read(campaigns_dir.join(format!("{cid}.json"))).expect("read stored campaign document");
assert_eq!(
show.stdout, stored_bytes,
"campaign show must print the stored canonical bytes byte-for-byte, no framing"
);
let roundtrip_path = cwd.join("roundtrip.campaign.json");
std::fs::write(&roundtrip_path, &show.stdout).expect("write roundtrip campaign doc");
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["campaign", "register", roundtrip_path.to_str().expect("utf-8 path")])
.current_dir(&cwd)
.output()
.expect("spawn aura campaign register");
assert_eq!(
register.status.code(),
Some(0),
"re-registering the shown document must succeed: {}",
String::from_utf8_lossy(&register.stderr)
);
let register_out = String::from_utf8_lossy(&register.stdout).into_owned();
assert!(
register_out.contains(&format!("registered campaign {cid}")),
"canonical bytes must re-register content-id-identically to {cid}: {register_out}"
);
let count = std::fs::read_dir(&campaigns_dir).expect("campaigns dir exists").count();
assert_eq!(count, 1, "the shown bytes dedup onto the same one stored document");
}
/// Property (#300): identical to `campaign_show_round_trips_the_registered_document`
/// but over the generated process document — `aura process show <PID>` prints
/// canonical bytes that re-register content-id-identically through `aura
/// process register`, with the store staying deduplicated at one document.
#[test]
fn process_show_round_trips_the_registered_document() {
// Ported onto a one-cell campaign document run through `exec` (#319 Task
// 8) — same setup rationale as the sibling `campaign_show_round_trips_
// the_registered_document` above.
let (cwd, _g) = fresh_project();
let bp_id = seed_blueprint(&cwd, "process-show-seed");
let proc_id = register_process_doc(&cwd, "processshow.process.json", GRID_THEN_WF_PROCESS_DOC_90_30_30);
let doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "processshow",
"data": {{ "instruments": ["GER40"], "windows": [ {{ "from_ms": 1735776899999, "to_ms": 1767128220000 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [3] }},
"slow.length": {{ "kind": "I64", "values": [12] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": [] }}
}}"#,
);
write_doc(&cwd, "processshow.campaign.json", &doc);
let (run_out, run_code) = run_code_in(&cwd, &["exec", "processshow.campaign.json"]);
if run_code == Some(3) && (run_out.contains("no local data") || run_out.contains("no recorded geometry") || run_out.contains("no data for instrument")) {
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(run_code, Some(0), "exec must succeed: {run_out}");
let processes_dir = cwd.join("runs").join("processes");
let pid = std::fs::read_dir(&processes_dir)
.expect("processes dir exists")
.next()
.expect("exactly one process document")
.expect("readable dir entry")
.path()
.file_stem()
.expect("process filename has a stem")
.to_string_lossy()
.into_owned();
let show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "show", &pid])
.current_dir(&cwd)
.output()
.expect("spawn aura process show");
assert_eq!(
show.status.code(),
Some(0),
"process show must succeed: {}",
String::from_utf8_lossy(&show.stderr)
);
// Directly pin the #164 no-framing invariant (see the sibling campaign
// test's comment): `show` must print the stored canonical bytes
// byte-for-byte, with no added trailing newline.
let stored_bytes = std::fs::read(processes_dir.join(format!("{pid}.json"))).expect("read stored process document");
assert_eq!(
show.stdout, stored_bytes,
"process show must print the stored canonical bytes byte-for-byte, no framing"
);
let roundtrip_path = cwd.join("roundtrip.process.json");
std::fs::write(&roundtrip_path, &show.stdout).expect("write roundtrip process doc");
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "register", roundtrip_path.to_str().expect("utf-8 path")])
.current_dir(&cwd)
.output()
.expect("spawn aura process register");
assert_eq!(
register.status.code(),
Some(0),
"re-registering the shown document must succeed: {}",
String::from_utf8_lossy(&register.stderr)
);
let register_out = String::from_utf8_lossy(&register.stdout).into_owned();
assert!(
register_out.contains(&format!("registered process {pid}")),
"canonical bytes must re-register content-id-identically to {pid}: {register_out}"
);
let count = std::fs::read_dir(&processes_dir).expect("processes dir exists").count();
assert_eq!(count, 1, "the shown bytes dedup onto the same one stored document");
}
/// Property (#300): `show` over an unknown id refuses with the same
/// project-store-not-found prose the referential tier already uses for a
/// missing `process.ref`/`strategy.ref` (`ref_fault_prose`'s
/// `ProcessNotFound`/campaign-equivalent hint shape) — no project run needed,
/// so this test needs no archive gate.
#[test]
fn show_refuses_an_unknown_id_with_the_store_hint() {
let (cwd, _g) = fresh_project();
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["campaign", "show", "deadbeef"])
.current_dir(&cwd)
.output()
.expect("spawn aura campaign show");
assert_eq!(campaign_show.status.code(), Some(1), "unknown campaign id is a runtime refusal");
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
assert!(
campaign_stderr.contains("campaign deadbeef not found in the project store"),
"campaign show stderr: {campaign_stderr}"
);
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "show", "deadbeef"])
.current_dir(&cwd)
.output()
.expect("spawn aura process show");
assert_eq!(process_show.status.code(), Some(1), "unknown process id is a runtime refusal");
let process_stderr = String::from_utf8_lossy(&process_show.stderr);
assert!(
process_stderr.contains("process deadbeef not found in the project store"),
"process show stderr: {process_stderr}"
);
}
/// Property (#300): `show`'s not-found prose reuses the existing `prefix_hint`
/// authoring-trap detector (#194) — an id pasted WITH the display-only
/// `content:` prefix (the form `ref_fault_prose` already prints elsewhere for
/// an unresolved `process.ref`/`strategy.ref`) still gets the "drop the
/// 'content:' prefix" hint through the new `show` code path, not a bare
/// not-found. This is the one authoring mistake the store can name outright,
/// and it must survive `show` reusing `ref_fault_prose`'s helper rather than
/// re-deriving its own prose.
#[test]
fn show_hints_the_content_prefix_authoring_mistake() {
let (cwd, _g) = fresh_project();
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["campaign", "show", "content:deadbeef"])
.current_dir(&cwd)
.output()
.expect("spawn aura campaign show");
assert_eq!(campaign_show.status.code(), Some(1), "unresolvable id is a runtime refusal");
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
assert!(
campaign_stderr.contains("drop the 'content:' prefix"),
"campaign show must hint the content: authoring mistake: {campaign_stderr}"
);
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "show", "content:deadbeef"])
.current_dir(&cwd)
.output()
.expect("spawn aura process show");
assert_eq!(process_show.status.code(), Some(1), "unresolvable id is a runtime refusal");
let process_stderr = String::from_utf8_lossy(&process_show.stderr);
assert!(
process_stderr.contains("drop the 'content:' prefix"),
"process show must hint the content: authoring mistake: {process_stderr}"
);
}
/// Property (#300): `show` is scoped to its own document-kind store — a
/// content id that IS a real, registered document in one store (here: a
/// minimal sweep-only process) does not resolve through the OTHER verb
/// (`campaign show`). `show_process`/`show_campaign` are two near-identical
/// functions written in the same change (both `registry.get_*(id) -> Option
/// -> print! the bytes or format! a not-found`); this pins that they read
/// from their own store method and neither accidentally shares the other's
/// namespace or falls back to a generic content-blob lookup.
#[test]
fn show_is_scoped_to_its_own_document_store() {
let (cwd, _g) = fresh_project();
const SWEEP_ONLY_PROCESS: &str = r#"{"format_version":1,"kind":"process","name":"knob-sweep-only","pipeline":[{"block":"std::sweep","metric":"sqn_normalized","select":"argmax"}]}"#;
std::fs::write(cwd.join("knob.process.json"), SWEEP_ONLY_PROCESS).expect("write process doc");
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "register", "knob.process.json"])
.current_dir(&cwd)
.output()
.expect("spawn aura process register");
assert_eq!(
register.status.code(),
Some(0),
"process register must succeed: {}",
String::from_utf8_lossy(&register.stderr)
);
let register_out = String::from_utf8_lossy(&register.stdout).into_owned();
let pid = register_out
.lines()
.find(|l| l.starts_with("registered process "))
.expect("register line")
.trim_start_matches("registered process ")
.split(' ')
.next()
.expect("process id")
.to_string();
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "show", &pid])
.current_dir(&cwd)
.output()
.expect("spawn aura process show");
assert_eq!(
process_show.status.code(),
Some(0),
"process show must find the just-registered process: {}",
String::from_utf8_lossy(&process_show.stderr)
);
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["campaign", "show", &pid])
.current_dir(&cwd)
.output()
.expect("spawn aura campaign show");
assert_eq!(
campaign_show.status.code(),
Some(1),
"a process id must NOT resolve through campaign show: {}",
String::from_utf8_lossy(&campaign_show.stdout)
);
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
assert!(
campaign_stderr.contains(&format!("campaign {pid} not found in the project store")),
"campaign show stderr: {campaign_stderr}"
);
}
/// Property (#302): `show` mirrors the prefix resolution `reproduce` gained
/// with #298 — a unique prefix of a registered content id, in the 8-hex form
/// every surface prints (family ids, run records), resolves to the registered
/// document, and `show` prints EXACTLY the bytes the full-id invocation
/// prints (the #164 byte-discipline: the stored canonical bytes, no framing).
/// One document per store makes any prefix of its id unique by construction.
/// Deliberately NOT pinned here: the ambiguous-prefix refusal (listing the
/// candidate ids) — content ids are hash-derived, not choosable, so two
/// documents sharing a printed prefix cannot be constructed honestly at this
/// layer; that arm belongs beside the resolution seam, where candidate id
/// lists are plain strings.
#[test]
fn show_resolves_a_unique_prefix_to_the_full_id_bytes() {
let (cwd, _g) = fresh_project();
fn aura(cwd: &Path, args: &[&str]) -> std::process::Output {
Command::new(BIN).args(args).current_dir(cwd).output().expect("spawn aura")
}
const PREFIX_PROBE_PROCESS: &str = r#"{"format_version":1,"kind":"process","name":"prefix-probe","pipeline":[{"block":"std::sweep","metric":"sqn_normalized","select":"argmax"}]}"#;
const PREFIX_PROBE_CAMPAIGN: &str = r#"{
"format_version": 1,
"kind": "campaign",
"name": "prefix-probe",
"data": { "instruments": ["GER40"], "windows": [ { "from_ms": 1, "to_ms": 2 } ] },
"strategies": [ { "ref": { "content_id": "9f3a" },
"axes": { "fast": { "kind": "I64", "values": [8] } } } ],
"process": { "ref": { "content_id": "4e2d" } },
"seed": 1,
"presentation": { "persist_taps": [], "emit": ["family_table"] }
}"#;
for (verb, file, doc) in [
("process", "probe.process.json", PREFIX_PROBE_PROCESS),
("campaign", "probe.campaign.json", PREFIX_PROBE_CAMPAIGN),
] {
std::fs::write(cwd.join(file), doc).expect("write document");
let register = aura(&cwd, &[verb, "register", file]);
assert_eq!(
register.status.code(),
Some(0),
"{verb} register must succeed: {}",
String::from_utf8_lossy(&register.stderr)
);
let register_out = String::from_utf8_lossy(&register.stdout).into_owned();
let marker = format!("registered {verb} ");
let id = register_out
.lines()
.find(|l| l.starts_with(&marker))
.expect("register line")
.trim_start_matches(marker.as_str())
.split(' ')
.next()
.expect("content id")
.to_string();
assert!(id.len() > 8, "content id longer than the printed 8-hex prefix form: {id}");
let full = aura(&cwd, &[verb, "show", &id]);
assert_eq!(
full.status.code(),
Some(0),
"{verb} show <full id> must succeed: {}",
String::from_utf8_lossy(&full.stderr)
);
assert!(!full.stdout.is_empty(), "{verb} show <full id> prints the document bytes");
// The headline: the 8-hex prefix — unique in this one-document store —
// resolves, and the output is byte-identical to the full-id output.
let prefix = &id[..8];
let short = aura(&cwd, &[verb, "show", prefix]);
assert_eq!(
short.status.code(),
Some(0),
"{verb} show {prefix} (unique prefix of {id}) must resolve: {}",
String::from_utf8_lossy(&short.stderr)
);
assert_eq!(
short.stdout, full.stdout,
"{verb} show via unique prefix must print the identical document bytes"
);
}
}