The single-file ledger had grown to 2968 lines / ~42k tokens, mixing current design law with accreted history: 59 cycle-stamped realization blocks, 18 [HISTORY] passages, 22 supersession markers, and the C10 / C22 / C24 reframe sagas layered several supersessions deep. A code-grounding audit (31 agents, adversarially verified) confirmed 11 defects stated as current truth: stale crate homes from the C28 #288 roster split (cost nodes, PositionManagement, PositionEvent, Session), the renamed InputSpec->PortSpec, the pre-#241 project model in C16 and the open-threads section, a stale HarnessKind retirement deferral in C24, and three C28-internal inconsistencies. New shape, per the ailang precedent: - INDEX.md stays the sole addressable entry point: foundation, external components, a C-id-keyed contract map (one line per contract), and only the genuinely open architectural threads. - contracts/cNN-<slug>.md carries each contract's current truth only: Guarantee / Forbids / Why with ratified refinements integrated, plus a code-anchored Current state. All confirmed defects are fixed here; crate anchors were re-verified against the tree. - contracts/cNN-<slug>.history.md (18 sidecars) and INDEX.history.md preserve every superseded block verbatim, stamps and issue refs intact, under a frozen-record banner. Nothing was deleted: superseded design intent remains an addressable working-tree artifact, off the per-cycle audit walk. - Ledger discipline is now stated in INDEX.md: live files are edited in place at cycle close, superseded text moves verbatim to the sidecar, and a supersession marker in a live file is itself an audit finding. Every contract file was verified against its old text by an independent zero-loss pass (statement-by-statement) plus a code-accuracy spot check; C-ids and contract titles are unchanged, so existing C-id citations in code, tests, and issues resolve as before.
6.0 KiB
C9 — Fractal, acyclic composition
Guarantee. A composite is an authoring-level Node: it declares the same
interface (typed inputs + ≤1 output, C8), is wireable wherever a node is, wires an
interior sub-graph, and exposes one output port carrying a record of 1..K
re-exported fields — signal, combined signal, and (with execution) strategy are
the same abstraction, nestable arbitrarily. A composite is not a runtime
object: the bootstrap compiles it away by inlining its sub-graph into the one
flat instance (C19/C23), so "nestable arbitrarily" and "graph-as-data" hold at the
blueprint (source) level while the running graph is the flat, index-wired
FlatGraph. The dataflow graph is a DAG; the only feedback path is an explicit
delay/state node (the RTL "register", C1). Wiring is written in Rust (builder API,
C17); the built graph is introspectable runtime data and, per C24,
bidirectional — a blueprint is not only emitted as data but is itself a
serializable data value with a load path (data → blueprint → FlatGraph), so
topology is a value the World generates, stores, and reproduces. The Rust builder
stays the primary human/LLM authoring surface; the data form is the machine surface
the World owns.
Forbids. Implicit dataflow cycles (combinational loops); special-casing
"signal-of-signals" as separate mechanics; a composite surviving as a runtime
sub-engine (a Box<dyn Node> driving a nested sub-loop); a blueprint whose
param_space() name projection is non-injective — a path-qualified knob name that
repeats, since it then has no distinct by-name address (C12/C19).
Why. Self-application of one contract gives unlimited composition with no adapter zoo. Acyclicity keeps the synchronous reactive model well-defined; forcing feedback through a visible delay node keeps per-cycle determinism intact (C1) and the one legitimate feedback path explicit. Graph-as-data enables visualization, freezing, and re-parameterization for sweeps, and — round-tripping both ways (C24) — lets the World own topology as content it serializes, loads, and generates rather than as Rust baked into the binary. Inlining, not a runtime sub-object, is what makes the composite boundary free: it keeps the interior transparent to the cross-graph optimiser (C23) and adds no runtime sub-loop the flat model does not need; the boundary dissolves into the raw index wiring the run loop already consumes, and names stay non-load-bearing (C23).
Current state
A composite is Composite (aura-engine::blueprint), a reusable sub-graph fragment
that is never eval'd. It holds interior items, interior edges (local indices),
input_roles: Vec<Role>, and output: Vec<OutField>.
- Multi-output record.
outputisVec<OutField { node, field, name }>— each entry a named projection of one interior(node, output-field). A consumer selects which re-exported field it reads viaEdge::from_field, the same binding that already selects a leaf record's columns (C8). This is the same arity C8 grants a leaf (OHLCV = one port, 5 columns): a multi-line indicator (MACD = macd/signal/histogram) is one record of K fields, one port, one row pereval. A strategy composite is the K=1 case (one bias field, C10). - Named boundary.
input_rolesisVec<Role { name, targets }>— rolerfans its source value into the interiortargets. Inputs and outputs are ordered, positionally-indexed named projections of interior handles. - Per-node param path. A node's surface param name flows from its own instance
name: every node carries a name (default = its lowercased type label, override
via
Node::named,aura-core::node), andparam_space()(aura-engine::blueprint) is uniformly<node>.<param>at every level including the root. A same-type fan-in is distinguished by naming the colliding legs — the same single act that qualifies their param paths. - Param-namespace injectivity. A blueprint compiles only if its
param_space()name projection is injective. A duplicated path is a knob no binding can select alone (C12/C19), rejected asCompileError::DuplicateParamPathcarrying the offending path; the cure is.named(...)on the colliding same-type siblings. One structural check,check_param_namespace_injective(aura-engine::blueprint), runs before name resolution incompile_with_paramsand both binders. Paramless interchangeable same-name sources stay legal (no path, no duplicate). - Names dropped at lowering. Output, role, and node names are non-load-bearing
(C23): they live at the blueprint boundary and in render, and are dropped at
inline —
ItemLowering::Compositecarriesoutput: Vec<(usize, usize)>(raw index pairs) androles: Vec<Vec<Target>>, so the flat graph is name-free and wired by raw index. - Bidirectional data form. The emit half is
model_to_json(aura-engine::graph_model); the load path is realised —aura-engine::blueprint_from_json(aura-engine::blueprint_serde, #155) parses the data form back to a blueprint that compiles to aFlatGraph(C24).
See also
- C1 — determinism, acyclic dataflow, the delay/state register.
- C8 — the node contract: ≤1 output, record columns,
Edge::from_field. - C10 — the strategy as a K=1 bias composite; the cost-model graph.
- C12 — binding by name; why a duplicated knob path is unbindable.
- C17 — the Rust builder as the human/LLM authoring surface.
- C19 — bootstrap: blueprint → instance; construction-phase checks.
- C23 — names non-load-bearing; the cross-graph optimiser; inlining.
- C24 — the blueprint as a serializable, World-owned data value.
- C28 — the layer ladder: construction phase vs the run loop.
History: c09-fractal-composition.history.md