Files
Aura/docs/design/contracts/c23-graph-compilation.md
T
claude 4ff85b94e5 audit: #331 cycle close — every authored intake gates the root name, cli_fixed_cost re-pinned
Architect drift review (drift_found) resolved fix-path on all three
items. [high] The sweep/walkforward/mc family builders and
validate_and_register_axes read authored envelope files and
put_blueprint'd them ungated — falsifying the C29 exemption's premise
that the store is populated through gated intakes; all four sites now
gate the root name before the family/axis build (placing the gate
after the build produced a misleading unknown-axis error instead of
the gate refusal — RED-verified), with an e2e pin on the synthetic
sweep route. The decision was gate-the-routes, not weaken-the-prose:
the exemption's premise is the invariant, not a wording choice.
[medium] C24 now states the gate rule as the class it is (the op
intake plus every CLI intake reading an authored envelope from a
file; store read-back exempt, C29). [low] C23 reconciles
names-as-debug-symbols with the render name's one operational role
(trace-directory key — never compilation, identity, or execution
semantics).

Ratify: aura-bench cli_fixed_cost baseline re-pinned
(run_line_fnv 6bb0d796f760d140 -> 9bdbc3acf7b2926a). The moved metric
was caused by the #328 tidy (4474814), which switched single-run
manifest defaults from wrapped to raw axis names after that cycle's
bench rerun had already passed; bbac29d and this cycle's HEAD produce
fingerprint-identical record lines (verified with both binaries on
one scratch project), so this cycle only inherits the stale pin. All
five surfaces fingerprint OK after the re-pin.

refs #331, #328
2026-07-25 04:55:12 +02:00

5.6 KiB

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. (Reconciling with the root composite's own render name, #331: that claim stands unweakened — the name never influences compilation, identity, or execution semantics — but at run time, outside compilation, it keys the trace directory (traces/<name>/); that operational role, not any load-bearing weight inside the flat graph, is exactly why the authored-blueprint intakes shape-gate it.) 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 — the bit-identity correctness invariant that licences every rewrite.
  • C8FieldSpec.name as the non-load-bearing precedent and the per-node param declarations.
  • C9 — inlining vs runtime sub-engine.
  • C11, C12 — recorded-stream sharing and the sweep family.
  • C19 — the flat-graph representation this optimises.