fix(cli): render sweep-boundary bind errors as prose, not Debug structs

The blueprint-sweep terminal rendered BindError via {:?}, leaking
KindMismatch { .. } and MissingKnob(..) Rust Debug structs where the
sibling diagnostic on the same verb ships prose. Both now render in
that register — the axis name, the expected-vs-supplied kinds resp.
the unbound knob, and the --list-axes pointer; exit stays 2.

Scoped out (same defect family, different verb path): the walkforward
path's UnknownKnob wrapper still prints its Debug-ish frame around an
already-prose payload and is pinned verbatim by two existing tests;
filed as a follow-up rather than widened into this slice.

closes #247
This commit is contained in:
2026-07-13 22:52:03 +02:00
parent ce12e7f976
commit 414448bd3f
+29 -4
View File
@@ -1915,6 +1915,31 @@ fn override_paths(
Ok(overrides) Ok(overrides)
} }
/// Renders a [`BindError`] as one-line prose in `override_paths`' sibling
/// register above (#247) — never the raw Rust `Debug` struct name
/// (`KindMismatch { .. }` / `MissingKnob("..")`), the two variants the sweep
/// terminal actually raises past `override_paths`' own pre-flight (which
/// already rejects an unresolvable axis name as prose before either call
/// site below ever reaches the terminal). Other variants keep the
/// established `{:?}` rendering unchanged — in particular `UnknownKnob`
/// already carries a fully-prosed message string at both call sites here
/// (wrapped from `override_paths`' own `Result<_, String>`), and pinned
/// tests (`aura_walkforward_emits_an_unknown_axis_rejection_once` et al.)
/// depend on that Debug-wrapped shape.
fn render_bind_error(e: &BindError) -> String {
match e {
BindError::MissingKnob(name) => format!(
"axis {name}: an open param with no axis and no bound default — \
bind it with `--axis {name}=<value>` see `aura sweep <bp> --list-axes`"
),
BindError::KindMismatch { knob, expected, got } => format!(
"axis {knob}: expected {expected:?}, supplied {got:?} — \
see `aura sweep <bp> --list-axes`"
),
other => format!("{other:?}"),
}
}
/// Apply a validated override set to a freshly loaded strategy (#246). /// Apply a validated override set to a freshly loaded strategy (#246).
/// Infallible by contract: the set was derived against this same document at /// Infallible by contract: the set was derived against this same document at
/// the family boundary. /// the family boundary.
@@ -2015,9 +2040,9 @@ fn blueprint_sweep_family(
// the shared reduce-mode member path — the same fn reproduction re-runs. // the shared reduce-mode member path — the same fn reproduction re-runs.
run_blueprint_member(reload(doc), point, &space, data.run_sources(env, &binding.columns()), window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, &binding, &[]) run_blueprint_member(reload(doc), point, &space, data.run_sources(env, &binding.columns()), window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, &binding, &[])
}) })
// render the sweep terminal's BindError to a message (the fn's String error contract), // render the sweep terminal's BindError to prose (#247), the fn's String error
// so `UnknownKnob("nope")` still surfaces verbatim at the IO wrapper. // contract — never the raw Debug struct.
.map_err(|e| format!("{e:?}")) .map_err(|e| render_bind_error(&e))
} }
/// Sweep the LOADED blueprint over the user `--axis` grid on an in-sample window /// Sweep the LOADED blueprint over the user `--axis` grid on an in-sample window
@@ -2188,7 +2213,7 @@ fn blueprint_walkforward_family(
// (#253): `validate_axis_grid` resolves the SAME grid the sweep terminal would, without // (#253): `validate_axis_grid` resolves the SAME grid the sweep terminal would, without
// running a single member — no IS window (or a second roller) is needed here at all. // running a single member — no IS window (or a second roller) is needed here at all.
if let Err(e) = validate_axis_grid(doc, axes, &raw_space, &probe_signal, env) { if let Err(e) = validate_axis_grid(doc, axes, &raw_space, &probe_signal, env) {
eprintln!("aura: {e:?}"); eprintln!("aura: {}", render_bind_error(&e));
std::process::exit(2); std::process::exit(2);
} }
walk_forward(roller, space.clone(), |w: WindowBounds| { walk_forward(roller, space.clone(), |w: WindowBounds| {