# Three pending move orders need different recovery steps

Source: https://rasa.community/library/casebook/utilities-service-move/
Author: Rod Rivera
Published: 2026-10-10T09:00:00.000Z

Three move requests all return pending, but their closure instructions differ. No confirmation and a wrong-date reply leave a hold. A lost acknowledgement leaves a scheduled future closure in this fixture.

These are actual offline results from the fictional Amber Grid service. All three still have the service on now:

```text
{"closure_instruction": "held: move-order system has not confirmed", "destination": "17 Kiln Street, Easton", "detail": "no_confirmation", "on_now": true, "orders": 0, "status": "pending"}
{"closure_instruction": "held: read-back did not match the confirmed dates", "destination": "19 Birch Walk, Millbrook", "detail": "readback_mismatch", "on_now": true, "orders": 1, "status": "pending"}
{"closure_instruction": "scheduled after 2026-10-24", "destination": "Flat 5, 41 Quarry Lane, Millbrook", "detail": "acknowledgment_lost", "on_now": true, "orders": 1, "status": "pending"}
```

## Do not recover from the word pending alone

The never-confirming path records no order. The wrong-date path has an order but holds the closure because its read-back disagrees with the customer’s confirmed dates. In the lost-acknowledgement path, the fixture committed an order and scheduled the closure before losing the reply.

| Pending detail       | Orders in fixture | Closure instruction                | Recovery evidence             |
| -------------------- | ----------------- | ---------------------------------- | ----------------------------- |
| No confirmation      | None              | Held                               | Draft and unresolved request  |
| Read-back mismatch   | One               | Held                               | Original order and wrong date |
| Acknowledgement lost | One               | Scheduled after confirmed move-out | Original committed order      |

The fixture’s scheduled closure is not a physical shutdown: every path leaves supply on now. Nor is it proof of a real supplier’s contract. The finding is that a single pending label cannot tell an operator which action already occurred. Carry the original reference and the concrete closure state into recovery.

::::solution{title="Show the counterexperiment script"}

Run this from the pinned companion tests directory shown below: save it as `counterexperiment.py`, then run `python3 counterexperiment.py`. It changes no files and uses no model or provider.

```python
"""Offline counterexperiment. Run from the pinned companion tests directory.
No source files are changed. Replacement functions exist only in this process.
"""
import inspect
import json
import textwrap
import test_guard as t

for to in ("17 Kiln Street, Easton", "19 Birch Walk, Millbrook", "Flat 5, 41 Quarry Lane, Millbrook"):
    service = t.ag.MoveService()
    draft, memory = t.drafted(service, to=to)
    result = t.submit(service, draft, memory)
    current = result["current_service"]
    print(json.dumps({"destination": to, "status": result["status"], "detail": result.get("detail"), "on_now": current["on_now"], "closure_instruction": current["closure_instruction"], "orders": len(service.orders)}, sort_keys=True))
    assert result["status"] == "pending" and current["on_now"] is True
```

::::

## Give operations a held closure to inspect

A move request has two responsibilities: arrange service at the destination and retire service at the old premises. Do not let an unknown order complete the second responsibility by default.

::::diagram{title="A missing or wrong-date confirmation holds the closure"}

```dot
queued [label="Customer confirmed move"];
mode [label="Fixture order-system response"];
never [label="No confirmation: hold closure", class="blocked"];
wrong [label="Wrong date: hold closure", class="blocked"];
lost [label="Committed, reply lost: schedule future closure"];
on [label="All three: service on now"];
queued -> mode;
mode -> never;
mode -> wrong;
mode -> lost;
never -> on;
wrong -> on;
lost -> on;
```

::::

The never-confirming fixture enters this branch before creating an order. The hold is a recorded service state, not just a reassuring message.

[Source excerpt](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-utilities-service-move-gpt/lib/moves.py#L855-L863):

```python
behaviour = service.premises[draft.to_premises]["order_system"]
amending = order is not None
if behaviour == "never_confirms":
    _hold(service, draft, "move-order system has not confirmed")
    return {"status": "pending", "reason": "move_order_unconfirmed", "detail": "no_confirmation",
            "draft_id": draft.draft_id, "effects": 0, "current_service": service.current_service(draft.from_sp),
            "next_step": ("The move-order system has not confirmed the order. The closure instruction is held and "
                          "the current service stays on. Call check_move_order with the draft_id once; if it is "
                          "still unknown, call route_move_review. Never submit it again.")}
```

The platform operator should be able to find the draft, the original request and the closure hold together. A customer-facing pending message is useful only when that operational state exists.

## Use lookup once before routing this incident

The sample’s unknown path instructs one status check, then review if it remains unknown. That is the playbook, not a general guarantee that software permits only one lookup.

::::callout{type="warn" title="Stop resubmitting an unresolved version"}

The existing-order branch returns pending when the same draft version was already sent. Keep the original draft identifier and query it. A new submission would make the incident harder to reconcile.

::::

This separate branch handles a known order whose submitted version has not been verified. It directs lookup instead of producing another effect.

[Source excerpt](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-utilities-service-move-gpt/lib/moves.py#L835-L842):

```python
order = service.orders.get(draft.order_ref or "")
if order is not None and order["draft_version"] == draft.version:
    if order["state"] == "verified":
        return _order_result(service, draft, order, replay=True, effects=0)
    return {"status": "pending", "reason": "move_order_unconfirmed", "detail": "already_sent", "effects": 0,
            "draft_id": draft.draft_id, "current_service": service.current_service(draft.from_sp),
            "next_step": ("This version was already sent and is not confirmed. Never send it again: call "
                          "check_move_order with the draft_id, then route_move_review if it stays unconfirmed.")}
```

::::checkpoint{id="utilities-service-move-decision" question="The move system never confirms the order. Lookup finds no order. May the old service be reported as off?" options="Yes because the customer asked to move|No; keep the closure held and reconcile the order|Yes after another submission" answer="1"}

The customer requested a move. The order receipt must establish what the service system accepted.

::::

## Compare the dates returned by the service

The draft needs exact move-out and move-in dates and resolved premises. A house number alone can match several addresses. A month alone is not a closure date. The premises and date tests check those refusals before an order is accepted.

The read-back-a-day-early test covers a different fault: the service returns the wrong date after submission. That response must not be treated as the receipt the customer confirmed. Keep the current service on while the order is reconciled.

| Operational signal      | Action                                 | Owner                                |
| ----------------------- | -------------------------------------- | ------------------------------------ |
| No confirmation         | Retain closure hold; lookup then route | Move-order support                   |
| Wrong date in read-back | Do not report a verified move          | Move-order support and service owner |
| Destination unresolved  | Preserve draft; resolve or route       | Address-register owner               |
| Lost acknowledgement    | Look up original order                 | Move-order support                   |

## Amend the existing order after a correction

A changed date after a confirmed move is not a second move. The amendment test keeps the original reference, changes nothing before fresh confirmation, and then updates the order revision. It leaves one order in the fixture.

That contract costs a held-closure queue and an amendment endpoint. The service owner must decide how unresolved dates affect support and billing. This sample supplies no meter operations, tenancy rules or real supplier guarantees.

## Rehearse the closure-hold playbook

Run these checks from the pinned companion project. They use Python's standard library and make no model calls. The outputs below were recorded on 7 October 2026; elapsed times can differ.

```bash
git clone https://github.com/RasaHQ/rasa-community-resources.git
cd rasa-community-resources
git checkout 4aa0c4419dc193fef7a969c12d59edcf720f2606
cd examples/mantle-text-utilities-service-move-gpt/tests
```

::::run{cmd="python3 -m unittest test_guard.MoveTests test_guard.DateTests test_guard.PremisesTests test_guard.ReceiptTests -q"}
::::

```text
----------------------------------------------------------------------
Ran 30 tests in 0.005s

OK
```

The [companion tests](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-utilities-service-move-gpt/tests/test_guard.py) contain the assertions for these cases. To print the additional fixture states yourself, run this script from the same directory:

::::solution{title="Show the script that printed the fixture states"}

Paste it into a file such as `trace.py`, then run `python3 trace.py`. It prints selected fields from the actual tool results; it does not invent replies.

```python
"""Run from the pinned companion project tests directory. Stdlib only."""
import json
import test_guard as t

service = t.ag.MoveService()
draft, memory = t.drafted(service, to="17 Kiln Street, Easton")
result = t.submit(service, draft, memory)
print(json.dumps({"status": result["status"], "detail": result["detail"], "effects": result["effects"], "on_now": result["current_service"]["on_now"], "closure_instruction": result["current_service"]["closure_instruction"]}, sort_keys=True))
lookup = t.ag.check_move_order(service, t.ME, draft["draft_id"])
print(json.dumps({"lookup": lookup["status"]}, sort_keys=True))
```

::::

Use the sandbox to inject missing and wrong-date replies. The move-order owner should inspect the original reference; the operator should inspect the hold. [Design the human handoff](/library/guides/design-a-human-handoff/) around that unresolved record.

::::cta{href="/library/guides/design-a-human-handoff/" label="Design the move-order review handoff"}
::::

The [complete fixture implementation](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-utilities-service-move-gpt/lib/moves.py#L1-L1034) defines the service state and remaining branches cited here.