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:?}");
|
eprintln!("aura: {path}: {e:?}");
|
||||||
std::process::exit(2);
|
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..])
|
let BlueprintRunArgs { params, data, seed } = parse_blueprint_run_args(&rest[1..])
|
||||||
.unwrap_or_else(|msg| {
|
.unwrap_or_else(|msg| {
|
||||||
eprintln!("aura: {msg}");
|
eprintln!("aura: {msg}");
|
||||||
|
|||||||
@@ -3655,6 +3655,37 @@ fn aura_mc_rejects_an_open_blueprint() {
|
|||||||
let _ = std::fs::remove_dir_all(&cwd);
|
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
|
/// 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.
|
/// cause on stderr), distinct from `runs family <id>`'s treat-as-empty exit 0.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user