A Decade of Donor Records, Rebuilt Without Losing a Row

A school district foundation was running its entire donor and donation operation (years of gift history, school-by-school allocations, the paperwork that turns into tax letters) on a web application that had not been touched since before the current generation of its own programming language existed. It sat on a shared host with no automated tests and no deployment process. Its login system hashed passwords with a scheme the foundation's own security audit flagged as one that should never be carried forward.

Funding the rebuild

The version of the programming language the application ran on was obsolete, and had stopped getting security fixes. We told the foundation that indicated potentially serious security problems. The foundation put the question to its own security team, and the security team agreed. That confirmation funded the rebuild.

The commitment: measured, not just promised

None of that data was expendable. Years of donor history, tied to real people and real gifts, doesn't come back if it's lost in a migration.

The foundation needed a modern system before the old one turned from a known risk into an actual outage, and it needed to know, before it switched over, that nothing had been left behind.

Every technology vendor doing a system migration will tell you your data is safe with them. That's easy to say but hard to prove.

The commitment at the center of this was specific: every donation record that existed in the old system would exist (with every field that mattered) in the new one. We call this theparity guarantee, and the guarantee comes in the form of a number we produced. Before we called the migration done, we compared the old system and the new system directly, every row, and can show the result.

What we built

The rebuild itself is a modern web application: a separate front end and back end, built so the part of the system that manages donations and the part that manages donor relationships stay cleanly separated from each other. That separation matters practically: a change to one doesn't put the other at risk, and the system can grow in either direction without the whole thing needing to be re-touched.

It ships with an automated test suite that runs the application through its paces on every change: 835 tests covering the core system (4,944 individual checks within those tests), a further 246 tests covering the interface, and 4 tests that walk through complete user workflows end to end. All of them passed at delivery.

The foundation's staff log in through the identity system they already use. We built that connection as a swappable piece, so when the foundation finishes deciding exactly which staff should get which access level (a decision that was still theirs to make at delivery) turning it on is a configuration change, not new code.

The migration itself was engineered, not just run

The riskiest moment in any system replacement is the night you move the real data over. We built a dedicated, one-command migration tool for that moment, with three properties that matter:

  • **It's atomic.**If anything goes wrong partway through, the entire operation rolls back and the target database is left untouched: no partial migration, no "we'll clean it up after."
  • **It's safe by default.**The tool refuses to run against a database that already has data in it unless someone deliberately confirms they mean to override that.
  • **It corrects, not just copies.**Rather than moving the old records over exactly as they were, the migration checks the schools directory against the district's current, authoritative source and fixes what had drifted: 36 name corrections, 11 missing school records restored, and duplicate records resolved. A naive migration would have copied the old, stale version forward. This one copies forward what's actually correct today.

Only one donation-level change happened anywhere in the migration: three donations tied to a school that had physically relocated were reassigned to the surviving record for that school. That's it. The only change to a donation record in the entire operation, done as a deliberate, logged, and separately verified step, not a side effect.

How we proved it

Proof on this work means three separate checks run in sequence, each one independent of the last:

First, 21 behavioral checks covering the entire donation workflow: record counts, exact field-by-field comparisons, school lookups, exports, letter and label generation, and the seasonal lock that prevents changes outside the giving period. 21 passed. 0 failed. 0 incomplete.

**Second, a full data comparison rather than a sample.**A database engineer loaded the old system's data and the new system's data side by side and ran a column-by-column comparison across every donation record (not a spot check, the entire set) on every field that mattered. The result: 0 mismatches. Not "close." Not "materially the same." Zero. Separately, exactly 27 records carried a special "amount unknown" flag in the old system, and all 27 came through with that flag intact.

**Third, someone else checked the work.**Our QA function reran the same comparison independently, from scratch, against the same data, and got the same numbers. That independent, reproduced result is what let us call the migration formally accepted: not because we said so, but because two separate checks, run separately, agreed.

The one place the new system's numbers intentionally differ from the old one is the schools directory itself, because we corrected it against the current authoritative source, as described above. The donation totals never move.

What the foundation has now

  • The application's source code, delivered directly into their code repository, ready to deploy. They own the deployment pipeline; we don't sit between them and their own system.
  • The one-command migration tool, with a written runbook, a documented safety model, and a built-in verification flag they can re-run any time.
  • Four separate handover documents, each written for a different reader: day-to-day office staff, IT operators, the foundation's future developers, and our own internal reference; none of them assembled from a spec, all of them checked against the running application.
  • A working system with four access levels (system administrator, school administrator, department administrator, and read-only), where each user only sees the data for their own school, enforced by the system itself, not by policy. A seasonal freeze mode prevents changes outside the active giving period. And a donor and company management surface that the old system never actually had, despite being asked to do that job.

What this says about legacy-system risk generally

This project started with a suspicion we couldn't prove. A system in that condition is easy to leave unchecked. It became a funded project because the foundation's security team checked it and agreed.

It's tempting to scope a project like this as "a rewrite" and treat the data move as an afterthought: build the new thing, copy the data across, hope it lines up. The expensive lesson in system migrations is that the data move deserves the same engineering discipline as the application itself: its own design, its own dedicated test suite, its own pass/fail criteria, checked independently before anyone calls it done.

The schools-directory correction is the clearest example of why that discipline pays off. Copying the old records forward unchanged would have been easy. But it would have carried old, uncorrected data into a new system. Cross-checking against the current source of truth instead means the system that goes live is accurate as of today, not whenever the old system was last updated. That distinction is exactly why the identity system will map staff to the right school when the mapping goes live: it depends on the same directory that migration went to the trouble of getting right.

If your own systems are carrying that same kind of risk (old, undocumented, load-bearing, and nobody quite sure what would happen if you touched them) the question isn't whether they eventually get rebuilt. It's whether, when they do, you'll be able to point to a number and know nothing was lost.

Tell us about yours

Most of these started with two paragraphs from somebody who wasn't sure the situation was fixable. Tell us what yours does, what's wrong with it, and what it would mean to have it working. We'll tell you whether it's ours to do.

Get in touch →

Based in Beaufort, SC. Names are left out of these unless the public record already carries them, and yours would be too.