Iteration 2 of cycle 0026 (graph render redesign). `aura graph` no longer emits
ASCII: it now prints one self-contained `.html` to stdout — the ported prototype
pin-graph viewer (drill / inline-expand / styled tooltips / breadcrumb) driven by
the deterministic model serializer that shipped in iteration 1, with layout and
SVG done in-browser by Graphviz compiled to WebAssembly. Closes the redesign
opened by spec 0026; unblocked by cycle 0027 (input ports are now named, so the
viewer labels every input pin from the model's real names instead of inventing
"a"/"b").
What ships:
- crates/aura-cli/assets/: the ported graph-viewer.js (prototype genDot/chrome
verbatim + a normalizeModel adapter mapping aura's real model tuple shape —
ins = [kind, firing, name], index node keys, `comp` refs, `src_<role>` — into
the viewer's native shape), plus the vendored Graphviz-WASM (@viz-js/viz@3.7.0)
and svg-pan-zoom@3.6.1 blobs and a refresh-assets.sh provenance script.
- crates/aura-cli/src/render.rs: render_html(&Composite) -> String, a read-only
(C9) assembly — model_to_json + include_str! of the inlined assets, no eval,
no build, no serde (C14). The `["graph"]` arm calls it.
- Retired: the ascii-dag adapter (graph.rs), the `ascii-dag` dependency, the
`--compiled`/`--macd` graph flag plumbing, render_compiled, the color/terminal
plumbing, and the 12 ascii-dag-asserting tests + the now-orphaned helpers.
The cli_run integration test now pins the HTML page envelope.
- crates/aura-core/src/node.rs: the two last ascii-dag prose justifications
re-grounded (single-line label rule kept, re-justified generically; the
param-generic rule re-justified on C23, not on a renderer limitation).
- docs/design/INDEX.md: the cycle-0026 C9 realization note (both iterations).
Vendoring decision (spec deferred it to the plan): the WASM/JS blobs are checked
in, not fetched at build time. include_str! needs them at compile time, and a
build-time unpkg fetch would make the build non-hermetic/non-offline and let the
shipped artifact drift with the registry — against C1 (determinism) and C8
(frozen artifacts). ~1.4 MB is the price of a hermetic, frozen render asset.
Three adaptations beyond the plan, each verified: (1) render.rs imports
`aura_engine::Composite` (the path model_to_json takes), not aura_core; (2)
macd_blueprint is now test-only — removing the `--macd` arm left it used solely
by the param-space test, so it took `#[cfg(test)]` rather than removal or an
`#[allow]` suppression (the production `aura run --macd` path is intact, verified
emitting JSON); (3) the `grep ascii-dag` over crates/ matches only the new
contract test, which names the term deliberately to document the retirement — no
stale justification prose remains.
Invariants held (verified independently, not on agent report): C9 (render path
read-only), C10 (viewer is a render asset, no logic/DSL), C4 (four-colour palette
= the four scalar base types), C14 (no serde). The iteration-1 model byte golden
is unchanged and green. cargo build --workspace: 0 errors. cargo test --workspace:
157 passed, 0 failed (incl. the two new HTML tests + the unchanged model_golden).
cargo clippy --workspace --all-targets -- -D warnings: clean. ascii-dag absent
from `cargo tree -p aura-cli`.
closes#51
Iteration-2 plan for cycle 0026 (graph render redesign): port the prototype
viewer, vendor Graphviz-WASM (checked in, for hermetic offline builds), add a
read-only render_html, wire `aura graph` to emit the self-contained HTML, and
retire ascii-dag (adapter, dep, --compiled/--macd flags, 12 ascii-dag tests).
Plus a stale-prose tidy in aura-core and the cycle-0026 ledger note.
refs #51
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
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
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
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.
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.
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.
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.
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.
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
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
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
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
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).
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
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
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
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
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).
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
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
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).
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
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
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).
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
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.
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
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
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.
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
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
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
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
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.
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
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
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.
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).
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
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
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
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
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
Milestone-scope fieldtest (the functional leg of the Walking-skeleton
milestone-close gate), from the public interface only. Three end-to-end
scenarios, all green:
- the newcomer CLI path: `aura run` -> single-line {manifest,metrics} JSON,
exit 0, byte-identical twice (C1), non-degenerate trace (C22), bad-args ->
exit 2;
- the real-data thread: real AAPL.US 2006-08 M1 bars -> load_m1_window ->
close_stream -> SMA-cross -> Exposure -> SimBroker -> summarize -> RunReport,
deterministic AND populated;
- epoch-ns coherence: C3 normalization holds ingest -> run -> sink.
Roll-up: the milestone DELIVERS its promise end to end (ingest -> one signal ->
exposure -> sim-optimal broker -> pip-equity metric -> structured RunReport,
deterministic, populated) on BOTH the synthetic and real-data paths. 0 bugs.
Headline concern (verified by the orchestrator): issue #19's premise is WRONG at
the shipped SMA(2)/SMA(4) demo config — 21 AAPL.US bars warm the cross and
produce a populated trace (total_pips=-1.5, max_drawdown=65.5, 6 sign flips),
not all-zero. The degeneracy #19 saw was deep-SMA/param-dependent, not intrinsic
to the symbol. #19 to be re-scoped. Other findings: `aura run <junk>` exit 0
(already #16), `aura --help` lands on the exit-2 error path (friction), SimBroker
two-f64-slot order unchecked at bootstrap (documented footgun) — both filed.