From d6f59d7a52eb2c3b932edebcf6d5723faa26e1a6 Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 9 Jun 2026 13:02:42 +0200 Subject: [PATCH] =?UTF-8?q?audit:=20cycle=200024=20(#43,=20#36)=20?= =?UTF-8?q?=E2=80=94=20drift-clean;=20ledger=20reconciled=20to=20the=20pre?= =?UTF-8?q?-build=20signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architect drift review (862882b..1b39093): NO code drift, no contract weakened. C1 (behaviour-preserving: 150 tests green, render goldens byte-identical, no behavioural assertion altered), C8 (sink output: vec![] preserved), C9/C23 (read-only render touches no eval/build; derive_signature's Box::leak is cold-path-only, leaked names non-load-bearing) all hold. Regression scripts: none configured (no-op) — the architect review is the gate. The only drift was design-ledger prose, anticipated and deferred to this audit by the plan. Resolved here (fix path, orchestrator-applied — docwriter is barred from the design ledger): - docs/design/INDEX.md C8 Guarantee: rewritten — a node's signature (NodeSchema) is declared pre-build on the value-empty recipe; the built node implements lookbacks() (the one param-dependent sizing quantity) + eval(); schema() is gone. - INDEX.md C8 cycle-0015 note: Blueprint::param_space -> Composite::param_space; the #36 lockstep-debt clause marked closed by 0024. - INDEX.md: added a C8 cycle-0024 realization (signature lives once on the recipe, the signature/sizing split, #43/#36 closed) and a C19 cycle-0024 realization (struct Blueprint collapsed into the root Composite; Role.source/runnable-iff- bound/UnboundRootRole; FlatGraph as the named compilat). Stale symbol names in the dated 0016/0017 notes annotated in place with their 0024 renames. - aura-std/src/lincomb.rs module doc: Blueprint::param_space -> Composite::param_space. Ledger-gap (four new load-bearing invariants previously unrecorded) closed by the two new realization notes. Green baseline re-confirmed after the edits: cargo test --workspace 150 passed / 0 failed; cargo clippy --all-targets -D warnings clean. refs #43 #36 --- crates/aura-std/src/lincomb.rs | 2 +- docs/design/INDEX.md | 72 ++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 12 deletions(-) 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**