---
title: "A Handwritten Recovery List Was Missing Rows a Real Database Audit Found"
canonical: https://dxdev.com/blog/2026-01-27_list-i-could-not-trust/
datePublished: 2026-01-27
---
# The List I Could Not Trust

A 1,477-line recovery file restores a customer's site from handwritten lists, one comma-separated list of columns for every table, and nothing checks those lists against the 14,536 columns the database actually stores. If someone adds a column and forgets to add it to the matching list, recovery still runs, still reports success, and still puts the site back together, minus that one column. No error appears and no log says anything was left behind. The only way anyone would find out is a customer noticing that a feature's data is missing.

Then I pulled a fresh export of everything the database actually stores, to compare against those lists, and the export came back at 14,536 rows, one per column, spread across hundreds of tables. I sat looking at that number next to my 1,477 lines of hand-typed column names and had to ask myself a question I did not like: how would I ever know if the two had drifted apart?

Because nothing would tell me. That was the part that actually worried me. The recovery file did not fail loudly when a list fell behind. If I added a new column to a table during ordinary feature work, and forgot to also add it to the matching recovery list two files away, the recovery would still run. It would still report success. It would still put a site back together for a customer. It just wouldn't carry that one column, silently, and there would be no error, no warning, nothing in any log to say a piece of that customer's site had been left behind. The failure mode wasn't that recovery would break. It was that it would look fine while quietly being wrong, and the only way anyone would ever find out is if a customer eventually noticed a feature's data was missing and asked why.

For a long stretch, my answer to that risk was to remember. Every time I added a column, I was supposed to also update the recovery list. That is not a system, it's a hope, and I had been running on it without ever once proving the lists were still accurate. I could not point to a day the drift actually happened. I also could not point to a day I had checked closely enough to know it hadn't.

So I stopped trying to remember and built something to check instead: a 418-line tool that reads the recovery file as plain text, pulls out every column list it declares, and compares each one against that table's real columns from the fresh export. It reports two things: columns the schema has that the recovery code does not copy, and columns the recovery code still names that no longer exist. The report is organized into four sections, generic tables, the customer records, the scores tables, and the stat tables, and the state you want is all four with nothing under them.

Getting that comparison right meant deciding which side to trust. My first instinct was that the schema should be the source of truth, since that's the real, current shape of the data. That instinct was wrong. Some columns are calculated fields, some are identity numbers the database assigns itself, some are timestamps that should be set fresh on arrival rather than copied. If the schema were canonical, every one of those would show up as a false problem, and I would spend my time maintaining a second list just to explain away the first. The recovery list had to be treated as the actual promise, the statement of exactly what recovery is responsible for carrying, and the fresh export as the thing being checked against that promise. The only question worth asking was whether the schema had grown columns the promise didn't know about yet.

The tool isn't fancy. It counts opening and closing braces to find the right chunk of code, because there's no real parser for the old scripting language the recovery file is written in, and a few plain heuristics keep it from flagging identity columns as missing. That's a fragile way to read a program, and I'd never trust it against a file that changed often or was edited by more than one person. It's the right tool here because this file barely changes, and a small fragile check that actually runs beats a perfect one that only exists as an instruction to remember.

The check earns its keep on one number: 14,536 columns in the fresh export, against 1,477 lines of hand-typed lists that would never have said a word if a single column fell out of step. What the 418 lines of the tool changed is that the silence now means something. A recovery report with four empty sections is a claim that every list still matches the schema, checked that day, where before it was only a hope that I had remembered to update the lists.
