977 Commits

Author SHA1 Message Date
Brummel 3aa124f815 plan: 0021 fan-in distinguishability
Four tasks: shared recursive signature_of helper (aura-engine); CLI
source-derived fan-in identifiers + sma_cross aliases + render goldens
(aura-cli); the IndistinguishableFanIn construction constraint + engine fixture
aliases (aura-engine); the C9 ledger refinement. Ordered so the workspace test
suite stays green at every task boundary.

Refs #44
2026-06-08 15:35:57 +02:00
Brummel 22a690361a spec: 0021 refine fan-in constraint to a param-aware criterion
A signature collision is a fault only when at least one colliding source
carries an unaliased param slot — the configuration axis the author left
unnamed (sma_cross's length). Equal-signature sources with no param are
genuinely interchangeable (fan_composite's Pass/Pass, add(price,price)) and
stay legal; the render uses the positional letter for them. Keeps the
construction blast radius to the param-bearing alias-less fixtures.

Refs #44
2026-06-08 15:21:51 +02:00
Brummel 14d91035ec spec: 0021 fan-in distinguishability
A construction-phase constraint (CompileError::IndistinguishableFanIn, raised in
inline_composite) makes a fan-in node whose sources have identical recursive
signatures illegal; the definition view then renders each fan-in input as the
shortest unique prefix of its source's signature (replacing positional #A/#B);
and the sma_cross fixture gains its missing fast/slow aliases.

Refs #44 — scope extended from render-only to a construction-phase invariant
during design (the positional #A was a symptom of unnamed, indistinguishable
fan-in sources, which is the actual fault to forbid).
2026-06-08 14:08:59 +02:00
Brummel b73d7076c3 feat(aura-cli): TTY-gated ANSI edge colouring + blank line after composite signature
Two presentation refinements to the `aura graph` views, render-only (no
engine change, C8/C9/C23 untouched).

Edge colouring: thread a `Color { Plain, Ansi }` through render_flat /
render_definition / render_blueprint / render_compilat. `Ansi` uses
ascii-dag's colored scanline pass (per-edge palette colours) so crossing
edges stay traceable; `Plain` is the existing monochrome `g.render()`.
The CLI selects `Ansi` only when stdout is a TTY (`IsTerminal`), so a
redirect to a file or pipe stays byte-clean — `aura graph | …` carries no
escape codes. ascii-dag colours only edges; node-label text is unaffected.

Tests render `Color::Plain` explicitly, so both goldens stay byte-stable
(compiled_view_golden remains the C23 guard); a new test pins that `Ansi`
emits escapes and `Plain` does not, and that labels survive colouring.

Blank line: render_definition now separates the typed signature title from
the graph body with an empty line. blueprint_view_golden re-captured.

Considered but rejected this round: per-node role highlighting of the
signature identifiers — needs either an ascii-dag fork (node colouring is
hard-disabled upstream) or a per-graph string-surgery pass that does not
generalise beyond hand-known labels. Only edge colouring survives here.
2026-06-08 12:08:13 +02:00
Brummel 32ac8a30ea feat(aura-cli): de-prefix input-role markers ([in:price] -> [price])
Symmetric with the output de-prefix shipped in 2f49165: the composite
definition render now shows input roles as bare [<name>] markers, matching
the [<name>] outputs. One-token change in render_definition's input-role
loop (format!("in:{}", role.name) -> role.name.clone()); the two render
doc comments + the blueprint_view_golden / needle / MACD-render asserts
updated. Render-only; compiled_view_golden byte-identical; aura run --macd
unchanged.

refs #41
2026-06-08 10:51:50 +02:00
Brummel efbe0f4a66 audit: cycle 0020 tidy (clean) — typed-signature render, render-only
Architect drift review over ebe2e71..HEAD (spec 42eb627, plan 71f5033,
impl 2f49165). No regression scripts configured (profile regression/
architect_sweeps both empty) — the architect is the sole gate.

Result: clean. Render-only confirmed against the diff:
- C23 intact — compiled_view_golden is NOT in the diff (byte-identical);
  render_compilat untouched; no role/param/output name reaches the compilat.
- C9 — the new signature/leaf_label/kind_str helpers are pure reads over the
  blueprint (shared borrows of params/nodes/edges/input_roles/output/factory
  params); no mutation, no build.
- C22 honoured — the richer pre-run definition (typed signature + folded
  leaf labels + ordered input stubs, [param:*] markers removed) surfaces more
  authored structure with no run change (aura run --macd unchanged).
- C8/C7/C4 untouched. No ledger change warranted: render specifics live only
  in graph.rs, and the ledger nowhere asserts a pre-build factory output
  interface (the #43 gap is consistent with the ledger, not drift). The
  node.rs LeafFactory::label doc fix (flat renderer since 0017) is accurate.

Two debt-low items carried, not fixed (deliberate over-provision the spec
called "defined for completeness"):
- leaf_label's multi-param `, `-join and both-present `; ` arm are untested
  (no node carries 2 aliased params or a param + >1 input);
- signature's `?` malformed-alias fallback is a defensive non-panic guard,
  unpinned by test.
No dead code, no weakened assertion (the five name:->name( repins are
equal-strength), no lying comment, no stray TODO.

drift-clean, not a milestone close.
2026-06-08 10:35:58 +02:00
Brummel 2f4916596d feat(aura-cli): composite definition renders as a typed signature
Refines #41's blueprint-definition render. The [param:<name>] marker nodes
#41 added bloated the ASCII MACD graph (three extra nodes + fan-in edges);
this moves the interface into the title line and folds names into the leaf
labels that already exist:

- title is the composite's typed signature, e.g.
  macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram)
  (param kinds from the aliased leaf's factory params via a new
  ScalarKind->lowercase kind_str; output NAMES only — output kinds need a
  pre-build factory interface, deferred to #43)
- leaf labels fold aliased param names: [EMA] -> [EMA(fast)]
- unnamed interior input slots render as ordered stubs: [Sub] -> [Sub(#A,#B)]
  (count/order derived render-side from c.edges() + c.input_roles(), no
  engine schema needed; arity-1 nodes get no stub)
- outputs de-prefixed: [out:macd] -> [macd]; the [param:*] marker nodes are
  gone
- two stale doc comments corrected: render_definition's [param:] prose and
  node.rs LeafFactory::label (the renderer went flat in 0017, so the cited
  ascii-dag cluster-garble rationale no longer applies — wide labels are
  safe; the factory label stays bare because alias names are composite-level)

Render-only: no engine/ParamAlias/param_space change. compiled_view_golden
byte-identical (C23 guard — verified untouched in the diff); aura run --macd
deterministic and unchanged (total_pips 0.1637945563898923, 3 sign flips).

The title format change repinned five .matches("name:")->.matches("name(")
definition-count asserts (sma_cross/outer/inner/dup/macd); the stub
separator is "," (no space) to match the [Sub(#A,#B)] surface.

Verification (orchestrator-run): cargo build/test/clippy --workspace
-D warnings all green; the redesigned MACD render confirmed by eye.

refs #41 #43
2026-06-08 10:33:13 +02:00
Brummel 71f50330db plan: 0020 composite signature render
Two-task render-only plan executing spec 0020:

- T1 (graph.rs + node.rs): add `signature` (typed title line, with a new
  ScalarKind->lowercase `kind_str` match — none exists today, {:?} gives
  PascalCase) and `leaf_label` (fold aliased param names + ordered
  input-slot stubs); edit render_definition (title->signature,
  leaf->leaf_label, drop [param:*] loop, de-prefix outputs); fix two stale
  doc comments (the render_definition [param:] description + the
  node.rs:93-101 cluster rationale). Gate: cargo build -p aura-cli (the
  goldens go red until T2 re-captures; cargo test is NOT a T1 gate).
- T2 (main.rs): repin the FIVE title asserts name:->name( (the blast
  radius: sma_cross/outer/inner/dup/macd, four beyond the spec's named
  one); update the definition needle ([Sub]->[Sub(#A,#B)], [out:cross]->
  [cross]); rewrite the MACD render assertions (drop [param:*], add the
  signature + [EMA(fast)] + [Sub(#A,#B)] + [macd] + [param:/[out: absence);
  re-capture blueprint_view_golden wholesale; confirm compiled_view_golden
  byte-identical (C23); full --workspace triple -D warnings.

Render-only: no engine/ParamAlias/param_space change; compiled_view_golden
byte-identical; aura run --macd unchanged. Output kinds deferred to #43.

refs #41 #43
2026-06-08 10:27:38 +02:00
Brummel 42eb62771d spec: 0020 composite signature render
Render-only refinement of #41's composite blueprint-definition view. The
[param:<name>] marker nodes #41 added bloat the ASCII graph; this moves the
interface into a typed signature title line and folds names into the node
labels that already exist:

- title: macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram)
  (param kinds from factory().params()[slot].kind; OUTPUT NAMES ONLY)
- leaf labels fold aliased param names: [EMA] -> [EMA(fast)]
- unnamed interior input slots as positional stubs: [Sub] -> [Sub(#A,#B)]
  (count/order derived render-side from c.edges() + c.input_roles())
- outputs de-prefixed: [out:macd] -> [macd]; [param:*] marker nodes removed
- fix stale node.rs:93-101 cluster rationale (renderer went flat in 0017)

Output KINDS are deferred: a value-empty LeafFactory exposes no output
schema pre-build (the mc_4/#42 finding), so the typed arrow (-> macd:f64)
waits on #43. Render-only: no engine/ParamAlias/param_space change;
compiled_view_golden byte-identical (C23); aura run --macd unchanged.

Gates green: feature-acceptance (the worked MACD before->after is the
evidence), placeholder scan clean, parse-every-block no-op (no
spec_validation parser), grounding-check PASS (10 assumptions ratified;
both load-bearing premises — no pre-build factory output schema, flat
renderer — confirmed).

refs #41 #43
2026-06-08 10:19:42 +02:00
Brummel ebe2e71349 audit: cycle 0019 (#41) — drift-clean; C9 boundary-uniform realization note
Architect drift review over 8bc429f..HEAD (spec 03f2dc4, plan c9115e3,
impl 41cbb55; plus the in-range #42 fieldtest port 241bb62). No regression
scripts configured (profile regression/architect_sweeps both empty) — the
architect is the sole gate.

Result: clean. C23 holds — Role.name / ParamAlias.name / OutField.name are
all dropped at lowering; compiled_view_golden byte-identical (untouched in
the diff); the param_space slot-order anchors green and unmodified. Param
aliasing is a pure naming overlay (relabel in place, slot order/arity/kind
unchanged), confirmed by the new relabel/unaliased/partial tests. C8/C7/C4/
C9 untouched (authoring-surface legibility only); BadInteriorIndex reused,
no new variant. The #42 port is faithful (assertions strengthened, not
gutted).

One drift item, resolved here: the 0018 C9 realization note carried a stale
"Input-role and param names ... remain index-only pending #41" sentence.
#41 has landed, so this commit replaces it with a cycle-0019 realization
note recording that all three composite-boundary edge-kinds (input roles /
params / outputs) are now uniform named projections of interior handles
(Role / ParamAlias / OutField), names non-load-bearing and dropped at
lowering — the prose now matches the shipped code.

drift-clean, not a milestone close (no milestone fieldtest run here).
2026-06-08 02:24:15 +02:00
Brummel 41cbb5506f feat(aura-engine): name the composite boundary — input roles + param aliases
Brings the other two composite-boundary edge-kinds to the named-projection
shape #40 gave outputs. input_roles changes from a bare Vec<Vec<Target>>
to Vec<Role { name, targets }> (rendered [in:<name>]); a composite gains
params: Vec<ParamAlias { name, node, slot }> that relabels an interior
leaf param slot's surface name in param_space() (rendered [param:<name>]).

Param aliasing is a PURE NAMING OVERLAY, not curation (the load-bearing
decision, per spec 0019): every interior param slot stays in param_space()
and sweepable; the alias only relabels in place, never reorders or hides.
Proven empirically — the MACD run is byte-identical (total_pips
0.1637945563898923, 3 sign flips), only the param labels improved:
param_space() now surfaces [macd.fast, macd.slow, macd.signal] instead of
three indistinguishable macd.length, and the manifest reads ema_fast/
ema_slow/ema_signal.

C23 honoured: role/param/output names are non-load-bearing debug symbols
dropped at lowering; identity is positional (role index, param slot,
output field). compiled_view_golden is byte-identical (verified: the
golden region is untouched in the diff). An out-of-range alias (missing/
non-leaf node or slot past the leaf's param count) is rejected at
compile_with_params as BadInteriorIndex, mirroring the output range-check
(no new variant). Orthogonal to #36 — purely additive at the composite
level.

Aliasing is demonstrated on the CLI MACD site only (the spec's worked
example + a new E2E test macd_param_space_surfaces_the_three_named_aliases);
sma_cross, the engine test fixtures, and the construction-layer fieldtests
get the forced role-name + empty params, so the param_space C23 anchor
goldens (param_space_mirrors_compiled_flat_node_param_order + siblings)
stay byte-identical.

Verification (orchestrator-run, not trusted from the agent report):
cargo build/test/clippy --workspace -D warnings all green (engine 66,
cli 12); the separate-workspace construction-layer fieldtest crate builds
(guards the #42 latent-drift recurrence); compiled_view_golden + MACD
determinism unchanged.

Two faithful repairs to the plan's literal test/code bodies, no semantic
change: the out-of-range test uses .err()/Some(BadInteriorIndex) (the Ok
arm Vec<Box<dyn Node>> is not Debug, so .unwrap_err() would not compile),
and the inline_composite destructure binds params as `param_aliases` to
avoid shadowing the injected `params: &[Scalar]` arg.

closes #41
2026-06-08 02:21:30 +02:00
Brummel c9115e3e0f plan: 0019 name composite boundary
Four-task plan executing spec 0019, sequenced like cycle 0018 (type-shape
change rippling engine -> CLI):

- T1 engine type migration, behaviour-preserving: Role/ParamAlias types,
  Composite struct/new/accessors, inline_composite role-walk, lib.rs
  re-export, all engine test Composite::new sites. Gate per-crate
  (cargo test -p aura-engine, NOT --workspace; aura-cli compiles in T3).
  The new params field is dormant -> param_space byte-identical, C23
  anchor goldens green.
- T2 param aliasing RED-first: collect_params alias relabel + inline_composite
  alias range-validation (-> BadInteriorIndex, reusing the variant), with
  4 new unit tests (relabel, out-of-range, unaliased regression, partial).
- T3 CLI: render_definition [in:name]/[param:name] markers; MACD author
  site (named role + fast/slow/signal aliases, the worked example);
  sma_cross named role only; blueprint_view_golden re-capture;
  compiled_view_golden stays byte-identical (C23 guard).
- T4 fieldtests sweep (separate workspace root -> explicit cargo build
  gate, guarding the #42 latent-drift recurrence) + full --workspace
  triple (-D warnings).

Aliasing demonstrated on the CLI MACD site only (the spec's evidence);
every other composite site gets the forced role-name + empty params, so
the param_space C23 anchor goldens stay byte-identical.

refs #41
2026-06-08 02:08:57 +02:00
Brummel 03f2dc4c51 spec: 0019 name composite boundary
Sibling of #40 (composite output as a named multi-field record). Brings
the other two composite-boundary edge-kinds to the same named-projection
shape: input roles (Vec<Role { name, targets }>, rendered [in:<name>])
and a param-alias overlay (Vec<ParamAlias { name, node, slot }>) that
relabels an interior leaf param slot's surface name in param_space().

Load-bearing decision recorded in the spec: param aliasing is a pure
naming overlay, NOT curation — every interior slot stays in param_space()
and sweepable; the alias only relabels in place, never reorders/hides
(forced by the issue's "legibility not capability" framing + C23). The
injected point vector is unchanged; MACD surfaces macd.fast/slow/signal
instead of three indistinguishable macd.length.

Gates green: feature-acceptance (worked MACD before->after is the
evidence), placeholder scan clean, parse-every-block no-op (profile
declares no spec_validation), grounding-check PASS (7 load-bearing
assumptions each ratified by a named currently-green test; compiled_view
+ param_space slot-order goldens stand as the C23 regression anchors).

refs #41
2026-06-08 01:58:44 +02:00
Brummel 241bb626e6 fix(fieldtests): port construction-layer fixtures to current authoring API
The four standalone construction-layer fieldtest bins (mc_1..mc_4) no
longer compiled: they are their own workspace root, so the engine's
`cargo build --workspace` never touched them and two waves of API drift
went latent.

Two breakage classes, both confined to the fixture files (no engine
change):

- Stale cycle-0016 authoring. `BlueprintNode::from(<Node>::new(..))`
  relied on the `From<NodeType>` impls retired in the value-empty
  migration; only `From<LeafFactory>` survives. Ported to
  `<Node>::factory().into()` — nodes are value-empty blueprint items,
  params bound at compile. Knock-on in mc_4 `describe_node`: a blueprint
  leaf is now a `&LeafFactory` with no pre-build schema, so leaf
  introspection reports the render label + declared param count (inputs
  / outputs exist only on the compiled flat node, post-build).

- Cycle-0018 (#40) OutPort -> OutField slice drift. `Composite::output()`
  now returns `&[OutField]`; the mc_4 read sites were still treating it
  as a single port. Ported to index the record; the walk assertion now
  also pins the arity (one re-exported field), which is the #40 shape.

Faithful ports, not assertion-gutting: each fixture still exercises its
axis (composite build+run / miswire render / nested composite / graph
introspection), verified by running all four bins to their `OK:` line.
One genuine semantic drift flagged in mc_2: the blueprint-view label is
now the bare value-empty type ("SMA"), so two same-type leaves share a
label and are disambiguated by the slot/edge table; the valued
SMA(2)/SMA(4) label lives on the compiled view. The fixture asserts what
the blueprint view actually exposes and keeps the load-bearing edge-table
observability check untouched.

closes #42
2026-06-08 01:23:47 +02:00
Brummel 8bc429f367 audit: cycle 0018 (#40) — drift-clean; C9 multi-output realization note
Architect drift review over a4cfe5c..HEAD (covers the previously-unaudited
EMA node and MACD PoC plus the #40 composite multi-output cycle): status
clean. C8/C7/C4 preserved (one port, one row per eval, K co-fresh base
columns — structurally identical to OHLCV); C23 honoured (ItemLowering::
Composite.output is Vec<(usize,usize)>, no name in the compilat; the
compiled-view render stayed bit-identical). EMA and MACD PoC carry no
ledger drift.

Resolution of the three minors:
- Ledger lagged the new behaviour: added a "Realization (cycle 0018)" note
  under C9 recording that a composite's output is now a named K-field record
  (Vec<OutField>), the same arity C8 already grants a leaf, names dropped at
  lowering (C23). Amended here (ledger amendment is the audit's job).
- blueprint.rs module-doc / Composite struct-doc / new-doc still said "one
  output port" singular — updated to "output record".
- Non-workspace fieldtest From<Sma> debt is pre-existing (cycle-0016) and
  already tracked as #42; not re-filed.

No regression scripts configured (profile regression: []), so the architect
review is the sole gate. Carry-on: the next natural iteration is the #41
legibility sibling (input-role + param naming at the composite boundary).
2026-06-08 01:05:41 +02:00
Brummel 784e6c917a feat(aura-engine): composite output is a named multi-field record
Replace a composite's single output port (OutPort { node, field }) with
an ordered, named record (Vec<OutField { node, field, name }>). A
composite can now re-export K interior fields as one output; a consumer
selects one via the existing Edge::from_field — the same field-wise
selector that already works for multi-output leaf producers (OHLCV).

Why: multi-line indicators (MACD, Bollinger, Stochastic, Ichimoku) could
not be authored as a composition and re-exported as a unit — only one
line escaped the boundary. The MACD PoC was forced to expose only the
histogram. This lifts the three `field == 0` / `from_field == 0` caps at
the composite boundary (inline_composite nested arm, rewrite_edge
composite arm), range-checking the index instead.

Boundary completion, not an invariant change:
- C8/C7/C4 untouched — one port, one row per eval, K base columns (a
  3-line MACD is identical in kind to OHLCV).
- C23 honoured — re-export names live at the blueprint boundary only and
  are dropped in the compilat (raw index wiring). compiled_view_golden
  stayed byte-identical (the regression guard): no name leaked into the
  flat graph.

The MACD PoC now re-exports macd / signal / histogram as a record; the
strategy still trades the histogram, selected via from_field: 2. The
render shows K [out:<name>] markers per multi-output composite (input
roles stay [in:<index>] — naming them is the dependent #41).

Output naming ships with the capability (OutField is born name-ready) so
#41 — composite param aliasing + input-role naming — does not reopen the
type.

Verification: cargo build/test/clippy --workspace -- -D warnings all
green; aura-engine 62 tests (+3 capability, +1 through-run E2E), aura-cli
unchanged-count green; compiled_view_golden byte-identical.

Known debt (out of scope, pre-existing): the non-workspace construction-
layer fieldtests (fieldtests/milestone-construction-layer/mc_1..mc_4)
carry their OutField sweep but still do not compile standalone — they use
the Sma::new / BlueprintNode::from(Sma) API retired in the cycle-0016
value-empty migration, never propagated to these fixtures. Tracked as a
follow-up.

closes #40
2026-06-08 01:00:04 +02:00
Brummel 2c33a8d74f plan: 0018 composite multi-output record
Four tasks: engine type migration (behaviour-preserving, caps kept) →
cap-lift with RED-first capability tests → CLI render + author sites +
golden re-capture → non-workspace fieldtest sweep + workspace gate.

refs #40
2026-06-08 00:47:31 +02:00
Brummel 9ddeab8b7e spec: 0018 composite multi-output record
Composite::output: OutPort -> Vec<OutField { node, field, name }> so
multi-line indicators (MACD/Bollinger/Stochastic/Ichimoku) can be
authored as a composition and re-exported as a named record. Output
half only; param/input-role naming is the dependent sibling.

refs #40
2026-06-08 00:37:37 +02:00
Brummel d8b2a2880d feat(aura-cli): MACD strategy PoC over the EMA node
Author MACD as a nested composite -- price -> fast/slow EMA -> Sub (MACD line) -> signal EMA -> Sub (histogram), the histogram exposed as the single output the strategy trades. A richer fixture than sma_cross: an EMA-of-EMA chain with interior fan-out (the MACD line feeds both the signal EMA and the histogram), exercising the nested-composite render shipped in cycle 0017.

It runs over the real path: the composite blueprint is compiled to a flat harness via compile_with_params + Harness::bootstrap (the same machinery as the SMA compiled view), not hand-flattened. New surfaces: aura run --macd (runs the strategy, prints metrics) and aura graph --macd [--compiled]. main's arg parsing is rewritten to a whole-argv match, preserving the #16 strict contract (trailing token / unknown subcommand -> usage, exit 2). MACD gets its own longer synthetic stream because the SMA-seeded EMAs warm up; the SMA sample's stream and goldens are untouched.

This PoC makes two parameter-space gaps concrete for the milestone: the strategy's param_space is [macd.length, macd.length, macd.length, scale] -- three identical names distinguishable only by slot (the named-param case, blueprint #30) -- and a composite exposes only one output port, so the MACD/signal/histogram tuple cannot all be tapped for display (the tuple-output case). The compiled view's valued labels (EMA(2)/EMA(4)/EMA(3)) are what currently disambiguate the three EMAs.

Tested: MACD compiles from the nested composite and runs deterministically (C1), the trace is non-trivial (>=1 exposure sign flip), and the blueprint view renders the composite definition once.
2026-06-07 23:20:42 +02:00
Brummel ae1d4564f5 feat(aura-std): EMA node (SMA-seeded, O(1) per cycle)
Add Ema, the std-lib's first recursive/stateful producer: exponential moving average with alpha = 2/(length+1). The building block MACD (and DEMA, MACD-histogram, etc.) needs, and a worked example of a node whose param sizes its math, not its input window.

Seeding is ratified as SMA-seeded (ta-lib convention): the EMA stays silent until it has seen length samples, seeds with their SMA, then runs the recurrence. Chosen over a first-value seed for three substantive reasons -- warm-up consistency with Sma (both emit None until length samples), parity with the EMA traders expect (ta-lib MACD seeds this way), and statistical honesty (no average claimed from a single observation). A first-value seed was the proof-of-concept convenience; rejected.

Performance: recursive state means lookback = 1 (length sizes alpha, not the window); eval is O(1) time and state via ema += alpha*(x - ema), with no hot-path allocation (the output buffer is reused).

Tested: warm-up-then-smooth (a step input separates it from a plain SMA), length-1 identity, label, and factory/schema agreement.
2026-06-07 23:20:25 +02:00
Brummel a4cfe5c53f audit: cycle 0017 (#38) — render_flat dedup + ledger #38 realization note
Architect drift review of the #38 cycle (a70c5fa..cd31447) against the design
ledger. No ledger-contract drift: C23 holds (render_compilat byte-identical,
compiled_view_golden green), C9/C12 honoured (the new main-graph + where:-
definitions view renders a composite as an opaque authoring-level node with its
body defined once — a faithful blueprint/source view vs. the inlined compiled
form), the nested-composite unimplemented! is genuinely removed and tested, and no
orphaned references to the removed ItemDisplay/producer_id/consumer_ids remain in
crates/ (only frozen historical plan docs mention them).

Two debt items, both fixed here (not carried):

- [medium] crates/aura-cli/src/graph.rs — render_compilat open-coded the same
  Graph::with_mode/add_node/add_edge/render idiom the new render_flat helper wraps.
  Fixed: render_compilat now builds its edge-pairs (its Edge structs + source
  targets) and delegates to render_flat, so render_flat is the single flat-build
  point shared by all three render functions. Behaviour-preserving — same node and
  edge insertion order — verified by compiled_view_golden staying byte-identical.

- [low] the retired cluster-box blueprint-render model and its substantive cause
  (ascii-dag 0.9.1's subgraph /2 centering bug; flat-only being the fix) lived only
  in the superseded spec 0013, not in the durable contract layer. Fixed: added a
  "Realization (cycle 0017 — blueprint render = main graph + definitions)" note to
  the ledger (INDEX.md, under the C22 blueprint-view detail) recording the
  opaque-node + where:-definitions model, the authoring-vs-compiled split, the
  ascii-dag-bug rationale, the scale/render-once/nested benefits, and the #37
  playground (enter/focus) counterpart vs. #38 static CLI form.

regression: profile declares no regression scripts and no architect_sweeps — the
architect review is the sole gate. No baseline to update.

Cycle 0017 is drift-clean after these fixes. NOT a milestone close — that needs the
end-to-end milestone fieldtest, still deferred until the sweep root (#32-#33) lands.
2026-06-07 22:29:42 +02:00
Brummel cd31447d98 feat(aura-cli): blueprint view = main graph + composite definitions (#38)
Rewrite the `aura graph` blueprint view from ascii-dag subgraph cluster boxes to
a "program with subroutines" model: a flat main graph that wires the harness with
each composite shown as a single opaque node, plus a `where:` section that defines
each distinct composite type once (its interior with [in:k]/[out] port markers).
`render_compilat` (flat, fully-inlined, C23) is byte-for-byte unchanged.

Why (not effort):
- Correctness under width. ascii-dag 0.9.1's subgraph level-centering rounds
  sibling x-positions with `/2`, overlapping wide sibling labels (a
  width/parity-sensitive bug with no config/padding dodge that survives
  unequal-width siblings; verified by reproduction). The flat layout is
  collision-free; this view renders only flat graphs, so the bug cannot arise.
- Scale. A composite is one opaque node in the main graph, so blueprint size
  tracks top-level wiring, not inlined node count — real strategies stay
  displayable.
- Render-once. A composite reused N times has its body rendered once
  (collect_distinct_composites dedups by name(), recursively).
- Removes the nested-composite `unimplemented!` — the definitions pass recurses,
  so nested composites render as opaque nodes with their own definitions.

Mechanics: ItemDisplay/producer_id/consumer_ids are gone (a composite is now one
display node, so boundary fan-resolution collapses); new render_flat (the same
no-subgraph idiom render_compilat uses), collect_distinct_composites, and
render_definition. The now-unused `Target` import is dropped.

Divergence from the plan's literal text: the plan's `collect_distinct_composites`
used a nested `if let { if .. }`; this toolchain's clippy (collapsible_if, rust
1.94) rejects it under -D warnings, so it is the behaviour-identical let-chain form
`if let BlueprintNode::Composite(c) = item && !seen.contains(&c.name())`.

Tests: the two cluster-asserting blueprint tests are replaced by four behavioural
tests (opaque-node main graph; defines-each-composite-once; nested-renders-without-
panic; reused-composite-defined-once) plus a recaptured blueprint_view_golden
(captured verbatim from `aura graph`, not hand-authored). The four must-stay-green
tests pass; compiled_view_golden is byte-identical (render_compilat untouched).
Verified: `cargo test --workspace` all green, `cargo clippy --workspace
--all-targets -D warnings` clean.

The interactive enter/focus navigation counterpart is the playground's, tracked
separately as #37.

closes #38
2026-06-07 22:25:48 +02:00
Brummel 870dfcec5b plan: 0017 blueprint view as main graph + composite definitions
Two tasks, CLI-render-only (graph.rs + main.rs tests):
- Task 1: rewrite render_blueprint to the main-graph + where:-definitions model
  (composite = opaque node; collect_distinct_composites recursive/deduped by name;
  render_definition with [in:k]/[out] port markers); drop ItemDisplay/producer_id/
  consumer_ids and the nested-composite unimplemented!; behavioural tests RED->GREEN
  (opaque-node, defines-once, nested-no-panic, reuse-defined-once).
- Task 2: recapture the blueprint golden from `aura graph` stdout; full-workspace
  test + clippy gate. render_compilat untouched (its golden stays byte-identical).

refs #38
2026-06-07 22:20:28 +02:00
Brummel 2d3792e45f spec: 0017 blueprint view as main graph + composite definitions
Redesign the `aura graph` blueprint view (render_blueprint) from ascii-dag
subgraph cluster boxes to a "program with subroutines" model: a small main
graph that wires the harness with each composite shown as a single opaque
node, plus a `where:` section rendering each distinct composite type's
interior once. render_compilat (flat, fully-inlined, C23) is unchanged.

Design ratified in-session. The 3a-vs-3b fork was resolved by medium: the
interactive enter/focus model (3a) is the playground's, parked as #37; the
static main+definitions model (3b) is the CLI's, this cycle.

Substance:
- Correctness under width: ascii-dag 0.9.1's subgraph level-centering rounds
  sibling x-positions with `/2`, overlapping wide sibling labels (a
  width/parity-sensitive bug with no config/padding dodge that survives
  unequal-width siblings). The flat layout is collision-free; the new model
  renders only flat graphs, so the bug cannot arise.
- Scale: a composite is one opaque node in the main graph, so blueprint size
  tracks top-level wiring, not inlined node count — real strategies stay
  displayable.
- Render-once: a composite reused N times has its body rendered once.
- Removes the nested-composite `unimplemented!` at graph.rs:74-77 (the
  definitions pass is recursive).

Defaults baked (not forks): composite-type dedup key = name() (assumes
same name => same structure, the authoring convention); opaque-node ports
shown as [in:k] / [out] markers in a definition.

grounding-check PASS — all nine load-bearing assumptions ratified by green
tests (Composite/Blueprint API, the flat render_compilat path, the
unimplemented! site, the two replaced cluster tests, the four must-stay-green
tests). spec_validation parse gate is a confirmed no-op (none configured).

refs #38
2026-06-07 22:13:15 +02:00
Brummel a70c5faab7 audit: cycle B (0016) — ledger 0016 realization note (C8/C19), debt #36 filed
Architect drift review of the #31 cycle (2a9a141..4b64409) against the design
ledger. The feature is clean: C19/C23 realized literally (LeafFactory recipe;
compile_with_params builds-then-wires slot-by-slot in the same walk param_space()
projects — #34's dual-traversal hazard genuinely subsumed, one traversal, both
mirror tests green incl. nesting); C12 "no recompile per param-set" holds
(value-empty blueprint, vector-bound, bit-identity green); the removed
Composite::schema/BlueprintNode::schema confirmed caller-free.

ledger (fixed here):
- C8: the 0015 note's "binding a value to a slot (#31) ... deferred" line was stale
  (shipped). Updated to record #31 landed in 0016, and that the param declaration's
  authoring home moved to LeafFactory.params() (read pre-build) alongside the built
  node's schema().params (the duplication is debt #36).
- C19: added a "Realization (cycle 0016 — param-set injection)" note (the per-cycle
  pattern C8/C10 already carry for 0005/0006/0007/0015): the value-empty leaf,
  build-then-wire bootstrap_with_params/compile_with_params, the typed kind/arity
  errors, the bit-identical compilat, and the C22 blueprint-view bare-type label
  (value-bearing SMA(2) now only in the compiled view).

debt (filed, carry-on):
- Every param-bearing node declares its ParamSpecs twice (factory() and schema()),
  kept in lockstep only by a per-node test; drift compiles. Filed as #36 (sibling of
  #34's traversal-duplication concern). Not active drift — the agreement holds and
  is tested per node.

regression: profile declares no regression scripts and no architect_sweeps — the
architect review is the sole gate. No baseline to update.

Cycle B is drift-clean after these amendments. NOT a milestone close — that needs
the end-to-end milestone fieldtest, deferred until the sweep root (#32-#33) lands.
2026-06-07 21:08:49 +02:00
Brummel 4b64409036 feat(aura-core,aura-std,aura-engine,aura-cli): inject a param-set vector at bootstrap (#31)
Cycle B of milestone "The World — parameter-space & sweep". A blueprint is now
value-empty: a leaf holds a param-generic recipe, not a built node, and a positional
Scalar vector is bound slot-by-slot at bootstrap — so one blueprint bootstraps into
many distinct instances under different vectors, with no cdylib rebuild (C12/C19).
This is the binding primitive a sweep (#32) drives.

Ratified design (brainstorm): value-empty reconstruct-through-new() over
mutate-in-place and over a default-bearing variant. The value lives only in the
injected vector (no baked default), keeping the blueprint a pure param-generic
recipe (C19); every injected value flows through the node's own constructor (the
single sizing/validation gate).

- aura-core: LeafFactory { name, params, build } — the recipe (params -> sized node
  through `new`); Scalar::as_i64/as_f64 value accessors. Node trait unchanged.
- aura-std: each of the 7 nodes exposes factory() (SMA length:I64, Exposure
  scale:F64, LinComb arity x weights[i]:F64; Sub/Add/SimBroker/Recorder paramless,
  capturing their non-param construction args — pip_size, the Recorder channel).
- aura-engine: BlueprintNode::Leaf(LeafFactory), From<LeafFactory>; param_space()
  reads factory.params() pre-build; compile_with_params/bootstrap_with_params build
  each leaf from its kind-checked slice while lowering (build-then-wire), arity
  checked up front via param_space().len(); CompileError::{ParamKindMismatch,
  ParamArity}. compile/inline/edge-rewrite are structurally unchanged, so the
  compilat stays bit-identical for a given point (the bit-identity and both
  param_space mirror tests stay green). The vestigial pre-build Composite::schema /
  BlueprintNode::schema (no live caller — interface resolution is structural on the
  built flat nodes) are removed.
- aura-cli: the blueprint view labels leaves by bare type via LeafFactory::label()
  (`[SMA]`) — the ascii-dag renderer cannot render wide cluster-sibling labels (see
  spec); the compiled view still labels valued (`SMA(2)`). The sample supplies its
  point as a vector; the mis-wiring swap moved to the compiled view.

The #34 dual-traversal drift hazard is subsumed: compile_with_params consumes the
vector in the same recipe walk param_space() reports, so the two share one
traversal.

Verified: cargo build/test --workspace green (127 tests, incl. bit-identity, both
mirror tests, and 4 new injection tests — different-vector-different-run, kind
mismatch, arity, determinism); clippy --workspace --all-targets -D warnings clean;
`aura graph` blueprint view renders cleanly.

Scope: one vector -> one instance. Deferred: sweep enumeration (#32), domain
validation (#32/C20), single-run authoring convenience (#35).

closes #31
2026-06-07 21:04:52 +02:00
Brummel b02f31cdd4 spec: 0016 — blueprint-view label is the bare type (ascii-dag wide-label limit) (#31)
The amend chose `SMA(length)` (type + knob names) for the param-generic blueprint
view. Implementation surfaced that the ascii-dag 0.9.1 renderer writes a label
verbatim on one line (no multi-line — `render/ascii.rs::write_node` brackets the
raw string) and its Sugiyama subgraph layout overlaps two wide sibling boxes inside
a cluster (`[SMA(length[SMA(length)]`). Width is auto-computed but the cluster
packing does not honor it; the only spacing knob (`node_spacing`) is on the
deprecated config path, global, and width-independent — no robust option, and
domain labels grow unboundedly wide (`LinComb(weights[0], weights[1])`, deep
path-qualified names). Horizontal mode is already rejected (collapses fan-outs).

So `LeafFactory::label()` renders the bare node type (`SMA`). The tunable knobs are
surfaced by `param_space()`, not in the graph; the compiled view still labels built
nodes valued (`SMA(2)`) via `Node::label`. Correct C22 reading either way —
structure (now: type-only) before, values after.

refs #31
2026-06-07 21:04:31 +02:00
Brummel bf5ff22371 plan: 0016 param-set injection (#31)
Four crate-gated tasks for spec 0016: aura-core (LeafFactory + Scalar accessors),
aura-std (factory() on the 7 nodes), aura-engine (value-empty Leaf, build-then-wire
compile_with_params/bootstrap_with_params, kind+arity errors, vestigial schema
removal, fixtures + injection tests re-expressed), aura-cli (param-generic render,
sample + goldens, swap moved to the compiled view). Compile gates are crate-scoped
so each task's gate is satisfiable with all its callers threaded in-task.

refs #31
2026-06-07 20:16:09 +02:00
Brummel e9eea1ada3 spec: 0016 amend — value-empty render surface + vestigial schema removal (#31)
Plan-recon surfaced two surfaces the first draft under-scoped; resolved here after
a user design decision on the render form.

- Blueprint rendering (C22): a value-empty blueprint has no bound values, so the
  `aura graph` blueprint view can no longer label a leaf `SMA(2)`. LeafFactory gains
  `name` + `label()`, and the blueprint view renders the param-generic form
  `SMA(length)` (type + tunable knob names, no values), chosen over a bare `SMA` to
  surface the knobs the milestone is about. The compiled view (post-build flat
  nodes) is unchanged — it still labels valued. Correct C22 reading: structure
  before, values after.
- Vestigial pre-build interface removed: `Composite::schema` / `BlueprintNode::schema`
  derive an item interface from built interior leaves; a value-empty leaf has none,
  and both methods have no live caller (compile resolves every interface structurally
  on the built flat nodes). Removed with their unit test. This is why the factory
  carries no io skeleton — the build-then-wire decision stands, no schema duplication.
- OQ3: the swapped-sample mis-wiring moves from the builder args to the injected
  vector; its observability moves to the compiled view (the blueprint view is
  param-generic and identical for both orderings).

Grounding-check re-run: PASS (LeafFactory::label internally consistent; the
no-live-caller claim for the removed methods verified; blueprint-view vs
compiled-view goldens correctly partitioned).

refs #31
2026-06-07 20:11:06 +02:00
Brummel b9edeaf2db spec: 0016 param-set injection — value-empty blueprints bound by a vector (#31)
Cycle B of milestone "The World — parameter-space & sweep". A blueprint's tunable
values are injected at bootstrap rather than baked into the builder: a leaf becomes
a param-generic recipe (LeafFactory: params -> sized node), and a new build-then-
wire compile path binds a positional Scalar vector slot-by-slot against
param_space(), building each node through its own constructor.

Ratified design (brainstorm): value-empty B over mutate-in-place and over the
default-bearing variant. The value lives only in the injected vector (no baked
default), so the blueprint stays a pure param-generic recipe (C19); injected
values flow through the single new() sizing/validation gate; and because
compile_with_params consumes the vector in the same depth-first order param_space()
reports, the two share one traversal — subsuming #34's dual-traversal drift hazard.

Scope: one vector -> one instance; LeafFactory in aura-core; bootstrap_with_params/
compile_with_params; CompileError::{ParamKindMismatch, ParamArity}; Scalar::as_i64/
as_f64 accessors; fixtures/CLI/bit-identity re-expressed against the vector.
Deferred: sweep enumeration (#32), domain validation (#32/C20), single-run
authoring convenience (#35).

Gates: grounding-check PASS (8 current-behaviour assumptions ratified against green
tests; the sole BLOCK — a missing Scalar value accessor — resolved by adding it as
in-scope surface). Parse gate a no-op (profile declares no spec_validation).

refs #31
2026-06-07 18:57:52 +02:00
Brummel a31d91453c test(aura-engine): mirror param_space against compile() under nesting (#34)
The cycle-0015 E2E guard param_space_mirrors_compiled_flat_node_param_order
pins the load-bearing C23/#31 invariant — Blueprint::param_space() slot order
mirrors compile()'s flat-node param order kind-by-slot — but only on the
single-level composite_sma_cross_harness. collect_params (blueprint.rs:217)
duplicates lower_items' depth-first traversal rather than sharing it (kept a
parallel read-only projection so compile/inline_composite stay untouched and
the compilat bit-identical, C9/C23), so the two orders must stay in lockstep
forever. No mirror test ever compiled a composite-inside-a-composite: a future
inliner reorder that desynced the projections only under nesting could pass the
single-level mirror and the isolated nested param_space order test, yet break
the slot-by-slot premise #31's binding rests on.

Adds param_space_mirrors_compiled_flat_node_param_order_under_nesting: compiles
a strategy -> { fast_slow -> [Sma, Sma, Sub], LinComb } nest and asserts
param_space() equals the compiled flat-node param order kind-by-slot, pinning
the concrete [I64, I64, F64, F64] shape. Green immediately — coverage-hardening,
no desync in the current tree. Test-only; collect_params/lower_items/compile
untouched.

Verified: cargo test -p aura-engine green (55 tests), clippy --all-targets -D
warnings clean.

closes #34
2026-06-07 16:16:18 +02:00
Brummel 2a9a141a6f audit: cycle A (0015) — ledger amendments (C8/C23), debt #34 filed
Architect drift review of the #30 cycle (561482c..931109d) against the design
ledger. Code holds: param_space() is a read-only C9 projection, compile/inline
untouched, compilat bit-identical, positional identity (C23) and topology-fixed
vector arity (C19) implemented and well-covered. Two ledger drifts + one debt item.

ledger (fixed here):
- C23 'Status' parenthetical said the sweep pass 'presupposes per-node param
  declarations (C8 — deliberately not in the schema yet)' — now stale, the
  declarations landed this cycle. Reworded to 'landed in cycle 0015 as name+kind;
  swept range still pending #32/C20'.
- C8 guarantee promises params 'typed, with ranges' but cycle 0015 shipped name+
  kind only. Added a 'Realization (cycle 0015)' note (the established per-cycle
  pattern, as C8 already carries for 0005/0006) recording what landed and the
  load-bearing refinement from the design discussion: the SEARCH-RANGE is the run's,
  not the node's (#32/C20) — the node declares the knob's existence and type, never
  its search interval; identity is positional (slot), the path-qualified name a
  non-load-bearing debug symbol.

debt (filed, carry-on):
- collect_params (blueprint.rs:217) duplicates lower_items' traversal order rather
  than sharing it (deliberate — keeps param_space a parallel projection so the
  compilat stays bit-identical). The E2E mirror guard is shape-specific (no nested-
  composite compile case). Filed as #34 to harden before #31 binds atop the
  invariant. Not active drift — the guard exists, only the shape coverage is partial.

regression: profile declares no regression scripts and no architect_sweeps — the
architect review is the sole gate. No baseline to update.

Cycle A is drift-clean after these amendments. NOT a milestone close — that needs
the end-to-end milestone fieldtest, deferred until the sweep root (#31-#33) lands.
2026-06-07 15:35:10 +02:00
Brummel 931109df58 feat(aura-core,aura-std,aura-engine): declare node tunable params (#30)
Cycle A of milestone 'The World — parameter-space & sweep'. A node now declares
its tunable parameters in its C8 schema, and a blueprint aggregates them into one
flat, inspectable param-space — the root that unlocks the C12 orchestration axes
(#31 bind, #32 sweep), filling the gap C8/C23 name 'deliberately not in the schema
yet'.

- aura-core: ParamSpec { name: String, kind: ScalarKind } as a third schema-
  declaration type; NodeSchema gains a third field 'params'. name is String (not
  &'static like FieldSpec) because a vector knob carries a runtime index and
  aggregation prefixes the composite path.
- aura-std: Sma declares [length:I64], Exposure [scale:F64], LinComb expands to N
  flat [weights[i]:F64] (N = its input arity, topology-fixed per C19); Sub/Add/
  SimBroker/Recorder declare none (pip_size is metadata, C10/C15; Recorder is
  wiring).
- aura-engine: Blueprint::param_space() walks the graph-as-data depth-first in
  lower_items order, path-qualifying via the already-public Composite::name() — a
  read-only projection (C9). compile/inline_composite/lower_items are untouched, so
  the compilat stays bit-identical (composite_sma_cross_runs_bit_identical_to_hand_wired
  and the golden render tests stay green).

Design: param identity is positional (slot after the deterministic inline order,
C23 'by index not name'); the path-qualified name is a non-load-bearing debug
symbol — same-type siblings in one composite share a name, uniqueness is at the
slot. Flat over a structured arity-bearing ParamSpec because flattening is
unavoidable (a sweep enumerates a flat point-space) and structure-in-the-runtime
is the nested-composite reading C23 rejects (see spec 0015 for the full rationale).

Scope is declaration + aggregation + inspection only; binding (#31), sweep
enumeration (#32), search-range, validity-constraint, and default-range/slider are
deferred. An E2E test pins the load-bearing C23/#31 invariant: param_space() slot
order mirrors compile()'s flat-node param order, kind-by-slot, on the SMA-cross
harness. fieldtests/ (excluded crates) left as frozen snapshots.

Verified: cargo build/test --workspace green (64 tests), clippy --all-targets -D
warnings clean; compile/inline diff empty.

closes #30
2026-06-07 15:29:58 +02:00
Brummel 6911fa52ed plan: 0015 node tunable-parameter declaration (#30)
Four bite-sized tasks, sequenced by crate-compile boundary (the non-Default
params field breaks every NodeSchema literal until repaired): aura-core type +
field, aura-std declarations, aura-engine fixture compile-repair, then the
param_space() aggregation + tests.

refs #30
2026-06-07 15:18:48 +02:00
Brummel 5f5d1501c5 spec: 0015 node tunable-parameter declaration (#30)
Cycle A of milestone 'The World — parameter-space & sweep'. A node declares
its tunable parameters in its C8 schema (ParamSpec { name, kind }, a third
NodeSchema field); Blueprint::param_space() aggregates them into one flat,
path-qualified param-space as a read-only projection of the graph-as-data.

Param identity is positional (slot after the deterministic inline order),
the name a non-load-bearing path-qualified debug symbol (C23). Vector knobs
(LinComb.weights) expand to N flat indexed entries; N is topology-fixed (C19).
compile/inline_composite are untouched — the compilat stays bit-identical.

Scope is declaration + aggregation + inspection only; binding (#31), sweep
enumeration (#32), search-range, constraint, and default-range are deferred.

refs #30
2026-06-07 15:09:57 +02:00
Brummel 561482c422 tidy(aura-engine): re-export scalar vocab (#29) + drop duplicate Recorder fixtures (#14)
Two behaviour-preserving tidies from the post-0013 fieldtest/backlog.

#29: aura-engine re-exports Firing/Scalar/ScalarKind/Timestamp at the crate
root. A Blueprint builder needs ScalarKind for SourceSpec.kind and Scalar/
Firing/Timestamp for sources & Recorder columns, but aura-engine previously
re-exported only the wiring types — forcing a second aura-core import for the
vocabulary its own public structs (SourceSpec.kind, ...) demand. The fieldtest
flagged this paper cut (mc_4). One import surface now. Guarded by a new
reexport_tests module.

#14: the two near-identical #[cfg(test)] Recorder fixtures in report.rs and
harness.rs are deleted in favour of the shipped aura-std::Recorder (already a
dev-dependency). Constructor signature, schema, and try_iter() drain are
identical, so no call site changed — only the struct/impl deletion plus adding
Recorder to each file's existing aura_std import. report.rs additionally drops
four imports (Ctx/InputSpec/Node/NodeSchema) that only the deleted fixture
used. The separate TapForward fixture in harness.rs is untouched.

Verified: cargo build/test/clippy --workspace all green (49 aura-engine lib
tests incl. the new one); bit-identical, golden-snapshot, SMA-disambiguation,
and mixed-kind recording tests all stay green — behaviour preserved.

closes #29
closes #14
2026-06-05 23:25:48 +02:00
Brummel 73938a4e4a plan: 0014 tidy — scalar re-export (#29) + Recorder-fixture dedup (#14)
Post-cycle tidy iteration (audit fix-path: planner + implement). Covers two
pure-tidy issues:

- #29: aura-engine re-exports the core scalar vocabulary (Firing/Scalar/
  ScalarKind/Timestamp) so a Blueprint builder needs one import surface.
- #14: delete the two near-identical #[cfg(test)] Recorder fixtures in
  report.rs/harness.rs in favour of the shipped aura-std::Recorder (identical
  constructor + try_iter drain, so call sites stay byte-identical).

#27 (cross-crate sma_cross dedup) was the third tidy candidate but is excluded:
after the bit-identical-test protection (hand_wired pole must stay independent)
and the aura-run-out-of-scope rule, its only remaining content is deduping a
concrete sma_cross composite across aura-cli (non-test) and aura-engine
(#[cfg(test)]). No C9-clean home exists for that (a feature-gated pub fn would
make a concrete trading signal an engine pub API, which C9 forbids). Narrowed
per orchestrator decision; refs #27.
2026-06-05 23:21:41 +02:00
Brummel 7e195f66b7 fieldtest: milestone construction-layer — 4 examples, 7 findings
End-to-end milestone fieldtest (closing gate) for the "Construction layer"
milestone (#12 composites + #13 aura graph render). Four real downstream tasks
authored against the PUBLIC interface only (ledger + doc-comments + re-exports
+ the aura CLI), under fieldtests/milestone-construction-layer/:
  mc_1 — author a named sma_cross composite, build a Blueprint, compile +
         bootstrap + run (12 populated equity rows);
  mc_2 — correct vs fast/slow-swapped cross: labels + graph-as-data differ
         observably (the headline mis-wire-visible property);
  mc_3 — composite-nested-in-composite inlines and runs;
  mc_4 — walk the built graph via the read-only accessors, no engine internals.

Roll-up: friction_found, NO bugs. The milestone delivers its core promise —
fractal authoring -> compile -> bootstrap -> run, introspection as graph-as-data,
and param-carrying labels that make a mis-wire readable. None of the findings
block the gate.

Findings filed to the forward queue (not fixed here):
- #28 (feature) — the headline friction: `aura graph` renders only the built-in
  sample and the render adapter is CLI-private, so a consumer can't render their
  OWN graph. Subsumes the reachability of #26 (the nested-render panic is
  structurally unreachable until the render is parameterizable). Likely next
  (consumer-project / World) milestone.
- #29 (idea) — aura-engine doesn't re-export the scalar vocabulary (ScalarKind
  etc.) a graph-builder needs; one-crate ergonomics tidy.
- #16 (comment) — `aura graph` arg policy (help / unknown-arg) should unify with
  the still-open `aura run` strictness question.

The fieldtester read no implementation source; all artefacts are the consumer
crate + the two live render captures (render_clustered.txt / render_compiled.txt,
byte-identical across runs).
2026-06-05 22:38:39 +02:00
Brummel d97fadaafa audit: cycle 0013 tidy (carry-on, debt filed)
Architect drift review over 9fbc16a..0a855c3 (aura graph ASCII-DAG render,
#13): no contract violation. Regression gate is a no-op (commands.regression
empty); architect is the sole gate.

Contracts verified against the diff (by fact, not claim):
- C8/C1: `Node::label()` is genuinely non-load-bearing — called only in the
  renderer (aura-cli/src/graph.rs) and test assertions, never in the run loop
  (harness.rs) nor the compile path (blueprint.rs). Wiring stays index-based;
  determinism untouched. The C8 Refinement note (INDEX.md) matches what shipped.
- C16: no engine-crate manifest changed (aura-core/std/engine/ingest Cargo.toml
  all clean); ascii-dag v0.9.1 appears only in aura-cli/Cargo.toml + the matching
  Cargo.lock delta. The zero-external-dependency engine firewall is intact.
- C9/C23: `Composite.name` dissolves at inline — `inline_composite` destructures
  `name: _` (blueprint.rs), the name never reaches the flat compilat. The
  compiled view emits no cluster (golden asserts `!contains("sma_cross")`); the
  blueprint view draws composites as clusters. Boundary dissolution holds.

Drift status: drift_found = technical debt only (no fix iteration; contracts
hold). Filed to the forward queue rather than fixed in-cycle:
- #26 (feature) — the blueprint (clustered) view panics `unimplemented!` on
  nested composites while the compiled view handles nesting; the two views
  diverge in capability. Built-in sample is single-level so it is unreachable
  today. Bundles the low-severity source-label gap (sources render `source:{kind}`
  because SourceSpec has no name — latent C20 gap).
- #27 (idea) — the SMA-cross sample wiring is now triplicated (aura run flat
  harness, aura graph composite blueprint, engine bit-identical fixture);
  distinct from #14's Recorder-fixture dedup.

No baseline update (no regression script). Next-iteration direction (architect):
resolve #26 before the next render-touching cycle so the two views stop diverging.
2026-06-05 21:00:45 +02:00
Brummel 0a855c3943 feat(aura-cli): aura graph renders a wired graph as an ASCII DAG (#13)
Make a wired graph introspectable so a mis-wiring becomes visible. Two
selectable views, both authored from one built-in sample blueprint:

  aura graph             clustered blueprint view — composites drawn as named
                         ascii-dag cluster boxes (pre-inline, C9)
  aura graph --compiled  flat compilat view — composite boundaries dissolved
                         (C23); the graph the run loop actually runs

The payoff is intrinsic, param-carrying node labels: two SMAs read SMA(2) /
SMA(4), so a swapped fast/slow input reads back differently. This is realized
by a `label()` default method on the core Node trait — a non-load-bearing
render symbol the run loop never reads (wiring is by index), the symbol C23
already reserves citing #13. Recorded as a C8 refinement in the ledger.
Rejected the alternatives in brainstorm: a separate Describe trait (forces a
combined trait-object dance for a label the ledger calls non-load-bearing) and
extrinsic parallel labels (decoupled from the node, so an author can label the
slow SMA "fast" and mask the very bug the render exists to surface).

Mechanism:
- aura-core: Node::label() default method (additive, object-safe, single-line).
- aura-std: per-node overrides — SMA(n)/Exposure(s)/SimBroker(p) carry params;
  Sub/Add/LinComb/Recorder are bare (their identity is not a mis-wiring axis).
- aura-engine: Composite gains an authored `name` (cluster title; dissolves at
  inline) + read-only graph-as-data accessors on Blueprint/Composite. No
  external dependency — the engine stays dependency-pure (C16); ascii-dag lives
  only in aura-cli. The label-free index-wired compilat (C23) is unchanged.
- aura-cli: ascii-dag v0.9.1 + a graph.rs adapter (Vertical mode; labels
  materialized into an owned Vec<String> that outlives the borrow-based Graph).
  `aura run` is untouched (the sample duplication is dedup idea #14).

Tests: a concern-defining test (swapped sample renders != correct), structure
pins (cluster present in blueprint view, absent in compiled view), per-node
label disambiguation, and two frozen byte-goldens of the deterministic render.
The cycle-0012 bit-identical and run-sample non-regression tests stay green.

Verified by the orchestrator (not the agent report): cargo build --workspace
clean; cargo test --workspace all green; cargo clippy --workspace --all-targets
-D warnings clean; both views run live and render as expected.

closes #13
2026-06-05 20:56:52 +02:00
Brummel b01821653e plan: 0013 aura-graph ascii-dag
Five-task plan for spec 0013: (1) Node::label() default method + C8 ledger
refinement; (2) aura-std label() overrides + disambiguation test; (3) Composite
name field + read-only blueprint/composite accessors + the 7 Composite::new
call sites threaded; (4) aura-cli ascii-dag dep + graph.rs adapter (clustered
blueprint view + flat compiled view) + sample_blueprint() + `aura graph
[--compiled]` dispatch; (5) render tests (concern-defining swap test, structure
pins, frozen goldens) + workspace gates.

refs #13
2026-06-05 20:45:45 +02:00
Brummel 33e87ff199 spec: 0013 aura-graph ascii-dag
Render a wired graph as an ASCII DAG via an `aura graph` subcommand so a
mis-wiring becomes visible (the concern of #13). Two selectable views: a
blueprint view (composites as cluster boxes, pre-inline) and a `--compiled`
view (the flat compilat, boundaries dissolved per C23).

Settled in brainstorm: render labels come from a non-load-bearing `label()`
default method on the core `Node` trait (a C8 refinement aligned with C23,
which already reserves render names citing #13), overridden per node to carry
params (SMA(2) vs SMA(4)) so a swap reads back differently. Composites gain an
authored name; the engine exposes read-only graph-as-data accessors and stays
dependency-free (C16) — ascii-dag lives only in aura-cli. `aura run` is left
untouched this cycle.

Gates: self-review clean; parse gate a no-op (no spec_validation in profile);
grounding-check PASS.

refs #13
2026-06-05 20:33:23 +02:00
Brummel 9fbc16afb9 audit: cycle 0012 tidy (clean)
Architect drift review over fef09d4..a4fb5d7 (blueprint construction layer /
composite inlining, #12): drift-clean, carry-on. Regression gate is a no-op
(commands.regression empty); architect is the sole gate and found nothing
actionable.

What holds against the ledger (verified by fact, not claim):
- C1/C16: `git diff --stat` shows only blueprint.rs (new) + lib.rs (+2 lines).
  harness.rs (run loop, bootstrap), node.rs (Node trait), and
  Edge/Target/SourceSpec are byte-untouched, so determinism is preserved — the
  compilat is the same flat topology a hand-wiring produces. aura-engine's
  Cargo.toml adds no external dependency (aura-std is [dev-dependencies] only),
  so the zero-external-dependency engine workspace invariant holds.
- C9/C23: `Composite` is not a `Node` (no `impl Node for Composite`, never
  `eval`'d); `compile()` inlines to a flat (nodes, sources, edges) wired by raw
  index; names survive only as non-load-bearing debug symbols. The
  bit-identical demonstrator proves the compilat equals the hand-wired flat
  graph for the SMA-cross. No runtime sub-engine reached the run loop.
- C19: binding is a compilation; typed CompileError validation delegates the
  lowered-compilat check to the unchanged bootstrap kind/Kahn check (no
  re-implementation).

Acknowledged debt (low, named — not silent drift), filed to the forward queue:
- #24 — Composite::schema() panics on malformed indices where compile() returns
  a typed CompileError (two validity surfaces, divergent failure modes;
  doc-acknowledged).
- #25 — compile() re-calls dyn Node::schema() (fresh Vec alloc) at each resolve
  site, duplicating bootstrap's kind-derivation; compile-time only, no hot-path
  cost.

Neither debt item blocks the deferred C23 optimisation passes, the natural
next direction.
2026-06-05 14:06:01 +02:00
Brummel a4fb5d7182 feat(aura-engine): blueprint construction layer with composite inlining
Add the construction layer (C9/C19/C23): a named, param-generic graph-as-data
(`Blueprint`) that *compiles* to the flat, type-erased instance the run loop
already runs. The unit of reuse is the `Composite` — a nestable sub-graph
fragment exposing one output port (C8) and named input roles — which
`Blueprint::compile()` **inlines** by raw-index lowering into the flat
`(nodes, sources, edges)` the unchanged `Harness::bootstrap` consumes.

Design (settled in spec 0012, ledger C9/C19/C23): a composite is an
authoring-level node, NOT a runtime `Box<dyn Node>` sub-engine. The chosen
inlining approach keeps the sacrosanct deterministic run loop untouched and
leaves the composite interior fully inspectable for the deferred cross-graph
optimiser; the rejected "runtime sub-engine" reading would have put throwaway
complexity into the run loop and kept the interior opaque. The compilat is
wired by raw index, not by name; names survive only as non-load-bearing debug
symbols (as `FieldSpec.name` already is).

Lowering is recursive index rewriting: interior items append at an offset,
interior edges rewrite by that offset, an edge into a composite fans through
its input roles (one blueprint edge -> several flat edges), an edge out
resolves to the interior output port, and nesting recurses inside-out.
Construction-phase faults are caught as a typed `CompileError`
(BadInteriorIndex / RoleKindMismatch / OutputPortOutOfRange); the lowered flat
compilat is validated by bootstrap's existing kind- and Kahn-cycle-check
(wrapped as `CompileError::Bootstrap`), with no re-implementation.

Non-goals (deferred per C23/C16): no optimisation pass (CSE/DCE,
sweep-invariant hoisting), no external optimisation crate, no named-handle
ergonomic wiring, no `aura graph` render (#13).

Verification: 48 engine tests green (8 blueprint: derived-schema, single +
nested composite inlining, the three CompileError paths, bootstrap-error wrap,
and the headline `composite_sma_cross_runs_bit_identical_to_hand_wired` C1
demonstrator — the SMA-cross composite lowers to a flat graph byte-identical to
the hand-wired sample harness, producing bit-for-bit identical equity +
exposure traces); `cargo clippy --workspace --all-targets -D warnings` clean.
`Node`, `Harness::bootstrap`, the run loop, and `Edge`/`Target`/`SourceSpec`
are unchanged.

closes #12
2026-06-05 14:03:27 +02:00
Brummel 5d0780aa89 plan: 0012 blueprint-compile composites
Bite-sized plan for the construction layer: Blueprint/Composite types +
derived schema (Task 1), the recursive compile() inliner with typed
CompileError validation (Task 2), and the SMA-cross bit-identical
demonstrator (Task 3). Lowers to the unchanged Harness::bootstrap;
optimisation passes deferred (C23).

refs #12
2026-06-05 13:56:24 +02:00
Brummel fb442a5304 spec: 0012 blueprint-compile composites
The Construction-layer milestone's anchor spec: a Blueprint graph-as-data that
compiles (inlines composites) to the flat (nodes, sources, edges) the unchanged
Harness::bootstrap consumes (C9/C19/C23). The SMA-cross composite runs
bit-identical to the hand-wired sample_harness (C1). Non-goals: optimisation
passes, external deps, named-handle ergonomics, aura graph.

Grounding-check PASS (13/13 current-behaviour assumptions ratified by green tests).

refs #12
2026-06-05 13:36:34 +02:00
Brummel ff1dce5a9b 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<dyn Node> 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
2026-06-05 12:52:14 +02:00
Brummel fef09d4a47 feat(aura-cli): strict aura run arg-parse rejects trailing tokens
`aura run` keyed only on the first token being `run`, so `aura run
extra-garbage` printed the full report and exited 0 -- a typo'd
`aura run --sweep` silently ran instead of hinting. #16 ratified the
strict reading over the lenient one (the lenient reading buys no real
forward-compat: future args would be parsed explicitly anyway, while the
silent-swallow actively hides typos and clashes with the exit-2 hygiene
the unknown-subcommand path already enforces).

Add a match guard so only a bare `run` reaches the JSON happy path:

    Some("run") if args.next().is_none() => println!(...)

A trailing token fails the guard and falls through to the existing
`_ =>` usage-error arm (stderr usage, exit 2) -- no literal duplication,
the error path is reused. Bare `aura run` is unchanged, pinned by the
positive-preservation assertion and the two sibling run-tests. Verified:
cargo test -p aura-cli green (6), clippy --all-targets -D warnings clean.

closes #16
2026-06-05 00:19:05 +02:00
Brummel 409b770ac6 test(aura-cli): RED for strict aura run arg-parse
`aura run extra-garbage` keys only on the first token being `run` and
ignores the rest, so it prints the full report and exits 0 -- a typo'd
`aura run --sweep` masquerades as a successful run instead of getting a
hint. The 0010 cycle_scope ("any other or missing subcommand -> usage +
exit 2") did not unambiguously cover `run <token>`; #16 ratifies the
strict reading.

This RED pins it: a trailing unexpected token takes the error path
(usage on stderr, empty stdout, exit 2), mirroring the no-args sibling;
bare `aura run` is held to its current happy path (JSON on stdout, exit
0) by a positive-preservation assertion so the strict guard cannot
regress it.

refs #16
2026-06-05 00:17:10 +02:00
Brummel 30e5abb6aa feat(aura-cli): self-identifying RunManifest.commit via build.rs git capture
RunManifest.commit read option_env!("AURA_COMMIT").unwrap_or("unknown"),
so a normal build's manifest identity field was a bare placeholder --
C8's audit trail (this run = this commit) had nothing to point back at
once runs are archived (C18 registry).

Add crates/aura-cli/build.rs: at compile time it shells out to the `git`
already in PATH -- `rev-parse HEAD`, plus a `-dirty` suffix when
`status --porcelain` is non-empty -- and emits
`cargo:rustc-env=AURA_COMMIT=<sha>`. The existing option_env! read is
untouched and now picks it up. Chose shelling to git over a libgit crate
to keep the zero-external-dependency commitment (C14/C18); the firewall
crate is aura-ingest, and a build-time process call adds no runtime dep.
No-git case (packaged source tree, no .git): build.rs returns early and
swallows git errors, leaving AURA_COMMIT unset so "unknown" still holds.

Staleness guard: build.rs emits rerun-if-changed on .git/HEAD and
.git/logs/HEAD (logs/HEAD grows on every HEAD move incl. branch switch),
so the captured sha is rebuilt when HEAD moves rather than going stale.
Determinism (C1) is intact: AURA_COMMIT is fixed at compile time, so two
runs of one build still produce byte-identical manifests.

Two tests that pinned the old "unknown" placeholder are relaxed to the
new structural contract (not scope creep -- they were the old contract):
the cli_run.rs JSON-shape prefix no longer pins the commit value, and
the main.rs unit test asserts commit is non-empty and stable across runs
instead of equal to "unknown". The headline contract (manifest.commit
contains the real HEAD sha) is pinned by run_manifest_commit_carries_
real_git_head (4552d0c). Verified: cargo test -p aura-cli green (5),
clippy --all-targets -D warnings clean.

closes #15
2026-06-05 00:10:42 +02:00