From ff1dce5a9b59eeefa9597757568ce402ea1eff1d Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 5 Jun 2026 12:52:14 +0200 Subject: [PATCH] docs(design): compilation/compilat reading of composites + C23 optimisation contract Settle the Construction-layer milestone's design fork before any spec is written: a composite is a blueprint fragment compiled away by inlining, not a runtime sub-engine Node. - C23 (new): the bootstrap is a compilation -- a param-generic, named blueprint is lowered to a flat, type-erased compilat wired by raw index; composites inline (C9). The compilat is the target of behaviour-preserving optimisation (C1 is the correctness invariant) on two levels: intra-compilat (CSE/DCE) and across the sweep family (loop-invariant prefix hoisting -- the sweep-invariant sub-graph computed once and shared via C11/C12). Passes are deferred follow-on; this milestone builds the representation only. - C9 refined: "a composite is itself a Node" is an authoring-level identity; the nested Box sub-engine reading is explicitly rejected (it keeps the interior opaque to the cross-graph optimiser and adds a runtime sub-loop the flat model does not need). - C19 refined: the blueprint->instance binding is a compilation; "no recompile" means no Rust/cdylib rebuild -- re-deriving an instance per param-set is a cheap graph re-compilation, not a code recompile. - Names are non-load-bearing debug symbols (as FieldSpec.name already is); the wiring resolves by raw index, names survive only for tracing/rendering. - CLAUDE.md domain-invariant 11 pulled coherent with the compilat reading. refs #12 --- CLAUDE.md | 6 ++++ docs/design/INDEX.md | 74 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index d6dd807..61d47b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,6 +114,12 @@ design decision, not a refactor, and belongs in the ledger. blueprint). The harness — sources + strategy + broker node(s) + sinks — is the root sim graph, itself bootstrapped, and is C1's disjoint unit; its structural axes form the experiment matrix, its tuning params the sweep. + **The bootstrap is a compilation** to a flat, type-erased *compilat*, wired + by raw index (composites inline — a composite is an authoring-level node, not + a runtime object; names survive only as non-load-bearing debug symbols, as + today). The compilat is the target of **behaviour-preserving** optimisation, + C1 the correctness invariant — intra-graph (CSE/DCE) and across a sweep family + (sweep-invariant sub-graph computed once, shared via C11/C12). See C9/C19/C23. 12. **The World is the product; the playground is a trace explorer.** Three ontological tiers: a *node* is an open, composable fragment (at most one output, C8); a *harness* is the closed root graph that runs (sources + diff --git a/docs/design/INDEX.md b/docs/design/INDEX.md index f1fc9ed..151201a 100644 --- a/docs/design/INDEX.md +++ b/docs/design/INDEX.md @@ -9,7 +9,10 @@ Provenance: contracts C1–C18 were settled in the initial rough-sketch design interview (2026-06-03), walking the design tree root-to-leaf (C16–C18 and the C10 refinement to a broker-independent position table came in follow-up turns; C10 was later reframed in cycle 0007 — the intent/exposure stream is the primary -output, the position table a derived layer — see C10). +output, the position table a derived layer — see C10). C19–C22 were added as the +construction / World / playground layer; C23 and the C9/C19 compilation +refinements were settled 2026-06-05 for the **Construction-layer** milestone (the +blueprint→compilat reading of composites and graph optimisation). The `CLAUDE.md` **Domain invariants** section is the compressed, always-loaded summary of the subset that agents must never violate; this file is the fuller form with @@ -247,6 +250,18 @@ adapter zoo. Acyclicity keeps the synchronous reactive model well-defined; forcing feedback through a visible delay node keeps the per-cycle determinism intact and the one legitimate feedback path explicit. Graph-as-data enables visualization, freezing, and re-parameterization for sweeps. +**Refinement (Construction-layer milestone, 2026-06-05).** "A composite is itself +a `Node`" is an **authoring-level** identity: a composite declares the same +interface (typed inputs + ≤1 output, C8) and is wireable wherever a node is, but it +is **not** a runtime object. The bootstrap **compiles it away by inlining** its +sub-graph into the one flat instance (C19/C23): the composite *boundary* dissolves +into the raw index wiring the run loop already consumes, and names stay +**non-load-bearing** (informative debug symbols only, as `FieldSpec.name` already +is). So "nestable arbitrarily" and "graph-as-data" hold at the **blueprint +(source)** level; the running graph is the flat, index-wired **compilat**. The earlier reading — *a composite survives as a `Box` +driving a nested sub-engine* — is **explicitly rejected**: it would keep the +interior opaque to the cross-graph optimiser (C23) and add a runtime sub-loop the +flat model does not need. Inlining is what makes the composite boundary free. ### C10 — Strategy output is an intent/exposure stream; position management is a decoupled derived layer; brokers are downstream nodes **Guarantee.** A strategy's primary, backtestable output is **not** an equity @@ -493,6 +508,16 @@ what makes one strategy reusable across a whole sweep and lets the optimizer mutate "the 20" by *rebuilding* an instance (cheap; no recompile, C12) instead of rewriting code. Naming the build phase makes the implicit "wiring" of C7/C12 explicit. +**Refinement (Construction-layer milestone, 2026-06-05).** This binding is a +**compilation**: the param-generic, named blueprint (source) is lowered to a flat, +type-erased **compilat** (C7) **wired by raw index, not by name** +(`Edge { from, to, slot, from_field }`): composite boundaries dissolve entirely, +and field / role names are demoted to **non-load-bearing** debug symbols (as +`FieldSpec.name` already is). "No recompile" above means no **Rust / +cdylib** rebuild (C12/C13: the cdylib loads once); re-deriving an instance per +param-set is a cheap **graph re-compilation**, not a code recompile. Naming the +build phase a compilation makes its successor explicit: the compilat is the target +of behaviour-preserving optimisation (C23). ### C20 — Strategy ↔ harness; the harness is the root sim graph **Guarantee.** A **strategy** is a reusable composite-node blueprint (C9): @@ -585,6 +610,53 @@ Recorded streams are sparse and timestamped (a record per fired cycle, tagged `ctx.now()`), matching a trace of timestamped events (C18). No new contract; the `Harness` API change (observe removed, `run -> ()`) is recorded here. +### 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 **compilat** — 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 compilat). 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, #13) but carrying no run semantics. The compilat 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-compilat** (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 compilats 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. +**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 +compilat 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 compilat is what makes the whole graph optimisable. +**Status (Construction-layer milestone).** The compilat *representation* — +blueprint → inline / compile → flat instance — is the load-bearing substrate built +**first**; the optimisation passes (intra-compilat CSE/DCE, sweep-invariant +hoisting) are **deferred**, behaviour-preserving follow-on work, each gated by a +"compilat with pass ≡ compilat without pass, bit-identical run" test (C1). The +sweep-level pass further presupposes per-node param declarations (C8 — *deliberately +not in the schema yet*) and the sweep orchestration itself (C12/C21). + --- ## Open architectural threads not yet resolved