A ledger that knows which rail it came from is already two ledgers.
The third rail was the one that hurt.
The first had been built cleanly. The second had been fitted around it — a few extra columns, a status code or two that only applied sometimes, a reconciliation job that knew to skip certain rows. Nobody would have called it a second ledger.
Then a wallet product arrived, with balances that had no counterparty bank at all. Then a batched settlement run, where a thousand payments share one reference. Each got its own accommodation in the same table.
By the time the third rail was being scoped, the honest answer to “how much money is in this account?” depended on which product you asked.
A ledger that knows which rail the money came from is already two ledgers.
It just has not split yet.
Why the first accommodation always feels harmless
An institution with one rail and one product has nothing to separate. Rail-specific fields in the transaction table are natural, because the table has nothing else to hold. It is not a mistake; it is just the last moment the design is free.
The second rail is where the instinct kicks in: give it its own tables. The rail is different, so the data must be different.
That instinct has a cost that arrives late. Rail-specific logic does not stay at the edges. It leaks into how a balance is computed, how a reversal is recorded, what a reconciliation report is comparing. Every product ships. Every product also carries a slightly different definition of a transaction, and the differences meet at month-end.
The finance team notices first. Then the auditors, who ask a simple question — which of these numbers is the balance? — and wait.
What the ledger actually needs to know
We stopped asking how to add the next rail to the ledger and asked what the ledger needed to know at all.
The answer was short. An account. An amount. A direction. A reference back to whatever caused it. Nothing about the rail’s messages, nothing about the rail’s states, nothing about why.
What a particular kind of payment does to which accounts is described in templates and configuration, not in schema. Adding a product means describing its postings. Everything rail-specific lives in an adapter above the ledger, where it can change without touching the record.

One ledger underneath. Adding a rail adds an adapter above the line, not a column below it.
What that bought
The things built on top stopped being projects. A wallet became an account type with stricter rules. A batched run became many ordinary postings sharing a reference. A second national scheme became an adapter and a set of templates. The ledger did not change for any of them.
And there was one balance. Whoever asked — finance, the regulator, the customer’s app — got the same number, from the same place.
The ledger records what happened. The adapter knows why.
What we do not mean
None of this makes the rails trivial. The adapter is where most of the domain expertise lives: the message formats, the timing rules, a scheme’s particular idea of a failure. It is real, specialist work.
The argument is only about where that work is allowed to go. Domain complexity belongs at the edge. The record stays dull.
Why this is a business decision
A ledger that has quietly split is not an engineering inconvenience. It is a month-end that takes days instead of hours, an audit finding waiting to happen, and a product launch that slips because the new balance cannot be reconciled to the old ones.
The first rail is never the last one. A ledger designed around one of them gets redesigned around every one after — usually at the least convenient moment.
A ledger that knows which rail it came from is already two ledgers.
The adapter can be clever.
The templates can be rich.
The ledger has to be dull, and stay that way.
