Split the aura-std roster along the C28 ladder into layer-aligned crates #288

Closed
opened 2026-07-19 19:38:19 +02:00 by claude · 4 comments
Collaborator

Phase 4 of the Stratification milestone. See contract C28 and its Status
block in the design ledger (docs/design/INDEX.md) for the trading-ladder model
and the phase enumeration; #286 ratified C28 (Phase 1) and gave the measurement
layer its bare run verb (Phase 3).

Situation

C28's import rule binds crate structure, not only graph composition: an inner
ladder layer never imports an outer one. Today aura-std bundles nodes from
several ladder layers in a single crate — engine-layer arithmetic / logic /
rolling blocks and sinks, market-layer session-anchoring and bar-resampling
geometry, and strategy-layer bias / stop-rule / position-management nodes. While
these share one crate the C28 import direction is structurally unenforceable:
nothing stops a layer-1 block from reaching a layer-3 one, because they compile
in the same unit.

Goal

Distribute the aura-std roster across layer-aligned crates so the C28 import
direction is enforced by crate boundaries. Behaviour-preserving — node logic is
unchanged; this is a structural cut, not a feature. The exact crate set, their
names, and the per-node roster assignment are derived against C28 and the current
workspace crate graph during spec production and recorded here as decision-log
comments before the spec is written.

Scope

  • Independent of #147 (RunReport<M> + the registry metric/deflation-vocabulary
    genericity), which is the separate coupled block for cutting the
    aura-engineaura-analysis edge (milestone Phases 2 and 6).
  • The aura run shape dispatch from Phase 3 (#286) already lets a
    measurement-shaped blueprint run without the strategy scaffold; this phase
    changes no run behaviour, only where node code lives.
Phase 4 of the **Stratification** milestone. See contract **C28** and its Status block in the design ledger (`docs/design/INDEX.md`) for the trading-ladder model and the phase enumeration; #286 ratified C28 (Phase 1) and gave the measurement layer its bare run verb (Phase 3). ## Situation C28's import rule binds **crate structure**, not only graph composition: an inner ladder layer never imports an outer one. Today `aura-std` bundles nodes from several ladder layers in a single crate — engine-layer arithmetic / logic / rolling blocks and sinks, market-layer session-anchoring and bar-resampling geometry, and strategy-layer bias / stop-rule / position-management nodes. While these share one crate the C28 import direction is structurally unenforceable: nothing stops a layer-1 block from reaching a layer-3 one, because they compile in the same unit. ## Goal Distribute the `aura-std` roster across layer-aligned crates so the C28 import direction is enforced by crate boundaries. Behaviour-preserving — node logic is unchanged; this is a structural cut, not a feature. The exact crate set, their names, and the per-node roster assignment are derived against C28 and the current workspace crate graph during spec production and recorded here as decision-log comments before the spec is written. ## Scope - Independent of #147 (`RunReport<M>` + the registry metric/deflation-vocabulary genericity), which is the separate coupled block for cutting the `aura-engine` → `aura-analysis` edge (milestone Phases 2 and 6). - The `aura run` shape dispatch from Phase 3 (#286) already lets a measurement-shaped blueprint run without the strategy scaffold; this phase changes no run behaviour, only where node code lives.
claude added this to the Stratification — ladder, process column, shell milestone 2026-07-19 19:38:19 +02:00
claude self-assigned this 2026-07-19 19:38:19 +02:00
Author
Collaborator

Design decisions (specify Step 1.5 — derived, not user-settled)

Recon of the aura-std roster and its intra-crate + workspace import graph (engine
commit 34ff539) settles the split shape below. These are derived orchestrator
decisions under the C28 layer model (docs/design/INDEX.md), recorded for
after-the-fact audit and veto; none was settled by user preference.

Verified ground. The only production intra-crate coupling in aura-std is
(a) the four cost nodes → cost.rs (all within the strategy layer) and (b)
vocabulary.rs → every node (the closed-roster dispatch table). Every other node is
an aura-core-only leaf: no production use crate:: crosses a layer boundary —
sim_broker.rs / position_management.rs import no strategy or market node, and
cost.rs imports no position_management (the "PM-geometry" coupling is a shared
field-layout / R convention, not a Rust use). So the four ladder layers in the
roster are cycle-free when cut into separate crates.

Crate split (D1/D2). Four node crates, each depending only on aura-core:

  • aura-std — kept as the engine-layer node crate: arithmetic / logic /
    comparison / rolling-window nodes, the generic combinators (When, LinComb,
    Select, Const), the registers (Delay, Latch), and the generic sinks
    (Recorder, GatedRecorder, SeriesReducer). C28 names "the domain-free part of
    aura-std" as engine-layer, so these stay put.
  • aura-market (new) — Session / SessionFrankfurt, Resample (C28 files
    session anchoring and bar resampling under market).
  • aura-strategy (new) — Bias, LongOnly, FixedStop, VolTfStop, Sizer, and
    the cost-model machinery (cost.rs trait/runner + ConstantCost, CarryCost,
    VolSlippageCost, CostSum). C28 files bias / stops / sizing / cost under
    strategy.
  • aura-backtest (new) — SimBroker, PositionManagement / ExitReason (C28 files
    the SimBroker and position-management under backtest).

Ambiguous placements resolved. Resample → market (OHLC / bar vocabulary, and
C28's explicit "bar resampling"). SimBroker → backtest (C28's explicit "the
SimBroker … under backtest", over its market-flavoured pip vocabulary). Latch
engine (a generic RTL SR-latch register; feeding SimBroker's exposure is
composition, not classification). The cost.rs trait/runner → strategy (a generic
adapter, but C28 files cost-model nodes under strategy and it carries no production
backtest import).

Considered and rejected: a combined strategy+backtest crate. The C28 phase-4
line reads "engine / market / strategy+backtest", readable as one combined crate.
Rejected: the nodes are fully decoupled (verified — no cost↔PM production import), so
nothing technical forces the weld; C28 already ratifies strategy (layer 4) and
backtest (layer 5) as distinct rungs, and phase 5 routes the backtest metrics (out
of aura-analysis) to a backtest home — so a distinct aura-backtest crate is the
target regardless. Combining would only defer a split the contract already mandates.
(Lower churn is the sole argument for combining, and effort is a tiebreaker at most.)

The closed-vocabulary roster (D3). vocabulary.rs's std_vocabulary /
std_vocabulary_types (the std_vocabulary_roster! single-source macro, #160) names
nodes across all four layers, so it cannot stay in engine-layer aura-std (that
would import outward). It moves to a new thin aura-vocabulary crate depending on the
four node crates; the macro invocation's node paths update to the new crates,
preserving the single-source no-drift property. Production reaches it only through
aura-cli's project::Env::resolve; the engine / registry test suites reach it as a
dev-dependency (C28-exempt). Consumers change the import path
aura_std::std_vocabularyaura_vocabulary::std_vocabulary, call sites unchanged.
A dedicated crate (over folding the roster into aura-composites) keeps
aura-engine's dev-dependency on the roster cycle-free, since aura-vocabulary
depends on no crate that reaches back to aura-engine.

Scope note. The wrap_r per-run scaffold stays in aura-cli this phase. The C28
phase-3 line attributes seeding an aura-backtest scaffold library to phase 3, but
34ff539 shipped only the shape dispatch + bare run_measurement. This phase moves
the backtest nodes; extracting the scaffold library is deferred so phase 4 stays a
pure node-roster split. Behaviour is byte-identical throughout (a structural cut).

## Design decisions (specify Step 1.5 — derived, not user-settled) Recon of the `aura-std` roster and its intra-crate + workspace import graph (engine commit `34ff539`) settles the split shape below. These are **derived** orchestrator decisions under the C28 layer model (`docs/design/INDEX.md`), recorded for after-the-fact audit and veto; none was settled by user preference. **Verified ground.** The only production intra-crate coupling in `aura-std` is (a) the four cost nodes → `cost.rs` (all within the strategy layer) and (b) `vocabulary.rs` → every node (the closed-roster dispatch table). Every other node is an `aura-core`-only leaf: no production `use crate::` crosses a layer boundary — `sim_broker.rs` / `position_management.rs` import no strategy or market node, and `cost.rs` imports no `position_management` (the "PM-geometry" coupling is a shared field-layout / R convention, not a Rust `use`). So the four ladder layers in the roster are cycle-free when cut into separate crates. **Crate split (D1/D2).** Four node crates, each depending only on `aura-core`: - `aura-std` — kept as the **engine**-layer node crate: arithmetic / logic / comparison / rolling-window nodes, the generic combinators (`When`, `LinComb`, `Select`, `Const`), the registers (`Delay`, `Latch`), and the generic sinks (`Recorder`, `GatedRecorder`, `SeriesReducer`). C28 names "the domain-free part of aura-std" as engine-layer, so these stay put. - `aura-market` (new) — `Session` / `SessionFrankfurt`, `Resample` (C28 files session anchoring and bar resampling under *market*). - `aura-strategy` (new) — `Bias`, `LongOnly`, `FixedStop`, `VolTfStop`, `Sizer`, and the cost-model machinery (`cost.rs` trait/runner + `ConstantCost`, `CarryCost`, `VolSlippageCost`, `CostSum`). C28 files bias / stops / sizing / cost under *strategy*. - `aura-backtest` (new) — `SimBroker`, `PositionManagement` / `ExitReason` (C28 files the SimBroker and position-management under *backtest*). **Ambiguous placements resolved.** `Resample` → market (OHLC / bar vocabulary, and C28's explicit "bar resampling"). `SimBroker` → backtest (C28's explicit "the SimBroker … under backtest", over its market-flavoured pip vocabulary). `Latch` → engine (a generic RTL SR-latch register; feeding SimBroker's exposure is composition, not classification). The `cost.rs` trait/runner → strategy (a generic adapter, but C28 files cost-model nodes under strategy and it carries no production backtest import). **Considered and rejected: a combined `strategy+backtest` crate.** The C28 phase-4 line reads "engine / market / strategy+backtest", readable as one combined crate. Rejected: the nodes are fully decoupled (verified — no cost↔PM production import), so nothing technical forces the weld; C28 already ratifies strategy (layer 4) and backtest (layer 5) as distinct rungs, and phase 5 routes the backtest *metrics* (out of `aura-analysis`) to a backtest home — so a distinct `aura-backtest` crate is the target regardless. Combining would only defer a split the contract already mandates. (Lower churn is the sole argument for combining, and effort is a tiebreaker at most.) **The closed-vocabulary roster (D3).** `vocabulary.rs`'s `std_vocabulary` / `std_vocabulary_types` (the `std_vocabulary_roster!` single-source macro, #160) names nodes across all four layers, so it cannot stay in engine-layer `aura-std` (that would import outward). It moves to a new thin `aura-vocabulary` crate depending on the four node crates; the macro invocation's node paths update to the new crates, preserving the single-source no-drift property. Production reaches it only through `aura-cli`'s `project::Env::resolve`; the engine / registry test suites reach it as a **dev-dependency** (C28-exempt). Consumers change the import path `aura_std::std_vocabulary` → `aura_vocabulary::std_vocabulary`, call sites unchanged. A dedicated crate (over folding the roster into `aura-composites`) keeps `aura-engine`'s dev-dependency on the roster cycle-free, since `aura-vocabulary` depends on no crate that reaches back to `aura-engine`. **Scope note.** The `wrap_r` per-run scaffold stays in `aura-cli` this phase. The C28 phase-3 line attributes seeding an `aura-backtest` scaffold *library* to phase 3, but `34ff539` shipped only the shape dispatch + bare `run_measurement`. This phase moves the backtest *nodes*; extracting the scaffold library is deferred so phase 4 stays a pure node-roster split. Behaviour is byte-identical throughout (a structural cut).
Author
Collaborator

Spec auto-signed (specify Step 6, /boss)

The design (the aura-std layer-crate split — decisions in comment
3875) was auto-signed on a grounding-check
PASS under /boss, with no human in the loop: an independent fresh-context
review ratified all load-bearing assumptions about current behaviour — the
cross-layer production decoupling of the roster, std_vocabulary's production
reach confined to aura-cli's resolve seam, the aura-engine / aura-registry
dev-dependency status, and the closed-roster macro's relocatability — against
currently-green tests and a clean cargo check --workspace. No golden is edited;
behaviour stays byte-identical. The design is settled and ready for plan
production. A user reply vetoes this signature.

## Spec auto-signed (specify Step 6, /boss) The design (the `aura-std` layer-crate split — decisions in comment [3875](issues/288#issuecomment-3875)) was auto-signed on a `grounding-check` **PASS** under `/boss`, with no human in the loop: an independent fresh-context review ratified all load-bearing assumptions about current behaviour — the cross-layer production decoupling of the roster, `std_vocabulary`'s production reach confined to `aura-cli`'s resolve seam, the `aura-engine` / `aura-registry` dev-dependency status, and the closed-roster macro's relocatability — against currently-green tests and a clean `cargo check --workspace`. No golden is edited; behaviour stays byte-identical. The design is settled and ready for plan production. A user reply vetoes this signature.
Author
Collaborator

Planner resolutions (specify → planner, derived)

Plan-recon surfaced four byte-level questions the spec left open; resolved here so
no implementer improvises.

  • chrono for aura-market. No [workspace.dependencies] chrono entry exists —
    aura-std / aura-engine / aura-ingest each pin it directly. aura-market
    mirrors that direct-pin style (chrono + chrono-tz, same versions/features as
    aura-std); no new workspace-level entry is introduced (that would be an unscoped
    change beyond this phase).
  • aura-std's own sma.rs test. Its #[cfg(test)] module references
    crate::{Bias, SimBroker}, which relocate to aura-strategy / aura-backtest.
    Resolution: add aura-strategy + aura-backtest to aura-std's
    [dev-dependencies] (test-only cross-layer edges are C28-exempt and cycle-free —
    the new crates depend only on aura-core), and rewrite the two test imports.
    This corrects the draft's "aura-std manifest unchanged": its [dependencies] is
    unchanged; its [dev-dependencies] gains two entries.
  • The c28_layering structural test's parser. No workspace-level toml;
    aura-cli pins toml = "0.8". aura-vocabulary takes the same pin as a
    [dev-dependencies] entry. The test inspects each node crate's [dependencies]
    table only — [dev-dependencies] are C28-exempt and deliberately not checked.
  • Reading a sibling manifest from the test. The repo idiom is
    env!("CARGO_MANIFEST_DIR") joined with /../.. to reach the workspace root, then
    crates/<name>/Cargo.toml per neighbour.

A cycle-close audit item, noted for later: the C28 Status block
(docs/design/INDEX.md, point 4 and the "aura-std holds four layers" paragraph)
describes this split as pending; landing this phase makes that prose current and it
is updated as part of the phase.

## Planner resolutions (specify → planner, derived) Plan-recon surfaced four byte-level questions the spec left open; resolved here so no implementer improvises. - **chrono for `aura-market`.** No `[workspace.dependencies]` chrono entry exists — `aura-std` / `aura-engine` / `aura-ingest` each pin it directly. `aura-market` mirrors that direct-pin style (`chrono` + `chrono-tz`, same versions/features as `aura-std`); no new workspace-level entry is introduced (that would be an unscoped change beyond this phase). - **`aura-std`'s own `sma.rs` test.** Its `#[cfg(test)]` module references `crate::{Bias, SimBroker}`, which relocate to `aura-strategy` / `aura-backtest`. Resolution: add `aura-strategy` + `aura-backtest` to `aura-std`'s `[dev-dependencies]` (test-only cross-layer edges are C28-exempt and cycle-free — the new crates depend only on `aura-core`), and rewrite the two test imports. This corrects the draft's "aura-std manifest unchanged": its `[dependencies]` is unchanged; its `[dev-dependencies]` gains two entries. - **The `c28_layering` structural test's parser.** No workspace-level `toml`; `aura-cli` pins `toml = "0.8"`. `aura-vocabulary` takes the same pin as a `[dev-dependencies]` entry. The test inspects each node crate's `[dependencies]` table only — `[dev-dependencies]` are C28-exempt and deliberately not checked. - **Reading a sibling manifest from the test.** The repo idiom is `env!("CARGO_MANIFEST_DIR")` joined with `/../..` to reach the workspace root, then `crates/<name>/Cargo.toml` per neighbour. A cycle-close audit item, noted for later: the C28 Status block (`docs/design/INDEX.md`, point 4 and the "aura-std holds four layers" paragraph) describes this split as pending; landing this phase makes that prose current and it is updated as part of the phase.
Author
Collaborator

Phase 4 implemented — awaiting review + merge

The aura-std roster split is implemented on branch issue-288-std-split
(commit b39fd63): aura-std is reduced to the engine nodes; aura-market,
aura-strategy, aura-backtest carry the outer rungs (each depending only on
aura-core); the closed roster moved to aura-vocabulary. Node modules moved
byte-identically (verified 0-change renames); consumers were rewired by import
path only. The full workspace suite is green (1448 tests, 0 failed), clippy -D warnings is clean, and a new structural test
(aura-vocabulary/tests/c28_layering.rs) enforces the C28 import direction —
catching the acyclic-but-outward edge the compiler misses. The C28 Status block
(docs/design/INDEX.md) is updated to mark phase 4 done.

The work awaits review and the ratified merge; the commit carries closes #288.

## Phase 4 implemented — awaiting review + merge The `aura-std` roster split is implemented on branch `issue-288-std-split` (commit `b39fd63`): `aura-std` is reduced to the engine nodes; `aura-market`, `aura-strategy`, `aura-backtest` carry the outer rungs (each depending only on `aura-core`); the closed roster moved to `aura-vocabulary`. Node modules moved byte-identically (verified 0-change renames); consumers were rewired by import path only. The full workspace suite is green (1448 tests, 0 failed), `clippy -D warnings` is clean, and a new structural test (`aura-vocabulary/tests/c28_layering.rs`) enforces the C28 import direction — catching the acyclic-but-outward edge the compiler misses. The C28 Status block (`docs/design/INDEX.md`) is updated to mark phase 4 done. The work awaits review and the ratified merge; the commit carries `closes #288`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#288