Lithic → Galileo transaction routing (removed)¶
Removed from be-lithic on 2026-08-12 under BCP-3563. This page is kept because the
routing produced data that is still in production, and because the failure mode it
caused is easy to mistake for a lost event.
What it did¶
Until it was removed, be-lithic could divert a Lithic transaction event to
galileo-service instead of posting it to the ledger.
GalileoTransactionAction.GetGalileoTransactionFlowId looked for a Galileo flow by
network transaction id — the Visa transaction_id or the Mastercard
switch_serial_number, collected from all events on the transaction — in this order:
- be-lithic's own
VisaTransactions/MastercardTransactionsmapping tables, by Lithic token - the same tables, by network id
- galileo-service itself, by network id (caching a mapping row on a hit)
On a hit, ProcessEvent built the processor callback using the Lithic event token as
the MAJORITY transaction id and returned before calling
ITransactionService.Event on the ledger.
flowchart TD
E[Lithic transaction event] --> M{Galileo flow matched<br/>by network id?}
M -- no --> L[be-ledger: apply to the Lithic flow]
M -- yes --> G[galileo-service: apply to the GA= flow]
G --> X[Lithic flow never updated<br/>reservation stays open]
L --> C[Processor callback]
G --> C
Why it mattered¶
The Lithic flow was left untouched. The reservation stayed open while the
settlement was applied to a GA= flow, so the customer ended up with a stuck hold
and a settled statement line for money that was never actually taken from them.
Both halves look correct in isolation: the app shows a settled purchase, and the
ledger shows a settlement — just on a different flow.
Network ids are not unique across schemes. A Lithic clearing could match an
unrelated Galileo transaction that had already settled weeks earlier and that
belonged to a different customer. The flow upsert then overwrote that flow's
AccountId. Two confirmed cases under BCP-3563:
| Lithic flow | matched Galileo flow | Galileo flow's original owner | account the append was written for |
|---|---|---|---|
eda0a9a6… |
GA=258774448 |
84cb974a… |
36577a68… |
65a1c0de… |
GA=259161602 |
91ef4b3d… |
73f018bd… |
The decision was re-evaluated per event. One transaction could post its
authorization and reversal to the ledger and its clearing to galileo, because the
network id that produced the match only appeared on the later event.
How to recognise it in data¶
A be-transactions row whose TransactionId equals a Lithic event token — a v4
guid — rather than the usual uuid5(NAMESPACE_URL, "minoritylithic/ML=" + ledgerTransactionId)
derivation, and whose LedgerBalance is 0, was written through this branch. That is
the cheapest way to identify a misroute after the fact, and it does not require
querying the ledger.
Corresponding ledger evidence: a TransactionCore row on a GA= flow whose
ExternalTransactionId is the Lithic clearing's event token.
Why removing it was safe¶
CheckGalileoTransactionFlow was already false in both prod and stage before
the code was deleted, so the branch was dead by then — the confirmed damage dates from
when the flag was still enabled. ProcessGalileoSettlementEvent had no production
caller at all, only a test.
Removed with it: the routing action and its interface, the Visa/Mastercard mapping
entities, repositories, tables and stored procedures, the CheckGalileoTransactionFlow
flag, and the galileo-service REST client registration in be-lithic.
What is not galileo, despite the name¶
TransactionOTypeParser, RdfCodeHelper and ActivityTypeParser in be-lithic derive
Galileo-shaped OType and RDF codes from Lithic data. Galileo does not consume them —
MAJORITY's own reporting does, via the RdfCode field on every processor callback, and
they run on every transaction. They are a wire format, not a dependency. Deleting them
blanks live reporting rather than removing legacy code. Tracked in BAN-298.