The migration that was finished until a stranger looked

Priya’s migration was complete by every measure her team had written down. That sentence is the whole problem, and this story is how I teach it. I wrote it for engineering leaders deciding how far to trust AI-assisted work, and for the engineers who have to stand behind the word “done.” Priya, her company, and the details of her migration are constructed; the failure class is one I’ve met repeatedly in real systems, and the practice that catches it is one I use in my own work.

Priya runs platform engineering at a mid-sized company. Her team has to get off a deprecated payments API before the vendor’s shutdown date. The old API is called across many services, including code written by teams that no longer own those paths. A classic migration: tedious, high-stakes, nobody’s favorite work — and exactly the shape of work where AI assistance pays off fastest, if you run it with discipline.

Priya runs it the way synthesis engineering prescribes. She draws the boundary herself: which services are in scope, what counts as a call site, what “done” means. Then she puts an AI coding assistant on the legwork under those rules. At her direction it inventories every place the old API is called, builds the automated checker she requires so nothing regresses, and migrates the call sites on a branch. She has it run the tests and the checker. Everything comes back green: every call site in the inventory migrated, with no violations in the checker.

At this point she has speed, evidence, a written plan, and a clean branch. The assistant completed the repetitive work faster than the team would have done by hand. Inside the boundary she defined, every available check says the migration is done.

The second assistant

The merge is the kind you don’t undo casually. So before it lands, Priya runs the step most teams haven’t learned yet. She brings in a second AI assistant, a different product from a different vendor, and she decides exactly what evidence it receives: the repository, her written migration plan, and one instruction. Tell me why this isn’t done.

Deliberately, the second assistant gets no access to the first one’s chat history, notes, or reasoning. That sounds like a handicap. It is the entire point, and it’s the part of the practice that took me longest to appreciate.

The second assistant, rebuilding the picture from the repository instead of inheriting the first one’s summary, asks its own questions. What generates code in this build? What’s vendored? What constructs calls at runtime? Its independently derived inventory doesn’t match the first one. It finds call sites in generated code the original search never scanned, in a vendored library, in a handful of calls assembled dynamically at runtime, invisible to the pattern that built the original list.

Priya reviews the findings and confirms the generated and runtime call sites as real gaps. She disputes the vendored copy because that library is not shipped, and the reviewer withdraws it after checking. The branch goes back for the confirmed gaps before any merge. The migration reaches the shutdown date with those paths covered.

Why her own checks couldn’t catch it

The first assistant’s inventory, checker, and green suite were accurate within the surface they examined. The mistake was treating that surface as the whole system. Re-running the same checks would prove the same bounded claim again; only a second, independently derived inventory could enlarge the review surface.

The reviewer’s ignorance is useful at the start. If it begins from the author’s notes, it is likely to inherit the same inventory and definition of done. In synthesis engineering I call the countermeasure Blind-Spot Rotation: give the reviewer enough separation to derive the work before it sees the author’s account, because the differences between those derivations are where the useful findings emerge.

The gaps in this story are the three I meet most often when a second derivation runs against a first. Generated and templated code: the original inventory searched the sources humans write, and nobody thought to scan what the build itself writes. Vendored and forked copies: code imported from elsewhere sits outside the mental boundary of “our code,” so it escapes searches that faithfully cover everything inside that boundary. Calls constructed at runtime: a call target assembled from strings defeats any static pattern, and only a reviewer asking “what constructs calls here?” goes looking for it. Notice that all three are boundary errors, not logic errors. The first assistant made no mistake inside its boundary; the boundary itself was the mistake, and boundaries are exactly what a fresh derivation re-draws.

The rotation is not limited to code. Hand a runbook to an engineer who has never performed the procedure and watch where they stop reading and start guessing: every stop is a gap the author cannot see, because the author’s memory silently fills it. Same mechanism, different artifact.

There’s a second, quieter benefit. Because the reviewer can only work from what’s written down, the practice forces everything load-bearing out of chat histories and into durable artifacts: the plan, the decisions, the current state, in ordinary files in the repository. My rule of thumb, and one of the discipline’s foundations: chat is a scratchpad; the record is the project. A project whose truth lives in files is a project a second vendor’s model, a new teammate, or future you can pick up and challenge. That property compounds far beyond any single review.

Running this on your own work

The practice is concrete:

Write the boundary before the work. Scope, definition of done, what evidence completion requires, decided by you, in a file, before any agent starts. This is what makes “done” checkable by a stranger at all.

Give the reviewer artifacts before explanations. Start with the repository, the written plan, and one adversarial instruction. Let the reviewer derive its own inventory before it reads the author’s account, so the comparison remains independent.

Rotate the review context. A second vendor’s model creates practical separation when it starts from the artifacts rather than the author’s reasoning. The value comes from different context and failure modes, not from a claim that the reviewer is smarter.

Track findings to a terminal state. Every finding ends as fixed and verified, disputed with a recorded reason, or withdrawn. Withdrawn matters more than it sounds: a reviewer that never retracts a wrong finding is just arguing, and an author that never concedes a real one is just defending. When both sides’ reversals show up in the record, the review is working.

Bound the review, and classify what it finds. An unbounded adversarial reviewer will keep producing findings, because finding things is its job; past some depth the findings start describing the review machinery instead of the product. Two rules keep it honest. Name the artifact universe up front, so “done reviewing” is checkable against a list rather than a feeling. And classify every finding at intake: does this block the change in front of us, or does it improve the system for next time? Both kinds have value; only one of them is allowed to stop the merge. Ship-improving findings route to their own queue instead of holding the branch hostage.

Blind-Spot Rotation protects speed when it runs before an irreversible merge. A bounded review surfaces gaps while the branch is still cheap to change, so the team keeps the gains from AI execution without treating the first green result as proof of completion.

The tooling I use for durable project records, the review protocol, and the finding ledger is open source under CC0 in my synthesis-skills collection. The discipline it belongs to is laid out at synthesisengineering.org. Start with one irreversible change you’re about to make, and before it lands, hand the artifacts to a stranger with the only instruction that matters: tell me why this isn’t done.

Also published on synthesisengineering.org