plan: 0069 exposure->bias rename tail (#126)

The deferred rename of the strategy-output residue (the typed exposure_sign_flips
metric with serde back-compat, the registry rank vocab, the exposure_scale manifest
label, the .named("exposure") Bias instances), keeping SimBroker's pre-reframe
exposure concept and the on-disk exposure tap label (scope fork on #117).

refs #126 #117
This commit is contained in:
2026-06-24 12:45:22 +02:00
parent 4e6b1d9b53
commit 34e8bbc803
+124
View File
@@ -0,0 +1,124 @@
# exposure→bias rename tail (cycle 0065, #126) — Implementation Plan
> **Parent spec:** `docs/specs/0065-stage1-r-signal-quality.md` (secondary change (a),
> deferred from iter-0; scope fork decided on #117).
>
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run this
> plan. Steps use `- [ ]` checkboxes.
**Goal:** complete the `exposure → bias` rename of the strategy-output residue — the
typed `exposure_sign_flips` metric (with serde back-compat), the registry rank vocab,
the `exposure_scale` manifest label, and the `.named("exposure")` Bias instances —
**without** touching SimBroker's deliberate pre-reframe exposure concept or the on-disk
`exposure` tap label (fork on #117).
**Architecture:** `RunMetrics.exposure_sign_flips` is a serde struct field, so its
rename is one atomic compile-unit across all crates; a `#[serde(alias =
"exposure_sign_flips")]` keeps legacy `runs.jsonl` readable while new output uses
`bias_sign_flips`. The registry rank metric accepts BOTH strings (CLI back-compat).
The manifest param label and Bias instance names are plain string renames.
**Test split rule (load-bearing — apply everywhere):** a test that pins **new output**
(serialize / byte-pin / a freshly-built `RunMetrics`) updates to `bias_sign_flips` /
`bias_scale`; a test that reads a **legacy** line (deserialize back-compat) KEEPS the
old `exposure_sign_flips` / `exposure_scale` key (it now pins the serde alias).
**Files this plan modifies** (site map from grep — the compiler enumerates the field-rename sites):
- Modify: `crates/aura-engine/src/report.rs``RunMetrics.exposure_sign_flips` field (+ alias), the `summarize` fold, the constructor, tests.
- Modify: `crates/aura-engine/src/mc.rs:40,88,211` — the parallel `exposure_sign_flips` aggregate field.
- Modify: `crates/aura-registry/src/lib.rs``Metric::ExposureSignFlips` vocab, `metric_cmp`, the rank-string match (+both), the error message, constructor, tests.
- Modify: `crates/aura-cli/src/main.rs` — the `:1462` JSON output key, the `exposure_scale` manifest builders (`:557,594,1439,1675,1825,2400`), the `.named("exposure")` instances (`:950,1042,1601,1738`), tests (`:2473,2560,2728`).
- Modify: `crates/aura-cli/tests/cli_run.rs:39` — the byte-pin.
- Modify: `crates/aura-engine/src/test_fixtures.rs:65`, `crates/aura-engine/src/blueprint.rs:2566``.named("exposure")`.
- Modify: `crates/aura-engine/tests/risk_executor.rs:142`, `crates/aura-ingest/tests/{real_bars,streaming_seam}.rs`, `crates/aura-ingest/examples/ger40_breakout_{compare,real,sweep}.rs` — field/label references.
---
## Task 1: Rename `exposure_sign_flips → bias_sign_flips` (typed field + serde alias + rank vocab)
**Files:** `report.rs`, `mc.rs`, `aura-registry/src/lib.rs`, `main.rs`, `cli_run.rs`, `risk_executor.rs`, the three ger40 examples — every `exposure_sign_flips` site (the compiler flags them once the field is renamed).
- [ ] **Step 1: Write the RED back-compat test** (in `crates/aura-engine/src/report.rs` tests)
```rust
#[test]
fn legacy_exposure_sign_flips_key_reads_via_alias_and_new_output_uses_bias() {
// a legacy runs.jsonl metrics line carries the OLD key — the serde alias must accept it.
let legacy = r#"{"total_pips":12.0,"max_drawdown":1.0,"exposure_sign_flips":3}"#;
let m: RunMetrics = serde_json::from_str(legacy).expect("legacy exposure_sign_flips deserialises");
assert_eq!(m.bias_sign_flips, 3);
// new output serialises the NEW key, and the old key is gone from output.
let json = serde_json::to_string(&m).unwrap();
assert!(json.contains("\"bias_sign_flips\":3"), "new output uses bias_sign_flips: {json}");
assert!(!json.contains("exposure_sign_flips"), "old key absent from new output: {json}");
}
```
- [ ] **Step 2: Run it to confirm RED**
Run: `cargo test -p aura-engine legacy_exposure_sign_flips_key_reads_via_alias`
Expected: FAIL — does not compile (`m.bias_sign_flips` does not exist yet).
- [ ] **Step 3: Rename the field + add the serde alias** (`crates/aura-engine/src/report.rs`)
In the `RunMetrics` struct (`:29`):
```rust
#[serde(alias = "exposure_sign_flips")]
pub bias_sign_flips: u64,
```
Rename the fold local (`:310,317`) `exposure_sign_flips``bias_sign_flips` and the constructor (`:322`) `RunMetrics { total_pips, max_drawdown, bias_sign_flips, r: None }`. Update the in-file tests per the **test split rule**: assertions on a freshly-summarised `m` (`:818,842,849`) and serialize round-trips (`:888,913,933,947,979`) → `bias_sign_flips`; the **legacy-deserialise** test (`:971`) KEEPS `exposure_sign_flips` in its JSON literal (it now pins the alias) and updates only its assertion field to `m.bias_sign_flips`.
- [ ] **Step 4: Propagate the field rename across the workspace**
Rename `exposure_sign_flips``bias_sign_flips` at every other site the compiler flags:
- `crates/aura-engine/src/mc.rs:40,88,211` — the aggregate field + its `pick`/constructor uses (if the MC aggregate serialises to `families.jsonl`, add the same `#[serde(alias = "exposure_sign_flips")]` on its field).
- `crates/aura-cli/src/main.rs:1462` (`"bias_sign_flips": agg.bias_sign_flips`), `:2560` (field ref); the byte-pin tests `:2473` (`"bias_sign_flips":`), `:2728` (assertion) → new key.
- `crates/aura-cli/tests/cli_run.rs:39``line.ends_with("\"bias_sign_flips\":1}}")` (new output).
- `crates/aura-engine/tests/risk_executor.rs:142`, the ger40 examples (`compare:130,154`, `real:89`, `sweep:149`) → field refs.
- [ ] **Step 5: Registry rank vocab — rename the variant, accept BOTH strings** (`crates/aura-registry/src/lib.rs`)
```rust
// the Metric enum variant (:103):
BiasSignFlips,
// rank-string match (:120-122) — accept the new name AND the old one (CLI back-compat):
"bias_sign_flips" | "exposure_sign_flips" => Metric::BiasSignFlips,
// metric_cmp arm (:130-131):
Metric::BiasSignFlips => a.metrics.bias_sign_flips.cmp(&b.metrics.bias_sign_flips),
// error/listing (:185): "(known: total_pips, max_drawdown, bias_sign_flips)"
// constructor (:214): bias_sign_flips: flips
```
Per the test split: the legacy-deserialise test (`:275`) KEEPS `exposure_sign_flips` in its JSON literal; the `rank_by` tests (`:296,297`) update the assertion field to `bias_sign_flips` (rank string may use either — keep `"exposure_sign_flips"` at `:296` to exercise the alias, or add a sibling asserting `"bias_sign_flips"` ranks identically).
- [ ] **Step 6: Build + the RED test now GREEN + suite green**
Run: `cargo test -p aura-engine legacy_exposure_sign_flips_key_reads_via_alias`
Expected: PASS.
Run: `cargo test --workspace`
Expected: all green (the renamed field + alias; legacy-read tests pin the alias; new-output tests pin `bias_sign_flips`).
---
## Task 2: Rename the `exposure_scale` manifest label + `.named("exposure")` Bias instances
**Files:** `main.rs` (manifest builders + Bias instances), `report.rs` (manifest-builder tests), `test_fixtures.rs`, `blueprint.rs`, `real_bars.rs`, `streaming_seam.rs`.
- [ ] **Step 1: Rename the `.named("exposure")` Bias instances → `.named("bias")`**
At `crates/aura-cli/src/main.rs:950,1042,1601,1738`, `crates/aura-engine/src/test_fixtures.rs:65`, `crates/aura-engine/src/blueprint.rs:2566`: `Bias::builder().named("exposure")``Bias::builder().named("bias")`. (Pure debug symbols — no behaviour change. If any test asserts the node label/name `"exposure"`, update that assertion to `"bias"`; if a graph-render fixture pins the name, update it.)
- [ ] **Step 2: Rename the `exposure_scale` manifest param label → `bias_scale`**
In every manifest **builder** (new output): `crates/aura-cli/src/main.rs:557,594,1439,1675,1825,2400`, `crates/aura-engine/src/report.rs:637,879,907,927`, `crates/aura-ingest/tests/real_bars.rs:87`, `crates/aura-ingest/tests/streaming_seam.rs:87``("bias_scale".to_string(), Scalar::f64(...))`. Per the test split: the **legacy-deserialise** JSON literal at `report.rs:894` KEEPS `exposure_scale` (it reads an old manifest; manifest params are an untyped `Vec<(String, Scalar)>`, so no serde alias is needed — old data just carries the old label).
- [ ] **Step 3: Build + suite green + lint**
Run: `cargo build --workspace`
Expected: clean.
Run: `cargo test --workspace`
Expected: all green.
Run: `cargo clippy --workspace --all-targets -- -D warnings`
Expected: clean.