Reconciliation & drift
Every mismatch between intent, provider, core, and ledger is classified, blocks the dependent work it makes unsafe, and is repaired in the domain that owns it.
The platform compares intent, provider, core and ledger continuously, classifies every mismatch it finds, blocks the work that mismatch makes unsafe, and repairs it in the domain that owns it. Three systems that are never compared will agree in reporting and disagree in fact, and the borrower finds out first: a payment the provider settled and the ledger never posted reads, from inside, as a missed installment. Reconciliation is the comparison that catches it — proving that what LendEasy intended, what an external provider reports, and what the authoritative financial record contains all say the same thing. It applies to the current composed Lending Core and its provider boundaries; the retained bring-your-own-core contract extends the same model to another authoritative core.
Six places two records must agree
Each row is a pair of systems that can drift apart quietly, because each side is individually correct and only the comparison is wrong:
| Domain | Records compared |
|---|---|
| Payment | Payment intent ↔ provider settlement/return ↔ loan transaction ↔ journal entry. |
| Funding | Funding request ↔ payout provider ↔ disbursement transaction ↔ journal entry. |
| Core action | Frozen request ↔ core acknowledgement ↔ resulting facts. |
| Merchant remittance | Sales report ↔ calculated remittance ↔ settlement ↔ purchased-balance reduction. |
| Statement | Generation key ↔ fact snapshot ↔ artifact hash ↔ delivery. |
| Identity mapping | LendEasy ID ↔ current external-core ID and lifecycle. |
An exception is work, not a warning
A detected mismatch is created as a classified exception against its target and given an owner immediately. A difference that only appears on a dashboard is a difference nobody has agreed to fix, and money-movement mismatches do not get better while they wait.
The remedy is not to debit Maya again
{
"exceptionId": 3301,
"exceptionType": "SETTLED_NOT_POSTED",
"targetType": "PAYMENT_INTENT",
"targetRef": "9011",
"status": "open",
"detail": {
"providerRef": "ach-settle-88"
},
"createdAt": "2026-09-15T14:30:00Z",
"lastSeenAt": "2026-09-15T18:30:00Z"
}
The provider already settled. The missing record is on this side, and a retry aimed at the symptom takes a second payment out of a borrower who made the first one. The payment domain verifies the event, posts or recovers the missing ledger effect idempotently, and reruns the match.
An unknown outcome is not a failure
- expected but still within normal pending window;
- timed out with unknown external outcome;
- provider settled but authoritative posting absent;
- authoritative state changed without a governed request;
- acknowledgement says complete but facts did not change;
- applied payload or amount differs from requested hash;
- duplicate provider/source reference;
- return or reversal missing on one side;
- identity mapping missing or points to an inactive resource;
- stale fact feed beyond execution threshold.
We do not know what happened and it did not happen are different findings, and treating the first as the second is how a timed-out debit becomes two debits. An unknown outcome parks dependent work and is settled by a status read or reconciliation rather than by resubmission.
On the built-in core these surface as the exception catalog: STUCK_BEFORE_SUBMIT, SETTLED_NOT_POSTED, RECORDED_NOT_POSTED, RETURN_NEEDS_REVIEW, DUPLICATE_REFERENCE, TXN_NO_RECORD, and EVENT_NO_RECORD.
Severity decides how much work stops
Severity and domain determine which work is blocked. A payment mismatch may suppress duplicate collection, receipts, closure, and downstream fees while allowing read-only investigation. A stale bankruptcy restriction feed blocks contact much more aggressively than a stale non-critical profile field — one of them risks a wrong figure on a statement, and the other risks calling somebody the platform is meant to be protecting.
Closing an exception repairs nothing
POST /v1/recon-exceptions/{exceptionId}/status moves an exception between open, in_review, resolved, and exported—a note is required to resolve—and it does not perform the financial repair. Resolution cites the payment, funding, governed command, identity-map correction, or other authoritative result that fixed the difference. Without that citation, resolved means only that somebody stopped looking at it, and the same mismatch is detected again on the next run. GET /v1/recon-exceptions/export downloads open exceptions as a CSV for external working sessions and marks them exported.
Drift arrives with its review task attached
The exception catalog above compares money-movement records inside the Lending Core. The servicing workspace keeps its own drift view — where operational state it expected diverged from the authoritative core — readable at GET /v1/servicing/reports/reconciliation:
{
"driftId": 81,
"driftType": "EXPECTED_STATE_MISMATCH",
"objectType": "AUTOPAY",
"objectRef": "7204",
"customerRef": "412",
"status": "OPEN",
"priority": "HIGH",
"reviewTaskId": 2005,
"detectedAt": "2027-05-05T18:30:00Z"
}
Each row already carries reviewTaskId. Drift becomes prioritized work at detection rather than sitting in a dashboard waiting to be noticed, which is the whole point of surfacing it: a workspace that tracks a desired state has to open work when reality disagrees. Filter by driftType, objectType, status, priority, or detection window; the report exposes the classification and resolution state without exposing implementation tables.
Use the right surface for the right question. /v1/recon-exceptions is the authoritative money-movement exception queue with its own status commands and CSV export. /v1/servicing/reports/reconciliation is a bounded read of servicing-to-core divergence — see Servicing reports.
A falling exception count can mean blindness
Track count and amount by type, age percentiles, critical time to acknowledge/repair, recurring source, false positives, reopened items, and percentage auto-matched. A declining count is good news only when detection coverage and freshness held steady over the same period; a matcher that stopped reading one feed produces the same graph as a book that stopped breaking.