Primitive spec
Seldon
A universal scheduling primitive that handles appointments, stays, events, queues, multi-stage flows, and open-capacity classes through one model. Offers, availability projections, bookings, and lifecycle emission — not just "slots on a grid".
What it owns
Seldon owns the question "when can this happen, who is doing it, and what state is it in?". It models the bookable concept, projects when it's available, records the customer's claim on it, and drives that claim through a status lifecycle — emitting events at every lifecycle anchor so other primitives can react.
Seldon does not own physical resources (that is Trantor), people (Terminus), money (Payments / Mallow), notifications (Speaker), or workflow orchestration (Daneel). It coordinates them by reference.
Concepts
- Offer
- The thing a customer buys, books, or claims. Polymorphic across
kind(appointment, stay, event, queue, staged_flow, open_capacity) anddurationShape(fixed, set, range, nightcount, queue, undefined). Carries resourcerequirements(refs into Trantor), party capacity, and policy fields likecancelDeadlineSecandmodifyRevalidates. - Availability
- A projection of when an Offer can be booked. One Offer can have several Availability projections — a hotel room on a daily-occupancy grid and in a no-show queue. Five kinds:
grid,range,queue,open_capacity,external. - Time scheme
- The grid backing model. Templated and instance-parameterised, with verticals (lanes, sides, rooms) and horizontals (courses, courts) as arbitrary dimensions. Generates
SlotInstancerows that grid availability projects. - Booking
- A customer's claim on an Offer at a specific Availability position. Anchors are deliberately polymorphic: a grid booking's anchor is a slot id; a stay's is a start/end pair; a queue booking's is a position. Carries Trantor resource holds, Terminus identity refs, and a pinned snapshot of the Radiant policy version at booking time.
- Lifecycle
- Statuses move
pending → confirmed → checked_in → live → completed, withcancelled,no_show,expired, andwaitlistedbranches.cancelDeadlineSecandmodifyRevalidatespolicies gate cancel and modify operations. A lifecycle auto-advancer subscribes to the outbox sobooking.startingandbooking.endingevents promote status without operator intervention. - Emission pipeline
- Bookings fire scheduled events at each lifecycle anchor (
checkInOpenAt,noShowAt,startedAt, …). A four-worker pipeline backed by a Valkey ZSET (Loader, Watcher, Meerkat, Runner) delivers them in deterministic order. Daneel hook triggers wire side effects to those emissions. - Waitlist
- Modelled as a Booking with
status=waitlisted, not a separate table. When capacity opens, the Promotion service transitions the highest-priority waitlisted booking toconfirmed.
API surface
All endpoints are versioned under /seldon/v1/, read tenantId from the bearer token, and return RFC 7807 problem details on error. Booking write paths use Idempotency-Key to make retries safe.
| Method | Path | Purpose |
|---|---|---|
| POST / GET | /seldon/v1/offers | Create or list bookable Offers. |
| GET / PUT / DELETE | /seldon/v1/offers/{id} | Fetch, replace, or soft-delete an Offer. |
| GET | /seldon/v1/availability | Query Availability projections for an Offer over a time window. |
| POST | /seldon/v1/bookings | Create a Booking. Holds Trantor resources, checks Terminus entitlement, pins the Radiant policy version. |
| GET | /seldon/v1/bookings/{id} | Fetch a Booking with its full lifecycle state. |
| PATCH | /seldon/v1/bookings/{id} | Modify a Booking. Reschedules swap Trantor holds and resync emissions. Gated by cancelDeadlineSec + modifyRevalidates. |
| POST | /seldon/v1/bookings/{id}/cancel | Cancel a Booking. Releases Trantor holds, fires booking.cancelled. |
| POST / GET / DELETE | /seldon/v1/time-schemes | Manage the grid-backing time scheme templates and instances. |
Example: a tee-time Offer
An Offer with a fixed 4-hour duration that requires one tee-time slot and optionally one cart:
POST /seldon/v1/offers
Content-Type: application/json
Authorization: Bearer <token>
Idempotency-Key: a3f7b1c2-…
{
"name": "18-hole round",
"kind": "appointment",
"durationShape": { "mode": "fixed", "fixedSec": 14400 },
"partyCapacity": { "min": 1, "max": 4, "default": 4 },
"requirements": [
{ "resourceTypeId": "rt_teetimeSlot", "count": 1 },
{ "resourceTypeId": "rt_cart", "count": 1, "optional": true }
],
"radiantAssetId": "asset_01J8K2…",
"cancelDeadlineSec": 86400,
"modifyRevalidates": true,
"isActive": true
}
A Booking against a grid Availability projection:
POST /seldon/v1/bookings
Content-Type: application/json
Authorization: Bearer <token>
Idempotency-Key: 9c2e4d1a-…
{
"offerId": "offer_01J9TQ…",
"availabilityId": "avlb_01J9TR…",
"anchor": {
"slotInstanceId": "si_01JAZB…",
"dimensionValues": { "side": "front" }
},
"party": {
"count": 4,
"members": [
{ "terminusPersonId": "per_01JAB1…", "role": "organizer" }
]
},
"requirements": [
{ "resourceTypeId": "rt_cart", "count": 2 }
]
}
How it fits with the rest
flowchart LR
Client[Client] --> S(Seldon Booking)
R[Radiant] -. policy YAML .-> S
T[Trantor] -. hold resources .-> S
Te[Terminus] -. entitlement .-> S
S --> OB[(Audit outbox)]
OB --> D[Daneel]
OB --> Sp[Speaker]
OB --> Pv[Palver]
Sp -. consent gate .-> Te
Seldon is the busiest hub on the platform. Offers carry pointers into Radiant for their pricing and cancellation YAML. Bookings ask Trantor to hold physical resources atomically and release them on cancel or no-show. Bookings reference Terminus for the booker's identity and pass through Terminus entitlement checks for tier-gated access rules. The lifecycle emission pipeline feeds Daneel, which in turn calls Speaker for outbound confirmations and reminders — with Speaker enforcing the Terminus consent gate before any dispatch. Mallow records the financial side of completed Bookings.
None of those references are foreign keys at the database level. The cross-primitive contract is opaque id in, validated reference confirmed, structured event out.