9f87e5a583
Architect review over 9636b00..9221bcd. What holds, confirmed against the diff rather than the commit body: no stored record shape moved (neither `aura-engine/src/report.rs` nor the registry compat mirror appears in the change at all, and the byte pins pass unedited); the handle travels the beside-the-report route C27/#297 established for the unbound-tap names, with the library still printing nothing; trace enumeration landed inside `aura-registry` where C22 puts trace file I/O; no C14 exit class moved, both refusal arms falling through to the same exit 1. Two high drift items, both fixed here: - The corrected refusal claimed a campaign run prints its handle as `trace_name` on stdout. It prints `campaign_run.trace_name` — the campaign leg wraps its record, as the glossary states outright — so the chain the refusal advertised returns null there. This is the third instance of one error class in this cycle: a false claim inside the very prose meant to stop misdirecting callers. Grounding caught the first two, review the third. - The measurement leg shipped without the handle pin the spec asked for. All three new pins drove the strategy path, so the measurement leg emitted a handle nothing asserted. It has its own test now, on the shared bind pair the two legs cannot drift across. Ledger and vocabulary brought into lockstep: C27's current state records the `RunOutcome` carrier that replaced the pair; the glossary's tap entry names the single-run handle and warns that a family id is not one; the authoring guide's own single-run tap example now says the name is printed rather than guessed. The suggestion line gained the `aura:` prefix every other stderr line carries. Bench: all five fingerprints OK — the decisive signal, since they hang on the record bytes this cycle was required not to move. Timing deltas are load noise (loadavg 8.7 under concurrent agents), not regressions. refs #309
169 lines
8.0 KiB
Rust
169 lines
8.0 KiB
Rust
//! #286 / C28 phase 3: `aura run` on a measurement-shaped blueprint (declares a
|
|
//! tap, exposes no `bias`) runs BARE — no wrap_r scaffold — emits a
|
|
//! `MeasurementReport` and persists the tapped series. A taps-only blueprint is
|
|
//! refused before this feature; this pins the new additive behaviour.
|
|
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
|
|
const BIN: &str = env!("CARGO_BIN_EXE_aura");
|
|
|
|
fn temp_cwd(name: &str) -> std::path::PathBuf {
|
|
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-cli-meas-{name}"));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).expect("create temp cwd");
|
|
dir
|
|
}
|
|
|
|
/// The shipped `examples/r_sma.json`, turned MEASUREMENT-shaped: one declared tap
|
|
/// on node 0 ("fast", SMA(2)) field 0, and its `bias` output removed. Same closed
|
|
/// topology (`sma_signal`), so it runs on the built-in synthetic stream.
|
|
fn measurement_blueprint_json() -> String {
|
|
let path = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
|
|
let doc = std::fs::read_to_string(path).expect("read examples/r_sma.json");
|
|
let mut v: serde_json::Value = serde_json::from_str(&doc).expect("parse r_sma.json");
|
|
v["blueprint"]["taps"] = serde_json::json!([{"name": "fast_tap", "from": {"node": 0, "field": 0}}]);
|
|
v["blueprint"]["output"] = serde_json::json!([]); // no `bias` — a measurement, not a strategy
|
|
serde_json::to_string(&v).expect("re-serialize measurement blueprint")
|
|
}
|
|
|
|
#[test]
|
|
fn measurement_blueprint_runs_bare_and_emits_its_tap() {
|
|
let cwd = temp_cwd("runs-bare");
|
|
let bp_path = cwd.join("measurement.json");
|
|
std::fs::write(&bp_path, measurement_blueprint_json()).expect("write measurement blueprint");
|
|
|
|
let out = Command::new(BIN)
|
|
.args(["exec", bp_path.to_str().expect("utf-8 path")])
|
|
.current_dir(&cwd)
|
|
.output()
|
|
.expect("spawn aura run");
|
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
|
|
// stdout is a MeasurementReport: the declared tap name, no R `metrics`, the
|
|
// measurement broker sentinel.
|
|
let report: serde_json::Value =
|
|
serde_json::from_slice(&out.stdout).expect("stdout parses as MeasurementReport JSON");
|
|
assert_eq!(report["taps"], serde_json::json!(["fast_tap"]));
|
|
assert!(report.get("metrics").is_none(), "a measurement run has no R metrics");
|
|
assert_eq!(report["manifest"]["broker"], "measurement");
|
|
|
|
// The tapped series is persisted through the trace store.
|
|
let trace_path = cwd.join("runs/traces/sma_signal/fast_tap.json");
|
|
let trace_text = std::fs::read_to_string(&trace_path)
|
|
.unwrap_or_else(|e| panic!("expected a persisted tap trace at {}: {e}", trace_path.display()));
|
|
let trace: serde_json::Value = serde_json::from_str(&trace_text).expect("parse tap trace json");
|
|
assert_eq!(trace["tap"], "fast_tap");
|
|
assert_eq!(trace["kinds"], serde_json::json!(["F64"]));
|
|
}
|
|
|
|
/// #310: the measurement arm honours the `--tap` selector — a `last`
|
|
/// fold lands one summary row instead of the full series.
|
|
#[test]
|
|
fn measurement_run_honours_the_tap_selector() {
|
|
let cwd = temp_cwd("selector-last");
|
|
let bp_path = cwd.join("measurement.json");
|
|
std::fs::write(&bp_path, measurement_blueprint_json()).expect("write measurement blueprint");
|
|
let out = Command::new(BIN)
|
|
.args(["exec", bp_path.to_str().expect("utf-8 path"), "--tap", "fast_tap=last"])
|
|
.current_dir(&cwd)
|
|
.output()
|
|
.expect("spawn aura run");
|
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
|
|
let trace_text = std::fs::read_to_string(cwd.join("runs/traces/sma_signal/fast_tap.json"))
|
|
.expect("read fold trace");
|
|
let trace: serde_json::Value = serde_json::from_str(&trace_text).expect("parse fold trace");
|
|
let rows = trace["columns"][0].as_array().expect("columns[0] array");
|
|
assert_eq!(rows.len(), 1, "a fold lands exactly one summary row");
|
|
// last warm SMA(2) value over the synthetic stream = (1.0097 + 1.0092) / 2
|
|
let got = rows[0].as_f64().expect("f64 row");
|
|
let expected = (1.0097_f64 + 1.0092) / 2.0;
|
|
assert!((got - expected).abs() < 1e-9, "last row: got {got}, expected {expected}");
|
|
}
|
|
|
|
/// #331 delta re-review, the third authored-file intake route: `dispatch_run`
|
|
/// loads a FRESH FILE via `blueprint_from_json` and, on this measurement arm,
|
|
/// feeds `signal.name()` straight into `bind_tap_plan` -> `TraceStore::begin_run`
|
|
/// (`trace_store.rs` joins the name unsanitized) — a hand-crafted envelope with
|
|
/// `"name":"../x"` used to reach `--tap fast_tap=record` and escape
|
|
/// `runs/traces/<name>/` for `runs/x/` (one level up, since `traces/../x`
|
|
/// normalizes there) instead of refusing at the same root-name choke point
|
|
/// `register`/`introspect --content-id <FILE>` already gate through. This pins
|
|
/// the closed route: exit nonzero (dispatch_run's own bad-input-file
|
|
/// convention, exit 2), the shared `name_gate_fault_prose` wording, and — the
|
|
/// property that actually matters — the escaped directory is never created.
|
|
#[test]
|
|
fn run_refuses_a_hand_crafted_envelope_with_a_bad_root_name_before_any_trace_write() {
|
|
let cwd = temp_cwd("bad-root-name");
|
|
let mut v: serde_json::Value =
|
|
serde_json::from_str(&measurement_blueprint_json()).expect("parse measurement blueprint");
|
|
v["blueprint"]["name"] = serde_json::json!("../x");
|
|
let bad = serde_json::to_string(&v).expect("re-serialize with a shape-violating root name");
|
|
let bp_path = cwd.join("bad-root-name.json");
|
|
std::fs::write(&bp_path, &bad).expect("write bad-root-name blueprint");
|
|
|
|
let out = Command::new(BIN)
|
|
.args(["exec", bp_path.to_str().expect("utf-8 path"), "--tap", "fast_tap=record"])
|
|
.current_dir(&cwd)
|
|
.output()
|
|
.expect("spawn aura run");
|
|
assert_eq!(
|
|
out.status.code(),
|
|
Some(2),
|
|
"a shape-violating root name refuses at dispatch_run's own bad-file exit code: {:?} stderr={}",
|
|
out.status,
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(
|
|
stderr.contains(r#"blueprint name "../x" is invalid"#),
|
|
"the shared name_gate_fault_prose wording names the offending root name: {stderr}"
|
|
);
|
|
assert!(
|
|
out.stdout.is_empty(),
|
|
"a refused run must not print a report: {:?}",
|
|
String::from_utf8_lossy(&out.stdout)
|
|
);
|
|
|
|
// The property that actually matters: the escaped write target (one level
|
|
// up from `traces/`, since `trace_store.rs` joins the name unsanitized)
|
|
// was never created — the gate fires before `begin_run` ever touches disk.
|
|
assert!(
|
|
!cwd.join("runs/x").exists(),
|
|
"the escaped trace directory must never be created"
|
|
);
|
|
assert!(
|
|
!cwd.join("runs/traces").exists(),
|
|
"no trace directory of any shape is written on a refused run"
|
|
);
|
|
}
|
|
|
|
/// #309: the measurement leg reports its trace handle exactly as the strategy
|
|
/// leg does — both bind through one shared tap-plan pair, so a handle present
|
|
/// on one and absent on the other would mean the seam had drifted.
|
|
#[test]
|
|
fn measurement_run_reports_its_trace_handle_on_stdout() {
|
|
let cwd = temp_cwd("measurement-trace-handle");
|
|
let bp_path = cwd.join("measurement.json");
|
|
std::fs::write(&bp_path, measurement_blueprint_json()).expect("write measurement blueprint");
|
|
|
|
let out = Command::new(BIN)
|
|
.args(["exec", bp_path.to_str().expect("utf-8 path")])
|
|
.current_dir(&cwd)
|
|
.output()
|
|
.expect("spawn exec");
|
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
|
|
let line = String::from_utf8_lossy(&out.stdout);
|
|
let report: serde_json::Value =
|
|
serde_json::from_str(line.trim()).expect("stdout is one JSON object");
|
|
let handle = report["trace_name"].as_str().expect("trace_name is present and a string");
|
|
assert!(
|
|
cwd.join("runs/traces").join(handle).join("fast_tap.json").exists(),
|
|
"the reported handle must name the directory the tapped series landed in; got {handle}"
|
|
);
|
|
|
|
let _ = std::fs::remove_dir_all(&cwd);
|
|
}
|