Files
Aura/docs/design/contracts/c19-bootstrap.md
T
claude 8688a60ded docs(ledger): split the design ledger into an INDEX map, per-contract live files, and history sidecars
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.
2026-07-21 16:40:36 +02:00

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-emptyBlueprintNode::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 / NodeSchema recipe and FieldSpec.name as 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