//! 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) { 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 `.` 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 ` 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 ''" 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////index.json` (one directory level deeper /// than the campaign path's depth-1 nominee layout `//index.json`). /// `aura chart ` 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 (`///index.json`) — not the // depth-1 nominee layout (`//index.json`) the campaign path writes. // `TraceStore::open` roots at `/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("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. Originally modelled on /// that sibling (the retired synthetic in-process walk-forward dispatch verb — /// no `--real`), NOT on `reproduce_real_sweep_family_re_derives_bit_identically`: /// a real-data mint routed through the campaign executor /// (`CliMemberRunner::run_member` + `validate_and_register_axes`), neither of /// which carried any #246 override threading — a gap outside that task's file /// scope (`blueprint_sweep_over`, `reproduce_family_in`) and spanning all four /// (then-live) dispatch verbs, so exercising it there would have silently /// expanded into unrelated, unreviewed surface. Back when the synthetic /// walk-forward dispatch verb existed, its `--axis` intake was RAW (#328): the /// CLI preflight refused a wrapped name before the run ever started /// (`refuse_wrapped_synthetic_axes`), and `blueprint_sweep_over`'s own /// `SweepBinder` call translated the RAW name onto its wrapped `param_space()` /// slot (`wrapped_name_of`) before keying by it. That path exercised exactly /// the threaded functions: `blueprint_sweep_over` (via /// `blueprint_walkforward_family`) re-opened `fast.length` for the per-window /// IS re-fit, and `reproduce_family_in` re-opened it again from the recorded /// manifest params so the re-derivation resolved against the same space the /// mint used. #319 Task 8 ported this test onto the campaign /// `std::walk_forward` stage (see the in-body port note below); the property /// pinned here is unchanged. #[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, ported onto the campaign /// `std::generalize` stage by #319 Task 8): grades ANY sweepable blueprint, /// not just the historical r-sma candidate — originally the whole point of /// the retired `aura generalize` verb dropping `--strategy r-sma`/`--fast`/ /// `--slow` for a positional blueprint + generic `--axis`; the property /// survives the #319 dissolution onto a `std::sweep, std::generalize` /// process document unchanged. 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, ported onto the campaign path by #319 Task 8). The original `aura /// walkforward` verb ran its per-window IS-refit inline (`walkforward_family` /// -> `select_winner` -> OOS run -> stitch/pool); the dissolution rerouted the /// same rolling IS-sweep -> OOS-run -> deflation through the campaign /// `std::walk_forward` stage, retaining the OOS pip curve the campaign runner /// used to discard (`oos_equity: vec![]`) so it could still 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 could not have shipped green. This pins the /// EXACT grade of the identical invocation over a fixed 2025 GER40 window that /// the dissolved `std::walk_forward` stage reproduces bit-for-bit (the /// acceptance gate the port had to clear). 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, ported onto the campaign /// `std::walk_forward` stage by #319 Task 8): IS-refits ANY sweepable /// blueprint, not just the historical r-sma candidate — originally the /// whole point of the retired `aura walkforward --real` verb dropping /// `--strategy r-sma`/`--fast`/`--slow` for a positional blueprint + generic /// `--axis`; the property survives the #319 dissolution unchanged. 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(®ister.stderr) ); let register_out = String::from_utf8_lossy(®ister.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(®ister.stderr) ); let register_out = String::from_utf8_lossy(®ister.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(®ister.stderr) ); let register_out = String::from_utf8_lossy(®ister.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(®ister.stderr) ); let register_out = String::from_utf8_lossy(®ister.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" ); } }