0015. Event ordering and conflicts
- Status: Accepted
- Date: 2026-10-27
Context
Section titled “Context”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.
Decision
Section titled “Decision”- An order’s state is rebuilt by
replayOrder: drop repeated ids, applyorder.openedfirst, 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
conflictslist 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
UPDATEandDELETEon the events table.
Consequences
Section titled “Consequences”- 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
receivedAtso 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