fix(aura-cli, aura-runner): review findings — duplicate --override refusal, live discovery prose, neutral JSON refusal

In-cycle fixes for the independent review of the #319 package, RED-first
for both Importants. A repeated --override path now refuses at the shared
lexer (exit 2, path named) instead of panicking exit 101 through
reopen_all — which also un-misattributes the campaign leg's collision
prose for duplicates. override_paths' refusal points at aura graph
introspect --params (the retired --list-axes reference swept from every
live comment, one explicitly-historical mention remains). exec classifies
file targets through a Result-bearing peek: unparseable JSON refuses
neutrally before any leg is chosen. RunPresentation (single-variant)
removed; run_campaign_returning's contract note names its one deliberate
usage-class exit; C14's routing sentence and C20's family-builder
paragraph amended to current truth (superseded text to the history
sidecars); bench error strings and deletion blank-line runs tidied.

refs #319
This commit is contained in:
2026-07-25 23:21:07 +02:00
parent 9eb6d6b4f6
commit dc5f1742f4
14 changed files with 326 additions and 236 deletions
+1 -1
View File
@@ -251,7 +251,7 @@ fn campaign_rep(bin: &Path, sizing: Sizing, process_doc: &str) -> Result<RepOutc
let timed: TimedChild = run_timed(bin, &["exec", &doc], &scratch.0)?;
if timed.exit != Some(0) {
return Err(format!(
"campaign run exited {:?}\nstdout: {}\nstderr: {}",
"aura exec exited {:?}\nstdout: {}\nstderr: {}",
timed.exit, timed.stdout, timed.stderr
));
}
+2 -2
View File
@@ -61,7 +61,7 @@ pub fn rep(bin: &Path) -> Result<RepOutcome, String> {
for _ in 0..RUN_SAMPLES {
let timed = run_timed(bin, &["exec", "bench_sma_a.json"], &scratch.0)?;
if timed.exit != Some(0) {
return Err(format!("aura run exited {:?}: {}", timed.exit, timed.stdout));
return Err(format!("aura exec exited {:?}: {}", timed.exit, timed.stdout));
}
run_walls.push(timed.wall_s * 1000.0);
if first_line.is_none() {
@@ -70,7 +70,7 @@ pub fn rep(bin: &Path) -> Result<RepOutcome, String> {
.stdout
.lines()
.next()
.ok_or("aura run must print its JSON record line")?
.ok_or("aura exec must print its JSON record line")?
.to_string(),
);
}
+32 -41
View File
@@ -108,15 +108,6 @@ struct CampaignRunLine<'a> {
campaign_run: &'a CampaignRunRecord,
}
/// Stdout shape of a campaign run: emit-gated member/selection lines + the
/// always-on final `campaign_run` record line. `MemberLinesOnly` (the
/// dissolved-verb sugar path's suppressed-record-line mode) retired with the
/// quintet (#319 Task 8) — `Full` is the only surviving shape.
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum RunPresentation {
Full,
}
/// `aura campaign run <target>`: resolve, gate, execute, emit. Every `Err`
/// is a refusal `campaign_cmd` renders as `aura: {msg}` + exit 1; an `Ok`
/// carries the failed-cell count (#272) so the caller can exit 3 on a
@@ -161,7 +152,7 @@ pub(crate) fn run_campaign(
));
};
run_campaign_by_id(&campaign_id, env, RunPresentation::Full, parallel_instruments, overrides)
run_campaign_by_id(&campaign_id, env, parallel_instruments, overrides)
}
/// An executed campaign plus the context its presenter and the dissolved-verb
@@ -180,18 +171,15 @@ pub(crate) struct CampaignRun {
/// The one campaign executor path from a resolved content id onward: fetch
/// the stored canonical bytes by id (so file addressing and id addressing
/// produce the same realization by construction) and re-run the intrinsic
/// tier on them, execute, and emit per `presentation`. Called by
/// `run_campaign` (`RunPresentation::Full`, the sole surviving shape since
/// the quintet's dissolved-verb sugar path retired, #319 Task 8).
/// tier on them, execute, and emit. Called by `run_campaign`.
pub(crate) fn run_campaign_by_id(
campaign_id: &str,
env: &Env,
presentation: RunPresentation,
parallel_instruments: NonZeroUsize,
overrides: &[(String, Scalar)],
) -> Result<usize, String> {
let run = run_campaign_returning(campaign_id, env, parallel_instruments, overrides)?;
present_campaign(run, presentation, env)
present_campaign(run, env)
}
/// The one campaign executor path from a resolved content id up to (not
@@ -206,6 +194,15 @@ pub(crate) fn run_campaign_by_id(
/// and BEFORE the referential gate — the stored bytes and the recorded
/// `campaign_id` stay those of the ORIGINAL document; only the executed
/// run-set changes, its audit record being the raw member manifests.
///
/// One deliberate exception to "every refusal is an `Err`" (review Minor-4):
/// the injection loop's axis-collision check exits directly with code 2
/// instead of returning `Err`. That collision is a USAGE-class fault (C14) —
/// `--override` naming a path the document already declares as an axis is a
/// malformed invocation, exactly like `--override`'s own malformed-token
/// refusal (`parse_override_tokens`, exit 2) — not a RUNTIME-class refusal
/// (missing environment/recorded state, exit 1), which is what this fn's
/// `Result<_, String>` return otherwise carries end to end.
pub(crate) fn run_campaign_returning(
campaign_id: &str,
env: &Env,
@@ -316,15 +313,11 @@ of the campaign; an override overrides a bound value, never an axis"
Ok(CampaignRun { outcome, campaign, strategies, server: runner.server() })
}
/// Emit an executed campaign's results per `presentation`: the zero-survivor
/// stderr notes, the emit-gated family/selection lines, the always-on record
/// line (Full only), then trace persistence. Split out of `run_campaign_by_id`
/// so the dissolved-verb sugar can consume the outcome without this stdout tail.
fn present_campaign(
run: CampaignRun,
presentation: RunPresentation,
env: &Env,
) -> Result<usize, String> {
/// Emit an executed campaign's results: the zero-survivor stderr notes, the
/// emit-gated family/selection lines, the always-on record line, then trace
/// persistence. Split out of `run_campaign_by_id` so a forthcoming caller
/// could consume the outcome without this stdout tail.
fn present_campaign(run: CampaignRun, env: &Env) -> Result<usize, String> {
let CampaignRun { outcome, campaign, strategies, server } = run;
// Zero-survivor stderr notes (exit stays 0 — a null result is a valid
@@ -413,23 +406,21 @@ fn present_campaign(
}
}
}
if presentation == RunPresentation::Full {
println!(
"{}",
serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
.expect("campaign run record serializes")
);
eprintln!(
"aura: campaign run {} recorded: {} cells{}",
outcome.record.run,
outcome.record.cells.len(),
if failed == 0 {
String::new()
} else {
format!(", {failed} failed ({})", fail_labels.join(", "))
}
);
}
println!(
"{}",
serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
.expect("campaign run record serializes")
);
eprintln!(
"aura: campaign run {} recorded: {} cells{}",
outcome.record.run,
outcome.record.cells.len(),
if failed == 0 {
String::new()
} else {
format!(", {failed} failed ({})", fail_labels.join(", "))
}
);
// Trace persistence runs LAST, after the always-on record line (0109):
// stdout stays data-pure (the record line is the wire contract) and the
+8 -6
View File
@@ -642,10 +642,11 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd, env: &aura_runner::project
} else if let Some(target) = cmd.params.as_deref() {
// --params <FILE|ID> (#196): the RAW composite param space — the one
// namespace campaign axes are validated against
// (`validate_campaign_refs`) and `aura sweep --list-axes` prints
// (#328: the wrapped `<blueprint>.<node>.<param>` form was retired —
// there is exactly one axis namespace now, so the two discovery
// surfaces agree line-for-line, see `params_lines`'s own doc comment).
// (`validate_campaign_refs`) and `exec --override`'s own resolution
// (`override_paths`, member.rs) resolves against (#328: the wrapped
// `<blueprint>.<node>.<param>` form was retired — there is exactly
// one axis namespace now, so the two surfaces agree line-for-line,
// see `params_lines`'s own doc comment).
match params_lines(target, env) {
Ok(s) => print!("{s}"),
Err(m) => {
@@ -938,8 +939,9 @@ fn resolve_blueprint_text(target: &str, env: &aura_runner::project::Env) -> Resu
/// the same form `introspect --node` already uses for param kinds. Followed
/// by one `{name}:{kind:?} default={value}` line per BOUND param (#328):
/// `bound_param_space()`'s names are already RAW (#203), so no blueprint-name
/// concatenation is needed — line-identical to the reconciled `--list-axes`
/// bound pass (`list_blueprint_axes`, main.rs), same `render_value` lexicon.
/// concatenation is needed — line-identical to the retired `aura sweep
/// --list-axes`'s own bound pass (`list_blueprint_axes`, main.rs, gone with
/// the #319 sugar retirement), same `render_value` lexicon.
/// #331 delta re-review: the FILE target is a FOURTH freshly-authored-file
/// intake this fn's own `composite_from_any` call had missed gating — a FILE
/// routes through [`composite_from_authored_text`] instead; a content id
+60 -33
View File
@@ -72,8 +72,8 @@ use aura_runner::reproduce::reproduce_family_in;
use aura_runner::measure::run_measurement;
// `render_value` is the single source (aura-runner, #295 Task 7): the
// campaign trace layout's member-key label and this shell's own
// presentation (reproduce output, `--list-axes` default printing) render a
// scalar identically.
// presentation (reproduce output, `graph introspect --params`'s bound-pass
// default printing) render a scalar identically.
use aura_runner::runner::render_value;
// Only reached from the test module's stop-regime default tests now (#319
// Task 8 — the quintet's own production callers are retired); test-only,
@@ -612,31 +612,30 @@ impl IcReport {
}
}
/// `aura sweep <blueprint.json> --list-axes`: one `<name>:<kind>` line per open
/// sweepable knob, in `param_space()` order (byte-identical to before #246),
/// followed by one `<name>:<kind> default=<value>` line per BOUND param —
/// every bound param IS a sweepable default, re-openable by naming it as an
/// `--axis` (#246), on EVERY blueprint shape (fully open, partially open, or
/// fully closed) — not only the fully-closed case: `--axis`/`override_paths`
/// accepts a bound name regardless of how many knobs happen to be open
/// alongside it, so the discovery surface must list it regardless too. Names
/// are RAW `<node>.<param>` paths (#328: the wrapped `<blueprint>.<node>.<param>`
/// form is retired from the surface) — exactly what `--axis` binds.
///
/// Lex `exec --override NODE.PARAM=VALUE` tokens (#319 Task 3/4): the same
/// int-then-float rule `parse_scalar_csv` uses for `--axis` values, shared by
/// int-then-float rule the campaign leg's `--axis` values use, shared by
/// both `exec` legs (the blueprint leg's reopen recipe and the campaign
/// leg's single-value-axis injection) so the wording and the parse rule
/// cannot drift between them. A malformed token refuses at the usage
/// boundary (exit 2) — intake, not a per-leg concern.
///
/// A repeated path (the same NODE.PARAM named twice) likewise refuses here
/// (exit 2), mirroring `--tap`'s own duplicate refusal
/// (`tap_plan_from_args`): downstream, the blueprint leg's `reopen_all`
/// folds the override set onto the signal one path at a time, so a second
/// occurrence of an already-reopened path is no longer bound and panics
/// (`NoSuchBoundParam`) — refusing the duplicate at the lexer, before either
/// leg ever resolves it, is the one gate that protects both.
fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
let mut ov = Vec::new();
let mut seen = BTreeSet::new();
for tok in tokens {
let Some((path_s, val_s)) = tok.split_once('=') else {
eprintln!("aura: exec: --override expects NODE.PARAM=VALUE, got `{tok}`");
std::process::exit(2);
};
if path_s.trim().is_empty() {
let path = path_s.trim();
if path.is_empty() {
eprintln!("aura: exec: --override expects NODE.PARAM=VALUE, got `{tok}`");
std::process::exit(2);
}
@@ -650,7 +649,11 @@ fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
}
},
};
ov.push((path_s.trim().to_string(), val));
if !seen.insert(path.to_string()) {
eprintln!("aura: exec: --override names path \"{path}\" twice");
std::process::exit(2);
}
ov.push((path.to_string(), val));
}
ov
}
@@ -659,15 +662,17 @@ fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
// The declarative argument grammar. clap owns argv tokenizing, scoped `--help`,
// `--version`, `--flag=value`, `--`, and long-option abbreviation; the `dispatch_*`
// handlers below convert each `*Cmd` into the argument shapes the existing execution
// fns accept, reusing the value helpers (`Selection`,
// `DataSource::from_choice`, `parse_scalar_csv`, `parse_select`,
// `parse_param_cells`). The four subcommands with an optional `[blueprint]` positional
// dispatch on `is_blueprint_file`: a first-positional naming an existing `.json` file
// selects the loaded-blueprint branch. There is no second built-in grammar anymore
// (#159 demo retirement / #220 axis generalization): without a blueprint the
// dispatcher prints a usage error and exits 2; a blueprint with `--real` routes to
// the campaign path. Usage errors exit 2; runtime refusals exit 1; a run that
// completes with ≥1 failed cell exits 3 (#272); a clean run exits 0.
// fns accept. `exec` (#319) is the one document-executing verb: it dispatches on
// `is_blueprint_file` layered with `classify_exec_document_file` (the "kind"-field
// peek) — a first-positional naming an existing `.json` file that parses as JSON
// and carries no top-level "kind" selects the loaded-blueprint leg; a file
// carrying "kind", or a target that is not a readable `.json` file at all (a
// registered content id), selects the campaign leg; a file that does not parse as
// JSON at all refuses neutrally before either leg's own validation runs.
// `--override`/`--tap` tokens are lexed by `parse_override_tokens`/
// `tap_plan_from_args` above, shared across both legs where applicable. Usage
// errors exit 2; runtime refusals exit 1; a campaign that completes with ≥1
// failed cell exits 3 (#272); a clean run exits 0.
/// The single build-time commit provenance (`option_env!("AURA_COMMIT")`,
/// falling back to `"unknown"`): both `--version` (via `version_string`) and
@@ -955,7 +960,7 @@ struct ExecCmd {
/// The file-target discriminator: a first-positional that names an existing
/// `.json` file is a document on disk. Shared by `exec`'s target routing
/// (layered with `is_campaign_document_file`) and `dispatch_graph`.
/// (layered with `classify_exec_document_file`) and `dispatch_graph`.
fn is_blueprint_file(arg: &Option<String>) -> Option<&str> {
arg.as_deref()
.filter(|a| a.ends_with(".json") && std::path::Path::new(a).is_file())
@@ -969,11 +974,19 @@ fn is_blueprint_file(arg: &Option<String>) -> Option<&str> {
/// token — it cannot tell a campaign document ending in `.json` from a
/// blueprint file, both being ordinary readable `.json` files — so `exec`'s
/// routing peeks this one cheap key before choosing a leg.
fn is_campaign_document_file(path: &str) -> bool {
std::fs::read_to_string(path)
.ok()
.and_then(|t| serde_json::from_str::<serde_json::Value>(&t).ok())
.is_some_and(|v| v.get("kind").is_some())
///
/// `Ok(true)`/`Ok(false)` classifies a file that parses as JSON at all
/// (campaign vs. not); `Err` means the file does NOT parse as JSON at all —
/// review Minor-3: a file this malformed has no leg to misattribute the
/// fault to (neither leg's own "document is not valid JSON" prose is the
/// right cause when the shape was never determined), so the caller refuses
/// neutrally instead of silently falling through to the blueprint leg.
fn classify_exec_document_file(path: &str) -> Result<bool, String> {
let text =
std::fs::read_to_string(path).map_err(|e| format!("target file is not readable: {e}"))?;
let v: serde_json::Value = serde_json::from_str(&text)
.map_err(|e| format!("target file is not valid JSON: {e}"))?;
Ok(v.get("kind").is_some())
}
/// Terminate per a campaign-path result (#272): a String error is a refusal
@@ -1017,10 +1030,24 @@ fn tap_plan_from_args(args: &[String]) -> Result<TapPlan, String> {
}
/// The one executor verb (#319): a campaign document (file or content id)
/// or a signal blueprint (single run). Flag discipline is per-leg.
/// or a signal blueprint (single run). Flag discipline is per-leg. A
/// `.json` file target is read and classified ONCE (review Minor-3): a file
/// that does not parse as JSON at all refuses neutrally here, before either
/// leg's own document-shape validation ever runs — so a malformed campaign
/// document is never misattributed to the blueprint leg's "blueprint
/// document is not valid JSON" prose.
fn dispatch_exec(a: ExecCmd, env: &aura_runner::project::Env) {
let target = Some(a.target.clone());
let blueprint_path = is_blueprint_file(&target).filter(|p| !is_campaign_document_file(p));
let file_path = is_blueprint_file(&target);
let is_campaign_file = match file_path.map(classify_exec_document_file) {
Some(Ok(is_campaign)) => is_campaign,
Some(Err(msg)) => {
eprintln!("aura: exec: {}: {msg}", file_path.expect("Some by the match arm above"));
std::process::exit(2);
}
None => false,
};
let blueprint_path = file_path.filter(|_| !is_campaign_file);
match blueprint_path {
Some(path) => exec_blueprint_leg(path, &a.r#override, &a.tap, env),
None => {
+63 -133
View File
@@ -203,7 +203,6 @@ fn sweep_campaign_doc(
)
}
/// 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
@@ -391,9 +390,6 @@ fn run_real_nonvetted_symbol_resolves_pip_from_sidecar() {
);
}
// 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
@@ -736,8 +732,6 @@ fn no_args_prints_usage_and_exits_two() {
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
@@ -878,7 +872,6 @@ fn graph_with_bad_blueprint_arg_is_a_usage_fault_not_a_silent_sample() {
);
}
#[test]
fn runs_families_list_and_per_family_rank_across_invocations() {
// Ported onto a one-cell campaign document run twice (#319 Task 8): two
@@ -1328,7 +1321,6 @@ fn run_real_ohlc_channel_example_end_to_end_hostless() {
);
}
/// 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
@@ -1755,24 +1747,27 @@ fn reproduce_real_sweep_family_re_derives_bit_identically() {
/// 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. The walk-forward
/// synthetic route's own `--axis` intake is now RAW (this cycle's tidy fix,
/// #328): the CLI preflight refuses a wrapped name before this run ever starts
/// 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 translates the RAW name onto its wrapped `param_space()`
/// slot (`wrapped_name_of`) before keying by it.
/// Modelled on that sibling (synthetic in-process walk-forward — no `--real`),
/// NOT on `reproduce_real_sweep_family_re_derives_bit_identically`: a real-data
/// mint routes through the campaign executor (`CliMemberRunner::run_member` +
/// `validate_and_register_axes`), neither of which carries any #246 override
/// threading — a gap outside this task's file scope (`blueprint_sweep_over`,
/// `reproduce_family_in`) and spanning all four dispatch verbs, so exercising
/// it here would silently expand into unrelated, unreviewed surface. The
/// synthetic walk-forward path exercises exactly the threaded functions:
/// `blueprint_sweep_over` (via `blueprint_walkforward_family`) re-opens
/// `fast.length` for the per-window IS re-fit, and
/// `reproduce_family_in` re-opens it again from the recorded manifest params
/// so the re-derivation resolves against the same space the mint used.
/// `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
@@ -1939,7 +1934,6 @@ fn reproduce_real_walkforward_family_does_not_panic() {
// 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
@@ -2042,12 +2036,6 @@ fn sweep_real_multi_column_blueprint_opens_high_and_low_columns() {
}
}
/// 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
@@ -2168,11 +2156,6 @@ fn sweep_over_a_bound_override_matches_the_historical_open_bind() {
);
}
// 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
@@ -2188,12 +2171,6 @@ fn sweep_over_a_bound_override_matches_the_historical_open_bind() {
// --- `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.
@@ -2307,17 +2284,20 @@ fn generalize_real_e2e_pins_the_exact_current_grade() {
assert_eq!(per[1][1].as_f64(), Some(0.005795903617609842), "USDJPY expectancy R: {record_line}");
}
/// Property (#220 generalize vertical): `aura generalize` grades ANY sweepable
/// blueprint, 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 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.
/// 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
@@ -2367,18 +2347,20 @@ fn generalize_grades_a_non_r_sma_blueprint() {
}
/// Characterization pin (byte-identity anchor for the walkforward dissolution,
/// #210). The current `aura walkforward` runs its per-window IS-refit inline
/// (`walkforward_family` -> `select_winner` -> OOS run -> stitch/pool); the
/// dissolution reroutes the same rolling IS-sweep -> OOS-run -> deflation through
/// the campaign `std::walk_forward` stage, and must retain the OOS pip curve the
/// campaign runner currently discards (`oos_equity: vec![]`) to reproduce
/// #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 cannot ship green. This pins the EXACT current
/// grade of the identical invocation over a fixed 2025 GER40 window; after the
/// dissolution the same command must reproduce these bytes (the acceptance gate).
/// Gated on the shared GER40 archive; skips cleanly on a data refusal.
/// 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
@@ -2453,21 +2435,24 @@ fn walkforward_real_e2e_pins_the_exact_current_grade() {
assert_eq!(ps[1]["mean"].as_f64(), Some(17.333333333333332), "slow-MA refit mean: {wf}");
}
/// Property (#220 walkforward vertical): `aura walkforward --real` IS-refits 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 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.
/// 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
@@ -2515,8 +2500,6 @@ fn walkforward_dissolves_a_non_r_sma_blueprint() {
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
@@ -2597,17 +2580,6 @@ fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
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
@@ -2626,12 +2598,6 @@ fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
// 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
@@ -2843,7 +2809,6 @@ fn aura_sweep_loads_a_blueprint_and_persists_a_sweep_family() {
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
@@ -3133,8 +3098,6 @@ fn aura_walkforward_over_a_blueprint_persists_a_family() {
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
@@ -3218,14 +3181,6 @@ fn aura_sweep_synthetic_member_panic_is_contained() {
);
}
/// 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]
@@ -3275,13 +3230,6 @@ fn aura_reproduce_rejects_a_missing_stored_blueprint() {
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),
@@ -3335,13 +3283,6 @@ fn version_flag_carries_the_engine_commit_from_manifest_provenance() {
);
}
/// 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
@@ -3715,8 +3656,6 @@ fn mc_real_fits_the_injected_roller_to_a_short_window() {
);
}
/// 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,
@@ -3962,15 +3901,6 @@ fn generalize_campaign_runs_an_arbitrary_blueprint() {
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
+96 -1
View File
@@ -234,6 +234,30 @@ fn exec_campaign_target_outside_project_refuses() {
assert!(!dir.join("runs").exists(), "a refused run must not create a store outside a project");
}
/// A malformed FILE target (a trailing comma — not valid JSON at all) must
/// refuse NEUTRALLY at the routing seam, before either leg's own
/// document-shape validation ever runs — not silently fall through to the
/// blueprint leg's "blueprint document is not valid JSON" prose (review
/// Minor-3). The file's shape (blueprint vs. campaign) was never
/// determined, so neither leg's own wording is the right attribution.
#[test]
fn exec_malformed_json_file_refuses_neutrally_not_misrouted() {
let (dir, _fixture) = fresh_project();
let malformed = r#"{
"format_version": 1,
"kind": "campaign",
"name": "broken",
}"#;
write_doc(&dir, "broken.campaign.json", malformed);
let (out, code) = run_code_in(&dir, &["exec", "broken.campaign.json"]);
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(out.contains("target file is not valid JSON"), "stdout/stderr: {out}");
assert!(
!out.contains("blueprint document is not valid JSON"),
"must not misattribute to the blueprint leg's own prose: {out}"
);
}
// ---------------------------------------------------------------------------
// Task 2: the exec blueprint leg (synthetic single run).
// ---------------------------------------------------------------------------
@@ -448,7 +472,9 @@ fn exec_override_malformed_token_refuses_with_usage() {
}
/// An override path naming no param of the blueprint (open or bound) refuses
/// with `override_paths`' own prose (`member.rs:768-771`).
/// with `override_paths`' own prose (`member.rs:768-771`), pointing at the
/// live axis-discovery verb (`aura graph introspect --params`) — the retired
/// `aura sweep --list-axes` no longer exists.
#[test]
fn exec_override_unknown_path_refuses_with_the_override_prose() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "nope.knob=1"]);
@@ -457,6 +483,34 @@ fn exec_override_unknown_path_refuses_with_the_override_prose() {
out.contains("names no param of this blueprint (open or bound)"),
"stdout/stderr: {out}"
);
assert!(
out.contains("aura graph introspect --params"),
"must point at the live discovery verb, not the retired --list-axes: {out}"
);
}
/// A repeated `--override` path (the same NODE.PARAM named twice) refuses as
/// a usage error naming the duplicated path — it must NOT fall through to
/// `reopen_all`'s `NoSuchBoundParam` panic (the second reopen of an
/// already-reopened bound param, exit 101): the first occurrence reopens
/// `fast.length`, so a second `override_paths` resolution against the SAME
/// un-reopened bound-name set still succeeds, and only `reopen_all`'s fold
/// discovers the collision, too late to refuse cleanly. Mirrors `--tap`'s own
/// duplicate refusal (`tap_plan_from_args`).
#[test]
fn exec_override_duplicate_path_refuses_with_usage_not_a_panic() {
let (out, code) = run_code(&[
"exec",
"examples/r_sma.json",
"--override",
"fast.length=5",
"--override",
"fast.length=9",
]);
assert_ne!(code, Some(101), "must not panic: {out}");
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(out.contains("fast.length"), "the duplicated path must be named: {out}");
assert!(out.contains("--override"), "stdout/stderr: {out}");
}
// ---------------------------------------------------------------------------
@@ -535,6 +589,47 @@ fn exec_campaign_override_colliding_with_a_document_axis_refuses() {
assert!(out.contains("declared axis"), "stdout/stderr: {out}");
}
/// A repeated `--override` path on the campaign leg refuses at the SAME
/// shared lexer (`parse_override_tokens`) both legs use — before the
/// per-strategy injection loop (`run_campaign_returning`) ever runs. Prior
/// to the fix, the first occurrence's own injection made the SECOND
/// occurrence look like a document-declared axis, misattributing the
/// refusal to "collides with a declared axis" (review Minor-1) even though
/// `bias.scale` is not declared by the document at all.
#[test]
fn exec_campaign_override_duplicate_path_refuses_at_the_shared_lexer() {
let (dir, _fixture) = fresh_project_with_data();
let runs_dir = dir.join("runs");
std::fs::remove_dir_all(&runs_dir).ok();
let _cleanup = ScratchGuard(vec![
ScratchPath::Dir(runs_dir.clone()),
ScratchPath::File(dir.join("ovdup.process.json")),
ScratchPath::File(dir.join("ovdup.campaign.json")),
]);
let bp_id = seed_blueprint(&dir, "exec-override-dup-seed");
let proc_id = register_process_doc(&dir, "ovdup.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = campaign_doc_json_for("SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999));
write_doc(&dir, "ovdup.campaign.json", &doc);
let (out, code) = run_code_in(
&dir,
&[
"exec",
"ovdup.campaign.json",
"--override",
"bias.scale=0.75",
"--override",
"bias.scale=0.9",
],
);
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(out.contains("bias.scale"), "the duplicated path must be named: {out}");
assert!(
!out.contains("collides with a declared axis"),
"must not misattribute to the axis-collision cause: {out}"
);
}
/// An override path naming no param of any strategy (a typo'd raw name)
/// falls out of `validate_campaign_refs`'s existing `AxisNotInParamSpace`
/// did-you-mean prose — pinned literally in `research_docs.rs` (:797-822);
+4 -2
View File
@@ -574,7 +574,8 @@ pub enum ReopenError {
/// One entry of the aggregated BOUND param surface (#246): the path-qualified
/// twin of [`ParamSpec`] carrying the bound value — the default a sweep axis
/// may override, and what `--list-axes` renders as `default=`.
/// may override, and what `aura graph introspect --params` renders as
/// `default=`.
#[derive(Debug, Clone, PartialEq)]
pub struct BoundSpec {
pub name: String,
@@ -3376,7 +3377,8 @@ mod tests {
}
/// #246: bound_param_space mirrors param_space's path qualification and
/// carries the bound value (the default the CLI renders in --list-axes).
/// carries the bound value (the default the CLI renders in
/// `aura graph introspect --params`).
#[test]
fn bound_param_space_is_path_qualified_with_values() {
use aura_strategy::Bias;
+2 -1
View File
@@ -64,7 +64,8 @@ pub fn raw_matches_wrapped(raw: &str, wrapped: &str) -> bool {
/// instead of refusing it.
#[derive(Debug, PartialEq, Eq)]
pub enum AxisIntake {
/// A legal RAW name (`--list-axes`'s own namespace, #328) — accept as-is.
/// A legal RAW name (`aura graph introspect --params`'s own namespace,
/// #328) — accept as-is.
Raw,
/// A retired WRAPPED name; the carried string is its translated RAW
/// candidate — never applied silently, only ever surfaced in a refusal.
+6 -4
View File
@@ -659,8 +659,9 @@ pub fn run_blueprint_member(
/// against: the loaded signal wrapped in the r-sma scaffolding (stop bound,
/// reduce, no cost), taps discarded. `param_space()` on it is the axis
/// namespace `--axis` binds; `.axis()` consumes it to seed a sweep. Single
/// source for the sweep terminal, the MC closed-check, AND `--list-axes`, so
/// the listed names track the swept names by construction (incl. across #159's
/// source for the sweep terminal, the MC closed-check, AND
/// `aura graph introspect --params`, so the listed names track the swept
/// names by construction (incl. across #159's
/// harness retirement). The reload is infallible under the SAME
/// dispatch-boundary contract the callers already rely on: the doc is
/// `blueprint_from_json`-validated at the `["sweep", ..]` / `["mc", ..]`
@@ -673,7 +674,8 @@ pub fn blueprint_axis_probe(doc: &str, env: &Env) -> Composite {
/// The axis probe with a #246 override set re-opened on the strategy BEFORE
/// wrapping — probe and per-member reloads must re-open identically so points
/// resolve against one space. The empty-set form is byte-equal to the old
/// probe (mc/run closed-checks and the open `--list-axes` lines read that).
/// probe (mc/run closed-checks and the open `aura graph introspect --params`
/// lines read that).
pub fn blueprint_axis_probe_reopened(doc: &str, env: &Env, overrides: &[String]) -> Composite {
let signal = blueprint_from_json(doc, &|t| env.resolve(t))
.expect("doc parse-validated at the dispatch boundary; reload is infallible");
@@ -767,7 +769,7 @@ pub fn override_paths(
None => {
return Err(format!(
"axis {name}: names no param of this blueprint (open or bound) — \
see `aura sweep <bp> --list-axes`"
see `aura graph introspect --params <bp>`"
));
}
}
@@ -36,3 +36,18 @@ execution layer is unchanged (arg-plumbing via thin `*_from` adapters). The
machine-first help surface (JSON/manifest help, stdin op-scripts) is
deferred to the #157 / C21 track, distinct from this human/GNU-convention
compliance."
**Current-state "Single document grammar" section's first-draft routing
sentence (superseded by the #319 sugar retirement, 2026-07-25):** "one verb,
`exec <target>`, dispatches on `is_blueprint_file` (a first-positional
naming an existing `.json` file selects the loaded-blueprint leg; anything
else — a campaign file or a registered content id — the campaign leg)." This
undersold the routing by one layer: the code always also peeked the file's
top-level `"kind"` field (`is_campaign_document_file`) to tell a campaign
document ending in `.json` from a blueprint file — both being ordinary
readable `.json` files `is_blueprint_file` alone cannot distinguish — and,
after an independent review found a malformed campaign file was silently
misrouted to the blueprint leg's own JSON-parse error prose, gained a
neutral not-valid-JSON refusal ahead of the leg choice (review Minor-3,
2026-07-25's fix cycle). The current contract's "Single document grammar"
section names this real routing.
@@ -62,11 +62,16 @@ caller branches on the failure class without parsing stderr.
**Single document grammar (#319, 2026-07-25).** The five dual-grammar
subcommands (run/sweep/walkforward/mc/generalize) that used to keep two
grammars under one token are retired; one verb, `exec <target>`, dispatches
on `is_blueprint_file` (a first-positional naming an existing `.json` file
selects the loaded-blueprint leg; anything else — a campaign file or a
registered content id — the campaign leg). The execution layer this
collapses onto is unchanged in kind (arg-plumbing via thin adapters). The
machine-first help surface (JSON/manifest help, stdin op-scripts) is
on `is_blueprint_file` layered with a "kind"-field peek
(`classify_exec_document_file`): a first-positional naming an existing
`.json` file that parses as JSON and carries no top-level `"kind"` selects
the loaded-blueprint leg; a file carrying `"kind"`, or a target that is not a
readable `.json` file at all (a registered content id), selects the campaign
leg. A file that does not parse as JSON at all refuses neutrally (exit 2)
before either leg's own document-shape validation runs, rather than
silently choosing a leg (review Minor-3, 2026-07-25). The execution layer
this collapses onto is unchanged in kind (arg-plumbing via thin adapters).
The machine-first help surface (JSON/manifest help, stdin op-scripts) is
deferred to the #157 / [C21](c21-world.md) track, distinct from this
human/GNU-convention compliance.
@@ -34,3 +34,16 @@ scaffolding's last data weld — `wrap_r`'s hard-wired `price`←close role and
`M1Field::Close`-only open sites — is retired; a strategy's input roles now bind
archive columns by name (C26). `wrap_r`'s remaining R-scaffolding retirement
stays #159.]
**Current-state "Pre-C24 scaffolding retired" paragraph's family-builder
naming (superseded by the #319 sugar retirement, 2026-07-25):** "The family
builders are now generic and blueprint-driven: `blueprint_sweep_family`,
`blueprint_walkforward_family`, `blueprint_mc_family` (and
`blueprint_sweep_over`) in `crates/aura-runner/src/family.rs`." Those four
functions are themselves retired by #319 (2026-07-25): the campaign executor
(`aura_campaign::exec::execute`) builds every family now, driven by the
process document's stage pipeline; `family.rs` keeps only the synthetic-
stream helpers `aura_runner::reproduce` calls to re-derive a recorded family
(`showcase_prices`, `walkforward_prices`, `walkforward_window_source`,
`synthetic_walk_sources`, `DataSource`/`DataChoice`). The current contract's
"Pre-C24 scaffolding retired" paragraph names this real state.
+14 -7
View File
@@ -71,13 +71,20 @@ rather than a hand-enumerated menu. Persisted experiment *intent* is the campaig
document (C25/C18).
**Pre-C24 scaffolding retired.** The pre-C24 scaffolding — `HarnessKind` and the
per-strategy `*_sweep_family` functions — is **gone**. The family builders are now
generic and blueprint-driven: `blueprint_sweep_family`, `blueprint_walkforward_family`,
`blueprint_mc_family` (and `blueprint_sweep_over`) in
`crates/aura-runner/src/family.rs`. The R-evaluator scaffold `wrap_r`
(defined in `crates/aura-runner/src/member.rs`; imported and called from
`runner.rs`) survives; its last hard data weld — the
hard-wired `price`←close role and the `M1Field::Close`-only open sites — is retired
per-strategy `*_sweep_family` functions — is **gone**. The generic blueprint-driven
family builders that first replaced it (`blueprint_sweep_family`,
`blueprint_walkforward_family`, `blueprint_mc_family`, `blueprint_sweep_over`) are,
in turn, retired by the #319 sugar retirement (2026-07-25): the **campaign
executor** (`aura_campaign::exec::execute`) now builds every family, driven by
the process document's stage pipeline (`std::sweep`/`std::grid`,
`std::walk_forward`, `std::monte_carlo`). `crates/aura-runner/src/family.rs`
retains only the synthetic-stream helpers serving `aura_runner::reproduce`
(`showcase_prices`, `walkforward_prices`, `walkforward_window_source`,
`synthetic_walk_sources`, `DataSource`/`DataChoice`) — reproduction's own
re-derivation path, not a family-minting path. The R-evaluator scaffold
`wrap_r` (defined in `crates/aura-runner/src/member.rs`; imported and called
from `runner.rs`) survives; its last hard data weld — the hard-wired
`price`←close role and the `M1Field::Close`-only open sites — is retired
(a strategy's input roles now bind archive columns by name, C26), and `wrap_r`'s
remaining R-scaffolding retirement is tracked at #159.