What a migration can't record
By Daniel Farré Manzorro ·
You can always reconstruct how a system got here from the low-level record. Done right, you have every column, every type, every constraint —when each was added or changed, in what order, and often what changed in the data itself: what, when, and by whom. Nobody can argue with it.
What that record doesn't hold is what any of it means, or what it's for. And the meaning can drift while the structure stays perfectly intact.
Take a field called "region", with values like "EU", "US", "APAC". The day it was added it meant one thing: tax jurisdiction. Billing read it to apply the right VAT rules. That was the whole story, and nobody wrote it down, because everyone who touched it already knew it.
Eighteen months later a different team needs to route fulfilment. "region" is already there, already populated, already, roughly, correct. So they read it —in the shipping service this time— and "EU" quietly starts to also mean ship from Amsterdam. And it works: Amsterdam is in the EU, the packages arrive, nobody notices —because nothing is wrong yet. The column never moved. No migration, no rename, no type change. Git cannot diff a belief.
Then EU VAT splits. France and Germany have to resolve differently, and billing handles it underneath —while still writing "EU" to the field, because that's what the invoicing logic expects. Now two teams read the same value and understand two different things. Each is right, for its own model —and that's exactly why it's dangerous: the next service to read "EU" the original way, one region one rate, will bill someone wrong, and nothing in the migration history will say why. The migration history was never meant to hold the business intent.
A schema can be perfectly valid and still describe a business that no longer exists.
That intent has to live somewhere else —a business-domain document most systems never build: one you can read, argue with, and tune on purpose, instead of one every team reconstructs, differently, in its head.