diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 88c82e4..39a5bff 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -3154,6 +3154,36 @@ fn run_blueprint_member( RunReport { manifest, metrics: m } } +/// The exact wrapped probe the loaded-blueprint sweep resolves its axes +/// against: the loaded signal wrapped in the stage1-r 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 +/// 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", ..]` +/// boundary before this runs, so a malformed doc has already exited 2 and +/// never reaches the `.expect`. +fn blueprint_axis_probe(doc: &str) -> Composite { + let signal = blueprint_from_json(doc, &|t| std_vocabulary(t)) + .expect("doc parse-validated at the dispatch boundary; reload is infallible"); + let (tx_eq, _) = mpsc::channel(); + let (tx_ex, _) = mpsc::channel(); + let (tx_r, _) = mpsc::channel(); + let (tx_req, _) = mpsc::channel(); + wrap_stage1r(signal, tx_eq, tx_ex, tx_r, tx_req, false, true, None) +} + +/// `aura sweep --list-axes`: one `:` line per open +/// sweepable knob, in `param_space()` order; a closed blueprint prints nothing. +/// Names are exactly what `--axis` binds (same probe as the sweep terminal). +fn list_blueprint_axes(doc: &str) { + for p in blueprint_axis_probe(doc).param_space() { + println!("{}:{:?}", p.name, p.kind); // ScalarKind Debug -> I64/F64/Bool/Timestamp + } +} + /// Sweep a serialized signal `doc` over user-named param-space axes — the structural /// twin of [`stage1_r_sweep_family`], with three deviations. (1) The signal source is /// `wrap_stage1r(blueprint_from_json(doc))` — a loaded blueprint, not the Rust-built @@ -3180,14 +3210,9 @@ fn blueprint_sweep_family( let pip = data.pip_size(); let window = data.full_window(); // a single throwaway floated build, only to resolve param_space (borrow) then seed - // the named axes (move) — its taps are never drained. Mirrors stage1_r_sweep_family: - // param_space takes &self, .axis() consumes self, so one build suffices. The signal - // is wrapped exactly as the single run wraps it (stop bound, reduce, no cost). - let (tx_eq, _) = mpsc::channel(); - let (tx_ex, _) = mpsc::channel(); - let (tx_r, _) = mpsc::channel(); - let (tx_req, _) = mpsc::channel(); - let probe = wrap_stage1r(reload(doc), tx_eq, tx_ex, tx_r, tx_req, false, true, None); + // the named axes (move) — its taps are never drained. The wrapped probe is the one + // `blueprint_axis_probe` single-sources (identical `false, true, None` wrap). + let probe = blueprint_axis_probe(doc); let space = probe.param_space(); // seed the named axes verbatim: the first via Composite::axis (consumes the probe), // the rest via SweepBinder::axis. resolve_axes name- and kind-checks them at the @@ -3231,13 +3256,9 @@ fn blueprint_mc_family(doc: &str, n_seeds: u64, data: &DataSource) -> Result