plan: 0095 monte-carlo over a loaded blueprint

4 tasks: parse_mc_blueprint_args, blueprint_mc_family (closed-blueprint
guard + seed-driven synthetic walks), the reproduce MonteCarlo realization
branch, and run_blueprint_mc + the .json dispatch arm + E2E. All in
aura-cli/main.rs (engine/registry untouched).
This commit is contained in:
2026-07-01 12:07:52 +02:00
parent 7116660fe8
commit 860b89ad93
@@ -0,0 +1,565 @@
# Monte-Carlo over a Loaded Blueprint — Implementation Plan
> **Parent spec:** `docs/specs/0095-mc-over-a-loaded-blueprint.md`
>
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run
> this plan. Steps use `- [ ]` checkboxes for tracking.
**Goal:** Add `aura mc <blueprint.json> --seeds N` — a Monte-Carlo family from a
loaded CLOSED blueprint, each seed drawing a distinct synthetic walk — persisted as
`FamilyKind::MonteCarlo` with the content-addressed store hook, `aura reproduce`
bit-identical.
**Architecture:** All code lands in `crates/aura-cli/src/main.rs` (engine + registry
untouched — invariants 8/9). A new `.json`-discriminator on the `["mc", ..]` dispatch
arm (mirroring the 0093 sweep arm) routes to `blueprint_mc_family` (builds an
`McFamily` by running the fixed closed blueprint over N seed-driven synthetic walks
through the shared `run_blueprint_member` path) → `run_blueprint_mc` (the persist seam:
`put_blueprint` + `append_family(MonteCarlo)` + print). `reproduce_family_in` gains a
`MonteCarlo` realization branch that reconstructs each member's seed-driven walk.
**Tech Stack:** Rust; `SyntheticSpec::source(seed)` (aura-engine), `McFamily`/`McDraw`/
`McAggregate` (aura-engine), `run_blueprint_member` (unchanged), `Registry::put_blueprint`
+ `append_family` (aura-registry), `topology_hash`/`content_id` (local).
---
**Files this plan creates or modifies:**
- Modify: `crates/aura-cli/src/main.rs` — new `parse_mc_blueprint_args` + `McBlueprintArgs`
(beside `parse_sweep_blueprint_args` ~3797-3835); new `synthetic_walk_sources` +
`blueprint_mc_family` + `run_blueprint_mc` (beside `blueprint_sweep_family`/`run_blueprint_sweep`
~3154-3246); the `MonteCarlo` branch in `reproduce_family_in` (~2503-2574, the run at ~2561);
the `["mc", rest @ ..]` dispatch arm (~4006-4015).
- Test: `crates/aura-cli/src/main.rs` — unit tests beside the sweep units (~5144-5230):
`parse_mc_blueprint_args_grammar`, `blueprint_mc_family_seeds_differ`,
`reproduce_family_re_derives_every_mc_member_bit_identically`.
- Test: `crates/aura-cli/tests/cli_run.rs` — E2E beside the sweep/reproduce E2Es (~3296-3489):
`aura_mc_over_a_blueprint_reproduces_bit_identically`, `aura_mc_rejects_an_open_blueprint`.
Facts pinned by plan-recon (line refs are current; re-anchor by symbol if drifted):
- `run_blueprint_member(signal: Composite, point: &[Cell], space: &[ParamSpec], sources:
Vec<Box<dyn aura_engine::Source>>, window: (Timestamp,Timestamp), seed: u64, pip: f64,
topo: &str) -> RunReport` — **unchanged**; stamps `manifest.seed`/`manifest.topology_hash`.
- `McDraw { pub seed: u64, pub report: RunReport }`, `McFamily { pub draws, pub aggregate }`,
`McAggregate::from_draws(&[McDraw]) -> McAggregate` (all pub; `from_draws` needs draws non-empty).
- `SyntheticSpec { start: f64, len: usize, step: i64 }`; `.source(seed) -> impl Source`;
`window_of(&[Box<dyn Source>]) -> Option<(Timestamp,Timestamp)>`.
- `mc_member_line(id, seed, report) -> String`; `mc_aggregate_json(&McAggregate) -> String`;
`mc_member_reports(&McFamily) -> Vec<RunReport>`.
- `Family { pub id, pub kind: FamilyKind, pub members: Vec<FamilyRunRecord> }`;
`FamilyKind::MonteCarlo` is a variant. `stage1_signal(fast: Option<i64>, slow: Option<i64>)
-> Composite` (Some binds → closed). `STAGE1_R_STOP_LENGTH = 3`, fixture SMA slow=4 → the
graph warms in ~4 bars, so a len-60 walk yields many trades that differ across seeds.
- All needed imports (`Cell`, `ParamSpec`, `Composite`, `McFamily`, `McDraw`, `McAggregate`,
`SyntheticSpec`, `window_of`, `blueprint_from_json`, `blueprint_to_json`, `FamilyKind`,
`mc_member_reports`, `std_vocabulary`) are already in `main.rs` — no `use` edits.
---
### Task 1: `parse_mc_blueprint_args` + `McBlueprintArgs`
**Files:**
- Modify: `crates/aura-cli/src/main.rs` (beside `parse_sweep_blueprint_args` ~3797-3835)
- Test: `crates/aura-cli/src/main.rs` (beside the sweep-arg unit `parse_sweep_blueprint_args_grammar` ~5107)
- [ ] **Step 1: Write the failing test**
Add to the `#[cfg(test)] mod tests` block, beside `parse_sweep_blueprint_args_grammar`:
```rust
#[test]
fn parse_mc_blueprint_args_grammar() {
// --seeds required and parsed; default name "mc"; seeds >= 1; unknown/real flags rejected.
let ok = parse_mc_blueprint_args(&["--seeds", "8", "--name", "mc1"]).expect("valid");
assert_eq!(ok.n_seeds, 8);
assert_eq!(ok.name, "mc1");
let def = parse_mc_blueprint_args(&["--seeds", "3"]).expect("default name");
assert_eq!(def.name, "mc");
assert!(parse_mc_blueprint_args(&[]).is_err(), "--seeds is required");
assert!(parse_mc_blueprint_args(&["--seeds", "0"]).is_err(), "seeds must be >= 1");
assert!(parse_mc_blueprint_args(&["--seeds", "x"]).is_err(), "seeds must be a number");
assert!(
parse_mc_blueprint_args(&["--seeds", "4", "--real", "EURUSD"]).is_err(),
"synthetic-only this cycle: --real is rejected (real-data MC is #172)"
);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `cargo test -p aura-cli --bin aura parse_mc_blueprint_args_grammar`
Expected: FAIL — compile error `cannot find function parse_mc_blueprint_args` (the fn/struct do not exist yet).
- [ ] **Step 3: Write minimal implementation**
Add beside `parse_sweep_blueprint_args` (~3797):
```rust
/// Parsed tail of `aura mc <blueprint.json> …`: the seed count + the family name.
/// Synthetic-only this cycle (real-data MC rides #172), so `--real`/`--from`/`--to`
/// are rejected as unknown flags. Mirrors `parse_sweep_blueprint_args`' shape.
struct McBlueprintArgs {
n_seeds: u64,
name: String,
}
fn parse_mc_blueprint_args(rest: &[&str]) -> Result<McBlueprintArgs, String> {
let usage = || "usage: mc <blueprint.json> --seeds <n> [--name <n>]".to_string();
let mut n_seeds: Option<u64> = None;
let mut name: Option<String> = None;
let mut tail = rest;
while let Some((flag, t)) = tail.split_first() {
let (value, t) = t.split_first().ok_or_else(usage)?;
match *flag {
"--seeds" => {
let n: u64 = value.parse().map_err(|_| usage())?;
if n == 0 {
return Err(usage());
}
n_seeds = Some(n);
}
"--name" if name.is_none() => name = Some((*value).to_string()),
_ => return Err(usage()),
}
tail = t;
}
let n_seeds = n_seeds.ok_or_else(usage)?; // --seeds is required
Ok(McBlueprintArgs { n_seeds, name: name.unwrap_or_else(|| "mc".to_string()) })
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `cargo test -p aura-cli --bin aura parse_mc_blueprint_args_grammar`
Expected: PASS.
---
### Task 2: `synthetic_walk_sources` + `blueprint_mc_family` (closed-blueprint guard)
**Files:**
- Modify: `crates/aura-cli/src/main.rs` (beside `blueprint_sweep_family` ~3154; `mc_family` pattern ~2210)
- Test: `crates/aura-cli/src/main.rs` (beside `blueprint_sweep_member_equals_single_run_and_shares_topology_hash` ~5151)
- [ ] **Step 1: Write the failing test**
```rust
#[test]
fn blueprint_mc_family_seeds_differ() {
// MC over a CLOSED signal (both SMA knobs bound): 3 seeds -> 3 draws, one shared
// topology_hash, and DIFFERING metrics — the seed reaches the DATA (a distinct
// synthetic walk per draw), not just the manifest label. The anti-degenerate guard:
// a regression to seed-as-label-only would make the three draws identical.
let closed = stage1_signal(Some(2), Some(4));
let doc = blueprint_to_json(&closed).expect("serializes");
let family = blueprint_mc_family(&doc, 3, &DataSource::Synthetic);
assert_eq!(family.draws.len(), 3, "one draw per seed");
assert_eq!(family.draws.iter().map(|d| d.seed).collect::<Vec<_>>(), vec![1, 2, 3]);
let topo = family.draws[0].report.manifest.topology_hash.clone();
assert!(topo.is_some(), "members carry a topology_hash");
assert!(
family.draws.iter().all(|d| d.report.manifest.topology_hash == topo),
"all members share one topology_hash"
);
let m: Vec<_> = family.draws.iter().map(|d| &d.report.metrics).collect();
assert!(m[0] != m[1] || m[1] != m[2], "seeds must yield differing realizations");
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `cargo test -p aura-cli --bin aura blueprint_mc_family_seeds_differ`
Expected: FAIL — compile error `cannot find function blueprint_mc_family` (fn does not exist yet).
- [ ] **Step 3: Write minimal implementation**
Add beside `blueprint_sweep_family` (~3154):
```rust
/// A fresh seeded synthetic price walk for one Monte-Carlo draw — the `mc_family`
/// pattern (a distinct realization per seed). A FIXED `SyntheticSpec` shared by the
/// `aura mc <blueprint.json>` persist path AND the reproduce MonteCarlo branch, so the
/// seed->walk reconstruction is bit-exact (C1). Length 60 comfortably warms the loaded
/// stage1-r graph (SMA slow=4 + the len-3 vol stop) so draws produce differing trades.
fn synthetic_walk_sources(seed: u64) -> Vec<Box<dyn aura_engine::Source>> {
let spec = SyntheticSpec { start: 1.0, len: 60, step: 1 };
vec![Box::new(spec.source(seed))]
}
/// Build a Monte-Carlo family from a loaded CLOSED signal blueprint: run the fixed
/// blueprint across `n_seeds` seeds, each seed drawing a distinct synthetic walk. The
/// blueprint must be CLOSED (empty wrapped `param_space`) — MC binds no axis, so a free
/// knob has no binder; an OPEN blueprint is rejected with a named error (exit 2) before
/// any run, pre-empting the `compile_with_params` arity panic. Each draw runs the shared
/// reduce-mode member path (`run_blueprint_member`, the same fn reproduce re-runs), so
/// reproduction is bit-identical (C1); every member carries the shared `topology_hash`.
fn blueprint_mc_family(doc: &str, n_seeds: u64, data: &DataSource) -> McFamily {
let reload = |d: &str| {
blueprint_from_json(d, &|t| std_vocabulary(t))
.expect("doc parse-validated at the dispatch boundary; reload is infallible")
};
let topo = topology_hash(&reload(doc));
let pip = data.pip_size();
// probe the wrapped param_space (as blueprint_sweep_family does); MC needs it empty.
let (tx_eq, _) = mpsc::channel();
let (tx_ex, _) = mpsc::channel();
let (tx_r, _) = mpsc::channel();
let (tx_req, _) = mpsc::channel();
let space =
wrap_stage1r(reload(doc), tx_eq, tx_ex, tx_r, tx_req, false, true, None).param_space();
if !space.is_empty() {
eprintln!(
"aura: mc requires a closed blueprint (no free parameters); {} free knob(s) — \
bind them or use `aura sweep --axis`",
space.len()
);
std::process::exit(2);
}
let point: Vec<Cell> = Vec::new(); // closed -> empty point (as `aura run <blueprint.json>`)
let draws: Vec<McDraw> = (1..=n_seeds)
.map(|seed| {
let sources = synthetic_walk_sources(seed);
let window = window_of(&sources).expect("non-empty synthetic walk");
let report =
run_blueprint_member(reload(doc), &point, &space, sources, window, seed, pip, &topo);
McDraw { seed, report }
})
.collect();
let aggregate = McAggregate::from_draws(&draws);
McFamily { draws, aggregate }
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `cargo test -p aura-cli --bin aura blueprint_mc_family_seeds_differ`
Expected: PASS. (If the three draws come back identical, the walk did not warm the
stop — increase `SyntheticSpec.len`; 60 should suffice for slow=4 + stop-len 3.)
---
### Task 3: `reproduce_family_in` MonteCarlo realization branch
**Files:**
- Modify: `crates/aura-cli/src/main.rs` (`reproduce_family_in` ~2503-2574; the run at ~2561-2570)
- Test: `crates/aura-cli/src/main.rs` (beside `reproduce_family_re_derives_every_member_bit_identically` ~5194)
- [ ] **Step 1: Write the failing test**
```rust
#[test]
fn reproduce_family_re_derives_every_mc_member_bit_identically() {
let dir = std::env::temp_dir().join(format!("aura-repro-mc-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
// a CLOSED signal (both SMA knobs bound) — MC binds no axis.
let closed = stage1_signal(Some(2), Some(4));
let doc = blueprint_to_json(&closed).expect("serializes");
let data = DataSource::Synthetic;
let family = blueprint_mc_family(&doc, 3, &data);
// persist exactly as run_blueprint_mc does: store the blueprint, append the MC family.
let topo = family.draws[0].report.manifest.topology_hash.clone().expect("topo");
let canonical =
blueprint_to_json(&blueprint_from_json(&doc, &|t| std_vocabulary(t)).unwrap()).unwrap();
reg.put_blueprint(&topo, &canonical).expect("store blueprint");
let id = reg
.append_family("mcrepro", FamilyKind::MonteCarlo, &mc_member_reports(&family))
.expect("append");
// reproduce: every MC member re-derives bit-identically (its seed-driven walk is
// reconstructed from manifest.seed — the realization branch).
let rep = reproduce_family_in(&reg, &id, &data);
assert_eq!(rep.outcomes.len(), 3, "three MC members reproduced");
assert!(
rep.outcomes.iter().all(|(_, ok)| *ok),
"every MC member re-derives bit-identically: {:?}",
rep.outcomes
);
let _ = std::fs::remove_dir_all(&dir);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `cargo test -p aura-cli --bin aura reproduce_family_re_derives_every_mc_member_bit_identically`
Expected: FAIL — the assertion `every MC member re-derives bit-identically` fails: without
the branch, reproduce re-runs MC members over `data.run_sources()` (the showcase series),
not the seed-driven walk, so `rerun.metrics != stored.metrics` for every member.
- [ ] **Step 3: Write minimal implementation**
In `reproduce_family_in`, replace the single member run (currently ~2561-2570):
```rust
let rerun = run_blueprint_member(
reload(),
&point,
&space,
data.run_sources(),
window,
stored.manifest.seed,
pip,
&hash,
);
```
with a realization-aware version that reconstructs the member's data from the facet its
family kind recorded:
```rust
// Realization-aware: a MonteCarlo member ran over a seed-driven synthetic walk,
// not the showcase — reconstruct it from manifest.seed so the re-run matches (C1).
let (sources, member_window, seed) = match family.kind {
FamilyKind::MonteCarlo => {
let s = synthetic_walk_sources(stored.manifest.seed);
let w = window_of(&s).expect("non-empty synthetic walk");
(s, w, stored.manifest.seed)
}
_ => (data.run_sources(), window, stored.manifest.seed),
};
let rerun = run_blueprint_member(
reload(),
&point,
&space,
sources,
member_window,
seed,
pip,
&hash,
);
```
(`window` is the pre-loop `data.full_window()` at ~2515, used unchanged for the `_` arm;
`family.kind` is on the grouped `Family` the loop iterates.)
- [ ] **Step 4: Run test to verify it passes**
Run: `cargo test -p aura-cli --bin aura reproduce_family_re_derives_every_mc_member_bit_identically`
Expected: PASS.
- [ ] **Step 5: Verify the Sweep reproduce path stays green**
Run: `cargo test -p aura-cli --bin aura reproduce_family_re_derives_every_member_bit_identically`
Expected: PASS (the `_` arm is byte-unchanged for Sweep members).
---
### Task 4: `run_blueprint_mc` + the `["mc", ..]` dispatch arm + E2E
**Files:**
- Modify: `crates/aura-cli/src/main.rs` (`run_blueprint_mc` beside `run_blueprint_sweep` ~3210; the
`["mc", rest @ ..]` dispatch arm ~4006-4015)
- Test: `crates/aura-cli/tests/cli_run.rs` (beside `aura_reproduce_re_derives_a_persisted_sweep_bit_identically` ~3456)
- [ ] **Step 1: Write the failing E2E tests**
Add to `crates/aura-cli/tests/cli_run.rs`:
```rust
/// 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);
}
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `cargo test -p aura-cli --test cli_run aura_mc_over_a_blueprint_reproduces_bit_identically`
Expected: FAIL — `aura mc <fixture>.json` currently falls into `parse_mc_args`, which does
not accept a `.json` first positional, so the command errors (no `.json` discriminator yet).
- [ ] **Step 3: Write `run_blueprint_mc`**
Add beside `run_blueprint_sweep` (~3210):
```rust
/// `aura mc <blueprint.json> --seeds N`: build a Monte-Carlo family from a loaded CLOSED
/// blueprint (the World/C21 verb), store the canonical blueprint ONCE keyed by the shared
/// `topology_hash` (the 0094 hook, so `aura reproduce` re-derives it), record it as a
/// `FamilyKind::MonteCarlo` family (C18/C21 lineage), and print each draw's member line
/// (carrying the seed) plus the aggregate — mirroring `run_mc` / `run_blueprint_sweep`.
fn run_blueprint_mc(doc: &str, n_seeds: u64, name: &str, data: DataSource) {
let family = blueprint_mc_family(doc, n_seeds, &data);
let reg = default_registry();
// Store the canonical blueprint ONCE, keyed by the family's shared topology_hash.
let topo = family.draws[0]
.report
.manifest
.topology_hash
.clone()
.expect("a blueprint mc stamps every member's topology_hash");
let canonical = blueprint_to_json(
&blueprint_from_json(doc, &|t| std_vocabulary(t))
.expect("doc parse-validated at the dispatch boundary"),
)
.expect("a loaded blueprint re-serializes");
reg.put_blueprint(&topo, &canonical).unwrap_or_else(|e| {
eprintln!("aura: {e}");
std::process::exit(2);
});
let id = match reg.append_family(name, FamilyKind::MonteCarlo, &mc_member_reports(&family)) {
Ok(id) => id,
Err(e) => {
eprintln!("aura: {e}");
std::process::exit(2);
}
};
for draw in &family.draws {
println!("{}", mc_member_line(&id, draw.seed, &draw.report));
}
println!("{}", mc_aggregate_json(&family.aggregate));
}
```
- [ ] **Step 4: Grow the `["mc", ..]` dispatch arm**
Replace the current arm (~4006-4015):
```rust
["mc", rest @ ..] => match parse_mc_args(rest) {
Ok(McArgs::Synthetic { name, persist }) => run_mc(&name, persist),
Ok(McArgs::RealR { choice, grid, block_len, n_resamples, seed }) => {
run_mc_r_bootstrap(DataSource::from_choice(choice), &grid, block_len, n_resamples, seed)
}
Err(msg) => {
eprintln!("aura: {msg}");
std::process::exit(2);
}
},
```
with the `.json`-discriminator arm (mirroring the `["sweep", ..]` arm ~3952):
```rust
["mc", rest @ ..] => {
// A `.json`-file first argument selects the loaded-blueprint Monte-Carlo (the
// World/C21 verb), mirroring the `["sweep", ..]` discriminator; a bare
// `--strategy`/`--name` token never ends in `.json`, so the paths stay disjoint.
if let Some(path) =
rest.first().filter(|a| a.ends_with(".json") && std::path::Path::new(a).is_file())
{
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
});
if let Err(e) = blueprint_from_json(&doc, &|t| std_vocabulary(t)) {
eprintln!("aura: {path}: {e:?}");
std::process::exit(2);
}
let McBlueprintArgs { n_seeds, name } =
parse_mc_blueprint_args(&rest[1..]).unwrap_or_else(|msg| {
eprintln!("aura: {msg}");
std::process::exit(2);
});
run_blueprint_mc(&doc, n_seeds, &name, DataSource::Synthetic);
return;
}
match parse_mc_args(rest) {
Ok(McArgs::Synthetic { name, persist }) => run_mc(&name, persist),
Ok(McArgs::RealR { choice, grid, block_len, n_resamples, seed }) => {
run_mc_r_bootstrap(DataSource::from_choice(choice), &grid, block_len, n_resamples, seed)
}
Err(msg) => {
eprintln!("aura: {msg}");
std::process::exit(2);
}
}
}
```
- [ ] **Step 5: Run the E2E tests to verify they pass**
Run: `cargo test -p aura-cli --test cli_run aura_mc_over_a_blueprint_reproduces_bit_identically`
Expected: PASS.
Run: `cargo test -p aura-cli --test cli_run aura_mc_rejects_an_open_blueprint`
Expected: PASS.
- [ ] **Step 6: Verify the hard-wired `aura mc` path is undisturbed**
Run: `cargo test -p aura-cli --test cli_run mc`
Expected: PASS (the hard-wired-`mc` E2Es still green — the `.json` discriminator keeps the
non-`.json` path falling through to `parse_mc_args`).
---
### Task 5: Full-workspace gate
**Files:** none (verification only)
- [ ] **Step 1: Build the workspace**
Run: `cargo build --workspace`
Expected: builds clean, 0 errors.
- [ ] **Step 2: Run the full test suite**
Run: `cargo test --workspace`
Expected: all suites pass (the 3 new units + 2 new E2Es green; no regressions).
- [ ] **Step 3: Lint**
Run: `cargo clippy --workspace --all-targets -- -D warnings`
Expected: 0 warnings.