Commit Graph

153 Commits

Author SHA1 Message Date
Brummel 7569980dcd audit: cycle 0027 (#50) — drift-clean; input ports named
Architect drift review (288f589..e304dba): cycle 0027 introduced NO drift, no
contract weakened, no ledger edit beyond the cycle's own C8 realization note.

- C23 (identity positional, names non-load-bearing): held. PortSpec.name is read
  only by port_json / scope_json (render); derive_signature carries Role.name for
  display; wiring stays by index (Edge/Target), the bootstrap kind-check reads
  .kind, never .name. The compilat is untouched.
- C9 (read-only render): model_to_json still &Composite -> String, no
  eval/build/bootstrap/compile on the path.
- C14 (no serde): still hand-rolled format! JSON.
- C4 (four scalar base types): unchanged.
- Homogeneity goal met: the re-captured model golden names every input tuple at
  both leaf and composite level, matching outputs/params; nothing half-named.
- Verification (re-run by the orchestrator, not taken on the agent's word): cargo
  build --workspace clean; cargo test --workspace 168 green / 0 red; clippy
  --all-targets -D warnings clean.

Regression gate: profile configures no regression scripts (regression: []) — a
no-op this cycle; architect is the sole gate and it is clear.

Deferred (pre-existing debt, NOT 0027 drift): graph.rs:324 multi_output_field_name
carries a stale #43 comment ("leaf field names unavailable pre-build") — false
since cycle 0024; the leaf branch falls back to the field index. The file is the
ascii-dag renderer slated for retirement by #51 (0026 it2); the dead fallback
dies with the file there. Recorded as a comment on #51 so it is not lost — not a
tidy iteration, since fixing soon-to-be-deleted code is wasted effort.

#21 intentionally NOT closed (a swapped same-kind wiring is still only
kind-checked) — the cycle's stated scope boundary, not drift.
2026-06-10 16:25:59 +02:00
Brummel e304dbaae1 feat(aura-core): name input ports
PortSpec gains a non-load-bearing `name: String`, so an input port is named just
as FieldSpec.name (output) and ParamSpec.name (param) already are — input ports
were the lone unnamed member of the node signature. Identity stays positional by
slot (C23); the name is render/debug only, never read by bootstrap or the run
loop. PortSpec drops Copy (String is not Copy), exactly as ParamSpec already does.

- Every aura-std node names its input slots: SMA/EMA "series", Sub/Add "lhs"/"rhs",
  Exposure "signal", SimBroker "exposure"/"price" (the slots become
  self-documenting), LinComb "term[i]" and Recorder "col[i]" generated in their
  build loops (mirroring LinComb's existing weights[i] param loop).
- derive_signature carries a composite's Role.name into the derived input port
  (it was dropped before — the output side already carried FieldSpec.name), so the
  graph model is homogeneously named at both levels.
- model_to_json (port_json + the composite-header inputs in scope_json) emits the
  name as a third tuple element: ["f64","any","exposure"]. The byte golden was
  re-captured (machine bytes) and its substring twins updated; the model is now
  fully named across inputs/outputs/params.
- All 16 PortSpec construction sites threaded in one compile-gate change; test
  fixtures carry fixture names. C8 realization note added to the design ledger.

Why name-only, no validation: the name is a pure debug symbol. Wire-by-name was
rejected (it would be a C23 contract change). Bootstrap slot-wiring validation
(which would close #21's same-kind swap footgun) is deferred to its own cycle —
a name alone does not catch the swap; it makes the slots self-documenting and
gives a future validation something to check against.

Verified: cargo test --workspace 168 green; clippy --all-targets -D warnings
clean; cargo build --workspace clean. Read-only render path (C9), no serde (C14),
scalar kinds unchanged (C4).

closes #50
refs #21
refs #51
2026-06-10 16:19:08 +02:00
Brummel 5288249b44 plan: 0027 name input ports
Four tasks for cycle 0027 (spec docs/specs/0027-name-input-ports.md):
1. Add name: String to PortSpec (drop Copy) and thread all 16 construction
   sites in one compile-gate task — aura-std nodes get real slot names, fixtures
   get fixture names, derive_signature carries Role.name; port_json untouched so
   the byte golden stays green and the full suite passes.
2. port_json appends the name; re-capture the model byte golden and update its
   two substring twins (sink_has_empty_outs witnesses a leaf PortSpec.name in the
   model, multi_input_composite witnesses a composite Role.name carried through).
3. Lock the per-node input-slot names (8 aura-std pins).
4. C8 realization note in the design ledger (cross-links C23).

refs #50
2026-06-10 16:04:10 +02:00
Brummel 55cfb29c74 spec: 0027 name input ports
Give a node's input ports a name. PortSpec { kind, firing } is the lone unnamed
member of the node signature today (FieldSpec.name / ParamSpec.name already
exist); a leaf node's input slots have no declared semantics. Add a
non-load-bearing name: String to PortSpec (drops Copy, mirrors ParamSpec), name
every input slot across aura-std nodes + fixtures, carry Role.name into
derive_signature's derived composite port (it is dropped today), and make
model_to_json emit the name in each port tuple. Identity stays positional by
slot (C23); the name is render/debug only, never read by bootstrap or the run
loop.

Surfaced by the 0026 graph-render redesign: a homogeneous pin-graph needs the
name to label input pins instead of inventing "a"/"b". Single iteration.

Design ratified interactively (brainstorm); grounding-check PASS (every
load-bearing current-state assumption ratified by a named green test); parse
gate a no-op (no spec_validation parser configured). Forks resolved: name is a
pure debug symbol, not wire-by-name (C23); String not &'static str (variadic
nodes generate term[i]/col[i]); no bootstrap validation this cycle.

refs #21
refs #50
2026-06-10 15:53:46 +02:00
Brummel 288f58953e feat(aura-engine): graph model serializer (0026 it1)
Iteration 1 of the graph render redesign: a read-only model_to_json(&Composite)
that serializes the harness root + every distinct composite type into the
canonical, deterministic JSON model the browser viewer will consume. New
crates/aura-engine/src/graph_model.rs; lib.rs re-exports model_to_json.

- House style: hand-rolled JSON like RunReport::to_json, NO serde (C14).
- Model shape (faithful to the types, resolving the spec's prototype-flavoured
  example): node keys are indices (C23, the blueprint has no unique names);
  input ports carry kind+firing only, no name (PortSpec has none — acceptance
  criterion 3); a vec![] output is the C8 sink; bound root source-roles are
  minted as synthetic src_<role> source nodes; composite boundaries use @role
  (input) and #N (output) endpoints; distinct composites collected once.
- Improvement over the plan: a composite's input kind is read from the actually
  fed interior slot (matching blueprint::derive_signature) rather than a hardcoded
  f64 — invents nothing.
- Read-only (C9): &Composite -> String, no eval/compile/bootstrap on the path.
- 10 new tests green: byte golden on the sample harness, determinism, structural
  mis-wire differs (param swaps are invisible to the pre-compile model — the
  property is carried by a structural re-target), read-only witness, sink /
  two-input / distinct-once structural assertions. Workspace green, clippy clean.

aura graph still renders via ascii-dag (untouched); iteration 2 ports the viewer
JS, vendors the WASM-Graphviz asset, emits the self-contained HTML, and retires
ascii-dag.
2026-06-10 11:18:28 +02:00
Brummel bbe2266b9e plan: 0026 graph model serializer
Iteration 1 of the graph render redesign: a read-only model_to_json(&Composite)
in a new crates/aura-engine/src/graph_model.rs, hand-rolled deterministic JSON
(RunReport::to_json house style, no serde). Six tasks: helpers, primitive record,
scope (index keys + synthetic source nodes from bound roles), composite defs
(@role/#N endpoints, distinct-once walk), top-level + byte golden + determinism,
structural assertions + structural-miswire-differs + read-only. Decisions: home
in aura-engine; node keys are indices (C23); input ports carry kind+firing, no
name (per acceptance criterion 3); bound root source-roles minted as synthetic
source nodes.
2026-06-10 11:10:23 +02:00
Brummel cdb6313ee8 spec: 0026 graph render redesign
Replace the ascii-dag aura graph view with a homogeneous pin-graph: aura emits a
deterministic JSON model (hand-rolled, RunReport::to_json house style, no serde);
a vendored viewer JS (genDot, ported from the prototype) turns it into DOT;
Graphviz-WASM lays out + draws it in the browser. aura stays a read-only
serializer (C9), ships no layout engine. The golden-tested contract is the JSON
model. Iteration 1: the model serializer + golden/determinism/read-only tests.
Iteration 2: viewer + WASM vendoring + self-contained HTML, retire ascii-dag.

Grounding-check PASS; design ratified interactively against the 0026 prototype.
2026-06-10 10:58:47 +02:00
Brummel 7f485bbe72 docs(design): graph-render redesign prototype
Interactive, throwaway prototype of the aura graph render redesign — the
visual language ratified for cycle 0026, replacing the ascii-dag text view.
Homogeneous pin-nodes (body + n input pins + m output pins) rendered via
Graphviz-WASM in the browser; pin-to-pin edges, type-as-colour, drill-down +
inline-expand of composites, per-element styled tooltips, source/sink colouring,
top-rank input ordering. index.html is the artifact; the ~1.4 MB WASM dep is
git-ignored and restored by fetch-deps.sh. Visual companion to docs/specs/0026.
2026-06-10 10:58:47 +02:00
Brummel 63af2d1095 audit: cycle 0025 (#49) — drift-clean; root renders like any composite
Architect drift review (d6f59d7..12b1bce): NO code drift, no contract weakened,
no ledger edit needed.

- C9 (read-only render): the change touches only label/entry assembly
  (render_blueprint, leaf_label, render_graph signature); no eval, no run-path
  code. 150 tests green (same count), clippy clean, build green.
- C12 ("mis-wiring visible before a run"): realized at the root, not overstated —
  a top-level fan-in's wiring was invisible (bare [SimBroker]); it now surfaces
  which producer feeds which slot ([SimBroker(#E,#price)]). The C8 label-render
  refinement (INDEX.md:250, "label() surfaces a mis-wiring") is extended to the
  root, not contradicted.
- C23 (names dissolve post-inline): render_compilat byte-untouched (still emits
  source:{kind} from SourceSpec.kind); the [price] pre-inline / [source:F64]
  post-inline split now renders C23's boundary-dissolution visibly, with
  compiled_view_golden as the negative control.
- C3 (sources bind at ingestion only): root entries filtered to source.is_some()
  bound roles — the one semantic root-vs-interior distinction, correctly retained.
- #49 acceptance: the Option<&Composite> drop unifies both render_graph callers
  onto one stub path; no dead arm, no second/root-only stub function, no scope
  creep beyond graph.rs (production) + main.rs (test-only goldens/assertions).

No ledger prose names the root source:{kind} marker as canonical; the C9
cycle-0017 realization (where: definitions render) is not stale. No new invariant
introduced — the cycle realizes existing contracts more fully, so no realization
note is manufactured.

Regression: none configured in the profile (no-op) — the architect review is the
gate. Resolution: carry-on (drift-clean). NOT a milestone close.

Tracker reconciliation done this cycle (no code, orchestrator-applied):
- #36 closed — 0024 resolved it (params declared once on the recipe; the 8
  lockstep tests deleted), but Gitea's `closes #43 #36` only auto-closed #43.
- #25 closed — 0024 removed the dyn Node::schema() re-call + bootstrap-derivation
  duplication this issue named (signatures gathered once into FlatGraph).
- #24 retitled to derive_signature()/signature() — the panic-vs-typed-error
  divergence persists under 0024's renamed symbols; still open, low-priority.
2026-06-09 18:16:57 +02:00
Brummel 12b1bcec22 feat(aura-cli): render the root composite like any other composite
Close #49 and remove the last two render special-cases the blueprint root still
carried, both pre-0024 vestiges. Since cycle 0024 the root is an ordinary
Composite with input_roles, so the render no longer needs to treat it apart:

(A) Fan-in slot stubs at the root. A composite-interior multi-input leaf already
    renders its wired slots as #… stubs; a top-level leaf did not, because
    render_blueprint threaded stub_ctx: None. It now passes the root composite
    (it IS a &Composite, carrying roles + edges), and stub_ctx drops its Option
    — both render_graph callers pass &Composite. The existing slot_source/
    fan_in_identifiers/signature_of serve a top-level leaf verbatim; no second
    stub path (#49 acceptance). SimBroker now renders [SimBroker(#E,#price)]
    (#E = the Exposure producer's sibling-unique signature prefix, #price = the
    price source role name).

(B) Role-named root entries. render_blueprint built entry markers from
    format!("source:{kind}") while render_definition uses role.name — the
    marker/stub asymmetry. The root now builds entries from role.name (filtered
    to bound roles, source.is_some()), identical to the interior, so the source
    marker reads [price] and matches the #price slot stub it feeds.

render_compilat is deliberately untouched: post-inline the role name has
dissolved (C23), so the compiled view keeps [source:F64] from the flat
SourceSpec.kind — [price] pre-inline / [source:F64] post-inline is C23 made
visible, and compiled_view_golden is the byte-unchanged negative control.

Design call (orchestrator, with the user): the marker/stub asymmetry was fixed at
its source (the entry naming) rather than by special-casing the stub to match the
kind-named marker — the latter would have been a root-only stub behaviour,
violating the "no second stub path" acceptance. The user chose full symmetry
([price], dropping the kind annotation) over a name+kind marker, prioritising
structural cleanliness; the only remaining root-vs-interior distinction is the
bound-role filter (C3), which is semantic, not cosmetic.

Scope: crates/aura-cli/src/graph.rs only; read-only render (C9),
behaviour-preserving for the run path (C1), no engine change.
blueprint_view_golden re-captured from the live `aura graph` output (the
where: section stays byte-identical — only the main graph re-flows); the
SimBroker needle, a root-SimBroker-stub assertion on the macd view, and a [src]
role-name-passthrough assertion are added.

Verification (orchestrator-run, not agent-reported): cargo build --workspace
green; cargo test --workspace 150 passed / 0 failed (same count, no run-path
test moved); cargo clippy --workspace --all-targets -D warnings clean.
compiled_view_golden and render_compilat byte-untouched.

closes #49
2026-06-09 18:11:22 +02:00
Brummel cd569aa6d0 plan: 0025 render root like any composite
Two-task plan for spec 0025 (#49). Task 1 is one compile unit in
crates/aura-cli/src/graph.rs: thread the root composite as stub_ctx (drop the
Option, update both render_graph callers) and name root entries by role.name
instead of source:{kind}; gate is `cargo build -p aura-cli` (goldens stay red
until Task 2 — the golden-recapture exception). Task 2 re-captures
blueprint_view_golden from the live `aura graph` output, updates the SimBroker
needle, adds a root-SimBroker-stub assertion to the macd test and a [src]
role-name-passthrough assertion to reused_composite_defined_once; gate is
`cargo test --workspace` + clippy. compiled_view_golden is the byte-unchanged
negative control (render_compilat path, C23). plan-recon mapped exact line
numbers and flagged the source:{kind} golden twin (blueprint changes /
compiled must not) and the stale, out-of-scope render_clustered.txt fixture.
2026-06-09 17:56:51 +02:00
Brummel ed1d22bbfa spec: 0025 render root like any composite (slot stubs + role-name entries)
Settled source: issue #49, enabled by cycle 0024 (1b39093, root is now a
Composite with input_roles). The cycle removes the last two render special-cases
the root still carries, both pre-0024 vestiges:

(A) render_blueprint threads stub_ctx: None, so a top-level multi-input leaf
    (SimBroker) renders bare [SimBroker] instead of the #… slot stubs an interior
    leaf already gets. Fix: pass the root composite as stub_ctx (it IS a
    &Composite since 0024); stub_ctx drops its Option (both render_graph callers
    pass &Composite). The existing slot_source/fan_in_identifiers/signature_of
    serve a top-level leaf verbatim — no second stub path (#49 acceptance #3).
    -> [SimBroker(#E,#price)] (#E = exposure producer's signature prefix,
    #price = the source role name).

(B) render_blueprint names root entries source:{kind} while render_definition
    names interior entries by role.name — the marker/stub asymmetry. Fix: build
    root entries from role.name (bound-only filter retained), identical to the
    interior. -> [price] instead of [source:F64], byte-symmetric with how the
    same role renders in a where: definition.

render_compilat is left as [source:F64]: post-inline the role name has dissolved
(C23), only the kind survives — [price] pre-inline / [source:F64] post-inline is
C23 made visible, and compiled_view_golden is the negative control.

Scope: crates/aura-cli/src/graph.rs only. Read-only render (C9),
behaviour-preserving for the run path (C1); no engine change. Grounding-check
PASS (10 assumptions ratified against green tests). Render goldens are the
accepted value-asserted regression; closes #49 on land.
2026-06-09 17:47:18 +02:00
Brummel d6f59d7a52 audit: cycle 0024 (#43, #36) — drift-clean; ledger reconciled to the pre-build signature
Architect drift review (862882b..1b39093): NO code drift, no contract weakened.
C1 (behaviour-preserving: 150 tests green, render goldens byte-identical, no
behavioural assertion altered), C8 (sink output: vec![] preserved), C9/C23
(read-only render touches no eval/build; derive_signature's Box::leak is
cold-path-only, leaked names non-load-bearing) all hold. Regression scripts:
none configured (no-op) — the architect review is the gate.

The only drift was design-ledger prose, anticipated and deferred to this audit
by the plan. Resolved here (fix path, orchestrator-applied — docwriter is barred
from the design ledger):

- docs/design/INDEX.md C8 Guarantee: rewritten — a node's signature (NodeSchema)
  is declared pre-build on the value-empty recipe; the built node implements
  lookbacks() (the one param-dependent sizing quantity) + eval(); schema() is gone.
- INDEX.md C8 cycle-0015 note: Blueprint::param_space -> Composite::param_space;
  the #36 lockstep-debt clause marked closed by 0024.
- INDEX.md: added a C8 cycle-0024 realization (signature lives once on the recipe,
  the signature/sizing split, #43/#36 closed) and a C19 cycle-0024 realization
  (struct Blueprint collapsed into the root Composite; Role.source/runnable-iff-
  bound/UnboundRootRole; FlatGraph as the named compilat). Stale symbol names in
  the dated 0016/0017 notes annotated in place with their 0024 renames.
- aura-std/src/lincomb.rs module doc: Blueprint::param_space -> Composite::param_space.

Ledger-gap (four new load-bearing invariants previously unrecorded) closed by the
two new realization notes. Green baseline re-confirmed after the edits: cargo test
--workspace 150 passed / 0 failed; cargo clippy --all-targets -D warnings clean.

refs #43 #36
2026-06-09 13:02:42 +02:00
Brummel 1b3909316e feat(aura): node signature lives in the blueprint; collapse Blueprint into Composite
Consolidate the node data structure so every node's signature (NodeSchema:
inputs/output/params) is declared once and exists in the blueprint pre-build,
and dissolve the special "root graph" type. Behaviour-preserving (C1).

Signature vs sizing
- NodeSchema is now the static signature only: InputSpec -> PortSpec{kind,firing},
  with lookback removed. The signature is fully static per blueprint (input
  kinds/firing, output fields, params); LinComb's variable arity is a builder arg,
  not an injected param.
- The one param-dependent quantity, an input's buffer lookback (e.g. Sma's window =
  its injected length), moves out of the signature to Node::lookbacks() -> Vec<usize>,
  read only by bootstrap for sizing. Node::schema() is removed.
- LeafFactory -> PrimitiveBuilder, which carries the full NodeSchema. The built node
  no longer re-declares it: closes the params-declared-twice drift (#36, the 8
  per-node factory_params_match_built_node_schema lockstep tests are deleted — their
  subject is now structurally impossible) and a value-empty recipe exposes its full
  I/O interface pre-build (#43).

Root is just a bound composite
- struct Blueprint is deleted; its compile/bootstrap/param_space methods move onto
  Composite. Role gains source: Option<ScalarKind> (None = open interior port,
  Some = bound ingestion feed). A composite is runnable iff every root role is bound;
  the "main graph" is no longer a category, only the fully-source-bound composite.
  New error CompileError::UnboundRootRole for an open root role.
- BlueprintNode::signature() answers uniformly for both arms: Primitive returns the
  builder's declared schema, Composite derives it from the interior (role kinds in,
  OutField kinds out, aggregated params), pre-build, no build.

compile -> FlatGraph -> bootstrap
- compile validates structure pre-build via signature() (validate_wiring: range +
  kind, returning the same variants as before, so an edge kind fault is now caught
  before any build closure fires) and emits FlatGraph{nodes,signatures,sources,edges}.
- bootstrap consumes the FlatGraph: kinds/firing/output from the carried signatures,
  buffer depth from node.lookbacks(). SourceSpec survives as the flat descriptor.

Renames: BlueprintNode::Leaf -> Primitive, LeafFactory -> PrimitiveBuilder.

Render (aura-cli/src/graph.rs) is migrated compile-only: it takes &Composite, maps
bound roles to the same source-entry shape, so both render goldens reproduce
byte-identical output (no re-capture needed). Render-fidelity tuning is the next cycle.

Verification (orchestrator-run, not agent-reported): cargo build --workspace green;
cargo test --workspace 150 passed / 0 failed; cargo clippy --workspace --all-targets
-D warnings clean. All pinned determinism/run-output tests pass with values unchanged;
no behavioural assertion was altered to go green. 5 new tests assert the signature is
pre-build and uniform, that compile rejects a kind mismatch without building (via a
panicking builder), UnboundRootRole, and lookbacks()/signature arity agreement.

Deferred to cycle-close audit (per plan): docs/design/INDEX.md and some aura-std
module docs still name the old Node::schema()/LeafFactory/BlueprintNode::Leaf/
Blueprint::param_space contracts; prose reconciliation is the architect's at audit.

closes #43 #36
2026-06-09 12:49:22 +02:00
Brummel c7fc2f6a37 plan: 0024 node signature in blueprint
Five-task, compile-gate-sequenced plan for the behaviour-preserving refactor:
aura-core types (PortSpec/PrimitiveBuilder/NodeSchema/lookbacks) -> aura-std
8 nodes -> aura-engine lib (Composite absorbs Blueprint, signature/derive_
signature, FlatGraph, pre-build validate_wiring, bootstrap reshape) -> engine
tests -> aura-cli/ingest + compile-only render. Per-crate partial build gates;
workspace gate only in the final task. One working-tree unit (committed once
after Task 5 green).

refs #43 #36
2026-06-09 11:30:38 +02:00
Brummel 78d68fa8ad spec: 0024 node signature in blueprint
Consolidate the node data structure so every node's signature (NodeSchema:
inputs/output/params) exists in the blueprint pre-build, declared once.

- Signature vs sizing split: NodeSchema becomes the static signature
  (InputSpec -> PortSpec, lookback removed); the one param-dependent quantity
  (input buffer lookback) moves to a build-time Node::lookbacks() query.
  Node::schema() is removed.
- PrimitiveBuilder (ex-LeafFactory) carries the schema; the built node no
  longer re-declares it -> closes the params-declared-twice drift.
- Blueprint collapses into Composite: Role gains source: Option<ScalarKind>;
  the root is the fully-bound composite (no "main graph"); new error
  UnboundRootRole.
- compile -> FlatGraph -> bootstrap: FlatGraph carries node + signature in
  parallel; bootstrap sizes from lookbacks(), kind-checks from the signatures.
- Renames: BlueprintNode::Leaf -> Primitive, LeafFactory -> PrimitiveBuilder.

Behaviour-preserving (C1). Render is out of scope beyond compiling (next cycle).

refs #43 #36
2026-06-09 11:09:06 +02:00
Brummel 862882bc04 audit: cycle 0023 (#48) — drift reconciled; render paths unified
Architect drift review (bb90c42..481172a): no contract drift. C9 (render
reads structure + label()/params()/output() only, never eval), C8 (sink =
zero-output leaf, no special case), C12 (blueprint = root composite is
render-structural only; types stay distinct), C23 (consumer-prefix mirrors
the := binding), #38 (composites stay opaque) — all hold. Three fidelity
items, resolved:

- [high, fixed] spec acceptance over-claimed: the SimBroker slot-stub box read
  as in-scope though the work is deferred. Spec amended — the stub acceptance
  is annotated against #49, the met boxes checked.
- [medium, recorded] no docs/plans/0023: this cycle ran spike→refactor under
  user steering instead of planner→implement, because the edge-label-vs-
  consumer-side fork needed an empirical probe (ascii-dag silently drops edge
  labels on collision) before a placeholder-free plan was possible. A plan
  RECORD (not a task plan) now holds the 0023 slot so the counter pairing
  stays intact and the next cycle takes 0024.
- [low, carry-on] multi_output_field_name's leaf-producer fallback is latent
  and untested (no multi-output leaf in the corpus). Carried as debt.

Regression scripts: none configured (no-op). Build/test/clippy green.
Deferred stub work is tracked in #49 (user-signed).

closes #48
2026-06-09 00:15:50 +02:00
Brummel 481172ae2e feat(aura-cli): unify blueprint main-graph render through one shared core
render_blueprint and render_definition were two divergent paths: the main
graph drew bare factory.label() leaves and bare edges, the composite interior
drew enriched leaves (params, slot stubs, output bindings). They now delegate
to one shared render_graph over borrowed slices — the blueprint is the root
composite, differing only at the borders (ParamNames::Factory vs Aliases,
Entry unifying SourceSpec and Role, empty OutField list = no bindings).

LeafFactory holds a Box<dyn Fn>, so it is not Clone — an owned root-composite
adapter is impossible; the shared core takes borrowed slices instead.

The main graph now shows what the strategy does: a multi-output producer's
selected field surfaces as a consumer-side prefix ([histogram -> Exposure(scale)]),
mirroring the := output-binding form. Edge labels are deliberately avoided —
ascii-dag 0.9.1 silently drops an edge label it cannot place without collision
(arena_render.rs can_place_label), unreliable in a dense graph.

Deferred: top-level blueprint multi-input leaves render without #-slot stubs
(stub_ctx None) — fan_in_identifiers is still composite-coupled via signature_of.

refs #48
2026-06-09 00:09:17 +02:00
Brummel 5dd9503e40 spec: 0023 unify blueprint main-graph render
The CLI blueprint main-graph render is a separate, poorer path than the
composite-definition render: bare leaves, bare edges, dropped from_field.
Unify both through the composite machinery — the blueprint is the root
composite, differing only at the borders (sources, factory params, sinks
as zero-output terminal leaves). Harness signature header deferred.

refs #48
2026-06-08 23:04:38 +02:00
Brummel bb90c42028 audit: cycle 0022 (#46) — drift reconciled; output bindings shipped
Architect drift review (range 69d2094..HEAD), regression gate empty
(no scripts configured). Status: drift_found, all resolved here or queued.

What holds: C8/C23 intact — output_binding folds OutField.name onto the
producer label as a pure render symbol; compiled_view_golden byte-identical,
no name reaches the compilat; the drawn where: graph is now exactly the
computation DAG (false terminals + node-count inflation gone).

Resolved (doc reconciliation, this commit):
- docs/design/INDEX.md: two stale render descriptions brought current —
  the C9/0018 realization said output names render as `[out:<name>]`
  markers; the C19/0017 realization said `[in:k]`/`[out]` port markers.
  Both superseded by the `name := producer` binding form (the `[out]`
  staleness is cycle-0022's; the adjacent `[in:k]` was pre-existing drift
  from 0019/0020 and is reconciled in the same clause while here).
- docs/specs/0022: post-ship note added — its Components/Testing strategy
  under-counted the blast radius, missing the full-render golden
  blueprint_view_golden (forced re-capture, caught at implement).

Queued (not fixed): output_binding's tuple arm `(a, b) := …` is spec'd +
shipped but unexercised (no fixture re-exports >1 field from one node) —
filed as #47 (idea).

cycle 0022 drift-clean after this reconciliation (not a milestone close).
2026-06-08 17:20:18 +02:00
Brummel 3b496883e6 feat(aura): render composite output re-exports as producer bindings
In the blueprint view's `where:` section, a composite's output re-exports
now fold onto their producing node's label as a binding
(`[macd := Sub(#Ef,#Es)]`) instead of each becoming a standalone terminal
node wired from its producer. An OutField re-exports an existing interior
node's value — it is never a new node — so the old form drew false
terminals (MACD's macd line feeds the signal EMA and histogram internally
yet rendered as a dead-end leaf) and inflated the node count by the output
arity (MACD where: 8 -> 6 nodes; sma_cross drops its [cross] stub + edge).
The drawn graph is now exactly the computation DAG; the `:=` prefix is the
visual signal that a node's value escapes the boundary. Input roles stay
standalone entry nodes (no producer to annotate) — the asymmetry is
deliberate.

render_definition drops its per-OutField node+edge loop; a new
output_binding helper yields the `name := ` / tuple `(a, b) := ` prefix.
Pure render layer: C8/C23 untouched, OutField.name never reaches the
compilat, compiled_view_golden byte-identical, MACD run deterministic and
unchanged in metrics.

Test blast radius was wider than spec 0022 §Components / the plan's Scope
note counted: besides the MACD pinning test, four more sites assert a
producer that carries an OutField and so gain the `:=` prefix —
blueprint_view_defines_each_composite_once, the two fan-in identifier
tests, the cli_run integration test, and (missed by the plan, caught at
implement) the full-render blueprint_view_golden. All re-pins are forced
by the design, zero judgement. The output_binding tuple arm is unexercised
by current fixtures (no composite re-exports >1 field from one node).

Verified: cargo test --workspace 0 failed; clippy --all-targets
-D warnings clean.

closes #46
2026-06-08 17:16:02 +02:00
Brummel b2f27f8590 plan: 0022 composite output binding render
RED-first: re-pin all five render assertions whose producer carries an
OutField (macd, sma_cross cross, two fan-in `o` outputs — across main.rs
+ cli_run.rs) to the `:= ` binding form, then fold output_binding onto
the producer label in render_definition. Blast radius wider than spec
§Components named (flagged for audit).

refs #46
2026-06-08 17:10:54 +02:00
Brummel 7d4740e8b2 spec: 0022 composite output binding render
Render composite output re-exports as producer-label bindings
(`name := <producer-label>`) instead of standalone terminal nodes:
folds the output name onto its producing node, dropping the false
terminals and the node-count inflation (MACD where: 8 -> 6). Pure
render layer; C8/C23 untouched, compiled view byte-identical.

refs #46
2026-06-08 17:04:16 +02:00
Brummel 69d20949c0 tidy(aura-engine): single-own alias-validity + shared alias-count anchor
Collapse the two correctness-neutral debt items the 0021 fan-in pre-pass
left behind (architect drift review at cycle close).

Alias-validity: the `BadInteriorIndex` check was raised in two places —
the structural pre-pass `check_composite_fan_in` and `inline_composite`.
Since `compile_with_params` runs the pre-pass over every composite before
any lowering, `inline_composite`'s copy was dead for that error path. The
check now lives in one helper `check_alias_indices`, called solely by the
pre-pass; `inline_composite` drops its copy (and the now-unused
`param_aliases` binding — the overlay is a pure naming layer, unused in
lowering, which is driven by the injected scalar vector).

Alias-count: the per-node `a.node == node` predicate was re-derived in
three spots (signature base, the unaliased-param test, the CLI render
base-length). All three now route through one exported anchor
`aliases_on`, so an alias-semantics change touches one site.

Behaviour-preserving: the full workspace suite is the guard (15+6 cli,
25 core, 69 engine, 6+1+1 ingest/misc, 29 std — 0 failed),
`out_of_range_param_alias_rejected` still green (now raised by the
pre-pass), clippy -D warnings clean. No ledger change (the C9 refinement
landed in 0021).

closes #45
2026-06-08 16:38:09 +02:00
Brummel 04c2280dd8 audit: cycle 0021 (#44) — drift-noted; fan-in distinguishability shipped
Architect drift review over efbe0f4..3f4d756 (the two un-audited interim
render commits 32ac8a3/b73d707 plus the 0021 fan-in cycle).

Holds:
- C23 intact — the IndistinguishableFanIn check is a construction-phase
  pre-pass; the compilat stays name-free (compiled_view_golden byte-stable).
- C9 boundary clean — signature distinguishability lives at the blueprint
  layer and dissolves at inline; the C9 ledger refinement is accurate and
  in-format.
- Pre-pass placement (before the arity gate, not inside inline_composite) is
  the spec-aligned home, not drift: a no-param compile() of a param-bearing
  composite would hit ParamArity first.

Regression: commands.regression is empty (no gate) — architect is the sole
gate; clean of contradictions.

Debt (correctness-neutral, not blocking) tracked in #45 for a focused tidy
iteration rather than rushed at cycle tail:
- inline_composite's alias-validity loop is now dead for BadInteriorIndex (the
  pre-pass raises it first for every composite).
- per-node alias count is re-derived in three sites (signature_of base,
  leaf_has_unaliased_param, signature_base_len).

refs #45
2026-06-08 15:53:42 +02:00
Brummel 3f4d756ed2 feat(aura): fan-in input distinguishability — construction constraint + source-derived render
A fan-in node (>1 input) whose colliding sources hide an unnamed configuration
axis is now illegal at construction, and the definition view renders each fan-in
input as a source-derived recursive-signature identifier instead of the
positional, meaningless #A/#B.

The root defect was sma_cross: two Sma into a Sub with unaliased length params,
rendering sma_cross() -> (cross) with two indistinguishable [SMA] and
[Sub(#A,#B)] — the author meant fast vs slow but the identity hid it. Now it
renders sma_cross(fast:i64, slow:i64) with [SMA(fast)]/[SMA(slow)] and
[Sub(#Sf,#Ss)].

Shape (single source of truth for "signature" shared by engine + CLI):
- aura-engine signature_of(): a node's recursive authoring identity — type
  initial + alias initials + each wired input's signature (recursing into
  interior leaves, stopping at named roles/composites at their initial).
- aura-cli leaf_label/fan_in_identifiers: shortest sibling-unique prefix of a
  source's signature, never below its base (type + alias initials); a role-fed
  slot renders its name verbatim (#price); equal-signature interchangeable
  inputs keep the positional letter.
- aura-engine IndistinguishableFanIn: a signature collision is a fault only when
  a colliding source carries an unaliased param (the unnamed axis); param-less
  interchangeable sources (fan_composite's Pass/Pass) stay legal.

Param-aware criterion (refined during design): keeps the blast radius to the
param-bearing alias-less fixtures (sma_cross CLI + engine, the nested fast_slow);
fan_composite and the hand-wired flat fixtures stay green.

Placement decision (deviates from the plan): the constraint runs as a structural
pre-pass (check_fan_in_distinguishability) at the head of compile_with_params,
BEFORE the param-arity gate — not inside inline_composite as the plan drafted.
Reason: the no-param compile() of a param-bearing composite would hit ParamArity
first and preempt the fault; the spec mandates a structural check needing no
param values, so the pre-pass is the spec-aligned home. Alias-validity ordering
(BadInteriorIndex before the fan-in fault) is preserved at the pre-pass head.

C23 untouched: the check is construction-phase; the compilat stays name-free
(compiled_view_golden byte-stable). Ledger C9 carries the refinement.

Known debt (non-gating): the alias-index validity check now exists in both
inline_composite and the pre-pass (the pre-pass reaches every composite first,
making inline_composite's copy effectively dead). Left for a separate tidy — a
5-line, correctness-neutral removal with its own test surface.

closes #44
2026-06-08 15:50:19 +02:00
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