The FOURTH and LAST verb dissolution. `aura mc --strategy r-sma --real`
dissolves into thin sugar over the one campaign executor — a generated
[std::sweep(argmax), std::walk_forward, std::monte_carlo] campaign
document whose terminal monte_carlo stage reproduces the current
`mc_r_bootstrap` grade byte-for-byte (the committed anchor 3fc491a gates
it). Pure sugar, no executor change; mirrors the walkforward dissolution.
The one new load-bearing decision is the seed mapping: the campaign
carries the mc `--seed` (the wf winners are argmax hence
deflation-seed-independent, proven by the shipped walkforward anchor).
Fork C retention as walkforward: the built-in arm splits, run_mc_r_bootstrap
/ run_mc stay inline for the fenced synthetic/non-real paths (until #159).
Spec grounding-check PASS, auto-signed on #210 (2026-07-06).
refs #210
13 KiB
mc R-bootstrap dissolution — Design Spec
Date: 2026-07-06 Status: Draft — awaiting user spec review Authors: orchestrator + Claude
Milestone #210, the FOURTH and LAST verb dissolution. Reference issue: Gitea Brummel/Aura #210. Mirrors the just-shipped walkforward dissolution; the only new load-bearing decision is the seed mapping (recorded as a #210 comment, 2026-07-06). Scope reading: decision 2 / Reading A (Fork C retention).
Goal
Dissolve aura mc --strategy r-sma --real's inline execution into thin sugar over
the one campaign executor (aura_campaign::execute), completing the verb set
(sweep, generalize, walkforward, mc). The dispatch_mc built-in arm gains an
r-sma-real branch that runs through a generated, content-addressed campaign
document with a [std::sweep(argmax), std::walk_forward, std::monte_carlo] process,
reproducing the {"mc_r_bootstrap":{…}} line byte-for-byte. The committed anchor
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade (3fc491a) is the acceptance
gate.
run_mc_r_bootstrap, run_mc, and their helpers are NOT deleted (Fork C, as
walkforward): the built-in arm splits, and everything the dissolution predicate
does not match stays inline, fenced until #159 — the synthetic-r-sma path
(--strategy r-sma without --real), the synthetic seed-resweep (aura mc), and
the blueprint mc path.
Architecture
Pure sugar — NO executor change (aura-campaign/aura-engine/aura-research
diff-free). The campaign monte_carlo stage already does the identical work:
crates/aura-campaign/src/exec.rs:400-446, StageBlock::MonteCarlo after a
std::walk_forward stage produces StageBootstrap::PooledOos(r_bootstrap( &pooled_trade_rs(&fam.reports), resamples, block_len, seed)), seeded from the
campaign seed, landing in StageRealization.bootstrap
(crates/aura-registry/src/lineage.rs:130). It is reachable in-memory at
run.outcome.record.cells[0].stages[].bootstrap. So the sugar reads that
PooledOos(RBootstrap) and renders the existing mc_r_bootstrap_json(&RBootstrap).
mc prints ONLY the one summary line (no per-window member lines, unlike walkforward).
The inline mc R-bootstrap is run_mc_r_bootstrap → mc_r_bootstrap_report →
walkforward_family → pooled_oos_trade_rs → r_bootstrap → mc_r_bootstrap_json
(main.rs ~2106-2133). The dissolution reroutes the same rolling walk-forward +
pooled-OOS bootstrap through the campaign path.
The seed mapping (the one new load-bearing detail). The inline path uses TWO
seeds: a fixed DEFLATION_SEED for the per-window wf winner selection, and the mc
--seed for the terminal r_bootstrap. The campaign path has ONE campaign.seed,
used for both. This reconciles because the wf winners are argmax — the deflation
seed only annotates the recorded selection provenance, never changes the pick — so
the winners, and the pooled OOS series, are seed-independent. The shipped
walkforward anchor already proved this (the campaign path at campaign.seed = 0
reproduced the inline DEFLATION_SEED winners byte-for-byte). So translate_mc
sets campaign.seed = the mc --seed: the pooled series is unchanged (n_trades =
20681, matching the walkforward multi-grid anchor — mc pools the same series) and
the r_bootstrap at that seed reproduces the inline bootstrap. block_len /
resamples map to StageBlock::MonteCarlo{resamples, block_len}.
Fork A (stop): a single RiskRegime::Vol{stop_length, stop_k} (multi-value stop
refused); fast/slow are the multi-value IS-refit grid.
Concrete code shapes
The user-facing program (unchanged surface, dissolved execution)
$ aura mc --strategy r-sma --real GER40 \
--from 1735689600000 --to 1767225599000 \
--fast 3,5 --slow 12,20 --stop-length 14 --stop-k 2.0 \
--block-len 5 --resamples 1000 --seed 42
{"mc_r_bootstrap":{"block_len":5,"e_r":{"mean":-0.0025753095301594307,…},
"n_resamples":1000,"n_trades":20681,"prob_le_zero":0.558}}
Byte-identical to today's inline output (the acceptance gate). The audience — a
researcher stress-testing a candidate's E[R] distribution — runs the identical
command; the execution becomes a durable, content-addressed campaign document (C18,
reproducible-from-manifest). It reintroduces no look-ahead or determinism failure:
the same deterministic engine r_bootstrap over the same pooled OOS series (C1),
no new streamed non-scalar (invariant 4). All mc-specific logic stays in the CLI
translator/reader (invariant 10: no verb-special engine logic); the World runs it
through the one campaign path (invariant 12).
Sugar translator — mirror of translate_walkforward (verb_sugar.rs), + the monte_carlo stage
// crates/aura-cli/src/verb_sugar.rs — new, mirror of translate_walkforward
pub(crate) struct GeneratedMc { pub process: ProcessDoc, pub campaign: CampaignDoc }
#[allow(clippy::too_many_arguments)] // #214: bundle deferred until after this last verb
pub(crate) fn translate_mc(
fast: &[i64], slow: &[i64],
stop_length: i64, stop_k: f64,
metric: &str, // "sqn_normalized" (the wf objective)
in_sample_ms: u64, out_of_sample_ms: u64, step_ms: u64,
resamples: u32, block_len: u32, seed: u64, // seed = the mc --seed
name: &str, symbol: &str, from_ms: i64, to_ms: i64,
blueprint_canonical: &str,
) -> Result<GeneratedMc, String> {
let process = ProcessDoc {
format_version: FORMAT_VERSION, kind: DocKind::Process,
name: "mc".to_string(), description: None,
pipeline: vec![
StageBlock::Sweep { selection: Some(SweepSelection {
metric: metric.to_string(), select: SelectRule::Argmax, deflate: false }) },
StageBlock::WalkForward {
in_sample_ms, out_of_sample_ms, step_ms,
mode: WfMode::Rolling, metric: metric.to_string(), select: SelectRule::Argmax },
StageBlock::MonteCarlo { resamples, block_len },
],
};
// …doc_axes for fast.length / slow.length exactly as translate_walkforward…
let campaign = CampaignDoc {
// …instruments: [symbol], windows: [{from_ms,to_ms}], one StrategyEntry with the axes…
risk: vec![RiskRegime::Vol { length: stop_length, k: stop_k }],
seed, // <-- the mc --seed (the wf winners are seed-independent)
// …presentation: persist_taps [], emit ["family_table"]…
// (process/data/strategies fields identical in shape to translate_walkforward)
};
Ok(GeneratedMc { process, campaign })
}
register_generated_mc is the verbatim register_generated_wf shape (put_process +
put_campaign).
Sugar runner — read the bootstrap from the record, print one line
// crates/aura-cli/src/verb_sugar.rs — new, mirror of run_walkforward_sugar
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_mc_sugar(/* translate_mc args + env */) -> Result<(), String> {
let generated = translate_mc(/* … */)?;
let probe_params = vec![
("fast.length".to_string(), Scalar::i64(fast[0])),
("slow.length".to_string(), Scalar::i64(slow[0])),
];
validate_before_register(&generated.process, &generated.campaign, blueprint_canonical, &probe_params, env)?;
let reg = env.registry();
let (_p, campaign_id) = register_generated_mc(®, &generated)?;
let run = crate::campaign_run::run_campaign_returning(&campaign_id, env)?;
// The single cell's monte_carlo StageRealization carries the pooled-OOS bootstrap.
let boot = run.outcome.record.cells.iter()
.flat_map(|c| c.stages.iter())
.find_map(|s| match &s.bootstrap {
Some(aura_registry::StageBootstrap::PooledOos(b)) => Some(b),
_ => None,
})
.ok_or("mc produced no pooled-OOS bootstrap")?;
println!("{}", crate::mc_r_bootstrap_json(boot));
Ok(())
}
Dispatch split — mirror of the walkforward split; run_mc_r_bootstrap/run_mc STAY
// crates/aura-cli/src/main.rs — dispatch_mc, the None (built-in) arm
None => {
// …existing usage / blueprint+seeds guards, unchanged…
if a.strategy.as_deref() == Some("r-sma") && a.real.is_some() {
// r-sma --real: the dissolved sugar path.
let (name, symbol, fast, slow, stop_length, stop_k, block_len, resamples, seed, from, to) =
mc_args_from(&a) /* unwrap_or_else: eprintln + exit 2 */;
let signal = sma_signal(None, None);
let canonical = blueprint_to_json(&signal).expect("a bare sma_signal serializes");
let reg = env.registry();
reg.put_blueprint(&topology_hash(&signal), &canonical) /* exit 1 on err */;
let (from_ms, to_ms) = resolve_window(&symbol, from, to, env); // full_window unconditionally
let (is_ms, oos_ms, step_ms) = wf_ms_sizes();
verb_sugar::run_mc_sugar(&fast, &slow, stop_length, stop_k, "sqn_normalized",
is_ms, oos_ms, step_ms, resamples, block_len, seed, &name, &symbol, from_ms, to_ms, &canonical, env)
/* unwrap_or_else: eprintln + exit 1 */;
return;
}
// …EXISTING fenced body, verbatim: the r_path / McArgs::RealR / McArgs::Synthetic
// logic → run_mc_r_bootstrap / run_mc…
}
mc_args_from mirrors walkforward_args_from plus block_len (default 1),
resamples (default 1000), seed (default 1) — single-value stop (multi-value
refused, Fork A), fast/slow CSV grids, --real required. run_mc_r_bootstrap,
run_mc, mc_r_bootstrap_report, mc_r_bootstrap_json, pooled_oos_trade_rs are
all RETAINED (the fenced paths + mc_r_bootstrap_json is reused by the sugar).
Components
crates/aura-cli/src/verb_sugar.rs—GeneratedMc,translate_mc,register_generated_mc,run_mc_sugar. Reusesaxis_from_values,validate_before_register,run_campaign_returning.crates/aura-cli/src/main.rs—mc_args_from, thedispatch_mcbuilt-in-arm split (add the r-sma-real sugar branch; retain the existing body). Reuseswf_ms_sizes,resolve_window(the walkforward helper if it was extracted, else the same inline match),sma_signal,mc_r_bootstrap_json.- No executor change.
Data flow
argv → mc_args_from → dispatch_mc synthesizes + stores the sma_signal
blueprint, resolves the window, ms roller sizes → run_mc_sugar → translate_mc
builds [sweep(argmax), walk_forward, monte_carlo] + a campaign (fast/slow axes,
one instrument, one Vol regime, seed = mc_seed) → validate_before_register →
register_generated_mc → run_campaign_returning (sweep → wf refit per window →
monte_carlo pools the OOS R + bootstraps at the campaign seed) → the sugar reads
StageBootstrap::PooledOos(RBootstrap) from the record's stages and prints
mc_r_bootstrap_json.
Error handling
- Non-
r-sma, missing knobs, empty--real: the fenced arm's /mc_args_from's refusals (exit 2), reusing message strings where shared. - Multi-value
--stop-length/--stop-k: exit 2 (Fork A), same message shape as walkforward. - Referential faults:
validate_before_registerrefuses before any store write. - Data refusal (no archive): the member runner's
no_real_datapath (exit 1).
Testing strategy
- The committed anchor
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade(3fc491a) is the equivalence gate: it must stay green through the dissolution (block_len, n_resamples, n_trades, e_r.mean, e_r.p50, prob_le_zero all exact). translate_mcunit tests (mirror the walkforward translator tests): deterministic content ids; the pipeline is[sweep(argmax), walk_forward, monte_carlo]with the right roller/mc fields + seed; the regime carries the stop; diverging seeds/stops do not collide content ids;register_generated_mcresolves.- Dissolution-proof e2e (mirror
walkforward_dissolves_through_the_campaign_path): a successful real run auto-registers one process + one campaign + one campaign-run, and the output is the singlemc_r_bootstrapline. - Front-end refusal tests: multi-value stop (exit 2), non-r-sma real handled by the fenced path (still works).
Acceptance criteria
aura mc --strategy r-sma --real <SYM> …runs through the campaign path; the anchor stays green (themc_r_bootstrapline byte-identical).--strategy r-sma --realno longer flows throughrun_mc_r_bootstrap; it runs the sugar path.run_mc_r_bootstrap/run_mcare RETAINED for the fenced branches.- The fenced branches are behaviour-unchanged (Reading A): synthetic-r-sma
(
--strategy r-smano--real), the synthetic seed-resweep (aura mc), and the blueprint mc path all run inline exactly as before. - Multi-value
--stop-length/--stop-kis refused (Fork A). - No executor change (
aura-campaign/aura-engine/aura-researchdiff-free). - Full workspace suite green;
clippy --all-targets -D warningsclean.