feat(0095): monte-carlo over a loaded blueprint — aura mc <bp.json> --seeds N
The MC half of #170 (World/C21): `aura mc <closed blueprint.json> --seeds N` builds a FamilyKind::MonteCarlo family from a loaded CLOSED blueprint. Each seed draws a distinct synthetic price walk (synthetic_walk_sources, the mc_family pattern), so members differ in the DATA (not just a manifest label) — the sim is deterministic (C1), so MC variation must come from re-drawing the input. Draws run disjointly in parallel via the shared monte_carlo helper (invariant 1). The canonical blueprint is stored once keyed by the shared topology_hash (the 0094 hook), and reproduce_family_in gains a MonteCarlo realization branch that reconstructs each member's seed-driven walk from manifest.seed — so a persisted mc family 'aura reproduce's bit-identically (the store-hook proof). Closed-blueprint only: MC binds no axis, so a free knob has no binder; an open blueprint yields a named Err (rendered to stderr + exit 2 at the run_blueprint_mc boundary, mirroring blueprint_sweep_family — no hidden process exit in the pure builder), pre-empting the compile_with_params arity panic. Two quality-gate-driven improvements over the ratified plan, both applied and verified: (1) delegate the disjoint draws to the engine monte_carlo seam instead of a hand-rolled loop (my plan's 'closure-shape mismatch' rationale was wrong — the Fn(u64,&[Scalar]) closure ignores the unused base_point, exactly as mc_family does); (2) the closed-blueprint guard returns Result rather than exiting inside the builder, matching the sweep sibling's contract and making the rejection unit-testable. Engine + registry untouched (invariants 8/9). Verified: cargo build --workspace clean; cargo clippy --workspace --all-targets -- -D warnings clean; cargo test --workspace green (5 new tests: the parser, anti-degenerate family, MC reproduce round-trip, and two E2Es incl. the open rejection; no regressions). refs #170
This commit is contained in:
@@ -3488,6 +3488,68 @@ fn aura_reproduce_re_derives_a_persisted_sweep_bit_identically() {
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// E2E (acc 1+3+4): `aura mc <closed blueprint.json> --seeds N` builds a MonteCarlo
|
||||
/// family (one member per seed), writes exactly one content-addressed blueprint, and
|
||||
/// `aura reproduce <id>` re-derives every member bit-identically (exit 0).
|
||||
#[test]
|
||||
fn aura_mc_over_a_blueprint_reproduces_bit_identically() {
|
||||
let cwd = temp_cwd("mc-blueprint-reproduce");
|
||||
let fixture = format!("{}/tests/fixtures/stage1_signal.json", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
let mc = Command::new(BIN)
|
||||
.args(["mc", &fixture, "--seeds", "4", "--name", "mcx"])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura mc blueprint");
|
||||
assert!(mc.status.success(), "mc exit: {:?} stderr={}", mc.status, String::from_utf8_lossy(&mc.stderr));
|
||||
let mc_out = String::from_utf8(mc.stdout).expect("utf-8 stdout");
|
||||
// one member line per seed (each carries "seed":n) + one aggregate line.
|
||||
assert_eq!(
|
||||
mc_out.lines().filter(|l| l.contains("\"seed\":")).count(),
|
||||
4,
|
||||
"one member line per seed: {mc_out}"
|
||||
);
|
||||
assert!(mc_out.contains("mc_aggregate"), "prints the aggregate line: {mc_out}");
|
||||
|
||||
// exactly one blueprint stored for the whole family (one topology per family).
|
||||
let store = cwd.join("runs/blueprints");
|
||||
let entries: Vec<_> = std::fs::read_dir(&store)
|
||||
.unwrap_or_else(|e| panic!("mc must create the blueprint store {store:?}: {e}"))
|
||||
.map(|e| e.expect("dir entry").path())
|
||||
.collect();
|
||||
assert_eq!(entries.len(), 1, "one stored topology per family: {entries:?}");
|
||||
|
||||
let repro = Command::new(BIN)
|
||||
.args(["reproduce", "mcx-0"])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura reproduce");
|
||||
assert!(repro.status.success(), "reproduce exit: {:?} stderr={}", repro.status, String::from_utf8_lossy(&repro.stderr));
|
||||
let repro_out = String::from_utf8(repro.stdout).expect("utf-8 stdout");
|
||||
assert!(
|
||||
repro_out.contains("reproduced 4/4 members bit-identically"),
|
||||
"every MC member re-derives: {repro_out}"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// E2E (acc 2): `aura mc <open blueprint.json>` is rejected with a named error + exit 2
|
||||
/// (NOT a panic / exit 101) — MC binds no axis, so a free knob has no binder.
|
||||
#[test]
|
||||
fn aura_mc_rejects_an_open_blueprint() {
|
||||
let cwd = temp_cwd("mc-blueprint-open-reject");
|
||||
let fixture = format!("{}/tests/fixtures/stage1_signal_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = Command::new(BIN)
|
||||
.args(["mc", &fixture, "--seeds", "4"])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura mc open-blueprint");
|
||||
assert_eq!(out.status.code(), Some(2), "an open blueprint fails clean (exit 2, not a panic): stderr={}", String::from_utf8_lossy(&out.stderr));
|
||||
let stderr = String::from_utf8(out.stderr).expect("utf-8");
|
||||
assert!(stderr.contains("closed blueprint"), "names the closed-blueprint requirement: {stderr}");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// 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]
|
||||
|
||||
Reference in New Issue
Block a user