Files
Aura/docs/design/contracts/c09-fractal-composition.history.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

6.2 KiB

C9 — Fractal, acyclic composition: history

FROZEN HISTORICAL RECORD. Each block below was true as of its cycle/date stamp and may be superseded; this file is NOT current truth and NOT a grounding surface. Current contract: c09-fractal-composition.md.

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 FlatGraph. The earlier reading — a composite survives as a Box<dyn Node> 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.

Realization (cycle 0018 — composite multi-output record, #40). "Exposes one output" is one output port carrying a record of 1..K re-exported fields, not one field: Composite.output is a Vec<OutField { node, field, name }> (was a single OutPort). Each entry is a named projection of one interior (node, output-field); a consumer selects which re-exported field it reads via the same Edge::from_field that already binds leaf record columns (C8 realization, cycle 0005). This is the same arity C8 already grants a leaf (OHLCV = one port, 5 columns): a multi-line indicator (MACD = macd/signal/histogram) is one record of K fields, one port, one row per eval — C8/C7/C4 untouched, a boundary completion, not a contract change. A strategy composite is simply the K=1 case (one bias field, C10). The re-export names are non-load-bearing (C23): they live at the blueprint boundary and in render (cycle 0022/#46 folds each onto its producing node as a name := … binding; originally [out:<name>] markers, #13) but are dropped at lowering — ItemLowering::Composite.output is Vec<(usize, usize)>, raw index pairs only, so the flat graph is name-free (verified: the compiled-view render stayed bit-identical across this change).

Realization (cycle 0019 — name the composite boundary, #41; param-overlay retired, cycle 0031). The named-projection shape covers the surviving boundary edge-kinds: input_roles is a Vec<Role { name, targets }> (was a bare Vec<Vec<Target>>), alongside output: Vec<OutField>. Each is an ordered, positionally-indexed named projection of interior handles. Param projection is no longer a composite overlay (the index-addressed ParamAlias was retired in cycle 0031): a node's surface param name flows from its own instance name — every node carries a name (default = its lowercased type label, override via .named()), and param_space() is uniformly <node>.<param> at every level including the root. A same-type fan-in is distinguished by naming the colliding legs, the same single act that qualifies their param paths. Like the output and role names, node names are non-load-bearing (C23): they live at the blueprint boundary and in render but are dropped at lowering — the flat graph is wired by raw index. The full composite boundary signature (named inputs, multi-outputs) and the per-node param path are legible without changing the flat graph.

Refinement (param-namespace injectivity, cycle 0032; supersedes the fan-in distinguishability check). A blueprint compiles only if its param_space() name projection is injective — every path-qualified knob name is unique. A duplicated path is a knob no binding can select alone (it has no distinct by-name address, C12/C19), so it is a CompileError::DuplicateParamPath carrying the offending path; the cure is to give the colliding same-type sibling nodes distinct names with .named(...), the same single act that qualifies their param paths. The param-bearing indistinguishable fan-in is one instance of a duplicated path (two default-named same-type legs share a leaf path). signature_of, leaf_has_param, and the fan-in-specific check (check_fan_in_distinguishability / check_composite_fan_in) are retired (cycle 0032), replaced by one structural check_param_namespace_injective over the param_space() names, run before name resolution in compile_with_params and both binders — so the canonical by-name author sees the structural cure rather than a downstream AmbiguousKnob symptom (that BindError arm is retired too: an injective space can never multi-match). Paramless interchangeable same-name sources stay legal (no path, no duplicate). The old signature-collision predicate had extra breadth — it also rejected an asymmetric param/paramless collision and a role-vs-leg collision, neither a path duplicate (each colliding configuration keeps a unique param path, or none). That breadth guarded render identity, dead since the renderer was retired in 0026; both extra rejections are intentionally dropped. A future node-/wiring-name distinguishability check, if ever wanted (e.g. for the WASM graph view's #21 thread), is decoupled from param-space injectivity. Construction-phase only; the flat graph stays name-free (C23).

Refinement (2026-06-29 — graph-as-data round-trips both ways, C24). "The built graph is introspectable runtime data" is now contract-level bidirectional: a blueprint is not only emitted as data (model_to_json, the render half) but is itself a serializable data value with a load path (data → blueprint → FlatGraph), so topology is a value the World generates, stores, and reproduces — see C24. The Rust builder API stays the primary human / LLM authoring surface (C17); the data form is the machine surface the World owns. (Load path realised cycle 0087 / #155, d5602ec: aura-engine::blueprint_from_json.)