eb1097d7caaef366b5cf01d3f0e3c13565030fa2
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
458b2ae74b |
audit(0067): cycle close — lift SQN100 / stage1-r-trace rationale to ledger, drop ephemera
Architect audit clean (no drift, no debt): raw sqn verified bit-identical, C18 serde back-compat + C4 SoA seam + C1 determinism + Part-B symmetry all hold. - Ledger C10: add a "Realization (cycle 0067, #130 + #135)" note — SQN100 as the turnover-robust opt-in rank key (raw sqn / default ranker byte-unchanged, serde(default) for back-compat), and per-member --trace now symmetric across all three sweep strategies via the shared persist_traces_r. - Glossary: R-metrics list gains sqn_normalized; the SQN entry documents the SQN100 variant (was "deferred #130"). - Remove the ephemeral spec + plan (cycle closed; rationale lives in the ledger and git history). refs #130, refs #135 |
||
|
|
986a9fd5c7 |
audit(0066): cycle close — lift R-sweep/SQN rationale to ledger, drop ephemera
Cycle 0066 (#133) closed. The close audit (architect) found the family ratified against C12 axis-2 (rank-by-metric over the C18 family store) and the C10 R framing, with one [high] gap (swept-member manifest omitted the fixed R-defining params) fixed forward in
|
||
|
|
f040b66f30 |
design(c10): reframe to a bias stream + R-unit signal quality
Adopt the R-reframe ratified in #117: the strategy's native unit is risk (R), not pips/dollars, and its primary output is a directional bias. - exposure -> bias: the primary output is an UNSIZED direction + conviction `f64 in [-1,+1]`; sizing and the protective stop leave the strategy. - Signal quality is measured in R (Stage 1): an R-evaluator integrates the per-trade R-outcomes of a flat-1R RiskExecutor into R-expectancy. R is defined by the stop, account-/instrument-agnostic; pips retired as the unit. Flat-1R needs no equity, so Stage 1 is feed-forward (no cycle). - Risk-based execution is a decoupled layer: a RiskExecutor composite (stop-rule -> Sizer -> Veto -> position-management) per symbol, nested in a Broker/Account composite; account mode (netting/hedging) = composition constraint. "Sizer" is not "risk-manager" (that names the Veto layer). - Currency P&L is Stage 2 (fixed-fractional, compounding). The only feedback (equity -> Sizer) is cut by a z^-1 register on the fill edge (mark-to-market stays a same-cycle price read), encapsulated in the executor; flat-1R vs compounding is a structural axis (the explicit register is mandatory because C23 reorders the flat graph). - Position-event table stays the decoupled Stage-2 audit layer = first difference of the book (deal = target - book - in_flight); the flawed 0064 exposure-integral derive is abandoned. Touches: ledger C10 + CLAUDE.md domain invariant #7 + glossary (adds bias, R, R-evaluator, RiskExecutor, Sizer, veto, Stage 1/2; revises broker, exposure stream, position table, sim-optimal broker, strategy, ...). Also fixes cross-file drift surfaced by adversarial review (C20 guarantee + front-matter provenance still claimed "exposure stream"; cross-refs corrected to C9/C19/C20/C23). Industry-grounded against LEAN / nautilus_trader / backtrader / QSTrader / vectorbt / zipline. refs #117 |
||
|
|
86746e3d5d |
refactor(aura-core): Scalar as a native tagged enum, disjoint from Cell; typed RunManifest.params
Scalar was `struct { kind: ScalarKind, cell: Cell }` — "a Cell wearing a kind hat." The recorded reason for that shape was migration ease, which is not a design rationale (CLAUDE.md: rationale != effort), so the struct had no substantive defense. Redefine it as the native tagged union it conceptually is:
enum Scalar { I64(i64), F64(f64), Bool(bool), Timestamp(Timestamp) }
This is substantively better on four axes: kind/bits skew becomes unrepresentable (illegal states gone); accessors panic loudly on the wrong variant instead of a release-mode silent bit reinterpret; PartialEq derives the documented IEEE-754 / cross-kind value semantics (the hand-roll, needed only because the struct inherited Cell's bitwise compare, is gone); and serde is a plain derive emitting the externally-tagged wire form ({"I64":10}/{"F64":2.5}) — the private ScalarRepr shadow enum that motivated this was never needed. It is also C7-honest: erased-on-the-hot-path (Cell) and self-describing-at-the-edge (Scalar) are two disjoint types bridged by explicit conversion.
The whole public API is preserved (Scalar's fields were private), so call sites do not churn: the enum change is contained to scalar.rs, where cell() now encodes and from_cell() decodes per kind. Cell and ScalarKind are untouched.
With Scalar serializable, lift RunManifest.params from Vec<(String, f64)> to Vec<(String, Scalar)>: the param's kind (an i64 length vs an f64 scale) now survives into the C18 record (runs.jsonl) and the CLI JSON instead of collapsing to f64. scalar_as_param_f64 is deleted; sim_optimal_manifest passes typed params through. This is a deliberate wire-shape change — params now render as tagged scalars; the JSON-asserting tests are updated to the new shape on purpose.
Hand-authored manifest params across the CLI's single-run/mc/macd sites use honest kinds (lengths -> i64, scales -> f64) so they match the sweep path (which already derives correct kinds via zip_params); their in-binary JSON assertions are re-tagged accordingly.
Walk-forward fork resolves with no code change: WindowRun.chosen_params stays Vec<Scalar> (the serializable record carrier), reduced to f64 only at the param_stability statistic boundary (scalar_as_f64 retained). Glossary 'cell' entry updated to describe Scalar as the disjoint tagged union, not 'a cell plus its kind tag'.
Gates: build --all-targets, test --workspace (incl. new scalar_serde_round_trips), clippy -D warnings, doc --no-deps — all clean.
|
||
|
|
3598cb9dde |
audit: cycle 0047 — drift-clean (cell hot-path carrier)
cycle 0047 tidy. Architect drift review (b188773..82635fa, against the design ledger C7/C8/C1) found the carrier swap drift-clean: - Node::eval -> Option<&[Cell]>, all 8 aura-std out-buffers [Cell; N], the inter-node forward via the branch-free push_cell (harness.rs:426); Scalar survives only on the three keep-set boundaries (param plane, AnyColumn::get, source ingestion at harness.rs:402). The convert/keep partition and the three-role test rule are honoured site-for-site. - The dual API is coherent: fallible push (ingestion + kind-guard tests) vs infallible push_cell (bootstrap-verified inter-node forward) are each load-bearing and tested. No dead code, no C7 violation (no tag on the hot path; scratch: Vec<Cell> reused via clear(), no per-cycle alloc). - Both C7 realization notes + the C8 prose accurately describe the landed state. 285 tests green; build/clippy/test/doc clean. Tidy fix (pre-existing, surfaced by the review): - docs/glossary.md `node` entry said a node implements `schema()`, but Node::schema() was removed in cycle 0024 (the signature is declared pre-build on PrimitiveBuilder). Corrected to `lookbacks()` + `eval(ctx)`, the methods the Node trait actually carries. Record-reality. |
||
|
|
8eff6398ca | docs(glossary): add the cell entry | ||
|
|
0d7deb3a91 |
design(C10): reframe strategy output to intent/exposure stream; position table becomes a derived layer
The DAG expresses exactly one state at time t and a node emits at most one record per eval (C8), so a *sequence* of position events — where one decision instant (a stop-and-reverse) needs a close AND an open at the same event_ts — cannot be the DAG's per-cycle output without violating C8. The state the DAG can express faithfully is the desired exposure (one value per cycle); the buy/sell/close events are its first difference, a derived consequence. C10 is reframed accordingly: the strategy's primary, backtestable output is an intent/exposure stream (one signed, bounded f64 in [-1,+1] per cycle). Signal quality is measured by the sim-optimal broker integrating exposure*return into a synthetic pip-equity curve. The broker-independent position-event table survives as a decoupled, derivable, downstream position-management layer (computed table, not a per-eval output) feeding realistic broker nodes for viability/deploy — no longer the DAG output nor the signal-quality measure. Touches the ledger contract (INDEX.md C10 + provenance/milestone/C20 ancillary), the always-loaded summary (CLAUDE.md invariant #7), the glossary (broker, equity stream, position table, realistic broker, signal, sim-optimal broker, strategy reframed + new exposure-stream entry), and the north-star layout doc. Sealed specs/plans (0001-0006) left as historical record. refs #4 #5 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3a02fbf383 |
audit: cycle 0006 tidy (clean) — glossary record-reality (sink is a role)
Architect drift review over 57310b8..HEAD: clean — no drift, no debt. The C1/C7 boundary is correctly drawn (recorded values escape only via a node-held mpsc::Sender with no out-edge; nothing recorded re-enters the graph; the engine surface shrank). C2 holds (Ctx::now() is the present cycle's ts). The C8/C22 cycle-0006 realization notes accurately describe what shipped — no overclaim. Test fixtures stayed in #[cfg(test)] (C9). Regression set is empty (profile) — architect is the sole gate; green. Record-reality: the glossary 'sink' entry defined a sink as 'a pure consumer node with no output', which the shipped TapForward node (records AND forwards an output) now contradicts. Updated to reality: a sink is a recording *role*, not a type — a node may be a pure consumer or record and produce in the same eval (C8 'both'). No invention; the entry now matches the substrate this cycle landed. |
||
|
|
57310b83c7 |
audit: cycle 0005 tidy — glossary record-reality (node-output-record)
Architect drift review over 9ff90b5..5228542 (node-output-record): drift_found,
all low-severity, NO contract violation. The load-bearing invariants hold:
- C7 forbids intact — the reused `scratch` buffer (cleared+extended, warm after
cycle 1) and node-owned `[Scalar; K]` output buffers give zero per-cycle heap
alloc on the inter-node forward path; no fifth scalar type, no dyn-Any, no
heterogeneous buffer (the TwoField mixed-kind producer is rejected at bootstrap,
never streamed). Purity grep clean. The sharpened C7 guarantee text matches the
shipped per-field kind check + the Edge::from_field scatter.
- C8 revised text faithful — one output port (out_len), record of 1..K columns
(Vec<FieldSpec>), field-wise binding only (N edges, no whole-record bind), scalar
= degenerate K=1. No internal contradiction with C7/C9/C10.
- C6 untouched, verified against the run loop — one eval per node per cycle, every
out-edge stamps SlotState { fresh_at: cycle_id, last_ts: ts } with the same ts,
so the K fields of one record are co-fresh by construction. fires() unchanged.
- C3/C4/C5/C9 unperturbed (diff touches only node/edge plumbing); C2 lookback
enforcement unperturbed (column.rs/ctx.rs/any.rs not in the diff).
- C10 coherence confirmed — a position event is a 5-field record
(event_ts/action/position_id/instrument_id/volume), exactly the K-field
base-column record C8/C7 now sanction; the decision/sizing node is an ordinary
K=5 producer, the broker an N-edge field-wise consumer. No contract gap blocks
the next consumer; C10 correctly out of scope this cycle.
Resolved [low] (the one confirmed drift) — glossary record-reality. The ledger
C7/C8 moved this cycle but the glossary lagged. Fixed three entries to the
post-0005 reality (this is audit's autonomous glossary write authority,
record-reality only): `node` ("at most one output" -> "at most one output port; a
producer's output is a record of 1..K base-scalar columns, scalar = degenerate
K=1"), `composite` (now connects the multi-column stream to the node-output model
— the record a producer's eval returns, bound field-wise), `edge` (forwards "one
field (from_field) of a producer's eval output record", per-field kind check).
The architect named `node` + `composite`; `edge` is an orchestrator-observed
addition in the same record-reality pass (post-from_field it read "forwards one
node's eval output", now imprecise for K>1).
Regression gate: profile regression list empty — architect is the sole gate, now
clean (36 tests: aura-core 19 + aura-std 3 + aura-engine 14; clippy -D warnings
clean; purity grep clean). Cycle 0005 is drift-clean.
Carry-on forward note (next-cycle owner, not a fix now):
- [low] cycle 0004's depth>1 join-lookback guard test remains merely OWED, not a
fresh hole — the substrate enforces it via Window bounds (column.rs/ctx.rs
untouched this cycle) and 0005 added no depth>1 join. Co-fresh borrowed-row
routing does not interact with per-input lookback. A confirmation test is still
owed when a depth>1 join lands.
NOT a milestone close: the walking-skeleton milestone still needs its end-to-end
fieldtest (ingest -> signal -> backtest -> position table -> broker -> pip-equity).
This cycle delivers the record-output substrate that the trading half (C10) now
builds on.
refs walking-skeleton
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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). |
||
|
|
d566fed92b |
docs: bootstrap the project glossary; wire paths.glossary
Build a 44-entry glossary from the prose surface (design ledger, project layout, CLAUDE.md, crate doc-comments) in the convention's three-field format, and set paths.glossary so it becomes standing reading for every skill/agent role. Contested clusters resolved with the user: - bot vs frozen artifact kept as distinct entries (genus/species). - backtest / sim / run pinned as three distinct framings, none banned. - signal kept as its own concept (a score-producing node). - sim-optimal broker distinguished from Aura.toml's "default broker". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |