Commit Graph

104 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

refs #12
2026-06-05 13:36:34 +02:00
Brummel ff1dce5a9b docs(design): compilation/compilat reading of composites + C23 optimisation contract
Settle the Construction-layer milestone's design fork before any spec is written:
a composite is a blueprint fragment compiled away by inlining, not a runtime
sub-engine Node.

- C23 (new): the bootstrap is a compilation -- a param-generic, named blueprint
  is lowered to a flat, type-erased compilat wired by raw index; composites
  inline (C9). The compilat is the target of behaviour-preserving optimisation
  (C1 is the correctness invariant) on two levels: intra-compilat (CSE/DCE) and
  across the sweep family (loop-invariant prefix hoisting -- the sweep-invariant
  sub-graph computed once and shared via C11/C12). Passes are deferred follow-on;
  this milestone builds the representation only.
- C9 refined: "a composite is itself a Node" is an authoring-level identity; the
  nested Box<dyn Node> sub-engine reading is explicitly rejected (it keeps the
  interior opaque to the cross-graph optimiser and adds a runtime sub-loop the
  flat model does not need).
- C19 refined: the blueprint->instance binding is a compilation; "no recompile"
  means no Rust/cdylib rebuild -- re-deriving an instance per param-set is a
  cheap graph re-compilation, not a code recompile.
- Names are non-load-bearing debug symbols (as FieldSpec.name already is); the
  wiring resolves by raw index, names survive only for tracing/rendering.
- CLAUDE.md domain-invariant 11 pulled coherent with the compilat reading.

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

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

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

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

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

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

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

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

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

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

closes #15
2026-06-05 00:10:42 +02:00
Brummel 4552d0c9c2 test(aura-cli): RED for self-identifying RunManifest.commit
RunManifest.commit comes from option_env!("AURA_COMMIT") defaulting to
"unknown", so the C18 manifest's identity field -- which code produced
this run -- is a bare placeholder in a normal build. C8's audit-trail
invariant (this run = this commit) needs the manifest to point back at
its source once runs are archived.

This RED pins the desired contract structurally: a normal `aura run`'s
manifest.commit CONTAINS the current git HEAD sha (obtained in-test via
`git rev-parse HEAD`) and is NOT the bare "unknown" literal. Structural
(contains, not equals) on purpose: the working tree may be dirty when
the test runs (this test file itself was uncommitted at author time), so
a build that appends a "-dirty" suffix must still pass. GREEN is a
build.rs in aura-cli capturing HEAD at compile time, "unknown" retained
only for the no-git case.

refs #15
2026-06-05 00:07:37 +02:00
Brummel e4eb70f083 feat(aura-cli): --help/-h print usage to stdout and exit 0
`aura --help` and `aura -h` are a newcomer's reflex first command but
fell through to the catch-all unknown-subcommand arm: exit 2, empty
stdout, error text on stderr. The conventional help affordance returning
the error path is a C22 first-contact friction (milestone fieldtest).

Add a `Some("--help") | Some("-h")` arm before the catch-all that prints
the usage to stdout and returns normally (exit 0). The `_ =>` error path
is untouched, so an unknown subcommand still prints `aura: usage: aura
run` to stderr and exits 2 -- pinned by the negative-preservation
assertion in the RED test (07ba20a). Verified: cli_run.rs all green
(help/no-args/run), clippy --all-targets -D warnings clean.

Minimal slice per the tdd handoff: the stdout help literal ("usage: aura
run") and the stderr error literal ("aura: usage: aura run") are left as
two distinct user-facing strings rather than factored into a shared
usage constant -- a single usage source is a deliberate non-goal here,
left for a future cycle if one wants it.

closes #20
2026-06-05 00:04:54 +02:00
Brummel 07ba20a991 test(aura-cli): RED for --help/-h success-path affordance
`aura --help` and `aura -h` are a newcomer's reflex first command, but
today they hit the unknown-subcommand error path: exit 2, empty stdout,
one-line `aura: usage: aura run` on stderr. For a C22 "newcomer sees a
populated trace" milestone the conventional help affordance returning
the error path is a first-contact friction.

This compile-clean RED pins the desired success-path contract: `--help`
and `-h` print non-empty usage to stdout and exit 0, and the usage names
the `run` subcommand. A negative-preservation assertion keeps an unknown
subcommand (`frobnicate`) on the exit-2 error path so the fix cannot
regress bad-args handling. Asserts structural facts only (exit code,
non-empty stdout, mentions `run`), not exact help prose.

refs #20
2026-06-05 00:03:10 +02:00
Brummel ac4a8c68cf docs(aura-ingest): precise None contract for load_m1_window
The rustdoc claimed 'None if the symbol has no data in [from_ms, to_ms]',
implying a bar-level None. Empirically (cycle-0011 fieldtest) None is
file-level: it propagates data-server's own None, returned only when no
archived file overlaps the window (far-future window or unknown symbol).
A window that overlaps a loaded file but holds zero bars returns
Some(M1Columns) with empty columns, not None.

Document the distinction precisely (chosen over collapsing in-coverage-
empty into None): None propagates data-server's one file-level meaning
unchanged -- 'no data source to read from' -- rather than overloading it
with a second aura-level 'source present but empty here' meaning. So
Some/None means 'data source present' vs 'nothing to read from', not
'has bars' vs 'has none'; a consumer testing for an empty window must
check cols.close.is_empty(). Doc-only, no behaviour change; ledger
untouched (the contract is not referenced there).

closes #18
2026-06-04 23:44:56 +02:00
Brummel 8bd5d0a3f9 fix(aura-ingest): express unbounded M1 window bounds via Option<i64>
load_m1_window took (from_ms: i64, to_ms: i64) and forwarded
Some(from_ms)/Some(to_ms) into stream_m1_windowed, throwing away the
underlying API's per-bound Option<i64> (None = unbounded). There was thus
no way to express an open upper bound, and the natural i64::MAX sentinel
overflowed chrono's from_timestamp_millis inside data-server's coarse
unix_ms_to_year_month file filter, panicking the whole ingest.

Thread Option<i64> through both bounds unchanged, so None = unbounded
matches the underlying contract and 'to the end of history' needs no
sentinel. Rejected the alternative of clamping a too-large bound to a
ceiling constant: a ceiling is not actually unbounded (it silently drops
any post-ceiling bar) and reintroduces a magic literal; restoring the
Option makes the open bound structurally expressible instead.

The committed RED test (2e38106) is now GREEN: it reads AAPL.US's latest
archived month with None as the upper bound and asserts bar-for-bar
equality with the finite sane bound. Verified: cargo test --workspace
green (unbounded_window and real_bars both ran the real /mnt/tickdata
path, not skipped); clippy --all-targets -D warnings clean.

Out of scope, left untouched: the None-vs-Some(empty) rustdoc semantics
(#18), and three out-of-workspace frozen fieldtest crates that still call
the old bare-i64 signature -- those are dated historical replay records
and are intentionally not rewritten.

closes #23
2026-06-04 23:41:41 +02:00
Brummel 2e381060fa test(aura-ingest): RED for unbounded M1 upper bound
load_m1_window cannot express an open upper bound: it hardcodes
Some(from_ms)/Some(to_ms) into stream_m1_windowed, so callers reach for
the i64::MAX sentinel, which panics upstream in chrono's
from_timestamp_millis (data-server records.rs:28). This compile-RED pins
the desired Option<i64> contract (None = unbounded) and asserts an
unbounded read equals the finite sane bound bar-for-bar.

refs #23
2026-06-04 23:37:49 +02:00
Brummel 4fac529de0 fieldtest: milestone walking-skeleton — 3 e2e scenarios, delivers end-to-end
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.
2026-06-04 22:25:01 +02:00
Brummel 77ad977623 fieldtest: cycle-0011 — 3 examples, 5 findings
Per-cycle fieldtest of the aura-ingest ingestion boundary (issue #7), from the
public interface only. 3 examples (transpose/normalize core; close_stream ->
deterministic harness run with epoch-ns end-to-end; load_m1_window over real
AAPL.US 2006-08 bars + None paths), all green. 0 bugs, 0 friction.

Two spec_gaps named for follow-up (neither a code defect; both filed to the
tracker):
- load_m1_window returns Some(empty M1Columns) — not None — for an in-coverage
  but bar-empty window. The rustdoc's "None if no data in [from_ms,to_ms]" reads
  bar-level but the realized boundary is file-level (it inherits data-server's
  file-skip None). A consumer matching None == "no bars" is wrong for the
  in-coverage-empty case. -> tighten the load_m1_window rustdoc (one line).
- AAPL.US (the carrier/integration-test symbol) has only ~21 M1 bars/month, so
  the SMA-cross never warms and the end-to-end run yields all-zero (degenerate
  but valid) metrics. The boundary is correct (C2 warm-up); the milestone's
  shipped demo data is too thin to show a non-degenerate trace. -> weigh against
  C22's "newcomer sees a populated trace" before the Walking-skeleton close
  (document expected density or name a denser demo symbol/window).
2026-06-04 22:04:48 +02:00
Brummel 63ea7eb3b1 audit: cycle 0011 tidy — record the external-dependency firewall invariant
Architect drift review (f68258b..HEAD) found no code drift — the firewall is
real at the dependency-graph level (aura-core/std/engine/cli carry no external
dep; the chrono/regex/zip tree is confined to aura-ingest), and C3/C7/C1 are
faithfully realized. Four ledger-level items, all resolved here:

- [ledger-drift] The zero-external-dependency commitment — the load-bearing
  rationale for making aura-ingest a crate rather than a feature-gate — lived
  only in spec 0011. Recorded in the ledger: C16 now states the engine workspace
  is zero-external-dependency by commitment, names aura-ingest as the ingestion
  edge / external-dependency firewall, and forbids an external dep in any engine
  crate other than aura-ingest.
- [ledger-drift] C16's reuse taxonomy did not place aura-ingest. C16 now lists
  the non-node engine crates (aura-engine, aura-cli, aura-ingest).
- [ledger-debt] C12's eager-materialization-now / Arc<[T]>-sharing-later choice
  was only in the spec. C12 now carries a "Status (cycle 0011)" note recording
  the deliberate gap.
- [debt] aura-engine/Cargo.toml carried a stale comment ("data-server enters
  when the ingestion task starts") that contradicted the firewall it should
  protect. Replaced with the dependency-pure / firewall-in-aura-ingest note.

The External components data-server bullet is updated to reality (pulled in by
aura-ingest; workspace now resolves from a populated cargo cache, not fully
offline).

Regression gate: no-op (commands.regression empty); architect is the sole gate.
Cycle 0011 is drift-clean. (Drift-clean, not a milestone close — the
Walking-skeleton milestone close additionally needs its end-to-end milestone
fieldtest, a separate deliberate act.)
2026-06-04 21:56:29 +02:00
Brummel a24729e97f feat(aura-ingest): data-server M1 ingestion at the one merge boundary
aura's first real data source (closes #7, Walking-skeleton milestone). A new
aura-ingest workspace crate transposes data-server's AoS M1Parsed records into
SoA base columns (C7), normalizes Unix-ms to canonical epoch-ns at the one
ingestion boundary (C3), and feeds the existing k-way merge a real close-price
stream so a backtest runs over real bars, deterministically (C1).

Surface:
- unix_ms_to_epoch_ns(time_ms) -> Timestamp (= ms * 1_000_000), the single C3
  unit normalization.
- M1Columns: the OHLCV bar as a bundle of base columns (open/high/low/close/
  spread: f64, volume: i64, ts: epoch-ns) — SoA, C7.
- transpose_m1(&[M1Parsed]) -> M1Columns: pure AoS->SoA (C1).
- M1Columns::close_stream() -> Vec<(Timestamp, Scalar)>: the price input the
  SMA-cross sample strategy consumes.
- load_m1_window(server, symbol, from_ms, to_ms): drains data-server's
  chronological chunks, transposes once at the boundary.

Design (per spec 0011):
- Eager materialization, not a lazy/shared Source abstraction: C12's cross-sim
  Arc<[T]> sharing has no consumer until the multi-sim orchestration cycle;
  deferred, the transpose logic carries over.
- aura-ingest is the external-dependency firewall. data-server transitively
  pulls chrono + regex + zip (+ their trees) into Cargo.lock; isolating it in
  this one crate keeps aura-core/std/engine zero-external-dependency and the
  frozen deploy artifact (C6 replays recorded streams, never re-ingests) clean.
  cargo test --workspace now needs a populated cargo cache (one Gitea fetch,
  done).
- Scope: boundary + tests only, no aura run CLI arg surface (M1 first; tick and
  the CLI wiring are follow-ups).

Tests: 6 hermetic unit tests on hand-built M1Parsed (normalization, field-wise
transpose, purity, close_stream order, empty edges) + one gated integration
test (tests/real_bars.rs) that runs the cycle-0007 sample harness over real
AAPL.US 2006-08 close bars and asserts finite metrics + two-run bit-identical
JSON (C1); it skips cleanly where /mnt/tickdata is absent. On this machine it
ran the real path. Workspace gates green: test (86), clippy -D warnings, doc
-D warnings.

Minor: transient unused-import warnings in the new lib.rs across the additive
build steps resolved once load_m1_window consumed the imports; final -D warnings
gate clean.
2026-06-04 21:52:33 +02:00
Brummel 44aa90be00 plan: 0011 data-server M1 ingestion boundary
Placeholder-free 6-task plan for spec 0011 (refs #7). T1 scaffolds the
aura-ingest crate + workspace member + unix_ms_to_epoch_ns (proves the
data-server git dep resolves); T2 M1Columns + transpose_m1; T3 close_stream;
T4 load_m1_window (data-server adapter, compile-gated against the real API);
T5 the gated real-bars integration test (cycle-0007 sample harness over real
AAPL.US close bars, finite + deterministic, skips where data absent);
T6 full-workspace gates. Mirrors report.rs build_two_sink_harness with the
shipped aura_std::Recorder. External data-server signatures verified against
its source in the spec commit.
2026-06-04 21:47:06 +02:00
Brummel df0574a75d spec: 0011 data-server M1 ingestion at the one merge boundary
First real data source (refs #7, Walking-skeleton milestone). A new
`aura-ingest` crate pulls data-server as a cargo git dependency, transposes
its AoS M1Parsed records into SoA base columns (C7), normalizes Unix-ms to
canonical epoch-ns at the one ingestion boundary (C3), and feeds the existing
k-way merge a real close-price stream so a backtest runs over real bars (C1).

Decisions captured:
- Eager materialization (not a lazy/shared Source abstraction): C12's cross-sim
  Arc<[T]> sharing has no consumer until the multi-sim orchestration cycle, so
  building it now would be speculative infra; deferred, transpose logic carries
  over.
- aura-ingest is the external-dependency firewall: data-server transitively
  pulls chrono+regex+zip; isolating it in its own crate keeps core/std/engine
  zero-external-dep. cargo test --workspace now needs a populated cargo cache.
- Scope: boundary + tests only, no aura run CLI arg surface (would force the
  unsettled #16 arg-parse decision); M1 first, tick deferred.

grounding-check returned BLOCK on the single load-bearing external assumption
(the data-server public API) — structurally unratifiable by an aura-side test
because data-server is brand-new this cycle; all 9 aura-internal assumptions
were ratified by named green tests. Overridden after verifying data-server's
API against its actual source (lib.rs DataServer::new/has_symbol/
stream_m1_windowed/next_chunk/DEFAULT_DATA_PATH; records.rs M1Parsed fields,
types, Copy), per the agent's documented override path. User-approved.
2026-06-04 21:41:58 +02:00
Brummel f68258b044 fieldtest: cycle-0010 — 3 examples, 6 findings
Public-interface-only field test of the walking-skeleton closing seam (#8):
the `aura run` CLI and the reusable aura-std::Recorder sink. A standalone
consumer crate (path-deps on the three engine crates, as a C16 project would)
drives the real binary as a subprocess and wires Recorder via the raw
Harness::bootstrap API.

Examples (all built from HEAD and ran green):
- c0010_1: drive `aura run` as a downstream CLI/jq consumer — single-line JSON
  report, byte-identical across two runs (C1), bad-args → exit 2 + exact usage on
  stderr with empty stdout.
- c0010_2: wire Recorder onto Sma(3) via raw bootstrap, drain the mpsc::Receiver
  — rows match the rustdoc warm-up model exactly.
- c0010_3: Recorder over [I64, Bool, Timestamp] — the four-kind claim (never
  exercised by the CLI's f64-only path) holds, verified from rustdoc alone.

Findings: 0 bugs, 4 working, 1 spec_gap, 1 friction.
- [spec_gap] `aura run <trailing-arg>` is accepted (exit 0), not rejected — the
  arg parse keys only on the first token being `run` and ignores the rest. The
  cycle_scope's "any other or missing subcommand -> exit 2" does not unambiguously
  cover `run <junk>`; the binary chose the lenient reading. Tracked for a
  ratify-or-tighten decision.
- [friction] verifying the documented {manifest, metrics} JSON nesting needs a
  hand-rolled string walker — the zero-dep consumer has only to_json() and no
  typed read-path. Low-priority tidy.

Both non-bug findings filed to the forward queue; neither blocks the cycle.
2026-06-04 20:55:28 +02:00
Brummel 1433b13495 audit: cycle 0010 tidy (clean)
Architect drift review over 7a8d209..559903a (aura run CLI, #8): drift-clean,
carry-on. Regression gate is a no-op (commands.regression empty); architect is
the sole gate and found nothing actionable.

What holds against the ledger:
- C22 closing seam genuinely delivered: `aura run` bootstraps a CLOSED harness
  WITH two sinks and emits a populated, non-trivial trace (one exposure sign
  flip, a real drawdown) — a newcomer sees a real trace, as C22 promises.
- C16 respected: Recorder ships in aura-std as a domain-free universal block
  (parameterised over ScalarKind/Firing/Sender, no instrument/strategy concepts);
  sample_harness lives in aura-cli, not aura-engine, so the engine stays
  domain-free while wiring stays plain Rust (C17/C20 — raw Harness::bootstrap, no
  DSL).
- C7 purity: mpsc::Sender out-of-graph handle, no Rc/RefCell. C8 sink contract:
  output: vec![], eval returns None. C14 structured face: real binary → canonical
  JSON on stdout, exit-code contract integration-tested. C1 determinism pinned.

Acknowledged, spec-deferred debt (tracked, not silent drift) — filed to the
forward queue:
- Three near-identical Recorder definitions now coexist: the shipped aura-std
  block (the strict superset — four kinds) plus the two #[cfg(test)] engine
  fixtures (f64-only) in report.rs / harness.rs. The spec explicitly defers
  de-dup; flagged for a later tidy.
- manifest.commit is option_env!("AURA_COMMIT") → "unknown" — the C18 identity
  field is a placeholder until build.rs git capture (named non-goal).

The one implementer deviation (a scoped #[allow(clippy::type_complexity)] on
sample_harness) is consistent with the identical allow on aura-engine's
build_two_sink_harness fixture — not drift.
2026-06-04 20:47:28 +02:00
Brummel 559903a14e feat(aura-cli): aura run — end-to-end sample-harness CLI
The Walking-skeleton milestone's closing seam: a real `aura run` binary that
bootstraps a built-in sample harness, runs it deterministically, and prints the
cycle-0009 metrics+manifest report as canonical JSON to stdout — the headline
C14 "run a sim, emit structured metrics" move, end-to-end, from a binary for the
first time.

Two deliverables:

- aura-std::Recorder — a reusable recording sink node (the glossary *sink* role,
  C8/C22): a pure consumer (output: vec![]) over kinds.len() input columns,
  holding an mpsc::Sender<(Timestamp, Vec<Scalar>)> as its out-of-graph
  destination. mpsc keeps the engine's purity invariant (C7): no Rc/RefCell.
  Supports all four base scalar kinds; returns None until every column is warm.
  Promotes the shape the #[cfg(test)] fixtures already proved into a shipped,
  reusable block (the fixtures stay as historical snapshots; de-dup is a later
  tidy).

- aura-cli `run` subcommand — synthetic_prices / sample_harness / run_sample /
  main. The sample harness (synthetic source → SMA(2)/SMA(4) → Sub → Exposure →
  SimBroker → two Recorder sinks) is authored in plain Rust over the raw
  Harness::bootstrap(nodes, sources, edges) API (C17/C20) — the open
  experiment-builder DSL thread is deliberately not committed this cycle. main
  hand-parses one subcommand: `run` prints run_sample().to_json() (exit 0),
  anything else prints a one-line usage to stderr (exit 2). aura-cli gains
  aura-std + aura-core path deps; the workspace stays zero-(external-)dependency.

The built-in synthetic stream (7 ticks, rises then reverses) yields a non-trivial
demo trace: equity [0,0,0,0,-0.08,-0.17,-0.13] → total_pips -0.13, max_drawdown
0.17, exposure_sign_flips 1. The run_sample unit test pins the integer flip count
exactly and the two f64 metrics within 1e-9 (the real run's float dust is ~7e-15,
confirming the tolerance); determinism is pinned exactly (two runs → identical
JSON). tests/cli_run.rs drives the built binary for the observable exit/stdout
contract.

manifest.commit is filled from option_env!("AURA_COMMIT") (defaults to
"unknown"); real HEAD capture via build.rs is deferred. Non-goals untouched:
experiment-builder DSL, aura new, Aura.toml schema, the data-server source (#7).

One deviation from the plan's verbatim code: a scoped
#[allow(clippy::type_complexity)] on sample_harness (its (Harness, Receiver,
Receiver) return tuple trips the lint under -D warnings) — consistent with the
identical allow on the build_two_sink_harness fixture in aura-engine. Verified
myself: cargo test --workspace (61 green, 0 red), clippy --all-targets
-D warnings (clean), cargo doc -D warnings (clean), and both smoke runs.

closes #8
2026-06-04 20:45:30 +02:00
Brummel 9159a550a3 chore: ignore fieldtest crate build artifacts (fieldtests/*/target/) 2026-06-04 20:40:21 +02:00
Brummel 93195aa04b plan: 0010 aura run CLI
Placeholder-free, verbatim-code plan for cycle 0010 (#8). Two deliverables in
dependency order, four tasks:

1. aura-std::Recorder — a four-kind recording sink (pure consumer, output:
   vec![], holds an mpsc::Sender), mirroring the existing #[cfg(test)] fixture;
   wired into lib.rs alphabetically; two unit tests (f64 capture after warm-up,
   None-until-all-columns-warm).
2. aura-cli run subcommand — synthetic_prices / sample_harness / run_sample /
   main over the raw Harness::bootstrap API (no builder DSL), + aura-std/aura-core
   path deps; a unit test pinning determinism and the hand-computed metrics.
3. tests/cli_run.rs — integration test driving the built binary (run -> exit 0 +
   single-line JSON; no args -> exit 2 + usage stderr).
4. Workspace gates (test / clippy -D warnings / doc -D warnings).

The chosen synthetic stream (7 ticks, rises then reverses) is traced tick-by-tick
in the plan: equity [0,0,0,0,-0.08,-0.17,-0.13] -> total_pips -0.13, max_drawdown
0.17, exposure_sign_flips 1. The integer flip count is pinned exactly; the two
f64 metrics within 1e-9 (dust ~1e-15); determinism pinned exactly.

refs #8
2026-06-04 20:39:50 +02:00
Brummel 4a78170dfb spec: 0010 aura run CLI
Cycle 0010, the Walking-skeleton milestone's closing seam: an `aura run`
subcommand that bootstraps a built-in sample harness (synthetic source →
SMA-cross → Exposure → SimBroker → recording sinks), runs it
deterministically, and prints the cycle-0009 metrics+manifest report as
canonical JSON to stdout.

The crux the cycle resolves: no recording sink node ships today (recording
lives only as #[cfg(test)] fixtures), and Harness::run returns () with a sink
as the only data-out path. So this cycle first ships a reusable
aura-std::Recorder (a pure consumer holding an mpsc::Sender, purity-preserving
per C7), then wires the CLI on top.

Load-bearing decisions, user-approved at the brainstorm gate:
- Recorder ships in aura-std (universal block, C16), not engine or CLI-local.
- Sample harness is authored as a plain Rust constructor over the raw
  Harness::bootstrap API (C17/C20) — the open experiment-builder DSL thread is
  deliberately NOT committed this cycle.
- Zero-dependency CLI: hand-parsed args (no clap); manifest commit from
  option_env!("AURA_COMMIT") defaulting to "unknown" (build.rs git capture
  deferred).

Grounding-check PASS (all load-bearing codebase assumptions ratified by green
tests). Non-goals: experiment-builder DSL, aura new, Aura.toml schema, the
data-server source (#7), build.rs git capture.

refs #8
2026-06-04 20:29:29 +02:00
Brummel 7a8d2097e7 docs(aura-engine): document to_json JSON schema + integer-token rendering
Resolves the two doc-level findings from the cycle-0009 fieldtest
(docs/specs/fieldtest-0009-run-metrics.md), both the same doc pass on
RunReport::to_json's rustdoc:

  - spec_gap: the JSON key names and {manifest,metrics} nesting were not on the
    public surface — a consumer parsing the JSON (the C18 registry, the deferred
    aura run printer) could not author against it from rustdoc alone. Now stated:
    keys mirror the struct field names in fixed order, window is a 2-element
    [from,to] array, params is an insertion-order object, with a worked sample
    object. Ratifies field-name-mirroring as the contract for the C14 face.
  - friction: a whole-valued f64 renders without a fractional part (12.0 -> 12),
    so one f64 field may appear as an integer or decimal token across runs. Now
    documented, with the consumer guidance to parse as a number, never key off
    token shape.

Doc-only change; no behaviour change. The sample is a `text` fence (not a
doctest). Verified: RUSTDOCFLAGS="-D warnings" cargo doc -p aura-engine clean;
clippy -p aura-engine --all-targets -D warnings clean; doctests 0.

refs #6
2026-06-04 19:06:59 +02:00
Brummel a982b96ecc fieldtest: cycle-0009 — 4 examples, 6 findings
First fieldtest of the run-metrics + manifest report surface. A standalone
downstream-consumer crate (fieldtests/cycle-0009-run-metrics/) path-depends on
the engine crates and exercises the post-0009 surface from the public interface
only (rustdoc + ledger + glossary + project layout, never crates/*/src).

Primary axis empirically met: the north-star "a run emits metrics + manifest"
move is reachable from rustdoc alone — drain two recording sinks -> f64_field ->
summarize -> RunManifest -> to_json, metrics matching the hand model on the first
run, deterministic across reruns.

Findings: 0 bugs, 1 friction, 1 spec_gap, 4 working.
  - working x4: north-star reachable from rustdoc; SimBroker firing/slot docs (a
    resolved 0007 gap) now carry the example; summarize metric definitions exact
    on six degenerate inputs (incl. negative-curve drawdown + flat-as-sign-0);
    f64_field panics precise and well-located.
  - spec_gap: to_json's JSON key names + {manifest,metrics} nesting are not on
    the public surface — a consumer parsing the JSON (C18 registry, the deferred
    aura run printer) cannot author against it from rustdoc alone.
  - friction: to_json renders whole-valued f64 without a decimal point (3.0 ->
    "3"), so one f64 field appears as integer or decimal token within one schema.

Both doc-level findings are the same doc pass and matter mainly for the deferred
aura run (#8) and the C18 registry that will parse this JSON. Spec feeds the next
plan as reference.

refs #6
2026-06-04 19:06:13 +02:00
Brummel 4dc1526196 audit: cycle 0009 tidy (clean)
Architect drift review over 521a16e..80d7bfb (run-metrics-and-manifest):
no drift, no debt. The shipped report surface matches the ledger:

- Pure-additive as claimed: lib.rs gains only `mod report;` + one re-export
  line; harness.rs `run` signature is untouched (`-> ()`). No engine / Harness
  / node-contract change — the cycle-0006 sink mechanism (C8/C22) is consumed
  read-only, not altered.
- C1/C12: summarize is a total, pure, value-only reduction; the e2e test
  asserts bit-identical metrics + JSON across two runs.
- C18 sequencing: ships manifest + metrics "from day one", explicitly defers
  the registry/index to its later milestone. C14 structured face delivered as
  hand-rolled JSON; the zero-dependency stance is preserved (all four crates
  path-only deps).
- params-as-honest-precursor wording matches the 0008 typed-param-space
  deferral (node.rs); f64_field panic-as-wiring-bug matches the engine's
  existing "checked at wiring" convention.

Forward note (not drift, no contract speaks to it, spec documents it as an
accepted assumption): to_json renders f64 via `{}` and is valid JSON only under
the finite-value invariant — a NaN/Inf would emit bare NaN/inf tokens. The
invariant holds today (SimBroker integrates finite returns, Exposure clamps),
so C14 "machine-readable" is not breached. Worth a debug-assert or named
constant only if a future broker can produce non-finite equity — flag it then.

Regression gate: profile commands.regression is empty — no-op; architect is the
sole gate and is green. carry-on.
2026-06-04 18:58:56 +02:00
Brummel 80d7bfbbf0 feat(aura-engine): run metrics + manifest report surface
Cycle 0009 (Walking skeleton milestone). A run's two C18 "from day one"
artefacts — a reproducible manifest and summary metrics — land as a new,
pure-additive `report` module in aura-engine:

- summarize(equity, exposure) -> RunMetrics: a post-run pure reduction over a
  run's recorded pip-equity + exposure streams. total_pips (last cumulative
  value), max_drawdown (worst running-peak-to-trough), exposure_sign_flips (a
  turnover proxy: adjacent normalized-sign changes, flat distinct from
  long/short via a three-way sign0). Total and pure: empty -> zeros, identical
  inputs -> identical metrics (C1/C12).
- RunManifest: the reproducible descriptor (commit, params as name->value
  pairs, data-window, seed, broker label). Caller-supplied — the engine cannot
  introspect a git commit or seed. params is the honest precursor to the
  deferred typed param-space (node.rs; 0008 audit).
- RunReport { manifest, metrics } + to_json(): canonical, hand-rolled JSON for
  the structured C14 face.
- f64_field(rows, field): bridges a recording sink's Vec<Scalar> rows to
  summarize; panics on a kind/width mismatch (a wiring bug), like the engine's
  other "checked at wiring" violations.

Why post-run, not a node: a node emits one record per eval with no terminal
eval (C8), so an end-of-run reduction has no home as a node; the World drains
its cycle-0006 recording sinks after Harness::run and folds them here. Matches
C18's "store manifests + metrics, re-derive results on demand".

Why hand-rolled JSON, not serde: the workspace is deliberately zero-dependency
and the schema is tiny/closed/flat; serde is reconsidered when the run-registry
milestone grows the schema. (User-approved at spec review.)

Scope: the surface + a determinism end-to-end test reusing the cycle-0007
two-sink harness; the `aura run` subcommand that prints the report is #8. No
engine/Harness/node-contract change; workspace stays zero-dependency.

Verified: cargo test --workspace green (aura-engine 40, incl. 10 new report
tests); clippy --all-targets -D warnings clean; RUSTDOCFLAGS=-D warnings cargo
doc clean.

closes #6
2026-06-04 18:57:14 +02:00
Brummel cb66708d26 plan: 0009 run metrics + manifest
Six-task plan for the cycle-0009 report surface (issue #6): module scaffold +
RunMetrics/RunManifest/RunReport types (Task 1), the pure summarize reduction
(Task 2, RED-first), the f64_field recorded-stream adapter (Task 3, RED-first),
RunReport::to_json canonical zero-dep JSON (Task 4, RED-first), an end-to-end
determinism test reusing the cycle-0007 two-sink harness (Task 5), and the full
workspace gates (Task 6). Pure-additive in a new aura-engine report module; the
re-export line grows per task so the crate compiles at every boundary.

refs #6
2026-06-04 18:51:04 +02:00