The single-file ledger had grown to 2968 lines / ~42k tokens, mixing current design law with accreted history: 59 cycle-stamped realization blocks, 18 [HISTORY] passages, 22 supersession markers, and the C10 / C22 / C24 reframe sagas layered several supersessions deep. A code-grounding audit (31 agents, adversarially verified) confirmed 11 defects stated as current truth: stale crate homes from the C28 #288 roster split (cost nodes, PositionManagement, PositionEvent, Session), the renamed InputSpec->PortSpec, the pre-#241 project model in C16 and the open-threads section, a stale HarnessKind retirement deferral in C24, and three C28-internal inconsistencies. New shape, per the ailang precedent: - INDEX.md stays the sole addressable entry point: foundation, external components, a C-id-keyed contract map (one line per contract), and only the genuinely open architectural threads. - contracts/cNN-<slug>.md carries each contract's current truth only: Guarantee / Forbids / Why with ratified refinements integrated, plus a code-anchored Current state. All confirmed defects are fixed here; crate anchors were re-verified against the tree. - contracts/cNN-<slug>.history.md (18 sidecars) and INDEX.history.md preserve every superseded block verbatim, stamps and issue refs intact, under a frozen-record banner. Nothing was deleted: superseded design intent remains an addressable working-tree artifact, off the per-cycle audit walk. - Ledger discipline is now stated in INDEX.md: live files are edited in place at cycle close, superseded text moves verbatim to the sidecar, and a supersession marker in a live file is itself an audit finding. Every contract file was verified against its old text by an independent zero-loss pass (statement-by-statement) plus a code-accuracy spot check; C-ids and contract titles are unchanged, so existing C-id citations in code, tests, and issues resolve as before.
8.0 KiB
C19 — Bootstrap: blueprint → instance (recursive)
Guarantee. Construction is a distinct phase, recursive at every level. Each
node type has a factory params → sized concrete node (e.g. SMA(length)
sizes its ring buffer). A blueprint is the param-generic, input-role-generic
graph-as-data produced by running a Rust builder (C9); it carries free numeric
params (declared ranges) and free input roles. The bootstrap binds
(blueprint + param-set + data bindings + seed) into a concrete, frozen
instance — buffers sized, topology fixed. This is precisely the "wiring / graph
build" that C7 ("sized at wiring", "topology frozen per sim") and C12 ("params
injected at graph build") reference; the same machinery applies recursively up to
the harness (C20). A sweep builds many instances from one blueprint; instances are
disjoint (C1).
This binding is a compilation: the param-generic, named blueprint (the source)
is lowered to a flat, type-erased FlatGraph (C23) 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
(exactly as FieldSpec.name already is, C8/C23). One narrow exception (#275): a
SourceSpec.role — the lowered bound-Role name — is load-bearing for source
binding, the key Harness::run_bound / bind_sources resolve a keyed source
supply against; every other flat-graph name (edges, ports, composite boundaries)
stays a non-load-bearing debug symbol, and the raw-index positional run path
carries no role. "No recompile" 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.
Forbids. Params that change topology (a topology change is a different blueprint — Fork A, C7 "frozen"); resizing buffers after bootstrap; running a sim against an un-bootstrapped blueprint.
Why. Separating the param-generic blueprint from the param-bound instance is 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 — and naming it a compilation makes its successor explicit: the flat graph is the target of behaviour-preserving optimisation (C23).
Current state
Value-empty recipe. A blueprint leaf is value-empty — BlueprintNode::Primitive
holds a PrimitiveBuilder { name, schema, build } recipe
(crates/aura-core/src/node.rs), not a built node — and the tuning value lives only
in the injected param vector (no baked default), so the blueprint stays a pure
param-generic recipe. This realizes C12's "params injected at graph build" and C19's
factory params → sized node literally. compile_with_params(&[Scalar]) /
bootstrap_with_params(Vec<Scalar>) on Composite
(crates/aura-engine/src/blueprint.rs) build each leaf through its own
constructor — the single sizing/validation gate — from its kind-checked slice
while lowering (build-then-wire), consuming the vector slot-by-slot in the same
depth-first walk param_space() projects, so the two share one traversal and the
value reaches the node at the slot the sweep enumerates. Arity is checked up front
(param_space().len()); a wrong-kind or wrong-length vector is a typed
CompileError::{ParamKindMismatch, ParamArity}. The value domain (e.g.
length ≥ 1) stays the constructor's own assert; the search-range is the run's
(#32/C20, still pending). The lowering is structurally invariant under the injected
point, so the flat graph stays bit-identical for a given point (C23, the
correctness invariant).
The root is the fully-bound composite. There is no separate Blueprint type:
the root graph is a Composite, and compile / compile_with_params /
bootstrap_with_params / param_space are its methods. What once distinguished the
root — its bound data sources — is a property of its input roles: Role carries
source: Option<ScalarKind> (crates/aura-engine/src/blueprint.rs) — None = an
open interior port wired by the enclosing graph, Some(kind) = a bound ingestion
feed (C3: sources bind at ingestion only). A composite is runnable iff every root
role is bound; an open root role is a compile-time CompileError::UnboundRootRole.
compile validates structurally pre-build (via signature(), no node
constructed — an ill-typed wiring is caught before any build closure fires) and
emits the FlatGraph, which Harness::bootstrap consumes (kinds/firing from the
carried signatures, buffer depth from lookbacks()); the per-flat-node signature
travels beside the node, so bootstrap needs no built-node schema() call.
FlatGraph shape. FlatGraph { nodes, signatures, sources, edges, taps }
(crates/aura-engine/src/harness.rs): signatures[i] is the static NodeSchema of
nodes[i] gathered at lowering; sources are the lowered bound roles in
role-declaration order; taps are the declared measurement points (C27), with
interior-composite taps hoisted to the root list.
Structural-constant bind (#55). PrimitiveBuilder::bind(slot, value)
(crates/aura-core/src/node.rs) adds the third param category beside the
topology factory-arg (C7/C19) and the tuning param (the value-pin): a structural
constant. Pinning a value in the injected vector leaves the knob in
param_space (a tuning param the sweep varies); bind instead removes the slot
from param_space entirely — the knob is gone, not fixed. The discriminator is the
deform-vs-tune test: a value whose variation yields another valid point of the
same strategy is a tuning param (stays in param_space); a value whose
variation deforms the strategy into a different one (e.g. the 2 of an
"SMA2-entry" bound to its two-candle construction) is a structural constant
(bound out), so a sweep never enumerates deformed strategies as valid family
members. Mechanically bind shrinks the builder's declared param surface
(schema.params) and wraps its build closure to re-splice the constant at its
original positional slot; both dock sites already key off builder.params(), so the
construction layer is byte-unchanged and chained binds reconstruct the correct
positional vector (each layer computes its slot index relative to the param list it
sees). bind resolves the param name to a position at authoring time (the
by-name authoring address space) and the flat graph stays wired by raw index — the
name never reaches it (C23 unaffected). The complementary export of a named
frozen strategy (all/most knobs bound) as a reusable blueprint value is deferred
(#60); bind ships only the per-knob overlay, no registry (C9/C10 intact).
Graph render. aura graph renders no ASCII: the render path is a deterministic
JSON model serializer (aura_engine::model_to_json,
crates/aura-engine/src/graph_model.rs) plus a self-contained WASM-Graphviz HTML
viewer (aura-cli::render::render_html) that inlines the model and lays it out in
the browser — aura ships no layout engine (C9: graph-as-data). This render surface,
and the debug-symbol labels it draws, belongs to C9/C22.
See also
- C7, C9, C12 — sized-at-wiring, graph-as-data, params-injected-at-build that this phase names.
- C8 — the
PrimitiveBuilder/NodeSchemarecipe andFieldSpec.nameas the non-load-bearing precedent. - C13 — "no recompile" means no cdylib rebuild.
- C20 — the harness is the recursive top of the bootstrap.
- C22 — the graph render / viewer surface.
- C23 — the flat graph as the optimisation target.
- C24 — the deferred named-frozen-blueprint value (#60).
History: c19-bootstrap.history.md