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.
Order, OrderLine, OrderLineModifier, OrderLineCompositeFill, OrderPayment, OrderEvent — runs open → settle → close, and closing lands a Mallow Invoice (the register-payment subscriber flips it to PAID). Order close is concurrency-hardened with a pessimistic lock and a status re-assert. Refunds and returns (an OrderRefund that books a Mallow CreditNote and restocks Trantor via the outbox), even-split payment, delivery-address edit, and a receipt-printer driver seam now ship; a ledger-readiness probe fails a refund up front if the Mallow accounts are missing. Subscriptions v0 (Plan / Subscription / per-period charge, billing into Mallow) shipped June 7, and the POS path now charges sales tax — the order total and the Mallow invoice can no longer diverge. KDS coursing refinements (split, merge, transfer) continue.
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, or an even-split endpoint that does server-authoritative N-way share math with exact penny-remainder distribution — the third split shape alongside split-by-item and split-by-seat. - OrderRefund & returns
- A post-close refund / return path. An
OrderRefundflips its lines toreturnedand books the reversal exactly once — either a MallowCreditNoteor a Payments refund, never both — with an optional card disbursement and a durable outbox subscriber that restocks Trantor inventory. Aledger-readinessprobe hard-fails the refund up front when the tenant's Mallow accounts aren't seeded, rather than failing silently at posting time. - Subscriptions (v0)
- Recurring revenue as a real state model. A
Plandefines what recurs (price, cadence); aSubscriptionbinds a customer to it; each billing period books aSubscriptionPeriodCharge. The billing cycle charges, invoices into Mallow, and advances the period — with pause / resume / cancel as first-class transitions. Beth reads subscriptions and operators manage them from the Demerzel console. - 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}/payments/even-split | Settle an order as an even N-way split (exact penny-remainder math). |
| PATCH | /order/v1/orders/{id}/delivery-address | Set, replace, or clear a delivery order's ship-to address. |
| POST / GET | /order/v1/orders/{id}/refunds | Issue a refund / return; books a Mallow CreditNote or Payments refund and restocks inventory. |
| GET | /order/v1/ledger-readiness | Probe whether the tenant's Mallow accounts are seeded for posting. |
| 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. |
| POST / GET | /hober/v1/plans | Define or list recurring Plans. |
| POST / GET | /hober/v1/subscriptions | Subscribe a customer to a Plan; list subscriptions. |
| POST | /hober/v1/subscriptions/{id}/bill | Run the period charge — charge, invoice into Mallow, advance the period. |
| POST | /hober/v1/subscriptions/{id}/pause · /resume · /cancel | Subscription lifecycle transitions. |
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.