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:
@@ -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, ¶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 `--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.
|
||||
Reference in New Issue
Block a user