fix(cli): aura run refuses an open blueprint with a clean exit 2, not a panic
`aura run <blueprint.json>` bootstraps the loaded signal over the EMPTY param point (a closed-blueprint assumption) and `.expect()`s `compile_with_params`, which panics (exit 101, ParamArity) when the blueprint carries >=1 free knob. The sibling `aura mc` already refuses an open blueprint cleanly at the dispatch boundary; `run` now does the same. The `["run", ..]` `.json`-discriminator arm probes the wrapped param_space (`blueprint_axis_probe`, the single source of that wrap) and, when it is non-empty, emits a "run requires a closed blueprint … N free knob(s) — bind them or use `aura sweep --axis`" diagnostic and exits 2 — mirroring `blueprint_mc_family`'s closed-guard. No panic on a naive consumer's first move (author an open blueprint, run it). RED-first: cli_run.rs `aura_run_rejects_an_open_blueprint_without_panicking` pins exit 2 + the named requirement + no `panicked`. Surfaced by the World/C21 milestone fieldtest. closes #176 refs #170
This commit is contained in:
@@ -4256,6 +4256,18 @@ fn main() {
|
||||
eprintln!("aura: {path}: {e:?}");
|
||||
std::process::exit(2);
|
||||
});
|
||||
// Refuse an open (free-knob) blueprint at the dispatch boundary, mirroring
|
||||
// `blueprint_mc_family`'s closed-guard: `run` bootstraps over the EMPTY point, so a
|
||||
// free knob would panic in `compile_with_params` — reject it clean instead (#176).
|
||||
let free = blueprint_axis_probe(&doc).param_space();
|
||||
if !free.is_empty() {
|
||||
eprintln!(
|
||||
"aura: run requires a closed blueprint (no free parameters); {} free knob(s) — \
|
||||
bind them or use `aura sweep --axis`",
|
||||
free.len()
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
let BlueprintRunArgs { params, data, seed } = parse_blueprint_run_args(&rest[1..])
|
||||
.unwrap_or_else(|msg| {
|
||||
eprintln!("aura: {msg}");
|
||||
|
||||
@@ -3655,6 +3655,37 @@ fn aura_mc_rejects_an_open_blueprint() {
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// E2E (#176): `aura run <open blueprint.json>` refuses an open blueprint the same way the
|
||||
/// sibling `aura mc` does — a clean named error + exit 2, NEVER a `compile_with_params`
|
||||
/// arity panic (exit 101). The `run` dispatch bootstraps over the EMPTY point (a
|
||||
/// closed-blueprint assumption), so a free knob must be rejected at the dispatch boundary
|
||||
/// before that bootstrap, mirroring `blueprint_mc_family`'s closed-guard.
|
||||
#[test]
|
||||
fn aura_run_rejects_an_open_blueprint_without_panicking() {
|
||||
let cwd = temp_cwd("run-blueprint-open-reject");
|
||||
let fixture = format!("{}/tests/fixtures/stage1_signal_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = Command::new(BIN)
|
||||
.args(["run", &fixture])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura run open-blueprint");
|
||||
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
|
||||
assert_eq!(
|
||||
out.status.code(),
|
||||
Some(2),
|
||||
"an open blueprint fails clean (exit 2, not a panic/exit 101): stderr={stderr}"
|
||||
);
|
||||
assert!(
|
||||
stderr.contains("closed blueprint"),
|
||||
"names the closed-blueprint requirement (mirroring `aura mc`): {stderr}"
|
||||
);
|
||||
assert!(
|
||||
!stderr.contains("panicked"),
|
||||
"must refuse at the dispatch boundary, never panic: {stderr}"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// E2E (negative): `aura reproduce <unknown>` is a hard error (exit non-zero + named
|
||||
/// cause on stderr), distinct from `runs family <id>`'s treat-as-empty exit 0.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user