{"record":{"id":"9d592f808a88301e","repo":"nautechsystems/nautilus_trader","slug":"position-fragments-do-not-cover-voided-commission","errorCode":null,"errorMessage":"position fragments do not cover voided commission for fill {}","messagePattern":"position fragments do not cover voided commission for fill (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/execution/src/engine/mod.rs","lineNumber":3964,"sourceCode":"                );\n                let removed_raw = remaining_commission.raw.abs().min(commission.raw.abs());\n                let removed = Money::from_raw(\n                    removed_raw * remaining_commission.raw.signum(),\n                    remaining_commission.currency,\n                );\n                allocations\n                    .entry(*position_id)\n                    .and_modify(|allocation| {\n                        allocation.1 = Some(\n                            allocation\n                                .1\n                                .map_or(removed, |commission| commission + removed),\n                        );\n                    })\n                    .or_insert((Quantity::zero(event.voided_qty.precision), Some(removed)));\n                remaining_commission = remaining_commission - removed;\n            }\n            anyhow::ensure!(\n                remaining_commission.is_zero(),\n                \"position fragments do not cover voided commission for fill {}\",\n                event.trade_id\n            );\n        }\n\n        let mut corrected_positions = Vec::new();\n\n        for (position_id, (voided_qty, commission_voided)) in allocations {\n            if voided_qty.is_zero() {\n                anyhow::bail!(\n                    \"commission-only position correction requires authoritative reconciliation for fill {}\",\n                    event.trade_id\n                );\n            }\n            let mut position = self\n                .cache\n                .borrow()","sourceCodeStart":3946,"sourceCodeEnd":3982,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/execution/src/engine/mod.rs#L3946-L3982","documentation":"After distributing the voided commission across position fragments, the engine requires the remaining commission to be exactly zero. If the recorded per-position commissions total less than the commission being voided, the leftover amount cannot be attributed to any position and the engine aborts the void with this error to keep commission accounting exact.","triggerScenarios":"Applying a fill void where commission_voided exceeds the sum of commissions recorded against the involved positions — e.g. the void event carries a stale or recomputed commission amount, or a position's commission fragment was already consumed by an earlier void.","commonSituations":"Replaying void events twice; commission recalculation (e.g. tiered fee change) making the voided amount larger than originally booked; partial cache restores dropping some position commission records.","solutions":["Verify commission_voided equals the commission originally recorded with the fill (same amount and currency).","Make void handling idempotent so a duplicated void event is skipped instead of re-distributing commission.","Restore cache state from a consistent snapshot so all position commission fragments are present before voiding.","Trace the distribution loop at crates/execution/src/engine/mod.rs (~line 3946-3962) and log remaining_commission after each fragment to find the shortfall."],"exampleFix":"// before: void event carries recalculated (larger) commission\nlet voided = Money::new(3.0, Currency::USD()); // recorded was 2.0\nengine.apply_fill_void(FillVoided { commission_voided: Some(voided), .. })?;\n// after: use the originally booked commission\nlet voided = recorded_fill.commission; // 2.0 USD\nengine.apply_fill_void(FillVoided { commission_voided: Some(voided), .. })?;","handlingStrategy":"validation","validationCode":"// voided commission must equal the commission originally booked with the fill\nlet booked = recorded_fill(event.trade_id).commission;\nif let Some(voided) = &event.commission_voided {\n    assert_eq!(voided, &booked, \"voided commission must equal booked commission\");\n}","typeGuard":null,"tryCatchPattern":"match engine.apply_fill_void(event) {\n    Err(e) if e.to_string().contains(\"do not cover voided commission\") => {\n        log::error!(\"commission under-covered for fill {}: {e}\", event.trade_id);\n        // stop replay; compare event.commission_voided against the fill record\n    }\n    other => other?,\n}","preventionTips":["Copy commission_voided from the original fill record, never recompute it at void time.","Skip already-processed void events to avoid double distribution.","Keep commission records and position fragments in the same snapshot."],"tags":["execution-engine","commission","fill-void","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-14T00:17:10.932Z"}