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;bbac29dand 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
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.
- C8 —
FieldSpec.nameas 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.