Collapse the three verbatim-duplicated SMA-cross harness test fixtures
(synthetic_prices, sma_cross, composite_sma_cross_harness) in aura-engine's
blueprint.rs and sweep.rs #[cfg(test)] modules into one shared crate-root
#[cfg(test)] mod test_fixtures. Behaviour-preserving, test-only.
Auto-signed under /boss: all objective gates green (precondition, self-review,
grounding-check PASS) and a unanimous five-lens spec-skeptic panel returned
SOUND.
refs #53
Six bite-sized tasks for spec 0037: aura-core records a BoundParam (original slot
position) in bind + a bound_params() accessor; aura-engine emits a conditional
"bound" field via scalar_str; graph-viewer.js merges free+bound params into slot
order and renders the bound slot dimmed as name=value; re-capture the sample-model
fixture; a new headless render guard (trailing + middle bind); full-workspace gate.
refs #63
Surface a bind-bound param in the graph model and the `aura graph` viewer
signature instead of dropping it. A node built with `.bind(slot, value)` keeps
the slot off the tunable surface (param_space / sweep axes — unchanged, correct)
but now ANNOTATES it in the render: all slots shown, the bound one as `name=value`,
dimmed. The cycle-0036 `blend` renders `LinComb[weights[0], weights[1], weights[2]=0.5]`.
Structurally a twin of cycle 0035's instance-name thread: a conditional field
(here `"bound"`) threaded engine -> model -> viewer, plus goldens and a headless
render guard. Render/debug surface only — dropped at lowering (C23), model stays
deterministic (C14).
Auto-signed under the /boss spec auto-sign gate: precondition clean (the one open
notation decision was settled with the user in-session and recorded as a
provenance-bearing reconciliation comment on the issue), self-review + parse-trace
clean, grounding-check PASS, and a unanimous five-lens spec-skeptic panel.
refs #63
Five tasks for the sample-showcase cycle: (1) the enriched blueprint source —
LinComb import, the `signals` composite (trend = sma_cross, momentum = macd, blend
= LinComb(3) with weights[2] bound), the node-0 swap in sample_blueprint_with_sinks,
the re-pathed sweep_family axes + a showcase_prices warm-up stream; (2) re-path the
two in-crate tests (bootstrap-runs-drains, sweep odometer); (3) re-capture the
sample-model.json render fixture; (4) a new headless depth-2 nesting guard
(viewer_nested_depth.mjs + .rs); (5) the workspace build/test/clippy gate.
refs #62
Enrich the `aura graph` / `aura sweep` sample blueprint into a "trend + momentum,
weighted blend" strategy that showcases four authoring/viewer capabilities the
single-level sample left dark: multiply-nested composites, a multi-param node
inside a composite, a multi-output (MACD-like) node, and bind() (a param fixed as
a structural constant, dropped from the sweep surface). Pure authoring over
already-shipped primitives — no engine semantics or viewer change; reuses the
existing sma_cross/macd builders.
Auto-signed under /boss (spec auto-sign enabled): objective gates green
(precondition clean, self-review clean, grounding-check PASS) and a unanimous
five-lens spec-skeptic panel (criterion, grounding, scope-fork, ambiguity,
plan-readiness) returned SOUND. Design provenance recorded on #62.
refs #62
Surface a node's explicit instance name in the `aura graph` viewer: a named leaf
renders its box head as `fast: SMA[length]` (the instance name as a `:`
declaration prefix), an unnamed leaf keeps the bare `SMA[length]`. The name
crosses two surfaces — a conditional `"name"` field in the JSON graph model
(engine half) and a `cellLabel` prefix in graph-viewer.js (viewer half).
Explicit-`.named()`-only; `node_name()`'s lowercased-type default is not
surfaced. `named()`'s non-empty rule is kept with its code unchanged, its doc
re-grounded to the load-bearing invariant (knob-address segment + Some/None
prefix switch).
Signed via the /boss auto-sign gate: all objective gates green (precondition,
self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel
(criterion, grounding, scope-fork, ambiguity, plan-readiness). Two earlier blocks
were resolved before the unanimous round — a separator-ambiguity by an editorial
fix, and a scope-fork (which nodes carry the prefix) by an explicit user decision
recorded as a provenance-bearing reconciliation comment on the issue.
refs #58
Architect drift review over 52e0214..HEAD. All hard invariants hold; the only
drift was a ledger-prose gap — the new .bind() structural-constant affordance had
no contract entry, so the code offered a capability the contracts did not name.
Contracts (all hold):
- C23: bind resolves a param name -> position at authoring time and stores the
index; the compilat stays wired by raw index, the name never reaches it. The
by-name authoring address space (0032 amendment) is exactly where bind sits.
- C19/C8: the shrink is schema.params.remove(pos) on the single source of truth
that params()/schema()/collect_params/lower_items all read, so no desync is
structurally possible; param_space() injectivity (0032) is untouched (bind only
removes entries). The param-declared-once posture holds.
- C9/C10: a pure builder-level value op — no by-name runtime node lookup, no
registry, no DSL-style dynamic resolution.
- C16: zero Cargo.toml / lockfile changes.
- Construction layer (collect_params/lower_items/param_space/compile_with_params)
byte-unchanged — verified independently; the blueprint.rs diff is a test-only
hunk at 1886, the sole production edit is bind in aura-core/src/node.rs.
Regression: none configured (profile regression: []) — no-op; architect is the gate.
Resolution -- fix (1 ledger note, added inline this commit):
- docs/design/INDEX.md C19 — added a cycle-0034 realization note recording
PrimitiveBuilder::bind as the structural-constant path (knob REMOVED from
param_space), distinct from the cycle-0016 value-pin (knob stays, fixed in the
injected vector); the #55 deform-vs-tune discriminator; C23 unaffected; export of
a named frozen strategy deferred to #60.
Cycle 0034 drift-clean after this note. Not a milestone close (no milestone
fieldtest run). Gates: doc-only edit, compile-inert; cargo build/clippy/test
--workspace verified green this session.
Task decomposition for PrimitiveBuilder::bind (spec 0034): T1 the method +
aura-core unit tests (Probe node drives positional reconstruction RED→GREEN;
three should_panic), T2 node-vehicle coverage (Sma/LinComb via eval in aura-std),
T3 Composite param_space coverage + construction-layer zero-change guard +
workspace gate. Crate-topology aware: aura-core unit tests use a local Probe,
not Sma/LinComb (no upward dep).
refs #55
Add PrimitiveBuilder::bind(slot, value): fix a node param as a structural
constant, removed from param_space entirely (not pinned in the injected
vector). Closes the gap #55 names — param-bearing nodes (Sma/Exposure/LinComb)
had no constant path, only the no-sweep-mode SimBroker precedent.
Settled direction (Option B, builder-level): .bind() shrinks the builder's
declared param surface and wraps its build closure to re-splice the constant;
the construction layer (collect_params/lower_items/param_space) is unchanged
because both already key off builder.params(). Addressing is by-name
(authoring address space, C23/0032); compilat stays index-wired.
Auto-signed under /boss spec_auto_sign: objective gates green, grounding-check
PASS, unanimous five-lens spec-skeptic panel SOUND (one editorial repair round:
corrected the harness param_space assertion and pinned the exactly-one-match
bind contract).
refs #55
Architect drift review over 065cf1d..HEAD. Code + contracts hold; the only drift
was documentation cross-references made stale by retiring RunReport's hand-rolled
to_json.
Contracts (all hold):
- C14: to_json still emits canonical, flat, deterministic JSON — now serde-produced.
model_to_json (the graph-model JSON) stays hand-rolled and was correctly untouched.
- C16: serde_json dev-dep → normal-dep in aura-engine is squarely sanctioned by the
amended per-case policy (INDEX.md:576-583) — a vetted standard crate used where it
does the job (incl. the frozen bot), removing a hand-rolled writer. No gap.
- C1: report types have no HashMap; serde is declaration-order/deterministic; the
r1.to_json()==r2.to_json() asserts and the new to_json_equals_serde_disk_shape pin
stay green.
Regression: none configured (profile regression: []) — no-op; architect is the gate.
Resolution — fix (3 prose cross-refs, fixed inline this commit):
- graph_model.rs:5 (module doc) — claimed the model JSON is in the "RunReport::to_json
house style (no serde)"; to_json is serde now. Reframed: the model JSON is hand-rolled
(no serde), and is the engine's last hand-rolled JSON writer since 0033.
- graph_model.rs:30 (json_str doc) — cross-referenced RunReport::json_str, deleted this
cycle. Reworded to describe the escape set directly (graph_model's own json_str stays).
- docs/design/INDEX.md:729 — "golden-tested like RunReport::to_json" framed the hand-rolled
model JSON by a now-serde to_json. Reframed to name model_to_json as the surviving
hand-rolled writer.
Cycle 0033 drift-clean after this tidy. Not a milestone close (the param-sweep milestone
fieldtest is a separate deliberate act). Gates: clippy --workspace --all-targets -D
warnings clean (doc-only edits, compile-inert).
refs #54
Two-task RED→GREEN plan for spec 0033. Task 1 (RED, test-only): flip the engine
canonical-form golden + the CLI sweep golden to the serde array-of-pairs/.0-float
shape, add the to_json_equals_serde_disk_shape pin test — all fail against the
current hand-rolled to_json. Task 2 (GREEN, atomic): promote serde_json dev-dep ->
normal dep, swap to_json's body to serde_json::to_string, delete the now-unused
json_str helper, refresh the method + module-header doc comments — one build+clippy
gate flips every Task-1 assertion green. graph_model.rs:30 + INDEX.md:729 stale
cross-refs deferred to cycle-close audit.
refs #54
Settled-source direct entry from issue #54 (no brainstorm). Retire the
hand-rolled RunReport::to_json writer and render stdout through serde, so a
record's stdout JSON is byte-identical to its runs.jsonl line, and pin that
identity with a test. Removes the last hand-rolled RunReport JSON writer
(amended C16). Disk shape unchanged; graph-model JSON (model_to_json) out of
scope (#58/#28).
Auto-signed under /boss spec_auto_sign: objective gates green (precondition
clean, self-review clean, grounding-check PASS) + unanimous 5-lens spec-skeptic
panel (criterion/grounding/scope-fork/ambiguity/plan-readiness all SOUND). The
grounding juror re-ran serde_json 1.0.150 against the canonical-form fixture and
confirmed the spec's "After" bytes. The one C16-scrutinized consequence
(serde_json dev-dep -> normal dep in aura-engine) is named and justified in the
spec; scope-fork juror confirmed it is settled by amended C16, not a silent pick.
refs #54
A non-injective param_space() — two slots under one path-qualified name — is
the by-name knob address space (C12/C19) being broken: no binding can select
one slot without the other. This cycle makes it a structural compile error.
One check, check_param_namespace_injective over the param_space() names,
replaces the whole fan-in machinery (signature_of, leaf_has_param,
check_fan_in_distinguishability, check_composite_fan_in) and runs from
compile_with_params AND from both binders before resolve/resolve_axes — so the
canonical by-name author sees the structural cure (CompileError::DuplicateParamPath,
carrying the path and pointing at .named(...)) instead of the AmbiguousKnob symptom
(#59). With an injective space guaranteed before resolve, no bound name can
multi-match, so BindError::AmbiguousKnob is dead and removed (both resolver arms ->
unreachable!). param_space() stays infallible; the invariant lives in the compile
step (C23).
The check is strictly the by-name-addressability property. The old fan-in predicate
(equal signature AND >=1 param) also rejected two collisions that are NOT
param_space duplicates — an asymmetric param/paramless collision and a role-vs-leg
collision — which guarded render identity, dead since the renderer was retired in
0026. Both are intentionally dropped; a future node-/wiring-name check, if wanted,
is decoupled from injectivity.
The new invariant correctly rejected a pre-existing fixture (multi_output_composite's
two unnamed Sma legs); cured in place with the .named(fast/slow) the invariant
mandates. A new E2E test drives the cured cross through the by-name binder
end-to-end (resolve + bootstrap + run to a populated exposure trace).
C9/C12/C19/C23 ledger notes amended. Verified: cargo build/test --workspace green
(0 failed), clippy --workspace --all-targets -D warnings clean.
closes#59
Test 5's asymmetric-collision fixture asserted a root-level param_space
["sma.length"] while also being "test 2's fixture with one leg swapped" (nested)
— a contradiction: check_fan_in_distinguishability only descends into composites,
so a root-level collision is not rejected today (not RED-first). Caught at plan
time while writing the verbatim fixture body.
Corrected: test 5 nests the asymmetric collision in an inner composite `asym`
(param-bearing Sma "sma" + paramless Pass1 .named("sma") + Sub, both leaves on
role price fanning into the Sub) under a source-bound root, asserting
param_space() == ["asym.sma.length"] (the single injective entry — the paramless
leg contributes no path) and that compile_with_params(&[I64(2)]) is Ok after.
Re-grounded PASS, re-panel unanimous SOUND. Forward correction on top of 7f059d0
(main is forward-only — no rewind of the boss-signed spec).
refs #59
param_space() injectivity becomes a stated, enforced compile invariant. One
structural check (check_param_namespace_injective) replaces the fan-in machinery
(check_fan_in_distinguishability, check_composite_fan_in, signature_of,
leaf_has_param) and the AmbiguousKnob binder branch. The error is path-carrying
(DuplicateParamPath) and the binders run the check before name resolution, so the
canonical by-name author sees the structural cure (.named(...)) instead of the
AmbiguousKnob symptom. param_space() stays infallible; the invariant lives in the
compile step (C23).
Two render-identity-only fan-in rejections (asymmetric param/paramless collision,
role-vs-leg signature collision) are intentionally dropped — neither is a
param_space duplicate (every knob keeps a unique path), and the property they
guarded (render distinguishability) has had no live consumer since the renderer
was retired in 0026. The larger is pinned by a now-compiles fixture.
Boss-signed under spec_auto_sign: objective gates green (precondition clean,
self-review clean, grounding-check PASS without override) + a unanimous five-lens
spec-skeptic panel. The grounding lens first BLOCKed a false set-equality claim;
the user ratified dropping both render-identity cases, the spec was reframed onto
the injectivity scope, re-grounded PASS, and the re-panel returned unanimous SOUND.
refs #59
Per-cycle fieldtest of node-instance naming (#56), driven as a downstream
consumer from the public interface only (ledger + spec + rustdoc + CLI; no
crates/*/src read), three fixtures built and run from HEAD.
Verdict: the core 0031 promise holds first-try. A consumer authors
Sma::builder().named("fast"), inspects param_space() (sma_cross.fast.length),
binds by name and runs; the default-name case (sma.length / exposure.scale,
verbatim lowercase) and paramless-interchangeable-stays-legal both hold.
0 bugs, 3 working, 2 friction, 1 spec_gap. The one real gap (verified by the
orchestrator against the fixture output): the spec's headline forcing function
IndistinguishableFanIn does NOT reach an author on the canonical
.with(...).bootstrap() flow. An un-named 2-SMA cross emits a literal DUPLICATE
knob (sma_cross.sma.length x2); the binder resolves names before the compile
fan-in check, so the author hits UnknownKnob / AmbiguousKnob("sma_cross.sma.length")
— which point at the knob, not at the cure "name your nodes". IndistinguishableFanIn
only surfaces via the positional compile_with_params path. Rejection still
happens (no invalid blueprint runs), so it is an ergonomic/signposting gap, not
a correctness bug. Routed to the backlog (relates to #58); 0031 stays
audit-closed. Minor: FlatGraph/Harness lack Debug, so a bootstrap Result can't
be {:?}-printed.
Architect drift review of cycle 0031 (node-instance naming; spec 5818497, plan
d890090, impl ffed8cc). Regression gate: no scripts configured (commands.regression
is empty) — the architect is the gate.
What holds: the ParamAlias retirement is complete and surgical — ParamAlias,
aliases_on, check_alias_indices, leaf_has_unaliased_param, Composite.params, and
the Composite::new 5th arg are gone from all code with no orphaned references
(only historical specs/plans 0016-0024 mention them, correctly untouched);
lib.rs re-exports cleaned. C23 preserved in fact: node_name() flows only into
signature_of (fan-in) and collect_params (param path), never into the compilat —
it stays name-free, wired by raw index. C11 value-empty holds: a node name is
construction identity, injected nowhere into the value vector. param_space() is
uniformly <node>.<param>; all call sites migrated in lockstep. 198 tests green,
clippy clean.
Drift fixed (this commit): the C9 fan-in refinement prose still asserted "the
graph view renders each fan-in input as the shortest sibling-unique prefix of its
source signature" — that render died with ascii-dag in cycle 0026; signature_of
now has zero render consumers. Corrected to state signature_of is
construction-phase-only with no render consumer, and that surfacing node identity
in the WASM viewer is tracked follow-up.
Drift routed to backlog: the node instance name does not reach the graph model
(model_to_json emits the type label, so the viewer shows identical [SMA] boxes
for named fan-in legs) — a deliberate 0031 scope decision, filed as #58 (idea,
relates to #13/#37), not a regression.
Cycle 0031 drift-clean after the one fix.
Every blueprint node now carries a name. A primitive builder gains an optional
instance name (Sma::builder().named("fast")); when omitted it defaults to the
node's type label, ASCII-lowercased verbatim ("SMA"->"sma", "SimBroker"->
"simbroker"). param_space() is now uniformly <node>.<param> at every level
including the root (sma_cross.fast.length, exposure.scale). Fan-in
distinguishability (C9) and signature_of re-key onto the node name: two same-type
siblings both defaulting to "sma" collide and IndistinguishableFanIn fires, so
one act -- naming the legs "fast"/"slow" -- fixes both the naming collision and
the fan-in check. The index-addressed ParamAlias overlay, Composite.params, the
Composite::new 5th argument, aliases_on, and check_alias_indices are removed
(Role.name and OutField.name untouched). Ledger C9 and C23 amended.
This is why the change is correct, not merely convenient: blueprints are
value-empty (C11), so the thing that would otherwise distinguish two SMAs --
their length -- is not present at construction; identity must come from a name,
not a deferred value. Closes the #56 friction surfaced by the param-space &
sweep milestone fieldtest (a freshly-authored 2-SMA cross was unbindable and
would not bootstrap without two hand-counted ParamAlias overlays).
Two plan defects were corrected during implementation and verified:
- the three new fan-in/path tests authored sma_cross at root level, which would
qualify to "fast.length" (root prefix is empty) and is not the nested case the
fan-in check inspects; nesting sma_cross under a root (a shared
sma_cross_under_root helper) restores the asserted "sma_cross.fast.length" and
IndistinguishableFanIn{node:2};
- three cycle-0030 named-binder tests bound the real harness by the old names
(sma_cross.fast / scale) and were migrated to the new path strings, surfaced by
the workspace test gate.
Verified by the orchestrator: cargo build/test --workspace green (198 tests,
0 red), clippy --all-targets -D warnings clean. model_to_json emits the type
label + factory param names (node-name-independent), so sample-model.json and the
graph_model golden are byte-identical (untouched). NodeSchema gained a Default
derive (the builder default-name test constructs an empty schema). fieldtests/
are frozen non-workspace records, not migrated.
closes#56
Implementation plan for spec 0031 (#56). Three tasks:
1. aura-core PrimitiveBuilder instance-name mechanism (.named()/node_name(),
default = lowercased type label) — additive.
2. The engine mechanism + the full compile-coupled migration, as one task by
necessity: collect_params node segment, signature_of + fan-in re-keyed onto
the node name, ParamAlias / Composite.params / the Composite::new 5th arg /
aliases_on / check_alias_indices removed, every in-workspace Composite::new
caller and ParamAlias reference migrated, the mirror / top_level (inverted) /
signature / CLI-golden assertions updated — all under one cargo build/test
--workspace gate that enumerates migration completeness.
3. Ledger C9 + C23 amendments.
plan-recon (DONE_WITH_CONCERNS) surfaced two sites the brief missed, both now in
the plan: the sweep-golden twin crates/aura-cli/tests/cli_run.rs:214 (lockstep
with main.rs), and the model_to_json goldens — resolved as OUT of scope (the JSON
model emits the type label + factory param names, both node-name-independent, so
sample-model.json and the graph_model.rs golden stay byte-identical; the fixtures
migrate structurally only). signature_of stays -> String, re-keyed onto the node
name. fieldtests/ are frozen non-workspace records, out of scope.
refs #56
Design spec for #56 — give every blueprint node a name so param-space
path-qualification and fan-in distinguishability both flow from one
author-controlled identity, and retire the index-addressed ParamAlias overlay.
A primitive builder gains an optional name (default = the node's type-label
string, ASCII-lowercased verbatim: "SMA"->"sma", "SimBroker"->"simbroker";
explicit names must be non-empty). param_space() becomes uniformly
<node>.<param> at every level including the root. signature_of and the C9
fan-in rule re-key onto the node name: two same-type siblings both defaulting
to "sma" collide and IndistinguishableFanIn fires correctly, so one act —
naming the legs "fast"/"slow" — fixes both the naming collision and the fan-in
check. ParamAlias / Composite.params / the alias index-check are removed
(Role.name and OutField.name untouched). Ledger C9 and C23 amended. One
cohesive iteration: the engine mechanism + in-workspace migration (CLI sample,
engine tests, sweep goldens, graph_model.rs, sweep.rs, lib.rs re-export) land
together since removing the struct is compile-coupled; fieldtests/ are frozen
non-workspace records, out of scope.
Boss-signed under spec_auto_sign: all objective gates green (precondition,
self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel
(criterion / grounding / scope-fork / ambiguity / plan-readiness) returned
SOUND after one fix round. The panel's first round caught real defects — a
worked example written against a non-existent fluent API, a false [param:]
render-marker claim (the marker was removed in cycle 0020), and an
under-specified compound default name — all corrected and re-grounded before
the unanimous pass.
refs #56
Milestone-scope fieldtest (closing functional gate) for "The World —
parameter-space & sweep" (issues #30-#36). Three curated end-to-end scenarios
derived top-down from the milestone promise, driven from the public interface
only (ledger + specs + rustdoc; no crates/*/src read), built and run from HEAD
via a downstream consumer crate.
Delivery verdict: GREEN. The milestone delivers its promise.
- mw_1 single run bound by name: .with("sma_cross.fast", 2).bootstrap() binds
and runs (12 equity rows); diagnostics precise and knob-named (UnknownKnob /
AmbiguousKnob / KindMismatch).
- mw_2 named-axis sweep + compare: .axis(..).sweep(run) yields a 4-member,
ordered (odometer), disjoint, non-constant SweepFamily; ranking from public
fields. CLI (aura sweep / aura runs rank) delivers the same with no Rust.
- mw_3 structural-constant negative space (deliberate): SMA2-entry's
definitional `2` has no honest expression today -> grounds #55. Out of
milestone scope; not a blocker.
Findings: 0 bugs, 2 working, 3 friction, 1 spec_gap. Verified independently
(re-ran all three from HEAD; confirmed a plain SMA-cross emits sma_cross.length
twice and the shipped CLI sample carries the fast/slow ParamAlias overlays).
Friction (routed to the forward queue, none blocks the gate):
- the promised sma_cross.fast/.slow names are not free — they require
author-added ParamAlias overlays, doubly mandatory because the canonical
2-SMA cross also will not bootstrap without them (IndistinguishableFanIn);
- the sweep closure drops back to positional bind + manual name re-zip.
spec_gap -> #55: the wished-for structural-constant consumer code is recorded
in mw_3 as #55's acceptance evidence.
This commit is the milestone fieldtest itself; it closes no issue. Closing the
tracker milestone stays a deliberate manual act on this green gate.
Iteration-2 plan for spec 0030 (the sweep side): two tasks.
- Task 1 (aura-engine): resolve_axes (shares resolve's name->slot mapping; adds the
EmptyAxis check and a per-element axis kind-check, a superset of GridSpace::new so
the downstream .expect() is infallible), Composite::axis / SweepBinder /
SweepBinder::sweep, the SweepBinder re-export, and the engine tests (round-trip,
EmptyAxis, MissingKnob, per-element KindMismatch on a mixed axis, a builder
no-panic wrong-kind test, and a GridSpace .points() parity test). RED-first via a
todo!() resolve_axes stub. EmptyAxis is now constructed (was defined in iter 1).
- Task 2 (aura-cli): convert sweep_family() grid construction to .axis(...).sweep(...)
(keeping the per-point closure and the local `space` it reads for manifest params);
drop the now-orphaned GridSpace import (the clippy -D warnings trap this iteration).
The sweep JSON param goldens (main.rs + tests/cli_run.rs) are name/value/order-
preserving and stay green unchanged. Engine core untouched (C1/C12/C19/C23).
refs #35
Iteration-1 plan for spec 0030 (the single-run side): two tasks.
- Task 1 (aura-engine): BindError vocabulary, the shared `resolve` core
(two-phase total error order), Composite::with / Binder / Binder::bootstrap,
the lib.rs re-export, and the engine tests (resolve round-trip, one per
single-run BindError variant, two precedence tests, the C1 named≡positional
equivalence test). RED-first via a todo!() resolve stub, GREEN on impl.
- Task 2 (aura-cli): convert the sample single-run test to .with(...).bootstrap()
using the exact param_space() names (sma_cross.fast/sma_cross.slow/scale).
The full BindError enum (incl. EmptyAxis) is defined now and re-exported, so the
unconstructed sweep-only variant is reachable API (not dead_code) — EmptyAxis is
constructed in iteration 2. Engine core untouched (C1/C12/C19/C23).
refs #35
Bind a blueprint's open knobs by name via a fluent builder instead of a
positional, Scalar-wrapped Vec in param_space() order: a single run as
bp.with("sma_cross.fast", 2).with("scale", 0.5).bootstrap(), a sweep as
bp.axis("sma_cross.fast", [2,3]).axis("scale", [0.5]).sweep(run). A pure
authoring layer over the existing bootstrap_with_params / GridSpace / sweep
primitives; the engine core is untouched (C1/C7/C12/C19/C23 preserved).
Ratified design (brainstorm -> specify):
- Fluent builder (.with()/.axis()), not a macro — C10 builder-API idiom.
- Raw literals via Into<Scalar>; the literal fixes the variant (2->I64,
0.5->F64); kind-check is pure equality, no coercion.
- Match key = the EXACT param_space() name (user's Option 1): path-qualified
for composite-interior knobs (sma_cross.fast), bare for root-level knobs
(scale). Short bare names are an authoring choice (promote to a root leaf),
not a job of this layer; no engine change, no unqualified matching.
- .with() not .bind() — bind is reserved for #55's structural-constant overlay.
- Total error order (a-f) over a single BindError vocabulary: Phase 1 validates
bindings (UnknownKnob/AmbiguousKnob/EmptyAxis/DuplicateBinding), Phase 2 walks
slots (MissingKnob/KindMismatch); first failing check wins; sweep kind-check is
per-element, making resolve_axes a superset of GridSpace::new so the downstream
.expect() is infallible.
- Two iterations: single-run side, then sweep-axis side (shared name-resolution
core). The CLI sample stays nested (user's call); the worked example shows both
qualified and bare names honestly, no fixture change.
Auto-signed under /boss spec_auto_sign after the objective gates (precondition,
parse no-op, grounding-check PASS) and a unanimous five-lens spec-skeptic panel.
The panel hardened the spec across five rounds: it corrected the worked-example
names to the real param_space() output, pinned the exact-name match key, and made
the error precedence a total order closing a sweep per-element panic path. The
literal-inference grounding gap was closed separately by e97906a. Two design
points were taken by the user directly (Option 1; keep the sample nested).
refs #35
Cycle-close architect review of 16e31fe..HEAD against docs/design/INDEX.md +
CLAUDE.md. Regression gate: none configured (commands.regression: []), so the
architect is the sole gate.
What holds:
- C18 honoured — aura-registry stores (manifest, metrics) RunReports append-only
to runs/runs.jsonl, reproducible-from-manifest; no git/Gitea duplication, no
multi-project manager. The depth deferrals (lineage, re-derive, run-diff,
run_id, Aura.toml runs-dir) are named in the spec, not silently dropped.
- C1 preserved — the sweep stays disjoint/lock-free; "sweep == N independent
runs" now compares full RunReports; serde_json output is deterministic.
- C12/C19/C20 fit — SweepPoint.report closes cycle C's deferred manifest-per-
point gap; the manifest stays World-supplied (engine `sweep` is `Fn -> RunReport`,
domain-free).
- Workspace green (cli_run 11, registry 5, engine 93, …); clippy clean.
Resolution:
- FIX (this commit): docs/design/INDEX.md "External components" (the data-server
row) still asserted the struck "external-dependency firewall / engine crates
stay external-dependency-free" framing — stale after the C16 amendment (aura-
core/aura-engine now link serde). Rewritten to the amended per-case policy.
The iter-1 commit's "no ledger text still asserts it" missed this mirror; it is
now true (re-grep clean across the ledger and live source).
- FOLLOW-UP (#54, idea): RunReport has two divergent JSON encoders — serde for
the on-disk store, hand-rolled to_json for stdout. This was a deliberate,
spec-named deferral (keep stdout goldens stable this cycle); filed for the
stdout→serde unification that retires the last hand-rolled writer.
Ratify: the C16 amendment (blanket zero-dependency -> considered per-case policy)
is the intentional contract change of this cycle, justified in the 0029 spec and
the iter-1 commit; this audit ratifies it as drift-clean (the codebase no longer
contradicts it anywhere).
closes#33
Final iteration of cycle D: aura sweep persists each point's RunReport to
runs/runs.jsonl; aura runs list prints every stored record; aura runs rank
<metric> prints them best-first. sweep_report splits into a production
sweep_family() + a #[cfg(test)] renderer; process goldens run in a temp cwd so
persistence never dirties the repo; /runs/ is git-ignored.
refs #33
Iteration 2 of cycle D: SweepPoint carries a full RunReport (closure bound ->
RunReport), all engine + CLI callers rethreaded in lockstep so the workspace
returns green; the hand-rolled sweep_point_to_json retired (each point prints
via RunReport::to_json); new aura-registry crate (open/append/load/rank_by +
RegistryError), built and unit-tested but not yet CLI-wired. The aura sweep
goldens pin the per-point manifest params, not the volatile git-HEAD commit.
refs #33
Iteration 1 of the run-registry cycle (#33): lay the dependency + serde
foundation the registry's typed read-path needs, and amend the contract that
forbade it.
Contract change (load-bearing — C18/C16). C16's blanket "zero-external-
dependency by commitment" + "aura-ingest is the sole external-dependency
firewall" framing is struck and replaced by a considered, per-case dependency
policy: dependencies are admitted by deliberate review (what a crate pulls in
vs. what it buys), with particular scrutiny for anything entering the frozen
deploy artifact (C13); standard vetted crates (serde, rayon, ...) pass that
review and are used wherever they do the job, including in the bot; hand-rolling
what a vetted crate does is the anti-pattern. C16's engine/project split +
three-tier node reuse are unchanged. The five source comments that asserted the
struck framing (aura-engine/aura-ingest Cargo.toml + aura-ingest/report.rs docs)
are rewritten to match; no live source or ledger text still asserts it.
Per-case justification for serde (the policy now requires one): it gives the
run-report types a typed (de)serialization path — exactly what ranking/compare
over stored runs needs, and what the writer-only hand-rolled JSON (C14) could
never provide; closes the typed-read-path gap (#17). Its closure is tiny,
ubiquitous, heavily audited, and its output deterministic (C1-safe). serde_json
is test-only this iteration (dev-dependency); no production serde_json yet.
Changes: [workspace.dependencies] serde (derive) + serde_json; serde derives on
Timestamp (aura-core) and RunMetrics/RunManifest/RunReport (aura-engine), with
serde_json round-trip tests (window renders as a [from,to] integer array via the
transparent Timestamp newtype). The hand-rolled to_json writers are untouched
(still drive stdout). No aura-registry crate, no SweepPoint change — those are
iterations 2 and 3.
Verified: cargo test --workspace green (incl. the two new round-trip tests);
clippy --workspace --all-targets -D warnings clean; no surviving assertion of
the struck zero-dep framing in live source or the ledger.
refs #33
Iteration 1 of cycle D: amend C16 (blanket zero-dep -> per-case policy) in the
ledger + the five source comments asserting the struck framing; add serde/
serde_json workspace deps; derive Serialize/Deserialize on Timestamp and the
RunMetrics/RunManifest/RunReport report types, with serde_json round-trip tests.
No CLI change, no aura-registry crate, no SweepPoint change (those are iter 2/3).
refs #33
Cycle D of the milestone "The World — parameter-space & sweep" (#33). A
persistent, append-only run registry: each run's (manifest, metrics) is a
record in runs/runs.jsonl; a read surface lists all records and ranks them
best-first by a named metric — C18's "compare experiments over time", which
has no home in git or Gitea. First cut = persist + list + rank; promotion/
status, lineage, run-diff, on-demand re-derivation, run_id, and Aura.toml
runs-dir wiring are deferred.
Contract-level change: C16's blanket "zero-external-dependency by commitment"
is amended to a considered, per-case dependency policy (scrutinize what enters
the frozen artifact; standard vetted crates like serde/rayon pass that review;
never hand-roll what they do). The engine/project split + three-tier node
reuse of C16 are unchanged. This admits serde/serde_json — the typed read-path
ranking needs, and which closes#17 (typed RunReport handle).
Shape: serde derives on RunManifest/RunMetrics/RunReport + Timestamp;
SweepPoint carries a full RunReport (closing the manifest-per-point gap cycle C
deferred to #33); new crate aura-registry (open/append/load/rank_by); CLI gains
aura runs list / aura runs rank <metric> and aura sweep persists each point.
Three iterations, first planner handoff = iteration 1 (foundation).
Sign-off: spec_auto_sign was enabled for this cycle; the five-lens spec-skeptic
panel returned 3 SOUND + 2 BLOCK (rank-ordering semantics; iteration-cut
compile-ordering + Scalar->f64 coercion). Both BLOCKs were genuine defects,
fixed before sign-off (rank is now best-first per-metric; the closure-type
change lands with its callers in one green iteration; the coercion is shown).
Non-unanimous panel routed the spec to human sign-off, granted by the user.
refs #33
A grid-enumerated family of disjoint instances from one value-empty
blueprint (milestone The World, cycle C). Engine-side GridSpace +
sweep() (std::thread::scope, lock-free disjoint per C1) + SweepFamily;
closure-driven so harness-specific metrics glue stays author-side.
refs #32
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