# 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` 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`, and `output: Vec`. - **Multi-output record.** `output` is `Vec` — each entry a **named projection** of one interior `(node, output-field)`. A consumer selects which re-exported field it reads via `Edge::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 per `eval`. A strategy composite is the K=1 case (one bias field, C10). - **Named boundary.** `input_roles` is `Vec` — role `r` fans its source value into the interior `targets`. 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`), and `param_space()` (`aura-engine::blueprint`) is uniformly `.` 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 as `CompileError::DuplicateParamPath` carrying 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 in `compile_with_params` and 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::Composite` carries `output: Vec<(usize, usize)>` (raw index pairs) and `roles: Vec>`, 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 a `FlatGraph` (C24). ## See also - [C1](c01-determinism.md) — determinism, acyclic dataflow, the delay/state register. - [C8](c08-node-contract.md) — the node contract: ≤1 output, record columns, `Edge::from_field`. - [C10](c10-bias-r-cost.md) — the strategy as a K=1 bias composite; the cost-model graph. - [C12](c12-atomic-sim-unit.md) — binding by name; why a duplicated knob path is unbindable. - [C17](c17-authoring-surface.md) — the Rust builder as the human/LLM authoring surface. - [C19](c19-bootstrap.md) — bootstrap: blueprint → instance; construction-phase checks. - [C23](c23-graph-compilation.md) — names non-load-bearing; the cross-graph optimiser; inlining. - [C24](c24-blueprint-data.md) — the blueprint as a serializable, World-owned data value. - [C28](c28-stratification.md) — the layer ladder: construction phase vs the run loop. > History: [c09-fractal-composition.history.md](c09-fractal-composition.history.md)