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
+35
View File
@@ -344,6 +344,41 @@ pub(crate) fn unresolved_namespace_hint(e: &LoadError, env: &crate::project::Env
}
}
/// Validate a blueprint-slot document — the `sweep`/`walkforward`/`mc`
/// loaded-blueprint branches' dispatch-boundary check (#210 c0110 fieldtest
/// finding: the sweep slot used to print the raw `{e:?}` Debug leak). Single-
/// sourced here so the three dual-grammar subcommands cannot diverge (#184's
/// convention, extended). Two refusals, checked in order:
///
/// 1. The document parses as JSON but is an op-script ARRAY (#157), not a
/// built blueprint envelope — refused with a targeted hint pointing at
/// `aura graph build`, since `blueprint_from_json` would otherwise surface
/// a confusing internal serde shape mismatch.
/// 2. `blueprint_from_json` fails to load the envelope — phrased house-style
/// via [`blueprint_load_prose`] (+ [`unresolved_namespace_hint`] where it
/// applies), never the raw `LoadError` Debug form.
///
/// `Ok(())` means the document loaded cleanly; callers that only need the
/// validation (not the `Composite`) re-parse `doc` themselves afterward
/// (`blueprint_from_json` is cheap and infallible at that point).
pub(crate) fn blueprint_slot_prose(doc: &str, env: &crate::project::Env) -> Result<(), String> {
if matches!(serde_json::from_str::<serde_json::Value>(doc), Ok(serde_json::Value::Array(_))) {
return Err(
"this is an op-script (an op array), not a built blueprint — run \
'aura graph build' first"
.to_string(),
);
}
blueprint_from_json(doc, &|t| env.resolve(t)).map(|_| ()).map_err(|e| {
let mut msg = blueprint_load_prose(&e);
if let Some(hint) = unresolved_namespace_hint(&e, env) {
msg.push_str("");
msg.push_str(&hint);
}
msg
})
}
/// #196: build a `Composite` from a document that is EITHER a #155 blueprint
/// envelope (a JSON object: `format_version` + `blueprint`) OR a construction
/// op-list (a JSON array) — shape-discriminated on the top-level JSON type,