The migration looked mechanical. Python 2 to 3, a well-understood path, a codebase I had already read end to end.
Then I hit a function that normalized incoming payment requisites before matching. Nothing special: a few string operations, a lookup, a fallback branch. Under Python 3 it behaved differently on a narrow slice of inputs, and that slice lived only in real production data. Not in the fixtures. Not in anything anyone had written down.
That branch was not a bug. It was a requirement. Someone had encoded a quirk of how a specific payment channel formatted references, years earlier, and the only record of that decision was the code path itself.
The fix was cheap. The week after it was not: I spent that week finding out how many more of those existed. Every unmatched payment after the cutover arrived with the same question attached. Did I break this, or was it always like that?
So now, before I agree to any rewrite, my first question is not about architecture or stack or timeline. It is this: can the current system be observed well enough to know when the new one disagrees with it?
If the answer is no, the rewrite is not a rewrite. It is a guess that gets deployed.
What I do differently: I run both paths against real traffic before anything switches. Same inputs, both implementations, outputs compared, every divergence logged. No cutover until the divergence list is empty or explained. It is slower. It also turns "I think it works" into something you can show a client.
The uncomfortable part is that this reorders the usual five questions. People start with scope, cost, team, timeline, stack. Those matter. They also sit downstream of whether you can prove equivalence. A rewrite you cannot verify is a rewrite you cannot finish: you can only abandon it, or ship it and find out from users.
The same logic applies to monolith-to-services work, which is where I have spent a lot of the last ten years. Pulling a service out is easy. Proving the extracted service makes the same decisions as the code it replaced, on the inputs that actually occur, is the whole job.
I wrote more about how I approach inherited systems and migrations here: https://polycratia.com/c/b4c6f625
If you have done a migration on a system with money in it, I want to know what finally exposed the undocumented behavior for you: shadow traffic, reconciliation mismatches, or a support ticket...