{"record":{"id":"13f24b056742a1a4","repo":"nautechsystems/nautilus_trader","slug":"replacement-scan-verification-evidence-is-incomple","errorCode":null,"errorMessage":"Replacement scan verification evidence is incomplete","messagePattern":"Replacement scan verification evidence is incomplete","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6238,"sourceCode":"                        .context(\"Replacement scan cursor timestamp is negative\")?,\n                    base_fee_per_gas: base_fee\n                        .map(|value| {\n                            value.parse::<u128>().map_err(|_| {\n                                anyhow::anyhow!(\"Replacement scan cursor base fee is invalid\")\n                            })\n                        })\n                        .transpose()?,\n                })\n            },\n        )\n        .transpose()\n    }\n\n    pub(crate) async fn record_execution_replacement_scan(\n        &self,\n        scan: &ExecutionReplacementScan<'_>,\n    ) -> anyhow::Result<()> {\n        anyhow::ensure!(\n            !scan.decisions.is_empty()\n                && scan.provider_ids.len() == 3\n                && scan.operator_ids.len() == 3\n                && scan.failure_domain_ids.len() >= 3,\n            \"Replacement scan verification evidence is incomplete\"\n        );\n        let chain_id = i32::try_from(scan.chain_id)\n            .context(\"Replacement scan chain ID exceeds PostgreSQL INTEGER\")?;\n        let nonce = i64::try_from(scan.nonce)\n            .context(\"Replacement scan nonce exceeds PostgreSQL BIGINT\")?;\n        let mut transaction = self\n            .pool\n            .begin()\n            .await\n            .context(\"failed to start verified replacement scan transition\")?;\n        let (stored_version, stored_digest, stored_nonce, revision) =\n            sqlx::query_as::<_, (String, String, i64, i64)>(\n                \"","sourceCodeStart":6220,"sourceCodeEnd":6256,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6220-L6256","documentation":"record_execution_replacement_scan validates that the scan carries complete verification evidence: at least one decision, exactly 3 provider IDs, exactly 3 operator IDs, and at least 3 failure domain IDs. If any of these invariants is unmet, the scan is rejected before any database writes.","triggerScenarios":"Calling record_execution_replacement_scan with an ExecutionReplacementScan where decisions is empty, provider_ids.len() != 3, operator_ids.len() != 3, or failure_domain_ids.len() < 3.","commonSituations":"Constructing the scan struct with default/empty collections; a provider/operator discovery step returning fewer identities than configured; filtering decisions out before recording, leaving an empty batch.","solutions":["Populate provider_ids and operator_ids with the 3 configured identities and at least 3 failure domain IDs before calling","Ensure at least one verification decision is collected into scan.decisions","Fix the upstream discovery/aggregation step that produced incomplete identity sets","Assert scan completeness at construction time to fail early"],"exampleFix":"// before\nlet scan = ExecutionReplacementScan { decisions: vec![], provider_ids: p, operator_ids: o, failure_domain_ids: f, .. };\n// after\nassert_eq!(p.len(), 3, \"provider_ids\");\nassert_eq!(o.len(), 3, \"operator_ids\");\nassert!(f.len() >= 3, \"failure_domain_ids\");\nassert!(!decisions.is_empty(), \"decisions\");\nlet scan = ExecutionReplacementScan { decisions, provider_ids: p, operator_ids: o, failure_domain_ids: f, .. };","handlingStrategy":"validation","validationCode":"fn scan_evidence_complete(scan: &ExecutionReplacementScan) -> bool {\n    !scan.decisions.is_empty()\n        && scan.provider_ids.len() == 3\n        && scan.operator_ids.len() == 3\n        && scan.failure_domain_ids.len() >= 3\n}\nanyhow::ensure!(scan_evidence_complete(&scan), \"scan evidence incomplete\");","typeGuard":"fn is_complete_scan(scan: &ExecutionReplacementScan) -> bool {\n    scan.decisions.len() > 0\n        && scan.provider_ids.len() == 3\n        && scan.operator_ids.len() == 3\n        && scan.failure_domain_ids.len() >= 3\n}","tryCatchPattern":"if let Err(e) = db.record_execution_replacement_scan(&scan).await {\n    if e.to_string().contains(\"evidence is incomplete\") {\n        // fix scan construction; this is a caller bug, not retryable\n        return Err(anyhow::anyhow!(\"refusing to record scan: {e}\"));\n    }\n    return Err(e);\n}","preventionTips":["Validate scan structs at construction, before I/O","Assert identity counts come from the configured provider/operator set","Never drop all decisions during filtering; reject instead","Unit-test scan builders against the completeness invariants"],"tags":["validation","incomplete-data","rust"],"backgroundTag":"schema-validation-failed","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"}