plan: 0092b run-from-blueprint CLI arm + tests (continuation)

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
This commit is contained in:
2026-06-30 20:25:49 +02:00
parent ff4a1b3d4a
commit 43a25547ce
+182
View File
@@ -0,0 +1,182 @@
# 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.md` Tasks 1-3, shipped in
> commit `ff4a1b3` (the restructure + `RunManifest.topology_hash` + the shared
> `run_signal_stage1r` seam). This plan is the remaining Tasks 4-5.
>
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill 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>` + the `aura-registry` read-mirror.
- `sha2` dep; `blueprint_to_json`, `BlueprintNode` imported.
- Inline RED seam tests the Task-3 implementer added (a `topology_hash` assertion on the existing `run_stage1_r` test + `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` (the `run` subcommand dispatch + `run_signal_stage1r`'s `#[allow(dead_code)]`)
- [ ] **Step 1: Add a blueprint dispatch arm BEFORE the existing harness-kind
parse**, so a `.json` file path routes to the data-driven path and every other
`aura run <kind>` argument keeps the existing `HarnessKind` behaviour untouched
(#159 stays). Read the current `run` dispatch + `parse_run_args` /
`resolve_run_data` (the `--real`/`--from`/`--to`/`--seed` flags already exist)
to thread data + seed. The arm:
```rust
// 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, &params, 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 `--params` JSON cell array
into `Vec<Scalar>` (`Scalar` already derives `Deserialize`; the `{"I64":2}`
tagged form is the #155 representation). Absent `--params` → empty vec:
```rust
/// 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)]`** on
`run_signal_stage1r` (`main.rs:2940` area) 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.rs` test 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:
```rust
#[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.rs` test
module — the KEYSTONE property, distinct from the Task-3 Rust-twin seam test):
```rust
#[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 is `crates/aura-cli/tests/fixtures/stage1_signal.json` relative to
the workspace — match the existing fixture-loading tests in that file):
```rust
#[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.