Split aura-analysis into generic statistics and backtest metrics (C28 phase 5) #291

Closed
opened 2026-07-20 11:47:01 +02:00 by claude · 1 comment
Collaborator

Phase 5 of the Stratification milestone (contract C28, Status block in docs/design/INDEX.md). User-ratified 2026-07-20 together with phase 2; the ratification and the split disposition of the coupled deferral are recorded on #147.

aura-analysis interweaves two roles:

  • Generic, domain-free statistics: inv_norm_cdf, expected_max_of_normals, MetricStats, the bootstrap kernel -- pure math naming no trading concept.
  • Backtest metrics: RMetrics, RunMetrics, summarize_r, r_metrics_from_rs, the position-event table (PositionEvent / PositionAction / derive_position_events), FamilySelection / SelectionMode.

Under C28 the statistics half is foundation-grade (usable from any rung), while the backtest-metrics half belongs to the backtest layer. The split is the precondition for phase 2 (cutting the aura-engine -> aura-analysis edge): after it, the engine may legally keep the domain-free statistics dependency -- only the backtest-metrics dependency is the layer violation.

Behaviour-preserving structural cut (C1); runs.jsonl byte-identity (C18) and all goldens unchanged. Exact crate names and the per-item roster assignment are derived against C28 during spec production and recorded here as decision-log comments before the spec is written.

Phase 5 of the Stratification milestone (contract C28, Status block in `docs/design/INDEX.md`). User-ratified 2026-07-20 together with phase 2; the ratification and the split disposition of the coupled deferral are recorded on #147. `aura-analysis` interweaves two roles: - **Generic, domain-free statistics**: `inv_norm_cdf`, `expected_max_of_normals`, `MetricStats`, the bootstrap kernel -- pure math naming no trading concept. - **Backtest metrics**: `RMetrics`, `RunMetrics`, `summarize_r`, `r_metrics_from_rs`, the position-event table (`PositionEvent` / `PositionAction` / `derive_position_events`), `FamilySelection` / `SelectionMode`. Under C28 the statistics half is foundation-grade (usable from any rung), while the backtest-metrics half belongs to the backtest layer. The split is the precondition for phase 2 (cutting the `aura-engine -> aura-analysis` edge): after it, the engine may legally keep the domain-free statistics dependency -- only the backtest-metrics dependency is the layer violation. Behaviour-preserving structural cut (C1); `runs.jsonl` byte-identity (C18) and all goldens unchanged. Exact crate names and the per-item roster assignment are derived against C28 during spec production and recorded here as decision-log comments before the spec is written.
claude self-assigned this 2026-07-20 11:47:01 +02:00
Author
Collaborator

Decision log (spec production) -- derived roster and crate assignment

Recon of the current tree (main HEAD b39fd63) settles the split's load-bearing forks. All five are derived decisions recorded with rationale for audit and veto; the phase itself was user-ratified 2026-07-20 (recorded on #147).

Ground (verified). aura-analysis is a single file (crates/aura-analysis/src/lib.rs, 1163 lines) whose two halves are internally uncoupled: the generic-statistics items (inv_norm_cdf at :501, expected_max_of_normals at :534) are called by no backtest item and reference no backtest type; the composition happens only externally (crates/aura-registry/src/lib.rs:824). MetricStats and the moving-block bootstrap kernel do NOT live in aura-analysis -- they are native to aura-engine/src/mc.rs (:48, :193) and out of this phase's scope (they belong to the engine-edge cut, phase 2).

  • D1 -- backtest metrics move to aura-backtest. RunMetrics, RMetrics, summarize_r, r_metrics_from_rs, PositionEvent/PositionAction/derive_position_events (with the private r_col/cost_col column maps and SQN_CAP) move into the existing backtest-layer crate as a metrics module. Rationale: C28 files backtest reductions under the backtest layer; and the positional-column lockstep contract between summarize_r and PositionManagement::FIELD_NAMES (crates/aura-backtest/src/position_management.rs:28-49 vs today's crates/aura-analysis/src/lib.rs:137-164) becomes intra-crate, removing a documented cross-crate fragility (that contract comment still names aura-std, stale since #288 -- evidence the cross-crate form drifts). aura-backtest gains the workspace serde dependency; its aura-* dependency set stays {aura-core}, so the C28 structural test (crates/aura-vocabulary/tests/c28_layering.rs) is unaffected by design (it filters non-aura deps).
  • D2 -- aura-analysis stays and becomes the domain-free statistics crate. Post-move it holds inv_norm_cdf + expected_max_of_normals (plus D3), with [dependencies] shrinking to serde only (no aura-core type remains). Rationale: no new workspace member, no rename cascade; the remaining content is exactly the "generic statistics" half C28 names; a pure-statistics crate is foundation-grade, importable from any rung without violating the ladder direction.
  • D3 -- FamilySelection + SelectionMode stay in aura-analysis. By heritage they are sweep-selection provenance, but by content they are domain-free (metric NAME as a free string, trial counts, deflation/plateau annotation scalars, seeds -- verified field-by-field at crates/aura-analysis/src/lib.rs:17-53: no R, no pip, no trading type). Keeping them beside the hurdle math means RunManifest.selection (engine-side) keeps embedding a foundation-grade type, so the phase-2 edge cut needs genericity only for RunReport.metrics, not a RunManifest redesign. This is the structurally decisive assignment of the split.
  • D4 -- the engine keeps re-exporting all eleven names, source-compatible. crates/aura-engine/src/report.rs:20 becomes two pub use lines (seven names from aura_backtest, four from aura_analysis); every indirect consumer (aura-registry, aura-cli, aura-composites, aura-ingest) compiles unchanged. Accepted consequence: aura-backtest moves from aura-engine's dev-dependencies into [dependencies] -- a transient widening of the known layer violation, removed by phase 2 on the same branch (the ratified order, #147).
  • D5 -- aura-campaign gains aura-backtest as a dev-dependency only. Its production code reaches RunMetrics through the engine re-export (crates/aura-campaign/src/exec.rs:17), which D4 preserves; the only direct import of a moving symbol is the metric-vocabulary drift guard (crates/aura-campaign/tests/metric_vocabulary_e2e.rs:12), a test target. The guard's import splits across the two crates (summarize_r/RunMetrics from aura_backtest; FamilySelection/SelectionMode unchanged from aura_analysis); its 17-name assertion is untouched.

Scope notes: mc.rs's R/bias-typed fields (McAggregate, RBootstrap) are untouched (phase 2). Extending the C28 structural test's coverage beyond the node crates is deferred to phase 2, when the target edges exist. Behaviour-preserving throughout: serde shapes byte-identical (C18), goldens and the drift guard stay green; the bulk move is a git mv so blame survives.

## Decision log (spec production) -- derived roster and crate assignment Recon of the current tree (main HEAD b39fd63) settles the split's load-bearing forks. All five are derived decisions recorded with rationale for audit and veto; the phase itself was user-ratified 2026-07-20 (recorded on #147). **Ground (verified).** `aura-analysis` is a single file (`crates/aura-analysis/src/lib.rs`, 1163 lines) whose two halves are internally uncoupled: the generic-statistics items (`inv_norm_cdf` at :501, `expected_max_of_normals` at :534) are called by no backtest item and reference no backtest type; the composition happens only externally (`crates/aura-registry/src/lib.rs:824`). `MetricStats` and the moving-block bootstrap kernel do NOT live in `aura-analysis` -- they are native to `aura-engine/src/mc.rs` (:48, :193) and out of this phase's scope (they belong to the engine-edge cut, phase 2). - **D1 -- backtest metrics move to `aura-backtest`.** `RunMetrics`, `RMetrics`, `summarize_r`, `r_metrics_from_rs`, `PositionEvent`/`PositionAction`/`derive_position_events` (with the private `r_col`/`cost_col` column maps and `SQN_CAP`) move into the existing backtest-layer crate as a `metrics` module. Rationale: C28 files backtest reductions under the backtest layer; and the positional-column lockstep contract between `summarize_r` and `PositionManagement::FIELD_NAMES` (`crates/aura-backtest/src/position_management.rs:28-49` vs today's `crates/aura-analysis/src/lib.rs:137-164`) becomes intra-crate, removing a documented cross-crate fragility (that contract comment still names `aura-std`, stale since #288 -- evidence the cross-crate form drifts). `aura-backtest` gains the workspace `serde` dependency; its aura-* dependency set stays `{aura-core}`, so the C28 structural test (`crates/aura-vocabulary/tests/c28_layering.rs`) is unaffected by design (it filters non-aura deps). - **D2 -- `aura-analysis` stays and becomes the domain-free statistics crate.** Post-move it holds `inv_norm_cdf` + `expected_max_of_normals` (plus D3), with `[dependencies]` shrinking to `serde` only (no `aura-core` type remains). Rationale: no new workspace member, no rename cascade; the remaining content is exactly the "generic statistics" half C28 names; a pure-statistics crate is foundation-grade, importable from any rung without violating the ladder direction. - **D3 -- `FamilySelection` + `SelectionMode` stay in `aura-analysis`.** By heritage they are sweep-selection provenance, but by content they are domain-free (metric NAME as a free string, trial counts, deflation/plateau annotation scalars, seeds -- verified field-by-field at `crates/aura-analysis/src/lib.rs:17-53`: no R, no pip, no trading type). Keeping them beside the hurdle math means `RunManifest.selection` (engine-side) keeps embedding a foundation-grade type, so the phase-2 edge cut needs genericity only for `RunReport.metrics`, not a `RunManifest` redesign. This is the structurally decisive assignment of the split. - **D4 -- the engine keeps re-exporting all eleven names, source-compatible.** `crates/aura-engine/src/report.rs:20` becomes two `pub use` lines (seven names from `aura_backtest`, four from `aura_analysis`); every indirect consumer (`aura-registry`, `aura-cli`, `aura-composites`, `aura-ingest`) compiles unchanged. Accepted consequence: `aura-backtest` moves from `aura-engine`'s dev-dependencies into `[dependencies]` -- a transient widening of the known layer violation, removed by phase 2 on the same branch (the ratified order, #147). - **D5 -- `aura-campaign` gains `aura-backtest` as a dev-dependency only.** Its production code reaches `RunMetrics` through the engine re-export (`crates/aura-campaign/src/exec.rs:17`), which D4 preserves; the only direct import of a moving symbol is the metric-vocabulary drift guard (`crates/aura-campaign/tests/metric_vocabulary_e2e.rs:12`), a test target. The guard's import splits across the two crates (`summarize_r`/`RunMetrics` from `aura_backtest`; `FamilySelection`/`SelectionMode` unchanged from `aura_analysis`); its 17-name assertion is untouched. Scope notes: `mc.rs`'s R/bias-typed fields (`McAggregate`, `RBootstrap`) are untouched (phase 2). Extending the C28 structural test's coverage beyond the node crates is deferred to phase 2, when the target edges exist. Behaviour-preserving throughout: serde shapes byte-identical (C18), goldens and the drift guard stay green; the bulk move is a `git mv` so blame survives.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#291