The remaining Tasks 4-5 of #165 after the ff4a1b3 infrastructure (restructure +
topology_hash + the shared run_signal_stage1r seam): the `aura run
<blueprint.json>` CLI arm (wiring the committed seam, retiring its transient
dead_code allow) + the loaded-signal bit-identical / determinism / E2E tests +
the in-repo demo blueprint. Resolves the Task-4 block by dropping the
consumer-less --pip-size flag (pip is data-derived).
refs #165
8.8 KiB
Run-from-blueprint — CLI arm + tests (continuation) — Implementation Plan
Parent spec:
docs/specs/0092-run-from-blueprint.md(#165) Continues:docs/plans/0092-run-from-blueprint.mdTasks 1-3, shipped in commitff4a1b3(the restructure +RunManifest.topology_hash+ the sharedrun_signal_stage1rseam). This plan is the remaining Tasks 4-5.For agentic workers: REQUIRED SUB-SKILL: use the
implementskill to run this plan. Steps use- [ ]checkboxes.
Goal: wire the committed run_signal_stage1r seam to a CLI aura run <blueprint.json> arm and prove it end-to-end (a loaded signal runs bit-identical
to its Rust-built twin, manifest carries topology_hash).
Already in the tree (commit ff4a1b3, do NOT re-create):
run_signal_stage1r(signal: Composite, params: &[Scalar], data: RunData, seed: u64) -> RunReport(crates/aura-cli/src/main.rs:2940) — currently#[allow(dead_code)](no production caller yet).topology_hash(signal: &Composite) -> String(main.rs:2700),stage1_signal(...)(:2670),wrap_stage1r(...)(:2759).RunManifest.topology_hash: Option<String>+ theaura-registryread-mirror.sha2dep;blueprint_to_json,BlueprintNodeimported.- Inline RED seam tests the Task-3 implementer added (a
topology_hashassertion on the existingrun_stage1_rtest +run_signal_stage1r_folds_the_same_r_block_as_run_stage1_r). Do NOT duplicate these; Task 2 below adds only the LOADED-signal path tests.
Design decision (resolves the Task-4 block): the speculative --pip-size
flag is DROPPED — pip_size is data-derived inside the run path
(synthetic → SYNTHETIC_PIP_SIZE, real → the instrument sidecar via
open_real_source/resolve_run_data), exactly as run_stage1_r does. The
user-ratified surface is positional entry + --params + the hash, not a pip
override.
Task 1: The aura run <blueprint.json> CLI arm
Files:
-
Modify:
crates/aura-cli/src/main.rs(therunsubcommand dispatch +run_signal_stage1r's#[allow(dead_code)]) -
Step 1: Add a blueprint dispatch arm BEFORE the existing harness-kind parse, so a
.jsonfile path routes to the data-driven path and every otheraura run <kind>argument keeps the existingHarnessKindbehaviour untouched (#159 stays). Read the currentrundispatch +parse_run_args/resolve_run_data(the--real/--from/--to/--seedflags already exist) to thread data + seed. The arm:
// in the `["run", rest @ ..]` dispatch arm, before the HarnessKind parse:
if let Some(path) = rest.first().filter(|a| a.ends_with(".json") && std::path::Path::new(a).is_file()) {
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| { eprintln!("aura: {path}: {e}"); std::process::exit(2); });
let signal = blueprint_from_json(&doc, &|t| std_vocabulary(t)).unwrap_or_else(|e| { eprintln!("aura: {path}: {e:?}"); std::process::exit(2); });
let params = parse_param_cells(rest); // --params '[{"I64":2},...]' -> Vec<Scalar>, [] if absent
let data = resolve_run_data(rest); // existing helper: --real <SYM> [--from --to] else Synthetic
let seed = parse_seed(rest); // existing --seed parse, 0 default
let report = run_signal_stage1r(signal, ¶ms, data, seed);
println!("{}", report.to_json());
return;
}
Use the EXISTING data/seed parsing helpers (resolve_run_data and the --seed
parse the implementer referenced in the block report) rather than new ones; add
use aura_std::std_vocabulary; and blueprint_from_json to the imports.
- Step 2: Add
parse_param_cells— parse the--paramsJSON cell array intoVec<Scalar>(Scalaralready derivesDeserialize; the{"I64":2}tagged form is the #155 representation). Absent--params→ empty vec:
/// Parse `--params '[{"I64":2},{"F64":0.5}]'` (the #155 tagged-Scalar cell array)
/// into the `Vec<Scalar>` `compile_with_params` wants. Absent flag -> empty.
fn parse_param_cells(args: &[&str]) -> Vec<Scalar> {
match flag_value(args, "--params") { // reuse the existing flag-extraction helper
Some(json) => serde_json::from_str(json).unwrap_or_else(|e| { eprintln!("aura: --params: {e}"); std::process::exit(2); }),
None => Vec::new(),
}
}
(Confirm the existing flag-extraction helper's name — the recon/Task-1
implementer used one for --real/--seed; match it. If none is reusable, a
minimal args.iter().position(|a| *a == "--params").and_then(...) is fine.)
-
Step 3: Retire the transient
#[allow(dead_code)]onrun_signal_stage1r(main.rs:2940area) now that the CLI arm is its production caller. -
Step 4: Verify the build + the existing dispatch is intact
Run: cargo build -p aura-cli
Expected: 0 errors, 0 warnings (the dead_code allow is gone and the fn is now
used, so clippy -D warnings is satisfied).
Run: cargo test -p aura-cli parse_run_args
Expected: PASS — the existing harness-kind arg parsing unchanged.
Task 2: Demo blueprint + the loaded-signal E2E + bit-identical test
Files:
-
Create:
crates/aura-cli/tests/fixtures/stage1_signal.json— the demo signal blueprint -
Create:
crates/aura-cli/tests/fixtures/unknown_node.json— a blueprint referencing a non-vocabulary type -
Test:
crates/aura-cli/src/main.rstest module (the loaded-signal bit-identical + determinism) -
Test:
crates/aura-cli/tests/cli_run.rs(binary E2E) -
Step 1: Generate + commit the demo signal blueprint. It must equal
blueprint_to_json(&stage1_signal(Some(2), Some(4)))so it loads cleanly. Add a one-off emitter and capture its output into the fixture:
Run: cargo test -p aura-cli emit_demo_signal_fixture -- --ignored --nocapture
where the emitter is:
#[test]
#[ignore = "regenerates the committed demo fixture"]
fn emit_demo_signal_fixture() {
let json = blueprint_to_json(&stage1_signal(Some(2), Some(4))).expect("serializes");
std::fs::write("tests/fixtures/stage1_signal.json", &json).expect("write fixture");
}
Then verify the fixture is the format_version:1 signal (4 nodes: fast/slow
SMA, Sub, Bias; one price role; one bias output). Also hand-write
tests/fixtures/unknown_node.json = the same shape but one node "type":"Nope"
(or copy the fixture and edit one type to a non-vocabulary name) for the
failure E2E.
- Step 2: The loaded-signal bit-identical test (unit,
main.rstest module — the KEYSTONE property, distinct from the Task-3 Rust-twin seam test):
#[test]
fn loaded_signal_runs_bit_identical_to_rust_built() {
let rust = stage1_signal(Some(2), Some(4));
let json = blueprint_to_json(&rust).expect("serializes");
let loaded = blueprint_from_json(&json, &|t| std_vocabulary(t)).expect("loads");
let a = run_signal_stage1r(stage1_signal(Some(2), Some(4)), &[], RunData::Synthetic, 0);
let b = run_signal_stage1r(loaded, &[], RunData::Synthetic, 0);
assert_eq!(a.to_json(), b.to_json(), "loaded run is bit-identical incl. topology_hash");
assert_eq!(a.manifest.topology_hash.as_ref().expect("hash").len(), 64, "64-hex sha256");
}
#[test]
fn topology_hash_is_stable_and_distinguishes() {
let h = topology_hash(&stage1_signal(Some(2), Some(4)));
assert_eq!(h, topology_hash(&stage1_signal(Some(2), Some(4))), "same signal -> same hash");
assert_ne!(h, topology_hash(&stage1_signal(Some(3), Some(4))), "different topology -> different hash");
}
- Step 3: The binary E2E in
crates/aura-cli/tests/cli_run.rs(use the file's existing binary-spawn helper; if it spawns from the crate root, the fixture path iscrates/aura-cli/tests/fixtures/stage1_signal.jsonrelative to the workspace — match the existing fixture-loading tests in that file):
#[test]
fn aura_run_loads_and_runs_a_blueprint_file() {
let (stdout, _stderr, ok) = run(&["run", "tests/fixtures/stage1_signal.json"]); // match the file's run() signature/cwd
assert!(ok, "exit 0");
assert!(stdout.contains("\"topology_hash\":\""), "manifest carries the topology hash: {stdout}");
}
#[test]
fn aura_run_rejects_an_unknown_node_blueprint() {
let (_stdout, stderr, ok) = run(&["run", "tests/fixtures/unknown_node.json"]);
assert!(!ok, "non-zero exit");
assert!(stderr.contains("UnknownNodeType") || stderr.to_lowercase().contains("unknown node"), "names the cause: {stderr}");
}
(Confirm the cli_run.rs run(...) helper's exact signature + working
directory — the existing tests there set the precedent; match them, including
whether stdin is needed.)
- Step 4: Full verification
Run: cargo test -p aura-cli loaded_signal_runs_bit_identical_to_rust_built
Expected: PASS.
Run: cargo test --workspace
Expected: PASS — full suite green.
Run: cargo clippy --workspace --all-targets -- -D warnings
Expected: clean.