diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index 6f0852e..c1dfd95 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -141,6 +141,40 @@ fn run_prints_json_and_exits_zero() { assert!(line.contains("\"r\":{\"expectancy_r\":"), "got: {line}"); } +/// Property (#249): a plain `aura run` on a fully bound blueprint records the +/// untouched bound param values *directly in the outcome artifact*, so a raw +/// reader of the manifest recovers the effective params without dereferencing +/// the topology hash into the content-addressed store and re-implementing +/// aura's merge semantics. The manifest carries a `defaults` field — the bound +/// params merged inside `PrimitiveBuilder::build` below every record-writing +/// layer — as wrap-prefixed `(name, Scalar)` pairs in `bound_param_space()` +/// order (the same coordinate `--axis` uses). `params` keeps its "what varied" +/// meaning: on a plain run nothing varied, so it stays `[]`, and every bound +/// value lands in `defaults` instead of vanishing from the record. +#[test] +fn run_manifest_stamps_untouched_bound_defaults() { + // The shipped r_sma.json is fully bound (fast.length=2, slow.length=4, + // bias.scale=0.5); no axis re-opens any of them, so none flow through the + // param space — today they appear in no outcome artifact ("params":[]). + let out = Command::new(BIN).args(["run", "examples/r_sma.json"]).output().expect("spawn aura run"); + assert!(out.status.success(), "exit status: {:?}", out.status); + let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); + let line = stdout.trim_end(); + + // "what varied" is empty on a plain run — the bound defaults are NOT params. + assert!(line.contains("\"params\":[]"), "params stays empty on a plain run: {line}"); + + // The untouched bound values are stamped in `defaults`, wrap-prefixed with + // the blueprint name and ordered by `bound_param_space()` (node-declaration + // order: fast, slow, bias), each a self-describing tagged Scalar (C14). + assert!( + line.contains( + r#""defaults":[["sma_signal.fast.length",{"I64":2}],["sma_signal.slow.length",{"I64":4}],["sma_signal.bias.scale",{"F64":0.5}]]"# + ), + "manifest must carry the untouched bound defaults: {line}", + ); +} + /// Property: the RunManifest is self-identifying — its `commit` carries the /// real code identity that produced the run, not a placeholder. C8/C18 audit /// trail (this run = this commit): once runs are archived, `manifest.commit`