A borrower pays €500. The account becomes current and collections stops. Two days later the payment reverses. Deleting the payment or silently setting its amount to zero may repair today's balance—but it destroys the explanation of everything that happened between.
DELETE FROM payments
WHERE payment_id = 'pmt_501';
-- Equally unsafe without preserved lineage:
UPDATE payments SET amount_minor = 0
WHERE payment_id = 'pmt_501';The institution loses the original payment, the cure, the collections hold, the reversal timing and the reason delinquency reopened.
Compensation records that an event happened and was later undone
Pretends the economic event never occurred and leaves intermediate decisions inexplicable.
Preserves the original fact, the later undoing, their timing and causal relationship.
type ReversalEvent = {
eventId: string;
eventType: "PAYMENT_REVERSED";
reversalOf: string;
amountMinor: bigint;
effectiveTime: Date;
reasonCode?: string;
};Causal linkage is as important as amount. Do not mutate PAYMENT_SETTLED into PAYMENT_FAILED when it genuinely settled and later reversed: those are two economic events.
| Event | Meaning | State treatment |
|---|---|---|
| Full reversal | Cancels the remaining original effect | Restore the complete realised allocation |
| Partial reversal | Cancels part of the original effect | Restore only causally specified components |
| Chargeback lifecycle | Dispute/recovery process with its own states | Keep dispute state separate from financial reversal |
| Correction | Original attributes were wrong | Compensate old effect and emit correct replacement where needed |
| Pending reversal | Potential future reversal | Do not undo until the governed economic state is reached |
Corrections form a graph, not merely a chronological list
Use different relations such as reversalOf, supersedesEventId, corrects, allocates and derivedFrom; one overloaded parent field hides meaning. Prevent causal cycles such as A reverses B while B reverses A unless domain semantics explicitly support them.
A wrong-account correction must retain: original application to Account A, reversal from A, and corrected application to Account B. Mutating account_id would silently change both accounts' DPD, collections history, features, statements and reconciliation before the institution knew of the error.
- Detect
- Validate
- Emit correction
- Restate state
- Rebuild dependencies
- Preserve historical decisions
- Reconcile
Reverse what actually happened—not what today's rules would allocate
Suppose the €500 payment applied €50 to fees, €100 to interest and €350 to principal. Full reversal restores those exact components. Simply adding €500 to principal corrupts every component balance.
type AllocationComponent = {
component: "FEE" | "INTEREST" | "PRINCIPAL";
amountMinor: bigint;
};
type CompensationRecord = {
originalEventId: string;
compensatingEventId: string;
compensatedMinor: bigint;
remainingReversibleMinor: bigint;
};If a €500 payment is reversed by €150 then €200, €150 remains applied. Validate cumulative compensation so Σ Reversals ≤ Remaining Original Applied Amount. Partial restoration can be source-defined or component-specific; never invent proportional allocation without economic authority.
The reducer restores components deterministically
function reverseAllocation(
state: LoanState,
allocation: PaymentAllocation
): LoanState {
return recalculate({
...state,
feeBalanceMinor:
state.feeBalanceMinor + allocation.feesAppliedMinor,
accruedInterestMinor:
state.accruedInterestMinor + allocation.interestAppliedMinor,
principalMinor:
state.principalMinor + allocation.principalAppliedMinor,
version: state.version + 1
});
}This illustration omits product-specific adjustments. The reducer loads the stored original allocation; it does not recalculate unrelated rules. Reopened principal may change interest base, exposure or later accruals, so compensation can trigger downstream recalculation beyond one monetary write.
Rebuild DPD; never “set it back”
The unsafe operation is DPD = previous_DPD. Restore the actual payment allocation, rebuild remaining obligations, identify the oldest relevant unpaid due date, then calculate DPD using current approved calendar rules.
The curing payment was undone, often indicating returned or corrected payment processing.
The borrower genuinely cured and later deteriorated again through new behaviour.
These paths are not methodologically interchangeable. A promise-to-pay that appeared kept may become reversed or not sustainably fulfilled; retain the original observation and later reversal rather than rewriting it.
Compensation propagates into risk, collections and recovery economics
| Domain | Potential change | Historical control |
|---|---|---|
| Collections | Reopen delinquency and reprioritise | Keep prior hold/contact actions immutable |
| Behavioural features | Missed payment, payment ratio, cure and duration | Version rebuilt features; retain actual feature manifest |
| Risk score | PD restated may differ from actual | Store counterfactual separately |
| Recovery / LGD | Remove reversed realised recovery economics | Preserve cash-flow and reversal timing |
| ECL | DPD, cure, EAD or stage inputs may change | Trace impact under governed reporting rules |
A reversed recovery left in LGD data overstates recovery and understates loss. Yet the operational cash-flow event should not simply disappear. Model/accounting policy determines the treatment of original and reversed discounted recovery; the event layer supplies immutable timing and lineage.
The event layer reconciles to the ledger; it does not replace it
Canonical events explain operational and economic history. Accounting remains authoritative for ledger state through debit/credit, clearing and correcting entries. A timing interval where operational reversal state differs from posted accounting state can be legitimate when explicitly classified.
- Reversal event
- Operational state restated
- Accounting posting
- Reconciliation match
Track original time, reversal time, posting time and risk-effective time. Collections may need governed early knowledge of likely reversal while monthly Finance waits for formal posting; finality is decision-specific, not one universal status.
Out-of-order reversals wait for their causal parent
A reversal with no resolvable original is an orphan reversal. It may indicate delivery order, a missing source event or identity mismatch. Do not apply it blindly.
When the parent arrives, resolve identity and causal linkage, then replay deterministically using effective time, causal order and source sequence where available. A correction to a reversal is another event: E → R(E) → C(R(E)).
Claim, validate, compensate and mutate in one boundary
Where possible, reversal processing atomically claims the reversal identity, validates the parent, updates cumulative compensation and updates account state. Two simultaneous partial reversals must not both observe the same remaining amount.
await db.transaction(async (tx) => {
const claimed = await claimEvent(tx, reversal.eventId);
if (!claimed) return;
const original = await lockOriginalAllocation(tx, reversal.reversalOf);
assertReversible(original, reversal.amountMinor);
await recordCompensation(tx, original, reversal);
await restoreAllocation(tx, original, reversal);
});Database constraints, row locks or expected-version updates enforce the cumulative invariant under concurrency. Duplicate reversal delivery then becomes a no-op instead of an impossible negative economic effect.
Backdated reversal repairs economics without changing what was known
A reversal effective yesterday but received today can invalidate a later snapshot. Rebuild Staterestated from a safe boundary while retaining Stateknown for yesterday's actual decisions.
Do not rewrite CollectionsDecision = HOLD because the reversal arrived later. Record that corrected state would have produced another action and classify impact: DPD changed, cure reversed, priority changed, PD changed or ECL input changed.
A golden reversal stream proves component restoration
A fictional facility draws €5,000, charges a €50 fee and accrues €100 interest. A €500 payment allocates €50 fee, €100 interest and €350 principal, leaving €4,650 outstanding.
| Step | Event | Principal | Interest | Fee | Outstanding |
|---|---|---|---|---|---|
| 1 | Drawdown €5,000 | €5,000 | €0 | €0 | €5,000 |
| 2 | Fee €50 | €5,000 | €0 | €50 | €5,050 |
| 3 | Interest €100 | €5,000 | €100 | €50 | €5,150 |
| 4 | Payment €500 | €4,650 | €0 | €0 | €4,650 |
| 5 | Partial reversal €200 (restores fee €50, interest €100, principal €50) | €4,700 | €100 | €50 | €4,850 |
| 6 | Duplicate delivery of reversal | €4,700 | €100 | €50 | €4,850 |
| 7 | Second valid reversal €300 (restores remaining principal) | €5,000 | €100 | €50 | €5,150 |
The final monetary state equals the state before the payment, while history contains payment, two valid compensation events and one suppressed duplicate delivery.
Test the failure shapes, not only the happy-path balance
| Test | Proof |
|---|---|
| Full / partial reversal | Exact realised components restore |
| Duplicate reversal | Effect occurs once |
| Multiple partials | Cumulative amount stays within original |
| Over-reversal | Reject or quarantine atomically |
| Orphan / out of order | Wait; parent arrival enables deterministic replay |
| Wrong-account correction | A reverses; B receives replacement |
| Correction of correction | Causal chain remains complete |
| After snapshot | Invalidate or use a prior safe snapshot |
| Cure / DPD impact | Rebuild arrears and DPD, never restore cached value |
Also inject duplicates of both original and reversal, compare snapshot-tail with full replay, verify allocation conservation after every step and assert that historical decisions remain unchanged.
Monitor reversal integrity and downstream materiality
No universal thresholds apply. Slice by source, event type, product, provider and reason. Long-lag reversals often reach deeper into cure history, recovery, model data and prior decisions. Repeated account-mapping or payment-type corrections reveal structural integration debt.
The Entimema reversal architecture preserves causality end to end
- Identify original event
- Validate causality
- Determine full / partial compensation
- Restore original allocation
- Recalculate derived state
- Rebuild dependencies
- Preserve decisions
- Reconcile
- Monitor
A Reversal & Correction Integrity Agent can validate compensation evidence
A future controlled agent can detect reversals, resolve parents, identify orphans, monitor cumulative compensation, reconstruct original allocation, compare pre/post state, trace DPD and cure changes, quantify recovery/LGD impact and reconcile operational with accounting correction state.
Credit Risk
Finance
Allocation trace, recovery economics and ledger reconciliation.
Decision Automation
Current workflow correction with immutable historical decisions.
Continue with Late-Arriving Events and Backdated Corrections, Reconstructing Account State from Financial Events, Idempotency in Payment and Credit Event Processing, Event Time vs Processing Time vs Posting Time, The Payment Is Not the Balance, Cure & Re-Default Analytics, Promise-to-Pay Analytics, Collections Prioritisation and IFRS 9 LGD. A dedicated DPD engine and cross-function event reconciliation remain future research directions, not fabricated routes.