2026-08-15 · 4 MIN

Undo on a write you cannot recall: a queue, two mirrored UPDATEs, and a window that is a floor

— WRITING

+

2026-08-15 · 4 MIN

A correction posted into somebody's general ledger cannot be pulled back from the other side. There is no unsend. Reversing a posted bill adjustment means a second journal entry, and the auditor sees both of them forever. So the feature request "let me undo that" is not a button problem.

The build I want to describe is an agent that reconciles a purchase order, a goods receipt and a bill against each other overnight, clears what agrees inside a tolerance, and queues what disagrees for a bookkeeper. When the bookkeeper approves a suggested fix, that fix eventually becomes a write into QuickBooks. Here is what "eventually" is made of.

Undo works by not doing the thing yet

Approving does not call the ledger. It inserts a row:

const postsAt = Date.now() + UNDO_WINDOW_SECONDS * 1000; // 60
await sb.from("matchrail_corrections").insert({
  status: "scheduled",
  scheduled_for: new Date(postsAt).toISOString(),
  payload: { approvedVarianceCents: Number(match.variance_cents), /* ... */ },
});

and writes an audit row saying who approved what, on what evidence. A separate cron sweep picks the row up later. Every gate below hangs off that single deferral.

MatchRail landing page for three-way data reconciliation product — It's a professionally-designed, web-based product interface.

MatchRail landing page for three-way data reconciliation product — It's a professionally-designed, web-based product interface.

The race is the whole mechanism

Two actors want the same row: the sweep, which wants to post it, and the human, who wants to kill it. They are written as mirror images of one conditional UPDATE.

// The dispatcher's claim, once per row, per sweep.
update matchrail_corrections set status = 'posting'
  where id = ? and status = 'scheduled' and scheduled_for <= now()

// The human's undo, from the countdown on screen.
update matchrail_corrections set status = 'undone'
  where id = ? and user_id = ? and status = 'scheduled'

Both predicates require status = 'scheduled', so exactly one of them affects a row and the other gets zero rows back. No read-then-write, no advisory lock, no interval in which both believe they hold it. That matters more than usual here because the two things they would each be doing are "write to a ledger" and "promise a human nothing was written".

The loser also has to say which way it went. undo re-reads the row and distinguishes "already undone" from "too late, it is posting", because those are different facts to somebody staring at a timer.

The window is a floor, and copy leaks it

A cron sweep runs on a cadence. A 60 second window plus a 15 minute sweep means a correction posts no sooner than 60 seconds and possibly ten minutes later. Everything user-facing has to be phrased that way, so the pages say "posts no sooner than 60s" rather than "posts in 60 seconds".

Because that number is quoted by a client component and enforced by the server sweep, it lives in a file with zero imports. Putting it next to the dispatcher would drag a service-role database client into the browser bundle the moment the landing page imported it.

That file also documents the cron expression it assumes. Reading this repo back, vercel.json was firing the dispatch route every 5 minutes while the constant, the route header and three rendered surfaces all said 15. Nothing posted early, since the floor is a WHERE clause rather than a schedule, but the deployment was running a cadence none of the published copy describes. Fixed to */15 * * * *.

The check people forget: revalidate at post time

Between approval and the sweep, the documents can move. A vendor re-syncs, a bill line changes, the variance is no longer what the human saw. So the approved figure is frozen into the correction payload and compared against the live match before the write:

if the current variance is not the approved variance, the row goes to held with a message naming both figures, and nothing posts. There is no retry anywhere in this path either. A blind retry against an accounting system is how you post the same adjustment twice, and the duplicate stays invisible until a reconciliation months later.

What each terminal state proves about the ledger

Row statusReached the ledgerHow it got there
undonenoThe human's UPDATE won the race. Audit row records wrote: false.
heldnoDaily post cap burned, exception already closed, or the approved figure moved. Returns to held, never re-arms itself to scheduled.
failednoThe rail refused the write, or write-back for that ledger is not wired. The match stays an open exception.
posted, price/quantity/void kindsyesThe vendor API response is stored verbatim on the audit row, not paraphrased.
posted, hold/accept kindsnoA decision recorded inside the product. wroteToLedger: false is carried explicitly rather than inferred from a rail nobody called.

The last row is the one worth stealing. Two of five correction kinds never touch anything outside the product, and if that distinction is left implicit, an audit trail full of posted tells you nothing about which writes actually happened.

How we built this: MatchRail, https://matchrail.kynth.studio/?utm_source=kynth-hashnode&utm_medium=social&utm_campaign=kynth


One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did — read off the repository and the live site, not written from memory. Join the list.

← All writing