Commit Graph

67 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 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 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 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 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 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 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 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 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 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 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 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
Brummel bf72924db7 spec: 0009 run metrics + manifest
Cycle 0009 (Walking skeleton milestone, issue #6): a post-run pure-function
reduction over the recorded pip-equity + exposure streams into summary metrics
(total pips, peak-to-trough drawdown, exposure sign-flip count) plus a
caller-supplied RunManifest (commit, params, data-window, seed, broker), paired
as RunReport with canonical hand-rolled zero-dep JSON (C14). Lives in a new
aura-engine report module; no engine/Harness/node-contract change.

The reduction is post-run, not a node (C8 caps a node at one record per eval
with no end-of-run eval); matches C18's manifest+metrics-per-run-from-day-one.
The `aura run` subcommand that prints the report is deferred to #8.

Grounding-check PASS (all load-bearing assumptions ratified by green tests /
authoritative sources). Spec-validation parse pass is a documented no-op (no
parser configured for the profile).

refs #6
2026-06-04 18:42:55 +02:00
Brummel 90e298d3b4 fieldtest: cycle-0008 — 5 examples, 6 findings
First fieldtest of the sum combinators. A standalone downstream-consumer crate
(fieldtests/cycle-0008-sum-combinators/) path-depends on the engine crates and
exercises the post-0008 surface from the public interface only (rustdoc + ledger
+ glossary + project-layout, never crates/*/src).

Primary axis empirically met: the north-star two-signal combine move now uses the
shipped Add (Add::new() dropped in exactly where the 0007 fixture hand-authored
an Add2) — the 0007 boilerplate is retired, end to end through SimBroker to a
deterministic recorded pip curve.

Findings: 0 bugs, 1 friction, 2 spec_gaps, 3 working.
  - working ×3: Add2 retired; LinComb weighted/variadic sums exact (<1e-12);
    warm-up barrier + empty-weights panic as documented.
  - spec_gap: Add/LinComb per-input firing policy (Firing::Any / mode-A as-of
    join) absent from the public surface — same class as the 0007 SimBroker gap.
  - spec_gap: a fired combinator emits one row per *cycle*, so a heterogeneous
    same-timestamp multi-source trace can carry >1 row per timestamp (correct per
    C4/C5, undocumented at this surface).
  - friction: nested consumer crate still needs the empty [workspace] table
    (carried from 0006/0007; #9 closed the docs half; folds into aura new).

Spec feeds the next plan as reference.
2026-06-04 18:21:57 +02:00
Brummel 23e4cdae14 plan: 0008 sum combinators (Add + LinComb)
Bite-sized RED-first plan for the two new aura-std nodes. Task 1 ships Add
(mirrors sub.rs modulo operator), Task 2 ships LinComb (Vec<f64> weights param,
variadic schema, empty-weights panic), Task 3 the crate-wide test/clippy/doc
gates. No engine change.

refs #11
2026-06-04 18:05:56 +02:00
Brummel 288e75e7fd spec: 0008 sum combinators (Add + LinComb)
Ships the missing sum combinator(s) in aura-std so the north-star combine move
(C10) is expressible from shipped blocks. Two nodes: Add (parameterless two-input
f64 sum, companion to Sub) and LinComb { weights } (N-input weighted sum, weights
as tunable params + arity, C8/C12). Both withhold output until all inputs are
present (no implicit cold-leg 0.0).

Resolves the cycle-0007 fieldtest [friction]: the consumer had to hand-author a
project-local Add2 to combine two signals.

refs #11
2026-06-04 18:01:09 +02:00
Brummel fa2835ebcc docs: capture the empty [workspace] table gotcha for nested project crates
A project crate that path-depends on the engine and lives under the engine
repo (common during authoring) is rejected by cargo unless its Cargo.toml
carries an empty [workspace] table. Surfaced repeatedly by the cycle-0006 and
cycle-0007 fieldtests as a non-obvious onboarding one-liner.

Document it on the consumer-facing surface (project-layout, the "A project
repo" section) so a nested-project author can predict and fix it. The aura new
scaffolder will emit the line once it exists (folds into the CLI work); the
docs capture closes the actionable half now.

closes #9
2026-06-04 17:47:45 +02:00
Brummel f3cd2e1320 fieldtest: cycle-0007 — 4 examples, 7 findings
First fieldtest of the signal-quality loop. A standalone downstream-consumer crate
(fieldtests/cycle-0007-signal-quality/) path-depends on the engine crates and
exercises the post-0007 surface from the public interface only (rustdoc + specs +
ledger + glossary, never crates/*/src):

  1 single-signal quality backtest, end to end (SMA-cross -> Exposure -> SimBroker
    -> Recorder pip curve)
  2 Exposure clamp + sizing (hard ±1 saturation, sign preserved)
  3 sim-optimal integration: short-on-falling pays positive pips; pip_size=2 curve
    exactly halves pip_size=1
  4 north-star combine-two-signals (two MA-cross spreads summed into one exposure)

Findings: 3 working (carry-on), 2 spec_gap, 2 friction.
  - spec_gap: SimBroker firing policy + cold-exposure->0.0 warm-up emission shape
    not on the public surface (only in the feat commit body).
  - spec_gap: SimBroker input slot order (0=exposure, 1=price) only in C10 prose /
    commit body; a swapped wiring is not caught at bootstrap (both f64).
  - friction: the north-star "combine two signals" move needs a sum/LinComb
    combinator aura-std does not ship (hand-authored Add2 in the fixture).
  - friction: standalone consumer crate still needs the empty [workspace] table
    (carried from cycle-0006; tracked as #9).

Spec at docs/specs/fieldtest-0007-signal-quality.md feeds the next plan.

refs #4 #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:30:13 +02:00
Brummel e7542864c6 audit: cycle 0007 tidy — add C10 Realization (cycle 0007) note
Architect drift review (f000373..HEAD) found one item: the C10 reframe was
amended in framing but carried no "Realization (cycle 0007)" note recording what
actually shipped — the spec's own ship gate required it (C8/C22 carry their
realization notes; C10 did not). Added: the signal-quality half realized as
Exposure { scale } + SimBroker { pip_size } on the unchanged, domain-free engine,
with the position-management half explicitly confirmed deferred.

Everything else holds: the reframe is propagated consistently across CLAUDE.md
invariant #7, the seven touched glossary entries + the new exposure-stream entry,
and project-layout.md (no stale "position table is the strategy output"
survivor); the engine stays domain-free (the only RefCell hit in aura-engine/src
is a #[cfg(test)] doc comment); C1 determinism and C2 causality are each pinned by
a behaviour test asserting observable output, not internals.

No regression scripts configured (commands.regression empty) — that gate is a
no-op; architect is the gate. Cycle 0007 is drift-clean.

refs #4 #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:18:14 +02:00
Brummel 386e1a9c3d plan: 0007 signal-quality loop (exposure + sim-optimal broker)
Bite-sized, placeholder-free plan for spec 0007: Task 1 ships the Exposure node
(clamp signal/scale to [-1,+1]) in aura-std; Task 2 the SimBroker node (causal
lagged exposure*return -> cumulative pips); Task 3 two aura-engine end-to-end
tests (pip-equity recording with a hand-computed curve + determinism); Task 4 the
full workspace gates (test/clippy/purity grep). Each node's lib.rs module
registration is folded into its own task so every per-task compile gate is
satisfiable.

refs #4 #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:10:14 +02:00
Brummel fe77b13e73 spec: 0007 signal-quality loop (exposure stream + sim-optimal broker)
Cycle 0007 bundles Gitea #4 (exposure node) and #5 (sim-optimal broker) — the two
halves of the signal-quality loop, the realization of the C10 reframe. Ships two
aura-std nodes: Exposure { scale } (clamp(signal/scale, -1, +1)) and
SimBroker { pip_size } (causal prev_exposure * dprice / pip_size integration, no
look-ahead). Engine unchanged — pure node authoring on the frozen substrate.

grounding-check: PASS (all substrate assumptions ratified by green tests).

refs #4 #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:02:33 +02:00
Brummel 0d7deb3a91 design(C10): reframe strategy output to intent/exposure stream; position table becomes a derived layer
The DAG expresses exactly one state at time t and a node emits at most one record
per eval (C8), so a *sequence* of position events — where one decision instant
(a stop-and-reverse) needs a close AND an open at the same event_ts — cannot be
the DAG's per-cycle output without violating C8. The state the DAG can express
faithfully is the desired exposure (one value per cycle); the buy/sell/close
events are its first difference, a derived consequence.

C10 is reframed accordingly: the strategy's primary, backtestable output is an
intent/exposure stream (one signed, bounded f64 in [-1,+1] per cycle). Signal
quality is measured by the sim-optimal broker integrating exposure*return into a
synthetic pip-equity curve. The broker-independent position-event table survives
as a decoupled, derivable, downstream position-management layer (computed table,
not a per-eval output) feeding realistic broker nodes for viability/deploy — no
longer the DAG output nor the signal-quality measure.

Touches the ledger contract (INDEX.md C10 + provenance/milestone/C20 ancillary),
the always-loaded summary (CLAUDE.md invariant #7), the glossary (broker, equity
stream, position table, realistic broker, signal, sim-optimal broker, strategy
reframed + new exposure-stream entry), and the north-star layout doc. Sealed
specs/plans (0001-0006) left as historical record.

refs #4 #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:02:33 +02:00
Brummel f000373507 docs(C8): state output:vec![] as the sink declaration + eval return-width contract
Closes the spec_gap from the cycle-0006 fieldtest
(docs/specs/fieldtest-0006-substrate.md): the public surface never stated that
an empty `output` is THE pure-consumer (sink) declaration, nor defined what a
`Some` return paired with an empty output means.

The behaviour was already principled and defended in the engine — this only
documents it (no code change):

  - `output: vec![]` is the pure-consumer declaration; there is no separate
    Sink type/trait/marker.
  - `eval` must return a row whose width equals `schema.output.len()`, so a pure
    consumer returns `None` or a zero-width `Some(&[])`; the run loop
    debug-asserts the width (harness.rs).
  - field-wise wiring resolves `Edge::from_field` against the producer's output
    at bootstrap, so no edge can bind a field of a zero-output node (it fails
    with `BadIndex`) — a sink is structurally unwireable as an in-graph
    producer; its only output is the out-of-graph side effect.

Ledger C8 gains an "Encoding & return contract" clause; aura-core node.rs
rustdoc (module, NodeSchema, Node trait) updated from the pre-0006 "1..K,
producer or transformer" wording to the three-role (producer/consumer/both)
contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:43:35 +02:00
Brummel 0bf15cb069 fieldtest: cycle-0006 sink recording — 5 examples, 6 findings
First fieldtest of the project. A standalone downstream-consumer crate
(fieldtests/cycle-0006-substrate/) path-depends on the engine crates and
exercises the post-0006 substrate from the public interface only (rustdoc +
specs + ledger, never crates/ source):

  1 custom recording node via the Node contract + Ctx::now()
  2 fan-out/fan-in DAG, 3-arg bootstrap, run() -> ()
  3 two interior streams recorded from one run (the cycle headline)
  4 byte-identical recorded output across two runs (C1 determinism)
  5 mis-wired recording edge rejected (KindMismatch) at bootstrap

Findings: 3 working (carry-on), 2 friction, 1 spec_gap.
  - friction: a nested standalone consumer crate fights the workspace
    resolver (needs an empty [workspace] table — Cargo's own hint).
  - friction: recorder boilerplate is hand-rewritten per author (C9
    deliberately ships no library sink).
  - spec_gap: the public surface never states output: vec![] is THE sink
    declaration, nor defines a Some return paired with empty output.

Spec at docs/specs/fieldtest-0006-substrate.md feeds the 0007 plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:36:50 +02:00
Brummel 3a02fbf383 audit: cycle 0006 tidy (clean) — glossary record-reality (sink is a role)
Architect drift review over 57310b8..HEAD: clean — no drift, no debt.
The C1/C7 boundary is correctly drawn (recorded values escape only via a
node-held mpsc::Sender with no out-edge; nothing recorded re-enters the
graph; the engine surface shrank). C2 holds (Ctx::now() is the present
cycle's ts). The C8/C22 cycle-0006 realization notes accurately describe
what shipped — no overclaim. Test fixtures stayed in #[cfg(test)] (C9).
Regression set is empty (profile) — architect is the sole gate; green.

Record-reality: the glossary 'sink' entry defined a sink as 'a pure
consumer node with no output', which the shipped TapForward node (records
AND forwards an output) now contradicts. Updated to reality: a sink is a
recording *role*, not a type — a node may be a pure consumer or record
and produce in the same eval (C8 'both'). No invention; the entry now
matches the substrate this cycle landed.
2026-06-04 14:41:38 +02:00
Brummel 1100a60c76 feat: recording is a node role, not a type (multi-sink substrate)
Replace the engine's single observe: usize recording affordance with
recording-by-node, so one run records many streams. A recording node
reads its typed input windows + ctx.now() in eval and pushes the record
to a destination it holds as a field (a channel, a chart handle) — an
out-of-graph side effect. There is no Sink type, trait, or engine flag:
a pure-consumer node returns None, and a node may record AND return a
forwarded output in the same eval (the C8 "both" case). In-graph routing
stays engine-owned data (the edge table); the escape out of the graph is
the node's own side effect, and that boundary is the determinism /
graph-as-data boundary.

Engine surface shrinks: Ctx gains now: Timestamp + now() (C2-causal, the
present cycle's timestamp); Harness loses the observe field, its
observe >= n bootstrap check, and the per-cycle observed-row collection;
bootstrap drops its 4th param; run returns (). Recorded streams are now
sparse and timestamped (a record per fired cycle) instead of the dense
Vec<Option<row>>.

The Ctx::new signature change touched 9 call sites across three crates
(not the 3 the spec estimated) — aura-std's node tests and the engine
run loop were threaded too. The engine test suite migrated to a
test-local Recorder fixture whose read-back is an mpsc channel, never
Rc/RefCell, keeping aura-engine/src purity-clean (C7). Eight new proof
tests cover the multi-sink headline, producer-and-sink, mixed-kind
recording, all-fields tap, both recorder firing modes, determinism, and
recorder-edge kind rejection. C8/C22 gain cycle-0006 realization notes.

Gates: workspace test 45 green (core 20, std 3, engine 22), clippy
-D warnings clean, purity grep clean (only a comment names Rc/RefCell).

closes #2
2026-06-04 14:38:26 +02:00
Brummel 7b8e8010dd plan: 0006 sink recording
Six tasks: (1) aura-core Ctx::now(); (2) thread the Ctx::new signature
change through aura-std's five test call sites; (3) aura-engine
production shrink (remove observe, run -> (), thread the run-loop Ctx);
(4) migrate the engine test suite to a test-local Recorder fixture +
drained channel (the finish-threading task — the test module is one
compile unit, so it restores the full cargo test); (5) eight new
node-recording proof tests; (6) ledger C8/C22 realization notes + the
ship gate (workspace test, clippy, purity grep).

Recon found the spec undercounts Ctx::new call sites (9, not 3): the
aura-std and aura-engine run-loop sites also break on the signature
change and are threaded in tasks 2-3.

refs #2
2026-06-04 14:28:14 +02:00
Brummel 22ee99abcd spec: 0006 sink recording
A sink is a role, not a type: recording is an out-of-graph side effect a
node performs in eval, pushing to a destination it holds as a field. No
Sink type, no trait, no engine sink-awareness. The engine stays the
router for in-graph routing (edge table = graph-as-data); the node-side
push is the only out-of-graph escape, and that boundary is the
determinism boundary. A node may be producer and sink at once (C8 both).

Engine surface shrinks: Ctx gains now(); the observe index (field,
bounds check, per-cycle collection) is removed; bootstrap drops its 4th
param; run returns (). Recording streams are sparse and timestamped
(only fired cycles), replacing the dense Vec<Option<row>>.

Realization notes land on C8 and C22; no new contract.

refs #2
2026-06-04 14:11:00 +02:00
Brummel 57310b83c7 audit: cycle 0005 tidy — glossary record-reality (node-output-record)
Architect drift review over 9ff90b5..5228542 (node-output-record): drift_found,
all low-severity, NO contract violation. The load-bearing invariants hold:

- C7 forbids intact — the reused `scratch` buffer (cleared+extended, warm after
  cycle 1) and node-owned `[Scalar; K]` output buffers give zero per-cycle heap
  alloc on the inter-node forward path; no fifth scalar type, no dyn-Any, no
  heterogeneous buffer (the TwoField mixed-kind producer is rejected at bootstrap,
  never streamed). Purity grep clean. The sharpened C7 guarantee text matches the
  shipped per-field kind check + the Edge::from_field scatter.
- C8 revised text faithful — one output port (out_len), record of 1..K columns
  (Vec<FieldSpec>), field-wise binding only (N edges, no whole-record bind), scalar
  = degenerate K=1. No internal contradiction with C7/C9/C10.
- C6 untouched, verified against the run loop — one eval per node per cycle, every
  out-edge stamps SlotState { fresh_at: cycle_id, last_ts: ts } with the same ts,
  so the K fields of one record are co-fresh by construction. fires() unchanged.
- C3/C4/C5/C9 unperturbed (diff touches only node/edge plumbing); C2 lookback
  enforcement unperturbed (column.rs/ctx.rs/any.rs not in the diff).
- C10 coherence confirmed — a position event is a 5-field record
  (event_ts/action/position_id/instrument_id/volume), exactly the K-field
  base-column record C8/C7 now sanction; the decision/sizing node is an ordinary
  K=5 producer, the broker an N-edge field-wise consumer. No contract gap blocks
  the next consumer; C10 correctly out of scope this cycle.

Resolved [low] (the one confirmed drift) — glossary record-reality. The ledger
C7/C8 moved this cycle but the glossary lagged. Fixed three entries to the
post-0005 reality (this is audit's autonomous glossary write authority,
record-reality only): `node` ("at most one output" -> "at most one output port; a
producer's output is a record of 1..K base-scalar columns, scalar = degenerate
K=1"), `composite` (now connects the multi-column stream to the node-output model
— the record a producer's eval returns, bound field-wise), `edge` (forwards "one
field (from_field) of a producer's eval output record", per-field kind check).
The architect named `node` + `composite`; `edge` is an orchestrator-observed
addition in the same record-reality pass (post-from_field it read "forwards one
node's eval output", now imprecise for K>1).

Regression gate: profile regression list empty — architect is the sole gate, now
clean (36 tests: aura-core 19 + aura-std 3 + aura-engine 14; clippy -D warnings
clean; purity grep clean). Cycle 0005 is drift-clean.

Carry-on forward note (next-cycle owner, not a fix now):
- [low] cycle 0004's depth>1 join-lookback guard test remains merely OWED, not a
  fresh hole — the substrate enforces it via Window bounds (column.rs/ctx.rs
  untouched this cycle) and 0005 added no depth>1 join. Co-fresh borrowed-row
  routing does not interact with per-input lookback. A confirmation test is still
  owed when a depth>1 join lands.

NOT a milestone close: the walking-skeleton milestone still needs its end-to-end
fieldtest (ingest -> signal -> backtest -> position table -> broker -> pip-equity).
This cycle delivers the record-output substrate that the trading half (C10) now
builds on.

refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:58:54 +02:00
Brummel 5228542bfd feat: node output is a record (composite stream), not a single scalar
Cycle 0005 (BLOCKER #1): a node's output generalizes from one scalar to a record
of K >= 1 named base-scalar columns, with a scalar being the degenerate K = 1
case. A node keeps exactly one output port; its payload is now an ordered bundle
of base columns (a composite stream, C7). This unblocks every multi-column
producer the engine needs next -- OHLCV bars and, later, the C10 position-event
table -- none of which could exist while a node emitted at most one column.
Revises C8, sharpens C7 in the design ledger.

Contract (aura-core):
- `NodeSchema.output: ScalarKind` -> `Vec<FieldSpec>` (FieldSpec = { name:
  &'static str, kind }; the field position is what an Edge binds, the name is
  metadata for later sinks/playground, C18).
- `Node::eval -> Option<Scalar>` -> `-> Option<&[Scalar]>`: a node fills a buffer
  it owns (sized once at construction) and returns a borrowed K-field row; `None`
  still means filter/not-warmed. This is the C7-faithful representation chosen
  with the user: zero per-cycle heap allocation on the forward path (rejected:
  Option<Vec<Scalar>> allocates per fire; an inline fixed [Scalar; N] bakes a
  width cap; engine-owned output columns invert the eval model). Scalar output is
  the degenerate 1-field record -- no separate scalar path.

Field-wise binding (aura-engine):
- `Edge` gains `from_field: usize` -- which producer column this edge forwards.
  Consuming a whole record is N edges; there is no "bind whole record" mechanism.
- bootstrap kind-checks per field (`from.output.get(from_field)` -> BadIndex if
  out of range; `field.kind != slot.kind` -> KindMismatch).
- the run loop copies a producer's returned row into one reused scratch buffer
  (resolving the borrow; no per-cycle alloc once warm) and scatters
  scratch[from_field] into each out-edge slot. `NodeBox.out_len` (set at
  bootstrap) backs a debug_assert on the returned row width. `run` returns
  `Vec<Option<Vec<Scalar>>>` (the observed node's full row per cycle -- a
  materialization-surface alloc, not the inter-node hot path).
- the K fields of one record are co-fresh by construction (one eval, one
  timestamp), so C6 is untouched.

aura-std: Sma/Sub migrate to the 1-field degenerate case (hold a [Scalar; 1]
buffer; behaviour unchanged). Sub drops #[derive(Default)] (Scalar has no Default)
for a manual Default.

Proof (aura-engine tests, +5): an Ohlcv 5-field bundler fed by five
timestamp-aligned barrier sources emits one complete bar per timestamp
(ohlcv_bundles_five_field_record); a downstream Sub binds high(1)-low(2)
field-wise, observed end-to-end + a determinism re-run
(edge_binds_single_field_high_minus_low); a second consumer binds close(3)-open(0)
on the same record (distinct_edges_read_distinct_fields); must-fail:
bootstrap_rejects_from_field_out_of_range (BadIndex),
bootstrap_rejects_per_field_kind_mismatch (a TwoField [f64,i64] producer, the i64
field into an f64 slot). All prior 0003/0004 tests adapted to from_field + the Vec
return, expected vectors unchanged (scalar = 1-field record).

Gates re-run by the orchestrator (not just the agent): cargo build/test
--workspace --all-targets (36: aura-core 19 + aura-std 3 + aura-engine 14, 0
failed) / clippy --workspace --all-targets -D warnings clean; surface-purity grep
(no dyn-Any / Rc / RefCell) clean. Ledger C8/C7 edits verified
(Option<&[Scalar]> present, "one series per node" gone, cycle-0005 realization
note added). The glossary composite/node record-reality pass is the audit-time
follow-up.

closes #1
refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:55:20 +02:00
Brummel f95fe0f4e4 plan: 0005 node output record
Four tasks, ordered by compile-gate discipline (each leaves a coherent gate
satisfiable):
1. aura-core contract (FieldSpec, NodeSchema.output -> Vec<FieldSpec>, Node::eval
   -> Option<&[Scalar]>) + aura-std Sma/Sub degenerate migration, threaded in one
   task. The signature change breaks aura-engine, so the gate is SCOPED to
   `-p aura-core -p aura-std` (engine is knowingly broken until task 3).
2. aura-engine production: Edge.from_field, NodeBox.out_len, per-field bootstrap
   kind/range check, reused-scratch field-indexed run forward, run ->
   Vec<Option<Vec<Scalar>>>. Breaks the in-module tests, so the gate is a PARTIAL
   `cargo build -p aura-engine --lib` + clippy --lib only.
3. aura-engine tests: migrate the 3 firing fixtures to the slice return; add the
   Ohlcv 5-field bundler + TwoField mixed-kind fixture; the field-binding proof
   (high-low, close-open), K>1 output, determinism, and must-fail (from_field OOB
   -> BadIndex, per-field kind mismatch -> KindMismatch); adapt all prior
   0003/0004 tests to from_field + the Vec return. Full workspace gate + purity
   grep.
4. design ledger: C8 revision (one output port carrying a record) + C7 sharpening
   (composite-bundle is the node-output model). Docs-only; glossary record-reality
   is audit-time, not this plan.

Orchestrator decisions baked in: the spec's illustrative `schemas_out_len[nidx]`
is committed as a `NodeBox.out_len` field (set at bootstrap) read into a run-loop
local; the firing fixtures hold a `[Scalar; 1]` buffer constructed at each call
site; Sub drops `#[derive(Default)]` (Scalar has no Default) for a manual Default
that keeps clippy's new_without_default quiet; the engine lib.rs doc is left
untouched (no single-scalar phrase there -- only harness.rs doc + Edge doc move).

plan-recon confirmed every spec-named path exists and the compile-driven site set
(6 output: literals, 6 fn eval, 6 Edge literals, 11 .run callers); from_field /
FieldSpec / Ohlcv are net-new. No content-pin/golden-file twin-miss risk.

refs #1
refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:46:56 +02:00
Brummel e9b4d51c30 spec: 0005 node output record
Cycle 0005 (BLOCKER, milestone walking-skeleton): generalize a node's output
from a single scalar to a record of base-scalar columns (K >= 1), with a scalar
being the degenerate K = 1 case. A node keeps exactly one output port; that
port's payload becomes an ordered list of named, typed base columns (a composite
stream, C7). This unblocks every multi-column producer the engine needs next --
OHLCV bars and, later, the C10 position-event table -- none of which can exist
while a node emits at most one column. This revises C8 and sharpens C7.

The load-bearing design call, made with the user: the eval output representation
under C7's "no per-event heap allocation". eval becomes
`-> Option<&[Scalar]>` -- the node fills a buffer it owns (sized once at
construction) and returns a borrowed K-field row; the engine copies it into one
reused scratch row (resolving the borrow, zero per-cycle alloc once warm) and
scatters each field to its out-edges. `None` still means filter/not-warmed.
Rejected alternatives, on substance: returning `Option<Vec<Scalar>>` allocates
per fire (violates C7's forbids); an inline fixed `[Scalar; N]` record bakes a
magic width cap; engine-owned output columns invert the eval model (write-into-out
+ a fired flag) and split "fired" from "produced" for an output-lookback benefit
we do not need (history is bound as an input, not read from a node's own past).
The borrowed row is the minimal faithful generalization of today's
`Some(Scalar)` push route, K-wide.

Binding stays field-wise only: `Edge` gains `from_field`; consuming a whole
record is N edges; no "bind whole record" mechanism. The K fields of one record
are co-fresh by construction (one eval, one timestamp), so C6 is untouched. The
record is structural bundling over the four base types (C7 intact) -- no fifth
scalar type, no dyn Any, no heterogeneous buffer.

Scope is the substrate only: the contract change, the Sma/Sub degenerate
migration, the engine from_field wiring, and a neutral OHLCV proof (a 5-field
bundler fed by five timestamp-aligned barrier sources + a downstream Sub binding
high - low field-wise, plus must-fail cases: from_field out of range -> BadIndex,
per-field kind mismatch -> KindMismatch). The C10 position table and broker nodes
are out of scope -- later cycles that consume this capability.

Self-review clean; grounding-check PASS (14 load-bearing "before"-state
assumptions ratified by named green tests; C6/C7/C8 match the ledger as written;
parse-every-block a documented no-op -- profile declares no spec_validation).

refs #1
refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:35:31 +02:00
Brummel 9ff90b5d80 audit: cycle 0004 tidy — diamond-barrier test + ledger/glossary record-reality
Architect drift review over 02fe891..f37b2a3 (firing-policies-and-merge):
drift_found, all medium/low, no contract violation. Invariants hold: C1 (the
merge is a deterministic linear scan, strict < on (timestamp, source-index) gives
C4 tie-break by declaration order; only per-call allocs), C3 (one merge at
ingestion), C5/C6 (both rails proven with worked vectors; sample-and-hold falls
out of the push model), C7 (type-erased edges, no dyn-Any/Rc/RefCell — purity grep
clean). Resolved the 0003 forward-note: cycle_id is now a real read field (the
freshness epoch, fresh_at == cycle_id), not write-only.

Resolved [med] (test gap): the barrier's timestamp token was proven only for the
multi-source same-timestamp case; C6's within-source diamond rejoin (one source,
two unequal-latency paths rejoining at a barrier) was claimed by the spec but
untested. Added `within_source_diamond_rejoin_barrier_fires`
(source -> {SMA(2), SMA(4)} -> BarrierSum): once SMA(4) warms at cycle 4, both
paths emit each cycle at the same timestamp and the barrier fires
(15+13, 17+15, 19+17). The code was already correct — every push in a cycle
stamps last_ts = that cycle's timestamp (source pushes and produced-edge forwards
alike) — so this closes the coverage gap, not a bug.

Resolved [med] (glossary record-reality): the run-count entry claimed it "drives
freshness-gated recompute", now false — the engine's firing gate reads the
per-wiring-slot cycle epoch (fresh_at), not Column::run_count. Sharpened the entry
to record reality: run-count is the node-visible per-column push counter;
fresh_at is the engine's per-slot freshness epoch. Not redundant (different
levels); both are maintained consistently on every push.

Resolved [low] (ledger silent on the barrier token): added a C6 "Realization
(cycle 0004)" note recording the load-bearing decision — barrier token is the
timestamp not cycle_id (forced by C4's same-timestamp-tie rule), firing tagged
per input with Firing::{Any, Barrier(u8)}, a mode-A input being its own trivial
group (so "per input group" and the per-input tag coincide).

Carry-on forward notes (next-cycle owners, not fixes now):
- [low] cycle_id is materialized internally (the freshness epoch) but not yet
  surfaced in run output (run returns Vec<Option<Scalar>>). Its first observable
  consumer is a sink / clock node (C18) or a timestamped trace — materialize the
  surfacing then.
- [low] all join fixtures declare lookback 1; a test guarding C8's "a node never
  reads beyond its declared lookback" for a depth>1 join is still owed (the
  substrate already enforces it via Window bounds, so this is confirmation, not a
  hole).

Regression gate: profile regression list empty — architect is the sole gate, now
clean (31 tests: aura-core 19 + aura-std 3 + aura-engine 9; clippy -D warnings
clean; purity grep clean). Cycle 0004 is drift-clean. NOT a milestone close: the
walking-skeleton milestone still needs its end-to-end fieldtest (ingest -> signal
-> backtest -> position table -> broker -> pip-equity), of which this cycle
delivers the firing/merge layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:27:00 +02:00
Brummel 10a40127b6 plan: cycle 0004 firing policies and merge
Three tasks, ordered by compile-gate discipline (each leaves a coherent build
gate satisfiable):
1. aura-core Firing enum + InputSpec.firing, threaded through aura-std Sma/Sub
   in the same task (the field addition breaks both callers) -> full workspace
   gate green.
2. aura-engine production rewrite: SourceSpec, SlotState (fresh_at + last_ts, the
   two read clocks), NodeBox/Harness fields, the k-way merge run loop, and the
   fires() firing predicate. The bootstrap/run signature change breaks the
   in-module tests, so this task ends on a PARTIAL gate (cargo build -p
   aura-engine --lib, which excludes #[cfg(test)]).
3. aura-engine tests: rewrite the 5 cycle-0003 tests onto the multi-source API
   and add the firing rails -- mode A as-of hold, mode B barrier wait, mixed A+B
   OR-combine, each with worked expected vectors -> full workspace gate.

Orchestrator decisions baked in: run length-mismatch is a caller bug -> assert_eq!
(not a typed error, not a silent no-op); historical specs/plans (0002/0003) pin
pre-cycle disk state and are left frozen; the test module gets an explicit
`use aura_core::{InputSpec, NodeSchema}` (harness.rs production never names those
types, so `use super::*` does not bring them, and putting them in the top-level
use would be an unused-import under -D warnings); the harness doc keeps RustAst's
model phrased without the literal Rc/RefCell tokens so the purity grep does not
self-match.

Grounding-check on the parent spec PASS; plan-recon confirmed every spec-named
path exists and the "before" shapes match disk.

refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:12:58 +02:00
Brummel 4cbb7795ef spec: cycle 0004 firing policies and merge
Fourth walking-skeleton cycle: the reactive-semantics layer — both firing
policies (C6), the k-way ingestion merge (C3), the monotonic cycle_id clock
(C4), and freshness-gated recompute / sample-and-hold (C5). This is the first
point where the engine departs from RustAst rather than sharpening it.

Both rails ship together (user directive: "must sit right from the start"):
- A = fire-on-any-fresh + hold (as-of join);
- B = all-fresh barrier (synchronizing join), accumulating across cycles.
Per-input-group, mixable on one node. Encoded as Firing::{Any, Barrier(u8)} on
InputSpec (where C8 places firing); default Any keeps Sma/Sub unchanged.

Key design resolution (studied against RustAst src/ast/rtl/streams/): the
barrier token is the data *timestamp*, not cycle_id. RustAst's
last_cycle_per_input.all(== cycle_id) synchronizes one source's fan-out but
cannot synchronize independent sources — under C4 four same-timestamp sources
are four different cycle_ids, so the cycle_id barrier never fires for C6's own
OHLC example. Substituting timestamp is the minimal faithful generalization
(fires both the diamond rejoin and the multi-source bar). Mode A and the k-way
merge are both new (RustAst had neither).

Two read clocks, both load-bearing (resolves the 0003 audit's "0004 owns
cycle_id" with no write-only field): cycle_id is the freshness epoch (C5 reads
it via per-slot fresh_at == cycle_id); timestamp is the barrier token (C6 reads
it via last_ts == T). Sample-and-hold falls out of the existing push model — a
non-firing node pushes nothing, so consumers see the held value via window[0].

Scope (user-chosen): reactive semantics only — in-memory (timestamp, value)
sources; the merge is the permanent ingestion core, the Source trait (C11) +
data-server IO are a later orthogonal cycle. bootstrap/run/InputSpec signatures
change (the 0003 tests re-express on the multi-source API).

Headline test pair (identical sources, firing-only difference): A emits
[None,None,120,130,140,240] (holds); B emits [None,None,120,None,None,240]
(waits for timestamp coincidence). Plus determinism (C1), mixed A+B, the 0003
fan-out/join compat baseline, and the bootstrap rejections.

Grounding-check PASS: 10 load-bearing substrate assumptions ratified by green
tests; the spec's "before" shapes match disk exactly.

refs walking-skeleton

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:00:06 +02:00