# What a migration needs to preserve from the system it replaces

> A migration should preserve the behaviour the organisation still needs, not every detail of the implementation it is replacing.

Paper: 04
Published: 2026-10-01
Canonical: https://harten.io/papers/04-what-a-migration-needs-to-preserve-from-the-system-it-replaces/

Cover illustration: [View the title image](https://harten.io/media/papers/covers/v1/04-what-a-migration-needs-to-preserve-from-the-system-it-replaces.png?v=5f4da8c1e579). Illustrative cover artwork, not an application screenshot or a record of measured results.

If we replace an application, what exactly needs to remain the same?

I think this becomes one of the harder questions in modernisation once we move beyond understanding the existing system and start deciding what the replacement is actually expected to do.

The obvious answer is to look at the current application.

It already contains years of business rules, operational behaviour, integrations and exceptions. If the new system behaves differently, there is a reasonable chance that something important has been lost.

But the opposite can also be true.

A long-lived application contains old implementation choices, workarounds, constraints introduced by platforms that may no longer matter, processes that exist because two systems never integrated properly, and defects that people have learned to work around.

So a difference between the old and new systems is not automatically a defect, and similarity is not automatically evidence that the migration is correct.

That is where the problem becomes more interesting.

Some existing behaviours may still be essential to how the organisation operates. Others may only exist because of a limitation that disappeared years ago. A few may be defects that people have compensated for so successfully that the workaround now looks like an intentional part of the process.

The application itself rarely tells us reliably which category a behaviour belongs to.

Finding a behaviour in code, production data or an operating process tells us that it exists in the current system. That is useful, but it is only the beginning. We still need to establish why the behaviour exists, whether it was intentional, whether it was introduced because of a constraint that still applies, who depends on the result, and whether the organisation still wants the same outcome in the future.

Those questions have to be investigated before the behaviour can safely become part of the replacement specification.

This paper is the start of the fourth part of The Modernisation Control Papers, a sequence in which I have been working through the modernisation problem one stage at a time.

The earlier papers looked first at understanding the existing application. Before an organisation decides how to change a complex system, it needs a reliable account of what the software actually does, where important behaviour lives, what depends on it, and what remains uncertain.

The most recent papers then looked at what changes when AI performs more of that investigation. A useful conclusion needs more than a convincing explanation. We need to know what evidence supports it, where that evidence is incomplete or contradictory, and what somebody still needs to decide before acting on it.

This paper starts from there.

Once we have a sufficiently reliable picture of the existing application, we face a different question: what from that application actually needs to survive into the system that replaces it?

The existing code cannot answer that question for us on its own. It can show us implemented logic and give us strong evidence about how part of the application works, but that is not the same as establishing the future requirement.

To do that, we still need to know whether the behaviour matters outside the code, whether it exists because of an old technical constraint, whether another system or team depends on the outcome, whether production behaves the same way, and whether the organisation now wants something different.

That is the judgement this part of the series is concerned with.

It is also where modernisation work can go wrong even when the technology work looks competent. A team can understand the old system accurately and still preserve the wrong things, remove something important, or carry old constraints into a new design simply because nobody separated observed behaviour from future intent.

The rest of this paper works through how I would make that distinction.

## Use the existing system to find behaviour, not to design the replacement

Suppose a billing application rounds a calculation at three different points because of the way an older database stored decimal values.

The current implementation may be internally consistent. Users may also have built processes around the results it produces.

At first glance, preserving the three rounding steps may look like the safest option. We know the current system works this way, and changing financial behaviour can have real consequences.

But I would want to ask why the rounding happens in those places before turning it into a requirement.

Was it introduced because the database could not represent the required precision?

Was it intended to implement a regulatory rule?

Does another system depend on the intermediate rounded values?

Do users reconcile against those intermediate numbers?

Or does only the final amount matter?

Those are very different situations.

If the three rounding steps exist only because of a technical limitation that no longer exists, reproducing them in a new system may preserve an implementation constraint rather than a business requirement.

If, however, another system consumes one of those intermediate values, removing the rounding could change behaviour outside the application even if the final invoice still looks correct.

This is why I would not ask only whether the replacement behaves like the current system.

I would ask what the organisation actually relies on.

The same problem appears outside the code.

An operations team may export a report every morning because two systems do not exchange data reliably. A user may enter the same information twice because the applications cannot share it. A nightly job may exist because the old platform could not process something during the day.

Those are all real behaviours, and they are useful evidence because they tell us how the organisation actually gets work done.

But I would not stop at observing them.

If a report is produced every morning, I want to know what happens next. Who reads it? What decision does it support? What happens if it is not produced? Does it catch a failure somewhere else in the process, or is it simply how information has historically been moved between teams?

If somebody enters the same data twice, I want to know whether both entries are genuinely required or whether the duplication only exists because the systems could not share data when they were built.

If a job runs overnight, I want to know whether the timing is important to the business or whether it was chosen because the old platform could not handle the workload during the day.

A behaviour may have been entirely rational when it was introduced. That does not make it a permanent requirement.

The constraint that created it may have disappeared. The business process may have changed. Another system may now provide a better way to achieve the same outcome.

Finding the behaviour gives us something to investigate. It does not yet tell us what the replacement should do.

## A defect or workaround can become normal without becoming a requirement

People adapt to software.

They learn that a report is wrong in one particular situation. They know to rerun a process after a certain failure. They avoid a combination of values because it causes trouble. They maintain a spreadsheet to correct something before it reaches another team.

After a few years, the workaround can look like part of the intended process.

Discovery should capture it because it affects how the system operates today. If we ignore the workaround, we may misunderstand how the organisation actually gets work done.

But capturing a behaviour is not the same as deciding to preserve it.

Suppose an application applies the wrong discount to a small group of orders and the finance team corrects those invoices manually at month end.

The manual correction is part of the current operating process. If we were documenting the application honestly, we would need to include it.

But the fact that the correction happens every month does not make the incorrect discount a requirement.

That example is easy because we already know there is a defect.

Real applications are usually messier.

A behaviour may look intentional because it has existed for years. A workaround may have been introduced by somebody who has since left. A manual check may originally have been added because an integration was unreliable, even though the integration was replaced later. A field may be treated as mandatory by users even though nobody can now explain why.

The application does not label those differences for us.

We have to reconstruct them from the code, the surrounding process, operational history, the people who use the system, and the consequences of changing the behaviour.

That is also why I would be cautious about asking users only what the new system should keep.

Users are often excellent sources of evidence about how a system behaves and where it causes friction. But they may reasonably assume that a long-standing behaviour is required simply because they have never seen the system operate without it.

Part of modernisation is separating familiarity from necessity.

If we do not make that distinction, a migration can reproduce the existing application very accurately and still miss the point of modernising it.

## Find out why the behaviour matters before deciding to keep it

For behaviour that may matter, I would want to understand why it exists and what depends on it.

A calculation required by law is one kind of requirement. A downstream application that still expects a particular interface is another. A recovery mechanism that prevents users losing work after a failure may be another.

Those examples matter for different reasons.

The calculation matters because there is an external rule the organisation must satisfy.

The interface matters because another system depends on a particular interaction.

The recovery mechanism matters because removing it changes what happens to users when something goes wrong.

The fact that all three behaviours exist in the current application is not enough to explain why they should survive.

The reason comes from the consequence of changing them.

This sounds like a small distinction, but it changes the conversation.

Instead of saying, "the old system does this, therefore the new system must do this", we can say, "the old system does this, and this is the outcome or dependency we believe it protects."

Now we have something we can test.

If the outcome can be achieved in another way, the implementation may be free to change.

If the dependency no longer exists, the behaviour may no longer be needed.

If the original reason cannot be established, we know we have uncertainty rather than silently converting history into a requirement.

That is also why code is not enough on its own.

Code can tell us a great deal about the implementation. It can show a calculation, a validation rule, an interface, a retry path or a scheduled process. In many cases that is the strongest evidence we have for where a behaviour is implemented.

But code does not necessarily tell us whether that path is still exercised in production, what happens around it operationally, or why the organisation cares about the result. Those questions often require evidence from outside the repository.

The repository can tell us that a report is generated. It may not tell us that somebody uses the report to stop an incorrect payment before it leaves the organisation.

It may show an API that appears unused inside the codebase. It may not show that another application calls it once at the end of every quarter.

It may show a field that is technically optional. It may not show that an operations team treats the field as mandatory because a downstream process fails without it.

Those are not edge cases around the application. They are part of how the application participates in a larger system.

If the outcome matters, the replacement needs to preserve the outcome.

It does not necessarily need to preserve the report, the API shape, the field, or the manual process that happens to provide it today.

That distinction gives the engineering team room to improve the system without accidentally removing something the organisation still relies on.

## Spend more effort where getting the behaviour wrong would matter more

I would not investigate every behaviour to the same depth.

A small formatting difference in an internal administration screen may be easy to correct later. A change to authentication, financial calculation, entitlement, a safety control or a widely used interface can have a very different consequence.

The amount of evidence we need should reflect that difference.

For a low-consequence feature, the existing code, a few representative examples and review may be enough.

For something more consequential, I would want a stronger basis for the requirement.

That might mean comparing the code with production examples, policy, operator knowledge, telemetry, historical incidents or the behaviour of connected systems.

The purpose is not to collect more material for its own sake.

It is to reduce the chance that we preserve the wrong thing, or remove something whose importance was not obvious from the implementation.

This matters because modernisation teams can easily fall into one of two extremes.

The first is to assume that everything in the old application must be preserved because changing anything feels risky.

The second is to assume that the replacement is a clean start and that old behaviour only matters if somebody can immediately justify it.

Neither position is particularly useful.

Treating everything as sacred makes improvement difficult and carries old constraints forward.

Treating unexplained behaviour as disposable can remove controls, dependencies or edge cases that only become visible after something breaks.

The amount of investigation should follow the consequence of being wrong.

This is also where uncertainty matters.

Sometimes the team will not know whether a behaviour should survive, and I think that is a valid state.

The important thing is to make the uncertainty explicit.

If we know that a behaviour exists but cannot yet establish why, that should be visible in the work. The team can then investigate further, narrow the change, preserve the behaviour for the first release, or make an explicit decision that the remaining uncertainty is acceptable.

What I would avoid is resolving that uncertainty by copying the old implementation simply because nobody is confident enough to do anything else.

That looks safe, but it can quietly turn an old constraint or workaround into a permanent design decision.

I would also avoid doing the opposite and removing the behaviour simply because nobody can explain it quickly.

Lack of explanation is evidence of a knowledge gap. It is not evidence that the behaviour has no value.

## Important behaviour may sit outside the user interface

User interfaces tend to dominate migration discussions because they are easy to see.

People can put the old and new screens next to each other and compare them. Missing fields, different labels and changed workflows are immediately visible.

That makes the interface useful evidence.

It also makes it easy to over-weight.

Some of the behaviour that matters most may not be visible there at all.

It could be the point at which data becomes authoritative, the order in which two systems have to process an event, a control that prevents duplicate processing, the way the application recovers after a partial failure, or an operational decision that is only made when the normal process breaks.

A user may never see those behaviours directly.

They may only notice when one of them stops working.

That is why a migration can look almost identical to the old system and still be wrong.

The screens can match while an event is processed in the wrong order, an audit trail is incomplete, a duplicate transaction is no longer prevented, or an exception path has disappeared.

The reverse is also true.

A replacement can look completely different and still preserve the behaviour that matters.

The workflow may be simpler, the user interface may be redesigned and the underlying architecture may bear little resemblance to the old system, while the important business rules and controls remain intact.

So visual similarity is a poor test for whether a migration has preserved what matters.

The test has to come back to the outcomes, rules, controls and constraints the organisation has decided the replacement still needs to satisfy.

That requires us to look beyond the visible application and ask what happens before the user arrives at a screen, what happens after they leave it, what other systems participate, and what the organisation does when the normal process fails.

## Record the behaviours we are intentionally changing as well

Modernisation is not only about preservation.

Sometimes the whole reason for doing the work is that the current behaviour needs to change.

A process may be too slow, a manual approval may no longer be necessary in every case, an integration may be expensive to operate, or a control may be compensating for a problem the replacement is specifically designed to remove.

Those are legitimate reasons for the future behaviour to differ from the present one.

The important thing is that the difference is deliberate and understood, rather than something we discover after release.

Suppose the current system requires manual approval for every case because the application cannot reliably distinguish low-risk from high-risk transactions.

The future service may be designed to automate low-risk cases while retaining human review for higher-risk ones.

If we compare the systems mechanically, the new application behaves differently.

That difference is not automatically a regression.

It may be exactly what the organisation wanted the modernisation to achieve.

But that only works if the decision has been made explicit.

Otherwise the same difference could be interpreted in two completely different ways.

One person may think the new system has removed a necessary control.

Another may think the old process was deliberately redesigned.

Testing cannot resolve that disagreement if the intended behaviour was never established.

For the behaviours that matter, I would want the migration to make four things clear: what we are keeping, what we are changing deliberately, what we are retiring because it is no longer needed, and what we still do not know.

Those are not administrative labels.

They change how the replacement is designed and tested.

A behaviour we are preserving should appear in the future requirements.

A deliberate change should be tested as the new expected behaviour, not reported as a regression.

Something being retired should have a reason, particularly if another team or process used to depend on it.

An unresolved question should remain visible until the people responsible for the outcome have enough evidence to decide it, rather than disappearing because an engineer, an AI system or a project plan needed a complete-looking answer.

This is also important when people join the work later.

If the only artefact they have is the new system, they may not know which differences were intentional and which were accidental.

Recording the decision gives them a way to understand not only what changed, but why.

## Turn the behaviours we decide to keep into requirements for the replacement

This is where the earlier discovery work starts to become useful to delivery.

An observed behaviour in the existing system is only the starting point.

Before I would turn it into a requirement for the replacement, I would want to know more.

Was the behaviour intentional?

Was it introduced because of a technical or operational constraint at the time?

Does that constraint still exist?

Does the organisation still need the same outcome?

Do users, other systems or controls depend on it?

Has the way the organisation works changed since the behaviour was introduced?

What happens if we remove or change it?

And how confident are we in the answers?

Those questions matter because an observed behaviour can be real without being right for the future system.

That is an important distinction.

A discovery process can establish with very high confidence that an application behaves in a particular way and still not tell us whether that behaviour belongs in the replacement.

The first question is descriptive: what happens today?

The second is a judgement about the future: what should happen tomorrow?

Evidence informs that judgement. It does not replace it.

Once we have enough evidence to answer the relevant questions, we can make a much clearer decision.

The outcome may need to remain exactly the same.

It may need to remain, but be achieved in a different way.

It may be safe to change because the original constraint no longer exists.

Or we may decide that the behaviour can disappear entirely because nobody depends on it and the organisation no longer needs the outcome it once supported.

Only then would I describe the required future behaviour clearly enough that the replacement can later be checked against it.

That does not mean specifying the target architecture.

This distinction matters because requirements can accidentally inherit implementation choices.

If the old system uses a nightly batch job, it is easy to write a requirement saying that the new system must run a nightly batch job.

But that may not be the real requirement.

The organisation may simply need a set of balances to be reconciled before trading starts the next morning.

A future system might satisfy that requirement continuously, or process the reconciliation at a different point in the workflow.

Writing the old mechanism into the requirement would remove that design freedom before the team had even considered the alternatives.

The same thing can happen with databases, interfaces, service boundaries and approval steps.

If one of those things is genuinely a constraint, it belongs in the requirement. For example, a regulatory rule may constrain where data can be stored, an external contract may constrain an interface, or an operational deadline may constrain when processing must complete.

If it is simply how the current system happens to work, I would keep it separate until there is evidence that the future system also needs that constraint.

Going back to the billing example, the important requirement might be how the final amount is calculated, which rule governs that calculation, when the amount becomes authoritative, and what should happen when an adjustment is required.

The new implementation may satisfy those requirements in a completely different way from the old database and still be correct.

That is useful because it gives the engineering team freedom without losing the behaviour the organisation cares about.

It also gives testing something meaningful to check later.

Instead of asking whether the new code resembles the old code, we can ask whether the new system still produces the agreed outcome, under the conditions that matter, including the exceptions and failure paths we decided must still be supported.

That is a much stronger basis for verification than similarity alone.

The next three papers in Series 4 go further into those questions.

[04.1, How to decide which behaviours need to survive the migration](https://harten.io/papers/) looks at the judgement involved in deciding what should be preserved.

04.2 looks at how to describe those behaviours without tying the replacement to the old implementation.

04.3 looks at how to check that the new system still behaves as required.

For me, that is the important shift at this point in the series.

Understanding the existing application tells us what is there today. It gives us evidence about behaviour, dependencies, controls, exceptions and uncertainty.

But it does not make the future decision for us.

Modernisation still requires somebody to establish why a behaviour exists, whether the reason still applies, who depends on the outcome, what would happen if it changed, and what the organisation actually wants the replacement to do.

That judgement sits between discovery and delivery.

It is not a documentation exercise. It is the point at which an organisation decides which parts of the existing system are requirements for the future and which parts are merely evidence about the past.

If we do that work well, the replacement has room to improve without losing the things the organisation still depends on.

If we skip it, the team is forced into one of two bad positions. It either reproduces the past because nobody can justify changing it, or it changes behaviour without understanding what depended on it and discovers the consequence later.

That is why I think this part of modernisation deserves more attention than it normally gets. The difficult work is not only understanding what the old application does. It is deciding, with enough evidence, what the new one still needs to do.

*Editorial note, 1 October 2026: this paper was expanded after publication to make the reasoning self-contained for readers encountering the series for the first time. The position of the paper is unchanged.*

---

*This paper is part of The Modernisation Control Papers, the first major sequence within The Harten Papers. [Read the complete series on harten.io](https://harten.io/papers/).*
