# How to describe required behaviour without copying the old implementation

> An order can appear cancelled in one system while the warehouse still dispatches it. Understanding that failure helps us specify what a replacement must guarantee without recreating the old implementation.

Paper: 04.2
Published: 2026-10-09
Canonical: https://harten.io/papers/04-2-how-to-describe-required-behaviour-without-copying-the-old-implementation/

Cover illustration: [View the title image](https://harten.io/media/papers/covers/v1/04-2-how-to-describe-required-behaviour-without-copying-the-old-implementation.png?v=2d89370cd366). Illustrative cover artwork, not an application screenshot or a record of measured results.

Imagine cancelling an online order.

You press the button, the website confirms the cancellation and you move on. Two days later, the parcel arrives. Your card has been charged.

From the customer's point of view, something fairly basic has gone wrong. They asked to cancel the order and were told it had been cancelled. The business then fulfilled it anyway.

Now imagine being the team asked to replace the ageing order-management application behind that experience.

The application has been running for years. Its cancellation process is spread across the user interface, a stored procedure, several database fields, a payment integration, a warehouse interface and a retry job. When things go wrong, operations has a queue of cases to investigate.

The modernisation team can read the code. They can trace the cancellation procedure and identify the values it changes. They can even reproduce that sequence in a new application.

And if they do only that, they may reproduce the very behaviour that allowed the order to be sent after the customer was told it was cancelled.

In [04.1 — How to decide which behaviours need to survive the migration](https://harten.io/papers/04-1-how-to-decide-which-behaviours-need-to-survive-the-migration/), I explored how we decide what the organisation still needs, what it can change and what it should deliberately leave behind.

That gives us an answer about *which* behaviour matters. It does not yet give an engineer, architect or software agent a sufficiently clear description of *what must remain true* when the behaviour is implemented differently.

I want to work through that second problem here.

## The order was cancelled in the application, but not in the business

In our example, the customer submits the request at 14:07.

The order-management application checks the status it currently holds. As far as that application knows, the order has not been dispatched, so it accepts the request. A procedure sets the order status to `CNL`, updates a payment flag and marks the stock for release.

The website displays a confirmation.

But the warehouse handed the parcel to the carrier at 14:04. Its dispatch message has been delayed and will not reach the order-management application until 14:11.

There is nothing especially complicated about the individual pieces of code. The problem is that they disagree about the state of the order at the moment the customer asks to cancel it.

The order-management application thinks it is still eligible for cancellation.

The warehouse has already dispatched it.

The payment process may act on the dispatch event and capture an earlier authorisation.

By the time the delayed message arrives, the customer has been told one thing and the organisation has done another.

Perhaps an operations analyst eventually sees the inconsistency and arranges a refund. Perhaps the customer has to contact support first.

Either way, a successful update to the cancellation status did not amount to a successful cancellation.

That is the distinction I would want the replacement team to understand before anybody writes the new cancellation service.

If the new specification simply says that pressing Cancel must set the order status to `CNL`, the replacement can satisfy it perfectly and still get the customer's experience wrong.

If it says the payment flag must become `N` and the inventory field must become `RLS`, the replacement may also inherit a particular database design without anyone having decided that those fields belong in the future system.

Those fields matter as evidence of how the existing application works.

They are not necessarily the requirement.

## Follow what happens outside the cancellation screen

I would begin with the customer-visible outcome, then follow the cancellation into the systems and people it affects.

First, what does the organisation mean when it says an order can be cancelled before dispatch?

That needs an agreed answer. Is the boundary when picking begins, when the parcel is packed, when it is handed to the carrier, or when a dispatch message reaches the ordering system?

Those are not equivalent moments.

The code may currently use the last one because that is the information available to it. The business may intend the earlier handover to the carrier to be the cut-off. If those two meanings differ, copying the code is not a neutral decision. It preserves the gap.

For this example, assume the organisation decides that a normal cancellation is allowed until the parcel is handed to the carrier.

That is a business rule. An accountable person needs to approve it.

Now the engineering team can investigate how the existing application observes the handover, how late messages are handled and what the customer is shown while the position is uncertain.

Then there is payment.

The payment provider may have authorised the card when the order was placed without actually capturing the money. For a valid cancellation before dispatch, the requirement may be to prevent capture and release the authorisation as appropriate.

If the order has already been dispatched and payment has been captured, that is a different situation. The organisation may need a returns and refund process. We should not describe it as a normal pre-dispatch cancellation or assume that retrying the cancellation procedure is equivalent to refunding the customer.

Then there is inventory.

If an order is genuinely cancelled, stock reserved only for that order should become available again. But it must not be returned to available stock while the warehouse is still going to ship it, otherwise another order could be promised the same item.

And there are downstream systems.

The warehouse must know what has been agreed. Finance needs the correct payment state. Customer support needs an account of what happened that it can explain. Operations needs to see cases where the cancellation was requested but could not be completed.

None of those responsibilities is captured by the statement "set the order status to cancelled".

Yet they are all part of the behaviour the customer and the organisation depend on.

## The awkward cases are where the requirement becomes precise

Our customer tried to cancel at 14:07, after dispatch had actually occurred.

The next question is what the replacement should do when it cannot establish that fact immediately.

It would be easy to design another screen that says "Order cancelled" as soon as the customer presses the button. That might make the interface feel responsive. It would also repeat the problem.

A more reliable response may be to acknowledge the *request* without claiming that cancellation is *complete*.

For example, the customer might see that cancellation is being checked. If the warehouse confirms that the order has already been handed over, the service explains that cancellation is no longer possible and gives the customer the appropriate returns route.

If the warehouse confirms that the order can still be stopped, the service can complete the cancellation, prevent the payment capture, release the reserved stock and inform the dependent systems.

How quickly that confirmation must arrive, and what the service should do when it cannot, are requirements the organisation needs to decide. They cannot be left to whichever implementation is easiest.

Consider a different order where the customer really does cancel before dispatch, but the inventory system is temporarily unavailable.

The application may have recorded the cancellation request, but stock has not yet been released. The warehouse may also need confirmation not to fulfil the order.

What does *complete* mean here?

I would not want the system to quietly mark the whole process successful if an important part remains unfinished.

It needs a way to represent work that is still pending, retry the appropriate action safely, and bring in an operator where automated recovery cannot resolve the problem.

Repeated requests introduce another question.

A customer whose connection times out may press Cancel twice. A message may be delivered more than once. A background job may retry after a failure.

The replacement should not create duplicate stock movements, repeated refunds or conflicting state changes because the same logical request was processed again.

That is an important requirement, even though the old retry job is only one possible way of achieving it.

The requirement is about avoiding repeated financial and inventory effects. The implementation can be different.

## Some technical details are genuine business constraints

There is a danger in going too far the other way.

Once people start talking about outcomes instead of implementation, they can strip out technical detail that actually matters.

Suppose the warehouse needs confirmed cancellations before 16:00 to complete same-day stock reconciliation.

The current application sends a file every fifteen minutes.

It would be tempting to put both details into the future requirement because that is how the process works today.

But they play different roles.

The 16:00 deadline may be a genuine operational constraint. Missing it could create reconciliation problems or cause warehouse staff to act on out-of-date information.

The fifteen-minute file schedule may simply be the mechanism the old system used to meet that deadline.

A new application could use an event, an API or a file. We should be free to choose whichever is appropriate, provided the organisation's timing and reliability requirements are met.

The same is true of ordering.

If inventory must not be released until fulfilment has been stopped, that dependency is part of the required behaviour. Removing it could allow the same stock to be allocated twice.

But the old procedure name, message format and status codes do not automatically need to survive.

This is why I think a useful specification needs both the outcome and the reason behind it.

Without the reason, it is easy to discard a necessary control.

Without separating the reason from the existing mechanism, it is easy to recreate unnecessary complexity.

## Write something a different implementation could satisfy

By now we have enough to describe the intended cancellation behaviour without dictating the old implementation.

For this illustrative order-management system, I would write something along these lines, subject to the business owners confirming the policy and timing:

> An order may be cancelled through the normal cancellation process only while it meets the agreed pre-dispatch eligibility rule. The system must distinguish receiving a cancellation request from confirming that cancellation has completed. If the fulfilment state cannot be established, the request must remain visibly pending rather than being reported as completed.
>
> For an accepted cancellation, the system must prevent subsequent fulfilment and payment capture, release stock reserved solely for that order and communicate the outcome to dependent systems within their agreed operational deadlines. Where one of these actions cannot be completed, the system must retain the unresolved state and provide a controlled route to recovery or authorised intervention. Repeated requests and retries must not cause duplicate financial or inventory effects.
>
> Where the order has already passed the agreed dispatch boundary, the system must not confirm a normal cancellation. It must direct the case into the separately authorised returns or refund process where applicable.

That is more demanding than a description of a stored procedure.

It tells us which conditions matter, what a successful cancellation means, what should happen when the normal path fails and what must not happen twice.

It also leaves the engineering team with meaningful freedom.

The replacement might use a workflow engine, an event-driven design, a transactional component with an adapter, or something else entirely.

I would not settle those architecture choices inside the behavioural specification unless the evidence shows that a particular constraint makes one unavoidable.

What I would keep behind the requirement is its supporting evidence.

The dispatch boundary should refer to the approved business policy.

The payment behaviour should be checked against how authorisation and capture actually work.

Inventory constraints should be confirmed with the warehouse.

The deadline should come from the operational process that depends on it.

The retry behaviour should be informed by source traces, production examples and previous failures where those records exist.

Otherwise, we have simply replaced undocumented code with undocumented assertions.

And the next team will inherit a cleaner-looking document that it still has to take on trust.

## Keep the question we still cannot answer

Now imagine the team finds another detail.

For one category of order, the old stored procedure waits ten minutes before releasing stock after cancellation.

The code does it consistently.

There are examples showing it happens.

Nobody can establish why.

It may be protecting an old warehouse process.

It may be compensating for delayed messages.

It may be a workaround for a defect that disappeared years ago.

Or it may still be preventing a race condition nobody has thought to test.

We do not know.

I would not convert that ten-minute delay into a permanent requirement simply because the code contains it.

I would not remove it simply because the future architecture has better messaging.

I would retain the observation, identify which orders it affects, investigate the downstream dependency and make the uncertainty visible in the proposed specification.

If the consequence of being wrong is material, the programme may need to preserve the delay temporarily or isolate those orders until the decision is resolved.

If better evidence establishes that it serves no remaining purpose, the accountable owner can authorise its removal.

The important thing is that the engineering team, and any agent working with it, does not quietly make a business decision from an unexplained technical detail.

That would be especially easy to do during a rewrite because the new code will look cleaner without the delay.

Cleaner code is not evidence that the behaviour was safe to remove.

## Why this matters when agents are doing more of the engineering

This is where the example becomes particularly important for agentic engineering.

A capable software agent can analyse the old cancellation procedure, identify the fields it updates, generate a replacement service and write tests for it. It may do that much faster than a person.

But imagine the agent is given only the old code and asked to preserve cancellation behaviour.

It can implement the `CNL`, `N` and `RLS` transitions perfectly. It can write tests proving those transitions happen. It can return a clean build and a convincing account of what changed.

And we could still end up with a customer being told an order is cancelled after the warehouse has already dispatched it.

The agent and its tests may have agreed with one another because both were based on the same incomplete description of the problem.

Faster generation does not resolve that missing business decision.

What changes the position is giving the engineering system a clear, authorised description of the required behaviour, with evidence behind it and explicit uncertainty where the answer is not known.

Then the agent has room to choose an implementation.

It can design the retry handling, build the integration, change the internal state model, create tests and verify mechanical properties without being told to reproduce the old stored procedure.

But it should not decide, merely from the code, what counts as dispatch, whether the ten-minute delay is obsolete or which financial effect is acceptable when an order has already shipped.

Those decisions belong with the people accountable for the business outcome.

The same specification also gives verification a basis that is independent of the generated implementation.

We can test whether a customer is incorrectly told that cancellation is complete. We can test whether delayed warehouse messages are handled. We can test payment capture, reserved stock, duplicate requests and incomplete recovery.

We are checking the behaviour the organisation chose, not simply whether the new code behaves as its author expected.

That is the reason I put clarity before the choice of technology or code structure.

The work is not to create a cleaner description of the legacy implementation. It is to carry forward the behaviour the organisation deliberately chose to preserve, with enough evidence and precision that a different implementation can be built and challenged against it.

For agentic engineering, that distinction becomes more important as machines do more of the implementation. We need to be very clear about what they are being asked to achieve, what they are free to change, and which decisions they are not authorised to make.

The next question is whether the replacement actually satisfies the description we agreed.

That is what I will look at in **04.3 — How to prove the new system still behaves as required**.

---

*This paper uses an illustrative order-management scenario, not a claim about a particular customer's application.*

*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/).*
