Extract the trading analysis layer (report.rs) out of aura-engine; keep the engine domain-agnostic #136

Closed
opened 2026-06-24 23:24:57 +02:00 by Brummel · 5 comments
Owner

Motivation

aura-engine is named and documented as "the headless, UI-agnostic reactive SoA
engine", and its hot path genuinely is domain-free: per C7 it streams only the
four type-erased scalars and never names exposure/R/equity, and C14 keeps it
UI-agnostic. But the crate also ships a trading-domain analysis layer that has
nothing to do with the dataflow engine, and the orchestration types are nailed to
it. aura is "a game engine for traders" — trading is the game, so agnosticism
is a means (a fast, deterministic, composable substrate), not an end — but SoC
is still worth keeping where it costs little
: the engine should be agnostic as
far as possible
, and the analysis layer is separable.

The drift is also self-reinforcing, and that is the trigger for filing this now.
Cycle 0068 (#115) placed derive_position_events in aura-engine with the
rationale "sibling of summarize_r" / "beside summarize" — i.e. "the trading
code is already there, so more of it belongs there too."
That is not a design
rationale; it is the existing wart justifying its own growth (cf. CLAUDE.md
"Design rationale ≠ implementation effort" — consistency-with-an-existing-placement
is the same category of non-reason). Each domain addition makes the next one look
natural. Left alone, aura-engine keeps accreting the trading stdlib.

Validated: what is domain vs generic in crates/aura-engine/src/report.rs

Trading-domain (should leave the engine):

  • RunMetrics (report.rs:17) — total_pips, max_drawdown, bias_sign_flips, r: Option<RMetrics>
  • RMetrics (report.rs:45) — expectancy_r, sqn, sqn_normalized, win_rate, profit_factor, max_r_drawdown, net_expectancy_r, conviction_terciles_r, …
  • summarize (report.rs:369) — pip-equity + bias reduction
  • summarize_r (report.rs:89) — R-metric reduction
  • PositionAction (report.rs:280) + PositionEvent (report.rs:314)
  • derive_position_events (report.rs:231)

Domain-free (stays — generic SoA / trace tooling):

  • ColumnarTrace (report.rs:487), join_on_ts (report.rs:462), JoinedRow
    (report.rs:443), f64_field (report.rs:425) — generic columnar/trace utilities
  • RunManifest (report.rs:328) — generic run identity (commit, params, window,
    seed); carries a free-text label that today happens to describe a broker
    (judgement call at plan time whether the label stays free-form here)
  • RunReport (report.rs:348) — the C18 (manifest, metrics) pair; generic once
    parameterised over the metric type
    (today concrete on RunMetrics)

Validated: the coupling (why this is non-trivial, not a file move)

The concrete RunMetrics is woven into the generic orchestration and the
registry, so extraction means parameterising the engine over a metric type:

  • crates/aura-engine/src/{sweep.rs (18), mc.rs (21), walkforward.rs (24)} — the
    SweepFamily / McFamily / WalkForwardResult families and their optimise
    closures carry RunMetrics.
  • crates/aura-registry/src/{lib.rs (36), compat.rs (10), lineage.rs (6)} — the
    C18 run store loads/stores RunReport/RunMetrics and owns the metric_cmp /
    rank vocabulary (itself domain: sqn, expectancy_r, total_pips, …).
  • crates/aura-cli/src/main.rs (66) — the largest consumer; folds summarize /
    summarize_r, builds RunReport, ranks families.

Proposed direction (settle in a spec, not here)

  1. New crate aura-analysis (name TBD): RunMetrics, RMetrics, summarize,
    summarize_r, PositionEvent, PositionAction, derive_position_events.
    Depends on aura-core (it reads type-erased Scalar records positionally, as
    it already does — so it does not link aura-std, same discipline as today).
  2. aura-engine keeps the generic trace utilities + a metric-generic
    RunReport<M> and orchestration (sweep/mc/walkforward) over a bounded
    generic M (the bound covers serde + whatever the rank/compare surface needs).
  3. aura-registry either parameterises over M too, or the domain rank
    vocabulary (metric_cmp) moves to aura-analysis; decide at spec time.
  4. aura-cli + the test crates instantiate M = the trading metric.

Invariant fit / hard constraints

  • C18 wire back-compat is the hard gate. The on-disk runs.jsonl shape must
    stay byte-identical (existing records still deserialise). A generic M must
    serialise to the same JSON — the serde(default) / skip_serializing_if
    discipline already in RunMetrics/RMetrics has to survive the move.
  • C7 / C14 unchanged — the hot path is already domain-free; this does not
    touch it. Behaviour-preserving (C1): a pure refactor + re-parameterisation,
    no metric value changes, all existing goldens/serde tests stay green.
  • Watch the generic-bound blast radius across sweep/mc/walkforward and the
    registry — this is the bulk of the work and likely spans 2-3 cycles (consider
    promoting to a milestone with its own spec).

Out of scope

  • Any hot-path change (already agnostic).
  • Any metric/behaviour change (pure SoC refactor).
  • Renaming/moving the generic trace utilities out of the engine.

Acceptance

  • The trading metric/position-event types + reductions live outside aura-engine.
  • aura-engine's [dependencies] and public surface name no trading concept
    (the orchestration + RunReport are generic over the metric type).
  • aura-engine still builds with only aura-core + serde deps; the generic
    trace utilities (ColumnarTrace/join_on_ts/f64_field) remain.
  • runs.jsonl back-compat: existing records still deserialise, on-disk JSON
    byte-unchanged (C18).
  • cargo build/test --workspace + clippy -D warnings clean; no metric value
    drift (goldens unchanged).

refs #115 (the placement whose "sibling of summarize_r" rationale is the creep this addresses)

## Motivation `aura-engine` is named and documented as "the headless, UI-agnostic reactive SoA engine", and its *hot path* genuinely is domain-free: per C7 it streams only the four type-erased scalars and never names `exposure`/`R`/`equity`, and C14 keeps it UI-agnostic. But the *crate* also ships a trading-domain analysis layer that has nothing to do with the dataflow engine, and the orchestration types are nailed to it. aura is "a game engine **for traders**" — trading is the game, so agnosticism is a means (a fast, deterministic, composable substrate), not an end — **but SoC is still worth keeping where it costs little**: the engine should be agnostic *as far as possible*, and the analysis layer is separable. The drift is also self-reinforcing, and that is the trigger for filing this now. Cycle 0068 (#115) placed `derive_position_events` in `aura-engine` with the rationale "sibling of `summarize_r`" / "beside `summarize`" — i.e. *"the trading code is already there, so more of it belongs there too."* That is not a design rationale; it is the existing wart justifying its own growth (cf. CLAUDE.md "Design rationale ≠ implementation effort" — consistency-with-an-existing-placement is the same category of non-reason). Each domain addition makes the next one look natural. Left alone, `aura-engine` keeps accreting the trading stdlib. ## Validated: what is domain vs generic in `crates/aura-engine/src/report.rs` **Trading-domain (should leave the engine):** - `RunMetrics` (report.rs:17) — `total_pips`, `max_drawdown`, `bias_sign_flips`, `r: Option<RMetrics>` - `RMetrics` (report.rs:45) — `expectancy_r`, `sqn`, `sqn_normalized`, `win_rate`, `profit_factor`, `max_r_drawdown`, `net_expectancy_r`, `conviction_terciles_r`, … - `summarize` (report.rs:369) — pip-equity + bias reduction - `summarize_r` (report.rs:89) — R-metric reduction - `PositionAction` (report.rs:280) + `PositionEvent` (report.rs:314) - `derive_position_events` (report.rs:231) **Domain-free (stays — generic SoA / trace tooling):** - `ColumnarTrace` (report.rs:487), `join_on_ts` (report.rs:462), `JoinedRow` (report.rs:443), `f64_field` (report.rs:425) — generic columnar/trace utilities - `RunManifest` (report.rs:328) — generic run identity (commit, params, window, seed); carries a free-text label that today happens to describe a broker (judgement call at plan time whether the label stays free-form here) - `RunReport` (report.rs:348) — the C18 `(manifest, metrics)` pair; generic *once parameterised over the metric type* (today concrete on `RunMetrics`) ## Validated: the coupling (why this is non-trivial, not a file move) The concrete `RunMetrics` is woven into the generic orchestration and the registry, so extraction means **parameterising the engine over a metric type**: - `crates/aura-engine/src/{sweep.rs (18), mc.rs (21), walkforward.rs (24)}` — the `SweepFamily` / `McFamily` / `WalkForwardResult` families and their optimise closures carry `RunMetrics`. - `crates/aura-registry/src/{lib.rs (36), compat.rs (10), lineage.rs (6)}` — the C18 run store loads/stores `RunReport`/`RunMetrics` and owns the `metric_cmp` / rank vocabulary (itself domain: `sqn`, `expectancy_r`, `total_pips`, …). - `crates/aura-cli/src/main.rs (66)` — the largest consumer; folds `summarize` / `summarize_r`, builds `RunReport`, ranks families. ## Proposed direction (settle in a spec, not here) 1. New crate `aura-analysis` (name TBD): `RunMetrics`, `RMetrics`, `summarize`, `summarize_r`, `PositionEvent`, `PositionAction`, `derive_position_events`. Depends on `aura-core` (it reads type-erased `Scalar` records positionally, as it already does — so it does **not** link `aura-std`, same discipline as today). 2. `aura-engine` keeps the generic trace utilities + a metric-generic `RunReport<M>` and orchestration (`sweep`/`mc`/`walkforward`) over a bounded generic `M` (the bound covers serde + whatever the rank/compare surface needs). 3. `aura-registry` either parameterises over `M` too, or the domain rank vocabulary (`metric_cmp`) moves to `aura-analysis`; decide at spec time. 4. `aura-cli` + the test crates instantiate `M` = the trading metric. ## Invariant fit / hard constraints - **C18 wire back-compat is the hard gate.** The on-disk `runs.jsonl` shape must stay byte-identical (existing records still deserialise). A generic `M` must serialise to the same JSON — the `serde(default)` / `skip_serializing_if` discipline already in `RunMetrics`/`RMetrics` has to survive the move. - **C7 / C14 unchanged** — the hot path is already domain-free; this does not touch it. **Behaviour-preserving** (C1): a pure refactor + re-parameterisation, no metric value changes, all existing goldens/serde tests stay green. - Watch the generic-bound blast radius across `sweep`/`mc`/`walkforward` and the registry — this is the bulk of the work and likely spans 2-3 cycles (consider promoting to a milestone with its own spec). ## Out of scope - Any hot-path change (already agnostic). - Any metric/behaviour change (pure SoC refactor). - Renaming/moving the generic trace utilities out of the engine. ## Acceptance - [ ] The trading metric/position-event types + reductions live outside `aura-engine`. - [ ] `aura-engine`'s `[dependencies]` and public surface name no trading concept (the orchestration + `RunReport` are generic over the metric type). - [ ] `aura-engine` still builds with only `aura-core` + `serde` deps; the generic trace utilities (`ColumnarTrace`/`join_on_ts`/`f64_field`) remain. - [ ] `runs.jsonl` back-compat: existing records still deserialise, on-disk JSON byte-unchanged (C18). - [ ] `cargo build/test --workspace` + `clippy -D warnings` clean; no metric value drift (goldens unchanged). refs #115 (the placement whose "sibling of summarize_r" rationale is the creep this addresses)
Brummel added the feature label 2026-06-24 23:24:57 +02:00
Author
Owner

/boss run — decision log (cycle 1 of this extraction)

Provenance. Entered /boss on this issue by explicit user instruction
("#136 — autonomously"). This run is anchored to session anchor 3e8fd73
(last user-ratified main HEAD). A pre-iteration survey (architect drift
review + readiness recon of the open candidates + adversarial challenge)
recommended sequencing this SoC extraction before the only open milestone
(Stage-2 realistic broker), on substance: the broker milestone authors
currency-metrics + position-event code whose natural home is exactly
report.rs, and #121 already mandates the #[serde(default)] back-compat
discipline this boundary establishes — so erecting the seam first means
Stage-2 lands in the right crate by construction rather than deepening a wart
a later sweep must unpick field-by-field.

Entry path: compiler-driven (not the heavy specify → planner → implement
loop). The cycle-1 cut is a behaviour-preserving definition relocation the
type checker enumerates; the existing C18 on-disk byte-identity goldens
(runs.jsonl shape, the full-object asserts in cli_run.rs, the legacy-line
loads in aura-registry) are the safety net. Verdict = clean workspace build
AND suite green unchanged.

Cycle-1 scope (derived, fork-free). Stand up a new crate aura-analysis
(dep: aura-core only) and relocate the pure trading-domain leaf out of
crates/aura-engine/src/report.rs:

  • Moves to aura-analysis: RunMetrics, RMetrics (+ its hand-written
    PartialEq), PositionAction (+ From/TryFrom<i64>), PositionEvent,
    summarize_r, r_metrics_from_rs, derive_position_events, and the
    deflation stats helpers inv_norm_cdf / expected_max_of_normals (+ the
    private r_col indices and SQN_CAP they pull). Verified: none of these
    reference the engine-local trace utils in their bodies, so aura-analysis
    needs only aura-core — no analysis → engine edge, no dependency cycle.
  • Stays in aura-engine: the generic trace plumbing (ColumnarTrace,
    join_on_ts, JoinedRow, f64_field, kind_tag, scalar_to_f64),
    RunManifest, RunReport, and summarize (it bridges recorded trace
    columns into RunMetrics, so it is trace-coupled — kept at the boundary
    this cycle).
  • Re-export to keep downstream byte-identical: report.rs pub uses the
    moved symbols back into its own namespace, so aura-engine's lib.rs
    re-export block and every crate::report::* / aura_engine::* consumer
    (aura-registry, aura-cli, aura-composites, aura-ingest, and the
    in-crate tests) compile unchanged. The only edited files are the new crate,
    the workspace Cargo.toml member list, aura-engine's Cargo.toml (new
    dep), and report.rs itself. The moved functions' unit tests stay in
    report.rs this cycle (exercising the re-exported symbols) to keep the test
    surface byte-unchanged; relocating them is a later cosmetic pass.

Deferred to later cycles (the genuine design forks — NOT this cycle):
making RunReport generic over a metric type M and re-bounding
sweep.rs / mc.rs / walkforward.rs; relocating the aura-registry
Metric / metric_cmp / rank vocabulary (the "registry-over-M vs.
metric-moves" fork the issue itself leaves open); and relocating the
mis-placed orchestration types FamilySelection / SelectionMode out of
report.rs (a dependency-direction tangle — registry already depends on the
engine — best decided with the registry-vocabulary fork). Those carry real
choices and route through specify when reached.

Adjacent. The ledger's C10 cycle-0065 note is stale on where composites
live (says aura-engine; they moved to aura-composites, aura-std is now a
dev-dep) — sync at this cycle's audit.

## `/boss` run — decision log (cycle 1 of this extraction) **Provenance.** Entered `/boss` on this issue by explicit user instruction ("#136 — autonomously"). This run is anchored to session anchor `3e8fd73` (last user-ratified `main` HEAD). A pre-iteration survey (architect drift review + readiness recon of the open candidates + adversarial challenge) recommended sequencing this SoC extraction **before** the only open milestone (Stage-2 realistic broker), on substance: the broker milestone authors currency-metrics + position-event code whose natural home is exactly `report.rs`, and #121 already mandates the `#[serde(default)]` back-compat discipline this boundary establishes — so erecting the seam first means Stage-2 lands in the right crate by construction rather than deepening a wart a later sweep must unpick field-by-field. **Entry path: `compiler-driven`** (not the heavy `specify → planner → implement` loop). The cycle-1 cut is a behaviour-preserving definition relocation the type checker enumerates; the existing C18 on-disk byte-identity goldens (`runs.jsonl` shape, the full-object asserts in `cli_run.rs`, the legacy-line loads in `aura-registry`) are the safety net. Verdict = clean workspace build AND suite green unchanged. **Cycle-1 scope (derived, fork-free).** Stand up a new crate `aura-analysis` (dep: `aura-core` only) and relocate the pure trading-domain leaf out of `crates/aura-engine/src/report.rs`: - **Moves to `aura-analysis`:** `RunMetrics`, `RMetrics` (+ its hand-written `PartialEq`), `PositionAction` (+ `From`/`TryFrom<i64>`), `PositionEvent`, `summarize_r`, `r_metrics_from_rs`, `derive_position_events`, and the deflation stats helpers `inv_norm_cdf` / `expected_max_of_normals` (+ the private `r_col` indices and `SQN_CAP` they pull). Verified: none of these reference the engine-local trace utils in their bodies, so `aura-analysis` needs only `aura-core` — no `analysis → engine` edge, no dependency cycle. - **Stays in `aura-engine`:** the generic trace plumbing (`ColumnarTrace`, `join_on_ts`, `JoinedRow`, `f64_field`, `kind_tag`, `scalar_to_f64`), `RunManifest`, `RunReport`, and `summarize` (it bridges recorded trace columns into `RunMetrics`, so it is trace-coupled — kept at the boundary this cycle). - **Re-export to keep downstream byte-identical:** `report.rs` `pub use`s the moved symbols back into its own namespace, so `aura-engine`'s `lib.rs` re-export block and every `crate::report::*` / `aura_engine::*` consumer (`aura-registry`, `aura-cli`, `aura-composites`, `aura-ingest`, and the in-crate tests) compile unchanged. The only edited files are the new crate, the workspace `Cargo.toml` member list, `aura-engine`'s `Cargo.toml` (new dep), and `report.rs` itself. The moved functions' unit tests stay in `report.rs` this cycle (exercising the re-exported symbols) to keep the test surface byte-unchanged; relocating them is a later cosmetic pass. **Deferred to later cycles (the genuine design forks — NOT this cycle):** making `RunReport` generic over a metric type `M` and re-bounding `sweep.rs` / `mc.rs` / `walkforward.rs`; relocating the `aura-registry` `Metric` / `metric_cmp` / rank vocabulary (the "registry-over-M vs. metric-moves" fork the issue itself leaves open); and relocating the mis-placed orchestration types `FamilySelection` / `SelectionMode` out of `report.rs` (a dependency-direction tangle — registry already depends on the engine — best decided with the registry-vocabulary fork). Those carry real choices and route through `specify` when reached. **Adjacent.** The ledger's C10 cycle-0065 note is stale on where composites live (says `aura-engine`; they moved to `aura-composites`, `aura-std` is now a dev-dep) — sync at this cycle's audit.
Author
Owner

/boss decision (cycle 1) — r_col private-module coupling forces test relocation

The first extraction attempt surfaced a hole the cycle-1 plan missed: report.rs's
#[cfg(test)] helpers (r_row / r_row_full / pm_row, 16 references) reach the
private mod r_col column indices directly — not through the public moved
functions. A pub use bridge cannot re-export a private module, so the original
"keep all tests in report.rs, reach the moved symbols via the re-export" framing is
unworkable once mod r_col moves.

Decision (derived, not a brainstorm fork): tests move with their subject. The
moved functions' unit tests AND their private helpers (r_row / r_row_full /
pm_row) AND mod r_col relocate into aura-analysis alongside the code they
exercise; report.rs keeps only the tests for the symbols that stay (summarize,
RunReport, RunManifest, FamilySelection/SelectionMode, the trace utils, the
end-to-end harness test). This is a faithful relocation — assertions byte-identical,
just hosted in the crate where the subject now lives — and is strictly better than the
alternatives: making r_col public would leak an internal column-index contract
(deliberately private per its own doc); duplicating the constants would split a
single-source-of-truth.

The earlier "zero test-file churn" framing is dropped — it was an accommodation for the
compiler-driven-edit workflow's strict verify gate (which mis-fired and was abandoned;
see Skills #11), not a project constraint. The C18 on-disk byte-identity goldens are
unaffected: they live in aura-cli's cli_run.rs, the aura-registry legacy-line
tests, and the engine integration tests under tests/ — never in report.rs's unit
module — and the re-exported types serialise byte-identically, so those goldens stay
green.

Partition (verified against the current test module):

  • aura-analysis: inv_norm_cdf / expected_max_of_normals tests; the
    position_event* serde + PositionAction tests; the summarize_r_* tests; the
    derive_* tests; helpers r_row / r_row_full / pm_row; the private mod r_col.
  • stays in report.rs: runmanifest_*, family_selection_*, report_is_deterministic_end_to_end
    (+ build_two_sink_harness / run_once), the summarize_* (total-pips / drawdown /
    sign-flips) tests, to_json_renders_the_canonical_form, helper samples.
## `/boss` decision (cycle 1) — `r_col` private-module coupling forces test relocation The first extraction attempt surfaced a hole the cycle-1 plan missed: report.rs's `#[cfg(test)]` helpers (`r_row` / `r_row_full` / `pm_row`, 16 references) reach the **private** `mod r_col` column indices directly — not through the public moved functions. A `pub use` bridge cannot re-export a private module, so the original "keep all tests in report.rs, reach the moved symbols via the re-export" framing is unworkable once `mod r_col` moves. **Decision (derived, not a brainstorm fork): tests move with their subject.** The moved functions' unit tests AND their private helpers (`r_row` / `r_row_full` / `pm_row`) AND `mod r_col` relocate into `aura-analysis` alongside the code they exercise; report.rs keeps only the tests for the symbols that stay (`summarize`, `RunReport`, `RunManifest`, `FamilySelection`/`SelectionMode`, the trace utils, the end-to-end harness test). This is a faithful relocation — assertions byte-identical, just hosted in the crate where the subject now lives — and is strictly better than the alternatives: making `r_col` public would leak an internal column-index contract (deliberately private per its own doc); duplicating the constants would split a single-source-of-truth. The earlier "zero test-file churn" framing is dropped — it was an accommodation for the `compiler-driven-edit` workflow's strict verify gate (which mis-fired and was abandoned; see Skills #11), not a project constraint. The C18 on-disk byte-identity goldens are **unaffected**: they live in `aura-cli`'s `cli_run.rs`, the `aura-registry` legacy-line tests, and the engine integration tests under `tests/` — never in report.rs's unit module — and the re-exported types serialise byte-identically, so those goldens stay green. **Partition (verified against the current test module):** - → `aura-analysis`: `inv_norm_cdf` / `expected_max_of_normals` tests; the `position_event*` serde + `PositionAction` tests; the `summarize_r_*` tests; the `derive_*` tests; helpers `r_row` / `r_row_full` / `pm_row`; the private `mod r_col`. - stays in report.rs: `runmanifest_*`, `family_selection_*`, `report_is_deterministic_end_to_end` (+ `build_two_sink_harness` / `run_once`), the `summarize_*` (total-pips / drawdown / sign-flips) tests, `to_json_renders_the_canonical_form`, helper `samples`.
Author
Owner

Cycle 1 landed (commits 94c88eb extraction + a8f67c7 audit). The pure analysis leaf (RunMetrics, RMetrics, summarize_r, r_metrics_from_rs, derive_position_events, PositionEvent/PositionAction, the deflation hurdle math) now lives in a new aura-analysis crate (aura-core + serde only; serde_json is a test-only dev-dep). aura-engine pub-re-exports for source compatibility, all four downstream crates byte-unchanged, full suite 665/0 incl. the C18 goldens, clippy clean. Ledger synced (a C16 cycle-0079 realization note + the stale C10 crate-topology spots). The key sequencing win is captured: the broker milestone's currency-metric additions (#121) now extend RunMetrics in aura-analysis by construction.

Remaining #136 tail (a new, fork-bearing cycle — deferred, not started): relocate the aura-registry Metric/metric_cmp/rank vocabulary; make RunReport generic over the metric type M and re-bound sweep/mc/walkforward; relocate the mis-placed FamilySelection/SelectionMode. This carries the open registry-over-M fork the issue body flagged: does aura-registry parameterise over a Metric trait (generic), or just depend on aura-analysis for the concrete Metric vocabulary (simpler)? Decide at spec time.

Cycle 1 landed (commits 94c88eb extraction + a8f67c7 audit). The pure analysis leaf (RunMetrics, RMetrics, summarize_r, r_metrics_from_rs, derive_position_events, PositionEvent/PositionAction, the deflation hurdle math) now lives in a new aura-analysis crate (aura-core + serde only; serde_json is a test-only dev-dep). aura-engine pub-re-exports for source compatibility, all four downstream crates byte-unchanged, full suite 665/0 incl. the C18 goldens, clippy clean. Ledger synced (a C16 cycle-0079 realization note + the stale C10 crate-topology spots). The key sequencing win is captured: the broker milestone's currency-metric additions (#121) now extend RunMetrics *in aura-analysis* by construction. Remaining #136 tail (a new, fork-bearing cycle — deferred, not started): relocate the aura-registry Metric/metric_cmp/rank vocabulary; make RunReport generic over the metric type M and re-bound sweep/mc/walkforward; relocate the mis-placed FamilySelection/SelectionMode. This carries the open registry-over-M fork the issue body flagged: does aura-registry parameterise over a Metric trait (generic), or just depend on aura-analysis for the concrete Metric vocabulary (simpler)? Decide at spec time.
Author
Owner

/boss decision (cycle 2) — registry stays domain-aware; #136 remainder is the FamilySelection/SelectionMode relocation only

The fork — and who decided it. The deferred #136 tail was framed as "make
RunReport generic over a metric type M and relocate the aura-registry
Metric/metric_cmp vocabulary". Inspecting the code showed the registry's
domain coupling is not in the vocabulary but in the algorithms:
metric_value (lib.rs:137) reaches into RunMetrics/RMetrics fields, and
optimize_deflated (lib.rs:386) branches on is_r_metric — R metrics get a
moving-block bootstrap, total_pips a closed-form dispersion floor. So "move
the vocabulary to aura-analysis, registry depends on it" would not decouple
the registry; it would stay thoroughly trading-aware.

This reframed the question to an architecture-intent fork the sources do not
settle: should the orchestration/World layer (aura-registry) be a reusable,
domain-agnostic substrate (symmetric with the engine), or is it legitimately
trading-aware?
Even full genericity would not erase the domain knowledge — the
R-vs-pips deflation statistics are irreducibly domain and would have to be
injected via a trait with exactly one implementor (the trading metrics) — the
speculative-generality smell.

Decision (user-ratified in the session discussion, 2026-06-27 — provenance, not
a derived fork):
the registry is legitimately trading-aware; the
Metric/metric_cmp/optimize_deflated vocabulary and statistics stay in
aura-registry
(it is the trading selection layer, not the domain-free engine).
Genericity is not forced now (a one-implementor abstraction). "The World as a
domain-agnostic orchestration substrate" is deferred to the World / C21
milestone, where it would actually pay off — not bolted onto #136.

This cycle's scope (the genuine #136 remainder, small & behaviour-preserving):
relocate the orchestration-provenance types FamilySelection / SelectionMode
out of the engine's report.rs into aura-analysis (the only non-cyclic home
that gets them out of the engine — RunManifest.selection needs the type and the
engine already depends on aura-analysis; the registry/CLI reach them unchanged via
aura-engine's re-export). This finishes getting the trading types out of the
engine. RunManifest / RunReport / summarize stay in the engine this cycle
(trace-coupled; their genericization is the deferred World-layer work, not #136).

Entry path: compiler-driven (behaviour-preserving relocation), executed via a
plain implementer rather than the compiler-driven-edit workflow (which no-ops on
crate-relocation — Skills #11). Verdict: clean build + suite green unchanged
(the family_selection_* tests + the C18 goldens are the oracle).

## `/boss` decision (cycle 2) — registry stays domain-aware; #136 remainder is the FamilySelection/SelectionMode relocation only **The fork — and who decided it.** The deferred #136 tail was framed as "make RunReport generic over a metric type M and relocate the aura-registry Metric/metric_cmp vocabulary". Inspecting the code showed the registry's domain coupling is **not** in the vocabulary but in the **algorithms**: `metric_value` (lib.rs:137) reaches into `RunMetrics`/`RMetrics` fields, and `optimize_deflated` (lib.rs:386) **branches on `is_r_metric`** — R metrics get a moving-block bootstrap, `total_pips` a closed-form dispersion floor. So "move the vocabulary to aura-analysis, registry depends on it" would **not** decouple the registry; it would stay thoroughly trading-aware. This reframed the question to an architecture-intent fork the sources do not settle: *should the orchestration/World layer (aura-registry) be a reusable, domain-agnostic substrate (symmetric with the engine), or is it legitimately trading-aware?* Even full genericity would not erase the domain knowledge — the R-vs-pips deflation statistics are irreducibly domain and would have to be injected via a `trait` with exactly one implementor (the trading metrics) — the speculative-generality smell. **Decision (user-ratified in the session discussion, 2026-06-27 — provenance, not a derived fork):** the registry is **legitimately trading-aware**; the `Metric`/`metric_cmp`/`optimize_deflated` vocabulary and statistics **stay in aura-registry** (it is the trading selection layer, not the domain-free engine). Genericity is **not** forced now (a one-implementor abstraction). "The World as a domain-agnostic orchestration substrate" is deferred to the **World / C21** milestone, where it would actually pay off — not bolted onto #136. **This cycle's scope (the genuine #136 remainder, small & behaviour-preserving):** relocate the orchestration-provenance types `FamilySelection` / `SelectionMode` out of the engine's `report.rs` into `aura-analysis` (the only non-cyclic home that gets them out of the engine — `RunManifest.selection` needs the type and the engine already depends on aura-analysis; the registry/CLI reach them unchanged via aura-engine's re-export). This finishes getting the trading types out of the engine. `RunManifest` / `RunReport` / `summarize` stay in the engine this cycle (trace-coupled; their genericization is the deferred World-layer work, not #136). Entry path: compiler-driven (behaviour-preserving relocation), executed via a plain implementer rather than the compiler-driven-edit workflow (which no-ops on crate-relocation — Skills #11). Verdict: clean build + suite green unchanged (the family_selection_* tests + the C18 goldens are the oracle).
Author
Owner

Closing — engine-side extraction complete (cycles 0079 + 0080)

The trading-analysis layer is out of aura-engine. Across two behaviour-preserving cycles (full suite 665/0 throughout, incl. the C18 goldens; clippy clean; aura-engine/src/lib.rs and all downstream crates byte-unchanged via re-export):

  • 0079 (94c88eb + audit a8f67c7): new aura-analysis crate (deps aura-core + serde only; serde_json test-only). Moved: RunMetrics, RMetrics, summarize_r, r_metrics_from_rs, derive_position_events, PositionEvent / PositionAction, and the deflation hurdle math inv_norm_cdf / expected_max_of_normals.
  • 0080 (04c581a + audit 226bf57): moved the last trading types still defined in report — the selection-provenance FamilySelection / SelectionMode.

Adjudicated (not done — decided), so this issue closes:

  • The aura-registry Metric / metric_cmp / deflation vocabulary stays in the registry by design — it is the trading selection layer, not the domain-free engine, and optimize_deflated's statistics branch irreducibly on metric identity (R-bootstrap vs. total_pips dispersion floor). Forcing genericity would be a one-implementor abstraction. (User-ratified, 2026-06-27.)
  • RunReport / RunManifest / summarize stay trace-coupled in the engine.

Making the orchestration/World layer a reusable domain-agnostic substrate (RunReport<M> + the registry vocabulary behind an injected score/null-model abstraction) is reframed to the World / C21 layer, where it would actually pay off — tracked as #147, not #136.

Ledger: C16 cycle-0079 / 0080 realization notes carry the rationale and the current crate topology.

## Closing — engine-side extraction complete (cycles 0079 + 0080) The trading-analysis layer is out of `aura-engine`. Across two behaviour-preserving cycles (full suite 665/0 throughout, incl. the C18 goldens; clippy clean; `aura-engine/src/lib.rs` and all downstream crates byte-unchanged via re-export): - **0079** (`94c88eb` + audit `a8f67c7`): new `aura-analysis` crate (deps `aura-core` + serde only; `serde_json` test-only). Moved: `RunMetrics`, `RMetrics`, `summarize_r`, `r_metrics_from_rs`, `derive_position_events`, `PositionEvent` / `PositionAction`, and the deflation hurdle math `inv_norm_cdf` / `expected_max_of_normals`. - **0080** (`04c581a` + audit `226bf57`): moved the last trading types still *defined* in `report` — the selection-provenance `FamilySelection` / `SelectionMode`. **Adjudicated (not done — decided), so this issue closes:** - The `aura-registry` `Metric` / `metric_cmp` / deflation vocabulary **stays in the registry by design** — it is the trading *selection* layer, not the domain-free engine, and `optimize_deflated`'s statistics branch irreducibly on metric identity (R-bootstrap vs. `total_pips` dispersion floor). Forcing genericity would be a one-implementor abstraction. (User-ratified, 2026-06-27.) - `RunReport` / `RunManifest` / `summarize` stay trace-coupled in the engine. Making the orchestration/World layer a reusable **domain-agnostic substrate** (`RunReport<M>` + the registry vocabulary behind an injected score/null-model abstraction) is reframed to the **World / C21** layer, where it would actually pay off — tracked as #147, not #136. Ledger: C16 cycle-0079 / 0080 realization notes carry the rationale and the current crate topology.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#136