Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2412 min read

I reverted a cancelled reservation and silently double-booked the table — state machine (PR #603 BBB F3)

Elodie Strasbourg La Petite France Pont du Corbeau 41-yo 16-yr Winstub La Cigogne 48-cover Alsatian winstub thMenu Platinum 19 months reservations + bill requests + order tracking. Sunday lunch service intense 16 pre-fixe reservations 12:30-14:30. 14:25 Elodie phone Monsieur Muller 6 people 14:00 table 6 confirmed. Door family of 4 walking in Bernard reservation table 6 14:00. Two separate reservations on same table at same time no warning uq_resv_active_slot supposed to catch. Fast pivot table 4 adjacent six-top + aperitif house Muller party accept crisis managed. Elodie mental note how thMenu let two reservations same table same time. Evening admin panel inspect timeline. Forensic audit log Monday 09:15 Bernard pending table 6 Sunday 14:00 4 people; Monday 09:20 Bernard confirmed; Thursday 11:42 Antoine wrong button cancelled accidental; Thursday 11:44 Antoine revert_cancel confirmed; Sunday 14:22 Muller pending table 6 14:00 6 people; Sunday 14:22 Muller auto-confirmed. Two reservations on (table_id=6 reserved_at=2026-05-24 14:00) both confirmed uq_resv_active_slot should have rejected. 3 wrong theories (1) index not deployed PRAGMA index_list live migration 0063; (2) reserved_at string drift PR #555 RR F4 migration 0077 both post-date normalised not drift; (3) auto-confirm race UNIQUE check same path. Forensic apps/web-admin/src/app/api/reservations/[id]/route.ts UPDATE reservations SET status=? updated_at=? WHERE id=? no state-machine. Tracing (1) Monday 09:15 Bernard pending no conflict; (2) Monday 09:20 confirmed tuple (table_6 Sunday 14:00 pending+confirmed) live index; (3) Thursday 11:42 cancelled partial WHERE no longer matches slot freed; (4) Thursday 11:44 revert_cancel confirmed UPDATE no state-machine check no conflict at that moment; (5) Sunday 14:22 Muller pending UNIQUE check Bernard active Muller INSERT should hit 23505 — INSERT log Muller 23505 UNIQUE violation BUT error handler try catch swallow console.error return ok=true CUSTOMER SEES SUCCESS INSERT FAILED auto-confirm cron non-existent id nothing. Yet Elodie phone Muller mental note table 6 14:00 6 people system no row but in head. Real conflict not on screen only Bernard row + Elodie mental note. Beyond obvious case PATCH bypass enables cancelled->confirmed revert with stale intent two reservations sequentially cancelled one reverted other auto-confirms order reads index at wrong moments slot silently double-books. UNIQUE encodes 'what is active now' historical intent lost. PR #603 batch BBB F3 canonical fix explicit state machine LEGAL_TRANSITIONS={ pending: [confirmed,cancelled], confirmed: [completed,cancelled,no_show], cancelled: [], completed: [], no_show: [confirmed] }. Before status change (a) SELECT current (b) check target ∈ LEGAL_TRANSITIONS[current] (c) not in → 422 illegal_transition current target allowed_next. Cancelled->confirmed REJECTED Antoine create new reservation adjacent table different time. Bonus completed->pending blocked email re-fire spam. UI revert button removed from cancelled new 'Create new reservation (same details)' button. Production audit 90-day cancelled->confirmed 23 restaurants 47 cases 4 actual double-bookings 43 no conflict. 4 affected restaurants email + 1-month Pro credit Elodie Winstub La Cigogne on list. 14-day post-deploy 13 cancelled->confirmed all 422 rejected UI new reservation 0 silent double-bookings. Bedri Konya Meram Etliekmek Konagi 40-yo 14-yr 45-cover etliekmek + tirit Saturday wedding season Hakan accidentally cancelled 8-person revert 2-min later 6-person same table same time silent double-book parallel ticket. Pattern PATCH endpoints status change LEGAL_TRANSITIONS lookup MUST free-form silent bypass partial UNIQUE 'currently active' historical intent not preserved state machine preserves. Sibling sweep dish-suggestions (PR #660 X F3) affiliate status (PR #660 X F4) ai-pricing-suggestions (PR #621 FFF F4) Stripe Connect flipPaymentStatus (PR #646 VI F1). Implementation LEGAL_TRANSITIONS const map + SELECT current + target check + 422 current/target/allowed_next + frontend UI hide illegal buttons + sibling sweep + test cancelled->confirmed reject completed->pending reject no_show->confirmed allow. PR #603 reference.

th

thMenu Team

thmenu.com

Found this helpful? Share it.