Every individual payout was correct. The reconciliation report still would not balance.
The setup: a lending marketplace where one borrower repayment gets split across many investors, pro-rata to their share of the deal. The split code did the obvious thing, computing each share, rounding it to the minor unit, writing it.
Round each share on its own and the parts do not have to add up to the whole. Some splits paid out a cent less than arrived. Some a cent more.
Nothing failed. No exception, no alert. Every row passed its per-row check, because per-row they were all fine. The error lived only in the sum, and the sum lived in a report nobody opened daily.
By the time it surfaced there was no single bug to fix. There was drift across a long history of repayments, to be reconstructed deal by deal, deciding who was actually owed the difference.
Three things I do differently now:
Money stays in integer minor units, never floats.
The split function hands the remainder to a named party, deterministically, instead of letting rounding pick a winner quietly.
The invariant sits inside the same transaction as the write: sum of parts equals the amount received, or nothing persists.
A split that does not balance should be impossible to store, not just unlikely.
I wrote more about invariants in payment flows here: https://polycratia.com/c/66039cf5
If you run money splits today, go look at where your remainder ends up, and whether that was a decision or an accident...