{"record":{"id":"7f68d4351678936c","repo":"nautechsystems/nautilus_trader","slug":"instrument-metadata-id-does-not-match-instrume","errorCode":null,"errorMessage":"instrument metadata ID {} does not match {instrument_id}","messagePattern":"instrument metadata ID (.+?) does not match (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/binance/src/futures/websocket/streams/recovery.rs","lineNumber":444,"sourceCode":"    if !open_ok && !algo_ok {\n        anyhow::bail!(\"recovery reconcile failed: both REST queries returned errors\");\n    }\n\n    log::info!(\"Recovery reconcile emitted {emitted} OrderStatusReport(s)\");\n    Ok(())\n}\n\nfn resolve_precision(\n    instruments: &DashMap<ustr::Ustr, crate::futures::http::client::BinanceFuturesInstrument>,\n    symbol_ustr: &ustr::Ustr,\n    product_type: BinanceProductType,\n) -> anyhow::Result<(InstrumentId, u8, u8)> {\n    let instrument_id = format_instrument_id(symbol_ustr, product_type);\n    let instrument = instruments\n        .get(symbol_ustr)\n        .map(|instrument| instrument.value().clone())\n        .with_context(|| format!(\"missing instrument metadata for {instrument_id}\"))?;\n    anyhow::ensure!(\n        instrument.id() == instrument_id,\n        \"instrument metadata ID {} does not match {instrument_id}\",\n        instrument.id()\n    );\n    let (price_precision, size_precision) = instrument.precisions()?;\n\n    Ok((instrument_id, price_precision, size_precision))\n}\n\n#[cfg(test)]\nmod tests {\n    use std::str::FromStr;\n\n    use axum::{\n        Json, Router,\n        extract::State,\n        http::{StatusCode, Uri},\n        response::{IntoResponse, Response},","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/binance/src/futures/websocket/streams/recovery.rs#L426-L462","documentation":"`resolve_precision` looks up cached instrument metadata for an order symbol to recover price/size precision during recovery reconciliation. Beyond a missing entry, it enforces an internal invariant with `anyhow::ensure!`: the cached instrument's ID must equal the instrument ID constructed from the symbol and product type (`format_instrument_id`). A mismatch means the cache is keyed by symbol but holds an instrument belonging to a different market/product, so precisions cannot be trusted and the open-order report fails (triggering another recovery attempt).","triggerScenarios":"`emit_open_order_reports` resolves precision for a returned open order; the instruments cache contains an entry for the symbol whose `instrument.id()` differs from `format_instrument_id(symbol, product_type)` — e.g. a USDT-M instrument cached under a symbol also used by COIN-M, or a stale cache built with a different product type.","commonSituations":"Mixed USDT-M/COIN-M setups where the instruments map was populated from the wrong product's exchange info; symbol reuse across markets (e.g. same base asset on both); cache not invalidated after switching product type.","solutions":["Check the log: compare the cached instrument ID (first `{}`) against the expected ID; identify which product type's cache was loaded.","Rebuild the instruments cache from the correct product type's exchangeInfo before recovery.","Ensure the cache key includes product type (or use separate maps for USDM/COINM) so symbols cannot collide.","Verify `format_instrument_id` and the cache population path use identical symbol normalization (case, suffixes)."],"exampleFix":"// before\nanyhow::ensure!(\n    instrument.id() == instrument_id,\n    \"instrument metadata ID {} does not match {instrument_id}\",\n    instrument.id()\n);\n// after (fix the cache keying so the invariant holds)\nlet key = (symbol_ustr, product_type); // key instruments by symbol AND product type\nlet instrument = instruments.get(&key)...;","handlingStrategy":"validation","validationCode":"// Rust: verify cache consistency before recovery\nfn cache_consistent(instruments: &DashMap<Ustr, AnyInstrument>, product: ProductType) -> bool {\n    instruments.iter().all(|e| {\n        let expected = format_instrument_id(e.key(), product);\n        e.value().id() == expected\n    })\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Key the instruments cache by (symbol, product_type) or keep separate maps per market","Rebuild the cache from the correct exchangeInfo when switching product type","Use identical symbol normalization in cache population and format_instrument_id","Test mixed USDT-M/COIN-M recovery paths to catch symbol collisions"],"tags":["instrument-metadata","cache","reconciliation","binance-futures","invariant-violation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}