Files
Aura/crates/aura-cli/src/verb_sugar.rs
T
claude ea4e79d73f feat(research,campaign,cli): std::grid — the enumerate-only leading stage
closes #256

Fork B (owner decision 2026-07-14): the dissolved walkforward/mc
translations' leading sweep executed the full grid over the whole campaign
window and persisted a Sweep family, yet only the enumerated parameter
points ever crossed the stage seam (the wf stage re-sweeps them per IS
window itself). The leading stage is now the fieldless vocabulary block
std::grid: it enumerates, executes nothing, persists nothing.

- aura-research: StageBlock::Grid ({"block":"std::grid"}), schema-strict
  parse arm (empty slot list: every key but "block" is refused by the
  generic unknown-slot check), PROCESS_BLOCKS entry, intrinsic-tier no-op
  arm; the vocabulary test's non-empty-slots guard carries a pinned
  std::grid-only exception (a nominal slot would misdescribe the
  vocabulary to describe_block consumers).
- aura-campaign: the inter-stage seam is a typed two-armed StageFlow
  (points-only vs executed members); gate / mc-per-survivor fence the
  points-only arm with defensive PipelineShape faults; preflight admits
  std::grid only as the first stage and only immediately before
  std::walk_forward (every other neighbor consumes executed reports).
- aura-cli: translate_walkforward / translate_mc lead with
  StageBlock::Grid; the two family-shape E2E pins flip to zero Sweep
  families; translate_generalize keeps its executed sweep(argmax) — its
  generalize stage consumes the argmax winner report as the cell nominee.
- docs: dated #256 amendment in the ledger's verb-dissolution narrative;
  the authoring-guide vocabulary transcript gains the std::grid line.

Behaviour preservation: the exact-grade real-data pins
(walkforward_real_e2e_pins_the_exact_current_grade,
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade) pass unmodified —
survivor points reach the wf stage in the same odometer order as before.

Measured (the #256 acceptance measurement; debug build, real GER40 2025,
2x2 grid, `aura walkforward --real`, 3 runs each):
  before (a55e4cf): 6.27 / 6.18 / 6.18 s
  after:            4.52 / 4.49 / 4.45 s   (~ -27% wall clock)

Suite: cargo test --workspace green (0 failed); clippy clean.
Decision log: #256 comments (fork rationale incl. the StageFlow seam and
the slot-guard exception).
2026-07-14 13:07:40 +02:00

1144 lines
53 KiB
Rust

//! Verb sugar: the dissolved orchestration verbs' argv → generated campaign
//! documents (docs/specs/sweep-dissolution.md; #210 fork decisions).
//!
//! A dissolved verb invocation is translated into a selection-free process
//! document plus a campaign document expressing exactly the invocation's
//! intent, both auto-registered content-addressed (every ad-hoc invocation
//! becomes durable, diffable, reproducible intent), then run through the one
//! campaign executor in sugar presentation mode (member lines only).
use std::collections::BTreeMap;
use aura_core::{Scalar, ScalarKind};
use aura_research::{
campaign_to_json, content_id_of, process_to_json, tap_vocabulary, Axis, CampaignDoc,
DataSection, DocKind, DocRef, Presentation, ProcessDoc, ProcessRef, RiskRegime, SelectRule,
StageBlock, StrategyEntry, SweepSelection, WfMode, Window, FORMAT_VERSION,
};
/// The two generated documents of one dissolved-verb invocation.
#[derive(Debug)]
pub(crate) struct GeneratedSweep {
pub process: ProcessDoc,
pub campaign: CampaignDoc,
}
/// One dissolved-verb invocation's shared shape (#214/#220): arbitrary RAW
/// axes (post `wrapped_to_raw_axis` strip), the family name, the instrument
/// list, the shared window, the canonical blueprint, and the optional single
/// Vol stop regime (`None` binds no regime — sweep's contract). All four
/// dissolved verbs (sweep, generalize, walkforward, mc) route their dispatch
/// args through this shape.
pub(crate) struct SugarInvocation<'a> {
pub axes: &'a [(String, Vec<Scalar>)],
pub name: String,
/// Instruments: sweep/walkforward/mc carry exactly one; generalize >=2.
pub symbols: Vec<String>,
pub from_ms: i64,
pub to_ms: i64,
pub blueprint_canonical: &'a str,
pub stop: Option<VolStop>,
/// Whether the invocation carried `--trace` (sweep/walkforward, #224):
/// when true, the generated campaign's presentation requests the full
/// tap vocabulary so `persist_campaign_traces` actually writes each
/// member's tap series, instead of the #168 no-op `persist_taps: vec![]`.
pub trace: bool,
}
/// The generated campaign's `persist_taps` section for one invocation:
/// when `--trace` was requested, the tap vocabulary MINUS the net channel
/// (`net_r_equity`), else empty (the #168 no-op — this is the one seam
/// `translate_sweep`/`translate_walkforward` share for the decision). Every
/// generated sugar campaign carries an empty cost section (`cost: vec![]`)
/// by construction, so the net channel is never producible there; requesting
/// it anyway would make `campaign_run`'s executor print an unreachable "add a
/// cost block to the campaign document" remedy (#240). The exclusion reuses
/// `campaign_run`'s own tap/channel classification (`tap_channel`/
/// `TapChannel::Net`) rather than duplicating the net-tap name here.
fn persist_taps_from(trace: bool) -> Vec<String> {
if trace {
tap_vocabulary()
.iter()
.filter(|t| crate::campaign_run::tap_channel(t) != Some(crate::campaign_run::TapChannel::Net))
.map(|t| t.to_string())
.collect()
} else {
vec![]
}
}
/// The single protective-stop regime a dissolved verb binds (`RiskRegime::Vol`).
#[derive(Clone, Copy)]
pub(crate) struct VolStop {
pub length: i64,
pub k: f64,
}
/// The walk-forward roller sizes (ms) a wf/mc invocation carries.
#[derive(Clone, Copy)]
pub(crate) struct WfWindows {
pub in_sample_ms: u64,
pub out_of_sample_ms: u64,
pub step_ms: u64,
}
/// The mc-specific bootstrap knobs.
#[derive(Clone, Copy)]
pub(crate) struct McKnobs {
pub resamples: u32,
pub block_len: u32,
pub seed: u64,
}
/// The generic doc-axes map: every invocation axis under its arbitrary raw
/// bind-key (the sweep shape, adopted by all four verbs — #220).
fn doc_axes_from(axes: &[(String, Vec<Scalar>)]) -> Result<BTreeMap<String, Axis>, String> {
let mut doc_axes: BTreeMap<String, Axis> = BTreeMap::new();
for (axis_name, values) in axes {
doc_axes.insert(axis_name.clone(), axis_from_values(axis_name, values)?);
}
Ok(doc_axes)
}
/// The optional single Vol stop regime -> the campaign risk vector.
fn risk_from(stop: Option<VolStop>) -> Vec<RiskRegime> {
stop.map(|s| vec![RiskRegime::Vol { length: s.length, k: s.k }]).unwrap_or_default()
}
/// One probe param per axis (any one grid value — `bind_axes` checks NAME
/// coverage/uniqueness, never the bound value).
fn probe_params_from(axes: &[(String, Vec<Scalar>)]) -> Vec<(String, Scalar)> {
axes.iter().filter_map(|(n, vals)| vals.first().map(|v| (n.clone(), *v))).collect()
}
/// Map one verb axis (`--axis name=csv`, already scalar-parsed) to a
/// document `Axis`: the kind is the first value's kind; a mixed-kind CSV is
/// refused (the document axis declares its kind ONCE — #189).
fn axis_from_values(name: &str, values: &[Scalar]) -> Result<Axis, String> {
let kind = match values.first() {
Some(Scalar::I64(_)) => ScalarKind::I64,
Some(Scalar::F64(_)) => ScalarKind::F64,
Some(Scalar::Bool(_)) => ScalarKind::Bool,
Some(Scalar::Timestamp(_)) => ScalarKind::Timestamp,
None => return Err(format!("axis {name}: empty value list")),
};
let homogeneous = values.iter().all(|v| {
matches!(
(v, kind),
(Scalar::I64(_), ScalarKind::I64)
| (Scalar::F64(_), ScalarKind::F64)
| (Scalar::Bool(_), ScalarKind::Bool)
| (Scalar::Timestamp(_), ScalarKind::Timestamp)
)
});
if !homogeneous {
return Err(format!(
"axis {name}: mixed value kinds in one axis (an axis declares its kind once)"
));
}
Ok(Axis { kind, values: values.to_vec() })
}
/// Translate a real-data blueprint sweep invocation into its two generated
/// documents. `inv.blueprint_canonical` is the canonical blueprint JSON already
/// stored by topology hash; its content id is the strategy ref.
pub(crate) fn translate_sweep(inv: &SugarInvocation) -> Result<GeneratedSweep, String> {
let process = ProcessDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Process,
name: "sweep".to_string(),
description: None,
pipeline: vec![StageBlock::Sweep { selection: None }],
};
let campaign = CampaignDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Campaign,
name: inv.name.clone(),
description: None,
data: DataSection {
instruments: inv.symbols.clone(),
windows: vec![Window { from_ms: inv.from_ms, to_ms: inv.to_ms }],
// The verb sugar passes no bindings: name defaults rule (#231);
// authored campaign documents may set them.
bindings: BTreeMap::new(),
},
strategies: vec![StrategyEntry {
r#ref: DocRef::ContentId(content_id_of(inv.blueprint_canonical)),
axes: doc_axes_from(inv.axes)?,
}],
process: ProcessRef { r#ref: DocRef::ContentId(content_id_of(&process_to_json(&process))) },
// No stop regime is bound for a dissolved single-sweep member (the
// dispatch passes `stop: None`; absent risk == empty risk, per the
// established parity). The other verbs bind the single Vol regime
// through this same seam.
risk: risk_from(inv.stop),
cost: vec![],
// No stage of a selection-free single-sweep pipeline consumes the
// seed; a fixed zero keeps generated bytes deterministic so identical
// invocations dedupe onto identical content ids.
seed: 0,
presentation: Presentation {
persist_taps: persist_taps_from(inv.trace),
emit: vec!["family_table".to_string()],
},
};
Ok(GeneratedSweep { process, campaign })
}
/// Auto-register both generated documents (content-addressed, idempotent).
/// Returns `(process_id, campaign_id)`.
pub(crate) fn register_generated(
reg: &aura_registry::Registry,
generated: &GeneratedSweep,
) -> Result<(String, String), String> {
let process_id =
reg.put_process(&process_to_json(&generated.process)).map_err(|e| e.to_string())?;
let campaign_id =
reg.put_campaign(&campaign_to_json(&generated.campaign)).map_err(|e| e.to_string())?;
Ok((process_id, campaign_id))
}
/// The two generated documents of one dissolved `generalize` invocation.
#[derive(Debug)]
pub(crate) struct GeneratedGeneralize {
pub process: ProcessDoc,
pub campaign: CampaignDoc,
}
/// Translate one `aura generalize` invocation into its two generated documents:
/// a **selection-bearing** process (`[std::sweep(argmax), std::generalize]` —
/// generalize needs a nominee, so the sweep stage is selection-bearing, unlike
/// the selection-free single-sweep translator) and a campaign running the one
/// fixed candidate (single-value raw axes) across all instruments under the
/// invocation's single risk regime. `inv.blueprint_canonical` is the user's
/// blueprint already stored by topology hash; its content id is the strategy ref.
pub(crate) fn translate_generalize(
inv: &SugarInvocation,
metric: &str,
) -> Result<GeneratedGeneralize, String> {
let process = ProcessDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Process,
name: "generalize".to_string(),
description: None,
pipeline: vec![
StageBlock::Sweep {
selection: Some(SweepSelection {
metric: metric.to_string(),
select: SelectRule::Argmax,
deflate: false,
}),
},
StageBlock::Generalize { metric: metric.to_string() },
],
};
let campaign = CampaignDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Campaign,
name: inv.name.clone(),
description: None,
data: DataSection {
instruments: inv.symbols.clone(),
windows: vec![Window { from_ms: inv.from_ms, to_ms: inv.to_ms }],
// The verb sugar passes no bindings: name defaults rule (#231);
// authored campaign documents may set them.
bindings: BTreeMap::new(),
},
strategies: vec![StrategyEntry {
r#ref: DocRef::ContentId(content_id_of(inv.blueprint_canonical)),
axes: doc_axes_from(inv.axes)?,
}],
process: ProcessRef { r#ref: DocRef::ContentId(content_id_of(&process_to_json(&process))) },
// The stop is a single structural risk regime; the member runner maps
// it back to StopRule::Vol at run time.
risk: risk_from(inv.stop),
cost: vec![],
seed: 0,
presentation: Presentation {
persist_taps: vec![],
emit: vec!["family_table".to_string()],
},
};
Ok(GeneratedGeneralize { process, campaign })
}
/// Auto-register both generated generalize documents (content-addressed,
/// idempotent). Returns `(process_id, campaign_id)`.
pub(crate) fn register_generated_g(
reg: &aura_registry::Registry,
generated: &GeneratedGeneralize,
) -> Result<(String, String), String> {
let process_id =
reg.put_process(&process_to_json(&generated.process)).map_err(|e| e.to_string())?;
let campaign_id =
reg.put_campaign(&campaign_to_json(&generated.campaign)).map_err(|e| e.to_string())?;
Ok((process_id, campaign_id))
}
/// Validate a generated `(process, campaign)` pair BEFORE anything is
/// registered (#210 c0110 fieldtest, "store litter" finding): a referential
/// refusal must never leave a dead generated document in the content-
/// addressed store. Defensive doc-shape checks first (generated docs should
/// always pass this — the translators build them by construction), then the
/// executable-shape preflight, then the axis-binding check against the
/// blueprint's own (wrapped) open param space via the pure `bind_axes` seam
/// (campaign_run.rs) — the referential shape the dispatch-boundary name
/// preflight (main.rs) does not catch: a subset of axes that leaves an open
/// param unbound (probe P3). Probe P3's space is the REOPENED one (#246): the
/// same `raw_bound_overrides_of` derivation `CliMemberRunner::run_member` uses
/// per member (campaign_run.rs) — a bound-param axis passes this
/// executable-shape preflight exactly like an already-open one; an axis
/// matching neither space still fails `bind_axes`'s own named check below.
/// `probe_params` is any one grid value per axis (checks NAME
/// coverage/uniqueness, never the bound value). Shared by both dissolved-verb
/// sugars: one seam enforcing the store-litter invariant, rather than two
/// drift-prone copies.
fn validate_before_register(
process: &ProcessDoc,
campaign: &CampaignDoc,
blueprint_canonical: &str,
probe_params: &[(String, Scalar)],
env: &crate::project::Env,
) -> Result<(), String> {
let doc_faults = aura_research::validate_campaign(campaign);
if !doc_faults.is_empty() {
return Err(crate::research_docs::fault_block(
"generated campaign document invalid:",
doc_faults.iter().map(crate::research_docs::doc_fault_prose).collect(),
));
}
let process_faults = aura_research::validate_process(process);
if !process_faults.is_empty() {
return Err(crate::research_docs::fault_block(
"generated process document invalid:",
process_faults.iter().map(crate::research_docs::doc_fault_prose).collect(),
));
}
aura_campaign::preflight(process, campaign).map_err(|f| crate::campaign_run::exec_fault_prose(&f))?;
// The un-reopened wrapped OPEN space (#246): derive which of the probe's
// RAW axis names re-open a bound param of the strategy, against a raw
// (un-reopened) reload of the same canonical bytes — infallible, the
// strategy ref's own content.
let raw_space = crate::blueprint_axis_probe(blueprint_canonical, env).param_space();
let raw_signal = aura_engine::blueprint_from_json(blueprint_canonical, &|t| env.resolve(t))
.expect("a generated sugar campaign's strategy ref reloads its own canonical bytes");
let param_names: Vec<String> = probe_params.iter().map(|(n, _)| n.clone()).collect();
let overrides = crate::campaign_run::raw_bound_overrides_of(&param_names, &raw_space, &raw_signal);
let space = crate::blueprint_axis_probe_reopened(blueprint_canonical, env, &overrides).param_space();
let strategy_id = content_id_of(blueprint_canonical);
crate::campaign_run::bind_axes(&space, &strategy_id, probe_params)
.map(|_| ())
.map_err(|f| crate::campaign_run::exec_fault_prose(&aura_campaign::ExecFault::Member(f)))
}
/// Run one dissolved sweep invocation end-to-end: register the generated
/// documents, then execute through the one campaign path in sugar mode.
pub(crate) fn run_sweep_sugar(
inv: &SugarInvocation,
env: &crate::project::Env,
) -> Result<(), String> {
let generated = translate_sweep(inv)?;
let probe_params = probe_params_from(inv.axes);
validate_before_register(&generated.process, &generated.campaign, inv.blueprint_canonical, &probe_params, env)?;
let reg = env.registry();
let (_process_id, campaign_id) = register_generated(&reg, &generated)?;
crate::campaign_run::run_campaign_by_id(&campaign_id, env, crate::campaign_run::RunPresentation::MemberLinesOnly)
}
/// Build the `CrossInstrument` family members from an executed generalize
/// campaign: each cell's nominee `RunReport`, in cell (instrument doc) order —
/// the same order + shape the inline `run_generalize` built its `members` in,
/// each already carrying `manifest.instrument`. Debug-asserts one member per
/// cell: generalize's pipeline has no gate stage, so every cell nominates —
/// a cell silently missing its nominee here would be a campaign-executor
/// regression, not a legitimate generalize outcome, and the filter_map below
/// must not swallow it unnoticed.
fn cross_instrument_members(
outcome: &aura_campaign::CampaignOutcome,
) -> Vec<aura_engine::RunReport> {
let members: Vec<aura_engine::RunReport> = outcome
.cells
.iter()
.filter_map(|c| c.nominee.as_ref().map(|(_, report)| report.clone()))
.collect();
debug_assert_eq!(
members.len(),
outcome.cells.len(),
"generalize has no gate stage — every cell must nominate"
);
members
}
/// Run one dissolved `generalize` invocation end-to-end: register the generated
/// documents, run through the one campaign path, then reprint today's exact
/// `{"generalize":{…}}` + `{"family_id":…}` lines from the recorded outcome and
/// persist the `CrossInstrument` family. The stdout is byte-identical to the
/// retired welded path (the committed exact-grade anchor is the gate).
pub(crate) fn run_generalize_sugar(
inv: &SugarInvocation,
metric: &str,
env: &crate::project::Env,
) -> Result<(), String> {
let generated = translate_generalize(inv, metric)?;
let probe_params = probe_params_from(inv.axes);
validate_before_register(&generated.process, &generated.campaign, inv.blueprint_canonical, &probe_params, env)?;
let reg = env.registry();
let (_process_id, campaign_id) = register_generated_g(&reg, &generated)?;
let run = crate::campaign_run::run_campaign_returning(&campaign_id, env)?;
// Reprint the verb's aggregate line from the recorded cross-instrument
// grade — byte-identical to the retired welded path's `generalize_json(&agg)`.
let cg = run
.outcome
.record
.generalizations
.iter()
.find_map(|g| g.generalization.as_ref())
.ok_or("generalize produced no cross-instrument grade")?;
println!("{}", crate::generalize_json(cg));
let members = cross_instrument_members(&run.outcome);
let family_id = reg
.append_family(&inv.name, aura_registry::FamilyKind::CrossInstrument, &members)
.map_err(|e| e.to_string())?;
println!("{{\"family_id\":\"{family_id}\"}}");
Ok(())
}
/// The two generated documents of one dissolved `walkforward` invocation.
#[derive(Debug)]
pub(crate) struct GeneratedWalkforward {
pub process: ProcessDoc,
pub campaign: CampaignDoc,
}
/// Translate one `aura walkforward --real` invocation into its two generated
/// documents: a `[std::grid, std::walk_forward]` process (the leading
/// `std::grid` stage only enumerates the IS-refit survivor grid for the wf
/// stage — it executes nothing and persists no family (#256)) and a campaign
/// running the invocation's axis grid over one instrument under its single
/// risk regime. The roller sizes come from `w` (ms). `inv.blueprint_canonical`
/// is the user's blueprint already stored by topology hash; its content id is
/// the strategy ref.
pub(crate) fn translate_walkforward(
inv: &SugarInvocation,
metric: &str,
w: WfWindows,
select: SelectRule,
) -> Result<GeneratedWalkforward, String> {
let process = ProcessDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Process,
name: "walkforward".to_string(),
description: None,
pipeline: vec![
StageBlock::Grid,
StageBlock::WalkForward {
in_sample_ms: w.in_sample_ms,
out_of_sample_ms: w.out_of_sample_ms,
step_ms: w.step_ms,
mode: WfMode::Rolling,
metric: metric.to_string(),
select,
},
],
};
let campaign = CampaignDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Campaign,
name: inv.name.clone(),
description: None,
data: DataSection {
instruments: inv.symbols.clone(),
windows: vec![Window { from_ms: inv.from_ms, to_ms: inv.to_ms }],
// The verb sugar passes no bindings: name defaults rule (#231);
// authored campaign documents may set them.
bindings: BTreeMap::new(),
},
strategies: vec![StrategyEntry {
r#ref: DocRef::ContentId(content_id_of(inv.blueprint_canonical)),
axes: doc_axes_from(inv.axes)?,
}],
process: ProcessRef { r#ref: DocRef::ContentId(content_id_of(&process_to_json(&process))) },
risk: risk_from(inv.stop),
cost: vec![],
seed: 0,
presentation: Presentation {
persist_taps: persist_taps_from(inv.trace),
emit: vec!["family_table".to_string()],
},
};
Ok(GeneratedWalkforward { process, campaign })
}
/// Auto-register both generated walkforward documents (content-addressed,
/// idempotent). Returns `(process_id, campaign_id)`.
pub(crate) fn register_generated_wf(
reg: &aura_registry::Registry,
generated: &GeneratedWalkforward,
) -> Result<(String, String), String> {
let process_id =
reg.put_process(&process_to_json(&generated.process)).map_err(|e| e.to_string())?;
let campaign_id =
reg.put_campaign(&campaign_to_json(&generated.campaign)).map_err(|e| e.to_string())?;
Ok((process_id, campaign_id))
}
/// Run one dissolved `walkforward` invocation end-to-end: register the generated
/// documents, run through the one campaign path, then reprint the per-window member
/// lines + the summary line from the recorded `WalkForward` family. The stdout
/// summary line is byte-identical to the retired welded path (the committed
/// exact-grade anchor is the gate).
pub(crate) fn run_walkforward_sugar(
inv: &SugarInvocation,
metric: &str,
w: WfWindows,
select: SelectRule,
env: &crate::project::Env,
) -> Result<(), String> {
let generated = translate_walkforward(inv, metric, w, select)?;
let probe_params = probe_params_from(inv.axes);
validate_before_register(&generated.process, &generated.campaign, inv.blueprint_canonical, &probe_params, env)?;
let reg = env.registry();
let (_process_id, campaign_id) = register_generated_wf(&reg, &generated)?;
let run = crate::campaign_run::run_campaign_returning(&campaign_id, env)?;
// The single cell's walk_forward StageFamily.
let wf = run
.outcome
.cells
.iter()
.flat_map(|c| c.families.iter())
.find(|f| f.block == "std::walk_forward")
.ok_or("walkforward produced no walk_forward family")?;
for report in &wf.reports {
println!("{}", crate::family_member_line(&wf.family_id, report));
}
// Summary axes: the invocation's raw axis names in argv order, then the
// stop columns when a regime is bound (#220 — the renderer hardcodes none).
let mut summary_axes: Vec<String> = inv.axes.iter().map(|(n, _)| n.clone()).collect();
if inv.stop.is_some() {
summary_axes.push("stop_length".to_string());
summary_axes.push("stop_k".to_string());
}
println!("{}", crate::walkforward_summary_json_from_reports(&wf.reports, &summary_axes));
// Trace persistence runs LAST (mirroring `present_campaign`'s ordering,
// 0109): a no-op when `--trace` was not requested (`persist_taps` empty
// => `trace_name` is `None`), so this is always safe to call. Unlike
// `run_sweep_sugar` (routed through `run_campaign_by_id` -> the shared
// `present_campaign` tail), this walkforward sugar calls
// `run_campaign_returning` directly and prints its own bespoke lines, so
// it must run this tail itself. Its generalize/mc siblings call
// `run_campaign_returning` the same way but do NOT run this tail: neither
// has a `--trace` grammar (generalize never did; `mc --real` refuses
// `--name`/`--trace` up front, #224) and `campaign.presentation.persist_taps`
// is always empty for them, so their `run.outcome.record.trace_name` is
// always `None`. `run`/`mc --trace` stay refused by design (#224), not by
// this tail being skipped.
if let Some(trace_name) = &run.outcome.record.trace_name {
crate::campaign_run::persist_campaign_traces(
trace_name,
&run.campaign.presentation.persist_taps,
&run.outcome,
&run.campaign,
&run.strategies,
&run.server,
env,
)?;
}
Ok(())
}
/// The two generated documents of one dissolved `mc` invocation.
#[derive(Debug)]
pub(crate) struct GeneratedMc {
pub process: ProcessDoc,
pub campaign: CampaignDoc,
}
/// Translate one `aura mc --real` invocation into its two generated documents:
/// a `[std::grid, std::walk_forward, std::monte_carlo]` process (the leading
/// `std::grid` stage only enumerates the IS-refit survivor grid for the wf
/// stage — it executes nothing and persists no family (#256); the terminal
/// monte_carlo stage pools the per-window OOS trade-R series and r-bootstraps
/// `E[R]`) and a campaign running the invocation's axis grid over one instrument
/// under its single risk regime. Unlike `translate_walkforward` (which hardcodes
/// `seed: 0`), `translate_mc` sets `campaign.seed = mc.seed` (the mc `--seed`):
/// the wf winners are argmax hence deflation-seed-independent (the shipped
/// walkforward anchor proved this), so remapping the campaign seed leaves the
/// pooled series unchanged while making the terminal `r_bootstrap` at that seed
/// reproduce the retired inline bootstrap.
pub(crate) fn translate_mc(
inv: &SugarInvocation,
metric: &str,
w: WfWindows,
mc: McKnobs,
) -> Result<GeneratedMc, String> {
let process = ProcessDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Process,
name: "mc".to_string(),
description: None,
pipeline: vec![
StageBlock::Grid,
StageBlock::WalkForward {
in_sample_ms: w.in_sample_ms,
out_of_sample_ms: w.out_of_sample_ms,
step_ms: w.step_ms,
mode: WfMode::Rolling,
metric: metric.to_string(),
select: SelectRule::Argmax,
},
StageBlock::MonteCarlo { resamples: mc.resamples, block_len: mc.block_len },
],
};
let campaign = CampaignDoc {
format_version: FORMAT_VERSION,
kind: DocKind::Campaign,
name: inv.name.clone(),
description: None,
data: DataSection {
instruments: inv.symbols.clone(),
windows: vec![Window { from_ms: inv.from_ms, to_ms: inv.to_ms }],
// The verb sugar passes no bindings: name defaults rule (#231);
// authored campaign documents may set them.
bindings: BTreeMap::new(),
},
strategies: vec![StrategyEntry {
r#ref: DocRef::ContentId(content_id_of(inv.blueprint_canonical)),
axes: doc_axes_from(inv.axes)?,
}],
process: ProcessRef { r#ref: DocRef::ContentId(content_id_of(&process_to_json(&process))) },
risk: risk_from(inv.stop),
cost: vec![],
seed: mc.seed,
presentation: Presentation {
persist_taps: vec![],
emit: vec!["family_table".to_string()],
},
};
Ok(GeneratedMc { process, campaign })
}
/// Auto-register both generated mc documents (content-addressed, idempotent).
/// Returns `(process_id, campaign_id)`.
pub(crate) fn register_generated_mc(
reg: &aura_registry::Registry,
generated: &GeneratedMc,
) -> Result<(String, String), String> {
let process_id =
reg.put_process(&process_to_json(&generated.process)).map_err(|e| e.to_string())?;
let campaign_id =
reg.put_campaign(&campaign_to_json(&generated.campaign)).map_err(|e| e.to_string())?;
Ok((process_id, campaign_id))
}
/// Run one dissolved `mc` invocation end-to-end: register the generated documents,
/// run through the one campaign path, then reprint the single `mc_r_bootstrap` grade
/// line from the recorded terminal `PooledOos` bootstrap. The stdout line is
/// byte-identical to the retired welded path (the committed exact-grade anchor is
/// the gate). mc prints ONLY the one summary line — no per-window member lines
/// (unlike walkforward): the monte_carlo stage is a terminal annotator, not a family.
pub(crate) fn run_mc_sugar(
inv: &SugarInvocation,
metric: &str,
w: WfWindows,
mc: McKnobs,
env: &crate::project::Env,
) -> Result<(), String> {
let generated = translate_mc(inv, metric, w, mc)?;
let probe_params = probe_params_from(inv.axes);
validate_before_register(&generated.process, &generated.campaign, inv.blueprint_canonical, &probe_params, env)?;
let reg = env.registry();
let (_process_id, campaign_id) = register_generated_mc(&reg, &generated)?;
let run = crate::campaign_run::run_campaign_returning(&campaign_id, env)?;
// The single cell's terminal monte_carlo StageRealization carries the pooled-OOS
// bootstrap (record path: outcome.record.cells[].stages[].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(())
}
#[cfg(test)]
mod tests {
use super::*;
fn axes() -> Vec<(String, Vec<Scalar>)> {
vec![
("fast.length".to_string(), vec![Scalar::I64(2), Scalar::I64(4)]),
("slow.length".to_string(), vec![Scalar::I64(8), Scalar::I64(16)]),
]
}
fn inv<'a>(
axes: &'a [(String, Vec<Scalar>)],
name: &str,
symbols: &[&str],
stop: Option<VolStop>,
bp: &'a str,
) -> SugarInvocation<'a> {
SugarInvocation {
axes,
name: name.to_string(),
symbols: symbols.iter().map(|s| s.to_string()).collect(),
from_ms: 100,
to_ms: 200,
blueprint_canonical: bp,
stop,
trace: false,
}
}
fn g_axes() -> Vec<(String, Vec<Scalar>)> {
vec![
("fast.length".to_string(), vec![Scalar::i64(3)]),
("slow.length".to_string(), vec![Scalar::i64(12)]),
]
}
fn wf_axes() -> Vec<(String, Vec<Scalar>)> {
vec![
("fast.length".to_string(), vec![Scalar::i64(3), Scalar::i64(5)]),
("slow.length".to_string(), vec![Scalar::i64(12), Scalar::i64(20)]),
]
}
const WF_W: WfWindows = WfWindows {
in_sample_ms: 7_776_000_000,
out_of_sample_ms: 2_592_000_000,
step_ms: 2_592_000_000,
};
#[test]
fn translate_sweep_is_deterministic_and_names_flow() {
let ax = axes();
let i = inv(&ax, "probe", &["GER40"], None, "{\"bp\":1}");
let a = translate_sweep(&i).unwrap();
let b = translate_sweep(&i).unwrap();
assert_eq!(
content_id_of(&campaign_to_json(&a.campaign)),
content_id_of(&campaign_to_json(&b.campaign)),
"identical invocations must generate identical campaign ids"
);
assert_eq!(a.campaign.name, "probe");
assert_eq!(a.campaign.seed, 0);
assert_eq!(a.process.name, "sweep");
assert_eq!(a.process.pipeline, vec![StageBlock::Sweep { selection: None }]);
assert_eq!(a.campaign.data.instruments, vec!["GER40".to_string()]);
assert_eq!(a.campaign.data.windows, vec![Window { from_ms: 100, to_ms: 200 }]);
assert_eq!(a.campaign.presentation.emit, vec!["family_table".to_string()]);
assert!(a.campaign.presentation.persist_taps.is_empty());
// the process ref is the content id of the generated process bytes
assert_eq!(
a.campaign.process.r#ref,
DocRef::ContentId(content_id_of(&process_to_json(&a.process)))
);
}
#[test]
fn translate_sweep_refuses_a_mixed_kind_axis() {
let mixed = vec![("k".to_string(), vec![Scalar::I64(1), Scalar::F64(2.5)])];
let err = translate_sweep(&inv(&mixed, "n", &["GER40"], None, "{}")).unwrap_err();
assert!(err.contains("mixed value kinds"), "{err}");
}
/// Property (#224, pin moved to #240): `SugarInvocation.trace = true`
/// requests the tap vocabulary MINUS the net channel on the generated
/// campaign's `presentation.persist_taps` — the honest inverse of the
/// #168 no-op (where `persist_taps` stayed `vec![]` regardless of
/// `--trace`), narrowed to what the generated campaign's own empty cost
/// section can actually produce (#240: `net_r_equity` is unproducible
/// there and must be excluded). `trace = false` (the default, pinned by
/// the sibling `translate_sweep_is_deterministic_and_names_flow`) keeps
/// the vocabulary empty. Kept as its own translator-level pin alongside
/// the cross-translator `dissolved_trace_requests_only_producible_taps_
/// excluding_the_net_channel` property test (#240): that test pins the
/// shared `persist_taps_from` seam once for both translators together,
/// this one keeps `translate_sweep`'s own regression independent of the
/// walkforward sibling so a translator-specific wiring break is not
/// masked by the other translator still passing.
#[test]
fn translate_sweep_trace_requests_the_producible_tap_subset() {
let ax = axes();
let mut i = inv(&ax, "probe", &["GER40"], None, "{\"bp\":1}");
i.trace = true;
let g = translate_sweep(&i).unwrap();
let want: Vec<String> = vec!["equity".to_string(), "exposure".to_string(), "r_equity".to_string()];
assert_eq!(g.campaign.presentation.persist_taps, want);
}
#[test]
fn generated_campaign_validates_intrinsically_and_preflights() {
let ax = axes();
let g = translate_sweep(&inv(&ax, "probe", &["GER40"], None, "{\"bp\":1}")).unwrap();
assert!(aura_research::validate_campaign(&g.campaign).is_empty());
assert!(aura_research::validate_process(&g.process).is_empty());
assert!(aura_campaign::preflight(&g.process, &g.campaign).is_ok());
}
/// The one seam all four verbs share (#220): `stop: Some(VolStop)` binds the
/// single Vol regime; `stop: None` binds no regime (sweep's contract).
#[test]
fn sugar_invocation_stop_maps_to_the_vol_regime() {
let ax = axes();
let mut i = inv(&ax, "n", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
let with = translate_sweep(&i).unwrap();
assert_eq!(with.campaign.risk, vec![RiskRegime::Vol { length: 14, k: 2.0 }]);
i.stop = None;
let without = translate_sweep(&i).unwrap();
assert!(without.campaign.risk.is_empty());
}
#[test]
fn translate_generalize_is_deterministic_and_carries_the_regime() {
let ax = g_axes();
let i = inv(&ax, "generalize", &["GER40", "USDJPY"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
let a = translate_generalize(&i, "expectancy_r").unwrap();
let b = translate_generalize(&i, "expectancy_r").unwrap();
assert_eq!(
content_id_of(&campaign_to_json(&a.campaign)),
content_id_of(&campaign_to_json(&b.campaign)),
"identical invocations must generate identical campaign ids"
);
// A selection-bearing pipeline: sweep(argmax) then generalize.
assert_eq!(
a.process.pipeline,
vec![
StageBlock::Sweep {
selection: Some(SweepSelection {
metric: "expectancy_r".to_string(),
select: SelectRule::Argmax,
deflate: false,
})
},
StageBlock::Generalize { metric: "expectancy_r".to_string() },
]
);
// The stop rides a single risk regime (the structural axis).
assert_eq!(a.campaign.risk, vec![RiskRegime::Vol { length: 14, k: 2.0 }]);
// All instruments under one campaign; the candidate as single-value axes.
assert_eq!(
a.campaign.data.instruments,
vec!["GER40".to_string(), "USDJPY".to_string()]
);
assert_eq!(a.campaign.strategies.len(), 1);
assert!(a.campaign.strategies[0].axes.contains_key("fast.length"));
assert!(a.campaign.strategies[0].axes.contains_key("slow.length"));
assert_eq!(a.process.name, "generalize");
assert_eq!(a.campaign.name, "generalize");
// Intrinsically valid + preflights (like the sweep sibling).
assert!(aura_research::validate_campaign(&a.campaign).is_empty());
assert!(aura_research::validate_process(&a.process).is_empty());
assert!(aura_campaign::preflight(&a.process, &a.campaign).is_ok());
}
/// Content-addressing correctness for the risk-regime axis (#210 T1-T2):
/// two invocations that differ ONLY in the protective stop (`stop_length`/
/// `stop_k`) must generate campaigns with DIFFERENT content ids. The stop
/// is now carried through `CampaignDoc.risk` rather than as an opaque
/// runtime constant, so a hashing/serialization bug that dropped or
/// normalized-away the regime would silently collide two distinct
/// invocations onto one cached campaign — the wrong grade would be served
/// for one of them without any error. This pins that a regime change is
/// visible in the generated bytes, both against a same-length,
/// different-k pair and a different-length, same-k pair.
#[test]
fn translate_generalize_diverging_regimes_do_not_collide_content_ids() {
let ax = g_axes();
let base = translate_generalize(&inv(&ax, "generalize", &["GER40", "USDJPY"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"), "expectancy_r").unwrap();
let different_k = translate_generalize(&inv(&ax, "generalize", &["GER40", "USDJPY"], Some(VolStop { length: 14, k: 3.0 }), "{\"bp\":1}"), "expectancy_r").unwrap();
let different_length = translate_generalize(&inv(&ax, "generalize", &["GER40", "USDJPY"], Some(VolStop { length: 20, k: 2.0 }), "{\"bp\":1}"), "expectancy_r").unwrap();
let base_id = content_id_of(&campaign_to_json(&base.campaign));
let k_id = content_id_of(&campaign_to_json(&different_k.campaign));
let length_id = content_id_of(&campaign_to_json(&different_length.campaign));
assert_ne!(base_id, k_id, "a different stop_k must not collide onto the same campaign id");
assert_ne!(
base_id, length_id,
"a different stop_length must not collide onto the same campaign id"
);
assert_ne!(k_id, length_id, "the two diverging regimes must not collide with each other");
}
/// The translator's output is not merely intrinsically valid in isolation
/// (preflight/validate never touch a store) — `register_generated_g` must
/// actually persist documents the campaign can resolve through a real
/// `Registry`: the campaign's `process.ref` content id must fetch back the
/// EXACT process the translator produced, and the returned `campaign_id`
/// must fetch back the exact campaign. A drift between what gets hashed
/// into the ref and what gets stored (e.g. a canonicalization mismatch)
/// would pass every static check here yet leave the campaign permanently
/// unresolvable at real run time — this is the property that would catch
/// that silently, before the dispatch rewire ever exercises the path live.
#[test]
fn register_generated_g_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-generalize-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
let ax = g_axes();
let generated = translate_generalize(
&inv(&ax, "generalize", &["GER40", "USDJPY"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"),
"expectancy_r",
)
.unwrap();
let (process_id, campaign_id) = register_generated_g(&reg, &generated).unwrap();
let stored_campaign = reg.get_campaign(&campaign_id).unwrap().expect("campaign stored");
assert_eq!(stored_campaign, campaign_to_json(&generated.campaign));
let DocRef::ContentId(ref_id) = &generated.campaign.process.r#ref else {
panic!("generalize's process ref is a content id");
};
assert_eq!(ref_id, &process_id, "the stored process id must be the campaign's own ref");
let stored_process = reg.get_process(ref_id).unwrap().expect("process stored");
assert_eq!(stored_process, process_to_json(&generated.process));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn translate_walkforward_is_deterministic_and_carries_the_regime() {
let ax = wf_axes();
let i = inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
let a = translate_walkforward(&i, "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let b = translate_walkforward(&i, "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
assert_eq!(
content_id_of(&campaign_to_json(&a.campaign)),
content_id_of(&campaign_to_json(&b.campaign)),
"identical invocations must generate identical campaign ids"
);
// pipeline: std::grid (enumerate-only) then walk_forward(argmax, rolling)
assert_eq!(
a.process.pipeline,
vec![
StageBlock::Grid,
StageBlock::WalkForward {
in_sample_ms: 7_776_000_000,
out_of_sample_ms: 2_592_000_000,
step_ms: 2_592_000_000,
mode: WfMode::Rolling,
metric: "sqn_normalized".to_string(),
select: SelectRule::Argmax,
},
]
);
assert_eq!(a.campaign.risk, vec![RiskRegime::Vol { length: 14, k: 2.0 }]);
assert_eq!(a.campaign.data.instruments, vec!["GER40".to_string()]);
assert_eq!(a.campaign.strategies.len(), 1);
// multi-value IS-refit grid axes
assert_eq!(
a.campaign.strategies[0].axes["fast.length"].values,
vec![Scalar::i64(3), Scalar::i64(5)]
);
assert_eq!(
a.campaign.strategies[0].axes["slow.length"].values,
vec![Scalar::i64(12), Scalar::i64(20)]
);
assert_eq!(a.process.name, "walkforward");
assert!(aura_research::validate_campaign(&a.campaign).is_empty());
assert!(aura_research::validate_process(&a.process).is_empty());
assert!(aura_campaign::preflight(&a.process, &a.campaign).is_ok());
}
/// Property (#224, pin moved to #240): mirrors
/// `translate_sweep_trace_requests_the_producible_tap_subset` for
/// walkforward's translator — the walkforward sibling refusal lifts
/// identically, riding the same `persist_taps_from` seam, and the same
/// net-channel exclusion applies (#240: the generated campaign's empty
/// cost section cannot produce `net_r_equity`). Kept as its own
/// translator-level pin alongside the cross-translator
/// `dissolved_trace_requests_only_producible_taps_excluding_the_net_
/// channel` property test for the same reason as the sweep sibling: a
/// walkforward-specific wiring break must not be masked by sweep alone
/// still passing.
#[test]
fn translate_walkforward_trace_requests_the_producible_tap_subset() {
let ax = wf_axes();
let mut i = inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
i.trace = true;
let g = translate_walkforward(&i, "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let want: Vec<String> = vec!["equity".to_string(), "exposure".to_string(), "r_equity".to_string()];
assert_eq!(g.campaign.presentation.persist_taps, want);
}
/// Property (#240): a dissolved `--trace` invocation's generated campaign
/// requests only the taps its own document can produce. Every generated
/// sugar campaign carries an empty cost section (`cost: vec![]`) by
/// construction, so the net-channel tap `net_r_equity` is unproducible and
/// must be excluded — the requested set is the tap vocabulary MINUS the net
/// channel, concretely `["equity", "exposure", "r_equity"]`. Pinned for
/// both trace-bearing translators (sweep + walkforward), which share the
/// one `persist_taps_from` seam; without this the executor later prints an
/// unreachable "add a cost block to the campaign document" remedy for a tap
/// the generator knowingly ordered but its own document cannot produce.
#[test]
fn dissolved_trace_requests_only_producible_taps_excluding_the_net_channel() {
let want =
vec!["equity".to_string(), "exposure".to_string(), "r_equity".to_string()];
let sax = axes();
let mut si = inv(&sax, "probe", &["GER40"], None, "{\"bp\":1}");
si.trace = true;
let s = translate_sweep(&si).unwrap();
assert_eq!(
s.campaign.presentation.persist_taps, want,
"sweep --trace must request only producible taps: the generated cost section is empty, so net_r_equity is unproducible and must be dropped"
);
let wax = wf_axes();
let mut wi = inv(&wax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
wi.trace = true;
let w = translate_walkforward(&wi, "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
assert_eq!(
w.campaign.presentation.persist_taps, want,
"walkforward --trace must request only producible taps: the generated cost section is empty, so net_r_equity is unproducible and must be dropped"
);
}
#[test]
fn translate_walkforward_diverging_regimes_do_not_collide_content_ids() {
let ax = wf_axes();
let base = translate_walkforward(&inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let different_k = translate_walkforward(&inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 3.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let different_length = translate_walkforward(&inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 20, k: 2.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let base_id = content_id_of(&campaign_to_json(&base.campaign));
let k_id = content_id_of(&campaign_to_json(&different_k.campaign));
let length_id = content_id_of(&campaign_to_json(&different_length.campaign));
assert_ne!(base_id, k_id, "a different stop_k must not collide onto the same campaign id");
assert_ne!(
base_id, length_id,
"a different stop_length must not collide onto the same campaign id"
);
assert_ne!(k_id, length_id, "the two diverging regimes must not collide with each other");
}
#[test]
fn register_generated_wf_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-walkforward-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
let ax = wf_axes();
let generated = translate_walkforward(
&inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"),
"sqn_normalized",
WF_W,
SelectRule::Argmax,
)
.unwrap();
let (process_id, campaign_id) = register_generated_wf(&reg, &generated).unwrap();
let stored_campaign = reg.get_campaign(&campaign_id).unwrap().expect("campaign stored");
assert_eq!(stored_campaign, campaign_to_json(&generated.campaign));
let DocRef::ContentId(ref_id) = &generated.campaign.process.r#ref else {
panic!("walkforward's process ref is a content id");
};
assert_eq!(ref_id, &process_id, "the stored process id must be the campaign's own ref");
let stored_process = reg.get_process(ref_id).unwrap().expect("process stored");
assert_eq!(stored_process, process_to_json(&generated.process));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn translate_mc_is_deterministic_and_carries_the_regime_and_seed() {
let ax = wf_axes();
let i = inv(&ax, "mc", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
let a = translate_mc(&i, "sqn_normalized", WF_W, McKnobs { resamples: 1000, block_len: 5, seed: 42 }).unwrap();
let b = translate_mc(&i, "sqn_normalized", WF_W, McKnobs { resamples: 1000, block_len: 5, seed: 42 }).unwrap();
assert_eq!(
content_id_of(&campaign_to_json(&a.campaign)),
content_id_of(&campaign_to_json(&b.campaign)),
"identical invocations must generate identical campaign ids"
);
// pipeline: std::grid (enumerate-only) then walk_forward(argmax, rolling) then monte_carlo
assert_eq!(
a.process.pipeline,
vec![
StageBlock::Grid,
StageBlock::WalkForward {
in_sample_ms: 7_776_000_000,
out_of_sample_ms: 2_592_000_000,
step_ms: 2_592_000_000,
mode: WfMode::Rolling,
metric: "sqn_normalized".to_string(),
select: SelectRule::Argmax,
},
StageBlock::MonteCarlo { resamples: 1000, block_len: 5 },
]
);
assert_eq!(a.campaign.seed, 42, "the campaign seed carries the mc --seed");
assert_eq!(a.campaign.risk, vec![RiskRegime::Vol { length: 14, k: 2.0 }]);
assert_eq!(a.campaign.data.instruments, vec!["GER40".to_string()]);
assert_eq!(a.campaign.strategies.len(), 1);
assert_eq!(
a.campaign.strategies[0].axes["fast.length"].values,
vec![Scalar::i64(3), Scalar::i64(5)]
);
assert_eq!(
a.campaign.strategies[0].axes["slow.length"].values,
vec![Scalar::i64(12), Scalar::i64(20)]
);
assert_eq!(a.process.name, "mc");
assert!(aura_research::validate_campaign(&a.campaign).is_empty());
assert!(aura_research::validate_process(&a.process).is_empty());
assert!(aura_campaign::preflight(&a.process, &a.campaign).is_ok());
}
#[test]
fn translate_mc_diverging_seeds_and_stops_do_not_collide_content_ids() {
let ax = wf_axes();
let base = translate_mc(&inv(&ax, "mc", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, McKnobs { resamples: 1000, block_len: 5, seed: 42 }).unwrap();
let different_seed = translate_mc(&inv(&ax, "mc", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, McKnobs { resamples: 1000, block_len: 5, seed: 7 }).unwrap();
let different_k = translate_mc(&inv(&ax, "mc", &["GER40"], Some(VolStop { length: 14, k: 3.0 }), "{\"bp\":1}"), "sqn_normalized", WF_W, McKnobs { resamples: 1000, block_len: 5, seed: 42 }).unwrap();
let base_id = content_id_of(&campaign_to_json(&base.campaign));
let seed_id = content_id_of(&campaign_to_json(&different_seed.campaign));
let k_id = content_id_of(&campaign_to_json(&different_k.campaign));
assert_ne!(base_id, seed_id, "a different mc seed must not collide onto the same campaign id");
assert_ne!(base_id, k_id, "a different stop_k must not collide onto the same campaign id");
assert_ne!(seed_id, k_id, "the two diverging documents must not collide with each other");
}
#[test]
fn register_generated_mc_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-mc-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
let ax = wf_axes();
let generated = translate_mc(
&inv(&ax, "mc", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}"),
"sqn_normalized",
WF_W,
McKnobs { resamples: 1000, block_len: 5, seed: 42 },
)
.unwrap();
let (process_id, campaign_id) = register_generated_mc(&reg, &generated).unwrap();
let stored_campaign = reg.get_campaign(&campaign_id).unwrap().expect("campaign stored");
assert_eq!(stored_campaign, campaign_to_json(&generated.campaign));
let DocRef::ContentId(ref_id) = &generated.campaign.process.r#ref else {
panic!("mc's process ref is a content id");
};
assert_eq!(ref_id, &process_id, "the stored process id must be the campaign's own ref");
let stored_process = reg.get_process(ref_id).unwrap().expect("process stored");
assert_eq!(stored_process, process_to_json(&generated.process));
let _ = std::fs::remove_dir_all(&dir);
}
}