A shift is not closed just because the screen says so

A cashier taps Close Shift. The network fails. Did the cash report close? Was the event published? Can they try again? These are architectural questions.

Innbase editorial illustration about a shift is not closed just because the screen says so, using linked records and hospitality symbols.

PUT THIS GUIDE TO WORK

Explore this topic with AI.

A clearer explanation. A useful checklist. A next step for your hotel.

Get a plain-English explanation with everyday hotel examples.

Open ChatGPT

Opens a new tab with this guide and your question ready.

A cashier taps Close Shift. The network fails. Did the cash report close? Was the event published? Can they try again? These are architectural questions.

The failure mode behind a simple button

A shift close sounds trivial until the request times out just after the database commits. From the staff member’s view, nothing happened. From the system’s view, the shift may already be closed. If the app blindly retries with a fresh operation, it can trigger duplicate side effects or invent a second close.

While building Innbase, we treated this as a state and evidence problem, not as an opportunity to make the loading spinner prettier.

First, make the lifecycle explicit

Our lifecycle distinguishes an active shift from a closeout undergoing validation. A validation failure produces a blocked state with a route to correct and retry; a successful validation produces the closed state. Exceptional force-close is a separate authority path.

That vocabulary matters. It prevents a partially validated closeout being presented to the next supervisor as a settled fact.

Next, keep the business transition atomic

The authoritative close decision and the event destined for downstream consumers belong in the same database unit of work. Either the transition and its outbox record commit together, or neither does. Sending a notification directly before commit is an invitation to tell the hotel something that did not happen.

An outbox worker can publish later. Publication does not rewrite the authoritative outcome of the closeout command.

What happens when the worker crashes?

Background delivery uses ownership fencing and a claim token so a worker that lost its lease cannot quietly mark another worker’s publication as complete. Consumers must tolerate replay; an event can be delivered more than once even when the business command executed once.

Per-aggregate ordering prevents a late earlier event from contradicting a newer state in projections. Delivery and projection are separate correctness questions.

What our certification did—and did not—prove

Our shift certification established a conditional pass for the lifecycle and certified the production write and projection delivery paths on the sealed revision. We did not represent that as proof the full repository CI had passed. That distinction is deliberate: narrow evidence should support narrow claims.

One open boundary in the engineering programme was production-persistent recovery for certain in-progress idempotency crashes. It is safer to name a gap than to confuse a test double with a production guarantee.

The principle we carried forward

The interface can say “closed” only when the authoritative command has completed; notifications and projections may follow asynchronously. If the result is uncertain to the client, reusing the same scoped operation identity is safer than inventing another operation.

For a hotel manager, the benefit is not knowing what an outbox is. It is being able to trust the shift status after a failed connection, retry or background-worker restart.

Keep reading

This is one part of a connected operating day. Read our related guide, “Two attendants. One last item. The architecture has to choose.”, for the next operational perspective.

Share this guideWhatsAppLinkedInX

GOOD QUESTIONS

Common
questions.

Still puzzling something out for your property? We’re happy to talk it through.

Ask the team
What is an outbox in backend engineering?
A transactional outbox saves an event for later publication in the same database transaction as the business state change.
Why does shift closeout need idempotency?
A client can retry after a timeout; idempotency makes repeated attempts of the same authorised command resolve consistently instead of performing duplicate closeouts.