Implement composite nodes (C9): a Node wiring a sub-graph with one output #12

Closed
opened 2026-06-04 18:52:59 +02:00 by Brummel · 3 comments
Owner

Concern

C9 (design ledger docs/design/INDEX.md) defines fractal composition: "A
composite is itself a Node that wires a sub-graph and exposes one output;
signal, combined signal, and strategy are all the same abstraction, nestable
arbitrarily." This is not implemented. aura-std ships only flat single nodes
(crates/aura-std/src/{sma,sub,add,lincomb,exposure,sim_broker}.rs); a graph is
assembled only as a flat node array via Harness::bootstrap
(crates/aura-engine/src/harness.rs). There is no way to package a sub-graph as
a reusable named node.

Direction

A composite is a type implementing the Node trait
(crates/aura-core/src/node.rs) that internally wires a sub-graph of nodes and
presents one output port (C8: at most one output) plus declared input roles. It
must hold the existing invariants: acyclic (C9), the four scalar kinds at its
edges (C7), and the firing policies on its declared inputs (C6).

Relation to the builder/blueprint layer

C9 also states "Wiring is written in Rust (builder API)" and C19 defines the
blueprint as "the param-generic graph-as-data produced by running a Rust
builder". Claim (unverified, a design fork for brainstorm to settle): a composite
is most naturally expressed through such a builder — wiring sub-nodes by named
handle rather than the raw Edge { from: 0, .. } indices used today — so the
builder/blueprint layer is likely a prerequisite or co-requisite, not a separate
later step. The exact boundary (does the composite own a Blueprint, or is it a
Node delegating to a bootstrapped sub-Harness?) is an open design question.

Why it matters

Without composites, "build me this signal and show its graph" cannot show a
signal's internals — a signal is not yet a named thing with a visible interior.
Composites are the unit that makes signal reuse (C16 three-tier) and the
playground's nested structure view (C22) possible.

Acceptance

  • A composite Node type that wires >=2 sub-nodes and exposes one output.
  • The composite is itself wireable into a Harness like any node (C9 self-application).
  • Acyclicity, scalar-kind, and firing invariants hold at the composite boundary.
  • An aura-std or example composite (e.g. an SMA-cross signal) demonstrates it end-to-end.
## Concern C9 (design ledger `docs/design/INDEX.md`) defines fractal composition: "A composite is itself a `Node` that wires a sub-graph and exposes one output; signal, combined signal, and strategy are all the same abstraction, nestable arbitrarily." This is not implemented. `aura-std` ships only flat single nodes (`crates/aura-std/src/{sma,sub,add,lincomb,exposure,sim_broker}.rs`); a graph is assembled only as a flat node array via `Harness::bootstrap` (`crates/aura-engine/src/harness.rs`). There is no way to package a sub-graph as a reusable named node. ## Direction A composite is a type implementing the `Node` trait (`crates/aura-core/src/node.rs`) that internally wires a sub-graph of nodes and presents one output port (C8: at most one output) plus declared input roles. It must hold the existing invariants: acyclic (C9), the four scalar kinds at its edges (C7), and the firing policies on its declared inputs (C6). ## Relation to the builder/blueprint layer C9 also states "Wiring is written in Rust (builder API)" and C19 defines the blueprint as "the param-generic graph-as-data produced by running a Rust builder". Claim (unverified, a design fork for brainstorm to settle): a composite is most naturally expressed *through* such a builder — wiring sub-nodes by named handle rather than the raw `Edge { from: 0, .. }` indices used today — so the builder/blueprint layer is likely a prerequisite or co-requisite, not a separate later step. The exact boundary (does the composite own a `Blueprint`, or is it a `Node` delegating to a bootstrapped sub-`Harness`?) is an open design question. ## Why it matters Without composites, "build me this signal and show its graph" cannot show a signal's internals — a signal is not yet a named thing with a visible interior. Composites are the unit that makes signal reuse (C16 three-tier) and the playground's nested structure view (C22) possible. ## Acceptance - [ ] A composite `Node` type that wires >=2 sub-nodes and exposes one output. - [ ] The composite is itself wireable into a `Harness` like any node (C9 self-application). - [ ] Acyclicity, scalar-kind, and firing invariants hold at the composite boundary. - [ ] An `aura-std` or example composite (e.g. an SMA-cross signal) demonstrates it end-to-end.
Brummel added the feature label 2026-06-04 18:52:59 +02:00
Author
Owner

Related: #13 — the graph-render feature consumes composites as ascii-dag subgraph (cluster) boxes; the "useful cut" there depends on the named builder/blueprint layer discussed here.

Related: #13 — the graph-render feature consumes composites as ascii-dag subgraph (cluster) boxes; the "useful cut" there depends on the named builder/blueprint layer discussed here.
Brummel added this to the (deleted) milestone 2026-06-05 11:31:46 +02:00
Brummel added this to the Construction layer milestone 2026-06-05 12:58:12 +02:00
Author
Owner

Design fork settled -- commit ff1dce5, ledger C23 + C9/C19 refinements.

A composite is NOT a runtime Box driving a nested sub-Harness; that reading is explicitly rejected (it keeps the interior opaque to the cross-graph optimiser and adds a runtime sub-loop the flat model does not need). A composite is an AUTHORING-LEVEL node: a blueprint fragment the bootstrap COMPILES AWAY BY INLINING into the one flat, type-erased compilat (wired by raw index; field/role names are non-load-bearing debug symbols).

So the blueprint layer is a co-requisite -- the representation IS the composite mechanism -- but only the minimal compile substrate. The ergonomic named-handle wiring API is deferred (out of this milestone's scope).

Acceptance below stands in spirit, read in light of C23:

  • 'a composite Node type' -> a composite blueprint fragment that compiles/inlines;
  • 'wireable into a Harness like any node' -> wireable at the authoring level, inlined at bootstrap.
    The exact API shape comes from the spec (next step).
Design fork settled -- commit ff1dce5, ledger C23 + C9/C19 refinements. A composite is NOT a runtime Box<dyn Node> driving a nested sub-Harness; that reading is explicitly rejected (it keeps the interior opaque to the cross-graph optimiser and adds a runtime sub-loop the flat model does not need). A composite is an AUTHORING-LEVEL node: a blueprint fragment the bootstrap COMPILES AWAY BY INLINING into the one flat, type-erased compilat (wired by raw index; field/role names are non-load-bearing debug symbols). So the blueprint layer is a co-requisite -- the representation IS the composite mechanism -- but only the minimal compile substrate. The ergonomic named-handle wiring API is deferred (out of this milestone's scope). Acceptance below stands in spirit, read in light of C23: - 'a composite Node type' -> a composite blueprint fragment that compiles/inlines; - 'wireable into a Harness like any node' -> wireable at the authoring level, inlined at bootstrap. The exact API shape comes from the spec (next step).
Author
Owner

Resolved by cycle 0012 (spec/plan 0012-blueprint-compile-composites, feat a4fb5d7, audit 9fbc16a, ledger contract ff1dce5).

The design fork this issue flagged — composite-as-runtime-Node vs composite-through-the-builder — was settled toward the latter: a Composite is an authoring-level node, NOT a Box<dyn Node> sub-engine. Blueprint::compile() inlines it by raw-index lowering into the flat (nodes, sources, edges) the unchanged Harness::bootstrap consumes, so the sacrosanct deterministic run loop is byte-untouched (C1) and the composite interior stays fully inspectable for the deferred C23 optimiser. Recursive index rewriting handles nesting; construction faults surface as a typed CompileError; the lowered compilat is validated by bootstrap's existing kind/Kahn check.

Acknowledged debt filed separately: #24 (schema() panic vs typed CompileError) and #25 (schema() re-alloc at resolve). Render follow-up tracked in #13.

Resolved by cycle 0012 (spec/plan 0012-blueprint-compile-composites, feat a4fb5d7, audit 9fbc16a, ledger contract ff1dce5). The design fork this issue flagged — composite-as-runtime-Node vs composite-through-the-builder — was settled toward the latter: a `Composite` is an authoring-level node, NOT a `Box<dyn Node>` sub-engine. `Blueprint::compile()` inlines it by raw-index lowering into the flat `(nodes, sources, edges)` the unchanged `Harness::bootstrap` consumes, so the sacrosanct deterministic run loop is byte-untouched (C1) and the composite interior stays fully inspectable for the deferred C23 optimiser. Recursive index rewriting handles nesting; construction faults surface as a typed `CompileError`; the lowered compilat is validated by bootstrap's existing kind/Kahn check. Acknowledged debt filed separately: #24 (schema() panic vs typed CompileError) and #25 (schema() re-alloc at resolve). Render follow-up tracked in #13.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#12