From 1718ba0f98f7c19f1486adaa6f420a512648ca81 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 14 Jun 2026 00:11:18 +0200 Subject: [PATCH] plan: 0038 dedup-sma-cross-test-fixtures --- .../0038-dedup-sma-cross-test-fixtures.md | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/plans/0038-dedup-sma-cross-test-fixtures.md diff --git a/docs/plans/0038-dedup-sma-cross-test-fixtures.md b/docs/plans/0038-dedup-sma-cross-test-fixtures.md new file mode 100644 index 0000000..15527f4 --- /dev/null +++ b/docs/plans/0038-dedup-sma-cross-test-fixtures.md @@ -0,0 +1,298 @@ +# Dedup the SMA-cross harness test fixtures — Implementation Plan + +> **Parent spec:** `docs/specs/0038-dedup-sma-cross-test-fixtures.md` +> +> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run +> this plan. Steps use `- [ ]` checkboxes for tracking. + +**Goal:** Collapse the three verbatim-duplicated SMA-cross harness `#[cfg(test)]` +fixtures (`synthetic_prices`, `sma_cross`, `composite_sma_cross_harness`) in +`aura-engine`'s `blueprint.rs` and `sweep.rs` test modules into one shared +crate-root `#[cfg(test)] mod test_fixtures`. + +**Architecture:** A new test-only module `crates/aura-engine/src/test_fixtures.rs` +holds the three fixtures as `pub(crate)` free fns; it is declared in `lib.rs` +with `#[cfg(test)] mod test_fixtures;` (sibling to the existing +`#[cfg(test)] mod reexport_tests`). The two consuming test modules delete their +local copies and import from `crate::test_fixtures`. Behaviour-preserving, +`#[cfg(test)]`-only — no production code, no public API, no runtime path changes. + +**Tech Stack:** Rust, `aura-engine` crate; verified against `cargo test` and +`cargo clippy -D warnings`. + +--- + +## Files this plan creates or modifies + +- Create: `crates/aura-engine/src/test_fixtures.rs` — the shared `#[cfg(test)]` + fixture module (three `pub(crate)` fns). +- Modify: `crates/aura-engine/src/lib.rs:53-54` — add the module declaration. +- Modify: `crates/aura-engine/src/blueprint.rs` — delete three local fixtures + (`:1518-1533`, `:1590-1610`, `:1612-1645`), add one import line to the test + module's `use` block (`:755-758`). No existing import becomes unused. +- Modify: `crates/aura-engine/src/sweep.rs` — delete three local fixtures incl. + two stale doc-comments (`:256-269`, `:271-293`, `:295-328`), and rewrite the + test module's `use` block (`:171-177`) to import the fixtures and drop the + now-unused imports. + +**Out of scope — must NOT be touched:** `blueprint.rs` `sma_cross_under_root` +(`:1114`), `hand_wired_sma_cross_harness` (`:1538`); `sweep.rs` `run_point` +(`:335`), `sma_cross_grid` (`:355`); `graph_model.rs` `sma_cross` (`:311`) / +`sample_root` (`:340`) — an independent minimal golden copy. + +--- + +## Task 1: Move the three fixtures into a shared `test_fixtures` module + +This is one atomic move: the new module and both consumers must change together +for the crate to build and lint clean. Intermediate steps are intentionally NOT +gated; the build / test / clippy gates run only at the end (Steps 5-7), after +every consumer is threaded — so no "unused fn / unused import" lint can fire on +a half-applied move. + +**Files:** +- Create: `crates/aura-engine/src/test_fixtures.rs` +- Modify: `crates/aura-engine/src/lib.rs:53-54` +- Modify: `crates/aura-engine/src/blueprint.rs` (`:755-758`, `:1518-1533`, `:1590-1610`, `:1612-1645`) +- Modify: `crates/aura-engine/src/sweep.rs` (`:171-177`, `:256-269`, `:271-293`, `:295-328`) + +- [ ] **Step 1: Create the shared fixture module** + +Create `crates/aura-engine/src/test_fixtures.rs` with exactly this content. The +import paths use the crate-root re-exports (`crate::{...}`) — verified present at +`lib.rs:40-45`; this matches the import style `sweep.rs`'s test module already +uses. (`Edge` and `Target` are re-exported from `crate::harness`, not +`crate::blueprint` — the crate-root path sidesteps that.) + +```rust +//! Shared `#[cfg(test)]` fixtures for the SMA-cross signal-quality harness, +//! consumed by the `blueprint` and `sweep` unit-test modules. Kept in one place +//! so the harness topology cannot silently diverge between them (see #53). +//! Gated test-only at the declaration site in `lib.rs` +//! (`#[cfg(test)] mod test_fixtures;`), matching the `reexport_tests` precedent. + +use crate::{BlueprintNode, Composite, Edge, OutField, Role, Target}; +use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; +use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; +use std::sync::mpsc; + +/// Seven synthetic F64 ticks driving the harness; deterministic input fixture. +pub(crate) fn synthetic_prices() -> Vec<(Timestamp, Scalar)> { + [ + (1_i64, 1.0000_f64), + (2, 1.0010), + (3, 1.0030), + (4, 1.0060), + (5, 1.0040), + (6, 1.0010), + (7, 0.9990), + ] + .iter() + .map(|&(t, p)| (Timestamp(t), Scalar::F64(p))) + .collect() +} + +/// The SMA-cross signal as a reusable value-empty composite: one input role +/// (price), one output (fast-minus-slow spread). Lengths injected at compile. +pub(crate) fn sma_cross() -> Composite { + Composite::new( + "sma_cross", + vec![ + Sma::builder().named("fast").into(), + Sma::builder().named("slow").into(), + Sub::builder().into(), + ], + vec![ + Edge { from: 0, to: 2, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 1, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], + source: None, + }], + vec![OutField { node: 2, field: 0, name: "out".into() }], + ) +} + +/// The signal-quality harness as a value-empty composite blueprint with two +/// recording sinks (equity, exposure). +#[allow(clippy::type_complexity)] +pub(crate) fn composite_sma_cross_harness() -> ( + Composite, + mpsc::Receiver<(Timestamp, Vec)>, + mpsc::Receiver<(Timestamp, Vec)>, +) { + let (tx_eq, rx_eq) = mpsc::channel(); + let (tx_ex, rx_ex) = mpsc::channel(); + let bp = Composite::new( + "root", + vec![ + BlueprintNode::Composite(sma_cross()), + Exposure::builder().into(), + SimBroker::builder(0.0001).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(), + ], + vec![ + Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // composite out -> Exposure + Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // exposure -> broker slot 0 + Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // equity -> sink + Edge { from: 1, to: 4, slot: 0, from_field: 0 }, // exposure -> sink + ], + vec![Role { + name: "src".into(), + targets: vec![ + Target { node: 0, slot: 0 }, // price -> sma_cross role 0 + Target { node: 2, slot: 1 }, // price -> SimBroker price slot + ], + source: Some(ScalarKind::F64), + }], + vec![], // output: the root ends in sinks + ); + (bp, rx_eq, rx_ex) +} +``` + +- [ ] **Step 2: Declare the module in `lib.rs`** + +In `crates/aura-engine/src/lib.rs`, the existing test-only module declaration is: + +```rust +#[cfg(test)] +mod reexport_tests { +``` + +Immediately **before** that `#[cfg(test)]` line (at `:53`), add the new module +declaration so it sits as a sibling: + +```rust +#[cfg(test)] +mod test_fixtures; + +``` + +(Result: a `#[cfg(test)] mod test_fixtures;` line, a blank line, then the +existing `#[cfg(test)] mod reexport_tests {` block — both gated, both crate-root.) + +- [ ] **Step 3: Switch `blueprint.rs` to the shared fixtures** + +In `crates/aura-engine/src/blueprint.rs`, inside `#[cfg(test)] mod tests`: + +(a) **Delete** the three local fixture definitions entirely (including their +doc-comments and the `#[allow(clippy::type_complexity)]` on the harness): +- `synthetic_prices` — `:1518-1533` +- `sma_cross` — `:1590-1610` +- `composite_sma_cross_harness` — `:1612-1645` + +(b) **Add** this import line to the test module's `use` block (currently +`:755-758`, which is `use super::*;` plus the `aura_core` / `aura_std` / `mpsc` +lines). Insert it directly after `use super::*;`: + +```rust + use crate::test_fixtures::{composite_sma_cross_harness, sma_cross, synthetic_prices}; +``` + +Do **not** remove any other import: every existing test-mod import +(`Exposure`, `Recorder`, `SimBroker`, `Firing`, `NodeSchema`, `FieldSpec`, +`Ctx`, `Ema`, `mpsc`) still has surviving uses in other tests +(`hand_wired_sma_cross_harness`, the MACD test, the local dummy-node defs). + +- [ ] **Step 4: Switch `sweep.rs` to the shared fixtures and drop dead imports** + +In `crates/aura-engine/src/sweep.rs`, inside `#[cfg(test)] mod tests`: + +(a) **Delete** the three local fixture definitions entirely, including the two +stale "duplicated from `blueprint.rs`" doc-comments: +- `synthetic_prices` — `:256-269` +- `sma_cross` (+ doc-comment `:271-273`) — `:271-293` +- `composite_sma_cross_harness` (+ doc-comment / `#[allow]` `:295-297`) — `:295-328` + +(b) **Replace** the test module's entire `use` block (currently `:171-177`): + +```rust + use super::*; + use crate::{ + f64_field, summarize, BlueprintNode, Composite, Edge, OutField, Role, RunManifest, Target, + }; + use aura_core::{Firing, ParamSpec, Scalar, ScalarKind, Timestamp}; + use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; + use std::sync::mpsc; +``` + +with exactly this (imports the shared fixtures; drops everything now used only +by the deleted fns — `BlueprintNode`, `Composite`, `Edge`, `OutField`, `Role`, +`Target`, `Firing`, the whole `aura_std` line, and `std::sync::mpsc`; keeps +`f64_field`, `summarize`, `RunManifest`, `ParamSpec`, `Scalar`, `ScalarKind`, +`Timestamp`, which `run_point` / `sma_cross_grid` / the grid tests still use): + +```rust + use super::*; + use crate::test_fixtures::{composite_sma_cross_harness, sma_cross, synthetic_prices}; + use crate::{f64_field, summarize, RunManifest}; + use aura_core::{ParamSpec, Scalar, ScalarKind, Timestamp}; +``` + +- [ ] **Step 5: Build + run the full workspace test suite (primary gate)** + +Run: `cargo test --workspace` +Expected: PASS — builds clean, every test green, zero failures. The result count +must match the pre-change baseline (this is a behaviour-preserving move; no test +is added or removed). If the build fails on an import-path error in +`test_fixtures.rs`, the engine types are reachable from the crate root +(`crate::{...}`) per `lib.rs:40-45` — do not invent a path. + +- [ ] **Step 6: Confirm the two load-bearing fixture-consuming tests are green** + +These two named tests exercise the moved fixtures and are the spec's verification +witnesses (C1 determinism; C11/C12 sweep-equivalence). Run each as its own +single-filter invocation (one positional filter per run): + +Run: `cargo test -p aura-engine composite_sma_cross_runs_bit_identical_to_hand_wired` +Expected: PASS — `test result: ok. 1 passed` (the filter matches exactly this +real test in `blueprint.rs:1647`; a `0 passed` here means the filter resolved +nothing and is a failure of this gate). + +Run: `cargo test -p aura-engine sweep_equals_n_independent_runs` +Expected: PASS — `test result: ok. 1 passed` (filter matches the real test in +`sweep.rs:369`). + +- [ ] **Step 7: Lint gate — no unused imports left behind** + +Run: `cargo clippy --workspace --all-targets -- -D warnings` +Expected: clean (exit 0, no warnings). This is the backstop for Step 4's import +removal: an over-removal fails to compile, an under-removal fails the +`unused_imports` lint. A clean run confirms the `sweep.rs` import set is exactly +right and the new module has no dead/unused symbol. + +--- + +## Self-review + +1. **Spec coverage:** the spec's single in-scope section (the three-fixture move + + module creation + consumer rewiring + import cleanup) is fully covered by + Task 1's seven steps. ✓ +2. **Placeholder scan:** no "TBD" / "TODO" / "implement later" / "similar to + Task N" / "add appropriate error handling" present. ✓ +3. **Type consistency:** fixture names (`synthetic_prices`, `sma_cross`, + `composite_sma_cross_harness`), module name (`test_fixtures`), and import + paths (`crate::test_fixtures::{...}`, `crate::{...}`) are identical across all + steps. ✓ +4. **Step granularity:** each step is one file action or one command, 2-5 min. ✓ +5. **No commit steps:** none present; the orchestrator commits. ✓ +6. **Pin/replacement substring contiguity:** the only presence-pin is Step 6's + `1 passed` against two named tests; no verbatim replacement body must contain + a pinned substring, so no soft-wrap split risk. ✓ +7. **Compile-gate vs. deferred-caller ordering:** the move makes the crate fail + to compile/lint until BOTH consumers are switched; all consumer threading + (Steps 3-4) precedes the first build/test/clippy gate (Steps 5-7). No gate + fires on a half-applied move. ✓ +8. **Verification-command filter strings resolve:** Step 5 is the unfiltered + workspace suite (the "nothing ran" masquerade is impossible). Step 6's two + filters name real tests verified to exist (`blueprint.rs:1647`, + `sweep.rs:369`) and assert `1 passed`, so a non-matching filter would surface + as `0 passed` and fail the gate. ✓ +9. **Parse-the-bytes-you-inline gate:** the project declares no spec-validation + parsers (CLAUDE.md project facts) → documented no-op. Additionally the only + inlined bodies are Rust (the project's source language), which is the + `implement` compile gate's target, not this gate's. ✓