be-ledger health audit
Does the ledger database match the money we moved, and is be-ledger the ledger it should be? Read-only checks against prod-ledger-use2-sqlDb and its read replica, the dw-prod-gwiiag warehouse and Elastic, and a review of be-ledger's code at prod 75725be and master b4bd523 and of the services that post to it.
Verdict
Mostly yes: the ledger matches the money it moved. Since 1 March 2026 every balance follows from the journal: 0 breaks in 104.8M journal rows, and each of the 676,914 balances that names its last journal row matches it. Against Lithic's settlement report, 18.81M debit card transactions from April to 5 October 2026 match to the cent, −$653.37M net, and no settled transaction lacks a ledger flow. The 58,157 Galileo-era GA= flows that also carry Lithic transactions charge exactly Galileo's own settlement plus Lithic's final amount, −$1.69M, and hold nothing. On the credit-card program 741,801 of 784,953 Lithic transactions match, and 43,109 more moved no money on either side. Apart from Galileo-era rows, whose keys do not map to be-transactions, every journal row that moved money from April to September 2026 has a be-transactions record.
Where it does not match. 43 credit-card transactions were charged $1,642.59 less than Lithic settled, all last written between January and April 2026: settlement-report rows overwrote each cleared amount, a posting be-lithic stopped on 25 April 2026, and no such row has reached a card flow since (W46). On 5 Visa stand-in transactions the ledger equals Lithic's transaction record, $0.00, while Lithic's settlement report settles $287.73 of credits to 4 customers and a $1.99 purchase; only Lithic can say which is right (CL-27). Before March 2026, 23 balance breaks on 17 accounts net −$701.98, and on one of those accounts available has been $973.00 below its flows since 7 January 2026 (DATA-1). Nothing reconciles the ledger with Lithic automatically, so none of these was caught before this audit.
Accounts older than the journal. The oldest journal row is from 20 March 2025, and nothing records what an account held before its first row, so no account opened before then can be proven from the ledger's own records. 348,378 accounts opened before April 2025 differ from their flows by +$47.03M net: +$40.59M is the balance they held before their first journal row, and +$6.43M, on 66,927 accounts, is more than that. Replayed version by version on 231 accounts, the excess equals, to the cent, 1,060 breaks, journal rows at which the balance moved by a different amount than the row's flow. 1,055 are the first journal row of a flow written 20–31 March 2025, which is what a movement taken from the balance before the journal began looks like; of the 5 later ones, 3 come from balance writes of June and July 2025 with no journal row of their own (P27). Accounts opened in April 2025 show that the gap hides real errors: be-ledger took their first balance from the caller (DB-20), so on 1,100 accounts opened 1–8 April 2025 a deposit, fee or credit was counted twice, +$54,793.43 net. Postings consistent with be-transactions' reconcile handler took each back out of the balance by 4 July 2025 (CL-30), so the balances are right and the flows of those accounts are off by the doubled amount (Opening balances).
Holds and reserves. 1,078 flows on 864 customer accounts hold a reserve last written between March 2025 and March 2026 (P23). 54 of them keep −$1,226.57 of 47 customers' money unavailable; 48 of those are be-transactions reservations of 11–12 December 2025 that never left PreAuth (W47). 727, on 702 accounts, were released in the balance but not on the flow, so a later event or expiry on one would release its money a second time; 647 of them, written from May to July 2025, have the exact shape of the rows be-transactions' reconcile handler writes, which sets an account's reserve to zero by writing its negative onto a new flow (CL-30). On 110 accounts, 182 GA= holds are offset to the cent by credit reserves of the same shape from May and September 2025, and available is unaffected. On the other 38 accounts whose reserve differs from their flows, each difference is traced to the journal rows, balance writes with no journal row or direct flow updates that caused it (P4b, P26, P28). Another 845 flows sit on the empty account id with +$20,739.61: 30 carry a PMA= credit whose journal row names a customer account, and 815 have no journal row (Holds, Flows with no account).
It is not yet an excellent ledger. Prod publishes a transfer's debit event before the transfer commits, so a rolled-back transfer still reaches statements, audit and the warehouse: 1,595 phantom events since February, −$1.72M of debits that never happened (EV-1, fixed on master). The database enforces no uniqueness, no references and no double entry, corrections are written straight into the money tables with no record of them (DB-11), and three callers can skip a debit on retry.
Does it match the money?
Data consistency 8 open
What is wrong in the data today, kept apart from the code that caused it. Since March 2026 the ledger's balances agree with its journal; these items are where the ledger, or a copy built from it, does not match the money, or where a measured difference has not been traced to its cause.
| Item | Scope | Money | Status |
|---|---|---|---|
| Event copies that differ from the journal | 1,595 phantom events, one published twice; 13 lost and 2 doubled; 2 rows missing in the warehouse | −$1,722,060.87 of debits that never happened, in statements, audit and the warehouse | Open: deploy, then clean up |
| Stale flow reserves and account reserves | 1,078 flow reserves on 864 accounts, March 2025 to March 2026; reserves differ from flows on 740 accounts | −$1,226.57 unavailable to 47 customers; −$20,610.06 released in the balance but open on the flow | Open |
| Balances with no opening entry | 348,378 accounts opened before April 2025; 1,111 opened in April 2025 | +$40,585,634.28 recorded only in balances; +$6,434,523.47 on 66,927 accounts beyond their opening; +$55,186.63 on the April 2025 accounts | Open: partly traced |
| Credit-card transactions charged differently from Lithic | 43 transactions on 39 accounts, January to April 2026 | $1,642.59 charged less than Lithic settled: $1,571.47 on 40 settled, $71.12 credited on 3 voided | Open: needs a decision |
| Flows on the empty account id | 845 flows, the newest from November 2025: 30 with a credit row naming a customer, 815 with no journal row | +$20,739.61 on no customer's balance; the 30 credits, +$4,749.55, name their accounts | Open: 815 owners not traced |
| Visa stand-in transactions where Lithic's two records disagree | 5 transactions on 5 accounts, 25 July and 31 August 2026 | Ledger at 0, as Lithic's transaction record; its settlement report settles $287.73 of credits and a $1.99 purchase | Open: ask Lithic |
| Balance breaks before March 2026 | 23 rows on 17 accounts, November 2025 to February 2026 | Net −$701.98; one account $973.00 below its flows | Open: cause unknown |
| Accounts with no balance row | 66 accounts, auto-created 3 February 2026 | No money; an authorisation on one is approved for any amount (CL-1) | Open |
All data items, including the ones checked and found correct
Code issues 52
Defects in be-ledger and the services that post to it, by severity.
Where to start
Data corrections
- Clean the phantom events out of statements, audit and the warehouse, after the deploy1,595 phantom debit events, −$1.72M, sit in copies that customers and finance read. A cleanup before the deploy would be overtaken by new ones.
- Clear the stale flow reserves without moving money twice54 holds keep −$1,226.57 of 47 customers' money unavailable; 48 are be-transactions reservations of December 2025 left in PreAuth, and 2 are transfers be-transactions reversed. 727 holds on 702 accounts are already released in the balance, so releasing them the normal way would credit $20,610.06 a second time; they need a correction on the flow only. 182 GA= holds and their 110 offsetting credits net to zero per account and close together.
- Decide on the 43 credit-card transactions with be-credit$1,571.47 under-charged on 40 transactions and $71.12 credited on 3. A ledger correction alone would leave LoanPro wrong.
Code fixes
- Deploy master to prod
- Reconcile the ledger against Lithic every dayNothing compares the ledger with Lithic today. The 43 credit-card transactions, and the 5 stand-in transactions on which Lithic's own records disagree, were found only by this audit.
- Measure and fix the retry paths that skip a debitUnmeasured. If they fire, Majority funds the payout, plan period or top-up.
The full fix plan, both tracks in order
Not measured or not traced
Not measured: CL-2, CL-3 and CL-8, which need the be-pspfunding, be-phoneplan, be-mtu and prod-wallet databases. Measured but not traced to a cause: whether the March 2025 first rows behind the difference beyond the opening, 1,055 of them on the 231 accounts replayed, were taken from the balance before the journal began, which the ledger alone cannot show; and whose money the 815 flows on the empty account id with no journal row hold (Flows with no account).
Scorecard
Each property an excellent ledger has, and where be-ledger stands today.
Meets: 1 · Partly: 8 · Missing: 5
| Property | What excellent looks like | be-ledger today | Status |
|---|---|---|---|
| Balances agree with the journal | Every balance equals the sum of its journal rows, checked continuously. | Since March 2026: 0 chain breaks in 104.8M journal rows, and 193,317 of 193,317 recent balances equal their last journal snapshot. Older balances do not: the oldest journal row is from 20 March 2025, nothing records what an account held before its first row, and 348,378 accounts opened before April 2025 differ from their flows by +$47.03M net, +$6.43M of it more than their opening balance (Opening balances). No automated check runs; this audit is the check. | Partly |
| Every movement is journaled | An append-only movement row for every change to a balance, holds included. | Card authorisation decisions reserve in AccountBalance and the flow without a journal row. The journal records the hold only when Lithic's AUTHORIZATION event arrives, and never if it does not. The flow table is overwritten in place, and journal rows are snapshots, not movements (DB-7). | Partly |
| Double entry | Every movement has an offsetting leg, and system accounts carry balances. | Customer side only. System-account tables stopped receiving rows in February 2026 and no code writes them (DB-16). | Missing |
| Processor reconciliation | Daily automated match of every processor movement to the ledger, with exceptions worked. | No automated reconciliation runs. Measured here: 18,811,746 debit card transactions from April to 5 October 2026 match Lithic's settlement report to the cent, and every settled transaction has a ledger flow; 5 Visa stand-in transactions differ from the report and match Lithic's transaction record (CL-27). On the credit-card program 43 of 784,953 Lithic transactions differ, $1,642.59 in the customers' favour (Credit-card flows). Nothing caught these before this audit. The 58,157 Galileo-era GA= flows that carry Lithic events match Galileo's own settlement plus Lithic's final amount to the cent (GA= flows). | Partly |
| Idempotency enforced by the database | Unique keys on transaction id, idempotency key and flow id. | No unique key on any of them; lookups are time-windowed (DB-2, DB-4, DB-6). Measured: no duplicates in September 2026. | Partly |
| Referential integrity | Foreign keys; unknown accounts rejected. | No foreign keys. Any GUID becomes an account on first use, with no balance row (DB-8), which is the precondition for CL-1. | Missing |
| Safe defaults on the money path | Unknown state fails closed. | The authorisation path forces available to 0 for an account with no balance row and approves any amount (CL-1). | Missing |
| State change and event are atomic | Transactional outbox. | Fixed on master (443b6bc) but not deployed. Prod publishes each event before the posting commits, so a rolled-back posting still emits one: 1,595 found, 1,551 of them confirmed absent from prod (EV-1). | Partly |
| Concurrency control | Every balance-changing path locks what it reads. | Per-account UPDLOCK on the posting paths. CreateAccount skips it (DB-1); cross-account balance rules read without locks (DB-5). | Partly |
| Corrections are journaled | Corrections are movements with a reason, never edits. | LedgerCorrection has no writer and 0 rows; journal rows can be edited by hand without a trace (DB-11). Corrections found in the journal, such as the type-7 rows on GA= flows, were posted as movements, which is the right pattern. | Partly |
| Callers move money exactly once | Each caller's retry reuses one key and only marks done after the ledger call succeeds. | Three callers can skip or lose a debit on retry (CL-2, CL-3). Measured for be-transactions, April–September 2026: no movement was posted twice. | Missing |
| Precision | Amounts stored at the currency's minor unit. | money (4 decimals) against C# decimal (DB-15). Measured: 0 sub-cent rows in 3.1M journal rows (21–27 Sep 2026). | Meets |
| Event stream matches the journal | Exactly one event per journal row. | 1,595 events for postings that rolled back (February to 7 October 2026), each the first leg of a posting whose later leg broke a balance rule; 13 journal rows with no event and 2 with two. The outbox on master fixes the first; prod does not run it (EV-1, EV-2). | Missing |
| Operability | Bounded retention, point lookups that stay cheap, no dormant hazards. | Balance history kept forever (356M rows, 86.8 GB). Partitioning makes each point lookup read about 50 pages; worst calls take 3.4–4.3 s on the card path (DB-12). A frozen LastRun row guards every lookup window (DB-4). | Partly |
Fix plan
Two tracks that run in parallel: correcting the data that is wrong today, and fixing the code so it cannot go wrong again. Each track is in the order that removes the most risk first, and each step links to the pages with the evidence, the fix steps and the verification.
Data corrections
What is wrong in the data now. Step 1 waits for the first code fix.
- D1Clean the phantom events out of statements, audit and the warehouse, after the deploy
1,595 phantom debit events, −$1.72M, sit in copies that customers and finance read. A cleanup before the deploy would be overtaken by new ones.
Where: be-accountstatements, be-auditing, warehouse - D2Clear the stale flow reserves without moving money twice
54 holds keep −$1,226.57 of 47 customers' money unavailable; 48 are be-transactions reservations of December 2025 left in PreAuth, and 2 are transfers be-transactions reversed. 727 holds on 702 accounts are already released in the balance, so releasing them the normal way would credit $20,610.06 a second time; they need a correction on the flow only. 182 GA= holds and their 110 offsetting credits net to zero per account and close together.
Where: be-ledger, be-transactions - D3Decide on the 43 credit-card transactions with be-credit
$1,571.47 under-charged on 40 transactions and $71.12 credited on 3. A ledger correction alone would leave LoanPro wrong.
Where: be-ledger, be-credit, LoanPro - D4Ask Lithic about the stand-in transactions
On 5 Visa stand-in transactions the ledger holds 0, as Lithic's transaction record does, while Lithic's settlement report settles $287.73 of credits to 4 customers and a $1.99 purchase; nothing is corrected until Lithic says which is right (CL-27).
Where: be-ledger, be-lithic, Lithic - D5Record corrections on the flows of the 1,100 April 2025 accounts
On 1,100 accounts opened 1–8 April 2025 a deposit, fee or credit was counted twice (DB-20), and reconcile postings took each back out of the balance by 4 July 2025 (CL-30): the balances are right, and the flows differ from them by the doubled amount, +$54,793.43 net. No money moves: record the correction on the flows (DB-11).
Where: be-ledger - D6Record opening balances, then correct what they do not explain
+$40,585,634.28 that 348,378 accounts held before the oldest journal row is recorded only in their balances, so no older account can be proven from the ledger, and real differences hide in the gap: +$6,434,523.47 on 66,927 accounts goes beyond the opening balance. Replayed on 231 accounts, 1,055 of the 1,060 breaks behind it are first journal rows of March 2025, and 3 balance writes of June and July 2025 with no journal row of their own moved the balance by −$2,677.61.
Where: be-ledger - D7Move the 30 credited flows off the empty account id, and trace the 815 others
30 flows on the empty account id carry a PMA= credit, +$4,749.55, whose journal row names the customer's account; on the 6 measured, the balance holds the credit, so the flow moves and the money does not. 815 have no journal row: 810 GA= flows, 798 credit reserves (+$16,661.14) and 12 holds (−$671.08), and 5 flows at zero, the newest from November 2025.
Where: be-ledger - D8Trace and correct the 23 balance breaks from before March 2026
Net −$701.98 on 17 accounts, cause unknown. On one account available has been $973.00 below its flows since 7 January 2026.
Where: be-ledger - D9Give the 66 accounts with no balance row a zero balance row
An authorisation on any of them is approved for any amount (CL-1). None has ever moved money, and none appears in the warehouse's account or card tables.
Where: be-ledger
Code fixes
Defects that produce wrong data or wrong decisions.
- C1
- C2Reconcile the ledger against Lithic every day
Nothing compares the ledger with Lithic today. The 43 credit-card transactions, and the 5 stand-in transactions on which Lithic's own records disagree, were found only by this audit.
Where: be-lithic or be-reporting - C3Measure and fix the retry paths that skip a debit
Unmeasured. If they fire, Majority funds the payout, plan period or top-up.
Where: be-pspfunding, be-phoneplan, be-mtu - C4Close the zero-balance authorisation path
Approves any amount on an account with no balance row. Has to land before Rain cards go live.
Where: be-ledger - C5Stop the credit-limit leak in the shared rules config
A small change; unmeasured exposure on CreditSecured accounts.
Where: be-transactions - C6
- C7Give the database its own guarantees
Unique keys, locked creates and locked cross-account rules. Today these hold only because of call order.
Where: be-ledger - C8Build the audit trail and fix operability
A movement table and recorded corrections, retention, partitioning, paging, the export gap and event contracts.
Where: be-ledger, be-reporting, be-lithicDB-7 Holds reach the journal lateDB-11 Corrections are not journaledDB-9 Balance history: retention and 13535DB-12 Partitioning slows point lookupsDB-13 Inquiry locks and batch lock orderDB-14 Report paging over non-unique sortsDB-10 Warehouse export skips late commitsCL-11 Lithic replays add rows with new keysCL-12 Auth events carry the cumulative holdCL-13 Card auth's ledger call has no deadline - C9Stop callers from setting balances
be-ledger takes a flow's previous state and an account's first balance from the caller, which counted a deposit, fee or credit twice on 1,100 accounts in April 2025 (DB-20); be-transactions' reconcile handler zeroes reserves on new flows and leaves holds open (CL-30).
Where: be-ledger, be-transactions, be-lithic
Do balances follow from the journal?
0 breaks in 104,773,210 journal rows from 1 March to 9 October 2026: each row's settled balance (available minus reserved) equals the previous row's plus its own movement. 193,317 of 193,317 balances written 1–9 October equal their last journal row. Before March there are 23 breaks on 17 accounts (November 2025 to February 2026), net −$701.98, cause not established (DATA-1).
Every balance against its journal row, 1,544,133 balances on 12 October 2026 (P21): the 676,914 that name their last journal row, all written in 2026, match it, with 0 missing rows and 0 differences in snapshot, account or time. The other 867,219 name no journal row: 538,704 have no balance time, 200,822 were last written in 2025 and 127,693 in 2026, and 147,527 of them are opening rows of accounts that never transacted.
Every account's balance against its flows, 1,544,134 accounts on 12 October 2026 (P4b). Of the 536,839 accounts opened from April 2025, 535,691 match in both available and reserved; 37 opened from May 2025 differ in available by +$4,531.20 net, 30 of them through a reserve difference.
Accounts older than the journal (P24b). The oldest journal row is from 20 March 2025, 12:11 UTC, and nothing in the ledger records what an account held before its first journal row. Of the 1,007,294 accounts opened before April 2025, 348,378 differ from their flows in available, +$47,033,607.61 net. +$40,585,634.28 of it is the balance they held before their first journal row, taken in write order, and on 915,832 accounts the settled difference equals that opening balance to the cent. On the other 66,927 it does not: they differ by +$6,434,523.47 more than their opening balance, +$6,465,947.34 where the balance is higher and −$31,423.87 where it is lower. The rest is reserves, +$14,993.26 on 698 accounts; −$1,618.36 on 55 accounts whose first journal row is from May 2025 or later; and +$74.96 on 16 with no flow. Replayed version by version on 231 accounts, 221 of them opened before April 2025 with large differences beyond their opening balance, the difference beyond the opening equals, to the cent, the journal rows at which the balance moved by a different amount than the row's own flow: 1,060 rows, +$175,406.61. Each is the first journal row of its flow, and 1,055 were written 20–31 March 2025 (P27).
Of the 29,822 accounts opened in April 2025, after the journal began, 1,111 differ in available by +$55,186.63 net. On 1,100 of them, all opened 1–8 April 2025, a movement was counted twice: the first journal row written on each account carried a balance that already included the next one to three rows, stamped within 5 seconds of it, and each of those rows then moved the balance again (P30). be-ledger takes an account's first balance from its caller instead of computing it (DB-20). 687 deposits (PM=) were counted twice, +$48,917.21; 336 deposit fees (FE=), −$1,327.20; credits (PMA=) on 76 accounts, +$7,103.42; and one deposit, +$100.00. Postings consistent with be-transactions' reconcile handler (CL-30) then took every one of them back out of the balance, between 2.8 hours and 88.5 days later and last on 4 July 2025: on 1,097 accounts one posting of exactly the doubled amount, on 3 two that together come to it (P31). Each balance is right after its reversal, and the flows are not: the reversal is a flow too, so the flows of all 1,100 differ from their balances by the doubled amount, +$54,793.43 net. On one of them a further posting of the same kind settled nothing and put a reserve of −$5.00 on a new flow, +$5.00. The other 11 accounts differ by +$388.20: 2 hold $0.00 while their flows are below zero (+$26.07), and 9 differ in reserve (+$362.13) (Opening balances).
Holds reach the journal late (DB-7), so they are checked as flow state. Card holds match Lithic. Older flow reserves do not: 1,078 flows on 864 accounts hold a reserve last written between March 2025 and March 2026 (P23). 54 keep −$1,226.57 of 47 customers' money unavailable; 727 on 702 accounts were released in the balance but not on the flow; 182 GA= holds on 110 accounts are offset to the cent by credit reserves. 647 of the 727, and the 110 credit reserves that offset the GA= holds, have the exact shape of the rows be-transactions' reconcile handler writes: it sets an account's reserve to zero by writing the reserve's negative onto a new flow (P29, CL-30). In all, 740 accounts' reserves differ from their flows, and each difference is traced to the journal rows, balance writes with no journal row or direct flow updates that caused it (P26, P28, Holds).
Checks behind this answer
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Settled-balance chain: each journal row's settled balance (available minus reserved) equals the previous row's plus its own settled movement | 104,773,210 journal rows, 1 Mar–9 Oct 2026 | 0 breaks | Pass | W7a |
| The same chain, earlier months | Sep 2025–Feb 2026 | 23 breaks (Nov 1, Dec 2, Jan 8, Feb 12) on 16 accounts, net −$701.98. The 349 in Sep 2025 are where the warehouse copy starts. | Minor | W7a, W7c |
| Balance equals its last journal row | 193,317 balances written 1–9 Oct 2026 | 193,317 equal in account, amounts and time | Pass | P17 |
| Every balance against its journal row | All 1,544,133 balances, 12 October 2026 | The 676,914 balances that name their last journal row, all written in 2026, match it: 0 without the row within 5 seconds, 0 with a different snapshot, account or time. 867,219 name no journal row: 538,704 have no balance time, 200,822 were last written in 2025 and 127,693 in 2026; 147,527 of these are opening rows of accounts that never transacted. | Pass | P21 |
| Flow state against the journal | 2,335,686 flows touched in one week | 0 inconsistent: 2,271,413 with one matching journal row, 64,273 closed holds at zero with no row | Pass | P5 |
| Balance equals the sum of its flows, per account | Every account with a balance or a flow: 1,544,134 on 12 October 2026, read under one snapshot | Opened from April 2025: 535,691 of 536,839 match in both available and reserved. 1,148 differ in available, +$59,717.83 net: 1,111 opened in April 2025 (+$55,186.63; 336 by exactly −$3.95, opened 1–8 April) and 37 opened later (+$4,531.20), 30 of them through a reserve difference and 7 in available only, among them 6b6afc2c-3156-48fb-8fbb-ac4e14564512 at −$973.00 (DATA-1). Opened before April 2025: available differs on 348,378 of 1,007,294, +$47,033,607.61 net, +$40,585,634.28 of it the balance held before the first journal row in write order (P24b); here only their reserves are compared. Reserves differ on 740 accounts, +$19,398.31: 702 by exactly their released stale holds (+$20,610.06) and 38 others (−$1,211.75), each traced (P26). 845 flows sit on the empty account id, +$20,739.61, with no balance row. 0 flows with a null account, 0 flows without a balance row, 0 balances without an account row, 0 accounts moved during the scan. | Minor | P4b |
| Balances against flows, with each account's opening balance in write order | All 1,544,144 balances on 12 October 2026, by the month the ledger first saw the account; opening balance from each account's first journal row in TransactionId order between 1 March and 30 April 2025: its settled balance less its own settled amount | Opened before April 2025, 1,007,294: available differs from the flows on 348,378, +$47,033,607.61 net. The opening balance is +$40,585,634.28, non-zero on 347,089; on 915,832 accounts the settled difference equals it to the cent. 66,927 differ by +$6,434,523.47 more than their opening (+$6,465,947.34 and −$31,423.87): 463 by $1,000 or more (+$600,895.35), 16,374 by $100 to $1,000 (+$4,389,185.91), 48,833 by $1 to $100 (+$1,443,862.62), 1,257 by less than $1 (+$579.59). Reserves +$14,993.26 on 698. 55 of 3,913 with no journal row before May 2025, −$1,618.36; 16 of 20,622 with no flow, +$74.96. Opened in April 2025, 29,822: 1,111 differ, +$55,186.63: settled +$54,819.50 on 1,102, reserve +$367.13 on 10. By the first journal row written: 336 a PM= deposit, each $3.95 below its flows (−$1,327.20); 687 an FE= fee, with an amount already in the balance (+$48,917.21); 76 a PMA= credit (+$7,103.42); 1 a PM= deposit (+$100.00); 11 with no opening balance, 2 of them +$26.07 and 9 by reserve (+$362.13). Opened from May 2025, 507,028: 37 differ, +$4,531.20, $4,037.92 of it in the reserve of 32. | Partly | P24b |
| Balance history replayed against the journal | 231 accounts: 221 opened before April 2025 with large differences beyond their opening balance, either way, and 10 accounts this audit names, 5 of them opened in April 2025. Every balance version since their first journal row, 293,609, against every journal row and flow of the account | On all 231 the settled difference beyond the opening balance equals the sum of the breaks, the journal rows at which the balance moved by a different amount than the row's own flow: 1,060 rows, +$175,406.61 (+$184,841.99 on 1,050, −$9,435.38 on 10). Each break is the first journal row of its flow, and 1,055 were written 20–31 March 2025; at 940 the balance did not move at all (+$154,461.64). Example: on 7d3a81d0-3754-4dc1-9e18-06fdbd1401b5, P=15832737, the first row of GA=129718840, set the flow's settled amount to −$953.74 on 21 March 2025 at 15:32 UTC while the balance's settled amount stayed at $178.27. 5 breaks are later, −$877.61: 3 balance writes of June and July 2025 under the external id of an existing journal row of the account (−$2,677.61); an authorisation of 2 April 2025 that put a $600.00 hold on its flow while the balance's available did not move (+$600.00 settled); and a +$1,200.00 credit of 18 September 2025 whose flow is on the empty account id. 637 flows dated April to October 2026 have no journal row, all at zero. 9 GA= holds on 4 accounts, −$1,819.00, were cleared on the flow on 8 December 2025 at 14:00:02 UTC with no journal row. 1,088 rows moved the balance's reserve by an amount their flow did not, +$1,929.66 net, in every month from March 2025 to October 2026, consistent with holds placed before their journal row (DB-7). | Info | P27 |
| First journal rows of the April 2025 accounts | The 1,111 accounts opened in April 2025 that differ from their flows, and 300 accounts opened 1–8 April 2025 that do not: each account's first eight journal rows in write order between 1 March and 31 May 2025, and all its flows | On all 1,100 with a non-zero opening balance, the opening equals, to the cent, the amount of the next one to three journal rows written on the account, stamped within 5 seconds of the first, and each of those rows moved the balance by its own amount: 687 PM= deposits after an FE= fee (+$48,917.21), 336 FE= fees after a PM= deposit (−$1,327.20), PMA= credits on 76 accounts, one row on 71 and two or three on 5 (+$7,103.42), and 1 PM= deposit (+$100.00). All 1,100 were opened 1–8 April 2025, and their first rows were written 1–18 April. None of the 300 has a non-zero opening balance. On 238, a Settlement on a new flow with a random external id and no reserve reversed exactly the doubled amount among the first eight rows, 2.8 hours to 55 days later (+$10,228.27 net); on 862 (+$44,565.16 net), none of the first eight rows does. 106 of the 336 fee accounts also have an AD= row of +$3.95, written 8 to 35 seconds after the doubled fee, not traced and not counted as a reversal. The 2 accounts at $0.00 and the 9 that differ in reserve have no opening balance. | Minor | P30 |
| Later journal rows of the April 2025 accounts | Every journal row since March 2025 on the 308,236 flows of the 1,100 April 2025 accounts with a movement counted twice: 501,336 rows after those P30 read, or, for the 238 reversed among them, after the double count | The test finds all 238 known reversals. On every one of the other 862, a posting of the same shape later reversed the doubled amount: 859 with one posting of exactly that amount, 3 with two that together come to it. Across the 1,100, the reversal came 2.8 hours to 88.5 days after the double count, half within 10 days; 51 were written on 4 July 2025 between 10:55 and 14:57 UTC. Each is a Settlement that is the only row on a new flow, with a GUID external id and a different GUID idempotency key; 98 later rows of the same amount and single-row shape, from December 2025 on, carry their external id as the key and are ordinary postings. No account has a second reversal. On 1,099 accounts the available balance differs from the flows by exactly the doubled amount; on the other, by $5.00 more, a reserve that another posting of this shape put on a new flow on 10 May 2025. | Minor | P31 |
| Flow reserves left after their transaction ended | Every flow dated before 4 September 2026 that holds a reserve, 12 October 2026 | 1,888 flows, dated 20 March 2025 to 24 March 2026; none is a native Lithic flow. 810 GA= flows on the empty account id: 798 credits, +$16,661.14, and 12 holds, −$671.08. 1,078 on 864 customer accounts, −$21,354.01 net: 54 holds on 47 accounts keep −$1,226.57 unavailable; 727 holds on 702 accounts, −$20,610.06, are released in the balance but not on the flow; 182 GA= holds on 110 accounts, −$7,920.64, are offset to the cent by 110 credit reserves; 5 credit reserves, +$482.62, 2 of them carried by the balance. | Minor | P23 |
| Stale uuid4 flow reserves in the ledger | The 762 uuid4 flows among the stale flow reserves that match no be-transactions record (W47), with every journal row on them | Each flow has one journal row, on the flow's own account, with an idempotency key, and the flow equals its row. 761 are Settlements of 0 with nothing settled, whose reserve is the negative of the account's reserve just before, and after which the account's reserve is 0: the shape be-transactions' reconcile handler writes (CL-30). 647 are holds, −$12,604.49, on accounts whose reserve was positive; 110 are credit reserves, +$7,920.64, that offset GA= holds; 4 are credit reserves, +$437.62. Written May 2025 751, June 3, July 2, September 5. The other is a type-1 hold of January 2026, −$6.96, that the balance carries. | Minor | P29 |
| Reserve differences replayed row by row | The 27 accounts whose reserve differs from their flows with no stale hold, uncarried credit reserve, DATA-1 break or FixGalileoExp balance write behind it: every flow and every journal row since 1 March 2025, 29,689 rows, in TransactionId order | All +$495.75 traced to the cent. 29 rows on Galileo-era GA= flows, March 2025 to February 2026, moved the balance's reserve by an amount their own flow did not, +$170.95. 8 balance writes with no journal row of their own, +$414.08: 7 of 30 June – 4 July 2025 (+$20.10) and the FixGalileoExp write on 50f08b14 of 8 December 2025 at 13:59:57 UTC (+$393.98); the replay counts each at the next journal row, an AD= row, and each AD= row moved the balance by exactly its own amount (P28). 3 rows on other flows, +$399.01. 16 holds on 10 accounts cleared on the flow with no journal row, all updated on 8 December 2025 at 14:00:02 UTC, −$488.29. One hold reserved on another account's balance and released on its own, ±$53.98. Settled side, all in 2025: at 26 settlements on 20–21 March 2025, each the first journal row of its GA= flow, the balance did not move (+$579.70); balance writes with no journal row of their own on 30 June – 4 July 2025 moved it by −$533.17 net; and between 1 and 4 July 2025 $393.98, the amount of another flow's hold, moved from the reserve into the settled balance of 50f08b14. On each account these make up the whole difference beyond the opening balance. The latest break that persists is from 7 February 2026. | Info | P26 |
| AD= rows against the balance history | Every journal row with an AD= external id, 232,263, and the balance versions around each of the 482 of one cent | All 232,263 are Settlements, each on its own flow and with an idempotency key, written on five days: 30 June 2025 51,206 (−$3,992,153.65), 3 July 56,081 (−$5,675,369.90), 4 July 55,641 (−$4,635,195.52), 18 August 53,900 (−$3,988,021.32) and 9 December 15,435 (−$1,284,598.21), −$19,575,338.60 in all. 482 are of one cent, 289 of −$0.01 and 193 of +$0.01; on the 475 with balance versions on both sides, the balance moved by exactly the row's own amount. Where a row-by-row replay counts a break at an AD= row, the balance moved before the row, by a write with no journal row of its own (P26, P27). | Pass | P28 |
| Flows on the empty account id, and the table catalog | Every flow whose account id is 00000000-0000-0000-0000-000000000000, with its journal rows since 1 March 2025; every table and procedure in the ledger database, with row counts and dates | 845 flows, 840 GA= and 5 uuid5, settled +$4,749.55 and reserved +$15,990.06, last written March 2025 226, April 514, May 72, June 2, August 21, September 1, October 4, November 5; the newest on 10 November 2025, 22:46:55 UTC. 30 GA= flows carry one journal row each, a PMA= Settlement with an idempotency key, +$4,749.55 in all; each row names a different customer account, and on 6 of them the account's balance is measured to hold the credit (P24b, P27). 815 have no journal row: 810 GA= flows, 798 credit reserves (+$16,661.14) and 12 holds (−$671.08), and 5 uuid5 flows at zero, all with whole-second times. Catalog: Account 1,544,210 rows, AccountBalance 1,544,144, AccountBalanceHistory 357,992,590, TransactionCore 294,938,436, TransactionFlowCore 193,661,587; LedgerCorrection 0; SystemAccountBalanceChange 0 and its history 7,615,896; AffectedAccountIds_20260424 58,537 (created 24 April 2026) and temp.ReservedReconcile 39,028 (created 15 June 2026), which no migration creates. AccountBalanceSaveAndGet and TransactionCoreInsertAndGet, created in February 2025, were last modified on 3 February 2026, the day their replacements were created. | Minor | P25b |
| Duplicate keys | Sep 2026 flow ids and idempotency keys in prod; every journal id in the warehouse | 0 duplicates | Pass | P6, P7, W7 |
| CreateAccount resets of a live balance | Since 1 Apr 2026 | 0 | Pass | P16 |
| Sub-cent amounts and identifier length | 3,126,209 journal rows, 21–27 Sep 2026 | 0 sub-cent rows; longest identifier 36 characters | Pass | P15 |
| Integrity counts | Future-dated flows, flows outside the lookup window, flows with no account, non-USD accounts, orphan balances, auto-created accounts | 0 future-dated flows, 0 flows before the lookup window, 0 flows without an account id (1 January 2025 – 11 October 2026), 0 non-USD accounts, 0 balances without an account. 66 accounts have no balance (DB-8); 538,704 balances have no balance time. 25,524 accounts were opened in September 2026, none auto-created. | Pass | P18r |
| CL-1 exposure | Accounts with an "Init" balance row or none | No account has an "Init" row. 66 accounts have no balance row (1,541,450 accounts, 1,541,384 balances): be-ledger auto-created all 66 on 3 February 2026 between 13:04:21 and 13:10:25 UTC. None has a journal row or a balance, and none of the 66 ids appears in the warehouse's ledger, account, card or transaction tables; the same search finds a known account id in each of the account tables (W37). An authorisation on one would be approved for any amount (CL-1). | Minor | P3, P20b, W33, W34, W37 |
| Negative balances | 808,704 accounts with journal rows | 11,635 negative, −$3,124,255.31: 9,894 CreditSecured (−$3,013,619.73), 1,670 CheckingPrimary (−$101,328.01), 22 ServicesClearing (−$9,112.51), 49 other (−$195.06). 0 CreditUnsecured accounts exist. | Info | W7f, W12 |
| Error 13535 on the balance history | be-ledger logs, last 3 months | 369 failed calls in the 3 months to 9 October 2026, the first on 12 July, all in the balance procedure; every one also hit the primary key, because the procedure falls through to an INSERT after the error (DB-9). 15 were declines, none wrongful; 2 stand-in approvals were recorded correctly. 1–7 October: 25 failed calls (2 authorisations, 17 card events, 6 be-transactions postings); retries not traced. | Minor | Elastic |
Related
Does the ledger match Lithic?
Lithic's settlement report lists the transactions Lithic reports as settled each day. Against it, 18,811,746 debit card transactions from April to 5 October 2026 match the ledger to the cent, −$653,366,606.49 net, and every transaction in the report that moved money has a ledger flow. 18,048,913 never settled and have no settled amount in the ledger. 5 differ, all Visa stand-in transactions: the report settles $287.73 of credits to 4 customers and a $1.99 purchase, while Lithic's own transaction record shows $0.00 authorised and cleared on each, which is what the ledger holds. Lithic has to say which of its records is right (CL-27).
Against Lithic's per-event data, all programs: 35,778,218 of 35,831,351 card transactions first seen April–September 2026 match to the cent, $630,477,763.45 settled, and the 408 that differ from the per-event amount equal the settlement report. None is missing. The 1,017 report transactions missing from the per-event table reconcile too: 1,002 match, 11 net to zero and have no flow, and 4 match on their native flow (W36). So do the 4,918 flows that the July masked token reaches (OPS-2): 2,370 are covered by report transactions, 2,471 end at zero, and the other 77 are credit-card flows that equal Lithic's final amount, −$1,510.83 (W37, W38).
Credit cards: of 784,953 Lithic transactions on the credit-card program, 741,801 match the ledger to the cent, −$12,906,618.68 settled, and 43,109 have no flow while Lithic shows zero. 43, on 39 accounts, differ: the ledger charged $1,642.59 less than Lithic settled. All 43 were last written between 26 January and 23 April 2026, and each of their flows carries settlement-report rows that overwrote the cleared amount: on 40 the last row is one, and on 3 an AuthExpiry row written after it carried its amount forward (W43, Credit-card flows).
GA= flows: from 22 April to 16 June 2026 be-lithic wrote 84,489 Lithic transactions onto 58,157 existing Galileo-era flows. Each of those flows, together with the native flows of its Lithic transactions, charges exactly Galileo's own settlement plus Lithic's final amount: −$1,689,088.96 in all, Galileo's −$770,733.77 and Lithic's −$918,355.19, with 0 flows that differ and none holding money (W41b). On the 147 flows with rows for two accounts, each account matches too (GA= flows). Against the settlement report, 39,500 of their debit transactions with no report row carry money only on a GA= flow, −$736,599.27, and nothing on their native flow; 13,130 match the report on their native flow; 91 sit only on a GA= flow, and the 38 inspected are refunds.
October to date: no difference. Warehouse lag explains all but 21 transactions, and 1 of those cleared with the next load. On the other 20, two clearing events share a timestamp and the per-event reading took the smaller; each ledger flow equals Lithic's final amount, 18 equal the settlement report and 2 settled after its last load (W31).
Checks behind this answer
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Debit card transactions against Lithic's settlement report | 36,913,385 debit card transactions, first and last Lithic event between 1 April and 5 October 2026; report loaded to 8 Oct 2026, 14:40 UTC | 18,811,746 match the report to the cent, −$653,366,606.49 net; 424 of them differ from Lithic's last event. 18,048,913 never settled and have no settled amount in the ledger. 5 differ: the Visa stand-in transactions of CL-27, where the ledger is $285.74 net below the report and equals Lithic's transaction record. Every report transaction that moved money has a ledger flow. On GA= flows: 39,500 with no report row carry money only on the GA= flow (−$736,599.27) and 0 on the native flow; 13,130 match on the native flow; 91 sit only on a GA= flow, and the 38 inspected are refunds. The 1,017 report transactions missing from the Lithic detail table: 1,002 match, 11 net to zero, 4 match on the native flow. The 4,918 flows the July masked token reaches: 2,370 covered by report transactions, 2,471 at zero, 77 credit-card flows equal to Lithic's final amount (−$1,510.83). Open holds: 1,331 settled transactions from September and October still hold −$10,733.58, none from earlier months; 3,338 authorisations Lithic still shows pending hold −$59,456.46. | Minor | W35–W38 |
| Lithic, per card transaction | 35,831,351 transactions first seen Apr–Sep 2026 | 35,778,218 match to the cent; 408 differ from the per-event amount and equal Lithic's settlement report (re-checked with signed amounts in W35); 52,725 on shared flows (next row); 0 missing | Pass | W8a, W8b, W13a |
| Credit-card transactions against Lithic | 784,953 Lithic transactions on the credit-card program with their last event before 6 October 2026 (779,946 tagged credit; 5,007 from before April 2026 with no program) | 741,801 match to the cent, 477,787 of them moved money: −$12,906,618.68 settled. 43,109 have no flow and Lithic shows zero. 43 differ, on 39 accounts, each on its own native flow: the ledger has −$1,663.94 where Lithic's final amounts come to −$3,306.53, $1,642.59 in the customers' favour. Each of the 43 flows also carries rows that are not Lithic events of its transaction. First events 15 January – 22 April 2026, last ledger writes 26 January – 23 April 2026; none holds money. Holds: 39 settled transactions hold −$251.24, all inside the 31-day window; 130 pending authorisations hold −$7,044.01. | Minor | W39 |
| Galileo-era GA= flows that carry Lithic events | 58,157 GA= flows; 84,489 Lithic transactions (84,340 debit, 149 with no program), first event 22 April – 16 June 2026; ledger copy to 12 October 2026, 07:55 UTC | 0 of 58,157 differ: each GA= flow plus the native flows of its Lithic transactions charges Galileo's own settled amount plus Lithic's final amount, −$1,689,088.96 in all (Galileo's −$770,733.77, Lithic's −$918,355.19). All 84,489 tokens found, none masked, none on two GA= flows. 0 GA flows and 0 native flows hold money. Journal rows on 147 GA= flows carry more than one account id: on each, the Galileo rows belong to one account and the Lithic transactions to another, and each account matches. 2,648 of the transactions have no native flow row. | Pass | W40, W41b, W44 |
| Settlement-report rows in the journal | Type-5 rows on card flows keyed by a uuid5 token that is in no Lithic event, September 2025 to 12 October 2026; the rows of the 43 credit-card flows that differ | 273,695 on native card flows, 11 November 2025 to 25 April 2026: November 166, December 158, January 2,185, February 7,497, March 20,285, April 243,404. 28,267 on 28,234 GA= flows, 23–25 April 2026. None after 25 April 2026. Each of the 43 credit-card flows that differ carries such rows, 87 in all: on 40 the last row is one, and on 3 an AuthExpiry row written after it carried its amount forward. | Info | W43, W45, W46 |
| Card transactions on flows that also carry other rows | 52,725 transactions, Apr–Sep 2026 (50,378 in April) | 52,723 sit on Galileo-era GA= flows that be-lithic's routing wrote them onto (April 50,378, May 2,218, June 127, none since). Those flows also carry Galileo's own rows, settlement rows and type-7 correction rows. On 23 April, 20:55–21:26 UTC, settlement rows credited 24,642 GA= flows carrying Lithic events $688,433.79, and the same amounts were debited back on 24 April. Each of these GA= flows, with the native flows of its Lithic transactions, charges Galileo's own settlement plus Lithic's final amount (W41b). Of the other 2 transactions, one is the July masked-token artefact (5,376 Lithic detail rows from 24–29 July carry the token "******", OPS-2) and one is a pending August transaction whose flow carries one extra AuthClearing row, −$1.85. The per-account check (W13b) was inconclusive because its expected amount left out returns. | Info | W13b, W13d, W15, W20d, W20e, W20f |
| Holds still open after Lithic settled | Card transactions to 5 October 2026: debit (W35), credit (W39) and the Lithic transactions on GA= flows (W40) | Debit: 1,331 settled transactions still hold −$10,733.58, all from September and October, inside Lithic's 31-day hold on a partial clearing's remainder; none from earlier months. Credit: 39 settled transactions hold −$251.24, last event 11 September – 5 October, all inside the same window. GA= flows: 0 GA flows and 0 native flows hold money. Authorisations Lithic still shows pending hold −$59,456.46 (3,338 debit) and −$7,044.01 (130 credit). | Pass | W35, W39, W40 |
| Lithic, October to date | Transactions first seen 1–9 Oct 2026 | No difference. With the ledger copy ending at 09:55 UTC on 9 October, warehouse lag explains all but 21: 948 that differ and 131 with no ledger flow had their last Lithic event after that time. Of the 21, 1 cleared with the next load. On the other 20, two clearing events share a timestamp and the per-event reading took the smaller; each ledger flow equals Lithic's final amount, 18 equal the settlement report, and 2 settled after its last load on 8 October, 14:40 UTC. | Pass | W13c, W31 |
| Authorisation events that moved settled money (the CL-1 signature) | 64,977,107 authorisation events, Mar 2025–Oct 2026 | 0 with the signature. 476 in Mar–Apr 2025 came from Galileo balance overrides (+$27,515.24 net). | Pass | W11 |
Related
Does it match what the callers moved?
April–September 2026: 64,163,747 journal rows moved money. 58,871,124 belong to a be-transactions record. 5,292,623 sit on Galileo flows, whose identifiers do not map to be-transactions keys: 5,284,186 in April, the cutover month, 249 in May and 8,188 in September. In reverse, every be-transactions record without a journal row is accounted for: Galileo-era records, whose GA= journal rows were compared by daily totals (1–22 April: 2,347,323 settlement debits against 2,347,334; April–May: 154,448 credits and money loads against 154,448, +$23,340,081.15 on both sides), Global wallet movements, which settle on-chain and never reach be-ledger, and records that moved no money. Of 24,179 settled movements on records marked Declined or Cancelled, 21,003 reverse the flow as the status describes, 2,997 sit on Galileo-era GA= flows that carry Lithic events, which match Galileo's own settlement plus Lithic's final amount (W41b), 163 are one account's declined ATM transactions that end at zero as Lithic's DECLINED status says, and the other 16 were traced: 11 are correct today and 5 are the Visa stand-in transactions of CL-27 (W30). The databases of be-pspfunding, be-phoneplan and be-mtu were not queried (CL-2, CL-3).
Checks behind this answer
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Every journal row that moved money has a be-transactions record | 64,163,747 money-moving journal rows, Apr–Sep 2026 | 58,871,124 have a be-transactions record; 2 of them were checked in be-transactions itself, as its warehouse copy lacks them. 5,292,623 are on Galileo flows, whose identifiers do not map to be-transactions keys. No movement was posted twice. | Pass | W14a, W14c |
| Settled movements on records be-transactions marks Declined or Cancelled | 24,179 journal rows that changed a settled amount, Apr–Sep 2026: 3,175 on Declined records (3,152 in April), 21,004 on Cancelled records | 21,003 of the 21,004 on Cancelled records set the flow's settled amount to zero, the reversal the status describes: 15,556 of the 15,563 on Lithic events end their flow at zero, and the 5,437 in the cutover week (22–30 April) were re-charged by later rows, every such flow ending equal to Lithic's final amount. On Declined records: 2,700 Lithic declines written onto GA= flows reset a settled Galileo flow and credited $52,331.87 when they were posted, and 297 settlement-report and uuid4 rows of 23–25 April net +$1,805.25; all sit on GA= flows that carry Lithic events, which match Galileo's own settlement plus Lithic's final amount (W41b). 163 are one account's declined ATM transactions: on 22 April 7,983 declined RETURN_REVERSAL events were posted as settlement rows totalling −$319,320.00, and on 24 April the declined RETURN events reset each flow to zero (+$317,440.00), matching Lithic's DECLINED status. The other 16, with the one on a Cancelled record, were traced (W30): 11 are correct today, nine rows from April on 6 flows, one from May and one from June on a GA= flow; 5 are Visa stand-in transactions from July and August that end at zero, as Lithic's transaction record does, while its settlement report lists them as settled (CL-27). | Minor | W14e, W16, W18, W19, W30 |
| Every be-transactions record has its journal row | be-transactions records with no journal row by key, Apr–Sep 2026 | All accounted for. 8,522,798 card records on 1–21 April, 284,516 on 22–30 April and 394 May reversals (the last on 23 May), plus 133,701 April money loads ("pm="), are Galileo-era records: their journal rows sit on GA= flows under Galileo's identifiers, so keys cannot match and they were compared by daily totals. On 1–22 April: 2,347,323 settlement debits (−$108,684,547.20) against 2,347,334 GA= settlement debits (−$108,693,864.77); 1,664,262 completions against as many AuthClearing rows. April–May: 154,448 credits and money loads against 154,448 GA= credits, +$23,340,081.15 on both sides. On 15 April, 98,854 of 98,856 settlement debits pair with a GA= row of the same settled amount within 2 minutes; the other 2 match on amount but not on settled change. Authorisations stop after 22 April; the tail is settlements of earlier authorisations to 29 April and reversals that release holds. 523 peer-to-peer credits still in PreAuth (+$43,960.05) moved no money. The rest are 258,902 zero-amount records and Global wallet movements, which settle on-chain in USDC and never reach be-ledger: deposits, disbursements and MPay Global transfers (9,250 in April, then 26,766 to 67,862 a month) and the recipient legs of MPay transfers between Global wallets ("r=", 2,261, +$196,884.62). On 15 September every internal record matched (W17). | Pass | W14d, W14f, W17, W18, W18b, W20, W20b, W20c |
| Stale uuid4 flow reserves against be-transactions | The 813 flows with uuid4 ids among the stale flow reserves (P23), looked up in dbt.fact_transaction by transaction flow id, transaction id, external transaction id, original transaction id and external original transaction id | 51 match a record. The 48 holds of 11–12 December 2025, −$1,072.23, are reservations in status PreAuth that never completed: 32 PeerToPeerDebit (MajorityPayAstra), 8 MobileTopUp, 7 Call and 1 InternationalTransfer; none is in status Initiated. 2 InternationalTransfer holds, February (−$20.01) and March 2026 (−$56.97), were reversed in be-transactions (Reversal, Approved) and the ledger kept the hold. The +$45.00 credit reserve of 8 December 2025 is an AdminCredit in status PreAuth. 762 match no record on any key: 757 from May to September 2025 (647 holds released in the balance, −$12,604.49, and 110 offsetting credits, +$7,920.64), 4 credit reserves from May and June 2025, and one hold from January 2026 (−$6.96). The warehouse holds 17.1M be-transactions records for May 2025. In the ledger, 761 of the 762 have the exact shape of the rows be-transactions' reconcile handler writes (P29, CL-30). | Minor | W47 |
Related
Does the event stream match the journal?
Prod publishes each leg's event before the posting commits. When a later leg breaks a balance rule, the posting rolls back but the earlier leg's event is already out. All 6 traced cases are transfers into an account at its $10,000 balance cap. be-transactions answered 400 "Transaction is not allowed." and no error was logged. Since February, 1,595 such events have reached be-accountstatements, audit and the warehouse. From February on, the events with no journal row include 1,593 Settlement-type ids whose debits total −$1,722,060.87; 2 of them carry a $0 event. Master's outbox (443b6bc) rolls the event back with the posting; prod runs the 9 September build.
Checks behind this answer
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Ledger events against the journal | Every ledger event and journal id, Sep 2025–7 Oct 2026 | 1,597 event ids with no journal row: 1,595 for postings that rolled back, 2 for rows the warehouse export skipped. 1,594 are Settlement-type, 2 of them carrying a $0 event and the rest debits, and 3 are card events; none is a Settlement-type credit. All 6 traced in the logs are transfers whose credit leg broke the recipient's balance cap. 13 journal rows with no event; 2 with two, and one phantom of July 2026 with two. | Fail | W9, W10, W13e, P3, Elastic |
| Phantom event ids looked up in prod | 1,553 ids, Oct 2025–Sep 2026 | 1,551 of the 1,553 ids are absent from prod, each looked up within two days of its event. The 2 found are ML=276715951 (10 October 2025, type 5) and ML=359981372 (24 March 2026, type 4), the rows the warehouse export skipped (DB-10). The 44 phantom events from 1–7 October 2026 were not in the list. | Pass | P22 |
Related
All checks
Every check this audit ran, grouped by the question it answers. P: a query on prod-ledger-use2-sqlDb or its read replica. W: a query on the dw-prod-gwiiag warehouse. Elastic: minority-backend-prod logs.
Do balances follow from the journal?
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Settled-balance chain: each journal row's settled balance (available minus reserved) equals the previous row's plus its own settled movement | 104,773,210 journal rows, 1 Mar–9 Oct 2026 | 0 breaks | Pass | W7a |
| The same chain, earlier months | Sep 2025–Feb 2026 | 23 breaks (Nov 1, Dec 2, Jan 8, Feb 12) on 16 accounts, net −$701.98. The 349 in Sep 2025 are where the warehouse copy starts. | Minor | W7a, W7c |
| Balance equals its last journal row | 193,317 balances written 1–9 Oct 2026 | 193,317 equal in account, amounts and time | Pass | P17 |
| Every balance against its journal row | All 1,544,133 balances, 12 October 2026 | The 676,914 balances that name their last journal row, all written in 2026, match it: 0 without the row within 5 seconds, 0 with a different snapshot, account or time. 867,219 name no journal row: 538,704 have no balance time, 200,822 were last written in 2025 and 127,693 in 2026; 147,527 of these are opening rows of accounts that never transacted. | Pass | P21 |
| Flow state against the journal | 2,335,686 flows touched in one week | 0 inconsistent: 2,271,413 with one matching journal row, 64,273 closed holds at zero with no row | Pass | P5 |
| Balance equals the sum of its flows, per account | Every account with a balance or a flow: 1,544,134 on 12 October 2026, read under one snapshot | Opened from April 2025: 535,691 of 536,839 match in both available and reserved. 1,148 differ in available, +$59,717.83 net: 1,111 opened in April 2025 (+$55,186.63; 336 by exactly −$3.95, opened 1–8 April) and 37 opened later (+$4,531.20), 30 of them through a reserve difference and 7 in available only, among them 6b6afc2c-3156-48fb-8fbb-ac4e14564512 at −$973.00 (DATA-1). Opened before April 2025: available differs on 348,378 of 1,007,294, +$47,033,607.61 net, +$40,585,634.28 of it the balance held before the first journal row in write order (P24b); here only their reserves are compared. Reserves differ on 740 accounts, +$19,398.31: 702 by exactly their released stale holds (+$20,610.06) and 38 others (−$1,211.75), each traced (P26). 845 flows sit on the empty account id, +$20,739.61, with no balance row. 0 flows with a null account, 0 flows without a balance row, 0 balances without an account row, 0 accounts moved during the scan. | Minor | P4b |
| Balances against flows, with each account's opening balance in write order | All 1,544,144 balances on 12 October 2026, by the month the ledger first saw the account; opening balance from each account's first journal row in TransactionId order between 1 March and 30 April 2025: its settled balance less its own settled amount | Opened before April 2025, 1,007,294: available differs from the flows on 348,378, +$47,033,607.61 net. The opening balance is +$40,585,634.28, non-zero on 347,089; on 915,832 accounts the settled difference equals it to the cent. 66,927 differ by +$6,434,523.47 more than their opening (+$6,465,947.34 and −$31,423.87): 463 by $1,000 or more (+$600,895.35), 16,374 by $100 to $1,000 (+$4,389,185.91), 48,833 by $1 to $100 (+$1,443,862.62), 1,257 by less than $1 (+$579.59). Reserves +$14,993.26 on 698. 55 of 3,913 with no journal row before May 2025, −$1,618.36; 16 of 20,622 with no flow, +$74.96. Opened in April 2025, 29,822: 1,111 differ, +$55,186.63: settled +$54,819.50 on 1,102, reserve +$367.13 on 10. By the first journal row written: 336 a PM= deposit, each $3.95 below its flows (−$1,327.20); 687 an FE= fee, with an amount already in the balance (+$48,917.21); 76 a PMA= credit (+$7,103.42); 1 a PM= deposit (+$100.00); 11 with no opening balance, 2 of them +$26.07 and 9 by reserve (+$362.13). Opened from May 2025, 507,028: 37 differ, +$4,531.20, $4,037.92 of it in the reserve of 32. | Partly | P24b |
| Balance history replayed against the journal | 231 accounts: 221 opened before April 2025 with large differences beyond their opening balance, either way, and 10 accounts this audit names, 5 of them opened in April 2025. Every balance version since their first journal row, 293,609, against every journal row and flow of the account | On all 231 the settled difference beyond the opening balance equals the sum of the breaks, the journal rows at which the balance moved by a different amount than the row's own flow: 1,060 rows, +$175,406.61 (+$184,841.99 on 1,050, −$9,435.38 on 10). Each break is the first journal row of its flow, and 1,055 were written 20–31 March 2025; at 940 the balance did not move at all (+$154,461.64). Example: on 7d3a81d0-3754-4dc1-9e18-06fdbd1401b5, P=15832737, the first row of GA=129718840, set the flow's settled amount to −$953.74 on 21 March 2025 at 15:32 UTC while the balance's settled amount stayed at $178.27. 5 breaks are later, −$877.61: 3 balance writes of June and July 2025 under the external id of an existing journal row of the account (−$2,677.61); an authorisation of 2 April 2025 that put a $600.00 hold on its flow while the balance's available did not move (+$600.00 settled); and a +$1,200.00 credit of 18 September 2025 whose flow is on the empty account id. 637 flows dated April to October 2026 have no journal row, all at zero. 9 GA= holds on 4 accounts, −$1,819.00, were cleared on the flow on 8 December 2025 at 14:00:02 UTC with no journal row. 1,088 rows moved the balance's reserve by an amount their flow did not, +$1,929.66 net, in every month from March 2025 to October 2026, consistent with holds placed before their journal row (DB-7). | Info | P27 |
| First journal rows of the April 2025 accounts | The 1,111 accounts opened in April 2025 that differ from their flows, and 300 accounts opened 1–8 April 2025 that do not: each account's first eight journal rows in write order between 1 March and 31 May 2025, and all its flows | On all 1,100 with a non-zero opening balance, the opening equals, to the cent, the amount of the next one to three journal rows written on the account, stamped within 5 seconds of the first, and each of those rows moved the balance by its own amount: 687 PM= deposits after an FE= fee (+$48,917.21), 336 FE= fees after a PM= deposit (−$1,327.20), PMA= credits on 76 accounts, one row on 71 and two or three on 5 (+$7,103.42), and 1 PM= deposit (+$100.00). All 1,100 were opened 1–8 April 2025, and their first rows were written 1–18 April. None of the 300 has a non-zero opening balance. On 238, a Settlement on a new flow with a random external id and no reserve reversed exactly the doubled amount among the first eight rows, 2.8 hours to 55 days later (+$10,228.27 net); on 862 (+$44,565.16 net), none of the first eight rows does. 106 of the 336 fee accounts also have an AD= row of +$3.95, written 8 to 35 seconds after the doubled fee, not traced and not counted as a reversal. The 2 accounts at $0.00 and the 9 that differ in reserve have no opening balance. | Minor | P30 |
| Later journal rows of the April 2025 accounts | Every journal row since March 2025 on the 308,236 flows of the 1,100 April 2025 accounts with a movement counted twice: 501,336 rows after those P30 read, or, for the 238 reversed among them, after the double count | The test finds all 238 known reversals. On every one of the other 862, a posting of the same shape later reversed the doubled amount: 859 with one posting of exactly that amount, 3 with two that together come to it. Across the 1,100, the reversal came 2.8 hours to 88.5 days after the double count, half within 10 days; 51 were written on 4 July 2025 between 10:55 and 14:57 UTC. Each is a Settlement that is the only row on a new flow, with a GUID external id and a different GUID idempotency key; 98 later rows of the same amount and single-row shape, from December 2025 on, carry their external id as the key and are ordinary postings. No account has a second reversal. On 1,099 accounts the available balance differs from the flows by exactly the doubled amount; on the other, by $5.00 more, a reserve that another posting of this shape put on a new flow on 10 May 2025. | Minor | P31 |
| Flow reserves left after their transaction ended | Every flow dated before 4 September 2026 that holds a reserve, 12 October 2026 | 1,888 flows, dated 20 March 2025 to 24 March 2026; none is a native Lithic flow. 810 GA= flows on the empty account id: 798 credits, +$16,661.14, and 12 holds, −$671.08. 1,078 on 864 customer accounts, −$21,354.01 net: 54 holds on 47 accounts keep −$1,226.57 unavailable; 727 holds on 702 accounts, −$20,610.06, are released in the balance but not on the flow; 182 GA= holds on 110 accounts, −$7,920.64, are offset to the cent by 110 credit reserves; 5 credit reserves, +$482.62, 2 of them carried by the balance. | Minor | P23 |
| Stale uuid4 flow reserves in the ledger | The 762 uuid4 flows among the stale flow reserves that match no be-transactions record (W47), with every journal row on them | Each flow has one journal row, on the flow's own account, with an idempotency key, and the flow equals its row. 761 are Settlements of 0 with nothing settled, whose reserve is the negative of the account's reserve just before, and after which the account's reserve is 0: the shape be-transactions' reconcile handler writes (CL-30). 647 are holds, −$12,604.49, on accounts whose reserve was positive; 110 are credit reserves, +$7,920.64, that offset GA= holds; 4 are credit reserves, +$437.62. Written May 2025 751, June 3, July 2, September 5. The other is a type-1 hold of January 2026, −$6.96, that the balance carries. | Minor | P29 |
| Reserve differences replayed row by row | The 27 accounts whose reserve differs from their flows with no stale hold, uncarried credit reserve, DATA-1 break or FixGalileoExp balance write behind it: every flow and every journal row since 1 March 2025, 29,689 rows, in TransactionId order | All +$495.75 traced to the cent. 29 rows on Galileo-era GA= flows, March 2025 to February 2026, moved the balance's reserve by an amount their own flow did not, +$170.95. 8 balance writes with no journal row of their own, +$414.08: 7 of 30 June – 4 July 2025 (+$20.10) and the FixGalileoExp write on 50f08b14 of 8 December 2025 at 13:59:57 UTC (+$393.98); the replay counts each at the next journal row, an AD= row, and each AD= row moved the balance by exactly its own amount (P28). 3 rows on other flows, +$399.01. 16 holds on 10 accounts cleared on the flow with no journal row, all updated on 8 December 2025 at 14:00:02 UTC, −$488.29. One hold reserved on another account's balance and released on its own, ±$53.98. Settled side, all in 2025: at 26 settlements on 20–21 March 2025, each the first journal row of its GA= flow, the balance did not move (+$579.70); balance writes with no journal row of their own on 30 June – 4 July 2025 moved it by −$533.17 net; and between 1 and 4 July 2025 $393.98, the amount of another flow's hold, moved from the reserve into the settled balance of 50f08b14. On each account these make up the whole difference beyond the opening balance. The latest break that persists is from 7 February 2026. | Info | P26 |
| AD= rows against the balance history | Every journal row with an AD= external id, 232,263, and the balance versions around each of the 482 of one cent | All 232,263 are Settlements, each on its own flow and with an idempotency key, written on five days: 30 June 2025 51,206 (−$3,992,153.65), 3 July 56,081 (−$5,675,369.90), 4 July 55,641 (−$4,635,195.52), 18 August 53,900 (−$3,988,021.32) and 9 December 15,435 (−$1,284,598.21), −$19,575,338.60 in all. 482 are of one cent, 289 of −$0.01 and 193 of +$0.01; on the 475 with balance versions on both sides, the balance moved by exactly the row's own amount. Where a row-by-row replay counts a break at an AD= row, the balance moved before the row, by a write with no journal row of its own (P26, P27). | Pass | P28 |
| Flows on the empty account id, and the table catalog | Every flow whose account id is 00000000-0000-0000-0000-000000000000, with its journal rows since 1 March 2025; every table and procedure in the ledger database, with row counts and dates | 845 flows, 840 GA= and 5 uuid5, settled +$4,749.55 and reserved +$15,990.06, last written March 2025 226, April 514, May 72, June 2, August 21, September 1, October 4, November 5; the newest on 10 November 2025, 22:46:55 UTC. 30 GA= flows carry one journal row each, a PMA= Settlement with an idempotency key, +$4,749.55 in all; each row names a different customer account, and on 6 of them the account's balance is measured to hold the credit (P24b, P27). 815 have no journal row: 810 GA= flows, 798 credit reserves (+$16,661.14) and 12 holds (−$671.08), and 5 uuid5 flows at zero, all with whole-second times. Catalog: Account 1,544,210 rows, AccountBalance 1,544,144, AccountBalanceHistory 357,992,590, TransactionCore 294,938,436, TransactionFlowCore 193,661,587; LedgerCorrection 0; SystemAccountBalanceChange 0 and its history 7,615,896; AffectedAccountIds_20260424 58,537 (created 24 April 2026) and temp.ReservedReconcile 39,028 (created 15 June 2026), which no migration creates. AccountBalanceSaveAndGet and TransactionCoreInsertAndGet, created in February 2025, were last modified on 3 February 2026, the day their replacements were created. | Minor | P25b |
| Duplicate keys | Sep 2026 flow ids and idempotency keys in prod; every journal id in the warehouse | 0 duplicates | Pass | P6, P7, W7 |
| CreateAccount resets of a live balance | Since 1 Apr 2026 | 0 | Pass | P16 |
| Sub-cent amounts and identifier length | 3,126,209 journal rows, 21–27 Sep 2026 | 0 sub-cent rows; longest identifier 36 characters | Pass | P15 |
| Integrity counts | Future-dated flows, flows outside the lookup window, flows with no account, non-USD accounts, orphan balances, auto-created accounts | 0 future-dated flows, 0 flows before the lookup window, 0 flows without an account id (1 January 2025 – 11 October 2026), 0 non-USD accounts, 0 balances without an account. 66 accounts have no balance (DB-8); 538,704 balances have no balance time. 25,524 accounts were opened in September 2026, none auto-created. | Pass | P18r |
| CL-1 exposure | Accounts with an "Init" balance row or none | No account has an "Init" row. 66 accounts have no balance row (1,541,450 accounts, 1,541,384 balances): be-ledger auto-created all 66 on 3 February 2026 between 13:04:21 and 13:10:25 UTC. None has a journal row or a balance, and none of the 66 ids appears in the warehouse's ledger, account, card or transaction tables; the same search finds a known account id in each of the account tables (W37). An authorisation on one would be approved for any amount (CL-1). | Minor | P3, P20b, W33, W34, W37 |
| Negative balances | 808,704 accounts with journal rows | 11,635 negative, −$3,124,255.31: 9,894 CreditSecured (−$3,013,619.73), 1,670 CheckingPrimary (−$101,328.01), 22 ServicesClearing (−$9,112.51), 49 other (−$195.06). 0 CreditUnsecured accounts exist. | Info | W7f, W12 |
| Error 13535 on the balance history | be-ledger logs, last 3 months | 369 failed calls in the 3 months to 9 October 2026, the first on 12 July, all in the balance procedure; every one also hit the primary key, because the procedure falls through to an INSERT after the error (DB-9). 15 were declines, none wrongful; 2 stand-in approvals were recorded correctly. 1–7 October: 25 failed calls (2 authorisations, 17 card events, 6 be-transactions postings); retries not traced. | Minor | Elastic |
Does the ledger match Lithic?
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Debit card transactions against Lithic's settlement report | 36,913,385 debit card transactions, first and last Lithic event between 1 April and 5 October 2026; report loaded to 8 Oct 2026, 14:40 UTC | 18,811,746 match the report to the cent, −$653,366,606.49 net; 424 of them differ from Lithic's last event. 18,048,913 never settled and have no settled amount in the ledger. 5 differ: the Visa stand-in transactions of CL-27, where the ledger is $285.74 net below the report and equals Lithic's transaction record. Every report transaction that moved money has a ledger flow. On GA= flows: 39,500 with no report row carry money only on the GA= flow (−$736,599.27) and 0 on the native flow; 13,130 match on the native flow; 91 sit only on a GA= flow, and the 38 inspected are refunds. The 1,017 report transactions missing from the Lithic detail table: 1,002 match, 11 net to zero, 4 match on the native flow. The 4,918 flows the July masked token reaches: 2,370 covered by report transactions, 2,471 at zero, 77 credit-card flows equal to Lithic's final amount (−$1,510.83). Open holds: 1,331 settled transactions from September and October still hold −$10,733.58, none from earlier months; 3,338 authorisations Lithic still shows pending hold −$59,456.46. | Minor | W35–W38 |
| Lithic, per card transaction | 35,831,351 transactions first seen Apr–Sep 2026 | 35,778,218 match to the cent; 408 differ from the per-event amount and equal Lithic's settlement report (re-checked with signed amounts in W35); 52,725 on shared flows (next row); 0 missing | Pass | W8a, W8b, W13a |
| Credit-card transactions against Lithic | 784,953 Lithic transactions on the credit-card program with their last event before 6 October 2026 (779,946 tagged credit; 5,007 from before April 2026 with no program) | 741,801 match to the cent, 477,787 of them moved money: −$12,906,618.68 settled. 43,109 have no flow and Lithic shows zero. 43 differ, on 39 accounts, each on its own native flow: the ledger has −$1,663.94 where Lithic's final amounts come to −$3,306.53, $1,642.59 in the customers' favour. Each of the 43 flows also carries rows that are not Lithic events of its transaction. First events 15 January – 22 April 2026, last ledger writes 26 January – 23 April 2026; none holds money. Holds: 39 settled transactions hold −$251.24, all inside the 31-day window; 130 pending authorisations hold −$7,044.01. | Minor | W39 |
| Galileo-era GA= flows that carry Lithic events | 58,157 GA= flows; 84,489 Lithic transactions (84,340 debit, 149 with no program), first event 22 April – 16 June 2026; ledger copy to 12 October 2026, 07:55 UTC | 0 of 58,157 differ: each GA= flow plus the native flows of its Lithic transactions charges Galileo's own settled amount plus Lithic's final amount, −$1,689,088.96 in all (Galileo's −$770,733.77, Lithic's −$918,355.19). All 84,489 tokens found, none masked, none on two GA= flows. 0 GA flows and 0 native flows hold money. Journal rows on 147 GA= flows carry more than one account id: on each, the Galileo rows belong to one account and the Lithic transactions to another, and each account matches. 2,648 of the transactions have no native flow row. | Pass | W40, W41b, W44 |
| Settlement-report rows in the journal | Type-5 rows on card flows keyed by a uuid5 token that is in no Lithic event, September 2025 to 12 October 2026; the rows of the 43 credit-card flows that differ | 273,695 on native card flows, 11 November 2025 to 25 April 2026: November 166, December 158, January 2,185, February 7,497, March 20,285, April 243,404. 28,267 on 28,234 GA= flows, 23–25 April 2026. None after 25 April 2026. Each of the 43 credit-card flows that differ carries such rows, 87 in all: on 40 the last row is one, and on 3 an AuthExpiry row written after it carried its amount forward. | Info | W43, W45, W46 |
| Card transactions on flows that also carry other rows | 52,725 transactions, Apr–Sep 2026 (50,378 in April) | 52,723 sit on Galileo-era GA= flows that be-lithic's routing wrote them onto (April 50,378, May 2,218, June 127, none since). Those flows also carry Galileo's own rows, settlement rows and type-7 correction rows. On 23 April, 20:55–21:26 UTC, settlement rows credited 24,642 GA= flows carrying Lithic events $688,433.79, and the same amounts were debited back on 24 April. Each of these GA= flows, with the native flows of its Lithic transactions, charges Galileo's own settlement plus Lithic's final amount (W41b). Of the other 2 transactions, one is the July masked-token artefact (5,376 Lithic detail rows from 24–29 July carry the token "******", OPS-2) and one is a pending August transaction whose flow carries one extra AuthClearing row, −$1.85. The per-account check (W13b) was inconclusive because its expected amount left out returns. | Info | W13b, W13d, W15, W20d, W20e, W20f |
| Holds still open after Lithic settled | Card transactions to 5 October 2026: debit (W35), credit (W39) and the Lithic transactions on GA= flows (W40) | Debit: 1,331 settled transactions still hold −$10,733.58, all from September and October, inside Lithic's 31-day hold on a partial clearing's remainder; none from earlier months. Credit: 39 settled transactions hold −$251.24, last event 11 September – 5 October, all inside the same window. GA= flows: 0 GA flows and 0 native flows hold money. Authorisations Lithic still shows pending hold −$59,456.46 (3,338 debit) and −$7,044.01 (130 credit). | Pass | W35, W39, W40 |
| Lithic, October to date | Transactions first seen 1–9 Oct 2026 | No difference. With the ledger copy ending at 09:55 UTC on 9 October, warehouse lag explains all but 21: 948 that differ and 131 with no ledger flow had their last Lithic event after that time. Of the 21, 1 cleared with the next load. On the other 20, two clearing events share a timestamp and the per-event reading took the smaller; each ledger flow equals Lithic's final amount, 18 equal the settlement report, and 2 settled after its last load on 8 October, 14:40 UTC. | Pass | W13c, W31 |
| Authorisation events that moved settled money (the CL-1 signature) | 64,977,107 authorisation events, Mar 2025–Oct 2026 | 0 with the signature. 476 in Mar–Apr 2025 came from Galileo balance overrides (+$27,515.24 net). | Pass | W11 |
Does it match what the callers moved?
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Every journal row that moved money has a be-transactions record | 64,163,747 money-moving journal rows, Apr–Sep 2026 | 58,871,124 have a be-transactions record; 2 of them were checked in be-transactions itself, as its warehouse copy lacks them. 5,292,623 are on Galileo flows, whose identifiers do not map to be-transactions keys. No movement was posted twice. | Pass | W14a, W14c |
| Settled movements on records be-transactions marks Declined or Cancelled | 24,179 journal rows that changed a settled amount, Apr–Sep 2026: 3,175 on Declined records (3,152 in April), 21,004 on Cancelled records | 21,003 of the 21,004 on Cancelled records set the flow's settled amount to zero, the reversal the status describes: 15,556 of the 15,563 on Lithic events end their flow at zero, and the 5,437 in the cutover week (22–30 April) were re-charged by later rows, every such flow ending equal to Lithic's final amount. On Declined records: 2,700 Lithic declines written onto GA= flows reset a settled Galileo flow and credited $52,331.87 when they were posted, and 297 settlement-report and uuid4 rows of 23–25 April net +$1,805.25; all sit on GA= flows that carry Lithic events, which match Galileo's own settlement plus Lithic's final amount (W41b). 163 are one account's declined ATM transactions: on 22 April 7,983 declined RETURN_REVERSAL events were posted as settlement rows totalling −$319,320.00, and on 24 April the declined RETURN events reset each flow to zero (+$317,440.00), matching Lithic's DECLINED status. The other 16, with the one on a Cancelled record, were traced (W30): 11 are correct today, nine rows from April on 6 flows, one from May and one from June on a GA= flow; 5 are Visa stand-in transactions from July and August that end at zero, as Lithic's transaction record does, while its settlement report lists them as settled (CL-27). | Minor | W14e, W16, W18, W19, W30 |
| Every be-transactions record has its journal row | be-transactions records with no journal row by key, Apr–Sep 2026 | All accounted for. 8,522,798 card records on 1–21 April, 284,516 on 22–30 April and 394 May reversals (the last on 23 May), plus 133,701 April money loads ("pm="), are Galileo-era records: their journal rows sit on GA= flows under Galileo's identifiers, so keys cannot match and they were compared by daily totals. On 1–22 April: 2,347,323 settlement debits (−$108,684,547.20) against 2,347,334 GA= settlement debits (−$108,693,864.77); 1,664,262 completions against as many AuthClearing rows. April–May: 154,448 credits and money loads against 154,448 GA= credits, +$23,340,081.15 on both sides. On 15 April, 98,854 of 98,856 settlement debits pair with a GA= row of the same settled amount within 2 minutes; the other 2 match on amount but not on settled change. Authorisations stop after 22 April; the tail is settlements of earlier authorisations to 29 April and reversals that release holds. 523 peer-to-peer credits still in PreAuth (+$43,960.05) moved no money. The rest are 258,902 zero-amount records and Global wallet movements, which settle on-chain in USDC and never reach be-ledger: deposits, disbursements and MPay Global transfers (9,250 in April, then 26,766 to 67,862 a month) and the recipient legs of MPay transfers between Global wallets ("r=", 2,261, +$196,884.62). On 15 September every internal record matched (W17). | Pass | W14d, W14f, W17, W18, W18b, W20, W20b, W20c |
| Stale uuid4 flow reserves against be-transactions | The 813 flows with uuid4 ids among the stale flow reserves (P23), looked up in dbt.fact_transaction by transaction flow id, transaction id, external transaction id, original transaction id and external original transaction id | 51 match a record. The 48 holds of 11–12 December 2025, −$1,072.23, are reservations in status PreAuth that never completed: 32 PeerToPeerDebit (MajorityPayAstra), 8 MobileTopUp, 7 Call and 1 InternationalTransfer; none is in status Initiated. 2 InternationalTransfer holds, February (−$20.01) and March 2026 (−$56.97), were reversed in be-transactions (Reversal, Approved) and the ledger kept the hold. The +$45.00 credit reserve of 8 December 2025 is an AdminCredit in status PreAuth. 762 match no record on any key: 757 from May to September 2025 (647 holds released in the balance, −$12,604.49, and 110 offsetting credits, +$7,920.64), 4 credit reserves from May and June 2025, and one hold from January 2026 (−$6.96). The warehouse holds 17.1M be-transactions records for May 2025. In the ledger, 761 of the 762 have the exact shape of the rows be-transactions' reconcile handler writes (P29, CL-30). | Minor | W47 |
Does the event stream match the journal?
| Check | Population | Result | Status | Source |
|---|---|---|---|---|
| Ledger events against the journal | Every ledger event and journal id, Sep 2025–7 Oct 2026 | 1,597 event ids with no journal row: 1,595 for postings that rolled back, 2 for rows the warehouse export skipped. 1,594 are Settlement-type, 2 of them carrying a $0 event and the rest debits, and 3 are card events; none is a Settlement-type credit. All 6 traced in the logs are transfers whose credit leg broke the recipient's balance cap. 13 journal rows with no event; 2 with two, and one phantom of July 2026 with two. | Fail | W9, W10, W13e, P3, Elastic |
| Phantom event ids looked up in prod | 1,553 ids, Oct 2025–Sep 2026 | 1,551 of the 1,553 ids are absent from prod, each looked up within two days of its event. The 2 found are ML=276715951 (10 October 2025, type 5) and ML=359981372 (24 March 2026, type 4), the rows the warehouse export skipped (DB-10). The 44 phantom events from 1–7 October 2026 were not in the list. | Pass | P22 |
How a card transaction is recorded
Every card transaction lives on one flow: a row in TransactionFlowCore that holds the transaction's current settled and reserved amounts. Each event on the transaction writes one journal row in TransactionCore carrying the flow's new state, so a journal row stores an absolute snapshot, not a change.
How the balance moves
The account moves by the change in the flow's state: reserved by the change in the flow's reserved amount, and available by the change in settled plus reserved. A hold lowers available; a clearing turns the hold into a settled debit and leaves available where it was; an expiry releases the hold and raises available again.
Amounts are signed from the account's side: a purchase is negative in the ledger. Lithic reports a purchase as a positive amount, and its settled amount is cumulative per transaction.
Transaction types
| Type | Name | Meaning |
|---|---|---|
| 1 | Auth | A hold is placed or changed. |
| 2 | AuthExpiry | A hold expires and is released. |
| 3 | AuthExpiryReversal | An expiry is undone. |
| 4 | DeniedAuth | A declined authorisation. |
| 5 | Settlement | A settled debit or credit. |
| 6 | AuthClearing | A clearing: the hold becomes a settled amount. |
| 7 | Reconciliation | A correction, for example the rows with external id ga-repair:… on GA= flows. |
Native flows
A native flow belongs to one Lithic transaction. Its id is derived from the transaction token: uuid5(URL namespace 6ba7b811-9dad-11d1-80b4-00c04fd430c8, "minoritylithic/" + token). Its journal rows carry the Lithic event token as external id.
The authorisation decision (the ASA call) places the hold in AccountBalance and TransactionFlowCore without a journal row. The journal gets a type-1 row when Lithic's AUTHORIZATION event arrives, with the same state, so the balance moves once (DB-7).
Settlement-report rows
From November 2025 to April 2026 be-lithic also posted each row of Lithic's daily settlement report to be-ledger (SettlementDetailBroadcastAction, added in 7a08268f), keyed by the report row's own token, which appears in no Lithic event. On a native flow the row became a Settlement (type 5) that set the flow's settled amount to that row's amount with nothing reserved. From February 2026, rows for a GA= flow went through the Galileo service instead (9e8a7bbe).
Because a journal row is a snapshot, not a change, a transaction with more than one report row ended at the last row's amount instead of its total: a purchase cleared in two parts was charged one part, and a purchase voided by a return ended at the return's credit (credit-card example). be-lithic stopped settlement-report intake on 25 April 2026 (eec3d987) and removed the be-ledger call on 27 April (6d03928f); master posts no settlement report to be-ledger. From September 2025, card flows carry 273,695 such rows on native flows (November 2025 166, December 158, January 2,185, February 7,497, March 20,285, April 243,404) and 28,267 on GA= flows, and none after 25 April 2026 (W46).
GA= flows
A GA= flow is a Galileo-era flow, GA=<be-galileo flow number>. Galileo's own rows carry the Galileo authorisation id as external id, for example v=339095122.
From 22 April 2026, be-lithic looked up a Galileo flow by the Visa original transaction id. That id points at an earlier related transaction, so many Lithic transactions after the move to Lithic were written onto an existing GA= flow instead of a native flow of their own. be-lithic removed the routing on 13 August 2026 (29bb3754); the last such write was on 16 June 2026 (W40).
58,157 GA= flows carry Lithic events today. Besides Galileo's rows and the Lithic events, they carry settlement rows from 23–25 April 2026, which on 23 April credited 24,642 of them and were debited back the next day, and 30,576 of them carry a type-7 correction row with external id ga-repair:…, written from 3 September to 8 October 2026. 81,841 of their 84,489 Lithic transactions also have rows on their own native flow, some written as late as October 2026.
Such a flow is right when the GA= flow and the native flows of its Lithic transactions together charge Galileo's own settled amount plus Lithic's final amount, and hold nothing. All 58,157 are: together they charge −$1,689,088.96, Galileo's own −$770,733.77 plus Lithic's −$918,355.19, and none holds money (W40, W41b). See GA= flows.
Worked examples
Native Lithic flow: one purchase, two events
A normal card purchase on Lithic. The flow id is derived from the Lithic token, and each Lithic event writes one journal row with the flow's new state.
- Account
- ab266053-ef66-435b-b30c-aec536b49722
- Lithic token
- 0026001b-461d-461e-aee1-fe5462e68b0d
- Native flow
- 73111304-c09e-5521-a7a4-3216c99d8ff4
- Network
- VISA, Lithic status SETTLED
Lithic events
| Time (UTC) | Event | Result | Status | Settled | Network | Event token |
|---|---|---|---|---|---|---|
| 2026-10-06 02:23:12 | AUTHORIZATION | APPROVED | SETTLED | 48.21 | VISA | 2f748737-cbd1-49a5-baa9-90b5cc7958a7 |
| 2026-10-06 16:01:23 | CLEARING | APPROVED | SETTLED | 48.21 | VISA | 099ef4ed-88f9-43cf-a3b1-6694061d83e3 |
Lithic reports a purchase as a positive amount. Its detail table carries the transaction's settled amount on every event, so each row shows the final amount.
Native flow 73111304-c09e-5521-a7a4-3216c99d8ff4
Ends at settled −48.21, reserved 0.00. 2 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-10-06 02:23:12 | ML=450564885 | Auth (1) | Lithic event | −48.21 | 0.00 | −48.21 | −48.21 |
Lithic AUTHORIZATION 48.21. The flow holds 48.21; available falls by 48.21.External id 2f748737-cbd1-49a5-baa9-90b5cc7958a7 | |||||||
| 2026-10-06 16:01:24 | ML=450739912 | AuthClearing (6) | Lithic event | −48.21 | −48.21 | 0.00 | 0.00 |
Lithic CLEARING 48.21. The hold becomes a settled debit; available does not move again.External id 099ef4ed-88f9-43cf-a3b1-6694061d83e3 | |||||||
Settled and reserved are the flow's state after each row. Available change follows from the formula on How a flow is recorded. An amber mark is a row that is neither Galileo's own nor a Lithic event of the flow's own transaction: a Lithic event written onto a Galileo flow, or a settlement row. A green mark is a correction (type 7). "Posted later" means more than 7 days after Lithic's event.
Outcome
The customer paid 48.21 once, Lithic's settled amount.
| Balance check | Amount |
|---|---|
| Native flow, final settled | −48.21 |
| Ledger total | −48.21 |
| Lithic's final settled amount, as a debit | −48.21 |
| Expected total | −48.21 |
| Difference | 0.00 |
| Reserved at the end | 0.00 |
Worth knowing
- Settled and reserved on each row are the flow's state after the event, not the change.
- The authorisation decision placed the hold a moment before the first row, without a journal row; the AUTHORIZATION event then wrote this row with the same state (DB-7).
GA= flow: a new purchase written onto a finished Galileo flow
A Lithic purchase of 47.45 was written onto a Galileo flow that had settled 134.00 three weeks earlier. The flow did not move, so the purchase was not charged. On 7 September 2026 a type-7 correction row left the GA= flow at Galileo's 134.00, and the purchase was charged on its own native flow.
- Account
- d9530bc6-4111-4bb5-a984-d2b6b8b40479
- GA flow
- GA=253805296
- Lithic token
- 05fe779f-d459-42ef-a26e-0c04c7b43a47
- Native flow
- 7697aa4a-c799-5907-9848-565ca81a729d
Lithic events
| Time (UTC) | Event | Result | Status | Settled | Network | Event token |
|---|---|---|---|---|---|---|
| 2026-04-26 15:03:07 | AUTHORIZATION | APPROVED | SETTLED | 47.45 | VISA | e906b5f0-eae4-45bc-a09c-ba6a86c6d6ce |
| 2026-04-27 06:48:36 | CLEARING | APPROVED | SETTLED | 47.45 | VISA | c8898b29-425b-43e7-ae11-9f02dedf9645 |
Lithic reports a purchase as a positive amount. Its detail table carries the transaction's settled amount on every event, so each row shows the final amount.
GA flow GA=253805296
Ends at settled −134.00, reserved 0.00. 6 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-04-03 08:19:52 | ML=365397160 | Auth (1) | Galileo | −134.00 | 0.00 | −134.00 | −134.00 |
Galileo authorisation: holds 134.00.External id v=339095122 | |||||||
| 2026-04-04 03:44:55 | ML=365940404 | AuthClearing (6) | Galileo | −134.00 | 0.00 | −134.00 | 0.00 |
Galileo clearing notice: no change.External id v=339390841 | |||||||
| 2026-04-04 14:58:43 | ML=366125758 | Settlement (5) | Galileo | −134.00 | −134.00 | 0.00 | 0.00 |
Galileo settles: the customer is debited 134.00.External id v=339390841 | |||||||
| 2026-04-26 15:03:08 | ML=378656683 | Auth (1) | Lithic event | −47.45 | −134.00 | 0.00 | 0.00 |
Lithic AUTHORIZATION 47.45 for a new purchase lands on this flow. The flow keeps settled 134.00 and takes no hold.External id e906b5f0-eae4-45bc-a09c-ba6a86c6d6ce | |||||||
| 2026-04-27 06:48:37 | ML=379029589 | AuthClearing (6) | Lithic event | −47.45 | −134.00 | 0.00 | 0.00 |
Lithic CLEARING 47.45 on the same flow: no change. The purchase is not charged.External id c8898b29-425b-43e7-ae11-9f02dedf9645 | |||||||
| 2026-09-07 11:09:32 | ML=437742924 | Reconciliation (7) | Correction | 0.00 | −134.00 | 0.00 | 0.00 |
Type-7 correction: sets the flow to Galileo's own state, settled 134.00. It was already there, so the Amount is 0.External id ga-repair:ga=253805296 | |||||||
Native flow 7697aa4a-c799-5907-9848-565ca81a729d
Ends at settled −47.45, reserved 0.00. 1 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-09-07 11:09:32 | ML=437742925 | AuthClearing (6) | Lithic event, posted later | −47.45 | −47.45 | 0.00 | −47.45 |
Lithic's clearing is posted on the purchase's own native flow: the customer is debited 47.45.External id c8898b29-425b-43e7-ae11-9f02dedf9645 | |||||||
Settled and reserved are the flow's state after each row. Available change follows from the formula on How a flow is recorded. An amber mark is a row that is neither Galileo's own nor a Lithic event of the flow's own transaction: a Lithic event written onto a Galileo flow, or a settlement row. A green mark is a correction (type 7). "Posted later" means more than 7 days after Lithic's event.
Outcome
Today: 134.00 on the GA flow plus 47.45 on the native flow, Galileo's settlement plus Lithic's. From 27 April to 7 September 2026 the customer had paid 134.00 for purchases worth 181.45.
| Balance check | Amount |
|---|---|
| GA flow GA=253805296, final settled | −134.00 |
| Native flow, final settled | −47.45 |
| Ledger total | −181.45 |
| Galileo's own settled amount, from its Auth, Settlement and AuthClearing rows | −134.00 |
| Lithic's final settled amount, as a debit | −47.45 |
| Expected total | −181.45 |
| Difference | 0.00 |
| Reserved at the end | 0.00, 0.00 |
Worth knowing
- The two GA-flow rows from 26 and 27 April are Lithic events, recognisable by their GUID external ids; Galileo's own rows carry
v=…. - Until the native flow was posted, every check that reads only the ledger saw a normal settled flow here; only a per-transaction comparison with Lithic shows the missing charge.
GA= flow: a Lithic clearing absorbed by an open Galileo hold
The card was authorised on Galileo before the move to Lithic and cleared on Lithic after it. The clearing landed on the open Galileo hold without changing it, the hold expired two days later, and the purchase was not charged until 7 October 2026, when Lithic's clearing was posted on the transaction's own native flow.
- Account
- cef46d8b-b236-417c-ab39-f8de4e2dc766
- GA flow
- GA=258895457
- Lithic token
- 1aad325e-4a7b-4500-a2c4-3b8afbd1ff42
- Native flow
- 336d3689-6b7d-5378-9eaf-6e61a4254b48
Lithic events
| Time (UTC) | Event | Result | Status | Settled | Network | Event token |
|---|---|---|---|---|---|---|
| 2026-04-26 07:01:42 | CLEARING | APPROVED | SETTLED | 25.00 | VISA | d91c0746-234c-4bb9-b81b-39480d2f6555 |
Lithic reports a purchase as a positive amount. Its detail table carries the transaction's settled amount on every event, so each row shows the final amount.
GA flow GA=258895457
Ends at settled 0.00, reserved 0.00. 3 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-04-20 21:31:24 | ML=375350869 | Auth (1) | Galileo | −25.00 | 0.00 | −25.00 | −25.00 |
Galileo authorisation: holds 25.00.External id v=344656339 | |||||||
| 2026-04-26 07:02:10 | ML=378563971 | AuthClearing (6) | Lithic event | −25.00 | 0.00 | −25.00 | 0.00 |
Lithic CLEARING 25.00 lands on the Galileo flow as an AuthClearing with no change: still a hold, nothing settled.External id d91c0746-234c-4bb9-b81b-39480d2f6555 | |||||||
| 2026-04-28 09:01:50 | ML=379508965 | AuthExpiry (2) | Galileo | −25.00 | 0.00 | 0.00 | +25.00 |
Galileo's hold expires and the 25.00 is released. The purchase is not charged.External id v=344656339 | |||||||
Native flow 336d3689-6b7d-5378-9eaf-6e61a4254b48
Ends at settled −25.00, reserved 0.00. 1 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-10-07 12:44:30 | ML=451099329 | AuthClearing (6) | Lithic event, posted later | −25.00 | −25.00 | 0.00 | −25.00 |
7 October 2026: Lithic's clearing is posted on the native flow, and the customer is debited 25.00.External id d91c0746-234c-4bb9-b81b-39480d2f6555 | |||||||
Settled and reserved are the flow's state after each row. Available change follows from the formula on How a flow is recorded. An amber mark is a row that is neither Galileo's own nor a Lithic event of the flow's own transaction: a Lithic event written onto a Galileo flow, or a settlement row. A green mark is a correction (type 7). "Posted later" means more than 7 days after Lithic's event.
Outcome
Lithic settled 25.00 with the merchant. The customer paid nothing from April until 7 October 2026, when the native flow was debited 25.00. The GA flow ends at Galileo's own settled amount, 0.00.
| Balance check | Amount |
|---|---|
| GA flow GA=258895457, final settled | 0.00 |
| Native flow, final settled | −25.00 |
| Ledger total | −25.00 |
| Galileo's own settled amount, from its Auth, Settlement and AuthClearing rows | 0.00 |
| Lithic's final settled amount, as a debit | −25.00 |
| Expected total | −25.00 |
| Difference | 0.00 |
| Reserved at the end | 0.00, 0.00 |
Worth knowing
- This shape, a clearing written with no state change followed by Galileo's expiry, looks like a normal expired hold to every ledger-only check. Only a per-transaction comparison with Lithic shows the missing charge.
GA= flow: Galileo settled its own hold
A 36.40 Lithic purchase landed on a Galileo flow that Galileo then settled for its own 676.00. The GA flow rightly ends at −676.00, Galileo's settlement, and the Lithic purchase is charged on its native flow.
- Account
- a69842cf-ed6c-474a-9f54-5ec3f3f21c7a
- GA flow
- GA=259240113
- Lithic token
- f72089a5-2eec-43fd-9cc7-0d413345a6e0
- Native flow
- 28432a83-4dd2-55ea-ad6a-80e49ad71ec1
Lithic events
| Time (UTC) | Event | Result | Status | Settled | Network | Event token |
|---|---|---|---|---|---|---|
| 2026-04-22 13:54:43 | AUTHORIZATION | APPROVED | SETTLED | 36.40 | VISA | ffad741d-28f9-40b7-97cc-645350591146 |
| 2026-04-23 07:22:13 | CLEARING | APPROVED | SETTLED | 36.40 | VISA | 80b261ef-2b15-4c68-be96-f016945b4c07 |
Lithic reports a purchase as a positive amount. Its detail table carries the transaction's settled amount on every event, so each row shows the final amount.
GA flow GA=259240113
Ends at settled −676.00, reserved 0.00. 6 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-04-22 01:57:40 | ML=376004248 | Auth (1) | Galileo | −676.00 | 0.00 | −676.00 | −676.00 |
Galileo authorisation: holds 676.00.External id v=345032364 | |||||||
| 2026-04-22 13:54:44 | ML=376160193 | Auth (1) | Lithic event | −712.40 | 0.00 | −676.00 | 0.00 |
Lithic AUTHORIZATION 36.40 lands on this flow. Amount reads 712.40, but the hold stays 676.00.External id ffad741d-28f9-40b7-97cc-645350591146 | |||||||
| 2026-04-22 15:12:49 | ML=376250267 | Settlement (5) | Galileo | −676.00 | −676.00 | 0.00 | 0.00 |
Galileo settles its own hold: the customer is debited 676.00.External id v=345032364 | |||||||
| 2026-04-23 07:22:17 | ML=376648752 | AuthClearing (6) | Lithic event | −36.40 | −676.00 | 0.00 | 0.00 |
Lithic CLEARING 36.40 on the same flow: no change. The purchase is not charged.External id 80b261ef-2b15-4c68-be96-f016945b4c07 | |||||||
| 2026-04-23 21:09:33 | ML=376953101 | Settlement (5) | Settlement report | 36.40 | −639.60 | 0.00 | +36.40 |
23 April, 21:09 UTC: a settlement row keyed by a settlement-report token credits 36.40 (settled −639.60).External id 725d8dfe-7fc0-591d-9d5c-ccbe86e6b03c | |||||||
| 2026-04-24 03:19:18 | ML=377254097 | Settlement (5) | Other settlement row | −36.40 | −676.00 | 0.00 | −36.40 |
24 April: a type-5 row with a uuid4 key debits the 36.40 back (settled −676.00).External id 9fc79021-b894-42cf-b5ac-a28573961886 | |||||||
Native flow 28432a83-4dd2-55ea-ad6a-80e49ad71ec1
Ends at settled −36.40, reserved 0.00. 1 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-10-08 11:40:19 | ML=451512985 | AuthClearing (6) | Lithic event, posted later | −36.40 | −36.40 | 0.00 | −36.40 |
8 October 2026: Lithic's clearing is posted on the native flow, and the customer is debited 36.40.External id 80b261ef-2b15-4c68-be96-f016945b4c07 | |||||||
Settled and reserved are the flow's state after each row. Available change follows from the formula on How a flow is recorded. An amber mark is a row that is neither Galileo's own nor a Lithic event of the flow's own transaction: a Lithic event written onto a Galileo flow, or a settlement row. A green mark is a correction (type 7). "Posted later" means more than 7 days after Lithic's event.
Outcome
676.00 on the GA flow plus 36.40 on the native flow: 712.40, the two purchases together.
| Balance check | Amount |
|---|---|
| GA flow GA=259240113, final settled | −676.00 |
| Native flow, final settled | −36.40 |
| Ledger total | −712.40 |
| Galileo's own settled amount, from its Auth, Settlement and AuthClearing rows | −676.00 |
| Lithic's final settled amount, as a debit | −36.40 |
| Expected total | −712.40 |
| Difference | 0.00 |
| Reserved at the end | 0.00, 0.00 |
Worth knowing
- The 23 April credit and 24 April debit were one batch: settlement rows credited 24,642 GA= flows carrying Lithic events $688,433.79 between 20:55 and 21:26 UTC on 23 April, and the same amounts were debited back on 24 April (W20d–W20f).
- A check that expects every GA= flow with an open Galileo hold to end at zero gets this flow wrong. Galileo's own settled amount has to be read from its Auth, Settlement and AuthClearing rows; its expiry rows copy whatever the flow holds.
Credit-card flow: settlement-report rows overwrite a cleared amount
A 622.26 purchase on a credit card cleared in two parts of 311.13, and the flow was right. Later that day be-lithic posted Lithic's settlement report for it as two Settlement rows, each setting the flow's settled amount to one part. The flow ends at −311.13: the customer was charged half the purchase.
- Account
- 033e5017-2484-4d66-b20e-bea0ed6b20fe
- Lithic token
- 8eb962eb-507c-4a74-ae00-6e7a03aa815e
- Native flow
- 8254f594-1239-5eab-8a26-aac36e13749b
- Program
- Credit card, Lithic status SETTLED
Lithic events
| Time (UTC) | Event | Result | Status | Settled | Network | Event token |
|---|---|---|---|---|---|---|
| 2026-03-29 22:24:50 | AUTHORIZATION | APPROVED | SETTLED | 622.26 | — | 6dca8d8a-6ffb-44d7-a51e-dd897d01ebcc |
| 2026-03-31 02:04:12 | CLEARING | APPROVED | SETTLED | 622.26 | — | 97eccb5f-5a33-4121-a2be-3ed562f4ef3f |
| 2026-03-31 02:04:12 | CLEARING | APPROVED | SETTLED | 622.26 | — | 3a2dda13-7f8a-4cfd-85fb-4b2b67abb392 |
Lithic reports a purchase as a positive amount. Its detail table carries the transaction's settled amount on every event, so each row shows the final amount.
Native flow 8254f594-1239-5eab-8a26-aac36e13749b
Ends at settled −311.13, reserved 0.00. 5 journal rows.
| Time (UTC) | Journal row | Type | Written by | Amount | Settled | Reserved | Available change |
|---|---|---|---|---|---|---|---|
| 2026-03-29 22:24:51 | ML=363060650 | Auth (1) | Lithic event | −622.26 | 0.00 | −622.26 | −622.26 |
Lithic AUTHORIZATION 622.26: the flow holds 622.26, and available falls by 622.26.External id 6dca8d8a-6ffb-44d7-a51e-dd897d01ebcc | |||||||
| 2026-03-31 02:04:13 | ML=363713711 | AuthClearing (6) | Lithic event | −311.13 | −311.13 | −311.13 | 0.00 |
First CLEARING, 311.13: half the hold becomes a settled debit.External id 97eccb5f-5a33-4121-a2be-3ed562f4ef3f | |||||||
| 2026-03-31 02:04:14 | ML=363713732 | AuthClearing (6) | Lithic event | −311.13 | −622.26 | 0.00 | 0.00 |
Second CLEARING, 311.13: the flow has settled 622.26 and holds nothing, matching Lithic.External id 3a2dda13-7f8a-4cfd-85fb-4b2b67abb392 | |||||||
| 2026-03-31 18:05:09 | ML=364054917 | Settlement (5) | Settlement report | −311.13 | −311.13 | 0.00 | +311.13 |
Settlement-report row, keyed by a token that is in no Lithic event: sets the flow's settled amount to −311.13, one part's amount. Available rises by 311.13.External id 377776c4-ab0b-5d9e-bd73-2af07da61762 | |||||||
| 2026-03-31 18:05:09 | ML=364054943 | Settlement (5) | Settlement report | −311.13 | −311.13 | 0.00 | 0.00 |
Second settlement-report row: sets it to −311.13 again, so nothing changes.External id 3ab17304-1b38-563e-b5e6-19ff3727f334 | |||||||
Settled and reserved are the flow's state after each row. Available change follows from the formula on How a flow is recorded. An amber mark is a row that is neither Galileo's own nor a Lithic event of the flow's own transaction: a Lithic event written onto a Galileo flow, or a settlement row. A green mark is a correction (type 7). "Posted later" means more than 7 days after Lithic's event.
Outcome
Lithic settled 622.26, and the ledger charged 311.13. The flow was right after the two clearings and wrong after the settlement report: the customer was charged 311.13 too little.
| Balance check | Amount |
|---|---|
| Native flow, final settled | −311.13 |
| Ledger total | −311.13 |
| Lithic's final settled amount, as a debit | −622.26 |
| Expected total | −622.26 |
| Difference, in the customer's favour | +311.13 |
| Reserved at the end | 0.00 |
Worth knowing
- Settlement-report rows are written as absolute Settlement states like every journal row, so each replaces the flow's amount instead of adding to it.
- be-lithic stopped sending the settlement report to be-ledger on 25 April 2026 and removed the call on 27 April (
eec3d987,6d03928f). The 43 credit-card flows that differ from Lithic were all last written between 26 January and 23 April 2026 (Credit-card flows).
Data consistency
What is wrong in the data today, kept apart from the code that caused it. Since March 2026 the ledger's balances agree with its journal; these items are where the ledger, or a copy built from it, does not match the money, or where a measured difference has not been traced to its cause.
Open 8
| Item | Scope | Money | Status |
|---|---|---|---|
| Event copies that differ from the journal | 1,595 phantom events, one published twice; 13 lost and 2 doubled; 2 rows missing in the warehouse | −$1,722,060.87 of debits that never happened, in statements, audit and the warehouse | Open: deploy, then clean up |
| Stale flow reserves and account reserves | 1,078 flow reserves on 864 accounts, March 2025 to March 2026; reserves differ from flows on 740 accounts | −$1,226.57 unavailable to 47 customers; −$20,610.06 released in the balance but open on the flow | Open |
| Balances with no opening entry | 348,378 accounts opened before April 2025; 1,111 opened in April 2025 | +$40,585,634.28 recorded only in balances; +$6,434,523.47 on 66,927 accounts beyond their opening; +$55,186.63 on the April 2025 accounts | Open: partly traced |
| Credit-card transactions charged differently from Lithic | 43 transactions on 39 accounts, January to April 2026 | $1,642.59 charged less than Lithic settled: $1,571.47 on 40 settled, $71.12 credited on 3 voided | Open: needs a decision |
| Flows on the empty account id | 845 flows, the newest from November 2025: 30 with a credit row naming a customer, 815 with no journal row | +$20,739.61 on no customer's balance; the 30 credits, +$4,749.55, name their accounts | Open: 815 owners not traced |
| Visa stand-in transactions where Lithic's two records disagree | 5 transactions on 5 accounts, 25 July and 31 August 2026 | Ledger at 0, as Lithic's transaction record; its settlement report settles $287.73 of credits and a $1.99 purchase | Open: ask Lithic |
| Balance breaks before March 2026 | 23 rows on 17 accounts, November 2025 to February 2026 | Net −$701.98; one account $973.00 below its flows | Open: cause unknown |
| Accounts with no balance row | 66 accounts, auto-created 3 February 2026 | No money; an authorisation on one is approved for any amount (CL-1) | Open |
Checked and correct 3
| Item | Scope | Money | Status |
|---|---|---|---|
| Galileo-era GA= flows that carry Lithic transactions | 58,157 flows, 84,489 Lithic transactions, April to June 2026 | 0 differ: −$1,689,088.96, Galileo's own settlement plus Lithic's final amount, to the cent; none holds money | Checked |
| Settled movements on Declined or Cancelled records | 16 journal rows, April to September 2026 | 11 correct today; the other 5 are CL-27 | Checked |
| October transactions that differ from Lithic's last event | 21 transactions, 1–9 October 2026 | No difference: warehouse lag and a same-timestamp reading artefact | Checked |
Checked and clean
- Every balance follows from the journal since 1 March 2026: 0 breaks in 104,773,210 journal rows, and 193,317 of 193,317 balances written 1–9 October equal their last journal row.
- Every balance that names its last journal row matches it: 676,914 on 12 October 2026, all written in 2026 (P21).
- 535,691 of the 536,839 accounts opened from April 2025 match their flows in both available and reserved (P4b).
- On 915,832 of the 1,007,294 accounts opened before April 2025, the balance differs from the flows by exactly what the account held before its first journal row in write order, to the cent (P24b).
- Flow state matches the journal: 2,335,686 flows touched in one week, 0 inconsistent.
- Lithic's settlement report: 18,811,746 debit card transactions from April to 5 October 2026 match to the cent and none is missing; the only 5 native transactions that differ are the stand-in transactions of CL-27, which equal Lithic's transaction record. Per event, 35,778,218 of 35,831,351 transactions match, and the 408 that differ equal the settlement report. October to date has no difference.
- GA= flows: all 58,157 Galileo-era flows that carry Lithic transactions charge exactly Galileo's own settlement plus Lithic's final amount, −$1,689,088.96 to the cent, and none holds money; on the 147 with rows for two accounts, each account matches.
- Credit-card program: 741,801 of 784,953 Lithic transactions match to the cent, −$12,906,618.68 settled, and 43,109 more have no flow while Lithic shows zero. Only the 43 on Credit-card flows differ.
- be-transactions: every money-moving journal row has a record, apart from Galileo-era rows, whose keys do not map, and every record without a journal row is accounted for.
- No duplicate flow ids, idempotency keys or journal ids; no CreateAccount reset of a live balance since 1 April; no sub-cent amounts.
- No future-dated flow, no flow without an account id since 1 January 2025, no non-USD account and no balance without an account; none of the 25,524 accounts opened in September 2026 was auto-created (P18r).
- Card holds: every hold left after Lithic settled is from September or October, inside Lithic's 31-day hold on a partial clearing's remainder (1,331 debit, −$10,733.58; 39 credit, −$251.24), and 3,468 holds, −$66,500.47, belong to authorisations Lithic still shows pending. No GA= flow, and no native flow of its Lithic transactions, holds money.
- No flow dated between 25 March and 3 September 2026 holds a reserve (P23).
Event copies that differ from the journal
Statements, audit and the warehouse's event data are built from be-ledger's events, not from the journal. Where the event stream differs from the journal, those copies are wrong although the ledger is right.
What is wrong
- 1,595 events were published for postings that rolled back (EV-1). By month: February 224, March 105, April 171, May 198, June 150, July 211, August 249, September 243, 1–7 October 44. From February on they include 1,593 Settlement-type events whose debits total −$1,722,060.87, on 14 to 60 accounts a month; 2 of them carry a $0 event, and none is a credit. The counts are of transaction ids: one July phantom was published twice, so the copies hold 1,596 events.
- be-accountstatements, be-auditing and the warehouse consumed them. 8 be-accountstatements balances were still wrong from such events when checked; a statement balance stays wrong until the account's next event.
- 13 journal rows have no event (December 2025 and January 2026), and 2 have two (EV-2). Statements and audit miss the 13 movements and count the 2 twice.
- The warehouse's journal copy misses 2 rows that the export skipped: ML=276715951 (10 October 2025, type 5) and ML=359981372 (24 March 2026, type 4). Both are in prod (P22, DB-10).
Why the deploy comes first
- The code fix is on master and stage (the outbox,
443b6bc; the double-rollback fix,dbf9d4d) but not in prod, which runs the 9 September build. Until it is deployed every capped transfer adds a phantom event, so a cleanup before the deploy would be overtaken.
How to fix
- Deploy master first (EV-1), so no new phantom events are published.
- Rebuild the be-accountstatements balance of every account that received a phantom event, from the journal.
- Mark or delete the phantom rows of the 1,595 ids, 1,596 events, in the warehouse and in be-auditing. The ids come from W10 and W13e. P22 found 1,551 of them absent from prod; the other 44, from 1–7 October, were not looked up.
- Add the 13 missing movements and remove the 2 duplicates in be-auditing and the warehouse.
- Backfill the 2 journal rows the warehouse export skipped.
How to verify
- W9 and W10 over the whole period: every journal row has exactly one event, and every event has a journal row.
- For each affected account, the be-accountstatements balance equals the ledger balance.
- Prod and warehouse journal row counts are equal for every month (P3).
Related
Credit-card transactions charged differently from Lithic
On the credit-card program, 43 Lithic transactions end with a different settled amount in the ledger than in Lithic. All 43 are in the customers' favour, and all were last written between 26 January and 23 April 2026.
What is wrong
- Of 784,953 Lithic transactions on the credit-card program with their last event before 6 October 2026, 741,801 match the ledger to the cent and 43,109 have no flow while Lithic shows zero (W39).
- 43 differ, each on its own native flow: the ledger holds −$1,663.94 where Lithic's final amounts come to −$3,306.53.
- 40 are settled in Lithic and under-charged in the ledger, $1,571.47 together: 2 first seen in January (+$145.05), 4 in February (+$136.23), 11 in March (+$532.60) and 23 in April (+$757.59).
- 3 are voided in Lithic and still carry a credit in the ledger, $71.12 together: +$10.22 (February), +$14.87 (March) and +$46.03 (April).
- None holds money, and none is a stand-in transaction.
Cause
- All 43 flows carry settlement-report rows, 87 in all, and they set the final amount (W43): on 40 the last row is one, and on 3 an AuthExpiry row written after it carried its amount forward. be-lithic posted each row of Lithic's settlement report as a Settlement that set the flow's settled amount to that row's amount (How a flow is recorded).
8254f594-1239-5eab-8a26-aac36e13749b: a 622.26 purchase cleared in two parts; two report rows set the flow to −311.13 (worked example).2fce44c7-c9ab-58fe-9f0e-16bcc8fd9dc4: a 146.46 purchase cleared in three parts of 48.82; three report rows on 17 January 2026 set the flow to −48.82, and an AuthExpiry row on 26 January carried that forward.80e5826d-b212-54cb-8bac-c426e77a83f4: a 46.03 purchase voided by a return, which left the flow at zero on 15 April; report rows on 16 April set it to −46.03 and then to +46.03, a credit where Lithic shows nothing.- be-lithic stopped settlement-report intake on 25 April 2026 and removed the be-ledger call on 27 April (
eec3d987,6d03928f), two days after the last of these ledger writes. No settlement-report row has reached a card flow since 25 April 2026 (W46).
All 43 transactions 39 accounts
| Lithic token | Account | Native flow | Lithic status | Lithic, as a debit | Ledger | Difference | First event | Last ledger write |
|---|---|---|---|---|---|---|---|---|
| 8eb962eb-507c-4a74-ae00-6e7a03aa815e | 033e5017-2484-4d66-b20e-bea0ed6b20fe | 8254f594-1239-5eab-8a26-aac36e13749b | Settled | −622.26 | −311.13 | +311.13 | 2026-03-29 | 2026-03-31 |
| b3033dba-8524-4ab5-acf5-4277f7dee4a7 | eacc530f-5b12-4e3a-ac6c-aa175594637b | 8bdd26d7-315f-54b7-899d-1088ca9be833 | Settled | −170.57 | −33.47 | +137.10 | 2026-04-13 | 2026-04-16 |
| 7e39311e-7ea5-43f3-90d3-b6b492888f55 | ef1b4f28-61b7-48b3-86dd-5e0cb80f2d97 | 2fce44c7-c9ab-58fe-9f0e-16bcc8fd9dc4 | Settled | −146.46 | −48.82 | +97.64 | 2026-01-15 | 2026-01-26 |
| ea8bc682-878e-435a-ad6c-5cfe573b58f8 | 1798a81b-cc2c-4b94-a5ec-1a36c426ee00 | 3e825fad-48c7-5a5f-a753-a5cd8f989258 | Settled | 0.30 | 89.23 | +88.93 | 2026-03-26 | 2026-03-28 |
| 1ea61ce8-7a04-4ad8-920c-307cb3d30fa5 | 32b1eefd-bd01-4fc7-be20-2903287ea6e5 | af33d998-ef1d-5f35-89fd-8ca1592c07f2 | Settled | −99.37 | −15.09 | +84.28 | 2026-04-17 | 2026-04-19 |
| aa486e05-b890-4c85-a13f-7d6dd73c9e29 | 9420168f-e86b-404b-8865-e491b6da9cb9 | fefbdb28-a8f3-5722-ac42-d0a66cd2df1a | Settled | −152.58 | −87.19 | +65.39 | 2026-02-13 | 2026-02-14 |
| 883f4008-481b-422b-b072-c6ad35478119 | cc106c9e-19c1-4746-a814-e8bbc9955292 | 9033d76b-dcd5-51ff-bbb7-4ce4984691df | Settled | −74.21 | −9.22 | +64.99 | 2026-04-01 | 2026-04-02 |
| 5dffe8b3-cf38-4d6a-acd0-f033d7ad10e7 | 06511648-df85-4226-87da-846d2a64b137 | b58d2011-693d-5cee-909d-ad21de650228 | Settled | −87.53 | −25.65 | +61.88 | 2026-04-12 | 2026-04-15 |
| bddb5e2f-16c9-4fff-834b-7a5420e62c20 | 2ac5a0c2-7f08-4773-8a90-100d77f79d77 | 7ee0a57f-b45c-5210-84ea-9dc310c9b392 | Settled | −73.61 | −12.94 | +60.67 | 2026-04-07 | 2026-04-18 |
| 2e09834f-483e-48c6-b139-a9ae2204348f | 35c25068-77db-465f-8e7e-0fa1962cfb8d | 310af8dc-0c82-55d4-b8fc-71ebccfde60e | Settled | −81.65 | −34.24 | +47.41 | 2026-01-30 | 2026-02-03 |
| 17f7c2df-a46e-429b-99bf-184161a43d28 | 5e7174b4-c2ad-4232-810c-c7082175818d | 80e5826d-b212-54cb-8bac-c426e77a83f4 | Voided | 0.00 | 46.03 | +46.03 | 2026-04-15 | 2026-04-16 |
| 9b19f89d-0968-479f-b66f-c7464999e7d1 | cc106c9e-19c1-4746-a814-e8bbc9955292 | 020a3e6e-b78f-5930-9967-caa352dc6d83 | Settled | −50.52 | −8.01 | +42.51 | 2026-04-08 | 2026-04-19 |
| 14a1500b-0c79-4e92-9529-b78ad4629b5a | eb6da776-d3ec-469d-afa6-118563685653 | 0f4ffd3d-b440-5c5e-9167-fbb38dba4758 | Settled | −35.60 | −0.60 | +35.00 | 2026-02-07 | 2026-02-09 |
| d2c55ccf-d6d7-4c21-ba5c-3da3f8e9da8e | ea51debd-9265-4554-9cbf-a0554cde793d | 32e50fe5-2cad-5d8c-8fc5-91922d0036ac | Settled | −62.12 | −28.62 | +33.50 | 2026-04-13 | 2026-04-13 |
| b55d1e4f-c567-4286-8858-55d9457ab24e | 8b6faf24-3c46-40fe-88ae-67ddc66319ed | b9222a7f-9769-506f-91c3-bbef0f2cacab | Settled | −189.26 | −159.35 | +29.91 | 2026-04-18 | 2026-04-20 |
| 20995d5e-a381-430e-8e1a-aa6daf587f45 | 0e1cd7d4-6b35-4df8-b48b-f7a8ce37c8ad | 6382e099-9dd6-54ed-9de1-ed105fad4377 | Settled | −54.04 | −26.49 | +27.55 | 2026-02-24 | 2026-03-01 |
| 6f047dbb-e751-4bae-9f82-3a82b0936114 | 6c46bc6b-c6ca-442b-b096-9d7a46955a95 | 4341e46f-620c-5f7a-8334-59905002a28d | Settled | −31.30 | −3.99 | +27.31 | 2026-04-13 | 2026-04-14 |
| 29da7f5a-6c1e-4534-92ef-c7cf3ee1fcfc | 79413433-399b-4fad-a62a-a192275b6c1d | ec92dd31-4da6-51ae-9871-17d7e60fed39 | Settled | −54.09 | −27.91 | +26.18 | 2026-03-29 | 2026-03-31 |
| 40027a36-76ef-4065-ba48-47c35fd623fd | 400383e2-3b1b-4631-a6d8-9ee8396819ef | 6a87af0b-35fc-5b8e-8e4f-bfd3915687bc | Settled | −49.92 | −24.98 | +24.94 | 2026-04-22 | 2026-04-23 |
| e2943f59-7fac-4b9e-9279-88bf17453b52 | 1f487f49-5b3d-4073-a456-b358578231f7 | 934cbc8f-c47d-5a3a-8922-5b1de54124f3 | Settled | −81.83 | −60.85 | +20.98 | 2026-04-19 | 2026-04-21 |
| a1d0af6c-0edb-4bab-9164-9266b12f1039 | 6894f106-9e3b-4649-9d0f-a0e6e8b4feb9 | 05519e7a-60cb-5396-ab6d-76208cdf768c | Settled | −50.47 | −29.63 | +20.84 | 2026-04-19 | 2026-04-21 |
| ac2237b4-d5a5-4ede-856e-acdfee1b33c1 | d4f50a44-d0a4-4fcb-8f87-c008657cb9fb | 1719745a-9147-52c8-b191-870e094d187e | Settled | −63.07 | −43.19 | +19.88 | 2026-04-14 | 2026-04-21 |
| a17176e6-60cb-4c55-bd46-64ee372d75b2 | cc106c9e-19c1-4746-a814-e8bbc9955292 | 4c233b62-374d-5a96-bead-c6f0deca8f51 | Settled | −40.04 | −21.06 | +18.98 | 2026-04-16 | 2026-04-19 |
| c42ad043-34d4-4f43-9479-1e69e06327e3 | a507ee7d-6884-4aca-98da-c1181b4e7aea | 9abe61e5-df7a-531e-99e6-8cdfa5b51553 | Settled | −131.70 | −113.08 | +18.62 | 2026-04-16 | 2026-04-18 |
| dc48641e-bef0-44c4-9c2c-3715eda3e308 | ea0d9609-114b-464a-aa13-8a4b6363ad9e | bc16d05e-a1ac-5dd8-a092-1bd70a437d7c | Settled | −94.00 | −76.87 | +17.13 | 2026-03-12 | 2026-03-15 |
| ff0cfea3-7039-4c30-befe-8ca23a2de73a | f5bab280-b028-4493-ba6e-74be853d479e | e1df3526-e45b-5fca-8147-c5c6531fec8e | Settled | −45.48 | −28.55 | +16.93 | 2026-03-28 | 2026-03-29 |
| 18307d56-7564-4d85-a0d3-cd3cdcd8a20d | 70659499-e641-4269-8b69-9226ec3006ab | a70bf9f5-7085-5cad-abeb-66a30d75b41d | Settled | −26.09 | −10.00 | +16.09 | 2026-04-08 | 2026-04-09 |
| 84600dc1-e06c-4c63-a04d-35593b1bfdef | a58613b8-2a6c-4d24-8d2c-9469fb57dea9 | 829f0d51-f3fb-52a5-9770-49119c750d08 | Settled | −66.84 | −51.59 | +15.25 | 2026-04-15 | 2026-04-17 |
| 6fa9886c-bbf9-4b71-b673-0cfc64493455 | 1fd5523d-5460-449f-a589-f3a30f676750 | 4bd9fa25-e7f0-545a-99b3-4a695c11bb13 | Voided | 0.00 | 14.87 | +14.87 | 2026-03-23 | 2026-03-27 |
| d162ed34-eb70-41b3-b0fb-0d74cdfde0b5 | 57fc8503-6079-474f-b4fa-c256fa83e5ea | 8750f1c1-c32a-52b4-9661-9589624e2070 | Settled | −33.07 | −18.24 | +14.83 | 2026-03-07 | 2026-03-08 |
| a02315b7-cab6-44c1-9b0e-382c75587da8 | c57cfe58-0945-4f0e-a3f0-e945d1f3b2b1 | 30a22724-1844-5439-892f-6c91d7fc6f2a | Settled | −79.59 | −65.69 | +13.90 | 2026-03-29 | 2026-03-31 |
| f4dc1fd8-be72-4489-ad06-42a593416bb7 | 57fc8503-6079-474f-b4fa-c256fa83e5ea | 50643e6a-5b03-5d9e-8ac7-8cc4099d85eb | Settled | −34.38 | −20.82 | +13.56 | 2026-03-01 | 2026-03-01 |
| fdd6be31-f3da-4b7d-96cf-567f1eb2928f | e08bf5f1-860e-47f0-941b-3a362d6e3832 | 5288e686-7e64-5990-9130-93558c304386 | Settled | −101.46 | −87.97 | +13.49 | 2026-04-02 | 2026-04-04 |
| 76e1c70f-108f-41a1-bcab-8e5fb0ad6b27 | 11331045-2be2-43ed-9d7c-237e8703fc0f | 1c0d687d-5d0c-5b3a-bd91-e02d450aa96a | Settled | −66.53 | −53.38 | +13.15 | 2026-03-26 | 2026-03-27 |
| f1fd099b-36ec-4a9d-a959-dc28ff3be15b | 04b0d454-9203-4e65-be8b-ae40fbf0abab | c67c8a10-c055-5ef3-9d58-6a5b72d53de6 | Settled | −33.47 | −21.75 | +11.72 | 2026-04-01 | 2026-04-02 |
| a53132b8-91a9-4c43-ab2d-6ba23c32e85d | b7d9b522-f6ab-4271-bdb4-980ee76aea7d | 528f2ec9-2de2-5d5e-a10a-e9ef3d8dc56a | Settled | −59.51 | −48.70 | +10.81 | 2026-04-14 | 2026-04-15 |
| 6030b99e-c598-4807-a29f-3b4f42cc895f | 88db060b-8cd2-40c8-a581-580feb7cbcf3 | e18ab343-c256-546c-bf7f-fdd477504a47 | Voided | 0.00 | 10.22 | +10.22 | 2026-02-24 | 2026-02-28 |
| bc7b74aa-f9a1-4972-a3a8-8f0b3e71e718 | a507ee7d-6884-4aca-98da-c1181b4e7aea | 47e5cb4f-98d7-5b1f-a607-f86e78f05db2 | Settled | −53.73 | −43.74 | +9.99 | 2026-04-16 | 2026-04-17 |
| f4ab0de2-e8f6-4c88-9d4b-d605e0819b2d | b2009b46-f885-461b-8f79-1914693928d2 | c82899d3-2e5d-5d1a-914e-0bdaeb2f53d3 | Settled | −28.47 | −18.48 | +9.99 | 2026-04-17 | 2026-04-18 |
| 4f8a3c1e-09da-4c9d-97b9-5577153433ff | df4e80b7-0a67-4f9f-85ac-682dd909efa8 | 841e585f-5789-5ede-b6a6-602270ad926e | Settled | −36.36 | −26.38 | +9.98 | 2026-03-28 | 2026-03-31 |
| 27ff8ec0-7bcc-4d38-8436-14b58ab6e541 | 48171b2c-f70a-43a2-8fd9-5e484d6ef60c | 75176952-8b20-5767-9e37-ccecd804283a | Settled | −74.30 | −66.01 | +8.29 | 2026-02-17 | 2026-02-19 |
| 1012af17-eb95-404a-b511-1784810e146b | 9a3ea45c-6a73-48a2-9a1b-053ac84858a5 | fb51e849-deb6-5f55-91e0-5b137d8838cb | Settled | −64.40 | −57.52 | +6.88 | 2026-03-19 | 2026-03-21 |
| 8298bfee-fcf1-49b0-b056-a9afbae7c069 | 738d9f49-eb3b-4d5b-9704-accce300c9e9 | 80596b7e-5adf-51ec-b70f-14f188c501e1 | Settled | −6.95 | −3.09 | +3.86 | 2026-04-21 | 2026-04-21 |
| Total | −3,306.53 | −1,663.94 | +1,642.59 |
Lithic's final settled amount is shown with the ledger's sign, a purchase negative. Difference is the ledger minus Lithic; positive is in the customer's favour. Source: W39.
How to fix
- Decide with be-credit whether to correct the 43 flows: $1,571.47 is owed by 40 customers, and $71.12 was credited to 3.
- If yes: set each native flow to Lithic's final amount with one correction per flow, and make the matching change in be-credit and LoanPro, so the statement and the amount owed follow the ledger.
- If no: record the decision, so later reconciliations exclude these 43 flows.
- Reconcile the ledger against Lithic every day, so a difference like these is caught within a day (CL-27).
How to verify
- W39 returns 0 credit-card transactions that differ.
- LoanPro's balance for each of the 39 accounts reflects the change.
Related
Galileo-era flows that carry Lithic transactions
From 22 April to 16 June 2026 be-lithic wrote Lithic transactions onto existing Galileo-era GA= flows instead of a native flow of their own. Such a flow is right when the GA= flow and the native flows of its Lithic transactions together charge Galileo's own settled amount plus Lithic's final amount, and hold nothing. All 58,157 are.
What is measured
- W41b, on the warehouse's ledger copy to 12 October 2026, 07:55 UTC: for each GA= flow, the settled amount of the GA= flow plus that of the native flows of its Lithic transactions, against Galileo's own settled amount plus Lithic's final amounts. 0 of 58,157 differ. In all the ledger charges −$1,689,088.96: Galileo's own −$770,733.77 and Lithic's −$918,355.19.
- Galileo's own amount is read from its Auth, Settlement and AuthClearing rows in order; its expiry rows are skipped, because they copy whatever the flow holds. All 84,489 Lithic tokens are found, none masked, and none sits on two GA= flows (W44).
- 84,489 Lithic transactions sit on 58,157 GA= flows: 84,340 on the debit program and 149 with no program. Lithic's status: 43,183 declined, 41,099 settled, 129 voided, 78 expired. Their first events run from 22 April to 16 June 2026, and none is newer (W40).
- No hold is left: 0 GA flows and 0 native flows of these transactions have a reserved amount.
- Besides 148,775 rows of Galileo's own and the Lithic events, the flows carry 28,267 settlement-report rows on 28,234 flows (23–25 April 2026), 25,375 type-5 rows with uuid4 keys on 24,980 flows (24 April 2026), 30,576 type-7 correction rows with external id
ga-repair:…, one per flow (3 September to 8 October 2026), and one cancel row (17 June 2026). - The last row is a correction row on 30,576 flows, a Galileo row on 25,888, a Lithic event on 1,223 and another row on 470.
- 2,648 of the Lithic transactions have no row on a native flow; their only ledger rows are on the GA= flow.
- Against Lithic's settlement report (W35): 39,500 debit transactions with no report row carry money only on a GA= flow, −$736,599.27, and nothing on their native flow; 13,130 match the report on their native flow; 91 settled transactions sit only on a GA= flow, and the 38 inspected are refunds.
- On 23 April 2026, 20:55–21:26 UTC, settlement rows credited 24,642 of these GA= flows $688,433.79, and the same amounts were debited back on 24 April (W20d–W20f).
Flows with rows for two accounts
- Journal rows on 147 GA= flows carry more than one account id (DB-2). On each, the Galileo rows belong to one account and the Lithic transactions to another, the account that holds the Lithic card: 146 of these flows carry one Lithic transaction and 1 carries two.
- Checked per account, each matches: the Galileo account is charged Galileo's own settlement and the other account Lithic's final amount, with 0 differences (W41b).
How to fix
- No correction is needed: every GA= flow matches.
- Re-run W41b after any correction that touches a GA= flow.
- Scope the flow update to its account (DB-2), so a later event cannot move a flow to another account; 147 GA= flows already carry rows for two accounts.
How to verify
- W41b keeps returning 0 flows that differ.
- W40 keeps returning 0 GA flows and 0 native flows that hold money.
Related
Balances with no opening entry
The oldest journal row is from 20 March 2025, 12:11 UTC. What an account held before its first journal row is recorded only in its balance: no journal row or flow carries it. On accounts opened before April 2025 this opening balance explains most of the difference between balance and flows, not all of it. On accounts opened in April 2025, be-ledger took the first balance from its caller, and on 1,100 of them a deposit, fee or credit was counted twice; a reconcile posting later took it out of each balance, so on those accounts the flows, not the balances, are wrong.
What is measured (P24b)
- For every balance on 12 October 2026: available in
AccountBalanceagainst the sum of the account's flows, and its opening balance, which is the settled balance (available minus reserved) on its first journal row between 1 March and 30 April 2025, inTransactionIdorder, less that row's own settled amount. Accounts are grouped by the month the ledger first saw them. Taking the first row byTimeStampinstead makes the opening balance of accounts opened before April 2025 $636,695.48 higher, because in 2025 the journal'sTimeStampis not write order (DB-19). - Opened before April 2025: 1,007,294 accounts. 348,378 differ in available, +$47,033,607.61 net; 348,312 differ in settled, +$47,018,614.35. Their opening balances come to +$40,585,634.28, non-zero on 347,089 accounts. On 915,832 accounts the settled difference equals the opening balance to the cent, and on 281,330 of them neither is zero (+$23,415,734.03).
- 66,927 differ by more than their opening balance, +$6,434,523.47 net: +$6,465,947.34 where the balance is higher and −$31,423.87 where it is lower. Their opening balances come to +$17,169,900.25 and their settled differences to +$23,604,423.72. By the size of the difference beyond the opening, either way: 463 by $1,000 or more (+$600,895.35 net), 16,374 by $100 to $1,000 (+$4,389,185.91), 48,833 by $1 to $100 (+$1,443,862.62) and 1,257 by less than $1 (+$579.59). Of the 6,423 accounts with the largest differences beyond their opening balance, +$3,293,323.57 together, 6,419 have their first journal row in March 2025. Example:
2a5d8fe6-2186-40b9-b8ec-a6e7d5ab4a26, with 1,515 flows, held $3,089.16 before its first journal row, V=223125413 on 20 March 2025 at 12:35 UTC; its balance is $8,025.99 above its flows, $4,936.83 beyond the opening. - The rest of the +$47,033,607.61 is reserves, +$14,993.26 on 698 accounts (Holds); −$1,618.36 on 55 of the 3,913 accounts whose first journal row is from May 2025 or later; and +$74.96 on 16 of the 20,622 accounts with no flow.
- Opened from May 2025: 507,028 accounts. No opening balance applies. 37 differ in available, +$4,531.20 net: +$4,037.92 in the reserve of 32 accounts (Holds) and +$493.28 in the settled balance of 9.
Where the difference beyond the opening comes from (P27)
- 231 accounts were replayed version by version: every version of their balance since their first journal row, 293,609 in all, against every journal row and flow of the account. 221 were opened before April 2025 and chosen for large differences beyond their opening balance, either way; 10 are accounts this audit names, 5 of them opened in April 2025. Their balance history reaches back to the first journal row on every account: March 2025 on 224, April 2025 on 7. A break is a journal row at which the balance's settled amount moved by something other than the row's own flow. On every one of the 231 accounts, the settled difference beyond the opening balance equals the sum of its breaks to the cent.
- 1,060 breaks, +$175,406.61: +$184,841.99 on 1,050 and −$9,435.38 on 10. Every one is the first journal row of its flow, and 1,055 were written 20–31 March 2025. At 940 of them the balance did not move at all (+$154,461.64). By type: 816 Settlements, 143 AuthClearings, 89 AuthExpiries, 7 Auths and 5 AuthExpiryReversals; by external id: V= 654, A= 184, P= 139, HO= 78, AD= 3 and PMA= 2.
- Example: on
7d3a81d0-3754-4dc1-9e18-06fdbd1401b5, P=15832737, the first journal row of GA=129718840, set the flow's settled amount to −$953.74 on 21 March 2025 at 15:32 UTC while the balance's settled amount stayed at $178.27. If that payment was taken from the balance before the journal began, the balance is right and the $953.74 belongs in the opening entry; if not, the balance is $953.74 too high. The ledger alone cannot prove which. - 5 breaks on these accounts are later, −$877.61 together:
- 3 balance writes under the external id of an existing journal row of the account, −$2,677.61: on
afba9b93-1641-4be1-81df-9bcdcfbdc85d, −$1,229.13 settled and +$5.00 reserve on 4 July 2025 at 13:06 UTC under V=260230048, whose journal row is of 1 July; on3eb2319d-df6b-4a28-a9da-f840e77e5d17, −$1,451.23 on 4 July 2025 at 13:12 UTC under V=261145379, whose journal row is of 09:24 that day; and on7fa9265c-f142-4871-ac30-f5923c198187, +$2.75 in a third balance write under V=259703075, whose two journal rows are of 29 and 30 June 2025. - On
71d9dfdf-ad0b-4d32-8fed-895ff6116154, V=229410341, an authorisation on GA=129832130 on 2 April 2025 at 15:04 UTC, put a $600.00 hold on its flow while the balance's available did not move, so its settled amount rose from $240.38 to $840.38, +$600.00. - On
452a799a-58c0-4f28-bcbc-c3b4e1ef00e2, PMA=283340888 added $1,200.00 to the balance on 18 September 2025 at 11:07 UTC; its journal row names this account, but its flow, GA=196835867, is on the empty account id (Flows with no account).
- 3 balance writes under the external id of an existing journal row of the account, −$2,677.61: on
- If the March 2025 breaks are movements taken from the balance before the journal began, the opening entry for these accounts is the whole settled difference less the 5 later breaks, which are errors to correct one by one.
Opened in April 2025 (P24b, P30, P31)
- 29,822 accounts. The ledger first saw them after the journal began, so none should hold anything before its first journal row. 1,111 differ in available, +$55,186.63 net: settled +$54,819.50 on 1,102 and reserve +$367.13 on 10. On 1,100, all opened 1–8 April 2025, the first journal row written, between 1 and 18 April, carried a balance that already included the movement of the next one to three rows on the account, stamped within 5 seconds of it, and each of those rows then moved the balance again by its own amount (P30). When an account has no balance row, be-ledger sets available to the balance its caller sends instead of computing it (DB-20). By the movement counted twice:
- 687 PM= deposits, each written after an FE= fee row whose balance already included it, +$48,917.21, most often $20 (191), $100 (85) or $50 (71). Example:
7921d833-e185-4eb6-81a7-0040057f414c: FE=173871101, −$3.95, was written first, stamped 1 April 2025 at 23:58:28 UTC, with a balance of $26.05; PM=38902854, +$30.00, stamped four seconds later and written next, took the balance to $56.05. - 336 FE= fees of $3.95, each written after a PM= deposit row whose balance already had the fee taken, −$1,327.20. Example:
47855d00-a25e-46fa-a18b-5373b5ff6606: PM=38836210, +$20.00, written on 1 April 2025 at 00:59:40 UTC with a balance of $16.05; FE=173527022, stamped two seconds earlier but written after it, took the balance to $12.10. - PMA= credits on 76 accounts, +$7,103.42: one credit on 71 and two or three on 5. Example:
8fa09a1d-6eb0-40fd-ab61-8da4c699216b: PMA=2270252515, +$170.96, was written first on 14 April 2025 at 10:53 UTC with a balance of $673.96; PMA=2270252655, +$503.00, written after it, applied the $503.00 again. - 1 PM= deposit of $100.00, on
f3886d5f-0d4b-4893-ba51-d680a47e2d7b: PM=39341241, +$300.00, was written first on 10 April 2025 at 11:39:41 UTC with a balance of $400.00, and PM=39341242, +$100.00, stamped the same second, took it to $500.00.
- 687 PM= deposits, each written after an FE= fee row whose balance already included it, +$48,917.21, most often $20 (191), $100 (85) or $50 (71). Example:
- A posting consistent with be-transactions' reconcile handler (CL-30) took the doubled amount back out of every one of the 1,100 balances, between 2.8 hours and 88.5 days after the double count, half within 10 days: 1,023 in April 2025, 23 in May, 3 in June and 51 on 4 July 2025, between 10:55 and 14:57 UTC (P30, P31). Each is a Settlement that is the only row on a new flow, with a GUID external id, a different GUID idempotency key and no reserve. On 1,097 accounts one posting reversed exactly the doubled amount; on 3, two postings together did, such as
7ec7a194-ce56-41a6-aa27-af73ade21735, −$51.19 on 15 April 2025 and +$1.19 on 22 April. Example: on7921d833-e185-4eb6-81a7-0040057f414c, the $30.00 deposit counted twice was taken back out on 14 April 2025 at 07:24:53 UTC by a posting with external id fcec2481-293e-490b-8539-48b7ae53338d on flow 076c8206-9da5-4a23-be15-49bf62219a78: its balance is right, and its flows are $30.00 below it. Each reversal is a flow as well, so after it the balance is right and the flows differ from it by the doubled amount: +$54,793.43 net over the 1,100, to the cent in available on 1,099. On the other,ea051237-0d8f-421d-ab7e-f0f687ffd59a, a further posting of the same shape on 10 May 2025 settled nothing and put a reserve of −$5.00 on a new flow, so it differs by $5.00 more. 106 of the 336 fee accounts also have an AD= row of +$3.95, written 8 to 35 seconds after the doubled fee; it is not traced, and not counted as a reversal. - None of 300 accounts opened 1–8 April 2025 that do not differ from their flows has a non-zero opening balance (P30).
- 11 more differ and have no opening balance: on 9 the reserve differs, +$362.13; 2 have a $0.00 balance while their flows come to −$22.60 and −$3.47, +$26.07 together:
f902ed1e-73a5-45de-b1ef-cce40aa06c32, first journal row FE=177340610, and00737ac9-873a-4bfe-800e-f8c7dcc00c61, first journal row V=234304860, a denied authorisation.
Why it matters
- No account opened before 20 March 2025 can be proven from the ledger's own records: +$40,585,634.28 sits in balances with no entry behind it.
- The gap hides real errors. Accounts opened in April 2025 show it: be-ledger took their first balance from the caller, and on 1,100 of them a deposit, fee or credit was counted twice (DB-20). A reconcile posting took each back out of the balance as an ordinary settlement on a new flow (CL-30), so the flows of those accounts remain off by the doubled amount.
- A comparison of balance with flows reports +$47.03M of differences on these accounts, most of it opening balances, so the errors inside it go unseen.
How to fix
- Replay all 66,927 accounts that differ beyond their opening, version by version and in
TransactionIdorder, in bounded batches on the read replica, and list each break by kind. - Record the opening balance of each account opened before April 2025 as a recorded correction (DB-11), taking the first journal row in
TransactionIdorder, because the journal'sTimeStampis not write order in 2025 (DB-19): where the settled difference equals the opening balance, that balance; elsewhere, the settled difference less the breaks from April 2025 on, once the March 2025 breaks are confirmed as movements taken before the journal began. - Correct the later breaks one by one, starting with the 3 balance writes of June and July 2025, −$2,677.61.
- Record a correction on the flows of the 1,100 April 2025 accounts with a movement counted twice, for the doubled amount, +$54,793.43 net: reconcile postings took it out of their balances (CL-30), so no money moves. Decide on the 2 accounts at $0.00 whose flows are below zero (+$26.07).
How to verify
- P24b, with the opening entries counted as flows, returns no account whose balance differs from its flows.
- P4b, extended to accounts opened before April 2025, returns 0 differences.
Related
Holds and account reserves
A hold is money reserved on a flow and in the account's reserve. A card hold should end when Lithic settles, voids or expires the transaction; the exception is the remainder of a partial clearing, which Lithic keeps for 31 days. Card holds match Lithic. Older flow reserves do not: 1,078 on 864 customer accounts were last written between March 2025 and March 2026, and on 740 accounts the reserve differs from the flows.
Card holds against Lithic
- Debit, to 5 October 2026 (W35): 1,331 settled transactions still hold −$10,733.58, all from September and October, inside Lithic's 31-day hold on a partial clearing's remainder; none from earlier months. 3,338 authorisations Lithic still shows pending hold −$59,456.46.
- Credit, to 5 October 2026 (W39): 39 settled transactions hold −$251.24, last event 11 September to 5 October, all inside the 31-day window. 130 pending authorisations hold −$7,044.01: 112 from October (−$2,730.14) and 18 from September (−$4,313.87).
- GA= flows (W40): 0 GA flows and 0 native flows of their Lithic transactions hold money.
Flow reserves older than a month (P23)
- On 12 October 2026, 1,888 flows dated before 4 September 2026 hold a reserve, all dated between 20 March 2025 and 24 March 2026. None is a native Lithic flow, and no flow dated 25 March to 3 September 2026 holds one. 810 are on the empty account id (Flows with no account). The other 1,078 are on 864 customer accounts, −$21,354.01 net, in four shapes.
- Holds that keep money unavailable. 54 holds on 47 accounts, −$1,226.57, with nothing settled, carried by the balance with nothing to offset them. 51 are on flows with uuid4 ids (−$1,156.17): 40 written on 11 December 2025, 8 on 12 December 2025, and one each on 25 January, 11 February and 24 March 2026. The largest are
3b227d46-759b-4c8a-b259-72a3d5415386on accountf50f174d-8889-455f-9a06-ce7547bb8ce2, −$320.00 since 11 December 2025, and32d56623-75cd-48d1-b135-712e78680c36on52fc4abb-2f64-4442-88cc-37de8ea9798c, −$276.00 since 12 December 2025. 3 are on GA= flows (−$70.40): GA=222407346 (8 December 2025, −$24.88), GA=245714070 (6 March 2026, −$13.00) and GA=247908459 (13 March 2026, −$32.52). In be-transactions (W47), the 48 holds of 11–12 December 2025, −$1,072.23, are reservations in status PreAuth that never completed: 32 PeerToPeerDebit (MajorityPayAstra), 8 MobileTopUp, 7 Call and 1 InternationalTransfer. The February and March 2026 holds, −$20.01 and −$56.97, are InternationalTransfers that be-transactions reversed while the ledger kept the hold. The January 2026 hold, −$6.96, matches no record. - Holds released in the balance but open on the flow. 727 holds on 702 accounts, −$20,610.06. On each of these accounts the balance's reserve differs from its flows by exactly the account's stale holds: the balance has released them and the flows have not. 647 are on uuid4 flows (−$12,604.49): 643 from May 2025, 341 of them on 7 May and 174 on 9 May, and 2 each from June and July. None of the 647 matches a be-transactions record on any of five keys (W47), although the warehouse holds 17.1M be-transactions records for May 2025. In the ledger each of the 647 is one Settlement of 0 with an idempotency key, whose reserve is the negative of the account's reserve just before, which was positive, and after which the account's reserve is 0 (P29): the shape be-transactions' reconcile handler writes (CL-30). 80 are on GA= flows (−$8,005.57): 65 from November and 15 from December 2025. 664 of the 702 accounts were opened before April 2025. On 16 of them the balance was last written on 27 January 2026 at 13:29 UTC, under external id
FixGalileoExp-<account id>, carrying +$2,894.40 of the reserve difference. - Holds offset by credit reserves. On 110 accounts, 182 GA= holds from March to July 2025 (−$7,920.64) are offset to the cent, per account, by 110 credit reserves on uuid4 flows (+$7,920.64), written in May 2025 (105, 86 of them on 7 May) and on 25 September 2025 (5). None of the 110 matches a be-transactions record (W47). Each is a Settlement of 0 of the same shape: it cleared the account's reserve by writing its negative onto a new flow and left the GA= holds open (P29, CL-30). 78 of the 110 credits are multiples of $2.50; the commonest are $2.50 (35), $5.00 (18), $7.50 (7) and $15.00 (4). Available is unaffected. Example: on account
b38258b4-61ad-47b5-8cff-c85e3693bd85, GA=130567599 holds −$149.99 from 21 March 2025 and flow88f8fc4b-aa35-4ac3-8f30-422ad02999dbholds +$149.99 from 9 May 2025. - Credit reserves alone. 5 flows, +$482.62. The balance carries 2, which raise available:
1bd8f598-7dae-44c1-b9dc-095e9863e050one95a0909-ad53-44bd-bce9-dfdfdb21933a, +$8.02, andbbafdde3-c58a-4e3e-9176-189de5a3bbafon2c8e4309-a6af-43db-835a-69415e276673, +$45.00. It does not carry the other 3:2256f563-da1f-459e-a9f5-ccea4e90efecon2aa420e3-dcf5-487b-b24a-f21a9a758c7a, +$393.00;0d9b1e2a-27ae-4afb-8ec0-04d87424045don7309f2c9-9eed-4f0d-83aa-a91978621b34, +$19.50; and4c728df7-4a87-42a8-811f-195a4f022a49on4fffc2a3-183d-4c4e-9a32-1bb2a357b064, +$17.10. In be-transactions, bbafdde3 is an AdminCredit of 8 December 2025 in status PreAuth; the 4 others, written in May and June 2025, match no record (W47), and each is a Settlement of 0 of the same shape (P29, CL-30).
Account reserves against flows (P4b)
- On 12 October 2026, on 740 accounts the reserve in
AccountBalancediffers from the sum of the flows' reserves, +$19,398.31 in all: 42 accounts opened from April 2025 (+$4,405.05) and 698 opened before (+$14,993.26). - 702 differ by exactly their released stale holds (above), +$20,610.06.
- The other 38 differ by −$1,211.75. 2 are DATA-1 breaks that left a reserve no flow backs, −$1,000.00 and −$353.00. 3 are the credit reserves the balance does not carry, −$429.60. On 6 the balance was last written on 8 December 2025 at 13:59:57 UTC, under external id
FixGalileoExp-<account id>, +$75.10. On the other 27, +$495.75 net, each difference is traced row by row to the cent (below).
The 27 traced row by row (P26)
- Every flow and every journal row since 1 March 2025 of the 27 accounts, 29,689 rows, replayed in
TransactionIdorder: at each row the balance's reserve should move by the row's reserved amount less its flow's previous one. A row where it moved by something else is a break. The breaks, and the flows changed with no journal row, account for all +$495.75. - 29 rows on GA= flows, +$170.95: 26 in 2025 (+$158.25), 1 HO= row (+$7.70) and 2 in 2026 (+$5.00). The largest are V=237333930 on GA=145404077, 23 April 2025, +$204.29 on
c60ec48d-bacf-4c99-9341-3d4df9b54fb0, and V=258136472 on GA=168416471, 24 June 2025, −$180.87 onb5b504bf-4065-4b1c-9a88-8a1710384e55. The latest that persists is V=322216932 on20908d3d-455e-43ed-bc66-4cda460c1c0c, 7 February 2026. - 8 balance writes with no journal row of their own, +$414.08: 7 of 30 June – 4 July 2025 (+$20.10), among them +$5.00 on
afba9b93-1641-4be1-81df-9bcdcfbdc85dat 13:06:00 UTC on 4 July, and the write with external id FixGalileoExp-50F08B14-E812-4C45-A7BC-5BF5F2E3CD22 on 8 December 2025 at 13:59:57 UTC (+$393.98). The replay counts a break at the next journal row, AD= rows among them; read against the balance history, each of those AD= rows moved the balance by exactly its own amount (P28). - 3 rows on other flows, +$399.01: the uuid4 settlement dd62392e-331a-47bf-b0d1-fbab6c171c3c on
50f08b14-e812-4c45-a7bc-5bf5f2e3cd22(+$393.98, below); uuid5 90869667-beba-575d-9252-d908f8ab6abb onafba9b93-1641-4be1-81df-9bcdcfbdc85d, 9 December 2025 (+$5.00); and uuid4 f5a3bb6c-7933-47ff-8d0f-5af3b17e67b7 on92174dd3-c973-44ac-a59e-987a8c0fe6fc, 29 January 2026 (+$0.03). - 16 holds on 10 accounts were cleared on the flow with no journal row, −$488.29: twelve GA= holds of $2.50, GA=135827179 ($7.70), GA=139145061 ($393.98), and the uuid4 holds e768cab8-b737-4ced-8f4c-6732e6905e25 ($4.27) and b2701f83-4730-4b3a-b908-65554bbe78a2 ($52.34). All 16 flows were updated on 8 December 2025 at 14:00:02 UTC, five seconds after the FixGalileoExp balance writes of 13:59:57, with their TimeStamp unchanged (DB-11).
- One hold, HO=28038542 on GA=213367807, a flow of
a1cd275b-0d20-47cd-bbc8-a381218f8414, was reserved on the balance of35864348-0578-4c8d-a235-9e1b372a44a8on 9 November 2025 at 18:31:41 UTC, and its expiry on 22 December 2025 released it on a1cd275b's: −$53.98 on one and +$53.98 on the other (DB-2). - Example,
50f08b14-e812-4c45-a7bc-5bf5f2e3cd22, +$393.98. HO=21932631 on GA=139145061 held −$393.98 on 8 April 2025, on the flow and in the balance's reserve. Between the account's journal row of 1 July 2025 at 13:59 UTC and the next one, the uuid4 settlement dd62392e-331a-47bf-b0d1-fbab6c171c3c on 4 July at 15:04 UTC (−$218.64 on flow 602b2aea-ba97-4f61-a3e4-7b5e1b774127), $393.98 moved from the reserve into the settled balance while the hold's flow did not change. By the balance write FixGalileoExp-50F08B14-E812-4C45-A7BC-5BF5F2E3CD22 on 8 December 2025 at 13:59:57 UTC, which has no journal row, the reserve had risen by $393.98 again, and at 14:00:02 the hold's flow was cleared with no journal row. AD=48684679 on 9 December then moved the balance by exactly its own −$5.99 (P28). The $393.98 is in the settled balance as a charge and in the reserve as a credit, and neither is on a flow.
Why it matters
- A stale hold keeps a customer's money unavailable, and nothing releases it: LastRun has stayed at its seed value, so the ExpireAuth job has not run (DB-4).
- A hold released in the balance but open on its flow is released again by any later event or expiry on that flow, which credits the customer twice.
- A reserve that differs from its flows makes a later release free money that was never held, or never free money that was.
How to fix
- Retire be-transactions' reconcile handler and its queue first, so no new reserve of this shape appears while the others are closed (CL-30).
- Release the 54 holds that keep money unavailable, one correction per flow that clears the flow's reserve and the account's together, recorded as a correction (DB-11). First end the 48 reservations of December 2025 in be-transactions, where they sit in PreAuth, so both systems agree (CL-7). Do not revive the ExpireAuth job to do it: its first run writes LastRun and narrows every lookup window (DB-4).
- Close the 727 holds released in the balance on the flow only, leaving the balance as it is; releasing them the normal way would credit $20,610.06 a second time.
- Close each of the 182 offset GA= holds together with its credit reserve, so the account's available does not move.
- Decide on the 5 credit reserves: the 2 the balance carries raise available by $53.02 with nothing settled behind them; the other 3 can be closed on the flow only.
- Correct the reserves of the 27 traced accounts and the 6 FixGalileoExp accounts, one recorded correction per account (DB-11), and the 2 DATA-1 accounts with DATA-1.
- Release stale holds per flow on a schedule, with a message keyed to the flow, so new ones do not build up.
How to verify
- P23 with its cutoff 31 days back returns no flow reserve without an open Lithic transaction.
- P4b returns 0 accounts whose reserve differs from their flows.
Related
Flows on the empty account id
845 flows carry the empty account id, 00000000-0000-0000-0000-000000000000. No account or balance row exists for it, so no per-account check sees them. 30 carry a credit whose journal row names a customer account; the other 815 have no journal row at all.
What is measured (P25b)
- Every flow on the empty account id, with its journal rows since 1 March 2025: 845 flows, 840 GA= and 5 uuid5, holding +$20,739.61: settled +$4,749.55 and reserved +$15,990.06. By last write: March 2025 226, April 514, May 72, June 2, August 21, September 1, October 4, November 5. The newest is
276d2f56-9c14-5828-ba1d-92657a5bdaf4, 10 November 2025, 22:46:55 UTC. - 30 GA= flows carry one journal row each, a PMA= Settlement with an idempotency key, +$4,749.55 in all, written April 2025 3, May 2, June 1, August 21, September 1, October 1, November 1. Each row names a different customer account, while its flow is on the empty account id. The largest: GA=196835867, $1,200.00, on
452a799a-58c0-4f28-bcbc-c3b4e1ef00e2; GA=189094890, $520.00, onf139a721-0c07-4bba-82a1-6eb48fefb2c9; GA=159233794, $400.00, on37d27c90-4009-44e0-b15e-4b1b8ed3e0d1. On 6 of the 30 accounts the balance is measured to hold the credit (P24b, P27): on those 6 the money reached the customer and only the flow is on the wrong account. How the flows came to the empty account id is not traced; be-ledger's flow update is not scoped to an account (DB-2). - 815 flows have no journal row at all. 810 are GA= flows with nothing settled, dated 20 March to 28 October 2025 (March 226, April 511, May 70, June 1, October 2): 798 credit reserves, +$16,661.14, 549 of them $5.00 or less, most often $1.00 (172), $0.10 (92), $5.00 (31) and $0.01 (14); and 12 holds, −$671.08. 5 are uuid5 flows at zero:
507107df-195c-527d-ab11-5152b0c6203bof October 2025, and744d9c2f-0dfe-5916-b90c-aab1ed28dee6,9ca024a0-5182-5736-b99c-77363989cf0d,82490ccb-9c60-5b2c-879f-395f27c372a5and276d2f56-9c14-5828-ba1d-92657a5bdaf4of November 2025. All 815 carry whole-second times. Nothing in the ledger names their owners.
Why it matters
- be-ledger accepts any GUID as an account, the empty one included (DB-8), and money posted to it reaches no customer's balance.
- No per-account check sees these flows, this audit's balance checks included, because no balance row exists for the empty account id.
- The 30 credited flows show up as unexplained differences on the accounts that received the money: their balance holds a credit that none of their flows carries.
How to fix
- Confirm each of the 30 with a bounded replay of the named account, then move the flow onto it with a recorded correction on the flow only (DB-11); where the balance does not hold the credit, credit it.
- Trace the 810 GA= flows outside the ledger, by GA= flow number, the only key they carry, and close each with a recorded correction.
- Close the 5 uuid5 flows at zero.
- Reject an empty or unknown account id on every ledger call (DB-8), and scope the flow update to its account (DB-2).
How to verify
- P25b returns no flow on the empty account id.
- For each of the 30 accounts, the flows equal the balance.
Related
Code issues
52 defects in be-ledger and the services that post to it, by severity. Each page has what happens, the evidence, the code, how to fix it and how to verify the fix. What is wrong in the data today is under Data consistency.
By area
Critical 3
High 12
Medium 17
Low 20
Hygiene, dead code and narrow edge cases, listed on Low severity.
Event stream
What be-ledger publishes, and what reaches statements, audit and the warehouse.
Balance rules and accounts
How an authorisation or posting is checked against the account it lands on.
Ledger database
What the database itself guarantees: keys, locks, history, the audit trail.
Callers
Services that move money through be-ledger, and what happens when a call fails or is retried.
An authorisation on an account with no balance row is approved for any amount and never deducted
- Services
- be-ledger
- Seen in prod
- Not seen: 0 of 64,977,107 authorisation events carry the signature (W11).
- Impact
- One free purchase of unbounded size per affected account.
What happens
When an account has no balance row, or its latest row is the 2024 "Init" seed, the authorisation path replaces the computed available balance with 0. Validation passes, the hold is approved whatever its size, and at clearing the normal formula nets the purchase to zero. With partial approval allowed and no row at all, the path throws instead.
Evidence
It has never fired: 0 of 64,977,107 authorisation events from March 2025 to October 2026 carry the signature (W11). 66 accounts have no balance row today; none has an "Init" row, and be-ledger auto-created all 66 on 3 February 2026 (P20b, W33). Rain authorisations would take this path (CL-24).
Where in the code
Minority.Ledger.Service/Transactions/Internals/SaveTransactionsAction.cs:121–123passes0as the event balance;:375(prod:369) applies it when the old balance is null or "Init";:106dereferences a null balance on the partial branch.Minority.Ledger.Service/Transactions/TransactionService.cs:35, 86creates an unknown account withAlreadyCreatedAccount = true, so no balance row.Minority.Ledger.Service/LedgerStatics.cs:6; be-lithicMinority.Lithic.Service/Authorization/AuthService.cs:265, 293.- History: the literal 0 since d73ff39 (2024-07-22); the "Init" branch since 56a0dc1 (2024-07-29); the seed handler removed in 2574917 (2025-02-27).
How to fix
- In
SaveTransactionsAction.cs, passnullinstead of0as the event balance (line 122) and null-guard the partial-approval branch (line 106). - On Auth, decline an unknown account instead of creating it (DB-8).
- Backfill a zero balance row for the 66 accounts that have none (P20b lists them); no account has an "Init" row.
How to verify
- Unit tests: an Auth on an account with no balance row is declined; an Auth larger than the available balance on an "Init" account is declined.
- On the replica: accounts with no
AccountBalancerow or an "Init" latest row return 0. - W11 keeps returning 0 authorisation events that moved settled money.
Related
be-pspfunding and be-phoneplan skip the debit when the finalize step is retried
- Services
- be-pspfunding be-phoneplan
- Seen in prod
- Not measured: needs the be-pspfunding and be-phoneplan databases.
- Impact
- Majority funds the payout or the plan period.
What happens
Both services save the finalize id before calling be-transactions and return early whenever it is set. If the debit call fails, the redelivered message returns early and completes. The PSP has captured the payout, or the plan period has been delivered, but the customer is never debited, and be-transactions releases the hold at expiry.
Evidence
Not measured. It needs the be-pspfunding and be-phoneplan databases: finalize id set with no matching be-transactions debit.
Where in the code
- be-pspfunding
Minority.PspFunding.Service/Transaction/TransactionService.cs:381–389(early return, then id saved),:391–417(debit or release);Payments/EventHandlers/FinalizePaymentMessageHandler.cs:18–27has no catch. - be-phoneplan
Minority.PhonePlan.Service/Invoice/EventHandler/InvoiceEventHandler.cs:351–352, 362–374.
How to fix
- Measure first: in each database, list finalize ids with no matching be-transactions debit.
- Keep saving the finalize id before the call, and add a separate "finalized" marker written only after be-transactions answers success.
- Return early only when that marker is set. On redelivery with an id but no marker, call be-transactions again with the saved id; it deduplicates on the id.
- Debit any customer the measurement finds, through be-transactions with the saved id.
How to verify
- Integration test: fail the first debit call, redeliver the message, and assert exactly one debit.
- The measurement query returns 0 after the backfill.
Related
be-mtu marks a top-up done when its debit, release or reverse call fails
- Services
- be-mtu
- Seen in prod
- Not measured: needs the be-mtu database.
- Impact
- Free top-ups, or customers not refunded. Amounts are top-up sized.
What happens
Any failure other than a timeout is swallowed and the status still advances. A Successful top-up whose debit failed is never charged and its hold expires; a Reversed one whose reverse failed is never refunded. No transaction id is passed, so a retry would post a new movement rather than repeat the old one.
Evidence
Not measured. It needs the be-mtu database: Successful with no finalize id, Reversed still holding the debit id.
Where in the code
- be-mtu
Minority.Mtu.Service/MobileTopUp/MobileTopUpService.cs:215–240(catch, then status update),:592–625(FinalizeFund). Minority.Mtu.Service/.../TransactionFundService.cs:33–55calls without a TransactionId.
How to fix
- Measure first: Successful top-ups with no finalize id, and Reversed top-ups still holding the debit id.
- Treat every
FinalizeFundfailure as retryable: rethrow instead of swallowing. - Advance the top-up status only after the funds call succeeds.
- Pass a name-based transaction id (top-up id plus operation) so a retry repeats the same movement instead of posting a new one.
How to verify
- Test: the funds call throws, the status does not change and the message is retried; the retry posts once, under the same id.
- The measurement queries return 0 after the backfill.
Related
Prod publishes ledger events for postings that roll back: 1,595 since February
- Services
- be-ledger
- Seen in prod
- Yes: 1,595 phantom events from February to 7 October 2026, every month.
- Impact
- Statements, audit and the warehouse show about $1.72M of debits that never happened. A statement balance stays wrong until the account's next event; the warehouse and audit keep the rows. Nothing is logged, so nothing alerts. The trigger is an ordinary user action and repeats daily.
What happens
Prod runs 75725be (image 26.0909.171). A batch posting saves, validates and publishes each record in turn and commits only after the last one. When a later record breaks a balance rule, the batch rolls back and the caller gets id ML=-1, but the earlier records' events are already out. All 6 phantoms traced in the logs (5 and 7 October) are mpay transfers (TransferMoneyBetweenUserAccounts) whose credit leg would take the recipient over its MaxTotalBalance 10,000 rule. be-ledger answered 200 with the broken rule, and be-transactions answered 400 "Transaction is not allowed.". be-accountstatements and be-auditing had already consumed the debit event. No error or warning was logged for any of them. Separately, the auth path commits and then publishes with failures swallowed, so its events can be lost (EV-2). Master writes every event to an outbox inside the posting's transaction (443b6bc), so a rollback removes it, and dbf9d4d stops a double rollback from masking the original exception. Stage runs 26.1008.179.
Evidence
1,597 event ids have no journal row in the warehouse (W10, W13e). 2 are rows the warehouse export skipped (DB-10); the other 1,595 are phantoms. By month: Feb 224, Mar 105, Apr 171, May 198, Jun 150, Jul 211, Aug 249, Sep 243, 1–7 Oct 44. From February on they include 1,593 Settlement-type events whose debits total −$1,722,060.87, on 14 to 60 accounts a month; 2 of them carry a $0 event, and none is a credit. The counts are of transaction ids: one July phantom was published twice, so the copies hold 1,596 events. That fits the traced pattern: a sender repeating a transfer into an account at its cap. Prod row counts equal the warehouse in 10 of 12 closed months, and the other two are each one row short in the warehouse (P3). So the postings never existed. 8 be-accountstatements balances were still wrong from such events when checked. P22 looked up the 1,553 ids to September in prod: the 1,551 phantoms among them are absent, and the 2 it found are the rows the export skipped. The 44 from 1–7 October were not looked up.
Where in the code
- Prod 75725be
SaveTransactionsAction.cs:192–206(batch loop; rollback on id −1 at:197, commit at:210),:328–331(rule check returns −1),:334(event published before the commit),:146–148(auth path: commit, then publish),:435–438(publish failure logged and swallowed). - Master
SaveTransactionsAction.cs:339publishes through:422SendEventWithOutbox with the posting's transaction. - be-deployments
Minority/Minority.Ledger/Minority.Ledger.Service/prod-values.yaml:5.
How to fix
- Deploy master to prod. It carries the outbox (
443b6bc) and the fix that stops a double rollback from masking the original exception (dbf9d4d); stage already runs 26.1008.179. - Rebuild the be-accountstatements balance of every account that received a phantom event, from the journal.
- Mark or delete the phantom rows of the 1,595 ids, 1,596 events, in the warehouse and in be-auditing. The ids come from W10 and W13e. P22 found 1,551 of them absent from prod; the other 44, from 1–7 October, were not looked up.
- Add an alert on ledger event ids that have no journal row, so the next gap pages someone instead of waiting for an audit.
How to verify
- W10 over the days after the deploy returns 0 event ids without a journal row.
- On stage: a transfer into an account at its MaxTotalBalance cap answers 400, and be-accountstatements and be-auditing consume no event for it.
Related
CreateAccount overwrites an existing balance with 0/0 without the account lock
- Services
- be-ledger
- Seen in prod
- Not seen since 1 April 2026 (P16).
- Impact
- On any replayed or future create, a positive balance vanishes or a negative one is forgiven, silently.
What happens
Creating an account that already has a balance runs the same absolute upsert the posting path uses, without the posting path's lock. A second create resets available and reserved to 0, with no journal row and no error. Open flows keep their holds, so later events compute against a balance that no longer matches.
Evidence
0 resets of a live balance since 1 April 2026 (P16).
Where in the code
Minority.Ledger.Service/Account/AccountService.cs:67, 73–83.Minority.Ledger.Db/Migrations/StoredProcedures/AccountBalanceSaveAndGetTimestamp.sql:16–31;AccountInsertOrUpdate.sql:21–24also rewrites the currency.- Only caller: be-wallet
AccountAction.cs:103–112, which today always sends new ids;AccountServiceV2.cs:123, 149takes the id from an internal request body.
How to fix
- Make CreateAccount insert-only:
INSERT … WHERE NOT EXISTSunderUPDLOCK, HOLDLOCK, returning the existing row when there is one. - Keep the absolute upsert on the locked posting paths only.
- Make
AccountInsertOrUpdateinsert-only; today it also rewrites the currency.
How to verify
- Integration test: create an account, post a movement, create it again; the balance does not change.
- P16 keeps returning 0 resets of a live balance.
Related
TransactionFlowCore has no key: unlocked upsert, unscoped update, windowed visibility
- Services
- be-ledger
- Seen in prod
- Yes: journal rows on 147 GA= flows carry more than one account id (W40). 0 duplicate flow rows in September 2026 (P6).
- Impact
- Two accounts can end up on one flow, after which every later event on it throws, and a flow outside the lookup window gets a second row. Journal rows on 147 GA= flows already carry more than one account id.
What happens
The flow table has no primary or unique key. The insert-or-update checks existence without a lock and only inside the lookup window, its UPDATE is not limited to the account, and the clustering and partitioning key changes on every write. Two accounts posting the same flow id at once both insert, after which every later event on either flow throws. A flow row outside the window is invisible, so a second row is inserted and the next UPDATE rewrites both.
Evidence
0 duplicate flow rows in September 2026 (P6). Flows do get shared: 52,725 Lithic transactions from April to September 2026 sit on a flow that also carries other rows (W8). 52,723 of them are on GA= flows that be-lithic's Galileo routing wrote them onto (April 50,378, May 2,218, June 127, none since; W15). Journal rows on 147 GA= flows carry more than one account id (W40); the account-unscoped UPDATE is what lets a flow change account. The other 2 are the July masked-token artefact (OPS-2) and an August pending transaction whose flow carries one extra AuthClearing row, −$1.85.
Where in the code
Minority.Ledger.Db/Migrations/012_Recreate_Transaction_Core_Table.sql:64–72.StoredProcedures/TransactionFlowCoreInsertOrUpdate.sql:35–51;TransactionFlowGetById.sql:12–22.- The only ownership check is in C#:
SaveTransactionsAction.cs:451–454.
How to fix
- On the replica, check the whole table for duplicate flow ids and clean any found.
- Add a non-aligned unique index on
TransactionFlowId. - In
TransactionFlowCoreInsertOrUpdate, takeUPDLOCK, HOLDLOCKon the existence check and addAND AccountId = @accountIdto the UPDATE. - Later, move the flow table to a non-partitioned table keyed on the flow id (DB-12).
How to verify
- The unique index builds, which proves there are no duplicates.
- Concurrency test: two accounts post the same flow id at once; one succeeds and the other is rejected.
Related
Every lookup window hangs on ExpireAuthRunHistory.LastRun, which is frozen at 2024-01-01
- Services
- be-ledger
- Seen in prod
- Latent: LastRun has been 2024-01-01 since it was seeded.
- Impact
- Latent. One write to LastRun would make replays post twice, create second flow rows and leave holds that never release.
What happens
Idempotency and flow lookups search from LastRun minus one day. Nothing writes LastRun, so the window is (2023-12-31, now + 1 day) and the 3-month floor never binds. A single write to that row, by reviving the job or by a manual fix, would hide 3- to 12-month-old flows and keys: a second flow row would be created, holds would never release, and replays would post twice.
Evidence
LastRun is 2024-01-01 in prod (P1). TransactionCore partitions start at 2024-09-01, so each lookup probes about 27 partitions today and one more each month.
Where in the code
StoredProcedures/TransactionCoreGetByIdempotencyKey.sql:13–17,TransactionFlowGetById.sql:12–15,TransactionFlowCoreInsertOrUpdate.sql:15–18.- Seed
016_Create_ExpireAuth_Run_History.sql:6; the only writer is registered in tests (Minority.Ledger.Test/AutomationLayer/LedgerIntegrationTest.cs:90).
How to fix
How to verify
- No procedure references
ExpireAuthRunHistory. - Key and flow lookups seek on the new unique indexes without a time predicate.
Related
Holder-level balance rules read the other accounts without locks
- Services
- be-ledger
- Seen in prod
- Not measured.
- Impact
- A holder can exceed a combined limit, such as a total balance cap, with two postings at the same moment on two of their accounts.
What happens
Rules that span a holder's accounts sum the other accounts' last committed balances without locking them. Two concurrent postings on two accounts of one holder each pass and both commit, breaching the combined limit; nothing re-checks.
Evidence
Not measured.
Where in the code
StoredProcedures/AccountBalancesGetAllByAccountIds.sql:12–14;Minority.Ledger.Service/AccountBalance/Internals/BalanceValidateAction.cs:33–51.- be-transactions
AccountBalanceRulesService.cs:59–76sends the cross-account rules.
How to fix
- At the start of the posting transaction, lock the posting account and every account a cross-account rule reads, in ascending id order.
- Read the rule accounts' balances under that lock.
How to verify
- Concurrency test: two postings on two accounts of one holder, each within the limit alone but over it together; exactly one passes.
Related
TransactionCore has no primary key; TransactionId and IdempotencyKey are not unique
- Services
- be-ledger
- Seen in prod
- Not seen: 0 duplicate ids or keys (September 2026 in prod; every journal id in the warehouse).
- Impact
- An idempotency key reused by another account at the same moment creates two rows, after which every retry of either throws.
What happens
Idempotency holds only because the account lock comes before the key check; the key itself is global. Another account reusing a key at the same moment inserts a second row, and every later retry of either one throws. TransactionId is unique only because every writer uses the sequence, which runs outside the transaction, so ids have gaps and do not follow commit order (see DB-10).
Evidence
0 duplicate ids or keys (September 2026 in prod; every journal id in the warehouse). The sequence stands at 451,933,452 with the default cache (P1).
Where in the code
012_Recreate_Transaction_Core_Table.sql:43–61;StoredProcedures/TransactionCoreInsert.sql:3, 18–47.SaveTransactionsAction.cs:260(lock),:263–282(key check),:289(id from the sequence),:331(insert).
How to fix
- On the replica, confirm the whole table has no duplicate
TransactionIdorIdempotencyKey. - Add non-aligned unique indexes on
TransactionIdand onIdempotencyKey, filtered on not null. - Replace the windowed key lookup with a seek on the new index.
- Catch errors 2601 and 2627 on insert and re-read the existing row.
How to verify
- Test: the same idempotency key from two accounts at once produces one row, and each retry returns that row.
Related
Holds reach the journal late or not at all, so the journal cannot rebuild a balance
- Services
- be-ledger
- Seen in prod
- By design: every card authorisation decision.
- Impact
- Holds are invisible to the journal until Lithic's event arrives, and drift between balances and movements cannot be found from the database alone.
What happens
The card authorisation decision (the ASA call) reserves the amount in AccountBalance and TransactionFlowCore without writing a journal row. The journal records the hold only when Lithic's AUTHORIZATION event arrives and writes a type-1 row with the same flow state. Until then, or if that event never arrives, the hold exists only as a flow state and a temporal balance version. The flow table is overwritten in place, and journal rows store absolute snapshots with no offsetting leg, so no immutable record can rebuild a balance or show drift on its own. This audit could check the settled balance (available minus reserved), which holds do not change, and holds only as flow state.
Evidence
64,977,107 authorisation-path events from March 2025 to October 2026 carry no journal row (W11). The journal row that follows is visible in the native example: native flow 73111304-c09e-5521-a7a4-3216c99d8ff4, ML=450564885, type 1, reserved −48.21, written when Lithic's AUTHORIZATION event arrived. On the 231 accounts replayed version by version (P27), 637 flows dated April to October 2026 have no journal row and hold nothing, consistent with authorisation decisions that no journal row followed, and 1,088 rows moved the balance's reserve by an amount their flow did not, +$1,929.66 net, in every month from March 2025 to October 2026.
Where in the code
SaveTransactionsAction.cs:126–133, 144.StoredProcedures/AccountBalanceSaveAndGetTimestamp.sql:11, 21;TransactionFlowCoreInsertOrUpdate.sql:45–51.
How to fix
- Add an append-only movement table written by the balance procedure itself with
OUTPUT … INTO, so no code path can change a balance without a movement row, holds included. - Reconcile it against
AccountBalancenightly and alert on any difference.
How to verify
- An authorisation decision writes a movement row at decision time.
- For a day, each account's movements sum to the change in its
AccountBalance.
Related
Any GUID becomes an account on first use
- Services
- be-ledger
- Seen in prod
- Yes: be-ledger auto-created 66 accounts on 3 February 2026, 13:04–13:10 UTC, that have no balance row, no journal row and no record in the warehouse's ledger, account, card or transaction tables (P20b, W33, W34). 845 flows, the newest from November 2025, carry the empty account id, which has no account or balance row: 815 with no journal row, and 30 whose one journal row names a customer account (P4b, P25b).
- Impact
- A caller that sends a wrong but valid GUID posts money to a phantom account, and an authorisation on that account is approved for any amount (CL-1).
What happens
Auth, Event and Events create an unknown account id, with no balance row, instead of rejecting it, and no foreign key exists. A caller that sends a wrong but valid GUID posts to a phantom account, and an authorisation on it takes the CL-1 path.
Evidence
66 accounts have no balance row today (P3, P20b). be-ledger auto-created all 66 on 3 February 2026 between 13:04:21 and 13:10:25 UTC, in 21 bursts of one to four accounts. None has a journal row or a balance, and none of the 66 ids appears in the warehouse's ledger, account, card or transaction tables, where the same search finds a known account id (W33, W34). None of the 25,524 accounts opened in September 2026 was auto-created (P18r).
Where in the code
Minority.Ledger.Service/Transactions/TransactionService.cs:35, 49, 62, 80–88.
How to fix
- Allow auto-create of an unknown account only behind an explicit migration flag.
- Reject unknown accounts on Auth, Event and Events otherwise.
How to verify
- Test: an Auth on a random GUID is rejected and no account row is created.
- P18r's count of auto-created accounts is 0 for the period after the change.
Related
PayFunds makes a new recipient id on every attempt
- Services
- be-transactions
- Seen in prod
- Not seen, April to September 2026 (W14).
- Impact
- A retried payment credits the recipient twice.
What happens
No caller sets the recipient id, so be-transactions generates one per attempt. When a payment is driven again with the same sender id, the sender leg is deduplicated but a second recipient credit is posted. The realistic trigger is a PayFunds call that fails after the ledger committed, followed by a platform retry or a broker redelivery.
Evidence
Not seen, April–September 2026 (W14).
Where in the code
- be-transactions
PayFundsAction.cs:99–102;MajorityLedgerProcessorGateway.cs:268, 279, 301. - be-mpay
CompletePaymentAction.cs:72passes only the sender id.
How to fix
- Derive the recipient id from the sender id, name-based, inside
PayFundsAction.
How to verify
- Test: drive PayFunds twice with the same sender id; the recipient is credited once.
A release after a debit on the same reservation undoes the debit
- Services
- be-transactions be-ledger
- Seen in prod
- Not measured. A warehouse check for flows where a release follows a debit is possible.
- Impact
- The customer gets a debit back after the provider was paid.
What happens
The ledger writes absolute flow states, so a release (reserved 0, settled 0) on a reservation that was already debited (0, −d) gives d back. be-transactions guards only by reading committed rows, and ReservationAlreadyDebitedException is caught but never thrown. With a concurrent debit and release or expiry, the debit is reversed while the provider has been paid.
Evidence
Not measured. A warehouse check for flows where a release follows a debit is possible.
Where in the code
- be-transactions
MajorityLedgerProcessorGateway.cs:141–144, 176–179;ReleaseReservedFundsAction.cs:122–126;DebitReservedFundsAction.cs:115–118;ExpireReservationMessageHandler.cs:35–80. - be-bank-platform
ReservationAlreadyDebitedException.cs:15.
How to fix
- In be-ledger, reject a release (reserved 0, settled 0) on a flow whose settled amount is not 0.
- In be-transactions, lock the reservation in both the debit and the release action, and throw
ReservationAlreadyDebitedException. - Run the warehouse check for flows where a release follows a debit, and correct any found.
How to verify
- Test: debit, then release the same reservation; the release is rejected.
- The warehouse check returns 0.
Related
A non-business ReserveFunds error leaves a hold nobody releases
- Services
- be-transactions
- Seen in prod
- Not seen as described: no stale hold matches a be-transactions record in status Initiated (W47). The 48 holds of 11–12 December 2025 on uuid4 flows, −$1,072.23, are reservations that be-transactions holds in status PreAuth and never completed: 32 PeerToPeerDebit, 8 MobileTopUp, 7 Call and 1 InternationalTransfer. They keep their customers' money unavailable just the same (P23).
- Impact
- The customer's money stays held with nothing to release it.
What happens
On a non-business error after the ledger reserve, be-transactions commits an Initiated row and throws. The reservation lookup requires status 1, so later debit or release calls get NoActiveReservationFound, and be-transactions never releases the hold. A retry with the same id heals it; be-mtu passes no id and creates a second hold.
Evidence
Not measured.
Where in the code
be-transactions ReserveFundsAction.cs:143–160 (commit at 157), :167–184; TransactionGetActivePreAuthByOwnerResourceId_V2.sql:21, 37.
How to fix
- Include status 6 (Initiated) in the reservation lookup.
- Save the open reservation before the ledger call.
- Sweep Initiated rows against the ledger and release orphan holds.
- Find why the 48 reservations of 11–12 December 2025 stayed in PreAuth, end them, and release their holds (Holds).
How to verify
- Fault-injection test: a non-business error after the reserve; a later debit or release finds the reservation.
Related
One account's credit limit leaks into the shared rules config for the life of the pod
- Services
- be-transactions
- Seen in prod
- Not measured: needs prod-wallet.
- Impact
- On that pod, until it restarts, CreditSecured accounts without a limits row can spend the leaked limit with no deposit.
What happens
AccountBalanceRulesService writes an account's limit into the process-wide rules object. After evaluating a CreditSecured account with limit −500, every CreditSecured account on that pod without a limits row gets −500 instead of 0 and can spend 500 with no deposit.
Evidence
Not measured. It needs prod-wallet: active CreditSecured accounts with cards and no AccountLimits row.
Where in the code
be-transactions Minority.Transactions.Service/AccountLimit/AccountBalanceRulesService.cs:57, 90; be-platform ConfigurationRegistry.cs:64 registers the config as a shared instance.
How to fix
- Build the rule response from local values; never assign to the shared config object.
- Measure in prod-wallet: active CreditSecured accounts with cards and no
AccountLimitsrow, and their authorisations below zero.
How to verify
- Unit test: evaluate account A with limit −500, then account B with none; B gets 0.
Related
23 unexplained settled-balance breaks, November 2025 to February 2026
- Services
- be-ledger
- Seen in prod
- Yes: 23 rows on 17 accounts, November 2025 to February 2026; none since 1 March 2026. Three show in today's balances (P4b): 55944b28-748e-44f0-ae4c-0ea153d5a535 and e98ed495-1475-4d45-b86b-e5ed992317a0 carry a reserve of −$1,000.00 and −$353.00 that no flow backs, offset by as much settled money, and the available balance of 6b6afc2c-3156-48fb-8fbb-ac4e14564512 has been $973.00 below its flows since 7 January 2026.
- Impact
- Net −$701.98 across 17 accounts, cause unknown. One account has $973.00 less available than its flows.
What happens
23 journal rows whose settled balance is not the previous row's plus their own movement: November 1, December 2, January 8, February 12. The 23 rows fall on 17 accounts, 11 of them with a non-zero net (W7d); 7 rows are on 30 January 2026; most are on GA= flows. None since 1 March 2026.
Evidence
Net −$701.98; the absolute account nets sum to $4,327.98 (W7a, W7c, W7d). Cause not established. Three show in today's balances (P4b): 55944b28-748e-44f0-ae4c-0ea153d5a535 (12 December 2025, ML=308380011 on GA=223589851, +1,000.00) and e98ed495-1475-4d45-b86b-e5ed992317a0 (12 December 2025, ML=308223522, +353.00) carry a reserve that no flow backs, offset by as much settled money, so their available balance is right; on 6b6afc2c-3156-48fb-8fbb-ac4e14564512, ML=320448644, a −1.20 settlement on 7 January 2026, took the settled balance from 974.20 to 0 instead of 973.00, and available has been $973.00 below its flows since. The −5.99 break on 6d6235cb-24b1-4180-9525-8cf06482680f (9 November 2025, ML=291851116) comes after ML=276715951 on the same account, a row in prod that the warehouse's copy lacks (P22, DB-10), so it may be that missing row rather than a balance error; not confirmed.
How to fix
- Trace the 17 accounts through their balance history around each of the 23 rows, with bounded AS OF reads, not history scans.
- Correct each account with a compensating movement, recorded as a correction (DB-11).
How to verify
- Each account's balance equals the expected net after its correction.
- W7a, re-run for March 2026 onwards, still shows 0 breaks.
- P4b shows no reserve or available difference on any of the 17 accounts.
Related
Lost and duplicate ledger events
- Services
- be-ledger be-auditing
- Seen in prod
- Yes: 13 journal rows with no event (December 2025 and January 2026) and 2 with two; one phantom of July 2026 was also published twice.
- Impact
- Statements and audit miss 13 movements and count 2 twice.
What happens
13 journal rows have no ledger event (December 2025: 1; January 2026: 12) and 3 journal ids have two. A swallowed publish failure loses an event. The outbox on master gives at-least-once delivery, so duplicates stay possible and be-auditing, which forwards to the warehouse, has neither deduplication nor gap detection.
Evidence
W9, W10. 0 "Cannot Send Event!" warnings in the be-ledger logs on 1–7 October 2026.
How to fix
- Deploy master: the outbox stops lost events.
- Deduplicate on the journal id in be-auditing, since the outbox delivers at least once.
- Alert on gaps between the journal and the event stream.
How to verify
- W9 and W10, run daily: 0 journal rows without an event and 0 ids with two.
Related
The warehouse export skips rows that commit late
- Services
- be-ledger be-reporting
- Seen in prod
- Yes: 2 rows, ML=276715951 (10 October 2025) and ML=359981372 (24 March 2026); both are in prod (P22).
- Impact
- The warehouse misses rows, so reports and reconciliations built on it come up short.
What happens
The export pages by journal id as if ids followed commit order. Ids are allocated before the commit, so a row whose transaction stays open more than 5 minutes after its id was taken is skipped, silently, because id gaps are normal.
Evidence
2 journal rows are in prod but not in the warehouse: id 276715951 (October 2025) and one in March 2026 (P3 month counts; P22 confirms the ids).
Where in the code
StoredProcedures/TransactionCoreGetAllByIdAndTimestamp.sql:8–13; Minority.Ledger.Service/Report/ReportService.cs:123–153; be-reporting LedgerTransactionService.cs:71–76.
How to fix
- Re-read an overlapping window on every export run and load idempotently on the journal id, or feed the warehouse from the outbox.
- Backfill the 2 missing rows.
How to verify
- Prod and warehouse row counts are equal for every month (P3).
Related
varchar(50) parameters silently truncate identifiers
- Services
- be-ledger
- Seen in prod
- Latent: the longest identifier in a September 2026 week is 36 characters (P15).
- Impact
- Latent. Two long identifiers that share their first 50 characters would merge into one flow or one key.
What happens
Procedure parameters for flow id, external id and idempotency key are varchar(50), and no contract limits their length. SQL Server truncates a longer parameter without an error, so two ids that share 50 characters become one flow or one key.
Evidence
Latent: the longest identifier in a week of September 2026 is 36 characters (P15).
Where in the code
StoredProcedures/TransactionFlowCoreInsertOrUpdate.sql:7, TransactionFlowGetById.sql:9, TransactionCoreInsert.sql:4, 5, 13, AccountBalanceSaveAndGetTimestamp.sql:10, TransactionCoreGetByIdempotencyKey.sql:10.
How to fix
- Widen the procedure parameters and THROW when a value exceeds 50 characters.
- Add
[MaxLength(50)]to the contract properties.
How to verify
- Test: a 51-character flow id is rejected with a 400.
Related
CreditUnsecured accounts have no balance floor
- Services
- be-configuration
- Seen in prod
- Latent: 0 CreditUnsecured accounts exist (W12).
- Impact
- Latent. CreditUnsecured cards would have no spending floor.
What happens
The prod rules config has rules only for CheckingPrimary, CheckingPocket and CreditSecured, and limit rows apply only to rules that exist, so the ledger never declines a CreditUnsecured authorisation. The LithicLoanproCreditUnsecured program is configured in prod.
Evidence
Latent: 0 CreditUnsecured accounts exist in the ledger (W12).
Where in the code
be-configuration src/prod/transactions-accountlimitrules-config.json; be-transactions AccountBalanceRulesService.cs:80–100.
How to fix
- Add a per-account floor for CreditUnsecured, driven by
AccountLimits, before the LithicLoanproCreditUnsecured program issues cards.
How to verify
- Test: a CreditUnsecured authorisation beyond the account's limit is declined.
Related
Rain crypto authorisations would approve anything
- Services
- be-ledger
- Seen in prod
- Latent: no Rain issuance, and CryptoCardEnrolmentEnabled is false in prod.
- Impact
- Latent. Rain card authorisations would be approved for any amount.
What happens
Where in the code
How to fix
How to verify
- On stage: a Rain authorisation on an account with no balance row is declined.
Related
Balance history: infinite retention, AS OF on begin time, error 13535
- Services
- be-ledger
- Seen in prod
- Yes: 369 failed calls in the 3 months to 9 October 2026.
- Impact
- Failed card events and postings, storage that grows without limit (86.8 GB), and statement cut-offs that can disagree with the balance.
What happens
Migrations 029 and 030 re-enabled system versioning without a retention clause, which means infinite. AS OF queries use transaction begin time, so a statement's opening balance plus its transactions need not equal the closing balance at a cut-off. A batch's later legs can hit error 13535. There is no history index on BalanceTime or on the numeric TransactionId.
Evidence
Retention INFINITE: migrations 002 and 006 set 2 months, and 029 and 030 re-enabled versioning with no retention clause. AccountBalanceHistory holds 357,992,590 rows on 12 October 2026 (P25b); at 356,171,057 rows it took 86.76 GB with indexes (P1, P3). On the 231 accounts replayed it reaches back to their first journal row, from 20 March 2025 (P27). Its index IX_AccountBalanceHistory_TransactionId keys ExternalTransactionId, the column migration 030 renamed from TransactionId; the numeric TransactionId and BalanceTime have no history index. Error 13535 failed 369 calls in the 3 months to 9 October 2026, the first on 12 July, all in AccountBalanceSaveAndGetTimestamp; 15 were authorisation declines, none wrongful. Every one of the 369 also carries "Violation of PRIMARY KEY constraint 'PK_AccountBalance'": with no XACT_ABORT or TRY/CATCH, the procedure reads @@ROWCOUNT as 0 after the failed UPDATE and tries to INSERT a second balance row for the account, which the primary key rejects. 1–7 October: 25 failed calls (2 authorisations, 17 card events, 6 be-transactions postings); retries not traced.
Where in the code
029_AccountBalance_BalanceTime.sql:9; 030_Add_InternalTransactionId_AccountBalance.sql:12–14; StoredProcedures/AccountBalanceGetAllByDate.sql:14–17; StoredProcedures/AccountBalanceSaveAndGetTimestamp.sql:31–33.
How to fix
- Add
SET XACT_ABORT ON, or TRY/CATCH, toAccountBalanceSaveAndGetTimestamp, so a failed UPDATE stops instead of falling through to the INSERT. - Retry error 13535 in the caller.
- Keep the balance history until the movement table of DB-7 exists, then set a retention policy: it is the only record of balance moves that have no journal row.
- Add history indexes on
(AccountId, BalanceTime)and on the numericTransactionId;IX_AccountBalanceHistory_TransactionIdkeys the external id. - Cut statements on
BalanceTime, not AS OF.
How to verify
- Elastic: error 13535 and
PK_AccountBalanceviolations from be-ledger drop to 0.
Related
Corrections are not journaled; money tables can be edited by hand without a trace
- Services
- be-ledger
- Seen in prod
- No corrections recorded: LedgerCorrection has 0 rows (P8, P25b). Corrections were made: the 30,576 type-7 rows on GA= flows (W40); balance writes with external id FixGalileoExp-<account id> on 8 December 2025 at 13:59:57 UTC and 27 January 2026 at 13:29 UTC, the last write on 22 accounts whose reserve differs from their flows (P4b), and where replayed such a write has no journal row (P26); and 3 balance writes of June and July 2025 with no journal row of their own, −$2,677.61 (P27). Flows were changed with no journal row: holds were cleared on 8 December 2025 at 14:00:02 UTC, with Updated changed and TimeStamp kept, on 16 flows of 10 accounts replayed row by row (P26) and 9 flows of 4 accounts replayed version by version (P27). The 232,263 AD= rows of June to December 2025 were posted as movements, each on its own flow (P28). The database also holds two tables that no be-ledger migration creates: AffectedAccountIds_20260424, 58,537 rows, created 24 April 2026, and temp.ReservedReconcile, 39,028 rows, created 15 June 2026 (P25b).
- Impact
- A hand edit to a money table leaves no trace and cannot be audited.
What happens
LedgerCorrection has no write procedure and is filled by hand, apart from the change itself, so the two are not atomic. Nothing protects TransactionCore, TransactionFlowCore or AccountBalance from direct edits.
Evidence
LedgerCorrection has 0 rows (P8). Corrections found in the journal, such as the 30,576 type-7 rows on GA= flows (W40), were posted as movements, which is the right pattern, but none is recorded in LedgerCorrection.
Where in the code
031_Create_LedgerCorrection_Table.sql.
How to fix
- Add a
LedgerCorrectionApplyprocedure that changes and records in one transaction. - Remove direct write rights on
TransactionCore,TransactionFlowCoreandAccountBalancefrom human roles. - Move tables that no migration creates out of the ledger database, or create them by migration.
How to verify
- A role audit shows no human role with write rights on the money tables.
- A test correction writes both the change and its record, or neither.
- The catalog lists no table that no migration creates.
Related
Partitioning slows every point lookup and blocks unique keys
- Services
- be-ledger
- Seen in prod
- Yes: about 50 page reads per point lookup; the worst card-path calls take 3.4–4.3 s.
- Impact
- Slow card-path calls and no way to add the unique keys DB-2 and DB-6 need without non-aligned indexes.
What happens
Boundaries were fixed at deploy time with every partition on PRIMARY, and nothing splits, merges or switches them. Every index is aligned, so a lookup without a time predicate seeks each partition, and the only unique keys possible are non-aligned ones. TransactionFlowCore is partitioned on a column rewritten on every event.
Evidence
101 boundaries from 2024-09-01 to 2033-01-01; the orphan TransactionHistoryPartitionFunction runs from 1905-07-05 to 1913-11-05 (P1). About 50 page reads per point lookup; the worst card-path calls take 3.4–4.3 s.
Where in the code
012_Recreate_Transaction_Core_Table.sql:10–41; 013_Create_System_Account_Table.sql:2–14; orphan 003_Create_Transaction_Table.sql:1–14.
How to fix
- Keep
TransactionCorepartitioned for range scans and add the non-aligned unique indexes (DB-6). - Un-partition
TransactionFlowCore. - Drop the orphan
TransactionHistoryPartitionFunctionand its scheme.
How to verify
- Query Store: logical reads per point lookup drop from about 50 pages to single digits.
Related
Balance inquiry takes an update lock; batches lock accounts in list order
- Services
- be-ledger
- Seen in prod
- Not measured.
- Impact
- Balance inquiries wait on authorisations, and two batches over the same accounts in opposite order deadlock and retry.
What happens
Every balance read serializes with authorisations on that account. Two batches with the same accounts in opposite order deadlock and the whole message is retried. AccountInsertOrUpdate opens a transaction with no XACT_ABORT.
Where in the code
Minority.Ledger.Service/Account/AccountService.cs:44–51; StoredProcedures/AccountBalanceGetById.sql:10–12; SaveTransactionsAction.cs:194–196; StoredProcedures/AccountInsertOrUpdate.sql:11–26.
How to fix
- Serve balance inquiries from a lock-free read procedure; RCSI is on.
- Sort batch accounts before locking them.
- Make account create a single statement.
How to verify
- Deadlock graphs for be-ledger drop to 0; inquiry calls no longer wait on authorisation locks.
Related
Report queries page with OFFSET over non-unique sorts
- Services
- be-ledger
- Seen in prod
- Not measured.
- Impact
- Report pages can skip or repeat rows, and reads grow with the page number.
What happens
Ties can skip or repeat rows across pages and reads grow quadratically; TransactionType < 5 also includes UnKnown (−1). GetAllByIdRange has no time bound and a catch-all predicate with pages up to 100k rows.
Where in the code
StoredProcedures/TransactionCoreGetAllAuthTransactionsByTimeStamp.sql:22–27, TransactionCoreGetAllByTimeStampAndType.sql:27–29, TransactionCoreGetAllByIdRange.sql:8–13.
How to fix
- Page with a keyset on
(TimeStamp, TransactionId). - Require a time range on
GetAllByIdRange, or addOPTION (RECOMPILE). - Use explicit column lists, and exclude UnKnown (−1) explicitly.
How to verify
- A paged export over a fixed range returns each row exactly once.
Related
Lithic replays add journal rows and events with new keys
- Services
- be-lithic
- Seen in prod
- Not measured.
- Impact
- Sums of Amount double count replayed events; balances are not affected.
What happens
A non-targeted reconciled replay keys the posting with a new GUID, so every replay of an already recorded event adds a journal row and an event carrying the event amount. Balances do not change, because the flow state is the same, but sums of Amount double count.
Where in the code
be-lithic Minority.Lithic.Service/Transactions/TransactionCallbackService.cs:510–517.
How to fix
- Key replays by the Lithic event token instead of a new GUID.
How to verify
- Replaying an event that is already recorded adds no journal row and no event.
Related
Authorisation events carry the cumulative hold, not the change
- Services
- be-ledger
- Seen in prod
- By design.
- Impact
- Consumers that sum Amount per flow overstate holds.
What happens
The auth event's Amount is reserved minus settled, cumulative per flow, its id is the Lithic token rather than ML=, and its key is about 90 characters. Consumers that sum Amount per flow overstate incremental holds.
Where in the code
SaveTransactionsAction.cs:146–164.
How to fix
- Publish the change in the hold as the event's Amount.
- Document the event contract, including the id and key formats.
How to verify
- For a sample of flows, the consumer's sum of Amount equals the flow's final reserved amount.
Related
The card authorisation's ledger call has no deadline
- Services
- be-lithic
- Seen in prod
- Not measured.
- Impact
- Lithic declines while the hold stays written, so the customer's money is held for a declined purchase.
What happens
Rules and validate calls are deadline-bounded but the ledger Auth is not. Lithic times out and declines while the hold is still written.
Where in the code
be-lithic Minority.Lithic.Service/Authorization/AuthService.cs:293 against :275–280, 437.
How to fix
- Give the ledger Auth call a deadline below Lithic's ASA timeout.
- When the deadline passes, release the hold.
How to verify
- Test with a slow ledger: the authorisation is declined and no hold is left.
Related
Ledger errors swallowed by callers
- Services
- be-credit be-remittance
- Seen in prod
- Not measured.
- Impact
- An account stays debited without its credit until someone replays the dead-letter queue, and failed remittance releases are only logged.
What happens
be-credit returns null from Credit on any error and dead-letters, leaving an account debited without its credit until someone replays the queue. be-remittance only logs release failures.
Where in the code
be-credit Minority.Credit.Service/MoneyTransfer/EventHandlers/TransferDebitedEventHandler.cs:75–81, 90–93, 122–139; be-remittance Minority.Remittance.Service/V3/Transaction/TransactionHelper.cs:103–153.
How to fix
- be-credit: return an error instead of null, and alert on the credit dead-letter queue.
- be-remittance: retry release failures and alert when they persist.
How to verify
- Fault-injection test per service: a funds error leaves the message retrying, not completed.
Related
Lithic's transaction record and its settlement report disagree on Visa stand-in transactions
- Services
- be-lithic
- Seen in prod
- Yes: 5 transactions, on 25 July and 31 August 2026, the only native card transactions from April to 5 October 2026 where the ledger differs from Lithic's settlement report (W35). On all 5 the ledger equals Lithic's transaction record.
- Impact
- Either 4 customers are owed $287.73 of credits and a $1.99 purchase was never charged, or no money moved and the reset left 3 of the 4 credited accounts negative. Only Lithic can say which. Nothing flags either case, because nothing compares the ledger with Lithic.
What happens
When Lithic does not answer Visa in time, Visa approves the transaction on its own (stand-in) and tells Lithic through an advice whose result is SWITCH_INOPERATIVE_ADVICE. On 5 such transactions Lithic's own record ends with $0.00 authorised and $0.00 cleared, and its dashboard shows the advice Declined, while Lithic's settlement report lists each one as settled. be-lithic books the credit or the purchase as soon as Lithic approves the authorisation, then turns the declined advice into a DeniedAuth with Lithic's transaction-level amounts, so the flow ends at 0. The ledger therefore agrees with Lithic's transaction record and not with its settlement report. The customer has the credit for 1 to 7 minutes, and a customer who spends it in that time is left negative. Which of Lithic's two records reflects the money that moved is for Lithic to answer; nothing in the ledger can decide it.
Evidence
25 July and 31 August 2026 (W30, W35): 5 card transactions on 5 accounts, the only native card transactions from April to 5 October 2026 where the ledger differs from Lithic's settlement report. Four are Visa Money Transfer credits of $253.83, $12.75, $1.50 and $19.65, and one is a $1.99 purchase. Each was booked when Lithic approved it, and a DeniedAuth row 1 to 7 minutes later set the flow's settled amount to 0. That is what Lithic's transaction data shows on every event of all 5, and what its dashboard shows: $0.00 authorised, $0.00 cleared. The settlement report lists all 5 as settled, on 25 July and 31 August, and the ledger is $285.74 net below it. The reset left 3 of the 4 credited accounts negative. Example: native flow f9a6708e-724d-5527-ae4d-fcbe7c4a47b4 on account 45b6527f-060d-4ce1-abe8-89174fa18159, Lithic token a7d43f11-ae23-4630-bfe0-a936278a33ee: credited $253.83 at 05:10:44 UTC, then ML=418357805 (type 4) at 05:11:51 UTC set it to 0 and left the account at −$253.77. The other stand-in transactions do not differ: 39 that settled match the report, 13,782 that did not settle are zero, and 4 carry money only on a GA= flow.
Where in the code
- be-lithic
Minority.Lithic.Service/Infrastructure/LithicEnumExtensions.cs:110: any event on a Declined transaction becomes DeniedAuth. Minority.Lithic.Service/Transactions/TransactionCallbackService.cs:187–200: on a Declined transaction the hold is set to 0 and the settled amount is Lithic's transaction-level settlement amount;:420–436passes the same amounts for every event.Minority.Lithic.Shared.Contract/TransactionResult.cs:185–188definesSwitchInoperativeAdvice, and nothing handles it. The stand-in test inMinority.Lithic.Test/Service/Transactions/TransactionFlowAmountAndTypeTest.cs:203–207carries "Todo: Check with lithic if there is clearing message".
How to fix
- Ask Lithic, for the 5 tokens, which record reflects the money that moved: the transaction record ($0.00 authorised and cleared) or the settlement report (settled on 25 July and 31 August).
- If the settlement report: credit the 4 customers $287.73, one correction per flow, and decide whether to charge the $1.99 or write it off. If the transaction record: leave the flows at 0 and decide on the negative balances the reset left.
- In be-lithic, handle an event with result
SWITCH_INOPERATIVE_ADVICEexplicitly, as Lithic's answer says, instead of through the transaction's Declined status. - Run the W35 comparison every day against both the settlement report and Lithic's transaction data, and alert on any transaction where the two disagree with each other or with the ledger.
How to verify
- be-lithic unit test: a stand-in credit followed by a declined advice ends as Lithic's answer says.
- W35 returns 0 native transactions where the ledger differs from the record Lithic confirms.
Related
The caller can set a flow's previous state and an account's first balance
- Services
- be-ledger
- Seen in prod
- Yes: on 1,100 accounts opened 1–8 April 2025 a deposit, fee or credit was counted twice, +$54,793.43 net; reconcile postings took each back out of the balance by 4 July 2025, and the flows remain off by that amount (P24b, P30, P31).
- Impact
- A caller can move a balance without a journal record of the move, a movement can be applied twice, and the balance cannot be rebuilt from the journal.
What happens
Every posting can carry the flow's previous reserved and settled amounts (PreviousReservedAmount, PreviousSettledAmount) and the account's balance (Balance). The contract documents the previous-state fields as used only for the Galileo migration. When be-ledger does not know the flow, available moves by the new state less the caller's previous state, while the reserve moves by the full new reserved amount, so the two can disagree. When the account has no balance row, or its row is the "Init" row, available is set to the caller's Balance instead of being computed, so whatever that figure already includes is counted again when its own journal row arrives. In the code read for this audit, two callers set these fields: be-transactions' reconcile handler sets Balance and PreviousSettledAmount (CL-30), and be-lithic's Galileo flow restore sets both previous-state fields.
Evidence
Accounts opened in April 2025, after the journal began (P24b, P30): on 1,100, all opened 1–8 April 2025, the first journal row written carried a balance that already included the next one to three rows, stamped within 5 seconds of it, and each of those rows moved the balance again by its own amount: 687 PM= deposits written after an FE= fee, +$48,917.21; 336 FE= fees written after a PM= deposit, −$1,327.20; PMA= credits on 76 accounts, +$7,103.42; and 1 PM= deposit, +$100.00. None of 300 accounts opened 1–8 April 2025 that do not differ from their flows has a non-zero opening balance. Postings consistent with be-transactions' reconcile handler took the doubled amount back out of all 1,100 balances between 2.8 hours and 88.5 days later (CL-30, P31), so the balances are right and the flows differ from them by the doubled amount. On the 231 accounts replayed version by version (P27), all 1,060 breaks are first journal rows of their flow, 1,055 of them of March 2025, and at 940 the flow moved while the balance did not, which is what a caller-supplied previous state equal to the new state produces.
Where in the code
- be-ledger
Minority.Ledger.Service/Transactions/Internals/SaveTransactionsAction.cs:362–368: the account's reserve moves by the new reserved amount less the flow's stored one, and an unknown flow counts as zero;:372: an unknown flow takes the caller's PreviousReservedAmount and PreviousSettledAmount as its previous state;:375–377: with no balance row, or the "Init" row, available is the caller's Balance, otherwise the old available less the previous state plus the new;:404: a partial authorisation on an unknown flow reads PreviousReservedAmount;:286–287pass the posting's Balance. Master b4bd523; prod 75725be has the same code at:356–362,:366,:369–371,:398and:281–282. Minority.Ledger.Service.Contract/Transactions/Models/BaseTransactionModel.cs:22–34: PreviousReservedAmount and PreviousSettledAmount, each documented as used only for the Galileo migration and due for removal;TransactionRecordModel.cs:14–18: Balance, documented as a starting balance to be removed once all balances are backfilled;Minority.Ledger.Service/LedgerStatics.cs:6: the "Init" external id.- be-transactions
Minority.Transactions.Service/Account/EventHandlers/ReconcileLedgerBalanceMessageHandler.cs:66, 93sets Balance and PreviousSettledAmount (CL-30). - be-lithic
Minority.Lithic.Service/Transactions/TransactionCallbackService.cs:520–531, called at:473and:506, withMinority.Lithic.Service/Transactions/ReconcileTransactionFlowHandler.cs:114–116: the Galileo flow restore sets both previous-state fields.
How to fix
- Remove PreviousReservedAmount, PreviousSettledAmount and Balance from the posting contract, or accept them only behind a migration flag that is off in prod.
- Start an account's first balance at zero and compute it from its journal rows; reject postings on unknown accounts (DB-8).
- Retire the callers that set them: be-transactions' reconcile handler (CL-30) and be-lithic's Galileo flow restore.
- Record corrections on the flows of the 1,100 April 2025 accounts, as on the Opening balances page.
How to verify
- Unit tests: a posting on an unknown flow that carries a previous state is rejected; an account with no balance row starts from zero whatever Balance the caller sends.
- P24b finds no account opened after the change whose first journal row starts from a non-zero balance.
Related
be-transactions' reconcile handler zeroes an account's reserve on a new flow
- Services
- be-transactions
- Seen in prod
- Yes: 761 flows, May to September 2025, carrying −$12,604.49 of holds and +$8,358.26 of credit reserves (P29); and on 1,100 April 2025 accounts, April to July 2025, postings that took a doubled amount back out of the balance, moving the error from the balance to the flows (P31).
- Impact
- Holds released this way stay open on their flows, so a later event or expiry releases them a second time; reserves cannot be rebuilt from the flows; and a failed processor inquiry can reset a balance to a stale or zero figure.
What happens
be-transactions' ReconcileLedgerBalance handler compares the processor's disposable balance for an account with be-ledger's available balance. When they differ and the account has a reserve, it waits a day unless available less the reserve equals the processor's balance. Otherwise it posts a Settlement on a new random flow, with a fresh random external id and idempotency key, whose reserved amount is the negative of the account's reserve, whose settled amount is the difference between the two balances, and whose Balance is the processor's balance. be-ledger then sets the account's reserve to zero and leaves every other flow as it is: holds stay open on their flows, so a later event or expiry on one of them releases it again. If the balances differ after it, the handler posts a second event on the same flow that undoes the first, and tries again an hour later. When the processor inquiry fails, the processor balance falls back to the balance of the account's most recent stored transaction, or zero. For a Galileo-type account with the Galileo ledger switched off, the inquiry resolves to be-ledger itself. In the Backend repositories nothing publishes to its queue except the handler's own reschedule.
Evidence
761 of the 762 stale uuid4 flow reserves that match no be-transactions record (W47) are Settlements of 0 with nothing settled, each the only journal row of its flow and with an idempotency key, whose reserve is the negative of the account's reserve just before and after which the account's reserve is 0 (P29). They were written in May 2025 (751), June (3), July (2) and September (5). 647 are holds, −$12,604.49, on accounts whose reserve was positive; 110 are credit reserves, +$7,920.64, that offset 182 GA= holds on 110 accounts; 4 are credit reserves, +$437.62. On all 1,100 April 2025 accounts with a movement counted twice (DB-20), Settlements of the shape this handler writes, each the only row of a new flow with a GUID external id, a different GUID idempotency key and no reserve, took the doubled amount back out of the balance between 2.8 hours and 88.5 days later: 1,023 in April 2025, 23 in May, 3 in June and 51 on 4 July 2025 between 10:55 and 14:57 UTC; on 1,097 one posting of exactly that amount, on 3 two that together come to it (P30, P31). The balances were brought back, and each posting stays in the flows, so the difference between balance and flows remains. On ea051237-0d8f-421d-ab7e-f0f687ffd59a one more, on 10 May 2025, settled nothing and put a reserve of −$5.00 on a new flow, the reserve difference that account shows.
Where in the code
- be-transactions
Minority.Transactions.Service/Account/EventHandlers/ReconcileLedgerBalanceMessageHandler.cs:44–48: the processor's balance against be-ledger's;:53–57: a day's wait;:59–78: the Settlement on a new flow, with Balance at:66, the reserve's negative at:67and the difference as the settled amount at:68;:80–84: the check after it;:86–102: the second event that undoes it, with PreviousSettledAmount at:93;:103: the retry an hour later. Minority.Transactions.Service/Account/MajorityAccountService.cs:187–193: the fallback on an inquiry error.Minority.Transactions.Service/LedgerGateway/LedgerGateway.cs:114–115andLedgerGatewaySwitch.cs:29–32: a Galileo-type account resolves to be-ledger when the Galileo ledger is off;Minority.Transactions.Service/TransactionServiceModule.cs:235registers the handler.Minority.Transactions.Service.Contract/Account/Models/Events/ReconcileLedgerBalanceMessage.cs:12: the queuereconcile-ledger-balance.
How to fix
- Retire the handler and its
reconcile-ledger-balancequeue. - Close the 761 flows with recorded corrections on the flows only (DB-11): each of the 110 credit reserves together with the GA= holds it offsets, and each of the 647 holds on its own, so no balance moves.
- Record corrections on the flows of the 1,100 April 2025 accounts whose double count it reversed, so their flows match their balances (Opening balances).
- If a processor-balance check is kept, stop on a processor error instead of falling back to a stored balance or zero.
How to verify
- The
reconcile-ledger-balancequeue has no consumer and no messages. - P29 finds no uuid4 Settlement of 0 written after the change.
- P4b returns 0 accounts whose reserve differs from their flows.
Related
Low severity
Hygiene, dead code and narrow edge cases. None moves money today; each is worth fixing when the code around it changes.
| ID | Issue | Where |
|---|---|---|
| DB-15 | Amounts are money (4 decimals) while C# publishes unrounded decimals; measured 0 sub-cent rows. | SaveTransactionsAction.cs:272–275, 339–349 |
| DB-16 | No system-account balances: the system tables stopped receiving rows in February 2026, no code writes them, and their dedup would drop a second system leg if revived. | StoredProcedures/SystemAccountBalanceChangeHistorySave.sql:11–12, 29–30 |
| DB-17 | Dead procedures and tables; deleting SystemAccountBalanceChangeHistorySave.sql would drop nothing, because the file creates a differently named procedure. | be-platform DbUp Repository.cs:49–54 |
| DB-18 | Nullable AccountId on journal and flow rows skips the owner check; sentinel "Init" and Guid.Empty drive logic without constraints; FILLFACTOR 81 on append-only indexes; AGENTS.md says "No HTTP API" but v1/account exists. | SaveTransactionsAction.cs:451; 015_Add_Account_Id.sql |
| CL-15 | A manual, publisher-less ReconcileLedgerBalance handler zeroes reserved without touching the flows and double-applies on a re-run. | be-transactions ReconcileLedgerBalanceMessageHandler.cs:61–102 |
| CL-16 | Entity.Amount is caller-supplied and never checked against the reserved and settled amounts; it only feeds the journal, events and warehouse. | SaveTransactionsAction.cs |
| CL-17 | The idempotency window starts 3 months back while the age gate allows 12; a late re-post duplicates the journal row and event, not the balance. | TransactionCoreGetByIdempotencyKey.sql:13–17; SaveTransactionsAction.cs:446–450 |
| CL-18 | ResolveSystemAccountId returns Guid.Empty for a null or unknown code. | be-transactions MajorityLedgerProcessorGateway.cs:439–454 |
| CL-19 | Charge-off and tier downgrade use the available balance, net of holds, so later releases move money on zeroed accounts. | be-wallet ChargeOffAccountMessageHandler.cs:71–116; TierUpdateAction.cs:192–219 |
| CL-20 | Card risk checks fail open on deadline and generic errors. | be-lithic AuthService.cs:212–221 |
| CL-21 | The Lithic callback publish is not outboxed; redelivery recovers through key dedup. | be-lithic TransactionCallbackService.cs:451, 457 |
| CL-22 | The flow-owner guard is skipped for legacy flows with no AccountId. | SaveTransactionsAction.cs:451 |
| CL-25 | be-cashdeposit ids assume Incomm never reissues an account number; a reissue would deduplicate a real deposit. | be-cashdeposit Transaction/TransactionIdGenerator.cs |
| CL-26 | Dead code: ReservationAlreadyDebitedException never thrown, be-calling duplicate-reservation retry, Galileo release key, mutable _useGalileoLedger. | be-transactions TransactionProcessorGateway.cs:540 |
| OPS-2 | 5,376 Lithic detail rows (5,245 debit, 131 credit) from 24–29 July 2026 carry the masked token "******", which merges unrelated transactions under one transaction token; they can be compared only by event token. | dbt_semantic.fact_lithic_card_transaction_detail |
| CL-28 | be-lithic's settlement-report handler has no case for the Rain crypto program: it saves the report row, then throws ArgumentOutOfRangeException on every delivery, although LithicClientFactory supports the program. No ledger posting depends on it; a daily reconciliation against Rain's settlement report would. | be-lithic Minority.Lithic.Service/Settlements/EventHandlers/SettlementReportUpdatedEventHandler.cs:57, 65, 220–227 |
| CL-29 | Dead code left by the removed settlement-report posting to be-ledger: an unused IsDebit method, an unused LedgerTransactionType alias and an ignored test. | be-lithic Minority.Lithic.Service/Settlements/Actions/SettlementDetailBroadcastAction.cs:33, 234–240; Minority.Lithic.Test/Api/EventControllerTest.cs:99 |
| DB-19 | A journal row's TimeStamp was the time its caller passed in until commit 071125b (2 September 2025), and has been the balance's write time since. In 2025 it is not write order: on the 27 accounts replayed row by row, 717 of 21,774 consecutive 2025 rows in time order have a lower TransactionId than the row before, against 0 of 7,888 in 2026 (P26). Taken by time instead of TransactionId, the opening balance of accounts opened before April 2025 is $636,695.48 higher, +$41,222,329.76 against +$40,585,634.28, and 73,321 accounts instead of 66,927 seem to differ beyond it (P24, P24b). Even in TransactionId order, two rows written on one account within seconds can carry their balance snapshots the other way round: 116 rows on 14 of those accounts break in opposite pairs within 60 seconds that cancel, the last on 13 February 2026. A replay must order by TransactionId and read such a pair as one movement. | be-ledger Minority.Ledger.Service/Transactions/Internals/SaveTransactionsAction.cs:321 (master; :316 in prod); Minority.Ledger.Db/Migrations/StoredProcedures/TransactionCoreInsert.sql:12, 44 |
| DB-21 | Two procedures that nothing calls remain in the database and the repository: AccountBalanceSaveAndGet and TransactionCoreInsertAndGet, created in February 2025 and last modified on 3 February 2026, the day their replacements AccountBalanceSaveAndGetTimestamp and TransactionCoreInsert were created (P25b). be-ledger binds procedures by attribute, and no attribute names either, so a change made to the old file instead of the new one would deploy and never run. | be-ledger Minority.Ledger.Db/Migrations/StoredProcedures/AccountBalanceSaveAndGet.sql; TransactionCoreInsertAndGet.sql; the bindings in Minority.Ledger.Service/AccountBalance/Repositories/IAccountBalanceRepository.cs:19 and Minority.Ledger.Service/Transactions/Repositories/ITransactionCoreRepository.cs:25 |
| DB-22 | SystemAccountBalanceChange has 0 rows and its history table 7,615,896 (P25b). Migration 028 creates the table and five procedures read and write it, but no be-ledger code at master b4bd523 binds any of them. | be-ledger Minority.Ledger.Db/Migrations/028_SystemAccountBalanceChange.sql; Minority.Ledger.Db/Migrations/StoredProcedures/SystemAccountBalanceChange*.sql |
Method and sources
How the numbers on these pages were produced, and what they do not cover.
Check ids
- P: a query on prod-ledger-use2-sqlDb, or on its read replica for whole-table checks.
- W: a query on the dw-prod-gwiiag warehouse, dry-run first.
- Elastic:
minority-backend-prodlogs, last 3 months.
Compared
- The settled-balance chain row by row, each balance against its last journal row, and each flow's state against its journal rows.
- The journal against Lithic per card transaction, joined on the flow id be-lithic derives from the transaction token, to the cent; where they differed, against Lithic's settlement report.
- The journal against be-transactions by the key each caller sends, and in reverse by transaction id, external id or flow id, grouped by key shape and period; Galileo-era records by daily totals and, for 15 April, record by record (W14, W17, W18, W20).
- Ledger events against journal ids, and the logs around every gap.
- be-ledger's code at prod 75725be and master b4bd523, be-lithic's at master d855900, and every caller that posts to the ledger.
Sources
- prod-ledger-use2-sqlDb: catalog, Query Store, partition metadata and bounded reads (P-numbered).
- dw-prod-gwiiag:
dbt.fact_ledger_transaction,dbt.fact_transaction,dbt_semantic.fact_lithic_card_transaction_detail,dbt_semantic.fact_lithic_debit_card_transaction(W-numbered). - Elastic
minority-backend-prod, last 3 months. - W39–W46: every Lithic transaction on the credit-card program; every GA= flow that carries a Lithic event, together with the native flows of those transactions, compared with Lithic's per-event data and with Galileo's own rows on each flow; and every settlement-report row on a card flow. W41b read the warehouse's ledger copy to 12 October 2026, 07:55 UTC.
- P4b, P18r, P21, P22 and P23 on the prod-ledger read replica on 12 October 2026, 08:10–08:21 UTC, one at a time, each started only when the replica's recent CPU and data IO were under 70%; the highest reading before any of them was 18.85% CPU and 42.76% data IO. P4b read under one snapshot.
- be-lithic at master d855900, for the settlement-report path and its history.
- P24 and P26 on the prod-ledger read replica on 12 October 2026, 09:49–09:55 UTC, one at a time behind the same gate; the highest reading before either was 19.36% CPU and 12.41% data IO. P24 read every account's balance, its flows and its first journal row by time between 1 March and 30 April 2025, and P24b, below, repeats it in write order and replaces it; P26 read every flow and every journal row since 1 March 2025 of 27 accounts.
- W47: the 813 stale flow reserves on uuid4 flows, looked up in
dbt.fact_transactionby transaction flow id, transaction id, external transaction id, original transaction id and external original transaction id. - P24b, P25b, P27, P28 and P29 on the prod-ledger read replica on 12 October 2026, 11:34–11:53 UTC, one at a time behind the same gate; the highest reading before any of them was 3.71% CPU and 5.3% data IO. P24b repeats P24 in TransactionId order; P27 replayed 231 accounts' balance history, 293,609 versions, against their journal rows; P28 read every AD= row; P29 read the 762 uuid4 flows of W47 and their rows; P25b read the flows on the empty account id and the table catalog.
- be-transactions at master 328f69689, for the reconcile handler and the balance inquiry path.
- P30 on the prod-ledger read replica on 12 October 2026 at 14:24 UTC, behind the same gate, after a reading of 0.0% CPU and 0.0% data IO: the first eight journal rows, in write order between 1 March and 31 May 2025, of the 1,111 April 2025 accounts that differ from their flows and of 300 accounts opened 1–8 April 2025 that do not, with all their flows.
- P31 on the prod-ledger read replica on 13 October 2026 at 08:12 UTC, behind the same gate, after a reading of 0.0% CPU and 0.0% data IO, 346 seconds: every journal row since March 2025 on the 308,236 flows of the 1,100 April 2025 accounts with a movement counted twice, returning only rows whose amount reverses the doubled amount, single-row settlements, and rows where the balance moved by something other than the row's own amount.
Load safety
- Read-only throughout.
- Prod queries at MAXDOP 1 over bounded windows, with FORCESEEK and NOLOCK and no history scans, behind a resource check; peak data IO was 45%, during P16.
- Whole-table checks go to the readable secondary and abort unless it reports READ_ONLY.
- Every warehouse query was dry-run first.
Limits
- The databases of be-pspfunding, be-phoneplan, be-mtu and prod-wallet were not queried, so CL-2, CL-3 and CL-8 are unmeasured.
- The Lithic checks cover credit-card transactions from September 2025 and debit-card transactions from April 2026, when debit cards moved to Lithic. The warehouse's journal copy is complete from October 2025.
- GA= flows cannot be checked against a Galileo processor file; they are checked against Lithic and against Galileo's own rows in the ledger.
- Holds are verified only as flow state (DB-7).
- No journal row is older than 20 March 2025, 12:11 UTC, so accounts opened before April 2025 are compared with their flows through an opening balance taken from their first journal row in write order (P24b). It explains +$40,585,634.28 of their +$47,033,607.61 difference; the +$6,434,523.47 beyond it, on 66,927 accounts, is replayed version by version on 231 accounts (P27) and row by row on 27 (P26).
- P21 can compare a balance with its journal row only where the balance names one: 676,914 of 1,544,133. The other 867,219 are compared with their flows only (P4b).
- P22 looked up the phantom event ids of October 2025 to September 2026 in prod, each within two days of its event; the 44 from 1–7 October 2026 were not looked up.
Data quality
- The Lithic detail table carries the transaction's cumulative settled amount on every event, so the latest event's amount was used. Where two clearings share a timestamp, the larger amount is the later one.
- Some Lithic transactions have no program in the detail table. W39 counts those from before April 2026 as credit-card transactions, since only credit cards were on Lithic then (5,007); W40 selects by flow, not by program, so it includes the 149 on GA= flows.
- Galileo's AuthExpiry and AuthExpiryReversal rows copy the flow's merged settled amount, so Galileo's own settled amount is read from its Auth, Settlement and AuthClearing rows only.
- OPS-2. The settlement report's
settled_amountalready carries the sign, purchase positive and credit negative; W35 sums it as it stands. W13a and W31 multiplied it bytransaction_sign, which sums absolute values and is wrong for a transaction with both a purchase and a return; W35 re-checked their conclusions. - A settlement-report row is recognised as a type-5 row keyed by a uuid5 token that is in no Lithic event. That test holds only on card flows: elsewhere it also matches ordinary postings whose id is a uuid5, 572,404 type-5 rows in September 2026 alone (W45), so W46 counts card flows only.
dbt.fact_transaction.ledger_account_iddoes not match the ledger's account id, and itsledger_keyis a hash; neither was used as a key.- A journal row's
TimeStampwas supplied by its caller before commit 071125b (2 September 2025) and in 2025 is not write order (DB-19). Opening balances, replays and first rows here are taken inTransactionIdorder (P24b, P26, P27); taking them by time moves the opening balance of accounts opened before April 2025 by $636,695.48. TransactionFlowCore.TimeStampis the time of the flow's last journal row.Updatedalso changes when a flow is updated directly, which is how the 16 flows cleared on 8 December 2025 with no journal row show.Account.Createdis when the ledger first saw the account; "opened" in this audit means that date.
References
Where to look further. No number in this audit comes from these documents; every number comes from the audit's own queries.
be-docs
Repository majority-dev/be-docs, branch master.
docs/Areas/Core-Banking/Ledger.mddocs/Areas/Core-Banking/Lithic-Galileo-Transaction-Routing.mddocs/Issues-and-Repairs/Galileo-Flow-Repair-BAN-306.mddocs/Issues-and-Repairs/BAN-306/galileo-flow-register.htmldocs/Issues-and-Repairs/BAN-306/crm-impact-lists.htmldocs/Issues-and-Repairs/Ledger-Repair-BAN-348.mddocs/Issues-and-Repairs/BAN-348/ledger-repair-register.htmldocs/Issues-and-Repairs/Ledger-Leftovers-BAN-348.mddocs/Issues-and-Repairs/BAN-348/ledger-leftovers.html
Code
- be-ledger: prod 75725be (the 9 September 2026 build) and master b4bd523
- be-lithic: master d855900
- be-transactions: master 328f69689
Data
- prod-ledger-use2-sqlDb and its read replica
- dw-prod-gwiiag:
dbt.fact_ledger_transaction,dbt.fact_transaction,dbt_semantic.fact_lithic_card_transaction_detailanddbt_semantic.fact_lithic_debit_card_transaction - Elastic
minority-backend-prod, last 3 months