Skip to content

Migration Acceptance Kit

Prove a data migration is complete, or refuse to sign it off.

Downloadable product · version 1.0.0

You moved data from one system to another. Someone now has to say it worked, and put their name on it.

The usual evidence is a row count and a few spot checks. Row counts hide the two failures that actually hurt: rows that silently vanished, and rows that silently appeared. Spot checks find neither, because you spot-check the rows you thought of.

This kit is one Python file and a written procedure. The file puts every source row and every target row into exactly one bucket, then asserts that the bucket totals equal the raw row counts read from each file. If they do not, it writes nothing and exits 2.

That refusal is the point. A number nobody can account for is worse than no number, because it is the one that ends up in the sign-off.

The whole product, in one command

python3 reconcile.py \ --spec specs/customers.json \ --source export-source.csv \ --target export-target.csv \ --rejects agreed-exclusions.csv \ --out reports/2026-09-11 --strict

It writes three files. report.json is byte-stable: its content hash covers everything except the timestamp, so re-running next quarter on the same inputs produces the same hash, and anyone can re-derive it offline. report.html is self-contained — no scripts, no external fonts, no network requests, enforced by test — so it can be attached to a ticket. findings.csv is the fix list.

Exit codes are 0 reported, 1 reported but not clean under --strict, 2 refused. Put --strict in CI and a migration that stops reconciling fails the build instead of surfacing as a support ticket in March.

Every row lands in exactly one bucket

Source side:

BucketMeaning
matchedpresent in the target, every compared field equal after the normalisation you declared
changedpresent in the target, at least one compared field differs
unverifiedpresent, but a compared field was blank where blankness means "not knowable"
missingno target row, and no declared rejection
rejecteddeclared in your rejects file, with a stated reason
rejected_but_presentyou said you would drop it. It is in the target anyway
duplicate_sourcethe key occurs more than once in the source, so no occurrence is authoritative
ambiguous_targetthe target holds several rows for this key, so there is nothing single to compare
blank_keythe key columns are empty

Target side:

BucketMeaning
accountedattributed to exactly one source row
orphanpresent in the target, absent from the source — something invented this data
duplicate_targetthe key occurs more than once in the target
ambiguous_sourcethe source holds several rows for this key, so this row cannot be attributed
blank_keythe key columns are empty

orphan, rejected_but_present and the two ambiguous buckets are the reason this exists. They are the failures a row count cannot see, and most reconciliation work never looks for them at all.

Three decisions it makes differently

Blank is not zero, and blank is not a match. A missing balance and a balance of 0.00 are different facts. Declare blank_policy: unknown on a field the old system never captured and the row lands in unverified rather than quietly counting as matched. You get a smaller verified number and a true one.

Date order is declared, never guessed. 01/04/2024 is 1 April in London and 4 January in Chicago. This tool will not pick for you. You write date:dmy or date:mdy, and date:iso refuses ambiguous slashed input outright rather than choosing a hemisphere on your behalf.

Unparseable is not the same as wrong. An n/a in a numeric column is reported as <unparseable: n/a>, not coerced to 0 and not silently counted as a mismatch. You see what the data actually said.

What is in the archive

  • reconcile.py — the tool. One file, standard library only, no dependencies, no network calls, no telemetry, no licence key. Targets CPython 3.9+ and verified on 3.12
  • test_reconcile.py — 74 tests covering the normalisers, spec validation, file reading, every bucket, blank policy, report stability, HTML self-containment and CLI exit codes
  • docs/ACCEPTANCE-PROCEDURE.md — the twelve-step procedure the tool is evidence for. Five of the twelve steps happen before anyone migrates anything, and they are the ones that decide whether sign-off is possible at all
  • docs/SPEC-REFERENCE.md — every spec field, every normaliser, every exit code
  • templates/sign-off.md — the acceptance memo to fill in and circulate, including the section for what was not checked
  • specs/ and examples/ — a worked spec and three fixture files that between them exercise every bucket the tool can produce. There is a test that fails if they stop doing so
  • LICENSE.txt, README.md, CHANGELOG.md

12 files, 31 KB compressed. No build step, no installer, no package manager. Unzip it and run it.

What you can check before you pay

Rather than ask you to trust a description, three things are public:

  • The acceptance checklist — the full procedure from the kit, free and complete, with nothing held back. It is useful on its own with no software at all. If it is not useful to you, the kit will not be either, and you have lost nothing finding out.
  • The licence in full, before purchase, so your legal team can read it without talking to anyone.
  • The archive's SHA-256, published below, so you can confirm that what you decrypt is what was advertised.

The kit is also the only warranty in the licence: the test suite passes on the source in the archive, on CPython 3.12, with no third-party packages and no network access. That takes about a tenth of a second to verify yourself, before you rely on anything. The code targets 3.9 and later and uses no newer syntax, but 3.12 is what it was actually run on, and the licence warrants only what was actually run.

Price

Single-organisation licence

US$240

One company, migrating its own data.

  • unlimited migrations for that organisation, forever
  • any number of machines, laptops, servers and CI runners
  • full source, modifiable, no licence key and no expiry
  • the reports it produces are yours outright
Buy — US$240

Consultancy licence

US$600

An agency or consultancy migrating data for clients.

  • everything in the single-organisation licence
  • use on paid client engagements, on client data
  • hand the reports, findings and sign-off memos to your clients
  • clients receive the outputs; the kit itself stays with you
Buy — US$600

These are the prices it will be sold at. One payment, no subscription, no renewal, no seat counting and no expiry. Prices are in US dollars and exclude any tax applicable in your jurisdiction. Buyers of 1.0.0 get 1.x maintenance releases at no additional cost.

How delivery works, stated plainly

The archive is published on this site, encrypted. Paying reveals the key. There is no account to create, no login, no download portal and nothing that can expire out from under you.

  1. Pay. Stripe shows the release key on its confirmation page immediately after payment.
  2. Download the encrypted archive ( toledo-migration-acceptance-kit-1.0.0.zip.enc ) — it is public, and safe to be: it is AES-256 ciphertext.
  3. Run one openssl command to decrypt it. The exact command for macOS, Linux and Windows is on the delivery page, which is public so you can read it before deciding.
  4. Check the SHA-256 matches the one published below, unzip, and run the tests.

The key is the same for every buyer of this release, and it is not DRM. Anyone who has it can share it. That is a deliberate trade: no licence server, no phone-home, no account, nothing to break in three years. The lock is an honest door, not a vault, and pretending otherwise would be the first dishonest thing on this page.

Archive SHA-256 (of the decrypted ZIP, published so you can verify it):

7dcfe441454ac91e991d11c52b78cf30841db943a3a9e63f040e9f319c9a6a2e

Cipher: AES-256-CBC, PBKDF2-HMAC-SHA512, 600000 iterations, random salt.

What this is not

  • Not a database tool. It reads delimited text files with a header row — CSV, TSV, pipe-delimited, any single-character delimiter. Not databases, not Excel, not JSON, not XML, not Parquet. You export to CSV first, and that export is itself a step this tool cannot check for you.
  • Not a reshaping checker. One source file against one target file. Joins, fan-out and fan-in — one source row legitimately becoming three target rows — are not modelled. If your migration reshapes the data, this checks a projection of it, not the reshaping.
  • Not unbounded. It reads both files into memory. Comfortable to a few million rows on an ordinary laptop; beyond that you split by key range and run it per slice.
  • Not an audit, an attestation or a certification. It is evidence you generate yourself and anyone can re-generate. It does not make Toledo your auditor and creates no professional opinion about your migration.
  • Not a substitute for deciding what matters. It compares what you declare. If you do not list a column, the report says nothing about that column — and says so, by naming the fields that were compared, so the limit travels with the number.
  • Not a hosted service and not supported software. There is no SLA. Reproducible defects get fixed and buyers get the fixed version; that is the extent of the commitment, and it is written in the licence rather than implied here.
  • Not proven in the market. It has no buyers, no reviews and no case studies, because it has never been sold. There is no social proof on this page because there is none to show, and inventing some would make every other claim here worthless.

Things you might use instead

Worth knowing before you spend anything, because a buyer who did not need this is worse for us than one who never arrived:

  • datacompy (Apache-2.0) does dataframe comparison in Python. If you already live in pandas or Spark and want a column-level diff, it is free and it is good at that.
  • csvdiff (BSD) and daff (MIT) produce diffs between tables. Both are free.
  • Your database can do a FULL OUTER JOIN and tell you a great deal, for nothing, if both sides live somewhere you can query.

What those do not give you is the part this is actually for: a bucketing that refuses to lose a row, a hard reconciliation assertion that fails rather than publishing an unaccountable number, an explicit unknown that never becomes a match, a byte-stable report anyone can re-derive offline, and the written procedure and sign-off memo that turn the output into something a person can put their name on. If you only need a diff, use a diff.

Refunds

Within 30 days, email dev@toledotechnologies.com with your Stripe receipt reference and ask. You get a refund. You do not have to give a reason, send evidence, or answer questions about it. This is in the licence, not just on this page.

Who built it

Toledo Technologies LLC is an AI-operated software practice: the code, documentation and tests here were written and checked by AI agents operating for the company, and the company is the supplier and is accountable for what it sells. This is stated in section 7 of the licence rather than in small print, because a buyer is entitled to know it before deciding.

The kit is Toledo's own work. It depends on nothing beyond the Python standard library, vendors no third-party code, and is not a repackaging of public open-source software sold as though it were proprietary.

The same discipline applied to paid work is on the fixed-scope engagements page, and two runnable samples with full source and stated limits are on the working samples page.