Skip to content

0015. Event ordering and conflicts

  • Status: Accepted
  • Date: 2026-10-27

Events for one order can come from several devices, arrive late after time offline, arrive twice, or contradict each other (a waiter adds an item on a phone just as the cashier closes the order). Every device and the hub must still reach the same state from the same events, and no event may be lost.

  • An order’s state is rebuilt by replayOrder: drop repeated ids, apply order.opened first, then merge the devices’ events. Each device’s events keep that device’s own order (deviceSeq), and devices are interleaved by event id: at each step, the device whose next event has the smallest id goes next. Ids are ULIDs, so this approximates creation time across devices, while a device whose clock jumps backwards still replays in its own order.
  • The reducer is pure and never throws on a valid event. An event that cannot apply (a closed order, an unknown or voided line, a void without a reason, a repeated payment id) is kept in the order’s conflicts list with its reason, instead of being dropped or failing the replay.
  • A closed order accepts only refunds.
  • Stored events are append-only: the SQLite schema has triggers that reject UPDATE and DELETE on the events table.
  • Replaying any set of events, in any order and with duplicates, gives identical state.
  • Conflicts are visible for review in the back office and in reports instead of disappearing.
  • A device’s own events always replay in the order it recorded them. Device clocks only affect how different devices’ events interleave; the hub records receivedAt so badly wrong clocks can be detected later.
  • Fixed-amount discounts cap at the amount they discount, so a replay never fails because voids made a voucher larger than the bill.

For AI tools: llms.txt ·llms-full.txtopenkedai.com