fix(cli): sweep-entry ergonomics — honest refusals, no store litter (#210 c0110 fieldtest)

Three test-first fixes from the cycle-0110 fieldtest findings:

- Axis-name preflight on the dissolved sweep arm: every --axis name is
  checked against the wrapped probe namespace (the --list-axes names)
  BEFORE the #203 strip; an unknown or raw-form name is refused
  house-style echoing exactly what the user typed, with a --list-axes
  pointer — no more stripped-then-mangled fragments from the deep
  referential refusal. Data-free, fires before the archive is touched.

- Validate-before-register in the sweep sugar: intrinsic doc checks +
  executor preflight + the pure bind_axes coverage check (reused, not
  re-inlined; now pub(crate)) all run in-memory before
  register_generated — a refused invocation leaves no generated
  document in the content-addressed store and no campaign_runs.jsonl
  line (previously P2/P3 refusals littered runs/campaigns, one doc
  malformed).

- blueprint_slot_prose: the sweep/walkforward/mc loaded-blueprint slots
  share one dispatch-boundary presenter — an op-script array gets a
  targeted 'run aura graph build first' hint, every load error goes
  through blueprint_load_prose + the missing-project hint, and the raw
  {e:?} Debug leak (#184's pattern, deliberately left on these three
  slots back then) is gone. Two stale Debug-leak pins flipped, two new
  refusal tests.

Suite 1060/0, clippy -D warnings clean.

refs #210
This commit is contained in:
2026-07-04 19:48:57 +02:00
parent f96796cd08
commit c9d962f0ea
5 changed files with 257 additions and 15 deletions
+29 -7
View File
@@ -4427,9 +4427,10 @@ fn dispatch_sweep(a: SweepCmd, env: &project::Env) {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
});
// Parse-validate the blueprint once at the boundary (with file-path context).
if let Err(e) = blueprint_from_json(&doc, &|t| env.resolve(t)) {
eprintln!("aura: {path}: {e:?}");
// Parse-validate the blueprint once at the boundary (with file-path context),
// house-style prose (#184's convention, single-sourced — #210 c0110 finding).
if let Err(msg) = graph_construct::blueprint_slot_prose(&doc, env) {
eprintln!("aura: {path}: {msg}");
std::process::exit(2);
}
// A built-in-only flag with a blueprint file is not in this grammar.
@@ -4490,6 +4491,27 @@ fn dispatch_sweep(a: SweepCmd, env: &project::Env) {
// strategy ref resolves against this one write; a second
// put under `content_id_of(&canonical)` would target the
// identical key and is not needed.
// FIX (#210 c0110 finding "wrapped-name refusal mangles the
// axis"): validate every `--axis` name against the WRAPPED
// probe namespace (`blueprint_axis_probe`/`--list-axes` —
// the same names #203's strip later consumes) BEFORE
// stripping/translating to the raw campaign namespace. A
// raw-form or fat-fingered name is refused here, echoing
// exactly what the user typed, instead of surfacing a
// stripped-then-mangled fragment deep inside a member run.
// Data-free (the probe only reloads the blueprint), so
// this fires before the archive is touched.
let space = blueprint_axis_probe(&doc, env).param_space();
for (n, _) in &axes {
if !space.iter().any(|p| &p.name == n) {
eprintln!(
"aura: axis \"{n}\" is not one of this blueprint's \
sweepable axes run 'aura sweep <bp> --list-axes' \
to see them"
);
std::process::exit(2);
}
}
let symbol = symbol.clone();
let source = DataSource::from_choice(data, env);
let (from_ts, to_ts) = source.full_window(env);
@@ -4593,8 +4615,8 @@ fn dispatch_walkforward(a: WalkforwardCmd, env: &project::Env) {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
});
if let Err(e) = blueprint_from_json(&doc, &|t| env.resolve(t)) {
eprintln!("aura: {path}: {e:?}");
if let Err(msg) = graph_construct::blueprint_slot_prose(&doc, env) {
eprintln!("aura: {path}: {msg}");
std::process::exit(2);
}
if a.strategy.is_some()
@@ -4672,8 +4694,8 @@ fn dispatch_mc(a: McCmd, env: &project::Env) {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
});
if let Err(e) = blueprint_from_json(&doc, &|t| env.resolve(t)) {
eprintln!("aura: {path}: {e:?}");
if let Err(msg) = graph_construct::blueprint_slot_prose(&doc, env) {
eprintln!("aura: {path}: {msg}");
std::process::exit(2);
}
// A built-in-only flag with a blueprint file is not in this grammar