feat(aura-cli): exec --override (the #246 residue) + the measurement leg

Slice 2 of the #319 retirement. The blueprint leg now carries both run
shapes: a bias signal via run_signal_r, a no-bias measurement blueprint
(>=1 declared tap) via run_measurement — record lines byte-identical to
run's. --override NODE.PARAM=VALUE (repeatable) is the one deliberate
sugar residue: validated through the shared override_paths prose, the
closed-guard runs on the reopened probe (space == override set), values
bind in reopened param_space order so the manifest records them raw under
params ("what varied"). A measurement blueprint refuses --override
explicitly (its space is not wrap_r-wrapped) rather than dropping it
silently.

refs #319
This commit is contained in:
2026-07-25 18:41:14 +02:00
parent 78b80ec0fd
commit da19e27b6a
2 changed files with 197 additions and 19 deletions
+97
View File
@@ -395,3 +395,100 @@ fn exec_blueprint_tap_selector_persists_a_fold_summary_row() {
let got = rows[0].as_f64().expect("f64 row");
assert!((got - expected).abs() < 1e-9, "mean row: got {got}, expected {expected}");
}
// ---------------------------------------------------------------------------
// Task 2b: the measurement leg of exec's blueprint target.
// ---------------------------------------------------------------------------
/// A no-bias (measurement) blueprint runs through `exec` exactly as through
/// `run`: same record shape (`manifest` + `taps`, no R `metrics` — a
/// measurement run has no broker) — mirrors
/// `run_measurement.rs::measurement_blueprint_runs_bare_and_emits_its_tap`
/// (a `MeasurementReport` carries no `metrics` field at all, unlike a
/// strategy `RunReport` — `aura_engine::report::MeasurementReport`'s doc
/// comment states this explicitly).
#[test]
fn exec_measurement_blueprint_emits_the_record_line() {
let cwd = temp_cwd("exec-measurement-blueprint");
let bp_path = cwd.join("m.json");
std::fs::write(&bp_path, measurement_blueprint_json()).expect("write measurement blueprint");
let (out, code) = run_code_in(&cwd, &["exec", bp_path.to_str().expect("utf-8 path")]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with('{')).expect("record line");
let v: serde_json::Value = serde_json::from_str(line).expect("record parses");
assert!(v.get("manifest").is_some(), "record: {out}");
assert_eq!(v["taps"], serde_json::json!(["fast_tap"]));
assert!(v.get("metrics").is_none(), "a measurement run has no R metrics: {out}");
assert_eq!(v["manifest"]["broker"], "measurement");
}
// ---------------------------------------------------------------------------
// Task 3: `--override` on the blueprint leg.
// ---------------------------------------------------------------------------
/// The one deliberate sugar residue: a bound-param override, recorded raw in
/// `manifest.params` ("what varied") and dropped from `manifest.defaults`
/// ("what was held") — the #246 reopen mechanism, now reachable through
/// `exec`. `examples/r_sma.json` binds `fast.length` to 2 (its "fast" `SMA`
/// node) — the same bound param
/// `cli_run.rs::sweep_dissolved_accepts_an_axis_over_a_bound_param` reopens
/// via `--axis` on the sweep-dissolved campaign path.
#[test]
fn exec_blueprint_override_reopens_and_records_the_value_in_params() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "fast.length=8"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with('{')).expect("record line");
let v: serde_json::Value = serde_json::from_str(line).expect("record parses");
let params = v["manifest"]["params"].as_array().expect("params array");
assert!(
params.iter().any(|e| e[0] == "fast.length" && e[1] == serde_json::json!({"I64": 8})),
"the overridden value must land in params: {out}"
);
let defaults = v["manifest"]["defaults"].as_array().expect("defaults array");
assert!(
!defaults.iter().any(|e| e[0] == "fast.length"),
"an overridden bound param drops out of defaults: {out}"
);
}
/// Without `--override`, the same blueprint's bound values stay untouched:
/// `fast.length` is a held default, not a varied param.
#[test]
fn exec_blueprint_without_override_runs_bound_values_untouched() {
let (out, code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let line = out.lines().find(|l| l.starts_with('{')).expect("record line");
let v: serde_json::Value = serde_json::from_str(line).expect("record parses");
let params = v["manifest"]["params"].as_array().expect("params array");
assert!(
!params.iter().any(|e| e[0] == "fast.length"),
"no override means fast.length stays out of params: {out}"
);
let defaults = v["manifest"]["defaults"].as_array().expect("defaults array");
assert!(
defaults.iter().any(|e| e[0] == "fast.length" && e[1] == serde_json::json!({"I64": 2})),
"fast.length is a held default at its bound value 2: {out}"
);
}
/// A malformed override token (no `NODE.PARAM=VALUE` shape) refuses as usage.
#[test]
fn exec_override_malformed_token_refuses_with_usage() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "fast.length"]);
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(out.contains("--override"), "stdout/stderr: {out}");
assert!(out.contains("NODE.PARAM=VALUE"), "stdout/stderr: {out}");
}
/// An override path naming no param of the blueprint (open or bound) refuses
/// with `override_paths`' own prose (`member.rs:768-771`).
#[test]
fn exec_override_unknown_path_refuses_with_the_override_prose() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "nope.knob=1"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert!(
out.contains("names no param of this blueprint (open or bound)"),
"stdout/stderr: {out}"
);
}