/** * The booking engine's declared operation surface (#707/#738/#865). * * ## Why this file exists now * * It used to not. `index.ts` carried an `OPERATIONS` map of handlers and a note * explaining that the map was "the only description of this engine's operation * surface", which is why the lifecycle had to live at the bottom of the same * file. That note is deleted along with the arrangement it described: a * DECLARATION imports only `entities.ts` and `schemas.ts`, so nothing here * reaches back into the implementation and there is no cycle to dodge. * * The immediate reason is #865: `entityCheckConformanceSuite` derives its * behavioural pair from `permission` and `input`, and seven of this engine's * checks narrow to a reservation. Undeclared, they were not merely untested but * undeclarable — `ctx.check(PERM.confirm, reservationRef(id))` and * `ctx.check(PERM.confirm)` are the same to a compiler, and the second lets * anyone holding `booking:confirm` anywhere in the scope confirm anyone's * reservation. * * What is deliberately NOT here is `http`. An engine is entity-agnostic and owns * no URL shape: a padel club calls a reservation a court booking and a clinic * calls it an appointment, and both are right. The path is the composing * vertical's decision, declared with `defineEngineRoutes` against these names. * * ## The node checks are a fact, not an omission * * Four operations check at the NODE while taking a `reservationId`, and that * asymmetry is deliberate enough to be worth naming: `booking/expire` sweeps a * lapsed hold and `booking/start` / `booking/complete` / `booking/no-show` are * service-desk verbs. Each is staff work over whatever reservation is in front * of them, not a participant reaching their own booking — so the authority is * scope-wide and a narrowed grant would not widen to cover it. `booking/get` * and the five participant-facing mutations narrow, because there a grant on * one reservation is the whole of someone's access. */ import { z } from '@substrat-run/contracts'; /** The keys these operations check. Mirrors `PERM` in index.ts. */ export declare const BOOKING_PERMISSIONS: readonly ['booking:create', 'booking:read', 'booking:hold', 'booking:confirm', 'booking:cancel', 'booking:move', 'booking:complete', 'booking:manage-resources']; export declare const bookingOperations: { readonly 'booking/create-resource': { readonly summary: "Register a bookable resource"; readonly permission: "booking:manage-resources"; readonly input: z.ZodObject<{ kind: z.ZodString; name: z.ZodString; capacity: z.ZodOptional; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; kind: z.ZodString; name: z.ZodString; capacity: z.ZodNumber; active: z.ZodBoolean; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/set-resource-active': { readonly summary: "Take a resource in or out of service"; readonly permission: "booking:manage-resources"; readonly input: z.ZodObject<{ resourceId: z.ZodString; active: z.ZodBoolean; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; kind: z.ZodString; name: z.ZodString; capacity: z.ZodNumber; active: z.ZodBoolean; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/list-resources': { readonly summary: "Bookable resources, by name"; readonly permission: "booking:read"; readonly input: z.ZodObject<{ kind: z.ZodOptional; }, z.core.$strip>; readonly inputOptional: true; readonly output: z.ZodObject<{ id: z.ZodString; kind: z.ZodString; name: z.ZodString; capacity: z.ZodNumber; active: z.ZodBoolean; createdAt: z.ZodString; }, z.core.$strip>; readonly paged: { readonly over: { readonly entity: "resource"; readonly sortable: readonly ["name", "kind", "created_at"]; readonly filterable: readonly ["kind", "active"]; }; }; }; readonly 'booking/hold': { readonly summary: "Place a tentative hold on a slot"; readonly permission: "booking:hold"; readonly input: z.ZodObject<{ resourceId: z.ZodString; startsAt: z.ZodPipe>; endsAt: z.ZodPipe>; expiresAt: z.ZodPipe>; quantity: z.ZodOptional; fillTarget: z.ZodOptional; note: z.ZodOptional; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/confirm': { readonly summary: "Confirm a held reservation"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/expire': { readonly summary: "Sweep a hold whose deadline has passed"; readonly permission: "booking:confirm"; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/join': { readonly summary: "Join a reservation that is on offer"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; partyRef: z.core.$ZodBranded; share: z.ZodOptional; currency: z.core.$ZodBranded; }, z.core.$strip>>; }, z.core.$strip>; readonly output: z.ZodObject<{ participant: z.ZodObject<{ id: z.ZodString; partyRef: z.ZodString; share: z.ZodNullable; currency: z.core.$ZodBranded; }, z.core.$strip>>; joinedAt: z.ZodString; leftAt: z.ZodNullable; }, z.core.$strip>; reservation: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }, z.core.$strip>; }; readonly 'booking/leave': { readonly summary: "Leave a reservation previously joined"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; participantId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/cancel': { readonly summary: "Cancel a reservation"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; reason: z.ZodOptional; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/move': { readonly summary: "Reschedule a reservation to another slot or resource"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; resourceId: z.ZodOptional; startsAt: z.ZodOptional>>; endsAt: z.ZodOptional>>; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; /** * A partial field-bag over the reservation's own columns — read-modify-write, * so the model requires the guard (#129). It was undeclared until #961 only * because the input carried `now`, which is not a column and hid the shape * from the check. The client opts in with `If-Match`; a caller sending none * is served exactly as before. The version moves because `moveReservation` * emits `booking.moved` about the reservation by hand, as every handler in * this engine emits. */ readonly concurrency: { readonly over: "reservation"; readonly idFrom: "reservationId"; }; }; readonly 'booking/open': { readonly summary: "Put a reservation on offer, or take it off"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; fillTarget: z.ZodNullable; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/start': { readonly summary: "Start service on a reservation"; readonly permission: "booking:complete"; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/complete': { readonly summary: "Complete a reservation"; readonly permission: "booking:complete"; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/no-show': { readonly summary: "Mark a reservation as a no-show"; readonly permission: "booking:complete"; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; }; readonly 'booking/get': { readonly summary: "One reservation with its participants"; readonly permission: { readonly key: "booking:cancel" | "booking:complete" | "booking:confirm" | "booking:create" | "booking:hold" | "booking:manage-resources" | "booking:move" | "booking:read"; readonly entity: 'reservation'; readonly idFrom: 'reservationId'; }; readonly input: z.ZodObject<{ reservationId: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ reservation: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; participants: z.ZodArray; currency: z.core.$ZodBranded; }, z.core.$strip>>; joinedAt: z.ZodString; leftAt: z.ZodNullable; }, z.core.$strip>>; }, z.core.$strip>; }; readonly 'booking/list': { readonly summary: "Reservations overlapping a window"; readonly permission: "booking:read"; readonly input: z.ZodObject<{ resourceId: z.ZodOptional; from: z.ZodOptional; to: z.ZodOptional; }, z.core.$strip>; readonly inputOptional: true; readonly output: z.ZodObject<{ id: z.ZodString; resourceId: z.ZodString; startsAt: z.ZodString; endsAt: z.ZodString; state: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; effectiveState: z.ZodEnum<{ cancelled: "cancelled"; completed: "completed"; confirmed: "confirmed"; expired: "expired"; held: "held"; in_service: "in_service"; no_show: "no_show"; }>; quantity: z.ZodNumber; expiresAt: z.ZodNullable; fillTarget: z.ZodNullable; note: z.ZodNullable; createdBy: z.ZodString; createdAt: z.ZodString; }, z.core.$strip>; /** * Handler-composed, and the cursor is `id` rather than `startsAt`. * * The window is an OVERLAP test — `starts_at < to AND ends_at > from` — and * the kernel's filter vocabulary is equality only, deliberately (a range * vocabulary is where a filter becomes a query language). So this read owns * its own `WHERE`, and `paged.sortKey` names the field the cursor walks. * * It has to be a UNIQUE field. This list shipped `ORDER BY starts_at, id`, * and a keyset cursor on `starts_at` skips and repeats rows wherever two * reservations share a start — which on a court schedule is every hour. Ids * are ULIDs, so `id` is unique and still roughly chronological by creation. * A caller rendering a calendar sorts the page by `startsAt` itself. */ readonly paged: { readonly sortKey: "id"; }; }; readonly 'booking/availability': { readonly summary: "Free intervals on a resource within a window"; readonly permission: "booking:read"; readonly input: z.ZodObject<{ resourceId: z.ZodString; from: z.ZodString; to: z.ZodString; }, z.core.$strip>; readonly output: z.ZodObject<{ startsAt: z.ZodString; endsAt: z.ZodString; available: z.ZodNumber; }, z.core.$strip>; /** * A computed fold, not a table walk — so handler-composed, like the list * above. The segments this returns are DISJOINT and returned in order, so * `startsAt` is unique among them and is a sound cursor where it would not * be over reservation rows. */ readonly paged: { readonly sortKey: "startsAt"; }; }; }; //# sourceMappingURL=operations.d.ts.map