Fourth walking-skeleton cycle: the reactive-semantics layer — both firing
policies (C6), the k-way ingestion merge (C3), the monotonic cycle_id clock
(C4), and freshness-gated recompute / sample-and-hold (C5). This is the first
point where the engine departs from RustAst rather than sharpening it.
Both rails ship together (user directive: "must sit right from the start"):
- A = fire-on-any-fresh + hold (as-of join);
- B = all-fresh barrier (synchronizing join), accumulating across cycles.
Per-input-group, mixable on one node. Encoded as Firing::{Any, Barrier(u8)} on
InputSpec (where C8 places firing); default Any keeps Sma/Sub unchanged.
Key design resolution (studied against RustAst src/ast/rtl/streams/): the
barrier token is the data *timestamp*, not cycle_id. RustAst's
last_cycle_per_input.all(== cycle_id) synchronizes one source's fan-out but
cannot synchronize independent sources — under C4 four same-timestamp sources
are four different cycle_ids, so the cycle_id barrier never fires for C6's own
OHLC example. Substituting timestamp is the minimal faithful generalization
(fires both the diamond rejoin and the multi-source bar). Mode A and the k-way
merge are both new (RustAst had neither).
Two read clocks, both load-bearing (resolves the 0003 audit's "0004 owns
cycle_id" with no write-only field): cycle_id is the freshness epoch (C5 reads
it via per-slot fresh_at == cycle_id); timestamp is the barrier token (C6 reads
it via last_ts == T). Sample-and-hold falls out of the existing push model — a
non-firing node pushes nothing, so consumers see the held value via window[0].
Scope (user-chosen): reactive semantics only — in-memory (timestamp, value)
sources; the merge is the permanent ingestion core, the Source trait (C11) +
data-server IO are a later orthogonal cycle. bootstrap/run/InputSpec signatures
change (the 0003 tests re-express on the multi-source API).
Headline test pair (identical sources, firing-only difference): A emits
[None,None,120,130,140,240] (holds); B emits [None,None,120,None,None,240]
(waits for timestamp coincidence). Plus determinism (C1), mixed A+B, the 0003
fan-out/join compat baseline, and the bootstrap rejections.
Grounding-check PASS: 10 load-bearing substrate assumptions ratified by green
tests; the spec's "before" shapes match disk exactly.
refs walking-skeleton
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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).
Three tasks, each leaving the workspace green:
1. Sub 2-input difference node in aura-std (+ test).
2. Sim in aura-engine: Edge/Target/BootstrapError + bootstrap (schema-sizing,
edge kind-check, Kahn topo-sort, cycle/bad-index/kind-mismatch rejection) +
the forwarding run loop (disjoint &mut-self destructure, no per-cycle alloc),
with 5 tests (chain, fan-out/join + determinism, cycle, kind-mismatch,
bad-index). aura-engine gains a test-only dev-dependency on aura-std.
3. Workspace gate: build / test (26) / clippy -D warnings / purity grep.
Orchestrator decisions baked in: observe captured at eval-time (no output
column); Sim stores only fields run reads (no write-only cycle_id/source_kind
that would trip `field never read`); the sim.rs doc avoids the literal
Rc/RefCell tokens so the purity grep does not self-match.
Third walking-skeleton cycle: aura-engine's first real content — a Sim that
runs a wired DAG of nodes deterministically, cycle by cycle. First point where
authored nodes execute against data, not just under a hand-fed Ctx.
Engine shape (contract-forced): a flat Vec<NodeBox> + an index edge table,
topologically ordered (Kahn), owned &mut by the loop — the flat, monomorphized
sharpening of RustAst's Rc<RefCell<dyn Observer>> push graph (C1/C7). Each node
owns its input columns (cycle-0002 shape), so Ctx is unchanged and the per-cycle
path allocates nothing. Wiring is by forwarding: a producer's eval output is
pushed into its consumers' input columns; a None forwards nothing (the seed of
sample-and-hold).
Bootstrap generalizes C7's "type check paid once at wiring" to the whole
topology: size input columns from each node's schema, kind-check every edge,
reject directed cycles (BootstrapError). A worked 2-input Sub node lands in
aura-std so the loop is proven on a real fan-out+join DAG
(source -> {SMA(2), SMA(4)} -> Sub -> observe), with a determinism assertion (C1).
Deliberately deferred (recorded as decisions): freshness gating / sample-and-hold
(C5 -> cycle 0004, only testable once a second source at a different rate exists);
the Source trait + data-server ingestion + k-way merge (C3/C11); the ergonomic
builder API (C19); the real sink + run registry (C18/C22); cross-sim parallelism.
Grounding-check PASS: all assumptions about the 0001 substrate and 0002 node
contract ratified by green tests; no phantom methods.
refs walking-skeleton
The second walking-skeleton cycle, on top of the 0001 streaming substrate:
the Node contract (C8) and its evaluation context.
Scope (one iteration):
- `Node` trait — `schema() -> NodeSchema` (inputs: kind + lookback; output
kind) + `eval(&mut self, Ctx) -> Option<Scalar>` (None = filter / not warmed).
- `Ctx` — a Copy borrow-wrapper handing eval zero-copy, financial-indexed
Windows per input (`ctx.f64_in(i)[k]`, index 0 = newest).
- Read-side `AnyColumn::as_f64/as_i64/as_bool/as_ts` — mirrors the existing
write-side `as_*_mut` and closes the cycle-0001 audit gap.
- `Sma` in aura-std — the worked producer node (the skeleton's first block),
driven by a hand-written test that mimics the future sim loop.
Deliberate deferrals (recorded as decisions, not gaps): the sim loop (C4),
freshness gating (C5), firing policies (C6, so InputSpec carries no firing
field yet), schema-level tunable params (C12/C19), and sinks / no-output
nodes (C8 consumer side). The hand-driven test stands in for the loop.
Grounding-check PASS: all load-bearing assumptions about the 0001 substrate
(Column/Window/AnyColumn/Scalar, financial indexing) ratified by green tests.
refs walking-skeleton
Five-task plan executing spec 0001: scalar set, Column/Window, KindMismatch,
AnyColumn, then a workspace build/test/clippy + surface-purity gate. Each task
adds its own module + re-export so the crate compiles green after every task;
14 unit tests pin the substrate contract incl. the wrong-kind-push guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first walking-skeleton cycle: the aura-core data substrate everything
streams through — the four scalar base types (Scalar/ScalarKind/Timestamp,
C7), the fixed-capacity financial-indexed SoA Column with its zero-copy
Window (C8), the type-erased AnyColumn edge with an edge-time kind check
(C7), and the per-column run_count freshness primitive (C5). Sharpens the
RustAst reference: no boxed Value, no VecDeque, no RefCell/Rc, no per-event
allocation. Node trait, Ctx, firing policies, and the sim loop are deferred
to subsequent cycles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The ledger named both sibling projects only conceptually; their concrete
location lived only in session memory. Add an "External components" section
so the repo is the single source of truth.
- data-server: path + Gitea URL, its role as the first data source, and the
concrete API shape (Arc<[T]> chunks, next_chunk, stream_*_windowed). Note
its records are AoS (M1Parsed/TickParsed), so the ingestion boundary (C3)
transposes them into SoA columns (C7) and normalizes time_ms -> epoch-ns.
- RustAst: not just a conceptual reference but a working reference
implementation of the streaming substrate. Map its src/ast/rtl/ layer
(RingBuffer, ScalarValue, ScalarSeries, SoA RecordSeries, the register/
delay node, seeded generator) to the contracts it prefigures (C4/C5/C7/
C8/C9/C12/C19), and state how aura sharpens it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Anchor the world/playground bundle. C21 (the World: harnesses are dynamically constructible first-class objects; orchestrating families of them via the C12 axes is aura's differentiator, not the single backtest which is commodity; registry is the World's memory). C22 (the World is a program -> nothing displayable until it runs; the only durable displayable substance is the recorded trace; sinks (C8) are the recording mechanism into the registry (C18); the playground plays ANY harness and is a trace explorer / execution viewer, never a scene editor; observability is explicit/selective; samples ship with sinks). Sharpen C20 (open node vs closed harness; a harness is NOT a node, it is the closure that runs / the root scope; harnesses do not nest as nodes — the World orchestrates them as objects). Correct C14 (playground is core, not 'freely deferrable'). Foundation positioning (World = product, single-harness engine = substrate). CLAUDE.md invariant 12; project-layout day-in-the-life.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
Supersede 'broker is part of the strategy'. A strategy outputs a time-ordered table of position events (event_ts, action[buy/sell/close], position_id, instrument_id, volume; open time = opening event's event_ts, so no open_ts; unsigned volume, direction in action). Brokers become downstream swappable plugins: a deterministic frictionless sim-optimal broker yields synthetic pip-equity for neutral comparison/optimization; realistic broker plugins add currency friction/constraints for viability/deploy. Stays within C7 (scalar columns). Updates CLAUDE.md invariant 7, the walking-skeleton line, provenance, and the day-in-the-life doc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Captures the rough-sketch interview as 15 discrete contracts (guarantee / forbids / why): determinism, causality, single-merge-at-ingestion, cycle granularity, freshness-gated recompute, firing policies A/B, SoA scalar payloads, node contract, fractal acyclic composition, the broker-is-part-of-strategy execution chain, generalized sources with the record-then-replay boundary, the atomic sim unit + four axes, authoring-only hot-reload, headless two-faced core, and resampling/sessions. Also corrects the tracker repo name to Brummel/Aura.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>