main
550 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b73d7076c3 |
feat(aura-cli): TTY-gated ANSI edge colouring + blank line after composite signature
Two presentation refinements to the `aura graph` views, render-only (no
engine change, C8/C9/C23 untouched).
Edge colouring: thread a `Color { Plain, Ansi }` through render_flat /
render_definition / render_blueprint / render_compilat. `Ansi` uses
ascii-dag's colored scanline pass (per-edge palette colours) so crossing
edges stay traceable; `Plain` is the existing monochrome `g.render()`.
The CLI selects `Ansi` only when stdout is a TTY (`IsTerminal`), so a
redirect to a file or pipe stays byte-clean — `aura graph | …` carries no
escape codes. ascii-dag colours only edges; node-label text is unaffected.
Tests render `Color::Plain` explicitly, so both goldens stay byte-stable
(compiled_view_golden remains the C23 guard); a new test pins that `Ansi`
emits escapes and `Plain` does not, and that labels survive colouring.
Blank line: render_definition now separates the typed signature title from
the graph body with an empty line. blueprint_view_golden re-captured.
Considered but rejected this round: per-node role highlighting of the
signature identifiers — needs either an ascii-dag fork (node colouring is
hard-disabled upstream) or a per-graph string-surgery pass that does not
generalise beyond hand-known labels. Only edge colouring survives here.
|
||
|
|
32ac8a30ea |
feat(aura-cli): de-prefix input-role markers ([in:price] -> [price])
Symmetric with the output de-prefix shipped in
|
||
|
|
2f4916596d |
feat(aura-cli): composite definition renders as a typed signature
Refines #41's blueprint-definition render. The [param:<name>] marker nodes #41 added bloated the ASCII MACD graph (three extra nodes + fan-in edges); this moves the interface into the title line and folds names into the leaf labels that already exist: - title is the composite's typed signature, e.g. macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram) (param kinds from the aliased leaf's factory params via a new ScalarKind->lowercase kind_str; output NAMES only — output kinds need a pre-build factory interface, deferred to #43) - leaf labels fold aliased param names: [EMA] -> [EMA(fast)] - unnamed interior input slots render as ordered stubs: [Sub] -> [Sub(#A,#B)] (count/order derived render-side from c.edges() + c.input_roles(), no engine schema needed; arity-1 nodes get no stub) - outputs de-prefixed: [out:macd] -> [macd]; the [param:*] marker nodes are gone - two stale doc comments corrected: render_definition's [param:] prose and node.rs LeafFactory::label (the renderer went flat in 0017, so the cited ascii-dag cluster-garble rationale no longer applies — wide labels are safe; the factory label stays bare because alias names are composite-level) Render-only: no engine/ParamAlias/param_space change. compiled_view_golden byte-identical (C23 guard — verified untouched in the diff); aura run --macd deterministic and unchanged (total_pips 0.1637945563898923, 3 sign flips). The title format change repinned five .matches("name:")->.matches("name(") definition-count asserts (sma_cross/outer/inner/dup/macd); the stub separator is "," (no space) to match the [Sub(#A,#B)] surface. Verification (orchestrator-run): cargo build/test/clippy --workspace -D warnings all green; the redesigned MACD render confirmed by eye. refs #41 #43 |
||
|
|
41cbb5506f |
feat(aura-engine): name the composite boundary — input roles + param aliases
Brings the other two composite-boundary edge-kinds to the named-projection shape #40 gave outputs. input_roles changes from a bare Vec<Vec<Target>> to Vec<Role { name, targets }> (rendered [in:<name>]); a composite gains params: Vec<ParamAlias { name, node, slot }> that relabels an interior leaf param slot's surface name in param_space() (rendered [param:<name>]). Param aliasing is a PURE NAMING OVERLAY, not curation (the load-bearing decision, per spec 0019): every interior param slot stays in param_space() and sweepable; the alias only relabels in place, never reorders or hides. Proven empirically — the MACD run is byte-identical (total_pips 0.1637945563898923, 3 sign flips), only the param labels improved: param_space() now surfaces [macd.fast, macd.slow, macd.signal] instead of three indistinguishable macd.length, and the manifest reads ema_fast/ ema_slow/ema_signal. C23 honoured: role/param/output names are non-load-bearing debug symbols dropped at lowering; identity is positional (role index, param slot, output field). compiled_view_golden is byte-identical (verified: the golden region is untouched in the diff). An out-of-range alias (missing/ non-leaf node or slot past the leaf's param count) is rejected at compile_with_params as BadInteriorIndex, mirroring the output range-check (no new variant). Orthogonal to #36 — purely additive at the composite level. Aliasing is demonstrated on the CLI MACD site only (the spec's worked example + a new E2E test macd_param_space_surfaces_the_three_named_aliases); sma_cross, the engine test fixtures, and the construction-layer fieldtests get the forced role-name + empty params, so the param_space C23 anchor goldens (param_space_mirrors_compiled_flat_node_param_order + siblings) stay byte-identical. Verification (orchestrator-run, not trusted from the agent report): cargo build/test/clippy --workspace -D warnings all green (engine 66, cli 12); the separate-workspace construction-layer fieldtest crate builds (guards the #42 latent-drift recurrence); compiled_view_golden + MACD determinism unchanged. Two faithful repairs to the plan's literal test/code bodies, no semantic change: the out-of-range test uses .err()/Some(BadInteriorIndex) (the Ok arm Vec<Box<dyn Node>> is not Debug, so .unwrap_err() would not compile), and the inline_composite destructure binds params as `param_aliases` to avoid shadowing the injected `params: &[Scalar]` arg. closes #41 |
||
|
|
8bc429f367 |
audit: cycle 0018 (#40) — drift-clean; C9 multi-output realization note
Architect drift review over a4cfe5c..HEAD (covers the previously-unaudited EMA node and MACD PoC plus the #40 composite multi-output cycle): status clean. C8/C7/C4 preserved (one port, one row per eval, K co-fresh base columns — structurally identical to OHLCV); C23 honoured (ItemLowering:: Composite.output is Vec<(usize,usize)>, no name in the compilat; the compiled-view render stayed bit-identical). EMA and MACD PoC carry no ledger drift. Resolution of the three minors: - Ledger lagged the new behaviour: added a "Realization (cycle 0018)" note under C9 recording that a composite's output is now a named K-field record (Vec<OutField>), the same arity C8 already grants a leaf, names dropped at lowering (C23). Amended here (ledger amendment is the audit's job). - blueprint.rs module-doc / Composite struct-doc / new-doc still said "one output port" singular — updated to "output record". - Non-workspace fieldtest From<Sma> debt is pre-existing (cycle-0016) and already tracked as #42; not re-filed. No regression scripts configured (profile regression: []), so the architect review is the sole gate. Carry-on: the next natural iteration is the #41 legibility sibling (input-role + param naming at the composite boundary). |
||
|
|
784e6c917a |
feat(aura-engine): composite output is a named multi-field record
Replace a composite's single output port (OutPort { node, field }) with
an ordered, named record (Vec<OutField { node, field, name }>). A
composite can now re-export K interior fields as one output; a consumer
selects one via the existing Edge::from_field — the same field-wise
selector that already works for multi-output leaf producers (OHLCV).
Why: multi-line indicators (MACD, Bollinger, Stochastic, Ichimoku) could
not be authored as a composition and re-exported as a unit — only one
line escaped the boundary. The MACD PoC was forced to expose only the
histogram. This lifts the three `field == 0` / `from_field == 0` caps at
the composite boundary (inline_composite nested arm, rewrite_edge
composite arm), range-checking the index instead.
Boundary completion, not an invariant change:
- C8/C7/C4 untouched — one port, one row per eval, K base columns (a
3-line MACD is identical in kind to OHLCV).
- C23 honoured — re-export names live at the blueprint boundary only and
are dropped in the compilat (raw index wiring). compiled_view_golden
stayed byte-identical (the regression guard): no name leaked into the
flat graph.
The MACD PoC now re-exports macd / signal / histogram as a record; the
strategy still trades the histogram, selected via from_field: 2. The
render shows K [out:<name>] markers per multi-output composite (input
roles stay [in:<index>] — naming them is the dependent #41).
Output naming ships with the capability (OutField is born name-ready) so
#41 — composite param aliasing + input-role naming — does not reopen the
type.
Verification: cargo build/test/clippy --workspace -- -D warnings all
green; aura-engine 62 tests (+3 capability, +1 through-run E2E), aura-cli
unchanged-count green; compiled_view_golden byte-identical.
Known debt (out of scope, pre-existing): the non-workspace construction-
layer fieldtests (fieldtests/milestone-construction-layer/mc_1..mc_4)
carry their OutField sweep but still do not compile standalone — they use
the Sma::new / BlueprintNode::from(Sma) API retired in the cycle-0016
value-empty migration, never propagated to these fixtures. Tracked as a
follow-up.
closes #40
|
||
|
|
d8b2a2880d |
feat(aura-cli): MACD strategy PoC over the EMA node
Author MACD as a nested composite -- price -> fast/slow EMA -> Sub (MACD line) -> signal EMA -> Sub (histogram), the histogram exposed as the single output the strategy trades. A richer fixture than sma_cross: an EMA-of-EMA chain with interior fan-out (the MACD line feeds both the signal EMA and the histogram), exercising the nested-composite render shipped in cycle 0017. It runs over the real path: the composite blueprint is compiled to a flat harness via compile_with_params + Harness::bootstrap (the same machinery as the SMA compiled view), not hand-flattened. New surfaces: aura run --macd (runs the strategy, prints metrics) and aura graph --macd [--compiled]. main's arg parsing is rewritten to a whole-argv match, preserving the #16 strict contract (trailing token / unknown subcommand -> usage, exit 2). MACD gets its own longer synthetic stream because the SMA-seeded EMAs warm up; the SMA sample's stream and goldens are untouched. This PoC makes two parameter-space gaps concrete for the milestone: the strategy's param_space is [macd.length, macd.length, macd.length, scale] -- three identical names distinguishable only by slot (the named-param case, blueprint #30) -- and a composite exposes only one output port, so the MACD/signal/histogram tuple cannot all be tapped for display (the tuple-output case). The compiled view's valued labels (EMA(2)/EMA(4)/EMA(3)) are what currently disambiguate the three EMAs. Tested: MACD compiles from the nested composite and runs deterministically (C1), the trace is non-trivial (>=1 exposure sign flip), and the blueprint view renders the composite definition once. |
||
|
|
ae1d4564f5 |
feat(aura-std): EMA node (SMA-seeded, O(1) per cycle)
Add Ema, the std-lib's first recursive/stateful producer: exponential moving average with alpha = 2/(length+1). The building block MACD (and DEMA, MACD-histogram, etc.) needs, and a worked example of a node whose param sizes its math, not its input window. Seeding is ratified as SMA-seeded (ta-lib convention): the EMA stays silent until it has seen length samples, seeds with their SMA, then runs the recurrence. Chosen over a first-value seed for three substantive reasons -- warm-up consistency with Sma (both emit None until length samples), parity with the EMA traders expect (ta-lib MACD seeds this way), and statistical honesty (no average claimed from a single observation). A first-value seed was the proof-of-concept convenience; rejected. Performance: recursive state means lookback = 1 (length sizes alpha, not the window); eval is O(1) time and state via ema += alpha*(x - ema), with no hot-path allocation (the output buffer is reused). Tested: warm-up-then-smooth (a step input separates it from a plain SMA), length-1 identity, label, and factory/schema agreement. |
||
|
|
a4cfe5c53f |
audit: cycle 0017 (#38) — render_flat dedup + ledger #38 realization note
Architect drift review of the #38 cycle (a70c5fa..cd31447) against the design ledger. No ledger-contract drift: C23 holds (render_compilat byte-identical, compiled_view_golden green), C9/C12 honoured (the new main-graph + where:- definitions view renders a composite as an opaque authoring-level node with its body defined once — a faithful blueprint/source view vs. the inlined compiled form), the nested-composite unimplemented! is genuinely removed and tested, and no orphaned references to the removed ItemDisplay/producer_id/consumer_ids remain in crates/ (only frozen historical plan docs mention them). Two debt items, both fixed here (not carried): - [medium] crates/aura-cli/src/graph.rs — render_compilat open-coded the same Graph::with_mode/add_node/add_edge/render idiom the new render_flat helper wraps. Fixed: render_compilat now builds its edge-pairs (its Edge structs + source targets) and delegates to render_flat, so render_flat is the single flat-build point shared by all three render functions. Behaviour-preserving — same node and edge insertion order — verified by compiled_view_golden staying byte-identical. - [low] the retired cluster-box blueprint-render model and its substantive cause (ascii-dag 0.9.1's subgraph /2 centering bug; flat-only being the fix) lived only in the superseded spec 0013, not in the durable contract layer. Fixed: added a "Realization (cycle 0017 — blueprint render = main graph + definitions)" note to the ledger (INDEX.md, under the C22 blueprint-view detail) recording the opaque-node + where:-definitions model, the authoring-vs-compiled split, the ascii-dag-bug rationale, the scale/render-once/nested benefits, and the #37 playground (enter/focus) counterpart vs. #38 static CLI form. regression: profile declares no regression scripts and no architect_sweeps — the architect review is the sole gate. No baseline to update. Cycle 0017 is drift-clean after these fixes. NOT a milestone close — that needs the end-to-end milestone fieldtest, still deferred until the sweep root (#32-#33) lands. |
||
|
|
cd31447d98 |
feat(aura-cli): blueprint view = main graph + composite definitions (#38)
Rewrite the `aura graph` blueprint view from ascii-dag subgraph cluster boxes to
a "program with subroutines" model: a flat main graph that wires the harness with
each composite shown as a single opaque node, plus a `where:` section that defines
each distinct composite type once (its interior with [in:k]/[out] port markers).
`render_compilat` (flat, fully-inlined, C23) is byte-for-byte unchanged.
Why (not effort):
- Correctness under width. ascii-dag 0.9.1's subgraph level-centering rounds
sibling x-positions with `/2`, overlapping wide sibling labels (a
width/parity-sensitive bug with no config/padding dodge that survives
unequal-width siblings; verified by reproduction). The flat layout is
collision-free; this view renders only flat graphs, so the bug cannot arise.
- Scale. A composite is one opaque node in the main graph, so blueprint size
tracks top-level wiring, not inlined node count — real strategies stay
displayable.
- Render-once. A composite reused N times has its body rendered once
(collect_distinct_composites dedups by name(), recursively).
- Removes the nested-composite `unimplemented!` — the definitions pass recurses,
so nested composites render as opaque nodes with their own definitions.
Mechanics: ItemDisplay/producer_id/consumer_ids are gone (a composite is now one
display node, so boundary fan-resolution collapses); new render_flat (the same
no-subgraph idiom render_compilat uses), collect_distinct_composites, and
render_definition. The now-unused `Target` import is dropped.
Divergence from the plan's literal text: the plan's `collect_distinct_composites`
used a nested `if let { if .. }`; this toolchain's clippy (collapsible_if, rust
1.94) rejects it under -D warnings, so it is the behaviour-identical let-chain form
`if let BlueprintNode::Composite(c) = item && !seen.contains(&c.name())`.
Tests: the two cluster-asserting blueprint tests are replaced by four behavioural
tests (opaque-node main graph; defines-each-composite-once; nested-renders-without-
panic; reused-composite-defined-once) plus a recaptured blueprint_view_golden
(captured verbatim from `aura graph`, not hand-authored). The four must-stay-green
tests pass; compiled_view_golden is byte-identical (render_compilat untouched).
Verified: `cargo test --workspace` all green, `cargo clippy --workspace
--all-targets -D warnings` clean.
The interactive enter/focus navigation counterpart is the playground's, tracked
separately as #37.
closes #38
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 (
|
||
|
|
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
|
||
|
|
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 (
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 (
|
||
|
|
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 |
||
|
|
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.)
|
||
|
|
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. |
||
|
|
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
|
||
|
|
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
|
||
|
|
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
|
||
|
|
521a16e56f |
docs(aura-std): document Add/LinComb firing policy + multi-row-per-ts shape
Resolves the two spec_gaps from the cycle-0008 fieldtest
(docs/specs/fieldtest-0008-sum-combinators.md), the same class the 0007 fieldtest
raised for SimBroker (and which
|
||
|
|
45920faa8f |
audit: cycle 0008 tidy — correct LinComb param-claim wording
Architect drift review (fa2835e..HEAD) at cycle close. Regression gate is a no-op (commands.regression empty); architect is the sole gate. What holds (no drift): purely additive C16 extension, one node per file, no engine/manifest change; C2/C8 warm-up discipline correct (None until every leg present, no implicit cold-leg 0.0); C7 zero per-cycle alloc. Shipping the named convenience nodes Add (and pre-existing Sub) beside the general LinComb is NOT C9 adapter-zoo drift — they are distinct named producers, not coercion shims, mirroring the already-shipped Sub. Fixed [ledger-gap/high]: lincomb.rs rustdoc and the feat commit body called the weights "the node's tunable parameters (C8/C12)", but aura_core/src/node.rs states tunable params (C12/C19) are deliberately not part of the schema yet (spec 0002 "Out of scope"). The weights are construction parameters that configure the node and fix its arity (C11) — the natural home for combination tuning params once the schema-level param surface lands, but not yet surfaced to any optimizer. Rustdoc reworded to say exactly that. No ledger change: the C8 text vs node.rs tension pre-exists this cycle and is already documented in node.rs; only the new rustdoc over-claimed. Carry-on [drift/high]: the cycle-0007 fieldtest still imports Sub-only and keeps its hand-authored Add2 (acceptance said the fieldtest *can* drop it). The 0007 fieldtest crate is a historical snapshot of what that run did; rewriting it retroactively would falsify why Add2 existed. The structural drop-in feasibility (the acceptance content) is confirmed by the architect (add.rs == sub.rs modulo operator; LinComb([1,1]) == Add; Add2 byte-identical to Add). End-to-end demonstration belongs to a cycle-0008 fieldtest (fresh example on the shipped node), dispatched next — not a retroactive edit of the 0007 record. Gates: cargo test -p aura-std 14 passed / 0 failed; clippy --all-targets -D warnings clean; RUSTDOCFLAGS="-D warnings" cargo doc -p aura-std clean. |
||
|
|
84130c2cab |
feat(aura-std): Add + LinComb sum combinators
aura-std shipped Sma, Sub, Exposure, SimBroker but no sum, so the north-star
"combine one signal with another" research move (C10) could not be expressed
from shipped blocks — the cycle-0007 fieldtest had to hand-author a project-local
Add2. This adds the missing combinator(s):
- Add — two-input f64 sum (a + b), the parameterless companion to Sub. Mirrors
sub.rs modulo the operator.
- LinComb { weights } — N-input weighted sum (Σ wᵢ·xᵢ). The weights are the
node's tunable parameters (C8/C12) and fix its arity (weights.len() inputs);
this is the form the north-star "combine A and B *with weights*" reaches for.
LinComb([1,1]) is Add; LinComb([1,-1]) is Sub.
Both withhold output (None) until *all* inputs are present — consistent with Sub,
and causally clean: a cold input leg is never silently folded in as 0.0.
LinComb::new panics on empty weights (build-time param error, like Sma::new).
Both ship because each is independently reached-for: Add for readability symmetry
with Sub, LinComb for the weighted/tunable combination — mirroring the project's
already-shipped choice to keep Sub as a named node beside a general form.
Hand-driven unit tests in the established aura-std style (5 new): Add sum, the
Add == LinComb([1,1]) identity, the N>2 warm-up, and the empty-weights panic.
Verified: cargo test -p aura-std (14 passed), clippy --all-targets -D warnings
clean, RUSTDOCFLAGS="-D warnings" cargo doc -p aura-std --no-deps clean.
closes #11
|
||
|
|
e9f4352640 |
docs(aura-std): document SimBroker input slots + firing/warm-up shape
Resolves the two spec_gaps from the cycle-0007 fieldtest
(docs/specs/fieldtest-0007-signal-quality.md): the consumer-facing SimBroker
struct rustdoc stated only the integration formula, leaving two things a
downstream author cannot infer from the public surface —
- input slot order (slot 0 = exposure, slot 1 = price). Both slots are f64, so
a swapped wiring is NOT caught at bootstrap (no kind mismatch) and silently
yields a wrong-but-plausible equity curve. The order is now documented as
load-bearing on the struct doc, with that hazard called out.
- firing / warm-up emission shape: both inputs are Firing::Any, so the broker
fires on every price-fresh cycle and reads a cold exposure leg as flat 0.0 —
emitting equity rows (leading 0.0s) from the first price tick, one per price
cycle, not one per exposure value. A consumer needs this to predict the
recorded curve's length and leading values.
Doc-only change; no behaviour change. Verified: RUSTDOCFLAGS="-D warnings" cargo
doc -p aura-std clean (intra-doc links to crate::Exposure and aura_core::Firing
resolve); clippy -p aura-std --all-targets -D warnings clean.
refs #5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3b49074156 |
feat(aura-std): signal-quality loop — Exposure node + sim-optimal broker
Realizes the C10 reframe (cycle 0007): the strategy DAG's output is an
intent/exposure stream, and a sim-optimal broker integrates it into a synthetic
pip-equity curve that measures SIGNAL QUALITY — the project's first trading
result, and its primary research loop.
Two new aura-std nodes (plain structs; the engine stays domain-free):
- Exposure { scale }: the decision/sizing node — clamp(signal/scale, -1, +1),
one f64 exposure per fired cycle, None until warmed up. Sizing/risk live in
`scale`.
- SimBroker { pip_size }: class (a) of C10 — consumes exposure (slot 0) + price
(slot 1), integrates the return earned by the exposure HELD INTO each cycle
(prev_exposure, decided at t-1) so it is causal / look-ahead-free (C2), and
emits cumulative pips. pip_size is held reference metadata (C7/C15), never
streamed.
End-to-end proof in the aura-engine harness test module (it dev-depends on
aura-std): a SMA(2)-SMA(4) cross -> Exposure -> SimBroker -> Recorder sink wires
a full signal-quality backtest; the recorded equity matches a hand-computed pip
curve ([0,0,0,0, 0.035]) and is bit-identical across two runs (C1). The engine
itself is unchanged — pure node authoring on the frozen substrate.
Verified: cargo test --workspace green (20 core + 30 engine + 9 std; 8 new);
clippy --all-targets -D warnings clean; engine/core surface-purity grep (no
dyn Any/Rc/RefCell) clean.
Note: the plan inlined `Window::first()`, which aura-core's Window does not
expose; the shipped code uses the semantically identical `Window::get(0)`
(newest value, or 0.0 when the exposure leg is cold).
closes #4
closes #5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
e5d80959e0 |
test: M×N×K stress matrix — node fan-out, deep chains, wide layers
The milestone's green end-to-end gate (#3): prove the closed bootstrap + run substrate carries arbitrary node-level fan-out / fan-in / depth / width DAGs deterministically (C1) and computes every recorded stream correctly. Closes the two coverage holes the engine tests left open after cycle 0006 — node fan-out (one PRODUCING node read by several consumers; prior coverage was source fan-out only) and deep-chain / wide-layer topologies. Six tests appended to harness.rs's test module, composing the existing Sma / Sub / Recorder / BarrierSum fixtures (no new fixtures, no aura-std surface — C9): - node_fan_out_identical_taps_record_identical_streams - node_fan_out_divergent_consumers_each_compute_their_own - node_fan_out_under_mixed_firing_each_consumer_records_per_policy - deep_transform_chain_propagates_end_to_end - wide_parallel_layer_multi_sink_records_each_stream - milestone_end_to_end_mixed_dag_records_every_stream_deterministically Each asserts exact (Timestamp, Vec<Scalar>) recorded values (hand-computed from the SMA/Sub + firing/warm-up semantics) AND determinism via a second fresh-harness drain proving bit-identical streams. 28 engine tests green (22 + 6), clippy -D warnings clean. No engine misbehaviour surfaced. closes #3 |
||
|
|
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 |
||
|
|
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> |
||
|
|
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>
|
||
|
|
f37b2a35d3 |
feat(engine): firing policies A/B + the k-way ingestion merge
Cycle 0004: the reactive-semantics layer. The engine now merges multiple
timestamped sources into one chronological cycle stream (C3) and gates each
node's re-evaluation by a per-input firing policy (C5/C6) — both rails shipping
together, as the user required ("must sit right from the start").
- `Firing` (aura-core) — a per-input enum on `InputSpec`, where C8 places firing:
`Any` (mode A, fire-on-any-fresh + hold / as-of join) and `Barrier(u8)` (mode B,
all-fresh barrier / synchronizing join). Default-free; Sma/Sub declare
`Firing::Any`, so under a single source ticking every cycle their behavior is
unchanged (backward compatible — the 0003 fan-out/join DAG still emits
[None,None,None,2,2,2]).
- `SourceSpec` + the k-way merge (aura-engine) — `run` takes one
`(Timestamp, Scalar)` stream per source and merges them by (timestamp, source
index) (C4 ties by declaration order). One record = one cycle. This is the
permanent ingestion core; the Source trait + data-server IO (C3/C11) are a
later orthogonal cycle (the user scoped this cycle to the reactive semantics).
- Two read clocks, both load-bearing (resolves the 0003 audit's "0004 owns
cycle_id" with no write-only field): each push stamps the input slot with
`fresh_at` (the cycle_id — freshness epoch, C5: fresh iff fresh_at == cycle_id)
and `last_ts` (the data timestamp — barrier token, C6: a barrier group is
complete iff all members carry last_ts == T). The `fires()` predicate ORs the
groups; the ">=1 member fresh this cycle" clause is the once-per-timestamp
guard. Sample-and-hold falls out of the push model: a non-firing node pushes
nothing, so consumers keep seeing the held value via window[0].
Key design call, 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); RustAst's total_count push counter is
aura's already-built Column::run_count.
Both rails proven on identical sources, firing-only difference:
- mode A (AsOfSum): [None,None,120,130,140,240] — holds the slow input;
- mode B (BarrierSum): [None,None,120,None,None,240] — waits for timestamp
coincidence;
- mixed A+B (MixedSum): the OR-combine fires both when the barrier pair completes
(holding the as-of input) and when the as-of input ticks (holding the pair).
Plus determinism (C1, bit-identical re-run of each rail) and the three bootstrap
rejections re-expressed on SourceSpec.
Gates green (re-run by the orchestrator, not just the agent): cargo build/test
(30: aura-core 19 + aura-std 3 + aura-engine 8) / clippy --workspace
--all-targets -D warnings clean; surface-purity grep (no dyn-Any / Rc / RefCell)
clean — the harness doc phrases RustAst's model without the literal tokens to
avoid self-match.
refs walking-skeleton
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
02fe89176f |
audit: cycle 0003 tidy — rename Sim -> Harness (glossary alignment)
Architect drift review over 71c8f6e..dd5c3fa (deterministic-sim-loop). Invariants hold: C1 (single-threaded, bit-identical re-run, alloc-free per-cycle ring path), C7 (AnyColumn type-erased edges, Box<dyn Node> is the node object not a dyn-Any payload, kinds checked once at bootstrap), C8/C9 (DAG wired, Kahn rejects cycles, None forwards nothing = sample-and-hold seed). Column::run_count already exists, so the C5 freshness primitive lives at the substrate — the deferral is honest, not a hole. Resolved [high]: the runtime type was named `Sim`, colliding with the glossary's load-bearing distinction — `sim` is the disjoint *executable unit / parallelism* framing, whereas the thing built here (a closed root graph of source + nodes + observe under a clock) is by C20 / the glossary a **harness**. Renamed `Sim` -> `Harness`, `sim.rs` -> `harness.rs`, and the module/exports/doc to match, before any other code takes a dependency on the wrong term. Gates re-run green (26 tests, clippy -D warnings, purity grep). Resolved [med] (glossary gap): added an `edge` entry anchoring the new wiring vocabulary (Edge / source target) to the glossary — record-reality per the boss glossary-write authority. Carry-on with forward notes (next-cycle owners, not fixes now): - [med] C4 monotonic cycle_id has no realization in the runtime type yet (the cycle clock is the record iteration; the explicit counter's first reader is freshness). Cycle 0004 owns materializing it together with C5. - [low] Sub declares lookback 1; once depth>1 join nodes exist, add a test guarding C8's "a node never reads beyond its declared lookback". Regression gate: profile regression list empty — architect is the sole gate, now clean. Next direction: freshness-gated recompute + a second source (C5/C3). |
||
|
|
dd5c3fad96 |
feat(engine): the deterministic single-source sim loop
Cycle 0003: aura-engine's first real content — a Sim that runs a wired DAG of
nodes deterministically, cycle by cycle. First point in the project where
authored nodes execute against data, not just under a hand-fed Ctx.
- `Sim` (aura-engine) — a bootstrapped, frozen root graph: a flat Vec<NodeBox>
(each node owns its input columns, the cycle-0002 shape) + an index edge
table, topologically ordered (Kahn). `bootstrap` sizes every input column from
its node's schema, kind-checks each edge and source target, and rejects
directed cycles — C7's "type check paid once at wiring" generalized to the
whole topology (`BootstrapError::{KindMismatch, BadIndex, Cycle}`).
- `run` — the deterministic loop: per record, forward the source value into its
target slots, evaluate nodes in topo order, capture the observed node's output
at eval time, and forward each `Some` output into its consumers' input columns
(a `None` forwards nothing — the structural seed of sample-and-hold). The loop
destructures `&mut self` into disjoint field borrows and allocates nothing on
the per-cycle path. This is the flat, monomorphized sharpening of RustAst's
reference-counted, interior-mutable observer push graph (C1/C7).
- `Edge` / `Target` (aura-engine) — producer->consumer and source->consumer wiring.
- `Sub` (aura-std) — a 2-input f64-difference node, so the loop is proven on a
real fan-out + join DAG (source -> {SMA(2), SMA(4)} -> Sub), with a determinism
assertion (C1: a second identical run is bit-identical).
Engine library depends only on aura-core; a test-only dev-dependency on aura-std
lets the integration test wire real Sma/Sub nodes. Sim carries a hand-written,
node-opaque `Debug` impl (Box<dyn Node> is not Debug; the impl prints
nodes.len() + the index/topology fields, needed by the bootstrap-rejection tests'
`unwrap_err`).
Deliberately deferred (recorded as decisions): freshness-gated recompute /
sample-and-hold (C5 -> cycle 0004, with the second source that makes it testable);
the explicit monotonic cycle_id counter (its first reader is freshness, 0004);
the Source trait + data-server ingestion + k-way merge (C3/C11); the builder API
(C19); the real sink + run registry (C18/C22).
Gates green: cargo build/test (26: 18 aura-core + 3 aura-std + 5 aura-engine)/
clippy -D warnings clean; surface-purity grep (no dyn-Any / Rc / RefCell) clean.
refs walking-skeleton
|
||
|
|
77a1d26017 |
feat(core): the node contract — Node, schema/eval, Ctx, and a worked SMA
Cycle 0002, the second walking-skeleton slice on top of the 0001 substrate:
the interface every node forever implements (C8), proven end-to-end by a real
node with no engine present.
- `Node` (aura-core) — `schema() -> NodeSchema` declares inputs (kind +
lookback) and the single output kind; `eval(&mut self, Ctx) -> Option<Scalar>`
computes one cycle's output (`None` = filter / not-yet-warmed-up). `&mut self`
so a node may keep its own derived state.
- `Ctx<'a>` (aura-core) — a `Copy` borrow-wrapper handing `eval` zero-copy,
financial-indexed `Window`s per input (`ctx.f64_in(i)[k]`, index 0 = newest).
A kind mismatch panics ("engine bug") — wiring guarantees the kind, so a
mismatch can only mean the wiring layer is broken; this keeps node-author code
clean (`w[k]`, not `w?[k]`).
- `AnyColumn::as_f64/as_i64/as_bool/as_ts` (aura-core) — the read-side mirror of
the existing `as_*_mut`, the mechanism `Ctx` uses to get a typed window from a
type-erased edge. Closes the read-side gap the cycle-0001 audit recorded.
- `Sma` (aura-std) — the skeleton's first real block: a producer node computing
the moving mean of one f64 input, emitting `None` until warmed up. Authored in
a downstream crate (proving aura-core's contract is usable across the crate
boundary) and driven by a hand-written test that mimics exactly what the sim
loop will later generalize: push fresh input, eval, collect.
Deliberately deferred (spec 0002 "Out of scope", recorded as decisions): the
firing policies (C6 — InputSpec carries no firing field yet), the sim loop (C4)
and freshness gating (C5), schema-level tunable params (C12/C19), and the
no-output sink refinement (C8 consumer side).
Gates green: cargo build/test (20: 18 aura-core + 2 aura-std)/clippy -D warnings
all clean; surface-purity grep (no dyn-Any / Rc / RefCell) clean.
refs walking-skeleton
|
||
|
|
a4760497ec |
feat(core): the streaming substrate — scalars, columns, type-erased edges
Cycle 0001 of the walking-skeleton milestone: the aura-core data substrate
everything streams through.
- scalar.rs: the closed four-type set — Scalar (Copy POD carrier), ScalarKind
(the kind tag), Timestamp (i64 epoch-ns newtype). No boxed Value, no dyn Any,
no heap (C7).
- column.rs: Column<T>, a fixed-capacity bounded-lookback ring pre-sized at
construction (no hot-loop realloc, C8), financial-indexed (index 0 = newest),
carrying a monotonic run_count freshness primitive (C5). Window<'_,T> is its
zero-copy read view with newest-at-0 Index. No RefCell/Rc — a sim is
single-threaded (C1), so push is &mut and read is &.
- any.rs: AnyColumn, the type-erased edge over the four Column kinds. push()
kind-checks at the edge (wiring time) and rejects a wrong-kind value with
KindMismatch, leaving the column untouched; the hot path bypasses the check
via the monomorphic as_*_mut accessors (C7).
- error.rs: KindMismatch { expected, got }, the guard error.
Sharpens the RustAst rtl/ reference (VecDeque + RefCell + boxed Value) into a
fixed ring of Copy scalars with direct dispatch. Node trait, Ctx, firing
policies, and the sim loop are deferred to subsequent cycles.
Verified independently: cargo build --workspace (0 warnings), cargo test
--workspace (14 aura-core tests green incl. ring-wrap, run_count monotonicity,
zero-copy window, and the wrong_kind_push_is_rejected guard), cargo clippy
--workspace --all-targets -D warnings (clean), and a surface-purity grep
confirming no RefCell/Rc/dyn Any on the substrate.
refs #0001-core-streaming-substrate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9eae43d308 |
docs: C19/C20 — bootstrap + strategy/harness; a project is a Rust program
Anchor the implementation-design branch: C19 (construction is a bootstrap phase — param-generic blueprint -> frozen instance; recursive up to the harness; params size/configure but never change topology). C20 (strategy = reusable context-free composite blueprint with role inputs + position-event output; harness = the root sim graph and C1's disjoint unit, with structural axes = experiment matrix vs tuning params = sweep; both strategy AND experiment authored in Rust via builder APIs, not a config DSL). Extend C8 (schema declares tunable params+ranges), C16 (a project is a Rust crate: cdylib of node/strategy/experiment blueprints + static Aura.toml; hosted by aura during research, frozen to a binary for deploy), C17 (all logic is Rust — nodes/strategies/experiments; Aura.toml = static context only), C12 (frozen topology = a harness instance; structural matrix is the outer axis). Add CLAUDE.md invariant 11; refresh project-layout.md (experiments/ dir, Rust experiments, day-in-the-life). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1467fcd30f |
docs: brokers are consumer nodes (C10), several attachable for comparable curves
Correct the broker mechanism: a broker is an ordinary downstream consumer node (C8/C9), not an external plugin/subsystem. It consumes the position-event stream + price streams and emits an equity stream; several brokers (e.g. sim-optimal pip + realistic currency) can be attached to the same position table at once, yielding directly comparable equity curves. Updates C10, CLAUDE.md invariant 7, aura-engine/aura-std crate docs, and the day-in-the-life doc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d8ccdcd806 |
docs: fix three drifts found in the session audit
C8: a node is producer/consumer/both — at most one output, pure consumers (sinks: chart/equity/logger) have none; records the node-role taxonomy the substrate requires (was wrongly 'exactly one output', contradicting pure consumers and C14). C3: merge by timestamp, normalizing source-native units (data-server Unix-ms) to the canonical epoch-ns of C7. aura-engine lib doc: broker is a downstream plugin over the position table (sim-optimal pip / realistic currency), not an in-strategy 'broker/portfolio node'. Plus a provenance line-wrap nit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b99912ca59 |
docs+scaffold: engine/project split (C16-C18), aura-std, usage doc
Refine the structure per interview: aura is the reusable engine, research projects are separate external repos depending on it (C16). Add aura-std (universal standard-node tier) to the workspace; remove nodes/ from the engine (project concept). Record authoring-surface = Claude Code + skills pipeline, no embedded coding-LLM (C17), and project management = one-repo-one-project + Aura-native run registry (C18). Add CLAUDE.md invariants 9-10, code_roots -> [crates], and docs/project-layout.md documenting the day-in-the-life and project repo layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
744c2f31a8 |
chore: scaffold aura workspace and wire the skills dev-cycle
Cargo workspace aura-core -> aura-engine -> aura-cli (bin `aura`) plus nodes/ for hot-reloadable cdylib node crates. Crate bodies are intentionally API-free; types arrive from the first spec. Adds the skills profile (.claude/dev-cycle-profile.yml), the project CLAUDE.md with the eight domain invariants, and the design-ledger skeleton. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |