Primitive spec

Trantor

The physical-world primitive. Trantor owns resources that have identity and location — rooms, courts, lanes, tables, carts, stylists, kitchen stations, POS terminals — and the atomic allocation of those resources to other primitives that need to hold them.

What it owns

Trantor models physical things. It does not own people (that is Terminus) — a stylist Resource in Trantor carries a terminusPersonId pointer and only the physical-state delta (current location, current status, calendar) that Terminus does not need to know about. One Person row in Terminus, one Resource row in Trantor that references it. No identity duplication.

Trantor also owns the act of allocation: the all-or-nothing hold that Seldon needs when it confirms a Booking and the inventory consume that Hober performs when it closes an Order. Allocation is pessimistic; conflict surfaces as a structured 409.

Concepts

ResourceType
A category of interchangeable physical things — tee_time_slot, standard_king_room, cart, stylist, pos_terminal, kitchen_station, table. Allocation works against the type, not the instance.
Resource
A concrete instance of a ResourceType. Carries optional terminusPersonId for human resources, location, state (open / closed / out-of-service), and a calendar reference for availability windows.
ResourceHold
An atomic lock on N instances of a ResourceType for a specific consumer (typically a Seldon Booking). All-or-nothing: a request for 4 carts at a moment that only has 3 free returns a structured conflict rather than holding 3. Expires if not consumed; an expired-hold sweeper releases them.
TrantorAllocator
The service Seldon and Hober inject directly today. Three methods: queryAvailableTypes (what is free in this window), holdResources (atomic hold, throws AllocationConflictException with structured shortfall on failure), releaseHolds (cancel a hold without consuming).
Discrete resource state
Some Resources have a long-lived state that is not a Hold. A dine-in table is held "until the tab closes" rather than "for a 90-minute window" — modelled as a state flag on the Resource (with the Order id in linkRef), not a ResourceHold row. Different physics, different storage.
Calendar-aware allocation
Resources can carry an availabilityTimeSchemeId pointing at a Seldon TimeScheme. The allocator respects open hours, maintenance windows, and per-resource calendars. Time-window-aware allocation against arbitrary calendars is a follow-up; today's allocator works on instantaneous counts.

API surface

All endpoints are versioned under /trantor/v1/. Seldon and Hober typically call TrantorAllocator directly via dependency injection rather than through HTTP; the endpoints below are for admin tooling, external integrations, and the AI-driven authoring surface.

MethodPathPurpose
POST / GET/trantor/v1/resource-typesCreate or list ResourceTypes for the tenant.
GET / PUT / DELETE/trantor/v1/resource-types/{id}Fetch, replace, or soft-delete a ResourceType.
POST / GET/trantor/v1/resourcesCreate or list concrete Resources of a given type.
GET / PATCH / DELETE/trantor/v1/resources/{id}Fetch, modify (state, location, calendar), or soft-delete a Resource.
POST/trantor/v1/holdsPlace an atomic ResourceHold. Body specifies the type, count, and window. 409 on shortfall with structured detail.
DELETE/trantor/v1/holds/{id}Release a ResourceHold without consuming.
GET/trantor/v1/availabilityQuery free counts per type for a window.

Example: hold two carts

Seldon's Booking flow calls this internally; the equivalent HTTP shape:

POST /trantor/v1/holds
Content-Type: application/json
Authorization: Bearer <token>
Idempotency-Key: 7a4b8c1f-…

{
  "resourceTypeId": "rt_cart",
  "count": 2,
  "window": {
    "startsAt": "2026-06-12T09:00:00Z",
    "endsAt":   "2026-06-12T13:00:00Z"
  },
  "linkRef": {
    "kind": "seldon_booking",
    "id":   "bk_01JAZB…"
  }
}

If only one cart is free, the response is a structured 409:

{
  "type":   "https://errors.foundation.dev/allocation-conflict",
  "title":  "Allocation conflict",
  "status": 409,
  "detail": "Insufficient resources of type rt_cart in window",
  "resourceTypeId": "rt_cart",
  "requested": 2,
  "available": 1,
  "conflictingHoldIds": ["rh_01JAZC…"]
}

How it fits with the rest

flowchart LR
  S[Seldon Booking] -- hold --> T(Trantor)
  Ho[Hober close] -- consume --> T
  T -. identity for humans .-> Te[Terminus]
  Pa[Payments POS terminals] -. device .-> T
            

Seldon calls TrantorAllocator during Booking confirm to hold the resources the Offer requires. Hober calls it on Order close to consume Hardin recipe ingredients out of Trantor inventory. Terminus is referenced for the identity behind any human Resource. Payments POS Terminals are Trantor Resources of type pos_terminal, not a Payments-owned entity. Kitchen stations, tables, and printers all live here too — one physical-asset choke point.