Record What You Can't Re-Derive
Migrating a decade of records taught us a one-line rule for what deserves to be written down at the moment it happens — and what should be recomputed forever.
Or: why “we’ll figure out which ones were grandfathered later” is a trap with a lid that closes.
We’re migrating a decade of policy records from a legacy system into a new platform. The new platform has rules the old one didn’t enforce — minimums, caps, combinations that are no longer allowed to exist. And scattered through the old book are records that violate those rules. Not many. A dozen here, a hundred there. All of them legitimately issued, years ago, under rules that were different or enforcement that was looser.
The business decision was easy: grandfather them. Carry them in as they are; the new rules apply to new business and to any future change.
The design decision was the interesting part: how does the system know which records are grandfathered?
The tempting answer
The obvious approach: don’t store anything. When you encounter a record that violates a current rule, check whether it’s old — if it predates the cutover, it must be grandfathered. Infer the status from the evidence.
This feels elegant. No new table, no flag to maintain, no list to keep in sync. And it is quietly catastrophic, for one reason:
💡 An inferred grandfather can’t tell the difference between a record that was legitimately carried and one that leaked through a bug.
“It’s old and it violates the rule, therefore it must have been allowed” is circular. The rule becomes self-justifying: everything that got through — however it got through — is retroactively blessed. Your grandfather clause silently becomes an amnesty for every historical defect, including the ones you haven’t found yet.
So the rule we adopted, which I’d now carve above the door of any migration project:
Record what you cannot re-derive. Re-derive what you can.
The grandfathered status is a fact about a decision made at import time. Nothing in the data can reconstruct it later — so it gets written down, explicitly, at the moment of import, by the import process, with a reason attached. Meanwhile, anything that can be recomputed from current state — does this record exceed the current threshold? does it need review at renewal? — is computed fresh every time, never copied forward. Snapshots of derivable state go stale the moment the inputs move; the legacy system we’re leaving is full of flags that were copied forward years ago and stopped being true.
”Whatever arrives,” not a list
Second trap, subtler. Early on, someone (fine: me) nearly built the grandfather set as a roster — here are the eleven records, seed them in a table, done.
Between the day the roster was proposed and the day the design was agreed — three days — the count went from eleven to twelve. A renewal had arrived, carrying the old terms, by exactly the mechanism the new rules would eventually stop. The list was stale before it was approved.
The fix: membership isn’t a list, it’s a predicate applied at arrival. Whatever crosses the boundary during migration carrying an old position gets recorded as grandfathered as it crosses. Nobody maintains a roster. Nobody can forget to add row twelve.
Sets that clean themselves up
The third piece makes the whole thing livable: the grandfather set is self-extinguishing. Nothing can enter it after migration ends — the recorder only runs at import. And every record leaves it the moment it’s genuinely changed — any substantive revision revokes the grandfathered status, and the record meets current rules from then on, like anything else.
Which means the set has a lifecycle with a guaranteed direction: it fills once, then only ever shrinks. No one reviews it quarterly. No one owns its hygiene. Time and ordinary business activity drain it to zero, and every exit is logged with the change that caused it.
💡 The best exception list is one that can only shrink. Design the mechanism so entropy works for you.
The shape, portable
None of this is insurance-specific. Any time a new system inherits an old system’s sins, the same three moves apply:
- Record the pardon at the border, with a reason. Never reconstruct it from the crime.
- Define membership by what arrives, not by a list. Lists are stale by the time they’re approved.
- Make the set self-extinguishing. Entry closes at cutover; any real change is an exit.
The one-line version has become how we triage every field in the migration: is this recomputable from current truth? Then compute it, always, never store it. Is this a fact about a moment — a decision, an authorization, an arrival? Then write it down at that moment, because the moment is the only place it exists.