diff --git a/crates/aura-std/src/lincomb.rs b/crates/aura-std/src/lincomb.rs index fd033b8..6074936 100644 --- a/crates/aura-std/src/lincomb.rs +++ b/crates/aura-std/src/lincomb.rs @@ -4,7 +4,7 @@ //! weights are construction parameters that configure the node and fix its //! arity (`weights.len()` inputs); they are also the combination's tunable //! params, declared in the schema (cycle 0015) as `weights[0..N]` — N flat -//! indexed `F64` knobs that `Blueprint::param_space` aggregates (C8/C12/C19). +//! indexed `F64` knobs that `Composite::param_space` aggregates (C8/C12/C19). use aura_core::{ Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, Scalar, diff --git a/docs/design/INDEX.md b/docs/design/INDEX.md index 5a01ec6..afe05fa 100644 --- a/docs/design/INDEX.md +++ b/docs/design/INDEX.md @@ -192,10 +192,15 @@ set and SoA. The open set is composites (schemas of columns), not scalar types. Type-erasure at the edge is also forced by the cdylib boundary (C13). ### C8 — The node contract -**Guarantee.** A node implements `schema()` (declares each input's scalar type, -required lookback depth, and firing group, **and the node's own tunable -parameters — typed, with ranges**, which aggregate into the blueprint's -param-space the optimizer sweeps, C12/C19/C20) + `eval(ctx) -> Option<&[Scalar]>`. The +**Guarantee.** A node has a **signature** — its `NodeSchema`: each input's scalar +type and firing group, its output record, **and the node's own tunable parameters — +typed, with ranges**, which aggregate into the blueprint's param-space the optimizer +sweeps (C12/C19/C20). The signature is **declared pre-build on the value-empty +recipe** (C19), so the whole interface is legible without building (cycle 0024). +The built node implements `lookbacks()` — the per-input buffer depth, the one +quantity that may depend on an injected param (e.g. an `Sma`'s window = its +`length`), read once at bootstrap to size the windows — and `eval(ctx) -> +Option<&[Scalar]>`. The engine provides read-only, zero-copy windows into each input's SoA ring buffer (`ctx.f64_in(x)[k]`, sized at wiring); a node may *additionally* keep its own mutable series for derived/intermediate state. `None`/Void return = filter / @@ -248,7 +253,8 @@ not part of the C8 dataflow contract — adding it changes no run behaviour. **Realization (cycle 0015 — param declaration).** The tunable-parameter half of this contract is now realized: a node declares its knobs in `schema()` as `params: Vec` (`ParamSpec { name: String, kind: ScalarKind }`), and -`Blueprint::param_space()` aggregates them into one flat, path-qualified list — a +`Composite::param_space()` (cycle 0024; was `Blueprint::param_space()`) aggregates +them into one flat, path-qualified list — a read-only projection of the graph-as-data (C9), mirroring the inline order (C19/C23) so a param's slot matches the later flat-node order. Two refinements to the guarantee's "typed, with ranges": (1) the declaration carries `name` + `kind` @@ -262,10 +268,31 @@ uniqueness is at the slot. A vector knob (`LinComb.weights`) expands to `N` flat `bool` (a `timestamp` knob is a structural axis, C20, never a numeric sweep param). Binding a value to a slot landed in cycle 0016 (#31, see C19/C23); enumerating a sweep (#32) is the deferred next layer. The 0016 binding also moved the param -declaration's *authoring* home: a value-empty blueprint leaf is a `LeafFactory` -recipe whose `params()` is read pre-build, while the built node's `schema().params` -still reports the same slots — the two are kept in lockstep by a per-node test -(a duplication filed as debt, #36). +declaration's *authoring* home into the value-empty recipe, whose `params()` is read +pre-build. In 0016–0023 the built node's `schema().params` still reported the same +slots, kept in lockstep by a per-node test (a duplication filed as debt, #36); **cycle +0024 dissolved this** — the signature is declared *once* on the recipe and the built +node no longer carries `schema()` (#36 closed). See the C8 cycle-0024 realization. + +**Realization (cycle 0024 — the signature lives in the blueprint, #43/#36).** The +node's whole declared interface — its `NodeSchema` (input scalar types + firing, +output record, params) — now lives **once**, on the value-empty recipe +`PrimitiveBuilder` (ex-`LeafFactory`), read pre-build by `param_space()`, the +compile-time structural validation, and the render. This closes two debts: **#43** +(a value-empty recipe used to declare *no* input/output interface pre-build — only +params) and **#36** (params declared twice, recipe vs built node, kept in lockstep +by a per-node test — those 8 tests are deleted, the duplication structurally gone). +The split that makes this work: a node's signature is **fully static** per blueprint +(input kinds/firing, output fields, params — verified across the roster; a variable- +arity node like `LinComb` takes its arity as a *recipe argument*, not an injected +param), so it can be declared without building; the **one** param-dependent quantity, +an input's buffer **lookback** depth, is no longer in the signature but answered by +`Node::lookbacks() -> Vec`, read only by bootstrap to size the windows. +`Node::schema()` is therefore **removed** — the signature is pre-build data, not a +built-node method. `BlueprintNode::signature()` answers uniformly for both arms (a +primitive returns its recipe's schema; a composite *derives* it from the interior: +role kinds in, re-exported field kinds out, aggregated params), so "every node has a +signature in the blueprint" holds for composites too. ### C9 — Fractal, acyclic composition **Guarantee.** A composite is itself a `Node` that wires a sub-graph and exposes @@ -594,7 +621,11 @@ sees a generic vector of typed ranges)" and C19's "factory `params → sized nod *literally*: a blueprint leaf is **value-empty** — `BlueprintNode::Leaf` holds a `LeafFactory { name, params, build }` recipe, not a built node — and the value lives only in the injected vector (no baked default), so the blueprint stays a pure -param-generic recipe. `Blueprint::bootstrap_with_params(Vec)` / +param-generic recipe. (Renamed in cycle 0024: `BlueprintNode::Primitive` holds a +`PrimitiveBuilder { name, schema, build }` — the recipe now carries the full +signature, see the C8 0024 realization; `bootstrap_with_params`/`compile_with_params` +moved onto `Composite` when `struct Blueprint` collapsed into it, see the C19 0024 +realization.) `bootstrap_with_params(Vec)` / `compile_with_params` **build each leaf through its own constructor** (the single sizing/validation gate) from its kind-checked slice **while lowering** (build-then- wire), consuming the vector slot-by-slot in the *same* depth-first walk @@ -607,7 +638,8 @@ the **compilat stays bit-identical** for a given point (C23, the correctness invariant). The value *domain* (e.g. `length ≥ 1`) stays the constructor's own `assert`; the search-range is still the run's (#32/C20). One value-empty leaf detail for C22: the blueprint view (pre-run, param-generic) labels a leaf by **bare -type** (`LeafFactory::label` → `[SMA]`) — the value-bearing `SMA(2)` of C8's render- +type** (`PrimitiveBuilder::label`, was `LeafFactory::label`, → `[SMA]`) — the +value-bearing `SMA(2)` of C8's render- label refinement now appears only in the *compiled* view (built nodes, `Node::label`). **Realization (cycle 0017 — blueprint render = main graph + definitions).** The `aura graph` blueprint view (C9 graph-as-data, #13) renders the **authored @@ -631,6 +663,24 @@ definitions pass recurses). The interactive *enter/focus* counterpart (a composi collapsed to a navigable node) is the playground's, parked as a separate concern (#37); the static CLI keeps the all-at-once definitions form (#38). +**Realization (cycle 0024 — the root is the fully-bound composite; the compilat is +a named type).** `struct Blueprint` is **deleted**: the root graph IS a `Composite`, +and `compile_with_params`/`bootstrap_with_params`/`param_space` are its methods. +What distinguished the root — its bound data sources — is now a property of its input +roles: `Role` carries `source: Option` (`None` = an open interior port, +wired by the enclosing graph; `Some(kind)` = a bound ingestion feed). A composite is +**runnable iff every root role is bound** (C3: sources bind at ingestion only); an +open root role is a compile-time `CompileError::UnboundRootRole`. So the "main graph" +is no longer a separate kind — only the composite all of whose roles are +source-bound, governed by the same conditions as any other node. Compilation now +targets a **named type**: `compile` validates structurally **pre-build** (via +`signature()`, no node constructed — an ill-typed wiring is caught before any build +closure fires) and emits `FlatGraph { nodes, signatures, sources, edges }` — the +C23 compilat, now first-class — which `Harness::bootstrap` consumes (kinds/firing +from the carried signatures, buffer depth from `lookbacks()`). The per-flat-node +signature travels beside the node, so `bootstrap` reads it without a built-node +`schema()` call (which no longer exists, see the C8 0024 realization). + ### C20 — Strategy ↔ harness; the harness is the root sim graph **Guarantee.** A **strategy** is a reusable composite-node blueprint (C9): broker-, data-, and viz-independent, with inputs declared as named **roles**