diff --git a/docs/plans/0069-bias-sign-flips-rename.md b/docs/plans/0069-bias-sign-flips-rename.md new file mode 100644 index 0000000..bd7d505 --- /dev/null +++ b/docs/plans/0069-bias-sign-flips-rename.md @@ -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.