Change the inside without the outside noticing
Part 1 was about knowing whether the new code is right. This part is about the question that comes after: how do you put it in front of real traffic when every real thing is still pointed at the old system, and there is no moment when you're allowed to turn it off?
The rewrite exists. The parity harness says it matches. And none of that has changed anything, because every client, every payment and every third-party integration is still talking to the fifteen-year-old app, and will keep doing so until something moves them.
The greenfield answer is "deploy it". The legacy answer has to account for the fact that the system is in use, right now, by people who did not agree to be part of a migration.
You don't get a maintenance window on a 15-year-old platform. You get a transition.
The new payment portal needed a backend service that didn't exist yet. It was on someone else's roadmap, on someone else's timeline. Waiting for it would have blocked the checkout flow, the card vault and the 3DS2 work, which between them were most of the portal.
So the AI built a mock of the missing service. Not a stub that returns canned data: a working stand-in with the same interface the real service will have, calling the payment gateway directly behind it. Everything above the seam is real. Only the thing below it is temporary.
// appsettings.json "UpstreamService": "Mock" // | "Real"
The dependency arrived late. The work never waited for it.
A cutover is usually imagined as a switch with two positions. The useful version has three.
Shadow is the position that earns its keep. Users still get the legacy answer, so nothing visible changes. But every request also runs the new path in parallel, and every field that disagrees gets logged: what differed, where, and by how much. You get the full weight of production traffic as a test suite, without a single user being exposed to the result.
Here is what the soak caught on live traffic, mid-migration.
Six divergences that turned out to be zero bugs is not a wasted week. It is the week you would otherwise have spent after the switch, with customers on the phone, trying to work out which of the six were real.
Production traffic becomes the test suite. Nobody notices.
External callers were built against the legacy endpoints years ago. You can't make other people redeploy on your schedule, and some of those integrations belong to insurers, who have their own change processes and their own definition of "soon".
The old surface stays up. Thin shims accept the legacy request shape and forward to the new services. The contract is honoured to the byte; the implementation behind it is gone. When an insurer eventually does move to the new API, that's their project, on their timeline, and nothing on our side is waiting for it.
Callers never know the backend changed.
Each one puts a fixed boundary between something that is changing and something that must not notice. The mock fixes the interface above a service that isn't there yet. Shadow fixes the response users see while the implementation underneath is swapped. The shim fixes the URL and payload shape while everything behind it is replaced.
The boundary holds still while everything behind it moves.
The AI writes the mocks, the shadow comparison, the shims. This is faithful-interface work: take an existing contract and produce something that honours it exactly, over and over, for every endpoint and every field. It is tedious for a person and exactly what the model is fast at.
But it is only faithful to interfaces it has read. A mock built from a description of the upstream is a guess with a nice API. A shim built from memory of the legacy endpoint will drift from it in a dozen small ways, and the callers will find every one.
So every one of these patterns starts with the real legacy source in context, never a description of it. That is Part 1's lesson applied: the model doesn't describe the system from memory. It reads it, or it doesn't speak.
Greenfield ships by deploying. Legacy ships by transitioning, and the transition is a design artefact of its own: the seams, the switch positions, the soak, the rollback path. It deserves the same care as the code it's carrying.
None of it is throwaway, either. The config seam that let the portal ship against a mock is the same seam that will let the next upstream be replaced. The shadow comparison is a parity harness you get to keep. And the seams you cut for the migration become the module boundaries of the system you keep.
Build the seam first. Move the system behind it.
Part 3 coming — the discipline: specs, logs as truth, and a fresh session for every spec.