# C23 — Graph compilation and behaviour-preserving optimisation **Guarantee.** The bootstrap (C19) is a **compilation**: it lowers a param-generic, named **blueprint** (the authoring source — nodes, composites, strategy, harness; C8/C9/C20) into a flat, type-erased **`FlatGraph`** — the frozen runnable instance (C7) — **wired by raw index, not by name** (`Edge { from, to, slot, from_field }`). Composites are **inlined** at this step (C9): the composite *boundary* dissolves entirely (there is no composite in the flat graph). The blueprint's field / input-role *names* are **non-load-bearing** — the wiring resolves by index, and names survive at most as **informative debug symbols** (exactly as `FieldSpec.name` already is, C8), kept for tracing / rendering (C9 graph-as-data) but carrying no run semantics. The flat graph is then the target of **behaviour-preserving optimisation** — any transform that leaves every observable sink trace bit-identical (C1 is the correctness invariant) — on two levels: - **intra-graph** (within one graph): **common-subexpression elimination** — two identical nodes (same type, same bound params, same input source) merge to one with output fan-out, so fractal composition (C9) costs no redundant compute — and **dead-node elimination** — a node on no path to a sink is dropped. Pure-consumer sinks (C8, no output) are never CSE candidates, so their out-of-graph side effects are preserved by construction. - **across the sweep family** (over the family of flat graphs one blueprint yields under a param sweep, C12): the sub-graph whose nodes carry **no swept param** and whose inputs are **all sweep-invariant** (transitively from the sources — same data window) is **loop-invariant** w.r.t. the sweep and is computed **once**; its output is materialised as a recorded stream (C11) shared read-only across the sweep instances (`Arc<[T]>`, C12). Only the parameter-dependent suffix re-runs per sweep point — loop-invariant code motion over the sweep loop. The compilation also carries one structural gate that **is** load-bearing: **param-namespace injectivity**. `check_param_namespace_injective` runs over the `param_space()` name projection before name resolution, reads the **boundary** name projection — the by-name authoring address space — and rejects a duplicated path (`CompileError::DuplicateParamPath`). This makes the authoring address space injective **without** making names load-bearing in the flat graph: node names qualify the param path at construction and are dropped at lowering (the flat graph stays wired by raw index). **Forbids.** Any "optimisation" that changes an observable trace (it breaks C1); optimising over a representation that is not graph-as-data — an opaque trait-object interior (the rejected nested-composite reading of C9) cannot be analysed or rewritten across its boundary, so it forecloses both levels. **Why.** Determinism (C1) is not merely an audit property; it is the **licence** for these rewrites — a behaviour-preserving transform is only meaningful because "same input → bit-identical run" makes "same result" decidable, and a sweep-invariant sub-graph is reproducible enough to compute once and share. The flat graph representation (C19) is the necessary condition: only a flat, inspectable graph-as-data — with each node's declared param-ranges (C8) — lets the compiler identify identical sub-expressions, dead nodes, and the sweep-invariant frontier. This is precisely why a composite is an inlining (C9) and not a runtime sub-engine: the flat graph is what makes the whole graph optimisable. ## Current state The flat-graph **representation** — blueprint → inline / compile → flat instance — is the load-bearing substrate, built **first**, and is realized: `Composite::compile` emits the first-class `FlatGraph` (C19). The per-node param declarations the sweep-level pass presupposes carry `name` + `kind` (C8); the swept *range* is still pending (#32/C20), as is the sweep orchestration itself (C12/C21). The **optimisation passes** — intra-graph CSE/DCE and sweep-invariant hoisting — are **deferred, behaviour-preserving follow-on work**; no pass exists yet (`Composite::compile` in `crates/aura-engine/src/blueprint.rs` adds none). Each is gated by a "flat graph with pass ≡ flat graph without pass, bit-identical run" test (C1). The one compilation-time structural check that is **live** today is the param-namespace injectivity gate above — `check_param_namespace_injective`, defined in `crates/aura-engine/src/blueprint.rs` and applied at that file's own compile entry points (`compile`, `compile_with_params`, and the `bind` paths) as well as from `construction.rs`'s `finish`, raising `CompileError::DuplicateParamPath`. ## See also - [C1](c01-determinism.md) — the bit-identity correctness invariant that licences every rewrite. - [C8](c08-node-contract.md) — `FieldSpec.name` as the non-load-bearing precedent and the per-node param declarations. - [C9](c09-fractal-composition.md) — inlining vs runtime sub-engine. - [C11](c11-sources-record-replay.md), [C12](c12-atomic-sim-unit.md) — recorded-stream sharing and the sweep family. - [C19](c19-bootstrap.md) — the flat-graph representation this optimises.