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:
@@ -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}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user