2d3792e45f4bb91400104b66cf5f37436412e8fa
104 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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. |
||
|
|
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). |
||
|
|
d97fadaafa |
audit: cycle 0013 tidy (carry-on, debt filed)
Architect drift review over 9fbc16a..0a855c3 (aura graph ASCII-DAG render, #13): no contract violation. Regression gate is a no-op (commands.regression empty); architect is the sole gate. Contracts verified against the diff (by fact, not claim): - C8/C1: `Node::label()` is genuinely non-load-bearing — called only in the renderer (aura-cli/src/graph.rs) and test assertions, never in the run loop (harness.rs) nor the compile path (blueprint.rs). Wiring stays index-based; determinism untouched. The C8 Refinement note (INDEX.md) matches what shipped. - C16: no engine-crate manifest changed (aura-core/std/engine/ingest Cargo.toml all clean); ascii-dag v0.9.1 appears only in aura-cli/Cargo.toml + the matching Cargo.lock delta. The zero-external-dependency engine firewall is intact. - C9/C23: `Composite.name` dissolves at inline — `inline_composite` destructures `name: _` (blueprint.rs), the name never reaches the flat compilat. The compiled view emits no cluster (golden asserts `!contains("sma_cross")`); the blueprint view draws composites as clusters. Boundary dissolution holds. Drift status: drift_found = technical debt only (no fix iteration; contracts hold). Filed to the forward queue rather than fixed in-cycle: - #26 (feature) — the blueprint (clustered) view panics `unimplemented!` on nested composites while the compiled view handles nesting; the two views diverge in capability. Built-in sample is single-level so it is unreachable today. Bundles the low-severity source-label gap (sources render `source:{kind}` because SourceSpec has no name — latent C20 gap). - #27 (idea) — the SMA-cross sample wiring is now triplicated (aura run flat harness, aura graph composite blueprint, engine bit-identical fixture); distinct from #14's Recorder-fixture dedup. No baseline update (no regression script). Next-iteration direction (architect): resolve #26 before the next render-touching cycle so the two views stop diverging. |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
9fbc16afb9 |
audit: cycle 0012 tidy (clean)
Architect drift review over fef09d4..a4fb5d7 (blueprint construction layer / composite inlining, #12): drift-clean, carry-on. Regression gate is a no-op (commands.regression empty); architect is the sole gate and found nothing actionable. What holds against the ledger (verified by fact, not claim): - C1/C16: `git diff --stat` shows only blueprint.rs (new) + lib.rs (+2 lines). harness.rs (run loop, bootstrap), node.rs (Node trait), and Edge/Target/SourceSpec are byte-untouched, so determinism is preserved — the compilat is the same flat topology a hand-wiring produces. aura-engine's Cargo.toml adds no external dependency (aura-std is [dev-dependencies] only), so the zero-external-dependency engine workspace invariant holds. - C9/C23: `Composite` is not a `Node` (no `impl Node for Composite`, never `eval`'d); `compile()` inlines to a flat (nodes, sources, edges) wired by raw index; names survive only as non-load-bearing debug symbols. The bit-identical demonstrator proves the compilat equals the hand-wired flat graph for the SMA-cross. No runtime sub-engine reached the run loop. - C19: binding is a compilation; typed CompileError validation delegates the lowered-compilat check to the unchanged bootstrap kind/Kahn check (no re-implementation). Acknowledged debt (low, named — not silent drift), filed to the forward queue: - #24 — Composite::schema() panics on malformed indices where compile() returns a typed CompileError (two validity surfaces, divergent failure modes; doc-acknowledged). - #25 — compile() re-calls dyn Node::schema() (fresh Vec alloc) at each resolve site, duplicating bootstrap's kind-derivation; compile-time only, no hot-path cost. Neither debt item blocks the deferred C23 optimisation passes, the natural next direction. |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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.
|
||
|
|
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). |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
1433b13495 |
audit: cycle 0010 tidy (clean)
Architect drift review over 7a8d209..559903a (aura run CLI, #8): drift-clean, carry-on. Regression gate is a no-op (commands.regression empty); architect is the sole gate and found nothing actionable. What holds against the ledger: - C22 closing seam genuinely delivered: `aura run` bootstraps a CLOSED harness WITH two sinks and emits a populated, non-trivial trace (one exposure sign flip, a real drawdown) — a newcomer sees a real trace, as C22 promises. - C16 respected: Recorder ships in aura-std as a domain-free universal block (parameterised over ScalarKind/Firing/Sender, no instrument/strategy concepts); sample_harness lives in aura-cli, not aura-engine, so the engine stays domain-free while wiring stays plain Rust (C17/C20 — raw Harness::bootstrap, no DSL). - C7 purity: mpsc::Sender out-of-graph handle, no Rc/RefCell. C8 sink contract: output: vec![], eval returns None. C14 structured face: real binary → canonical JSON on stdout, exit-code contract integration-tested. C1 determinism pinned. Acknowledged, spec-deferred debt (tracked, not silent drift) — filed to the forward queue: - Three near-identical Recorder definitions now coexist: the shipped aura-std block (the strict superset — four kinds) plus the two #[cfg(test)] engine fixtures (f64-only) in report.rs / harness.rs. The spec explicitly defers de-dup; flagged for a later tidy. - manifest.commit is option_env!("AURA_COMMIT") → "unknown" — the C18 identity field is a placeholder until build.rs git capture (named non-goal). The one implementer deviation (a scoped #[allow(clippy::type_complexity)] on sample_harness) is consistent with the identical allow on aura-engine's build_two_sink_harness fixture — not drift. |
||
|
|
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
|
||
|
|
9159a550a3 | chore: ignore fieldtest crate build artifacts (fieldtests/*/target/) | ||
|
|
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 |
||
|
|
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
|
||
|
|
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
|
||
|
|
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
|
||
|
|
4dc1526196 |
audit: cycle 0009 tidy (clean)
Architect drift review over 521a16e..80d7bfb (run-metrics-and-manifest):
no drift, no debt. The shipped report surface matches the ledger:
- Pure-additive as claimed: lib.rs gains only `mod report;` + one re-export
line; harness.rs `run` signature is untouched (`-> ()`). No engine / Harness
/ node-contract change — the cycle-0006 sink mechanism (C8/C22) is consumed
read-only, not altered.
- C1/C12: summarize is a total, pure, value-only reduction; the e2e test
asserts bit-identical metrics + JSON across two runs.
- C18 sequencing: ships manifest + metrics "from day one", explicitly defers
the registry/index to its later milestone. C14 structured face delivered as
hand-rolled JSON; the zero-dependency stance is preserved (all four crates
path-only deps).
- params-as-honest-precursor wording matches the 0008 typed-param-space
deferral (node.rs); f64_field panic-as-wiring-bug matches the engine's
existing "checked at wiring" convention.
Forward note (not drift, no contract speaks to it, spec documents it as an
accepted assumption): to_json renders f64 via `{}` and is valid JSON only under
the finite-value invariant — a NaN/Inf would emit bare NaN/inf tokens. The
invariant holds today (SimBroker integrates finite returns, Exposure clamps),
so C14 "machine-readable" is not breached. Worth a debug-assert or named
constant only if a future broker can produce non-finite equity — flag it then.
Regression gate: profile commands.regression is empty — no-op; architect is the
sole gate and is green. carry-on.
|
||
|
|
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
|
||
|
|
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 |