From 80ba74460d4f19be3c869b4830d43786171475ac Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 25 Jun 2026 15:55:30 +0200 Subject: [PATCH] plan: 0073 instrument-geometry cross-check + deploy fields Three tasks at the aura-ingest edge: (1) re-export InstrumentGeometry + a thin instrument_geometry accessor over symbol_meta; (2) two additive InstrumentSpec fields tick_size/digits (compile-gated literal update); (3) a real-data cross-check of the authored floor vs provider geometry, iterating the vetted set. refs #143 --- .../0073-instrument-geometry-crosscheck.md | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 docs/plans/0073-instrument-geometry-crosscheck.md diff --git a/docs/plans/0073-instrument-geometry-crosscheck.md b/docs/plans/0073-instrument-geometry-crosscheck.md new file mode 100644 index 0000000..a8db7c0 --- /dev/null +++ b/docs/plans/0073-instrument-geometry-crosscheck.md @@ -0,0 +1,309 @@ +# Instrument-geometry cross-check + deploy fields — Implementation Plan + +> **Parent spec:** `docs/specs/0073-instrument-geometry-crosscheck.md` +> +> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run +> this plan. Steps use `- [ ]` checkboxes for tracking. + +**Goal:** Consume data-server's neutral `InstrumentGeometry` at the aura-ingest edge, add the two deferred deploy-grade `InstrumentSpec` fields (`tick_size`, `digits`), and cross-check the hand-authored vetted floor against provider truth. + +**Architecture:** All work is in `crates/aura-ingest`. A thin re-export + accessor surface the tier-2 geometry source (the resolver stays #124); two `Copy` scalar fields are appended to `InstrumentSpec` (additive, C15) with values authored from the verified sidecars; a new real-data integration test cross-checks the authored floor against provider geometry, iterating the vetted set (so #140 is covered for free) and skipping where the archive is absent. + +**Tech Stack:** Rust; `crates/aura-ingest/src/lib.rs`; the `data-server` git dependency (`symbol_meta` → `meta::InstrumentGeometry`, lock already at `694f96f2`, commit `d5e9c27`). + +--- + +**Files this plan creates or modifies:** + +- Modify: `crates/aura-ingest/src/lib.rs` — re-export (`:30`), accessor (near `default_data_server` `:338`), `InstrumentSpec` struct (`:347`, fields `:348-364`), `instrument_spec()` table literals (`:376/:380/:384/:388/:392`), unit-test literals (`:410/:420/:433/:445/:457`), one new hermetic unit test in `mod tests`. +- Modify: `crates/aura-ingest/tests/instrument_spec_deploy_metadata.rs:27-33` — two sanity asserts for the new fields. +- Create: `crates/aura-ingest/tests/instrument_geometry_crosscheck.rs` — the real-data cross-check. + +> **Not a task:** the `Cargo.lock` bump to `data-server 694f96f2` is already committed (`d5e9c27`); nothing to do for it this cycle. + +> **Dependency order (sequential, no skip-ahead):** Task 1 (accessor) → Task 2 (fields) → Task 3 (cross-check uses both). Every intermediate state compiles. + +--- + +### Task 1: Geometry-access seam (re-export + accessor) + +**Files:** +- Modify: `crates/aura-ingest/src/lib.rs` (`:30` re-export, near `:340` accessor, `mod tests` new unit test) + +- [ ] **Step 1: Write the failing test** + +In `crates/aura-ingest/src/lib.rs`, inside `#[cfg(test)] mod tests` (which has `use super::*;`), add: + +```rust + #[test] + fn instrument_geometry_none_for_unknown_symbol() { + // A symbol with no sidecar yields None — the accessor surfaces the reader's + // absent/untrusted contract at the aura-ingest edge. Hermetic: a bogus symbol + // has no .meta.json whether or not the local archive is present, and + // `default_data_server()` constructs safely even when the path is absent + // (see tests/open_ohlc_absent_archive.rs). + let server = default_data_server(); + assert!(instrument_geometry(&server, "NOT_A_REAL_SYMBOL").is_none()); + } +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test -p aura-ingest instrument_geometry_none_for_unknown_symbol` +Expected: FAIL — compile error `cannot find function 'instrument_geometry' in this scope`. + +- [ ] **Step 3: Write minimal implementation** + +In `crates/aura-ingest/src/lib.rs`, immediately after the existing re-export at `:30` (`pub use data_server::{DataServer, DEFAULT_DATA_PATH};`), add: + +```rust +/// Re-export of data-server's neutral instrument geometry (#143): a consumer reads +/// provider geometry through `aura-ingest` alone, never naming `data_server`. +pub use data_server::meta::InstrumentGeometry; +``` + +And near `default_data_server` (after its closing brace at `:340`), add the accessor: + +```rust +/// Load the provider-recorded neutral geometry for `symbol` — the recorded +/// dataset-metadata tier (tier 2) of #124's resolution hierarchy — or `None` when +/// the dataset carries no usable sidecar (missing / unparseable / unsupported +/// version / a non-finite field; the reader absorbs every provider quirk). +/// +/// Surfaces the tier-2 *source*; the tier-composing, authored-override-wins +/// *resolver* is #124. The geometry is an **untrusted input** the authored override +/// supersedes — never a silent runtime fallback for an unvetted symbol +/// (refuse-don't-guess stands: [`instrument_spec`] still returns `None`). It is +/// non-scalar reference metadata held beside the hot path (C15); it is **not** a +/// [`Source`](aura_engine::Source) and never enters the engine's +/// `(Timestamp, Scalar)` seam. +pub fn instrument_geometry(server: &Arc, symbol: &str) -> Option { + server.symbol_meta(symbol) +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cargo test -p aura-ingest instrument_geometry_none_for_unknown_symbol` +Expected: PASS (`1 passed`). + +--- + +### Task 2: Two additive `InstrumentSpec` fields (`tick_size`, `digits`) + +**Files:** +- Modify: `crates/aura-ingest/tests/instrument_spec_deploy_metadata.rs:27-33` +- Modify: `crates/aura-ingest/src/lib.rs` (struct `:348-364`; table literals `:376/:380/:384/:388/:392`; unit-test literals `:410/:420/:433/:445/:457`) + +- [ ] **Step 1: Write the failing test** + +In `crates/aura-ingest/tests/instrument_spec_deploy_metadata.rs`, in `every_vetted_symbol_resolves_to_a_complete_spec`, immediately after the existing `quote_currency` assert (`:33`), add: + +```rust + assert!(s.tick_size > 0.0, "{sym}: tick_size must be positive"); + assert!(s.digits > 0, "{sym}: digits must be positive"); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test -p aura-ingest every_vetted_symbol_resolves_to_a_complete_spec` +Expected: FAIL — compile error `no field 'tick_size' on type 'InstrumentSpec'`. + +- [ ] **Step 3: Write minimal implementation** + +(a) In `crates/aura-ingest/src/lib.rs`, append two fields to `InstrumentSpec` immediately before its closing brace (`:365`, after `quote_currency`): + +```rust + /// Smallest representable price increment (provider geometry; deploy-grade + /// price rounding). 1e-5 for a 5-decimal FX major, 0.1 for the indices. + pub tick_size: f64, + /// Decimal places in a quoted price (provider geometry; deploy-grade price + /// rounding). 5 for FX majors, 1 for the indices. + pub digits: u32, +``` + +(b) Update the 5 `instrument_spec()` table literals (the compiler forces every one). After-form of each: + +```rust + "GER40" => InstrumentSpec { + instrument_id: 1, pip_size: 1.0, contract_size: 1.0, + pip_value_per_lot: 1.0, min_lot: 0.1, lot_step: 0.01, quote_currency: "EUR", + tick_size: 0.1, digits: 1, + }, + "FRA40" => InstrumentSpec { + instrument_id: 2, pip_size: 1.0, contract_size: 1.0, + pip_value_per_lot: 1.0, min_lot: 0.1, lot_step: 0.01, quote_currency: "EUR", + tick_size: 0.1, digits: 1, + }, + "EURUSD" => InstrumentSpec { + instrument_id: 3, pip_size: 0.0001, contract_size: 100_000.0, + pip_value_per_lot: 10.0, min_lot: 0.01, lot_step: 0.01, quote_currency: "USD", + tick_size: 1e-5, digits: 5, + }, + "GBPUSD" => InstrumentSpec { + instrument_id: 4, pip_size: 0.0001, contract_size: 100_000.0, + pip_value_per_lot: 10.0, min_lot: 0.01, lot_step: 0.01, quote_currency: "USD", + tick_size: 1e-5, digits: 5, + }, + "USDCAD" => InstrumentSpec { + instrument_id: 5, pip_size: 0.0001, contract_size: 100_000.0, + pip_value_per_lot: 10.0, min_lot: 0.01, lot_step: 0.01, quote_currency: "CAD", + tick_size: 1e-5, digits: 5, + }, +``` + +(c) Update the 5 unit-test literals in `instrument_spec_lookup_by_symbol` (`mod tests`). Each is a field-per-line `Some(InstrumentSpec { .. })`; insert the two new fields immediately before the closing `})`. After-form of each: + +```rust + // GER40 (:410-418 -> +2 lines) + Some(InstrumentSpec { + instrument_id: 1, + pip_size: 1.0, + contract_size: 1.0, + pip_value_per_lot: 1.0, + min_lot: 0.1, + lot_step: 0.01, + quote_currency: "EUR", + tick_size: 0.1, + digits: 1, + }) + // FRA40 + Some(InstrumentSpec { + instrument_id: 2, + pip_size: 1.0, + contract_size: 1.0, + pip_value_per_lot: 1.0, + min_lot: 0.1, + lot_step: 0.01, + quote_currency: "EUR", + tick_size: 0.1, + digits: 1, + }) + // EURUSD + Some(InstrumentSpec { + instrument_id: 3, + pip_size: 0.0001, + contract_size: 100_000.0, + pip_value_per_lot: 10.0, + min_lot: 0.01, + lot_step: 0.01, + quote_currency: "USD", + tick_size: 1e-5, + digits: 5, + }) + // GBPUSD + Some(InstrumentSpec { + instrument_id: 4, + pip_size: 0.0001, + contract_size: 100_000.0, + pip_value_per_lot: 10.0, + min_lot: 0.01, + lot_step: 0.01, + quote_currency: "USD", + tick_size: 1e-5, + digits: 5, + }) + // USDCAD + Some(InstrumentSpec { + instrument_id: 5, + pip_size: 0.0001, + contract_size: 100_000.0, + pip_value_per_lot: 10.0, + min_lot: 0.01, + lot_step: 0.01, + quote_currency: "CAD", + tick_size: 1e-5, + digits: 5, + }) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cargo test -p aura-ingest every_vetted_symbol_resolves_to_a_complete_spec` +Expected: PASS (`1 passed`). The lib unit tests (`instrument_spec_lookup_by_symbol` etc.) also compile and pass. + +--- + +### Task 3: Provider-geometry cross-check (real-data, skip-if-absent) + +**Files:** +- Create: `crates/aura-ingest/tests/instrument_geometry_crosscheck.rs` + +- [ ] **Step 1: Write the test** + +Create `crates/aura-ingest/tests/instrument_geometry_crosscheck.rs`: + +```rust +//! Cross-check the hand-authored vetted InstrumentSpec floor (#113) against the +//! provider-recorded geometry sidecars (#143 — tier 2 of #124's resolution +//! hierarchy). Skips with a note where the local Pepperstone archive is absent, +//! so it is hermetic elsewhere and exercises broker truth where the files exist. + +use aura_ingest::{default_data_server, instrument_geometry, instrument_spec, DEFAULT_DATA_PATH}; + +/// The vetted symbols. Kept in lock-step with the authored table; #140 extends it. +const VETTED: [&str; 5] = ["GER40", "FRA40", "EURUSD", "GBPUSD", "USDCAD"]; + +#[test] +fn authored_floor_agrees_with_provider_geometry() { + let server = default_data_server(); + // Gate on a sidecar's presence (symbol_meta reads the file directly, not the + // bar index) — the same skip-if-absent discipline as the other real-data tests. + if instrument_geometry(&server, "EURUSD").is_none() { + eprintln!("skip: no local geometry sidecars at {DEFAULT_DATA_PATH} (EURUSD.meta.json absent)"); + return; + } + for sym in VETTED { + let spec = instrument_spec(sym).expect("vetted symbol"); + let geo = instrument_geometry(&server, sym) + .unwrap_or_else(|| panic!("vetted symbol {sym} must carry a geometry sidecar")); + + assert!( + (spec.pip_size - geo.pip_size).abs() < 1e-12, + "{sym}: authored pip_size {} != provider {}", spec.pip_size, geo.pip_size + ); + assert!( + (spec.contract_size - geo.lot_size).abs() < 1e-6, + "{sym}: authored contract_size {} != provider lot_size {}", spec.contract_size, geo.lot_size + ); + // the C10 pip-value invariant, now against broker truth + let provider_pip_value = geo.lot_size * geo.pip_size; + assert!( + (spec.pip_value_per_lot - provider_pip_value).abs() < 1e-9, + "{sym}: authored pip_value_per_lot {} != provider lot_size*pip_size {}", + spec.pip_value_per_lot, provider_pip_value + ); + assert!( + (spec.tick_size - geo.tick_size).abs() < 1e-12, + "{sym}: authored tick_size {} != provider {}", spec.tick_size, geo.tick_size + ); + assert_eq!( + spec.digits, geo.digits, + "{sym}: authored digits {} != provider {}", spec.digits, geo.digits + ); + assert_eq!( + spec.quote_currency, geo.quote.as_str(), + "{sym}: authored quote_currency {} != provider {}", spec.quote_currency, geo.quote + ); + } +} +``` + +- [ ] **Step 2: Run the test** + +Run: `cargo test -p aura-ingest authored_floor_agrees_with_provider_geometry` +Expected: PASS (`1 passed`). This is a *verification* test: it is green on the first correct run because the authored fields were taken from the same sidecars; its value is failing on a FUTURE divergence (a table typo, or a #140 symbol whose hand-registered values disagree with the provider). The archive IS present on this host (`/mnt/tickdata/Pepperstone`, 30 sidecars), so the test runs rather than skips — confirm the output is `1 passed`, NOT a skip line. + +--- + +### Final gates (run once, after all tasks) + +- [ ] **Workspace test suite** + +Run: `cargo test --workspace` +Expected: all suites pass; no pre-existing golden metric changes (this cycle touches no engine/CLI hot path). + +- [ ] **Lint** + +Run: `cargo clippy --workspace --all-targets -- -D warnings` +Expected: clean (no warnings).