Primitive spec
Hober
The live-order primitive. Hober holds the runtime state of a sale-in-progress — the dine-in tab, the retail cart, the takeout ticket — from open through settle to close, when it becomes an immutable Mallow Invoice.
What it owns
Hober owns mutable runtime state. Mallow owns the finalised ledger — the Invoice that the Order becomes on close. They are deliberately different entities: different audiences (KDS reads Order, finance reads Invoice), different lifecycles (Order mutable, Invoice immutable), different timing (Order exists from open to close, Invoice exists from close forever).
On close, Hober triggers: Mallow Invoice generation, Trantor inventory consume per RecipeIngredient, Payments transaction reconciliation, and a Speaker receipt — all in one transaction. Any failure rolls back the close.
Concepts
- Order
- The live tab. Carries
orderType(dine_in, takeout, delivery, retail_quick_sale, online_pickup, online_delivery), optional table ref (a Trantor DiscreteResource), party size, server, customer, and a denormalised total cache. - OrderLine
- One item on the order. Snapshots
unitPriceCents,displayName, andkitchenNameat add-time, locking the price the customer saw. Status movespending → fired → ready → served, with branches forcancelled,comped, andreturned. - OrderLineModifier & CompositeFill
- Modifiers attach to a line with a snapshotted price delta. Combo slots create sub-lines (
parentLineId) rather than embedded data — KDS routes naturally, modifiers on the side work via the normal modifier table. - OrderPayment
- An applied tender. Multiple per order for split-tender. Carries the tip (tip lives on the payment, not the order, so split tenders each carry their own tip). Optional
appliedToLineIdsfor split-by-item. - Lifecycle
OPEN → CLOSING → CLOSED, withVOIDEDas the abandon branch. CLOSING blocks new lines while payment is in flight. CLOSED is terminal — voids, comps, and returns on closed orders become status changes on lines or new compensating orders, never deletes.- Table state
- A dine-in table is held "until the tab closes," not "for a 90-minute window." Modelled as a
stateflag on the Trantor Resource with the Order id inlinkRef, not as a ResourceHold. Different physics, different storage. - Snapshot at add-time
- Every line, modifier, and composite fill captures
priceCentsand display names at the moment it was added. Catalog changes mid-order never move the goalposts on what the customer agreed to pay.
API surface
Endpoints are versioned under /order/v1/ — the wire namespace uses the descriptive name even though the primitive is "Hober," to match how POS UIs and integrations talk about orders.
| Method | Path | Purpose |
|---|---|---|
| POST | /order/v1/orders | Open an Order. |
| GET / PATCH | /order/v1/orders/{id} | Fetch or modify metadata (party size, table, course pacing). |
| POST / PATCH / DELETE | /order/v1/orders/{id}/lines | Add, modify, or void OrderLines (voids keep the audit row). |
| POST | /order/v1/orders/{id}/fire | Fire pending lines. Daneel routes them to stations. |
| POST | /order/v1/orders/{id}/transfer | Transfer server (audit only). |
| POST | /order/v1/orders/{id}/split | Move lines into N new child orders. |
| POST | /order/v1/orders/{id}/merge | Move lines from N source orders into this one; sources void. |
| POST | /order/v1/orders/{id}/payments | Apply a tender. |
| POST | /order/v1/orders/{id}/close | Finalise. Generates Mallow Invoice, consumes Trantor inventory, fires Speaker receipt. |
| POST | /order/v1/orders/{id}/void | Admin abandon. No Invoice generated. |
| GET / PATCH | /order/v1/lines?station=&status= | KDS read path. Filter by station, status. Bump / recall / serve via line status PATCH. |
Example: open, add, fire, pay, close
POST /order/v1/orders
{
"orderType": "dine_in",
"tableId": "res_table_07",
"serverId": "per_alice…",
"partySize": 2
}
→ 201 Created Order ord_01JAZB…
POST /order/v1/orders/ord_01JAZB…/lines
{
"productVariantId": "pvar_burger_single",
"quantity": 2,
"modifiers": [
{ "modifierId": "mod_medium_rare" },
{ "modifierId": "mod_add_bacon" }
]
}
POST /order/v1/orders/ord_01JAZB…/fire
POST /order/v1/orders/ord_01JAZB…/payments
{ "tenderType": "card", "amountCents": 3500, "tipCents": 500,
"paymentMethodId": "pm_visa_4242" }
POST /order/v1/orders/ord_01JAZB…/close
→ 200 OK Invoice in_01JAZC… (Mallow)
How it fits with the rest
flowchart LR
Server[Server / customer] --> Ho(Hober Order)
Ho -. catalog .-> Ha[Hardin]
Ho -. table / inventory .-> T[Trantor]
Ho -. identity .-> Te[Terminus]
Ho -- close --> M[Mallow Invoice]
Ho -- tender --> Pa[Payments]
Ho -- receipt --> Sp[Speaker]
Ho -- KDS --> D[Daneel]
Hardin is the catalog that lines reference and snapshot from. Trantor provides the table (as DiscreteResource state) and the inventory that close-time consume decrements. Terminus is the identity ref for server and customer. Payments processes each tender via an OrderPayment. Mallow generates the Invoice on close. Daneel runs the kitchen routing, coursing, and comp-approval workflows wired to OrderLine status events. Speaker sends the receipt, gated by Terminus consent.