Run a harness from a serialized blueprint (aura run <blueprint.json>) #165

Closed
opened 2026-06-30 18:02:08 +02:00 by Brummel · 3 comments
Owner

The keystone of the World/C21 milestone: turn a serialized C24 blueprint into an actual run from the CLI, closing the data → bootstrap → run loop the cycle-0090 milestone fieldtest flagged as friction (the CLI could author and emit topology but not run an emitted blueprint).

Deliverable

aura run <blueprint.json> [--params <json>]:

  • load the serialized blueprint (blueprint_from_json), compile via the injected std_vocabulary resolver, bootstrap (params + data + seed), run the harness, persist a RunReport;
  • the manifest carries a topology_hash field from the start.

Acceptance (validated property)

  • A blueprint serialized from a Rust-built harness, then loaded and run via aura run, produces bit-identical metrics / traces to the Rust-builder path for the same topology + params + seed + data (C1).
  • The run's manifest carries topology_hash = SHA256 of the canonical (#164, no-trailing-newline) blueprint form.
  • topology_hash is a Tier-1 optional manifest field (serde default / skip), so existing manifests deserialize unchanged (#156).
  • A demo blueprint shipped in-repo runs via aura run <demo>.json.

Settled forks (charter, ratified by the user "macht alles sinn, leg los" — 2026-06-30)

  • Entry syntax: positional — aura run <blueprint.json> (data-driven is the default going forward; a --blueprint alias can follow if wanted).
  • Param injection: positional JSON cells first — --params '[{"F64":0.5},{"I64":2}]' (matches compile_with_params, used by the sweep path); by-name --bind name=value deferred as a fast follow-on.
  • Topology hash: SHA256 of the canonical blueprint_to_json form, stored as a hex string in manifest.topology_hash.

Notes

  • Node logic stays Rust (C17); only topology is data. The injected resolver resolves only compiled-in type identities (invariant 9; #160 guard at the data-plane face — an unknown type fails clean as UnknownNodeType).
  • The charter expects this to be largely CLI wiring over the existing bootstrap / compile / run plumbing; the grounding-check verifies what already exists vs. what must be added.

Part of the World/C21 milestone (run + orchestrate families from blueprint-data).

The keystone of the World/C21 milestone: turn a serialized C24 blueprint into an actual run from the CLI, closing the data → bootstrap → run loop the cycle-0090 milestone fieldtest flagged as friction (the CLI could author and emit topology but not run an emitted blueprint). ## Deliverable `aura run <blueprint.json> [--params <json>]`: - load the serialized blueprint (`blueprint_from_json`), compile via the injected `std_vocabulary` resolver, bootstrap (params + data + seed), run the harness, persist a RunReport; - the manifest carries a `topology_hash` field from the start. ## Acceptance (validated property) - [ ] A blueprint serialized from a Rust-built harness, then loaded and run via `aura run`, produces **bit-identical** metrics / traces to the Rust-builder path for the same topology + params + seed + data (C1). - [ ] The run's manifest carries `topology_hash` = SHA256 of the canonical (#164, no-trailing-newline) blueprint form. - [ ] `topology_hash` is a Tier-1 optional manifest field (serde default / skip), so existing manifests deserialize unchanged (#156). - [ ] A demo blueprint shipped in-repo runs via `aura run <demo>.json`. ## Settled forks (charter, ratified by the user "macht alles sinn, leg los" — 2026-06-30) - **Entry syntax:** positional — `aura run <blueprint.json>` (data-driven is the default going forward; a `--blueprint` alias can follow if wanted). - **Param injection:** positional JSON cells first — `--params '[{"F64":0.5},{"I64":2}]'` (matches `compile_with_params`, used by the sweep path); by-name `--bind name=value` deferred as a fast follow-on. - **Topology hash:** SHA256 of the canonical `blueprint_to_json` form, stored as a hex string in `manifest.topology_hash`. ## Notes - Node logic stays Rust (C17); only topology is data. The injected resolver resolves only compiled-in type identities (invariant 9; #160 guard at the data-plane face — an unknown type fails clean as `UnknownNodeType`). - The charter expects this to be largely CLI wiring over the existing bootstrap / compile / run plumbing; the grounding-check verifies what already exists vs. what must be added. Part of the World/C21 milestone (run + orchestrate families from blueprint-data).
Brummel added this to the World/C21: run + orchestrate harness families from blueprint-data milestone 2026-06-30 18:02:08 +02:00
Brummel added the feature label 2026-06-30 18:02:08 +02:00
Author
Owner

Derived design decisions (cycle-1 grounding, specify Step 1.5)

A read-only recon of the existing run plumbing settled the one load-bearing
fork the charter left implicit. Recorded here before spec production.

Fork — what does aura run <blueprint.json> actually run? A serialized C24
blueprint is a SIGNAL (a strategy Composite, ≤1 output): recording sinks
(Recorder, runtime mpsc::Sender identity), brokers (SimBroker,
construction-arg builder), the data source, and the clock are deliberately OUT
of the round-trippable set (C24 ledger, "Out of the first cut's round-trippable
set"). So aura run cannot "just run" a blueprint — it must WRAP the loaded
signal in a runnable harness.

Decision (derived): aura run <blueprint.json> wraps the loaded signal in
the existing Stage-1-R run scaffolding — the same broker + equity/exposure
recording sinks + data binding that run_stage1_r (crates/aura-cli/src/main.rs)
builds around its hard-wired signal Composite — then compiles
(compile_with_params), bootstraps (Harness::bootstrap), runs, and emits a
RunReport. Basis: (1) the existing single-run path already establishes exactly
this wrap → compile → bootstrap → run → manifest shape; (2) wrapping the loaded
twin and the Rust-built twin IDENTICALLY is what makes the acceptance "bit-
identical to the Rust-builder path" (C1) a real, testable property — it is
precisely the blueprint_serde_e2e.rs Recorder-wrapping pattern lifted to the
CLI; (3) it keeps node logic Rust and only topology as data (C17), the broker /
sinks supplied at run, not serialized (honoring the C24 deferral).

Sub-decisions (derived, all precedent-established in the existing run path):

  • Broker: the Stage-1-R broker (pip from --pip-size / instrument sidecar /
    default), as run_stage1_r does — not added to the serialized blueprint.
  • Data: the existing split — synthetic by default, --real <SYM> [--from --to]
    opens the real M1 source.
  • Seed/window: seed from --seed (default 0); window probed from the sources
    post-run (window_of), as today.
  • topology_hash: SHA256 of the canonical (#164, no-trailing-newline)
    blueprint_to_json of the LOADED SIGNAL blueprint, as a Tier-1 optional
    RunManifest field (serde default / skip — old manifests load unchanged, #156).

The user-ratified surface forks (positional aura run <blueprint.json>;
positional --params JSON cells; SHA256 hash) are in the issue body
("Settled forks", ratified "macht alles sinn, leg los" — 2026-06-30).

This is a derivable wiring task over proven substrate, not a new design — ready
for spec production.

## Derived design decisions (cycle-1 grounding, specify Step 1.5) A read-only recon of the existing run plumbing settled the one load-bearing fork the charter left implicit. Recorded here before spec production. **Fork — what does `aura run <blueprint.json>` actually run?** A serialized C24 blueprint is a SIGNAL (a strategy `Composite`, ≤1 output): recording sinks (`Recorder`, runtime `mpsc::Sender` identity), brokers (`SimBroker`, construction-arg builder), the data source, and the clock are deliberately OUT of the round-trippable set (C24 ledger, "Out of the first cut's round-trippable set"). So `aura run` cannot "just run" a blueprint — it must WRAP the loaded signal in a runnable harness. **Decision (derived):** `aura run <blueprint.json>` wraps the loaded signal in the **existing Stage-1-R run scaffolding** — the same broker + equity/exposure recording sinks + data binding that `run_stage1_r` (crates/aura-cli/src/main.rs) builds around its hard-wired signal `Composite` — then compiles (`compile_with_params`), bootstraps (`Harness::bootstrap`), runs, and emits a `RunReport`. Basis: (1) the existing single-run path already establishes exactly this wrap → compile → bootstrap → run → manifest shape; (2) wrapping the loaded twin and the Rust-built twin IDENTICALLY is what makes the acceptance "bit- identical to the Rust-builder path" (C1) a real, testable property — it is precisely the `blueprint_serde_e2e.rs` Recorder-wrapping pattern lifted to the CLI; (3) it keeps node logic Rust and only topology as data (C17), the broker / sinks supplied at run, not serialized (honoring the C24 deferral). **Sub-decisions (derived, all precedent-established in the existing run path):** - **Broker:** the Stage-1-R broker (pip from `--pip-size` / instrument sidecar / default), as `run_stage1_r` does — not added to the serialized blueprint. - **Data:** the existing split — synthetic by default, `--real <SYM> [--from --to]` opens the real M1 source. - **Seed/window:** seed from `--seed` (default 0); window probed from the sources post-run (`window_of`), as today. - **topology_hash:** SHA256 of the canonical (`#164`, no-trailing-newline) `blueprint_to_json` of the LOADED SIGNAL blueprint, as a Tier-1 optional `RunManifest` field (serde default / skip — old manifests load unchanged, #156). The user-ratified surface forks (positional `aura run <blueprint.json>`; positional `--params` JSON cells; SHA256 hash) are in the issue body ("Settled forks", ratified "macht alles sinn, leg los" — 2026-06-30). This is a derivable wiring task over proven substrate, not a new design — ready for spec production.
Author
Owner

Grounding refinements from plan-recon (derived decisions)

A read-only plan-recon found the wrapping seam is not a clean line-lift and
left three derivation questions. All four are derivable from the code + the C24
serialization requirement; resolved here.

Q1 — the seam is a behaviour-preserving RESTRUCTURE, not an extraction.
stage1_r_graph (crates/aura-cli/src/main.rs:2631-2790) co-builds the SIGNAL
(fast/slow SmaSubBias) and the WRAPPING (broker / Recorder /
SeriesReducer / RiskExecutor / cost-graph) in ONE GraphBuilder, with a
single g.feed(price, [...]) into both and the wrapping reading the signal's
internal handle exposure.output("bias"). There is no standalone signal
Composite today.
Decision: restructure it into stage1_signal() -> Composite (the
fast/slow/sub/bias signal, exposing a price input-role + a bias
output at its boundary — the serializable shape) and
wrap_stage1r(signal, pip) -> Composite (the broker / sinks / exec / cost
wrapping), with stage1_r_graph() = wrap_stage1r(stage1_signal(), pip).
Basis: this is intrinsic, not optional — a serialized signal MUST have a
clean boundary (only a bounded signal round-trips), and reusing the Stage-1-R
wrapping requires separating it from the signal; both force the same split. The
blueprint_serde_e2e.rs template (a nested signal-Composite with a price role +
single bias output, wrapped with a Recorder) proves the boundary shape works.
Behaviour-preserving: run_stage1_r's metrics + traces stay identical (gated by
its existing tests + the bit-identical e2e).

Q2 — manifest.params comes from the signal's param_space(), not a
hard-coded tuple.
run_stage1_r hard-codes a 5-named-param tuple. The
data-driven path derives the names from the loaded signal's param_space()
(its open-param names) zipped with the --params values.

Q3 — param threading. wrap_stage1r binds its OWN params (pip etc.),
leaving only the signal's params open on the wrapped graph; the shared seam
calls wrapped.compile_with_params(signal_params). So --params is exactly the
signal's open-param vector (param_space() ordered), and run_stage1_r passes
its values through the same seam (no more compile_with_params(&[]) on a fully
pre-bound graph).

Q4 — "behaviour-preserving" semantics (the AC tension). When run_stage1_r
routes the shared seam, its manifest GAINS topology_hash (a new key) — this is
by design, not a regression: every run becoming self-identifying is exactly
what #158 wants. "Behaviour-preserving" here means metrics + traces identical;
the manifest additionally carrying topology_hash is the intended feature. The
structural stage1-r test (run_stage1_r_synthetic_folds_an_r_block) stays green;
no exact-JSON pin exists on the stage1-r manifest to break (recon-verified).

Blast radius note (for the plan): adding RunManifest.topology_hash without a
Default derive is a hard compile break at 24 literal RunManifest { … }
sites across 7 crates
(recon-enumerated), not the two the spec names — each gets
topology_hash: None. The hash helper + sha2 live in aura-cli (the seam),
never in the frozen aura-engine (invariant 8; #158 is research-side).

## Grounding refinements from plan-recon (derived decisions) A read-only plan-recon found the wrapping seam is **not** a clean line-lift and left three derivation questions. All four are derivable from the code + the C24 serialization requirement; resolved here. **Q1 — the seam is a behaviour-preserving RESTRUCTURE, not an extraction.** `stage1_r_graph` (crates/aura-cli/src/main.rs:2631-2790) co-builds the SIGNAL (fast/slow `Sma` → `Sub` → `Bias`) and the WRAPPING (broker / `Recorder` / `SeriesReducer` / `RiskExecutor` / cost-graph) in ONE `GraphBuilder`, with a single `g.feed(price, [...])` into both and the wrapping reading the signal's *internal* handle `exposure.output("bias")`. There is no standalone signal `Composite` today. **Decision:** restructure it into `stage1_signal() -> Composite` (the fast/slow/sub/bias signal, exposing a `price` **input-role** + a `bias` **output** at its boundary — the serializable shape) and `wrap_stage1r(signal, pip) -> Composite` (the broker / sinks / exec / cost wrapping), with `stage1_r_graph() = wrap_stage1r(stage1_signal(), pip)`. **Basis:** this is intrinsic, not optional — a *serialized* signal MUST have a clean boundary (only a bounded signal round-trips), and *reusing* the Stage-1-R wrapping requires separating it from the signal; both force the same split. The `blueprint_serde_e2e.rs` template (a nested signal-Composite with a price role + single bias output, wrapped with a `Recorder`) proves the boundary shape works. Behaviour-preserving: `run_stage1_r`'s metrics + traces stay identical (gated by its existing tests + the bit-identical e2e). **Q2 — `manifest.params` comes from the signal's `param_space()`, not a hard-coded tuple.** `run_stage1_r` hard-codes a 5-named-param tuple. The data-driven path derives the names from the loaded signal's `param_space()` (its open-param names) zipped with the `--params` values. **Q3 — param threading.** `wrap_stage1r` binds its OWN params (pip etc.), leaving only the signal's params open on the wrapped graph; the shared seam calls `wrapped.compile_with_params(signal_params)`. So `--params` is exactly the signal's open-param vector (`param_space()` ordered), and `run_stage1_r` passes its values through the same seam (no more `compile_with_params(&[])` on a fully pre-bound graph). **Q4 — "behaviour-preserving" semantics (the AC tension).** When `run_stage1_r` routes the shared seam, its manifest GAINS `topology_hash` (a new key) — this is **by design**, not a regression: every run becoming self-identifying is exactly what #158 wants. "Behaviour-preserving" here means **metrics + traces identical**; the manifest additionally carrying `topology_hash` is the intended feature. The structural stage1-r test (`run_stage1_r_synthetic_folds_an_r_block`) stays green; no exact-JSON pin exists on the stage1-r manifest to break (recon-verified). **Blast radius note (for the plan):** adding `RunManifest.topology_hash` without a `Default` derive is a hard compile break at **24 literal `RunManifest { … }` sites across 7 crates** (recon-enumerated), not the two the spec names — each gets `topology_hash: None`. The hash helper + `sha2` live in `aura-cli` (the seam), never in the frozen `aura-engine` (invariant 8; #158 is research-side).
Author
Owner

Task-4 block resolved: --pip-size dropped

The cycle-1 implement run (Tasks 1-3 shipped in ff4a1b3) blocked Task 4 on a
contradiction the plan introduced: it asked for a --pip-size <n> flag, but the
shared run_signal_stage1r(signal, params, data, seed) seam takes no pip_size
(pip is derived inside the run path) and no consumer for the flag exists.

Decision (derived): drop --pip-size. pip_size is data-derived —
SYNTHETIC_PIP_SIZE for a synthetic run, the instrument sidecar for --real,
exactly as run_stage1_r already does. The user-ratified surface is positional
entry + --params + the topology hash, not a pip override; a pip override is an
additive follow-on if ever wanted, not a keystone need. The continuation plan
(docs/plans/0092b-run-from-blueprint-cli.md, Tasks 4-5) carries this.

## Task-4 block resolved: `--pip-size` dropped The cycle-1 implement run (Tasks 1-3 shipped in `ff4a1b3`) blocked Task 4 on a contradiction the plan introduced: it asked for a `--pip-size <n>` flag, but the shared `run_signal_stage1r(signal, params, data, seed)` seam takes no `pip_size` (pip is derived inside the run path) and no consumer for the flag exists. **Decision (derived):** drop `--pip-size`. `pip_size` is data-derived — `SYNTHETIC_PIP_SIZE` for a synthetic run, the instrument sidecar for `--real`, exactly as `run_stage1_r` already does. The user-ratified surface is positional entry + `--params` + the topology hash, not a pip override; a pip override is an additive follow-on if ever wanted, not a keystone need. The continuation plan (`docs/plans/0092b-run-from-blueprint-cli.md`, Tasks 4-5) carries this.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#165