ProductField notes

An undo button that tells the truth

A TyreStack fitter now gets 30 seconds to take back a Done tap, but only while undoing it would not falsify the stock or money records. The same audit found our board plotting jobs at invented places.

30s set large on a pale grey background beside a phone showing a fitter job screen with test data, and an Undo button with a 30 second countdown ring, above the words: Undo, but only while it is still true
30 sundo window after Done or Can't complete
2 to 18 kmfrom the patch centre, where pinless jobs were plotted
£0for the postcode lookup, a free service
6/6post-deploy checks passed on the undo release

A fitter on a customer’s driveway taps Done on the wrong job. Until 25 October that tap was permanent. A mistaken Can’t complete was worse: the job dropped straight back onto the dispatcher’s To place pile with no reason attached.

TyreStack is our software for mobile tyre fitters: a desk and scheduling board for the office, and a phone app for the fitter in the van. On 24 October we ran a second audit of the scheduling side, as five independent read-only reviews, one per angle. Its verdict was plain. The scheduling engine’s hard rules are strong, but the system was not yet safe for a first paying fitter. Two of its findings are below, and both come down to software claiming something it could not back up.

Thirty seconds, if it is honest

An undo button is easy to build badly. The easy version remembers the last state and puts it back. But Done is not just a status. It can take tyres off the van’s stock and settle a payment. An undo that flips the status back without un-fitting the tyre leaves the stock count wrong. One that pretends to reverse a payment already taken is a lie in the money records.

So the server decides, at the moment of the tap, whether an undo would be honest. When a fitter taps Done or Can’t complete, it saves what the job looked like just before: its status, its slot on the board, the van’s running order for the day and any overrun. If nothing irreversible happened, the reply carries an undo deadline 30 seconds ahead. If stock came off the van, reserved stock was released, or the job was paid and invoiced, the reply carries no deadline, and the app offers no button at all.

Fitter taps Doneor Can't completeDid it move stock or money?tyres off the van, stock released,or a payment takenyesNo Undo buttonthe office corrects itnoUndo offered: the job and its slot can be put backWindow closed0 s30 sOffice moved the job meanwhile"The office has changed this job since.Ask them to put it right."Fitter taps Undo after 30 s"Too late to undo.Ask the office to put it right."
The rule the server applies. The refusal wording is the server’s own, written to be shown on the phone as it is.

Inside the window the undo is checked again when it arrives. It is refused if the 30 seconds are up, if it comes from a different fitter, or if the office has changed the job or reordered that van’s day in the meantime. A thumb slip in a driveway must not overwrite a dispatcher’s newer decision. Every refusal is one plain sentence that ends the same way: “Stock has been taken off the van. Ask the office to put it right.”

Why 30 seconds? The audit suggested 10 to 30. We took the top of that range and turned down anything longer, because stock and payments move on, and the longer the window stays open the more often the honest answer becomes no. An open-ended undo would mostly be a button that refuses.

The same change added “do this one later”. A fitter can now leave a job they have not started: it keeps its slot on the board, moves to the end of their own list, and the board flags who left it and when. Before, the only way to leave a job was to fail it. A Can’t complete now carries the fitter’s reason onto the card on the board, so the dispatcher sees why the job came back.

One detail we did not expect. The table of fitter events has a check on its allowed event types, and that check cannot be widened on a live Durable Object without rebuilding the table. So undo and defer have two small tables of their own. Older versions of the phone app ignore the new fields.

The server side went to production on 25 October and its six post-deploy checks passed. The phone app’s Undo and Later buttons, and an “Are you sure?” on Done, were not built yet on 26 October. The contract went first on purpose, so the app is built against an API that already exists and already refuses correctly.

The fitter app's Today screen with a demo banner reading fabricated data: one of seven jobs done, the next job at 09:15, a Running late button, and the day's jobs on a timeline with drive times between them.
Today
A demo job screen for a puncture repair call-out: customer, vehicle, tyre size, two stock items to pick from the van with checkboxes, a green Finish job button and a red Can't complete link.
A job
The van stock screen with demo data: tyres listed by size with bin, DOT date code and quantity, one marked ageing and one marked too old, and a Count stock button.
Van stock
The fitter app with demo data. Finish job and Can't complete are the taps the undo protects; the stock screen is the record it refuses to falsify.

The map was guessing

The audit’s first finding, marked to fix before anything else, had nothing to do with buttons. A job created at the desk had no map pin, and no code anywhere looked an address up. So the board put each one at a made-up point: a hash of the postcode turned into an angle and a distance from the centre of the fitter’s patch. The point was stable, so the same postcode always landed in the same wrong place. And it was labelled with postcode precision, so the arrival window promised to the customer looked exact.

2 to 18 kmfrom the centre of the patch: where a job with no pin was plotted, chosen by a hash of its postcode rather than by where the house is.

Everything downstream was priced on that point: drive times, run order, the one-hour windows. The road-time work that came before had only made the times between invented points more precise.

A fair question is why software that labelled a guess with postcode precision got this far before anyone caught it. The honest answer is the stage: there was no paying fitter and no real customer on TyreStack yet, only our own demo data and a second read-only audit done on purpose before a first launch. To our knowledge, nobody outside Mediant had been told a wrong arrival hour. That is exactly why the audit marked it fix-first rather than fix-eventually, and the trigger that would turn this from a pre-launch finding into a live incident, a real booking confirmed on an invented point, is the one we closed before it could happen.

The fix shipped on 24 October. A background job sends the postcode on its own, with no name, phone number or street, to postcodes.io, a free service that needs no key, and caches the centre point it returns. A postcode centre is good to about a street, which is enough for a one-hour window. A dispatcher’s own pin still wins. Until the lookup answers, the job keeps a made-up point but is marked approximate: the offer says “about” and the board asks for a pin. A failed call is retried every five minutes, and nothing waits on it.

We looked at paid geocoders with house-level accuracy and passed for now, on cost and because a postcode centre already gets a one-hour window right. If arrival times turn out wrong in practice, that is the next step. postcodes.io only ever sees a postcode, but that still makes it a processor of address data, so it goes on our sub-processor list before any fitter signs up.

The tests for the new lookup passed, 15 of 15, and it deployed to staging the same evening. We have not yet watched it place a real postcode there, because the staging sign-in is gated and a plain request only proves the site answers. That check, and the Undo button on the phone, are the next two jobs.