diff --git a/crates/aura-bench/src/surfaces/campaign.rs b/crates/aura-bench/src/surfaces/campaign.rs index 7f733f6..eaf53e6 100644 --- a/crates/aura-bench/src/surfaces/campaign.rs +++ b/crates/aura-bench/src/surfaces/campaign.rs @@ -106,9 +106,9 @@ fn seed_blueprint(bin: &Path, dir: &Path, file: &str, name: &str) -> Result (#196): the RAW composite param space — exactly the - // namespace campaign axes are validated against (`validate_campaign_refs` - // checks the raw space; the wrapped `--list-axes` namespace on `aura sweep` - // is the sweep-verb view, not the campaign view). + // --params (#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 `..` 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). match params_lines(target, env) { Ok(s) => print!("{s}"), Err(m) => { diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 0dfb531..37573f6 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -2523,10 +2523,11 @@ fn dispatch_walkforward(a: WalkforwardCmd, env: &aura_runner::project::Env) { exit_on_campaign_result(result); return; } - // Synthetic in-process family path — unchanged (#220 non-goal); it - // persists no per-window taps, so `--trace` stays refused here (#224 - // delivered only the `--real` campaign path above), mirroring - // `dispatch_sweep`'s synthetic-arm refusal with its own named pointer. + // Synthetic in-process family path (#220 non-goal, unchanged beyond + // the #328 tidy below); it persists no per-window taps, so + // `--trace` stays refused here (#224 delivered only the `--real` + // campaign path above), mirroring `dispatch_sweep`'s synthetic-arm + // refusal with its own named pointer. if a.trace.is_some() { eprintln!( "aura: --trace is not yet available on a synthetic walkforward (no --real); see #224" @@ -2545,6 +2546,10 @@ fn dispatch_walkforward(a: WalkforwardCmd, env: &aura_runner::project::Env) { None => Selection::Argmax, }; let name = a.name.clone().unwrap_or_else(|| "walkforward".to_string()); + // #328: this route bypasses `validate_and_register_axes` (no + // project/registry involved, same gap the plain synthetic sweep + // arm had) — the same WRAPPED-name preflight closes it here too. + refuse_wrapped_synthetic_axes(&doc, env, &axes); run_blueprint_walkforward(&doc, &axes, &name, DataSource::Synthetic, select, env); } None => { @@ -3627,9 +3632,8 @@ mod tests { /// two are disjoint by construction. Sweeping `fast.length` (RAW, #328) on /// the fully bound r_sma example leaves `slow.length`/`bias.scale` /// untouched (they stay in `defaults`), while `fast.length` moves to - /// `params` and drops out of `defaults` entirely. `manifest.params` keys - /// are RAW (#328); `manifest.defaults` (a different field, `run`'s own - /// stamp of untouched bound params — untouched this cycle) stays WRAPPED. + /// `params` and drops out of `defaults` entirely. Both `manifest.params` + /// and `manifest.defaults` keys are RAW (#328, batch 2 flips defaults too). #[test] fn sweep_override_excludes_the_reopened_default_from_the_manifest() { let env = aura_runner::project::Env::std(); @@ -3646,13 +3650,13 @@ mod tests { let default_names: Vec<&str> = manifest.defaults.iter().map(|(n, _)| n.as_str()).collect(); assert!( - !default_names.contains(&"sma_signal.fast.length"), + !default_names.contains(&"fast.length"), "the reopened default must not also appear in defaults: {default_names:?}" ); assert_eq!( default_names, - ["sma_signal.slow.length", "sma_signal.bias.scale"], - "the untouched bound params stay in defaults: {default_names:?}" + ["slow.length", "bias.scale"], + "the untouched bound params stay in defaults, RAW: {default_names:?}" ); } diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index 8f8c813..1f1e2d4 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -133,7 +133,8 @@ fn run_prints_json_and_exits_zero() { /// the topology hash into the content-addressed store and re-implementing /// aura's merge semantics. The manifest carries a `defaults` field — the bound /// params merged inside `PrimitiveBuilder::build` below every record-writing -/// layer — as wrap-prefixed `(name, Scalar)` pairs in `bound_param_space()` +/// layer — as RAW `(name, Scalar)` pairs (#328: `bound_param_space()`'s own +/// path-qualified name, no wrap-prefix concatenation) in `bound_param_space()` /// order (the same coordinate `--axis` uses). `params` keeps its "what varied" /// meaning: on a plain run nothing varied, so it stays `[]`, and every bound /// value lands in `defaults` instead of vanishing from the record. @@ -150,12 +151,12 @@ fn run_manifest_stamps_untouched_bound_defaults() { // "what varied" is empty on a plain run — the bound defaults are NOT params. assert!(line.contains("\"params\":[]"), "params stays empty on a plain run: {line}"); - // The untouched bound values are stamped in `defaults`, wrap-prefixed with - // the blueprint name and ordered by `bound_param_space()` (node-declaration - // order: fast, slow, bias), each a self-describing tagged Scalar (C14). + // The untouched bound values are stamped in `defaults`, RAW (#328) and + // ordered by `bound_param_space()` (node-declaration order: fast, slow, + // bias), each a self-describing tagged Scalar (C14). assert!( line.contains( - r#""defaults":[["sma_signal.fast.length",{"I64":2}],["sma_signal.slow.length",{"I64":4}],["sma_signal.bias.scale",{"F64":0.5}]]"# + r#""defaults":[["fast.length",{"I64":2}],["slow.length",{"I64":4}],["bias.scale",{"F64":0.5}]]"# ), "manifest must carry the untouched bound defaults: {line}", ); @@ -1688,15 +1689,15 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() { // Odometer order: `fast.length` (first `--axis`, outer/slowest, typed RAW // per #328) x `slow.length` (second `--axis`, inner) -> - // (2,8),(2,16),(4,8),(4,16). `report.manifest.params` itself is UNTOUCHED - // by #328 (out of this cycle's scope — only the synthetic sweep route's - // manifest moved to the raw frame) and still carries the WRAPPED - // `param_space()` name the real/campaign member actually bound against. + // (2,8),(2,16),(4,8),(4,16). `report.manifest.params` now records RAW + // names on the real/campaign route too (this cycle's tidy fix, #328 batch + // 2): a name-only reshaping at the mint seam, the member still binds + // against the WRAPPED `param_space()` name internally. let expected: [[(&str, i64); 2]; 4] = [ - [("sma_signal.fast.length", 2), ("sma_signal.slow.length", 8)], - [("sma_signal.fast.length", 2), ("sma_signal.slow.length", 16)], - [("sma_signal.fast.length", 4), ("sma_signal.slow.length", 8)], - [("sma_signal.fast.length", 4), ("sma_signal.slow.length", 16)], + [("fast.length", 2), ("slow.length", 8)], + [("fast.length", 2), ("slow.length", 16)], + [("fast.length", 4), ("slow.length", 8)], + [("fast.length", 4), ("slow.length", 16)], ]; for i in 0..4 { @@ -2015,18 +2016,16 @@ fn reproduce_real_sweep_family_re_derives_bit_identically() { /// Property (C18/C1 reproduce guarantee, #246): the same reproduce guarantee holds /// when the swept axis names a BOUND param of a CLOSED starter blueprint, not an -/// already-open one — `r_sma.json` (both `sma_signal.fast.length` and -/// `sma_signal.slow.length` bound), overriding only the `fast.length` axis and -/// leaving `slow.length` bound — unlike the walk-forward reproduce sibling +/// already-open one — `r_sma.json` (both `fast.length` and `slow.length` bound), +/// 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 UNCHANGED by #328 (out of this -/// cycle's scope — batch 1 is the sweep verb's raw switch only), so this axis -/// is still typed WRAPPED here (`sma_signal.fast.length`); `override_paths`'s -/// #328 fuzzy matching tolerates either shape, but the `SweepBinder` call this -/// route drives (`blueprint_sweep_over`, untouched this cycle) still keys by -/// the exact wrapped `param_space()` name, so a RAW-typed axis here would -/// mismatch and misreport `UnknownKnob`. +/// 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 +/// (`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` + @@ -2044,7 +2043,7 @@ fn reproduce_family_with_overridden_bound_param_re_derives() { let cwd = temp_cwd("walkforward-reproduce-override"); let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); let run = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=2,3", "--name", "wfo"]) + .args(["walkforward", &bp, "--axis", "fast.length=2,3", "--name", "wfo"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward"); @@ -2412,6 +2411,44 @@ fn aura_sweep_synthetic_blueprint_refuses_a_wrapped_form_axis_name() { let _ = std::fs::remove_dir_all(&dir); } +/// Property (#328, cycle-close tidy fix — walkforward's own sibling of +/// `aura_sweep_synthetic_blueprint_refuses_a_wrapped_form_axis_name`): the +/// SYNTHETIC (no `--real`) walk-forward route bypassed `validate_and_register_axes` +/// entirely (no project/registry touched) and silently accepted a WRAPPED +/// `..` axis name — the gap this cycle closes by +/// wiring the same `refuse_wrapped_synthetic_axes` preflight the plain +/// synthetic sweep already had, ahead of `run_blueprint_walkforward`. +#[test] +fn aura_walkforward_synthetic_blueprint_refuses_a_wrapped_form_axis_name() { + let dir = temp_cwd("walkforward_synthetic_wrappedname_refusal"); + let fixture = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); + let out = Command::new(BIN) + .args(["walkforward", &fixture, "--axis", "sma_signal.fast.length=2,4"]) + .current_dir(&dir) + .output() + .expect("spawn aura walkforward (synthetic, wrapped-form axis)"); + assert_eq!( + out.status.code(), + Some(2), + "a wrapped-form axis name must be refused, not run: status={:?}", + out.status + ); + let stderr = String::from_utf8_lossy(&out.stderr).into_owned(); + assert!( + stderr.contains("axis \"sma_signal.fast.length\""), + "the refusal echoes exactly the name the user typed: {stderr}" + ); + assert!( + stderr.contains("axis names are raw node.param paths") && stderr.contains("\"fast.length\""), + "translates the wrapped name to its raw candidate, naming the retirement: {stderr}" + ); + // Data-free: no archive/store access at all — this route never touches a + // project store, so there is no `runs` dir to check for absence either + // way, but stdout must carry no member line (the walkforward never ran). + assert!(out.stdout.is_empty(), "a refused axis name must not print a member line: {out:?}"); + let _ = std::fs::remove_dir_all(&dir); +} + /// Property (#247, #328 render-seam echo): every sweep-verb boundary /// `BindError` renders as prose in the same register as its unknown-axis /// sibling (`aura: axis : names no param of this blueprint … — see @@ -4798,15 +4835,15 @@ fn aura_mc_over_a_blueprint_reproduces_bit_identically() { /// E2E (#173): an IS-refit walk-forward over a loaded blueprint persists a /// content-addressed WalkForward family that `aura reproduce`s bit-identically — /// each OOS member's windowed slice + winner params are recovered from its manifest. -/// The synthetic walk-forward route's own `--axis` intake is unchanged by #328 -/// (batch 1 is the sweep verb's raw switch only), so these axes stay WRAPPED. +/// The synthetic walk-forward route's own `--axis` intake is now RAW (this +/// cycle's tidy fix, #328 batch 2). #[test] fn aura_walkforward_over_a_blueprint_reproduces_bit_identically() { let cwd = temp_cwd("blueprint-walkforward-reproduce"); let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); let run = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=2,3", - "--axis", "sma_signal.slow.length=4,6", "--name", "wfr"]) + .args(["walkforward", &bp, "--axis", "fast.length=2,3", + "--axis", "slow.length=4,6", "--name", "wfr"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward"); @@ -4830,8 +4867,8 @@ fn aura_walkforward_over_a_blueprint_persists_a_family() { let cwd = temp_cwd("blueprint-walkforward-persist"); let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=2,3", - "--axis", "sma_signal.slow.length=4,6", "--name", "wfx"]) + .args(["walkforward", &bp, "--axis", "fast.length=2,3", + "--axis", "slow.length=4,6", "--name", "wfx"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward"); @@ -4860,8 +4897,8 @@ fn aura_walkforward_all_zero_trade_windows_emit_one_note() { let cwd = temp_cwd("blueprint-walkforward-zero-trade-note"); let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=8", - "--axis", "sma_signal.slow.length=8", "--name", "wfzero"]) + .args(["walkforward", &bp, "--axis", "fast.length=8", + "--axis", "slow.length=8", "--name", "wfzero"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward (degenerate)"); @@ -4892,8 +4929,8 @@ fn aura_walkforward_with_trades_emits_no_zero_trade_note() { let cwd = temp_cwd("blueprint-walkforward-traded-no-note"); let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=2,3", - "--axis", "sma_signal.slow.length=4,6", "--name", "wftrades"]) + .args(["walkforward", &bp, "--axis", "fast.length=2,3", + "--axis", "slow.length=4,6", "--name", "wftrades"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward (trading)"); @@ -4922,7 +4959,7 @@ fn aura_walkforward_synthetic_member_panic_is_contained() { // during member compile — the same fault class the real path records as // "a member panicked: …" and survives. let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "sma_signal.fast.length=0", "--name", "wfpanic"]) + .args(["walkforward", &bp, "--axis", "fast.length=0", "--name", "wfpanic"]) .current_dir(&cwd) .output() .expect("spawn aura walkforward (poison member)"); @@ -5153,8 +5190,15 @@ fn aura_walkforward_over_a_blueprint_rejects_an_unknown_axis() { fn aura_walkforward_emits_an_unknown_axis_rejection_once() { let bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR")); for attempt in 1..=20 { + // "graph.nope.length" is dotted (multi-segment, like a real axis path) + // but matches neither the raw NOR the wrapped namespace (#328's own + // `refuse_wrapped_synthetic_axes` preflight, now ahead of this route + // too, would otherwise intercept a dotted name that happens to + // wrap-strip onto a real bound param, e.g. the retired + // `graph.fast.length` literal this test used to carry) — so it still + // reaches the genuine "names no param" rejection this test pins. let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) - .args(["walkforward", &bp, "--axis", "graph.fast.length=2,3"]) + .args(["walkforward", &bp, "--axis", "graph.nope.length=2,3"]) .output() .expect("spawn aura walkforward (unknown axis)"); assert_eq!(out.status.code(), Some(2), "an unknown axis must fail clean, not panic"); diff --git a/crates/aura-cli/tests/graph_construct.rs b/crates/aura-cli/tests/graph_construct.rs index 66352d4..7c3575e 100644 --- a/crates/aura-cli/tests/graph_construct.rs +++ b/crates/aura-cli/tests/graph_construct.rs @@ -660,9 +660,11 @@ fn register_refuses_undescribed_composites_end_to_end() { /// Property (#196, the campaign-axis namespace): `--params ` prints the /// RAW composite param space — one `{name}:{kind:?}` line per open param, in -/// lowering order, WITHOUT the harness-wrap prefix `aura sweep --list-axes` -/// shows. The open fixture leaves exactly the two SMA lengths unbound -/// (`bias.scale` is bound in the document). +/// lowering order (#328: the one raw axis namespace — `aura sweep +/// --list-axes` prints the identical lines for the same blueprint, see +/// `graph_params_and_sweep_list_axes_are_line_identical` below). The open +/// fixture leaves exactly the two SMA lengths unbound (`bias.scale` is bound +/// in the document). #[test] fn graph_params_lists_raw_axis_namespace() { let dir = temp_cwd("params"); @@ -675,6 +677,29 @@ fn graph_params_lists_raw_axis_namespace() { ); } +/// Property (#328, cycle-close tidy fix): `graph introspect --params` and +/// `aura sweep --list-axes` are two independently-dispatched discovery +/// surfaces over the SAME raw axis namespace — the open pass shares one +/// wrap-strip convention, the bound pass shares one `bound_param_space()` + +/// `render_value` lexicon (`list_blueprint_axes`, main.rs; `params_lines`, +/// this crate). Pinned here as a same-fixture LINE EQUALITY (not just two +/// independently-asserted shapes) so the duplicated formatting cannot +/// silently drift apart across an edit to either surface. +#[test] +fn graph_params_and_sweep_list_axes_are_line_identical() { + let dir = temp_cwd("params-vs-list-axes"); + let bp = fixture("r_sma_open.json"); + let (params_out, params_err, params_code) = + run_in(&dir, &["graph", "introspect", "--params", &bp]); + assert_eq!(params_code, Some(0), "graph introspect --params: {params_out} {params_err}"); + let (axes_out, axes_err, axes_code) = run_in(&dir, &["sweep", &bp, "--list-axes"]); + assert_eq!(axes_code, Some(0), "sweep --list-axes: {axes_out} {axes_err}"); + assert_eq!( + params_out, axes_out, + "the two discovery surfaces must print byte-identical lines for the same blueprint" + ); +} + /// Property (#196, file-mode --content-id): a blueprint FILE's printed content /// id is exactly the store key `graph register` prints — the file is shape- /// discriminated from the op-list form and canonicalized by the blueprint rules. diff --git a/crates/aura-cli/tests/project_sweep_campaign.rs b/crates/aura-cli/tests/project_sweep_campaign.rs index 11a9b90..302424b 100644 --- a/crates/aura-cli/tests/project_sweep_campaign.rs +++ b/crates/aura-cli/tests/project_sweep_campaign.rs @@ -212,11 +212,11 @@ fn project_node_open_param_sweeps_and_reproduces() { let lines: Vec<&str> = stdout.lines().collect(); assert_eq!(lines.len(), 2, "one member line per factor point: {stdout}"); - // Each member's manifest carries the swept PROJECT-node param binding under - // its wrapped name (the real/campaign route's own `manifest.params` is - // unchanged by #328 — batch 1 is the synthetic sweep route's manifest - // only) — the load-bearing proof that it is MY node's knob that varied - // across the family, not some incidental std axis. + // Each member's manifest carries the swept PROJECT-node param binding + // under its RAW name (this cycle's tidy fix, #328 batch 2: the + // real/campaign route's own `manifest.params` mints raw too now) — the + // load-bearing proof that it is MY node's knob that varied across the + // family, not some incidental std axis. for (line, factor) in lines.iter().zip([0.5_f64, 1.0]) { let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON"); assert_eq!( @@ -229,7 +229,7 @@ fn project_node_open_param_sweeps_and_reproduces() { .expect("manifest.params is an array"); let bound = params .iter() - .find(|p| p[0].as_str() == Some("scaled_signal.gain.factor")) + .find(|p| p[0].as_str() == Some("gain.factor")) .and_then(|p| p[1]["F64"].as_f64()); assert_eq!( bound, diff --git a/crates/aura-runner/src/axes.rs b/crates/aura-runner/src/axes.rs index 35df8f0..10c45f4 100644 --- a/crates/aura-runner/src/axes.rs +++ b/crates/aura-runner/src/axes.rs @@ -96,6 +96,24 @@ pub fn classify_axis_intake( } } +/// Translate one RAW axis name onto the ONE wrapped `space` slot it +/// suffix-matches ([`raw_matches_wrapped`]), falling back to `raw` unchanged +/// when no wrapped slot matches — the caller's own downstream name +/// resolution (e.g. the sweep terminal's `UnknownKnob`) surfaces that case, +/// never panicked here. #328: the shared translation `blueprint_sweep_family` +/// established for its `SweepBinder::axis` call, extracted so +/// `blueprint_sweep_over`/`validate_axis_grid` (the walkforward synthetic +/// route's own binder calls, which key by the same exact wrapped +/// `param_space()` name) can translate a RAW `--axis` name too instead of +/// requiring the retired wrapped form. +pub fn wrapped_name_of(raw: &str, space: &[ParamSpec]) -> String { + space + .iter() + .find(|p| raw_matches_wrapped(raw, &p.name)) + .map(|p| p.name.clone()) + .unwrap_or_else(|| raw.to_string()) +} + /// Suffix-join each raw campaign axis onto exactly one wrapped param /// ([`raw_matches_wrapped`] — wrapped == raw, or stripping the wrapper's one /// node segment yields raw), then require every wrapped slot to be covered. diff --git a/crates/aura-runner/src/family.rs b/crates/aura-runner/src/family.rs index 35d3c55..2080f3b 100644 --- a/crates/aura-runner/src/family.rs +++ b/crates/aura-runner/src/family.rs @@ -408,11 +408,17 @@ fn validate_axis_grid( ) -> Result<(), BindError> { let overrides = override_paths(axes, raw_space, probe_signal).map_err(BindError::UnknownKnob)?; let probe = blueprint_axis_probe_reopened(doc, env, &overrides); + let space = probe.param_space(); + // #328: `axes` names are RAW (refused at the walkforward dispatch-boundary + // preflight ahead of this call, mirroring the plain sweep verb) — translate + // each onto the ONE wrapped `space` slot it suffix-matches + // (`wrapped_name_of`, the same convention `blueprint_sweep_family` feeds its + // own `SweepBinder`) before this terminal's exact-name resolution. let mut iter = axes.iter(); let (first_name, first_vals) = iter.next().expect("a blueprint walk-forward declares >= 1 axis"); - let mut binder = probe.axis(first_name, first_vals.clone()); + let mut binder = probe.axis(&crate::axes::wrapped_name_of(first_name, &space), first_vals.clone()); for (n, vals) in iter { - binder = binder.axis(n, vals.clone()); + binder = binder.axis(&crate::axes::wrapped_name_of(n, &space), vals.clone()); } binder.sweep_with_lattice(|_| axis_grid_probe_report()).map(|_| ()) } @@ -582,11 +588,16 @@ pub fn blueprint_sweep_over( let overrides = override_paths(axes, &raw_space, &probe_signal).map_err(BindError::UnknownKnob)?; let probe = blueprint_axis_probe_reopened(doc, env, &overrides); let space = probe.param_space(); + // #328: `axes` names are RAW (refused at the walkforward dispatch-boundary + // preflight ahead of the caller, mirroring the plain sweep verb) — + // translate each onto the ONE wrapped `space` slot it suffix-matches + // (`wrapped_name_of`, the same convention `blueprint_sweep_family` feeds + // its own `SweepBinder`) before this terminal's exact-name resolution. let mut iter = axes.iter(); let (first_name, first_vals) = iter.next().expect("a blueprint walk-forward declares >= 1 axis"); - let mut binder = probe.axis(first_name, first_vals.clone()); + let mut binder = probe.axis(&crate::axes::wrapped_name_of(first_name, &space), first_vals.clone()); for (n, vals) in iter { - binder = binder.axis(n, vals.clone()); + binder = binder.axis(&crate::axes::wrapped_name_of(n, &space), vals.clone()); } let faults: Mutex, String)>> = Mutex::new(Vec::new()); let (family, lattice) = binder.sweep_with_lattice(|point| { diff --git a/crates/aura-runner/src/measure.rs b/crates/aura-runner/src/measure.rs index d196f8d..8ed476b 100644 --- a/crates/aura-runner/src/measure.rs +++ b/crates/aura-runner/src/measure.rs @@ -12,7 +12,7 @@ use std::collections::BTreeMap; use aura_core::{Scalar, Timestamp}; use aura_engine::{Composite, Harness, MeasurementReport, RunManifest}; -use crate::member::{key_supply, resolve_run_data, wrapped_bound_defaults, RunData}; +use crate::member::{key_supply, raw_bound_defaults, resolve_run_data, RunData}; use crate::project::Env; use crate::tap_plan::{bind_tap_plan, TapPlan}; @@ -79,7 +79,7 @@ pub fn run_measurement( std::process::exit(1); } let names: Vec = signal.param_space().iter().map(|p| p.name.clone()).collect(); - let defaults = wrapped_bound_defaults(&signal); + let defaults = raw_bound_defaults(&signal); let (sources, window, _pip_size) = resolve_run_data(&data, env, &binding); // Compile the signal DIRECTLY — no wrap_r, no broker/executor/eq-ex-r sinks. diff --git a/crates/aura-runner/src/member.rs b/crates/aura-runner/src/member.rs index 34cf124..fa30a2b 100644 --- a/crates/aura-runner/src/member.rs +++ b/crates/aura-runner/src/member.rs @@ -82,22 +82,20 @@ pub fn sim_optimal_manifest( } } -/// The wrap-prefixed BOUND-param defaults of `signal` (#249): every -/// `bound_param_space()` entry as a `(., value)` pair, in -/// `bound_param_space()` order — the `RunManifest.defaults` field. Must be read -/// off `signal` BEFORE it is consumed by `wrap_r`/reopening: an axis-reopened -/// bound param has already left `bound_param_space()` by construction -/// (`Composite::reopen` forgets the bound value), so a caller that reopens -/// overrides before calling this naturally excludes them — no separate filter -/// needed. Same prefixing rule as `wrapped_bound_names`, kept separate because -/// that helper discards the value this one needs. -pub fn wrapped_bound_defaults(signal: &Composite) -> Vec<(String, Scalar)> { - let prefix = format!("{}.", signal.name()); - signal - .bound_param_space() - .into_iter() - .map(|b| (format!("{prefix}{}", b.name), b.value)) - .collect() +/// The RAW-namespace BOUND-param defaults of `signal` (#249/#328): every +/// `bound_param_space()` entry as a `(, value)` pair, already RAW +/// (`bound_param_space()`'s own path-qualified name, #203 — no wrap-prefix +/// concatenation), in `bound_param_space()` order — the `RunManifest.defaults` +/// field. Must be read off `signal` BEFORE it is consumed by +/// `wrap_r`/reopening: an axis-reopened bound param has already left +/// `bound_param_space()` by construction (`Composite::reopen` forgets the +/// bound value), so a caller that reopens overrides before calling this +/// naturally excludes them — no separate filter needed. Contrast +/// `wrapped_bound_names`, which DOES wrap-prefix (it matches against the +/// WRAPPED open `param_space()`, a different coordinate system than this +/// manifest-recording helper needs). +pub fn raw_bound_defaults(signal: &Composite) -> Vec<(String, Scalar)> { + signal.bound_param_space().into_iter().map(|b| (b.name, b.value)).collect() } /// The honest broker label for the dual-tap r-sma harness: it runs a RiskExecutor @@ -506,7 +504,7 @@ pub fn run_signal_r( .iter() .map(|p| p.name.clone()) .collect(); - let defaults = wrapped_bound_defaults(&signal); // read before `signal` is consumed below + let defaults = raw_bound_defaults(&signal); // read before `signal` is consumed below let (tx_eq, rx_eq) = mpsc::channel(); let (tx_ex, rx_ex) = mpsc::channel(); let (tx_r, rx_r) = mpsc::channel(); @@ -580,7 +578,7 @@ pub fn run_blueprint_member( cost: &[aura_research::CostSpec], instrument: &str, ) -> RunReport { - let defaults = wrapped_bound_defaults(&signal); // read before `signal` is consumed below + let defaults = raw_bound_defaults(&signal); // read before `signal` is consumed below let (tx_eq, rx_eq) = mpsc::channel(); let (tx_ex, rx_ex) = mpsc::channel(); let (tx_r, rx_r) = mpsc::channel(); diff --git a/crates/aura-runner/src/reproduce.rs b/crates/aura-runner/src/reproduce.rs index 929b1f1..147f376 100644 --- a/crates/aura-runner/src/reproduce.rs +++ b/crates/aura-runner/src/reproduce.rs @@ -143,15 +143,17 @@ pub fn reproduce_family_in( }); } let space = crate::member::wrap_r(reload()?, tx_eq, tx_ex, tx_r, tx_req, stop, true, SYNTHETIC_PIP_SIZE, &binding, None).param_space(); - // #328: `stored.manifest.params` may be RAW (a `Sweep` family minted by - // `blueprint_sweep_family`, whose manifest now records the raw campaign - // namespace) or WRAPPED (every other route, unchanged this cycle) — - // `reproduce_family_in` reproduces either generically, so each recorded - // name is translated onto its matching WRAPPED `space` slot - // (`raw_matches_wrapped`, tolerant of both shapes) before - // `point_from_params`, which still keys by the exact wrapped name. - // Non-axis stamps (`stop_length`, `cost[k].`) carry no wrap - // segment either way and translate to themselves (no `space` hit). + // #328: `stored.manifest.params` is RAW on every mint route now (the + // synthetic sweep family and, since this cycle's tidy fix, the + // real/campaign route too) — but a family minted before this fix may + // still carry WRAPPED names on disk (C29: no retroactive rewrite of a + // registered artifact), so `reproduce_family_in` reproduces either + // shape generically: each recorded name is translated onto its + // matching WRAPPED `space` slot (`raw_matches_wrapped`, tolerant of + // both shapes) before `point_from_params`, which still keys by the + // exact wrapped name. Non-axis stamps (`stop_length`, `cost[k].`) + // carry no wrap segment either way and translate to themselves (no + // `space` hit). let wrapped_params: Vec<(String, Scalar)> = stored .manifest .params diff --git a/crates/aura-runner/src/runner.rs b/crates/aura-runner/src/runner.rs index 5e3044c..7216a9e 100644 --- a/crates/aura-runner/src/runner.rs +++ b/crates/aura-runner/src/runner.rs @@ -116,6 +116,21 @@ impl MemberRunner for DefaultMemberRunner<'_> { .param_space(); let point = bind_axes(&space, &cell.strategy_id, params)?; let signal = reopen_all(signal, &overrides); + // manifest.params records RAW names (#328: the real/campaign route was + // the one mint left wrapped after the synthetic route's own switch) — + // a name-only reshaping of `space` (order/kind untouched) fed to + // `run_blueprint_member`, which only zips it against `point` BY + // POSITION (`zip_params`) — never re-resolves by name — so renaming + // here is purely the manifest's own namespace, with no effect on + // member resolution (`space` itself stays wrapped for `bind_axes` + // above, which still keys by the exact wrapped `param_space()` name). + let manifest_space: Vec = space + .iter() + .map(|p| aura_core::ParamSpec { + name: crate::axes::wrapped_to_raw_axis(&p.name).to_string(), + kind: p.kind, + }) + .collect(); // The member's resolved input binding (campaign data.bindings // overrides win over name defaults). A refusal is a member fault, @@ -173,7 +188,7 @@ impl MemberRunner for DefaultMemberRunner<'_> { let mut report = run_blueprint_member( signal, &point, - &space, + &manifest_space, sources, (from, to), 0, @@ -490,7 +505,28 @@ pub fn persist_campaign_traces( // is the member report's own `manifest.window` — already // epoch-ns (`run_blueprint_member` stamped the post-seam // bounds), so no second ms->ns crossing here. - let point = point_from_params(&space, &member_report.manifest.params)?; + // + // #328: `member_report.manifest.params` is RAW (this cycle's tidy + // fix put the real/campaign mint on the raw frame too), but + // `point_from_params` still keys by the exact WRAPPED `space` + // name — translate each recorded name onto its matching wrapped + // slot first (`raw_matches_wrapped`, tolerant of either shape, the + // same `reproduce_family_in` recipe), never re-resolving by a + // renamed identity. + let wrapped_params: Vec<(String, Scalar)> = member_report + .manifest + .params + .iter() + .map(|(n, v)| { + let wrapped = space + .iter() + .find(|p| crate::axes::raw_matches_wrapped(n, &p.name)) + .map(|p| p.name.clone()) + .unwrap_or_else(|| n.clone()); + (wrapped, *v) + }) + .collect(); + let point = point_from_params(&space, &wrapped_params)?; let (from, to) = member_report.manifest.window; let no_data = || { format!(