Skip to content

Lifecycle & resolution

ABP has two orthogonal axes. The commercial FSM tracks the Booking; the resolution axis tracks which seat, per Allocation. They move independently.

Every transition is validated against a matrix, audited in an append-only log, and fires side-effects as named handlers (via NATS, not DB triggers). Transitions are always 1-to-1 (no bulk).

Hold Confirm Settle Fulfill Redeem
init ────────▶ HELD ──────────▶ CONFIRMED ──────▶ PAID ──────▶ FULFILLED ──────▶ USED
│ TTL expires │ Release │ Release │ Refund
▼ ▼ ▼ ▼
EXPIRED RELEASED RELEASED REFUNDED
└──────────── Unit returns to the Bucket (available) ───────────┘
StateMeaningUnit
HELDallocated, TTL runningleaves the Bucket
CONFIRMEDbuyer committed (PII attached)held
PAIDpayment capturedheld
FULFILLEDartifact issued (idempotent)sold
USEDcheck-in via Redeem; validated against physical capacitysold
REFUNDEDreal post-sale refundback to the Bucket
EXPIREDTTL elapsed before fulfillback to the Bucket
RELEASEDexplicit release pre-fulfillback to the Bucket

Settle is separate from Confirm: payment ≠ commitment (an invoice confirms without paying). USED is reached by Redeem — Access triggers it, but the Registry validates against physical capacity and may deny with BUMPED (overbook only).

A second independent axis per Allocation: the resolution of the Unit. Same principle as Tickets’ physical × commercial split, here applied to which seat.

COMMERCIAL: HELD → CONFIRMED → PAID → FULFILLED → USED
RESOLUTION: ANONYMOUS (slot guaranteed, Unit TBD) ──Assign/AutoAssign──▶ CONCRETE (Unit X)
  • A quantitative Hold (N seats of a QUALITATIVE Bucket) guarantees the count (no oversell, headroom reserved). Whether the response is ANONYMOUS (resolve later) or already CONCRETE (eager best-available) is the implementation’s choice — the protocol describes the states, not the timing. An explicit Hold is born CONCRETE. In a QUANTITATIVE Bucket there is no Unit → resolution is N/A.
  • Assign (Channel chooses) / AutoAssign (Registry chooses) concretize. The concretization atomically pulls the Unit out of the still-available pool.
  • The order between the two axes is not the protocol’s. You can Fulfill with allocations still ANONYMOUS (e.g. a self-service totem issues a “seat TBD” voucher). If a Catalog requires CONCRETE before some state, that is the implementer’s policy.
  • The linear status a consumer sees (RESERVED → ALLOCATING → DONE) is a derived projection of this axis — not a first-class FSM state.
OpEffect
ReseatAssign with overwrite=true over a CONCRETE allocation — assigns the new Unit, releases the old one, atomically. Without overwrite: ALREADY_ASSIGNED.
UnassignCONCRETE → ANONYMOUS: drops the Unit but keeps the slot (the count). Over an ANONYMOUS allocation: NOT_RESOLVED.
Swapatomic exchange of Units between two CONCRETE allocations (possibly across Bookings → between holders). A dedicated RPC because Unassign+Assign would leave a “loose seat” window.