Due diligence11 min read

What a migration changes when nothing is supposed to change

A reviewer's view for buyers, sellers and advisors, from our own re-platforming: where behaviour changes when code is rewritten, the six layers it hides in, and the evidence that shows a migration really changed nothing.

Sellers often describe a recent move to a new platform in one line: we migrated last year, and nothing changed for customers. Buyers tend to take it on trust, because the product works and the demo looks the same. A reviewer cannot. A rewrite in another language or framework is exactly when behaviour changes without anyone deciding that it should, and the line in the memorandum is a claim like any other.

We had a chance to test this on ourselves. A few days after launch, this website moved from a static build written in Python to a Laravel application in PHP, so that it could be run from an admin. The rule for the move was that no reader and no search engine should notice, and we set the standard the way we would for a seller: every public page compared byte for byte with the old one, a live check of 47 points after the switch, and a way back in one command. Our engineering partner PERETZ did the engineering and describes it step by step in Moving The Structure to Laravel Without Changing a Single Byte.

This note is the reviewer's side of the same work: what we found, which of our six layers each finding sits in, and what it means when the migration belongs to a company you are buying or selling.

Why "nothing changed" is a claim to check

When a system is rewritten, its documented behaviour usually survives. The team knows the pricing rules, the screens and the integrations, and it tests them. What does not survive on its own is behaviour nobody wrote down because nobody chose it: how a number is rounded, how a date is printed, what counts as a space, how a quotation mark is escaped. In the old system those answers came from the language and its libraries. In the new one they come from a different language, with different defaults.

Most of these differences are invisible in a demo. They surface later, in places a buyer cares about: an invoice total that is a cent off on some lines, a report that no longer matches last year's, an export a customer's system rejects, a search engine that reads the site differently. The question for a review is therefore not whether the new system works. It is what the team did to prove that it behaves like the old one where it matters, and what record of that proof exists.

What we found, layer by layer

Our move was small: about 2,700 lines of Python became about 3,500 lines of PHP, routes and templates, serving fifty pages. Small is useful here, because every finding below would be larger in a product with a billing engine and ten years of data. We set them out as we would in a report, by layer.

Code & architecture: defaults that differ between languages

The page-by-page comparison found five places where Python and PHP give different answers to what looks like the same request. Each would have changed every page it touched.

BehaviourPythonPHP by defaultWhere it matters in a product
Rounding a halfround(2.5) gives 2, to the nearest even numberround(2.5) gives 3, away from zeroInvoice lines, tax, currency conversion, commission
Splitting text on spacesCounts non-breaking spaces and some control characters as spacesA plain \s in a pattern misses some of themImports, search, matching names and addresses
Writing JSONA space after every comma and colonNo spaces, and every slash escapedSignatures and checksums over payloads, cached responses
Escaping an apostrophe''Exports compared with older files, audit trails
Formatting a dateIts own set of format codesA different set of codes for the same outputReports, file names, anything another system parses

On our site, the rounding rule decided whether an article showed ten minutes of reading time or eleven. In a billing system the same difference is money: a half cent rounded to even in one version and away from zero in the other moves totals on a share of lines, and nobody sees it until someone reconciles. A generic, fictional illustration of how that would read in a findings register:

Invoice totals are calculated with a different rounding rule since the platform migration. Layer: Code & architecture. Severity: Medium. Evidence: rounding calls in the old and new billing code; a sample of 2,000 historical invoices recalculated under both rules, with about 3% of lines differing by one cent; no parity test in the migration plan. Impact: totals that customers' finance teams may challenge; a reconciliation gap in revenue reported for the months after the move. Cost to fix: roughly 2 to 4 engineer weeks to align the rule and add a parity test over historical invoices, assuming one engineer who knows the billing code; more if issued invoices have to be corrected, which is a question for finance and counsel. Recommendation: before closing, confirm which rule the contracts and tax filings assume.

Our own fix was a small helper class, about a hundred lines, that reproduces the Python behaviour wherever the output had to stay the same. The evidence was not the helper. It was the comparison that ran after every change and printed each difference line by line, old above new, until there were none left.

Infrastructure & cost: the hosting layer changes what the code serves

Two findings had nothing to do with the code. The hosting panel writes its own block into robots.txt, the file that tells crawlers what they may read, with a few dozen crawlers it turns away. That block is in no repository; it is added on the way out. The same hosting answered some automated browsers with an error page instead of the site, which surfaced only when our own testing tools were refused. A review that reads only the code would describe a robots file the site does not actually serve, and a site that answers everyone.

The lesson is one we apply in every review: read what the live system serves, not only what the code says it should serve. Rules in a control panel, a CDN, a load balancer or a managed service shape the product as much as the code does, and they move with the hosting account, not with the repository. Which crawlers may read a site, for instance, is a business decision that ends up sitting in a default. A buyer who takes over the code without the accounts inherits a different product.

The second infrastructure finding came at the switch itself. For about a minute after the site was pointed at the new application, some requests failed: PHP keeps a cache of where file paths lead, for 120 seconds by default, and went on using the old answer. None of the guides we read mentioned it. The fix was absolute paths in the application's entry file and a switch script that waits for three good answers in a row before running any other check. In a review it raises a broader question: what happened at the last cutover, and is there a log that shows it? A team that cannot say how long its last switch was degraded most likely did not measure it.

Data & privacy: what the system says about itself

The old sitemap gave every page the date of the last build, the day the generator ran, rather than the day the page changed. Nothing was broken, and every date was wrong. The new application gives each page the date of its real last edit. Systems publish many such facts about themselves, such as last-modified dates, counts, statuses and audit fields, and a migration is the moment they either become true or quietly become false.

Two privacy points were checked on the live site rather than taken from the plan. Public pages set no cookies, as before; sessions exist only inside the admin. And the inquiry form was tested with a request it must refuse, so the check proved the form was live without a single test inquiry reaching the log or anyone's inbox. Test records in production are a frequent finding in reviews: test orders in revenue reports, test leads in a CRM, test users in an active-user count. A migration plan should say how it tests without creating them.

Security & access: who can switch, and who can undo

The switch was one script, and most of it dealt with failure. It checked the server's PHP version first, kept the old site as a full copy beside the new one, then ran 47 checks over the internet: every page byte for byte, the sitemap, the redirects, the admin login open and the admin itself closed to guests, the form probe. Had any check failed, it would have put the old site back on its own and named the check. Going back by hand also took one command.

That is what a reviewer looks for in this layer for any migration: whether the switch was scripted or done by hand, who could run it, whether the rollback existed before the switch or was improvised after it, and whether the old system is still there to roll back to. A migration whose only rollback plan is "restore the backup" has a plan nobody has tried.

Team & knowledge: behaviour nobody decided

None of the language defaults in the table was a decision. Nobody chose to round reading times half to even; Python did. That is what this layer is about: part of what a product does lives in no document and in nobody's head, only in the defaults of the tools it was built with. When the tools change, that knowledge has no owner. In a company, a migration done by a team that has since left can carry the same gap: the new system does what it does, and nobody can say which differences were intended.

The record that closes the gap is plain: a written list of what had to stay the same, and the comparison that shows it did. For our move it is one table and one report. For a larger product it is usually a parity test suite run over historical data. Either way, it is evidence a buyer can read without having to trust the people who left.

Ownership & IP: code written with an AI

Most of the code for the move was written by an AI model, Claude, under the founder's direction and within his rules: nothing visible changes, the way back is ready before the way forward, no test inquiry reaches anyone. Buyers meet this more and more in targets. The questions are the ones in our note on AI-written code in due diligence: who owns the result, which tools were used and on what terms, who reviewed it, and what proves it works. Here the code sits in the company's own repository, and the proof is not anyone's assurance but the byte-for-byte comparison. Whether a particular tool's terms raise ownership questions is for counsel; whether the code does what is claimed is for the review.

What to ask about a migration in a target

Questions worth putting to any company that has changed platform in the last few years:

  • When did the move happen, from what to what, and what was it meant to keep the same?
  • Is there a written definition of "the same", and a test or comparison that checks it, with its last result?
  • Were the numbers that carry money, such as totals, tax, currency and commission, compared over historical data, and under which rounding rule?
  • What does the live system serve that the code does not explain: hosting rules, CDN rules, managed settings?
  • What happened at cutover: how long it took, what failed, and where the log is?
  • Was the rollback built and tested before the switch, and does the old system still exist?
  • How were forms, payments and emails tested without leaving test records in production?
  • Who did the work, are they still there, and which differences between old and new were intended?

The answers do not need to be perfect. A recent migration with a clear record reads well to a buyer. One with no record turns into a list of questions in the middle of exclusivity, and the review then has to rebuild the evidence the team never kept.

What a seller should keep

A seller who has migrated, or plans to, can turn the move into evidence instead of a risk:

  • Keep the definition of what had to stay the same and the comparison reports, including the last clean one.
  • Keep the cutover log and the rollback plan, and say plainly if anything went wrong and for how long.
  • Record the settings that live outside the code, and who controls the accounts they sit in.
  • List the known differences between old and new behaviour, intended or not, with a reason for each.
  • Avoid starting a large migration shortly before a sale; finish or stabilize what is in flight, as we note on technical debt in due diligence.

All of it belongs in the technical section of the data room, where reviewers will compare it with what they find in the systems.

Review and repair, kept apart

On this move the roles were the ones we keep in client work. The Structure set the standard and the checks; PERETZ, our engineering partner under common ownership, did the engineering. When a review finds that a migration needs work after closing, such as parity tests, an aligned rounding rule or a rollback path, PERETZ can do it under a separate contract, or any other team can, working from the cost ranges in the report. It is never a condition, and the fix is never priced inside the review.

What to do next

If you are buying a company that has recently changed platform, ask for the record above and share your plan for the product with the reviewer, because the plan decides which differences matter. Our technical due diligence reads the migration across all six layers and gives each finding evidence, impact and a cost to fix. If time is short, a red-flag memo says whether the migration is a pricing issue or a planning one.

If you are selling, exit readiness builds that record before a buyer asks for it. The standard is the one we applied to ourselves: write down what must not change, measure it, and keep the way back.

Questions

Is a recent platform migration a red flag in due diligence?

No. Many good products change platform, often for sound reasons such as a framework past its end of support. What matters is the record: what the move was meant to keep the same, how that was checked, what happened at cutover, and which differences remain. A recent migration with a clear record usually reads well. One without it becomes a set of questions the review has to answer from scratch.

How can a buyer check that a migration did not change how the product behaves?

Ask for the team's definition of what had to stay the same and for the comparison or tests that check it, with the last result. Then sample where it matters most: recalculate invoices, tax or reports from historical data under the old and the new code, and compare what the live system serves with what the code produces.

Why do rewrites change behaviour nobody meant to change?

Because part of what a system does comes from the defaults of its language and libraries rather than from decisions: how numbers round, how dates print, what counts as a space, how text is escaped. When the language changes, those defaults change with it. On our own move from Python to PHP we found five such differences, and each would have changed every page it touched.

What lives outside the code that a review should check?

Rules set in hosting panels, CDNs, load balancers and managed services: which crawlers may read the site, which visitors are blocked, redirects, caching and headers. On our own site the hosting panel added a block to robots.txt that is in no repository. A buyer who takes over the code without those accounts inherits a different product.

Will The Structure fix the problems a review finds in a migration?

Not inside the review. The report gives each finding a cost and time to fix that any team can work from. Our engineering partner PERETZ, under common ownership with The Structure, can do the work under a separate contract if you want it, and that is never a condition.

Written by
Yevhen Borovoi
FounderYevhen Borovoi

Founder of PERETZ, DeZZign, The Structure and NEX, partner at SCHOEFFEL. An engineer by training who reads a business as one system: strategy, product, technology, processes and economics.

Next step

Preparing a sale or looking at a target?

We reply within one business day. NDA before any documents change hands.