kaafil-js 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2311 @@
1
+ import { a3 as components, w as KaafilResponse, a4 as operations, t as KaafilPaginator, p as KaafilPagedResponse, R as Resolvable, E as Environment, V as VendorsResource, J as JourneyResource, W as ResponseMeta, a5 as RoomingHeuristicKey, G as Gender } from './paginator-CE9JhsLi.cjs';
2
+ export { A as AgencyAdminRefreshResult, a6 as ApiKeyEnvironmentMismatchError, a7 as BoardStatus, B as BookingStatus, a8 as ChecklistAudience, a9 as ChecklistGate, aa as ChecklistItemStatus, ab as ChecklistPhase, ac as ChecklistPullTemplateMode, C as ClaimStatusIngest, ad as CollectionMode, ae as Currency, a as CursorPageMeta, b as ERROR_CODE_TABLE, c as ErrorCodeTableEntry, d as EventType, af as ExpenseCategory, ag as ExpensePaymentMode, F as FetchPage, ah as FileContentType, ai as FilePurpose, aj as FloatDirection, ak as ItineraryItemStatusUpdate, al as ItineraryItemType, am as JourneyTriggersResource, K as KaafilAbortError, e as KaafilApiError, f as KaafilCapabilityUnavailableError, an as KaafilConfigError, g as KaafilEntitlementError, h as KaafilError, i as KaafilErrorCode, j as KaafilErrorKind, k as KaafilInvalidRequestError, l as KaafilLockedError, m as KaafilNetworkError, n as KaafilNotFoundError, o as KaafilNotImplementedError, q as KaafilPaginationError, r as KaafilPaginationExhaustedError, s as KaafilPaginationInFlightError, u as KaafilRateLimitedError, v as KaafilReadOnlyRoleError, x as KaafilShareTokenExpiredError, y as KaafilShareTokenRevokedError, z as KaafilTimeoutError, D as KaafilTransportError, H as KaafilUnauthenticatedError, I as KaafilValidationError, L as KaafilVersionConflictError, ao as ListJourneyTriggersOptions, ap as ListTripVendorsOptions, M as ManagerRefreshResult, N as ManagerRole, O as ManifestMode, P as OutboxClass, Q as PagedResponseMeta, S as PaginatorPage, T as PartyKind, aq as PatchJourneyTriggerOptions, ar as PickupKind, U as PlanRefusalReason, as as ReadJourneyCapabilitiesOptions, at as ReadJourneyOptions, au as RebuildJourneyOptions, X as Retryability, av as RoomType, aw as RoomingFillOrder, ax as RoomingGenderPolicy, ay as RunJourneyStepOptions, az as SeatingFillOrder, aA as SeatingGenderAdjacency, aB as SeatingHeuristicKey, Y as TriggerAnchor, Z as TripMode, _ as TripStatus, $ as UnsatisfiableSchemeError, aC as VehicleLayout, aD as VehicleType, a0 as WaitUntilJourneyReadyOptions, a1 as isKaafilError, a2 as isRetryable } from './paginator-CE9JhsLi.cjs';
3
+
4
+ /**
5
+ * `auth` — minting a manager session or an agency-admin session.
6
+ * `architecture/08-sdk.md §3.2, §4`.
7
+ *
8
+ * Rotation is deliberately absent as a public method for either kind:
9
+ * `../auth/credentials.ts` calls each kind's own refresh endpoint itself,
10
+ * pre-emptively and reactively, as part of resolving every request's
11
+ * credential — a consumer never calls it directly (this wave's governing
12
+ * principle: the SDK does the refresh, the consumer only ever hands the SDK
13
+ * the tokens it was given).
14
+ *
15
+ * `08-sdk.md §4`'s table also lists a `revoke` method for this group. No such
16
+ * operation exists anywhere in the vendored spec: the `Auth` tag in
17
+ * `openapi/openapi.json` names exactly four operations —
18
+ * `mintManagerTokens`, `refreshManagerTokens`, `mintAgencyAdminTokens`,
19
+ * `refreshAgencyAdminTokens` (verified via `jq '.paths[][] | select(.tags ==
20
+ * ["Auth"]) | .operationId'`) — and the two `refresh*` ones are the
21
+ * resolver's job, not a public method here. Per this lane's brief — "if this
22
+ * list and the spec disagree, the spec wins, and you say so" — no `revoke`
23
+ * method ships. Flagged here rather than silently dropped.
24
+ */
25
+
26
+ type MintManagerTokenRequest = components['schemas']['MintManagerTokensRequest'];
27
+ type ManagerSessionResponse = components['schemas']['ManagerSessionResponse'];
28
+ type MintAgencyAdminTokenRequest = components['schemas']['MintAgencyAdminTokensRequest'];
29
+ type AgencyAdminSessionResponse = components['schemas']['AgencyAdminSessionResponse'];
30
+ interface MintManagerTokenOptions extends MintManagerTokenRequest {
31
+ readonly signal?: AbortSignal;
32
+ }
33
+ interface MintAgencyAdminTokenOptions extends MintAgencyAdminTokenRequest {
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ interface AuthResource {
37
+ /**
38
+ * `POST /api/v1/auth/manager-tokens` — opens a manager session. Server-side
39
+ * only (API key; `mintManagerTokens` accepts only `apiKeyAuth`). The
40
+ * returned `refreshToken` is shown exactly once, here and at every
41
+ * subsequent rotation — hand `accessToken`, `refreshToken`, and
42
+ * `expiresAt` straight to a `managerSession`-credentialed client
43
+ * (`../auth/credentials.ts`'s `ManagerSessionCredentialConfig`, or the
44
+ * browser entry's `session.open(...)`).
45
+ *
46
+ * Not idempotent — no `idempotencyKey` field on this options type — by the
47
+ * operation's own design (`refreshManagerTokens`'s response carries a
48
+ * plaintext refresh token; replaying it from an idempotency cache would
49
+ * mean persisting that secret, which the hash-only refresh-token storage
50
+ * rule forbids). A caller that loses this response to a network error
51
+ * simply mints again.
52
+ */
53
+ mintManagerToken(options: MintManagerTokenOptions): Promise<KaafilResponse<ManagerSessionResponse>>;
54
+ /**
55
+ * `POST /api/v1/auth/agency-admin-tokens` — opens an agency-admin session,
56
+ * the agency-admin analogue of `mintManagerToken` above. Server-side only
57
+ * (API key; `mintAgencyAdminTokens` accepts only `apiKeyAuth`) — hand the
58
+ * response's `accessToken`, `refreshToken`, and `expiresAt` straight to an
59
+ * `agencyAdmin`-credentialed client (`../auth/credentials.ts`'s
60
+ * `AgencyAdminCredentialConfig`, or the browser entry's `admin.open(...)`).
61
+ *
62
+ * Not idempotent, for the same reason as `mintManagerToken`: the response
63
+ * carries a plaintext refresh token, and the hash-only refresh-token
64
+ * storage rule forbids persisting one in an idempotency cache to replay.
65
+ */
66
+ mintAgencyAdminToken(options: MintAgencyAdminTokenOptions): Promise<KaafilResponse<AgencyAdminSessionResponse>>;
67
+ }
68
+
69
+ /**
70
+ * `checklists` — sections, items, toggle, and the agency's reusable template
71
+ * library. `architecture/08-sdk.md §4`. `checklists` FRD §3, RULES R1–R14,
72
+ * `D-029` (pull-template copy semantics), `modules/checklists/checklists
73
+ * .routes.ts`'s own header (the source of every auth/lock line below).
74
+ *
75
+ * `read` and `templates.list` accept a manager session OR an API key; every
76
+ * WRITE (`items.add/patch/remove/toggle`, `templates.pull`) is
77
+ * `managerAuth`-only — admin template CONFIG (`POST/PATCH` against the
78
+ * library itself) does not exist yet (`checklists.routes.ts`'s "ADMIN
79
+ * TEMPLATE CONFIG IS DEFERRED" note), so there is no write here an API key
80
+ * could ever reach even in principle. Every write also declares
81
+ * `closeoutLock: 'ref'` engine-side — a write racing trip close-out answers
82
+ * `423 LOCKED` (`KaafilLockedError`), terminal, never retried.
83
+ *
84
+ * ── FOUR QUIRKS A CONSUMER WILL OTHERWISE GET WRONG ─────────────────────────
85
+ *
86
+ * 1. **`toggle`'s concurrency guard is on the item's STATUS, not its
87
+ * `version`.** Every OTHER version-guarded write in this API takes an
88
+ * `If-Match`/`version` and answers `409 CONFLICT_VERSION` with
89
+ * `details.currentVersion` on staleness. `toggle` takes `expectedStatus`
90
+ * in the BODY instead, and a stale value still answers
91
+ * `409 CONFLICT_VERSION` — same code, `KaafilVersionConflictError` still
92
+ * catches it — but the payload carries `details.currentStatus`, never
93
+ * `details.currentVersion`. Reading `.currentVersion` off the caught
94
+ * error here is silently `undefined`; read `error.details?.currentStatus`
95
+ * (typed `ChecklistItemStatus`) instead. This file does not add a second
96
+ * getter to `KaafilVersionConflictError` for it — `../http/errors.ts`'s
97
+ * own header states the bar for a code-specific getter, and one class per
98
+ * code that merely carries an unusual `details` shape is exactly what
99
+ * that bar exists to avoid.
100
+ * 2. **A `COMPLETE` item cannot be deleted.** `items.remove` on a `COMPLETE`
101
+ * item answers `422 BUSINESS_RULE_VIOLATION` with
102
+ * `details.rule: 'item_complete_delete_blocked'` — un-toggle it first.
103
+ * Not a module-local code: the FRD only describes the refusal, never
104
+ * names it as an identity a client branches on, so it never got minted
105
+ * one (`checklists.constants.ts`'s own line on the point).
106
+ * 3. **`templates.pull` COPIES (`D-029`).** Both `append` and `replace`
107
+ * write TRIP-OWNED rows; neither creates a live link. A template edited
108
+ * AFTER a pull never reaches the trip's copy, and an edit to the trip's
109
+ * copy never reaches the template. `sourceSectionId` on the pulled
110
+ * section is the only trace of where it came from, stamped once, on
111
+ * first pull only — a second pull of the same template onto a section
112
+ * that already exists never rewrites it.
113
+ * 4. **Editing an item's `phase` RE-DERIVES its `gate`, unless `gate` is
114
+ * sent in the SAME request.** `phase` is a hint only — `ChecklistItem`
115
+ * has no `phase` column (phase is the SECTION's) — so `items.patch` never
116
+ * echoes it back on the response, and an explicit `gate` in the same call
117
+ * always wins over the fixed phase→gate map.
118
+ *
119
+ * Beyond those four: the aggregate's `sections[]` is always the FULL live
120
+ * set (a trip's sections are a handful); `items[]` is the ONLY delta axis —
121
+ * `?since=` narrows it, with a deletion arriving as the same tombstone shape
122
+ * `itinerary`/`pickups` already use. `progress` and `hasOpenMandatoryByPhase`
123
+ * are always computed over the full live set, independent of `?since=`. The
124
+ * four reserved sections (`medical`/`documents`/`logistics`/`handover`) are
125
+ * seeded at TRIP-INGEST, inside the upsert transaction — never by this
126
+ * read — so a fresh trip's `read()` already carries them on its very first
127
+ * call; there is no seed-on-read call to make or race against.
128
+ *
129
+ * ── THE SIX `console`-PLANE READS ARE NOT IN THIS PACKAGE ───────────────────
130
+ *
131
+ * `GET /api/v1/console/trips/{ref}/checklist` (`readConsoleChecklist`) and
132
+ * its five siblings (`listConsoleTrips`, `…/itinerary`, `…/rooming`,
133
+ * `…/seating`, `…/pickups`) exist in the vendored spec and are excluded from
134
+ * this SDK on purpose — the same decision `../auth/credentials.ts` and
135
+ * `webhooks.ts` already state for `consoleAuth` generally. They authenticate
136
+ * with a console session cookie (`consoleAuth`), which is the partner
137
+ * console's OWN administrative UI's credential, never something a CRM
138
+ * integrating this package holds — a partner API key satisfies none of
139
+ * `readConsoleChecklist`'s `security` alternatives (there is only one:
140
+ * `consoleAuth`), so a method wrapping it would be uncallable by every
141
+ * consumer of this package. `scripts/gen-meta.mjs`'s
142
+ * `nonIntegrationRequestSchemaNames` already enforces the analogous exclusion
143
+ * for request-body enums reachable only from a console/platform path; this
144
+ * resource file is the read-side statement of the identical decision. If a
145
+ * console read ever gains a second, API-key-satisfiable security
146
+ * alternative, that is a spec change this SDK would need to notice and
147
+ * decide about again — not something to pre-empt here.
148
+ */
149
+
150
+ type ChecklistAggregateResponse = components['schemas']['ChecklistAggregateResponse'];
151
+ type ChecklistItemResponse = components['schemas']['ChecklistItemResponse'];
152
+ type DeleteItemResponse = components['schemas']['DeleteItemResponse'];
153
+ type ToggleResponse = components['schemas']['ToggleResponse'];
154
+ type TemplatesListResponse = components['schemas']['TemplatesListResponse'];
155
+ type AddChecklistItemRequest = components['schemas']['AddChecklistItemRequest'];
156
+ type PatchChecklistItemRequest = components['schemas']['PatchChecklistItemRequest'];
157
+ type ToggleChecklistItemRequest = components['schemas']['ToggleChecklistItemRequest'];
158
+ type PullChecklistTemplateRequest = components['schemas']['PullChecklistTemplateRequest'];
159
+ /**
160
+ * One row of `read`'s `items[]`: a live item, or the tombstone for one
161
+ * deleted since the cursor. Aliased off the response type, never restated —
162
+ * `itinerary.ts#ItineraryItemDeltaRow`'s own reasoning applies verbatim.
163
+ */
164
+ type ChecklistItemDeltaRow = ChecklistAggregateResponse['items'][number];
165
+ interface ReadChecklistOptions {
166
+ readonly tripRef: string;
167
+ /**
168
+ * The delta cursor: a previous response's own `meta.serverTime`, and
169
+ * nothing else. Omit for the full aggregate — `sections[]` and the two
170
+ * progress figures are always full regardless; only `items[]` narrows.
171
+ * Prefer `createDeltaCursor` (`../delta/cursor.ts`) over passing this by
172
+ * hand, same as every other delta read in this SDK.
173
+ */
174
+ readonly since?: string;
175
+ readonly signal?: AbortSignal;
176
+ }
177
+ interface AddChecklistItemOptions extends AddChecklistItemRequest {
178
+ readonly tripRef: string;
179
+ readonly idempotencyKey?: string;
180
+ readonly signal?: AbortSignal;
181
+ }
182
+ interface PatchChecklistItemOptions extends PatchChecklistItemRequest {
183
+ readonly tripRef: string;
184
+ readonly itemId: string;
185
+ /**
186
+ * Required, not optional — `itinerary.ts#PatchItineraryItemOptions
187
+ * .version`'s own argument for why a missing `If-Match` and a stale one
188
+ * answering the identical `409 CONFLICT_VERSION` means this SDK should
189
+ * make forgetting it impossible rather than merely discouraged. Pass the
190
+ * `version` from the item as last read; the SDK builds the `If-Match`
191
+ * header itself. NOT the same guard as `toggle`'s `expectedStatus` — see
192
+ * this file's header, quirk 1.
193
+ */
194
+ readonly version: number;
195
+ readonly signal?: AbortSignal;
196
+ }
197
+ interface DeleteChecklistItemOptions {
198
+ readonly tripRef: string;
199
+ readonly itemId: string;
200
+ /** See `PatchChecklistItemOptions.version` — same contract. Refused with
201
+ * `422 BUSINESS_RULE_VIOLATION` while the item is `COMPLETE` — this file's
202
+ * header, quirk 2. */
203
+ readonly version: number;
204
+ readonly signal?: AbortSignal;
205
+ }
206
+ interface ToggleChecklistItemOptions extends ToggleChecklistItemRequest {
207
+ readonly tripRef: string;
208
+ readonly itemId: string;
209
+ readonly idempotencyKey?: string;
210
+ readonly signal?: AbortSignal;
211
+ }
212
+ interface ChecklistItemsResource {
213
+ /**
214
+ * `POST …/checklist/items` — auto-creates the section when `sectionKey`
215
+ * is new FOR THIS TRIP (taking `sectionTitle`/`audience` as the new
216
+ * section's display; both are ignored on an already-existing section). An
217
+ * omitted `key` is slugged from `title` and de-duplicated within the
218
+ * section; an explicit colliding `key` is `422 VALIDATION_ERROR`, never a
219
+ * silent rename. `gate` is derived from the section's `phase` — the
220
+ * create body carries no `gate` of its own.
221
+ */
222
+ add(options: AddChecklistItemOptions): Promise<KaafilResponse<ChecklistItemResponse>>;
223
+ /** See this file's header, quirk 4 — a `phase` sent here without an
224
+ * explicit `gate` re-derives the item's `gate`. */
225
+ patch(options: PatchChecklistItemOptions): Promise<KaafilResponse<ChecklistItemResponse>>;
226
+ /** See this file's header, quirk 2. */
227
+ remove(options: DeleteChecklistItemOptions): Promise<KaafilResponse<DeleteItemResponse>>;
228
+ /**
229
+ * `POST …/checklist/items/{itemId}/toggle` — flips OPEN↔COMPLETE. See
230
+ * this file's header, quirk 1, before catching `KaafilVersionConflictError`
231
+ * off this call. Returns the updated item PLUS its section's and the
232
+ * trip's own progress, so a caller patches its local state without a
233
+ * refetch. `checklist.item_completed` fires on every `OPEN → COMPLETE`
234
+ * transition; `checklist.completed` fires additionally when it was the
235
+ * LAST mandatory item trip-wide. `COMPLETE → OPEN` emits nothing.
236
+ */
237
+ toggle(options: ToggleChecklistItemOptions): Promise<KaafilResponse<ToggleResponse>>;
238
+ }
239
+ interface ListChecklistTemplatesOptions {
240
+ readonly tripRef: string;
241
+ /** Default `"en"`, with `"en"` fallback on no exact match. */
242
+ readonly locale?: string;
243
+ readonly signal?: AbortSignal;
244
+ }
245
+ interface PullChecklistTemplateOptions extends PullChecklistTemplateRequest {
246
+ readonly tripRef: string;
247
+ readonly idempotencyKey?: string;
248
+ readonly signal?: AbortSignal;
249
+ }
250
+ interface ChecklistTemplatesResource {
251
+ /** `GET …/checklist/templates` — the agency's library (`tripId` is
252
+ * `null`), resolved to `?locale=`. `lastUsedAt` is `null` until this
253
+ * template is pulled onto some trip, or the pull marker has expired. */
254
+ list(options: ListChecklistTemplatesOptions): Promise<KaafilResponse<TemplatesListResponse>>;
255
+ /** See this file's header, quirk 3 — COPY semantics, never a live link.
256
+ * Returns the FULL new aggregate (cold, no `?since=`) for atomic
257
+ * replacement. */
258
+ pull(options: PullChecklistTemplateOptions): Promise<KaafilResponse<ChecklistAggregateResponse>>;
259
+ }
260
+ interface ChecklistsResource {
261
+ /** `GET …/trips/{ref}/checklist` — the full aggregate: sections, items,
262
+ * both progress figures, and the agency's available templates. A PURE
263
+ * READ — it never seeds; the four reserved sections are already present
264
+ * by trip-ingest time (this file's header). */
265
+ read(options: ReadChecklistOptions): Promise<KaafilResponse<ChecklistAggregateResponse>>;
266
+ readonly items: ChecklistItemsResource;
267
+ readonly templates: ChecklistTemplatesResource;
268
+ }
269
+
270
+ /**
271
+ * Coercion for the caller-supplied date-time fields scattered across the
272
+ * ingest surface (`trips.upsert`, `trips.travellers.*`, `trips.managers.*`,
273
+ * `trips.balance.push`, `trips.bulk.push`, `shareTokens.create`). Every one
274
+ * of those fields is, in the generated schema, a bare `string` that the API
275
+ * requires to be a full ISO-8601 datetime WITH an offset — a caller who
276
+ * hand-builds that string is the single most common way to fail this API's
277
+ * validation, and JavaScript already has a `Date` for exactly this job.
278
+ *
279
+ * This module is the one seam every such field routes through. It is not
280
+ * wired into the generated schema types (those stay a faithful mirror of
281
+ * the OpenAPI spec) — it is wired into the hand-written `*Options` types in
282
+ * `resources/trips.ts` and `resources/share-tokens.ts`, which widen the
283
+ * relevant fields from `string` to `DateTimeInput` and normalize back down
284
+ * to the schema's `string` shape before the body reaches `client.execute`.
285
+ */
286
+ /**
287
+ * What a caller may hand a date-time field on the ingest surface.
288
+ *
289
+ * - `Date` is the point of this whole module: build it however you like,
290
+ * the SDK converts it correctly.
291
+ * - `number` is epoch milliseconds — `Date.now()` and friends, without
292
+ * forcing a caller to wrap it in `new Date(...)` first.
293
+ * - `string` is the escape hatch for a caller who already has a correctly
294
+ * formatted ISO datetime (e.g. echoed back from another system) and does
295
+ * not want it touched — see `normalizeDateTimeInput` for why a full
296
+ * datetime string passes through byte-for-byte instead of round-tripping
297
+ * through `Date`.
298
+ */
299
+ type DateTimeInput = Date | number | string;
300
+
301
+ /**
302
+ * `collections` — customer payments recorded against a traveller's balance,
303
+ * who still owes, and the void correction. `architecture/08-sdk.md §4`.
304
+ *
305
+ * `list`/`eligible` accept a manager session OR an API key; `record`/`void`
306
+ * are `managerAuth`-only — collecting cash and correcting a mistake both
307
+ * happen on the ground.
308
+ *
309
+ * `mode ∈ CollectionMode` (`CASH|UPI|CARD|BANK`) is a CUSTOMER payment
310
+ * mode, disjoint from `expenses`' agency-spend `paymentMode` — the two
311
+ * enums never overlap and are never interchangeable even where a value
312
+ * name might coincide.
313
+ *
314
+ * ── THE OVERPAY GUARD IS A HARD REFUSAL ──────────────────────────────────
315
+ *
316
+ * `record()`'s `amountMinor` must not exceed the traveller's DERIVED
317
+ * `outstandingMinor` (computed live from the ingested `Balance`, never
318
+ * stored) — a caller cannot record more than what is actually owed.
319
+ * Exceeding it answers `422 BUSINESS_RULE_VIOLATION` with
320
+ * `details.remainingMinor`, the room actually left. `eligible()` is the
321
+ * companion read: only travellers with `outstandingMinor > 0` appear in
322
+ * it — a traveller with nothing outstanding, or with no `Balance` row at
323
+ * all, is simply absent, though still recordable through `record()` as an
324
+ * advance.
325
+ *
326
+ * `void()` is a correction, never an edit or a hard delete — there is no
327
+ * edit-after-record. `If-Match: <version>` is required; a missing or stale
328
+ * one answers `409 CONFLICT_VERSION` with `details.currentVersion`, never
329
+ * silently overwriting a version the caller has not seen.
330
+ */
331
+
332
+ type CollectionResponse = components['schemas']['CollectionResponse'];
333
+ type CollectionDeltaResponse = components['schemas']['CollectionDeltaResponse'];
334
+ type EligibleRowResponse = components['schemas']['EligibleRowResponse'];
335
+ type RecordCollectionRequest = components['schemas']['RecordCollectionRequest'];
336
+ type VoidCollectionRequest = components['schemas']['VoidCollectionRequest'];
337
+ /**
338
+ * One row of `list`'s result: a live collection with its derived
339
+ * `outstandingMinor`, or the tombstone for one deleted since the cursor.
340
+ * Aliased off the response type rather than restated — see
341
+ * `../delta/cursor.ts`'s `isTombstone`.
342
+ */
343
+ type CollectionDeltaRow = CollectionDeltaResponse;
344
+ interface ListCollectionsOptions {
345
+ readonly tripRef: string;
346
+ /** Narrows to one traveller. Omitted lists every traveller's rows. */
347
+ readonly travellerId?: string;
348
+ /**
349
+ * The delta cursor: a previous response's own `meta.serverTime`, and
350
+ * nothing else. Omit it for the full list. Prefer `createDeltaCursor`
351
+ * (`../delta/cursor.ts`) over passing this by hand — the window is
352
+ * INCLUSIVE and overlapped by five seconds, so a pull is at-least-once
353
+ * and re-delivers rows a caller already holds; that module owns the
354
+ * bookkeeping this endpoint's overlap assumes.
355
+ */
356
+ readonly since?: string;
357
+ readonly signal?: AbortSignal;
358
+ }
359
+ interface ListEligibleCollectionsOptions {
360
+ readonly tripRef: string;
361
+ readonly signal?: AbortSignal;
362
+ }
363
+ interface RecordCollectionOptions extends Omit<RecordCollectionRequest, 'collectedAt'> {
364
+ readonly tripRef: string;
365
+ readonly collectedAt?: DateTimeInput;
366
+ readonly idempotencyKey?: string;
367
+ readonly signal?: AbortSignal;
368
+ }
369
+ interface VoidCollectionOptions extends VoidCollectionRequest {
370
+ readonly tripRef: string;
371
+ readonly collectionId: string;
372
+ /**
373
+ * Required, not optional. A missing `If-Match` and a stale one both
374
+ * answer `409 CONFLICT_VERSION` — identically confusing either way — so
375
+ * this method makes forgetting it impossible instead of merely
376
+ * discouraged. Pass the `version` from a collection just read via
377
+ * `list()` or the row `record()` returned. The SDK builds the `If-Match`
378
+ * header itself; never set it in `headers`.
379
+ */
380
+ readonly version: number;
381
+ readonly signal?: AbortSignal;
382
+ }
383
+ interface CollectionsResource {
384
+ /** `GET /api/v1/trips/{ref}/collections` — recorded collections, each row's
385
+ * `outstandingMinor` derived live on this read. See this file's header for
386
+ * the delta-cursor semantics. */
387
+ list(options: ListCollectionsOptions): Promise<KaafilResponse<readonly CollectionDeltaRow[]>>;
388
+ /** `GET /api/v1/trips/{ref}/collections/eligible` — travellers with a
389
+ * derived `outstandingMinor > 0`; "who has a balance to chase", not "who
390
+ * may be collected from". */
391
+ eligible(options: ListEligibleCollectionsOptions): Promise<KaafilResponse<readonly EligibleRowResponse[]>>;
392
+ /** `POST /api/v1/trips/{ref}/collections` — records a customer payment.
393
+ * See this file's header for the overpay guard. */
394
+ record(options: RecordCollectionOptions): Promise<KaafilResponse<CollectionResponse>>;
395
+ /** `DELETE /api/v1/trips/{ref}/collections/{id}/void` — a correction, not
396
+ * an edit or a hard delete. `DELETE`, not the FRD table's nominal `POST`:
397
+ * a real `If-Match` guard is unsatisfiable on a `POST` under this
398
+ * engine's OpenAPI generator, and a version-guarded soft void is the
399
+ * shape the generator already blesses `DELETE` for — same path, same
400
+ * body, same response either way. */
401
+ void(options: VoidCollectionOptions): Promise<KaafilResponse<CollectionResponse>>;
402
+ }
403
+
404
+ /**
405
+ * `events` — the event feed. `architecture/08-sdk.md §4`: "the delta feed for
406
+ * missed-webhook recovery." `listEvents` is `apiKeyAuth`-only and
407
+ * cursor-paginated (`cursor`+`limit` query params, `meta.page.hasNext`);
408
+ * `list()` returns a `KaafilPaginator` and `listPage()` is the manual
409
+ * single-page escape hatch (`../pagination/paginator.ts`).
410
+ */
411
+
412
+ type EventEnvelopeResponse = components['schemas']['EventEnvelopeResponse'];
413
+ type ListEventsQuery = NonNullable<operations['listEvents']['parameters']['query']>;
414
+ /** The filterable half of `listEvents`'s query params — `cursor` and
415
+ * `limit` are the paginator's job, never the caller's to hold. */
416
+ type EventFilters = Omit<ListEventsQuery, 'cursor' | 'limit'>;
417
+ interface ListEventsOptions {
418
+ readonly filters?: EventFilters;
419
+ /** Page size sent as the `limit` query param on every page this paginator fetches. */
420
+ readonly limit?: number;
421
+ }
422
+ interface ListEventsPageOptions extends EventFilters {
423
+ readonly cursor?: string;
424
+ readonly limit?: number;
425
+ readonly signal?: AbortSignal;
426
+ }
427
+ interface EventsResource {
428
+ /** `GET /api/v1/events` as a `KaafilPaginator` — the SDK holds the cursor;
429
+ * the consumer never sees or stores one. */
430
+ list(options?: ListEventsOptions): KaafilPaginator<EventEnvelopeResponse, EventFilters>;
431
+ /** The manual single-page escape hatch: fetches exactly one page and
432
+ * returns it directly, for a caller who wants to drive paging itself
433
+ * rather than use the paginator `list()` returns. */
434
+ listPage(options?: ListEventsPageOptions): Promise<KaafilPagedResponse<readonly EventEnvelopeResponse[]>>;
435
+ }
436
+
437
+ /**
438
+ * `expenses` — on-trip agency spend, its receipt lane, and the claim
439
+ * lifecycle for a manager's own `PERSONAL` spend. `architecture/08-
440
+ * sdk.md §4`.
441
+ *
442
+ * `list`/`read` accept a manager session OR an API key; `log`,
443
+ * `linkReceipt`, `void` and every `claims.*` method except
444
+ * `claims.ingest` are `managerAuth`-only. `claims.ingest` is the one
445
+ * method on this resource an API key alone may call — see its own doc
446
+ * below.
447
+ *
448
+ * `paymentMode ∈ ExpensePaymentMode` (`FLOAT_CASH|PERSONAL|OTHER`) is
449
+ * IMMUTABLE after `log()` and never accepted on any later call — an agency
450
+ * spend mode, disjoint from `collections`' customer payment `mode`.
451
+ * `category ∈ ExpenseCategory`.
452
+ *
453
+ * ── FLOAT_CASH AUTO-COUPLES TO THE FLOAT LEDGER ──────────────────────────
454
+ *
455
+ * `log()` with `paymentMode: 'FLOAT_CASH'` creates, in ONE transaction, the
456
+ * expense, a paired `FloatMovement.EXPENSE(OUT)`, and sets the expense's
457
+ * `floatMovementId` — the negative-float guard (`float.ts`'s header)
458
+ * applies unconditionally, so an expense that would overdraw the manager's
459
+ * float is refused rather than logged. `PERSONAL`/`OTHER` touch no float
460
+ * at all. `log({ submitClaim: true, ... })` (PERSONAL only) files the
461
+ * claim in the SAME transaction — equivalent to a `log()` immediately
462
+ * followed by `claims.submit()`, but atomic.
463
+ *
464
+ * ── THE CLAIM STATE MACHINE, AND WHO MAY MOVE IT ─────────────────────────
465
+ *
466
+ * `ExpenseResponse.claimStatus` is a five-member vocabulary —
467
+ * `SUBMITTED|WITHDRAWN|APPROVED|PAID|REJECTED` — split down the middle by
468
+ * WHO can set each value. `claims.submit()`/`claims.withdraw()` are the
469
+ * only two a Kaafil caller ever drives, and neither takes a `status` field
470
+ * at all: submitting always produces `SUBMITTED`, withdrawing always
471
+ * produces `WITHDRAWN`, so there is no enum a manager writes here.
472
+ * `APPROVED|PAID|REJECTED` arrive ONLY through `claims.ingest()` — the
473
+ * CRM's own decision, mirrored, never decided by Kaafil. That three-member
474
+ * subset is `ClaimStatusIngest` (`../generated/enums.ts`), deliberately not
475
+ * named `ClaimStatus` — see `scripts/gen-meta.mjs`'s `REQUEST_ENUM_NAMES`
476
+ * comment on that entry for why a bare domain name here would be the same
477
+ * trap as `ItineraryItemStatusUpdate`'s.
478
+ *
479
+ * `claims.submit()` only succeeds on a row the caller themselves logged —
480
+ * a different manager's row answers `404 RESOURCE_NOT_FOUND`, never `403`
481
+ * — with `paymentMode: 'PERSONAL'` and `claimStatus` currently `null` or
482
+ * `WITHDRAWN`. `claims.withdraw()` requires `If-Match` and only succeeds
483
+ * while `claimStatus = SUBMITTED` AND no CRM decision has landed yet — a
484
+ * decision arriving mid-flight wins the race cleanly, the withdraw loses.
485
+ * `claims.ingest()` accepts ONLY the three CRM-only values; `SUBMITTED` or
486
+ * `WITHDRAWN` on this route is `422 VALIDATION_ERROR` — the ingest route is
487
+ * one-directional by contract, not just by convention.
488
+ *
489
+ * `void()` reverses the paired float movement (if any) and cascades an
490
+ * open claim; `linkReceipt()` back-fills `receiptFileKey` for a client that
491
+ * logged an expense offline before an upload could confirm — it takes an
492
+ * `Idempotency-Key`, never an `If-Match`: it addresses no client-held
493
+ * version, only a key the log call could not carry offline. Both refuse on
494
+ * an already-voided row or one the CRM has already decided.
495
+ */
496
+
497
+ type ExpenseResponse = components['schemas']['ExpenseResponse'];
498
+ type ExpenseListResponse = components['schemas']['ExpenseListResponse'];
499
+ type ClaimStatusIngestResponse = components['schemas']['ClaimStatusIngestResponse'];
500
+ type LogExpenseRequest = components['schemas']['LogExpenseRequest'];
501
+ type LinkExpenseReceiptRequest = components['schemas']['LinkExpenseReceiptRequest'];
502
+ type VoidExpenseRequest = components['schemas']['VoidExpenseRequest'];
503
+ type ClaimStatusIngestRequest = components['schemas']['ClaimStatusIngestRequest'];
504
+ type ListExpensesQuery = NonNullable<operations['listExpenses']['parameters']['query']>;
505
+ /** The filterable half of `list`'s query params — `since` is the delta
506
+ * cursor and is passed as its own option, never bundled in here. */
507
+ type ExpenseFilters = Omit<ListExpensesQuery, 'since'>;
508
+ interface ListExpensesOptions extends ExpenseFilters {
509
+ readonly tripRef: string;
510
+ /**
511
+ * The delta cursor: a previous response's own `meta.serverTime`, and
512
+ * nothing else. Omit it for the full list. Prefer `createDeltaCursor`
513
+ * (`../delta/cursor.ts`) over passing this by hand — the window is
514
+ * INCLUSIVE and overlapped by five seconds, so a pull is at-least-once.
515
+ */
516
+ readonly since?: string;
517
+ readonly signal?: AbortSignal;
518
+ }
519
+ interface ReadExpenseOptions {
520
+ readonly tripRef: string;
521
+ readonly expenseId: string;
522
+ readonly signal?: AbortSignal;
523
+ }
524
+ interface LogExpenseOptions extends Omit<LogExpenseRequest, 'spentAt'> {
525
+ readonly tripRef: string;
526
+ readonly spentAt?: DateTimeInput;
527
+ readonly idempotencyKey?: string;
528
+ readonly signal?: AbortSignal;
529
+ }
530
+ interface LinkExpenseReceiptOptions extends LinkExpenseReceiptRequest {
531
+ readonly tripRef: string;
532
+ readonly expenseId: string;
533
+ /** Required — this route addresses no client-held version; see this
534
+ * file's header for why `If-Match` never applies here. */
535
+ readonly idempotencyKey: string;
536
+ readonly signal?: AbortSignal;
537
+ }
538
+ interface VoidExpenseOptions extends VoidExpenseRequest {
539
+ readonly tripRef: string;
540
+ readonly expenseId: string;
541
+ /**
542
+ * Required, not optional. A missing `If-Match` and a stale one both
543
+ * answer `409 CONFLICT_VERSION` — identically confusing either way — so
544
+ * this method makes forgetting it impossible instead of merely
545
+ * discouraged. Pass the `version` from an expense just read via `list()`
546
+ * or `read()`. The SDK builds the `If-Match` header itself; never set it
547
+ * in `headers`.
548
+ */
549
+ readonly version: number;
550
+ readonly signal?: AbortSignal;
551
+ }
552
+ interface SubmitExpenseClaimOptions {
553
+ readonly tripRef: string;
554
+ readonly expenseId: string;
555
+ readonly idempotencyKey?: string;
556
+ readonly signal?: AbortSignal;
557
+ }
558
+ interface WithdrawExpenseClaimOptions {
559
+ readonly tripRef: string;
560
+ readonly expenseId: string;
561
+ /** Required — see `VoidExpenseOptions.version` above for why this SDK
562
+ * never makes `If-Match` optional on a versioned write. */
563
+ readonly version: number;
564
+ readonly signal?: AbortSignal;
565
+ }
566
+ interface IngestClaimStatusOptions extends Omit<ClaimStatusIngestRequest, 'decisionAt'> {
567
+ readonly tripRef: string;
568
+ readonly expenseId: string;
569
+ readonly decisionAt: DateTimeInput;
570
+ readonly idempotencyKey?: string;
571
+ readonly signal?: AbortSignal;
572
+ }
573
+ interface ExpenseClaimsResource {
574
+ /** `POST /api/v1/trips/{ref}/expenses/{id}/claim` — files a claim on the
575
+ * caller's own `PERSONAL` expense. No body: submitting always produces
576
+ * `SUBMITTED`, never a status the caller names. */
577
+ submit(options: SubmitExpenseClaimOptions): Promise<KaafilResponse<ExpenseResponse>>;
578
+ /** `DELETE /api/v1/trips/{ref}/expenses/{id}/claim/withdraw` — only while
579
+ * still `SUBMITTED` with no CRM decision landed. `DELETE`, not the FRD
580
+ * table's nominal `POST` — the same forced deviation `expenses.void()`
581
+ * and `collections.void()` make, for the identical `If-Match`-on-`POST`
582
+ * reason; nothing else about the contract changes. */
583
+ withdraw(options: WithdrawExpenseClaimOptions): Promise<KaafilResponse<ExpenseResponse>>;
584
+ /** `POST /api/v1/trips/{ref}/expenses/{id}/claim-status` — partner API
585
+ * key ONLY; a manager bearer answers `401 UNAUTHENTICATED`, never `403`.
586
+ * The CRM's own decision, mirrored — Kaafil never approves, rejects or
587
+ * pays. `status` accepts only `ClaimStatusIngest`'s three values;
588
+ * `SUBMITTED`/`WITHDRAWN` here is `422 VALIDATION_ERROR`. The response's
589
+ * `verdict` (`applied`/`ignored_stale`) tells the CRM whether this
590
+ * decision actually landed or arrived after a newer one already had. */
591
+ ingest(options: IngestClaimStatusOptions): Promise<KaafilResponse<ClaimStatusIngestResponse>>;
592
+ }
593
+ interface ExpensesResource {
594
+ /** `GET /api/v1/trips/{ref}/expenses` — a trip's expenses, with category
595
+ * totals and a spend summary over the filtered set (live rows only). See
596
+ * this file's header for the delta-cursor semantics. */
597
+ list(options: ListExpensesOptions): Promise<KaafilResponse<ExpenseListResponse>>;
598
+ /** `GET /api/v1/trips/{ref}/expenses/{id}` — one expense, including its
599
+ * claim view. `id` is a plain Kaafil id, never dual-resolved. */
600
+ read(options: ReadExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
601
+ /** `POST /api/v1/trips/{ref}/expenses` — logs an on-trip spend. See this
602
+ * file's header for `FLOAT_CASH` auto-coupling and `submitClaim`. */
603
+ log(options: LogExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
604
+ /** `PATCH /api/v1/trips/{ref}/expenses/{id}/receipt` — back-fills
605
+ * `receiptFileKey` against a confirmed (`ready`) upload from `files.*`. */
606
+ linkReceipt(options: LinkExpenseReceiptOptions): Promise<KaafilResponse<ExpenseResponse>>;
607
+ /** `DELETE /api/v1/trips/{ref}/expenses/{id}/void` — reverses the paired
608
+ * float movement and cascades an open claim. See this file's header for
609
+ * the `DELETE`-not-`POST` note. */
610
+ void(options: VoidExpenseOptions): Promise<KaafilResponse<ExpenseResponse>>;
611
+ readonly claims: ExpenseClaimsResource;
612
+ }
613
+
614
+ /**
615
+ * `files` — the blob lane: request a presigned upload slot, confirm it,
616
+ * read metadata, and mint a short-lived signed GET. `architecture/08-
617
+ * sdk.md §4`. `request`/`meta`/`url` accept a manager session OR an API
618
+ * key; `confirm` is `managerAuth`-only — a manager confirms the upload
619
+ * they themselves just made.
620
+ *
621
+ * ── THE FOUR-STATUS LIFECYCLE ────────────────────────────────────────────
622
+ *
623
+ * `FileResponse.status` is `pending → ready → orphaned`/`purged`. Bytes
624
+ * never pass through this SDK or the Kaafil API at all: `request()` hands
625
+ * back a presigned `PUT` URL (`CreateFileResponse.uploadUrl`) that the
626
+ * caller's own HTTP stack uses to write directly to object storage, with
627
+ * NO Kaafil auth header — the signed URL itself is the authorization. This
628
+ * package does not perform that `PUT`; it only mints the URL and later
629
+ * confirms what landed.
630
+ *
631
+ * `confirm()` verifies the object actually arrived — existence, the
632
+ * declared `sizeBytes`, and the leading bytes against `contentType`'s file
633
+ * signature — because the presigned `PUT` pins `sizeBytes` into its
634
+ * signature but NOT `contentType`, so a caller-declared type is otherwise
635
+ * unverified. A mismatch answers `422 UPLOAD_MISMATCH` and the file stays
636
+ * `pending`; re-`PUT` and re-confirm until the presigned URL's window
637
+ * lapses. On success `retentionClass` is stamped SERVER-SIDE from
638
+ * `purpose` (never client-declared) and the file flips to `ready`.
639
+ *
640
+ * `url()` mints a fresh 5-minute signed GET on every call — never stored,
641
+ * never reused — and only for a `ready` file. A `purged` file answers
642
+ * `410 FILE_PURGED`: the metadata row survives forever, the bytes do not.
643
+ * `meta()` itself never 404s for a purged file — it is the one read that
644
+ * still answers `200` whatever `status` says, which is how a caller tells
645
+ * "gone" (`410` from `url()`) apart from "never existed" (`404` from
646
+ * `meta()`) without a second not-found class.
647
+ *
648
+ * `purpose ∈ FilePurpose` gates what `contentType ∈ FileContentType` may
649
+ * accompany it server-side; both are `../generated/enums.ts` constants
650
+ * that come from the same vendored spec.
651
+ */
652
+
653
+ type CreateFileResponse = components['schemas']['CreateFileResponse'];
654
+ type FileResponse = components['schemas']['FileResponse'];
655
+ type FileUrlResponse = components['schemas']['FileUrlResponse'];
656
+ type RequestFileUploadRequest = components['schemas']['RequestFileUploadRequest'];
657
+ interface RequestFileUploadOptions extends RequestFileUploadRequest {
658
+ readonly idempotencyKey?: string;
659
+ readonly signal?: AbortSignal;
660
+ }
661
+ interface ReadFileOptions {
662
+ readonly fileId: string;
663
+ readonly signal?: AbortSignal;
664
+ }
665
+ interface ConfirmFileUploadOptions {
666
+ readonly fileId: string;
667
+ readonly idempotencyKey?: string;
668
+ readonly signal?: AbortSignal;
669
+ }
670
+ interface ReadFileUrlOptions {
671
+ readonly fileId: string;
672
+ readonly signal?: AbortSignal;
673
+ }
674
+ interface FilesResource {
675
+ /** `POST /api/v1/files` — mints a `pending` metadata row plus a presigned
676
+ * `PUT`, 15-minute window, `contentType`/`sizeBytes` pinned as request
677
+ * conditions. This method never uploads bytes itself — see this file's
678
+ * header. */
679
+ request(options: RequestFileUploadOptions): Promise<KaafilResponse<CreateFileResponse>>;
680
+ /** `GET /api/v1/files/{id}` — the metadata skeleton, `200` for every
681
+ * status including `purged`. Never carries `storageKey`, `tripRef` or
682
+ * `agencyId`; `retentionClass` is `null` until the file is `ready`. */
683
+ meta(options: ReadFileOptions): Promise<KaafilResponse<FileResponse>>;
684
+ /** `POST /api/v1/files/{id}/confirm` — verifies the uploaded object and
685
+ * flips `pending` → `ready`. See this file's header for the mismatch and
686
+ * retention-stamping behaviour. */
687
+ confirm(options: ConfirmFileUploadOptions): Promise<KaafilResponse<FileResponse>>;
688
+ /** `GET /api/v1/files/{id}/url` — a fresh 5-minute signed GET for a
689
+ * `ready` file. `410 FILE_PURGED` once the bytes are gone. */
690
+ url(options: ReadFileUrlOptions): Promise<KaafilResponse<FileUrlResponse>>;
691
+ }
692
+
693
+ /**
694
+ * `float` — cash advanced to a manager, its ledger, and the derived
695
+ * per-manager balance. `architecture/08-sdk.md §4`.
696
+ *
697
+ * `readSummary`/`readLedger`/`issue`/`adjust` accept a manager session, an
698
+ * API key, OR an agency-admin session; `return` is `managerAuth`-only —
699
+ * only the manager holding the cash may hand it back (an admin is
700
+ * view-only on that row).
701
+ *
702
+ * Every movement is one of four `type`s (`ISSUE|RETURN|ADJUSTMENT|EXPENSE`)
703
+ * with a `direction ∈ FloatDirection` (`IN|OUT`) — `EXPENSE` movements are
704
+ * created by `expenses.log` when `paymentMode: 'FLOAT_CASH'`, never by a
705
+ * method on this resource directly.
706
+ *
707
+ * ── THE NEGATIVE-FLOAT GUARD ─────────────────────────────────────────────
708
+ *
709
+ * Every `OUT` movement — `return()` and `adjust()` with `direction: 'OUT'`
710
+ * — is guarded: `amountMinor` must not exceed the manager's current
711
+ * derived `balanceMinor`, checked and enforced under an advisory lock so
712
+ * two concurrent `OUT`s can never both pass the same stale read. A refusal
713
+ * is `422 BUSINESS_RULE_VIOLATION` with `details.currentBalanceMinor` — the
714
+ * figure the guard actually compared against. `issue()` (`direction: 'IN'`,
715
+ * always additive) and `adjust()` with `direction: 'IN'` are never guarded.
716
+ *
717
+ * `adjust()`'s `note` is REQUIRED (the audit trail of every manual
718
+ * correction) — unlike `issue()`/`return()`, where it is optional.
719
+ * `issue()` never accepts a `currency` field: every write takes the trip's
720
+ * own currency, never a caller-named one.
721
+ *
722
+ * ── DERIVED, NOT DELTA-MERGEABLE ─────────────────────────────────────────
723
+ *
724
+ * `readSummary()`'s rows are entirely derived (`issuedMinor`,
725
+ * `returnedMinor`, `spentMinor`, `adjustmentsMinor`, `balanceMinor`) —
726
+ * nothing here is stored. `since` narrows WHICH managers are returned (only
727
+ * those with a movement at or after the cursor) but every returned row
728
+ * still carries its FULL current balance, never a partial delta — a
729
+ * derived scalar cannot be delta-merged the way a versioned list row can.
730
+ * `mergeDeltaRows`/`isTombstone` (`../delta/cursor.ts`) do not apply here:
731
+ * there is no tombstone case and no per-row `version` to compare.
732
+ * `readLedger()` carries no `since` at all — it always returns one
733
+ * manager's complete movement history, each row stamped with
734
+ * `runningBalanceMinor` immediately after that row.
735
+ */
736
+
737
+ type FloatSummaryResponse = components['schemas']['FloatSummaryResponse'];
738
+ type FloatLedgerResponse = components['schemas']['FloatLedgerResponse'];
739
+ type AdjustFloatResponse = components['schemas']['AdjustFloatResponse'];
740
+ type IssueFloatResponse = components['schemas']['IssueFloatResponse'];
741
+ type ReturnFloatResponse = components['schemas']['ReturnFloatResponse'];
742
+ type AdjustFloatRequest = components['schemas']['AdjustFloatRequest'];
743
+ type IssueFloatRequest = components['schemas']['IssueFloatRequest'];
744
+ type ReturnFloatRequest = components['schemas']['ReturnFloatRequest'];
745
+ interface ReadFloatSummaryOptions {
746
+ readonly tripRef: string;
747
+ /** Narrows which managers are returned to those with a movement at or
748
+ * after this cursor. See this file's header — the rows themselves are
749
+ * never partial, so this is not a `../delta/cursor.ts` cursor. */
750
+ readonly since?: string;
751
+ readonly signal?: AbortSignal;
752
+ }
753
+ interface ReadFloatLedgerOptions {
754
+ readonly tripRef: string;
755
+ readonly managerId: string;
756
+ readonly signal?: AbortSignal;
757
+ }
758
+ interface IssueFloatOptions extends IssueFloatRequest {
759
+ readonly tripRef: string;
760
+ readonly idempotencyKey?: string;
761
+ readonly signal?: AbortSignal;
762
+ }
763
+ interface ReturnFloatOptions extends ReturnFloatRequest {
764
+ readonly tripRef: string;
765
+ readonly idempotencyKey?: string;
766
+ readonly signal?: AbortSignal;
767
+ }
768
+ interface AdjustFloatOptions extends AdjustFloatRequest {
769
+ readonly tripRef: string;
770
+ readonly idempotencyKey?: string;
771
+ readonly signal?: AbortSignal;
772
+ }
773
+ interface FloatResource {
774
+ /** `GET /api/v1/trips/{ref}/float` — one row per manager who has ever had
775
+ * a movement on this trip, entirely derived. See this file's header. */
776
+ readSummary(options: ReadFloatSummaryOptions): Promise<KaafilResponse<FloatSummaryResponse>>;
777
+ /** `GET /api/v1/trips/{ref}/float/{managerId}/ledger` — one manager's
778
+ * full movement history, ordered `(createdAt asc, id asc)`, each row
779
+ * carrying `runningBalanceMinor`. `404` if `managerId` does not name a
780
+ * manager currently assigned to this trip. */
781
+ readLedger(options: ReadFloatLedgerOptions): Promise<KaafilResponse<FloatLedgerResponse>>;
782
+ /** `POST /api/v1/trips/{ref}/float/issue` — `type=ISSUE`, `direction=IN`,
783
+ * never negative-float guarded. An admin or the manager themselves may
784
+ * call this; both paths produce the identical row. */
785
+ issue(options: IssueFloatOptions): Promise<KaafilResponse<IssueFloatResponse>>;
786
+ /** `POST /api/v1/trips/{ref}/float/return` — `type=RETURN`,
787
+ * `direction=OUT`, `managerAuth`-only and negative-float guarded. See
788
+ * this file's header. */
789
+ return(options: ReturnFloatOptions): Promise<KaafilResponse<ReturnFloatResponse>>;
790
+ /** `POST /api/v1/trips/{ref}/float/adjust` — `type=ADJUSTMENT`, direction
791
+ * named by the caller; `note` is required. `direction: 'OUT'` is
792
+ * negative-float guarded identically to `return()`; `direction: 'IN'`
793
+ * never is. */
794
+ adjust(options: AdjustFloatOptions): Promise<KaafilResponse<AdjustFloatResponse>>;
795
+ }
796
+
797
+ /**
798
+ * `itinerary` — the day cards and their ordered items, plus the change log.
799
+ * `architecture/08-sdk.md §4`.
800
+ *
801
+ * `read` and `changeLog.list` accept an API key, a manager session OR an
802
+ * agency-admin session (`readItinerary`/`readItineraryChangeLog` declare all
803
+ * three as alternatives, never an AND) — the consumer never chooses which;
804
+ * `../auth/credentials.ts`'s `CredentialResolver` picks whichever scheme the
805
+ * configured credential satisfies. Every WRITE here is `managerAuth`-only: the
806
+ * itinerary is edited on the ground, and an API-key write answers `401`.
807
+ *
808
+ * Every path parameter is a named field on its options object — never a
809
+ * positional string — so `items.patch` (a trip ref AND an item id) and
810
+ * `days.patch` (a trip ref AND a day index) can't be called with their
811
+ * arguments swapped.
812
+ *
813
+ * `read` is one operation with three modes, because OpenAPI keys an operation
814
+ * by (path, method) and all three are `GET /trips/{ref}/itinerary`: the full
815
+ * aggregate, `?dayIndex=` for one day, and `?since=` for a delta. The two
816
+ * compose. For the delta mode, drive `since` through
817
+ * `../delta/cursor.ts`'s `createDeltaCursor` rather than by hand — that file's
818
+ * header is the argument for why any other cursor loses rows.
819
+ */
820
+
821
+ type ItineraryReadResponse = components['schemas']['ItineraryReadResponse'];
822
+ type ItineraryDayResponse = components['schemas']['ItineraryDayResponse'];
823
+ type ItineraryItemResponse = components['schemas']['ItineraryItemResponse'];
824
+ type ItineraryItemTombstoneResponse = components['schemas']['ItineraryItemTombstoneResponse'];
825
+ type ItineraryReorderResponse = components['schemas']['ItineraryReorderResponse'];
826
+ type ItineraryChangeLogEntryResponse = components['schemas']['ItineraryChangeLogEntryResponse'];
827
+ type AddItineraryItemRequest = components['schemas']['AddItineraryItemRequest'];
828
+ type PatchItineraryItemRequest = components['schemas']['PatchItineraryItemRequest'];
829
+ type ReorderItineraryItemRequest = components['schemas']['ReorderItineraryItemRequest'];
830
+ type PatchItineraryDayRequest = components['schemas']['PatchItineraryDayRequest'];
831
+ /**
832
+ * One row of `read`'s `items[]`: a live item, or the tombstone for one deleted
833
+ * since the cursor. Aliased off the response type rather than restated, so the
834
+ * union stays whatever the contract says it is.
835
+ *
836
+ * Named and exported because a consumer writing a render function over
837
+ * `items` has to name its parameter, and the union is the whole point — see
838
+ * `../delta/cursor.ts`'s `isTombstone`.
839
+ */
840
+ type ItineraryItemDeltaRow = ItineraryReadResponse['items'][number];
841
+ interface ReadItineraryOptions {
842
+ readonly tripRef: string;
843
+ /** Narrows the same shape to one day. 0-based, as `days[].dayIndex` reports it. */
844
+ readonly dayIndex?: number;
845
+ /**
846
+ * The delta cursor: a previous response's own `meta.serverTime`, and nothing
847
+ * else. Omit it for the full aggregate. Prefer `createDeltaCursor`
848
+ * (`../delta/cursor.ts`) over passing this by hand — the boundary is the one
849
+ * thing that silently loses rows, and that module owns it.
850
+ */
851
+ readonly since?: string;
852
+ readonly signal?: AbortSignal;
853
+ }
854
+ interface AddItineraryItemOptions extends Omit<AddItineraryItemRequest, 'isoDate' | 'startTime' | 'endTime'> {
855
+ readonly tripRef: string;
856
+ /**
857
+ * Which day the item belongs to, as a date. The engine resolves it to a day
858
+ * index in the TRIP's timezone and find-or-creates the day — a client never
859
+ * computes a day index itself, and must not, because the answer depends on a
860
+ * timezone the client may not be in.
861
+ */
862
+ readonly isoDate: DateTimeInput;
863
+ readonly startTime?: DateTimeInput | null;
864
+ readonly endTime?: DateTimeInput | null;
865
+ readonly idempotencyKey?: string;
866
+ readonly signal?: AbortSignal;
867
+ }
868
+ interface PatchItineraryItemOptions extends Omit<PatchItineraryItemRequest, 'startTime' | 'endTime'> {
869
+ readonly tripRef: string;
870
+ readonly itemId: string;
871
+ readonly startTime?: DateTimeInput | null;
872
+ readonly endTime?: DateTimeInput | null;
873
+ /**
874
+ * Required, not optional. A missing `If-Match` and a stale one both answer
875
+ * `409 CONFLICT_VERSION` — identically confusing either way — so this method
876
+ * makes forgetting it impossible instead of merely discouraged. Pass the
877
+ * `version` from the item as last read. The SDK builds the `If-Match` header
878
+ * itself; never set it in `headers`.
879
+ */
880
+ readonly version: number;
881
+ readonly signal?: AbortSignal;
882
+ }
883
+ interface DeleteItineraryItemOptions {
884
+ readonly tripRef: string;
885
+ readonly itemId: string;
886
+ /** See `PatchItineraryItemOptions.version` — same contract. */
887
+ readonly version: number;
888
+ readonly signal?: AbortSignal;
889
+ }
890
+ interface ReorderItineraryItemOptions extends ReorderItineraryItemRequest {
891
+ readonly tripRef: string;
892
+ readonly itemId: string;
893
+ readonly idempotencyKey?: string;
894
+ readonly signal?: AbortSignal;
895
+ }
896
+ interface ItineraryItemsResource {
897
+ /** `POST …/itinerary/items` — appends at the tail of its day. */
898
+ add(options: AddItineraryItemOptions): Promise<KaafilResponse<ItineraryItemResponse>>;
899
+ patch(options: PatchItineraryItemOptions): Promise<KaafilResponse<ItineraryItemResponse>>;
900
+ /**
901
+ * Soft-deletes the item. Answers the TOMBSTONE — the same `_tombstone`
902
+ * marker a later `?since=` pull would carry for it — rather than an empty
903
+ * body, so the caller that issued the delete applies the identical drop to
904
+ * its local store as one that learned about it from a delta.
905
+ */
906
+ remove(options: DeleteItineraryItemOptions): Promise<KaafilResponse<ItineraryItemTombstoneResponse>>;
907
+ /**
908
+ * Moves the item to `index` within its own day; the index is clamped
909
+ * server-side, and the whole day is re-stamped densely so two devices
910
+ * replaying the same reorder land on identical `sortOrder` values.
911
+ *
912
+ * No `version` and no `If-Match`, deliberately: a reorder is last-write-wins
913
+ * by design, and a conflict is resolved by re-reading the day (the response
914
+ * already carries the day's full item list, in its new order).
915
+ */
916
+ reorder(options: ReorderItineraryItemOptions): Promise<KaafilResponse<ItineraryReorderResponse>>;
917
+ }
918
+ interface PatchItineraryDayOptions extends PatchItineraryDayRequest {
919
+ readonly tripRef: string;
920
+ /** 0-based, as `days[].dayIndex` reports it — the day's position, not its id. */
921
+ readonly dayIndex: number;
922
+ /** See `PatchItineraryItemOptions.version` — same contract. */
923
+ readonly version: number;
924
+ readonly signal?: AbortSignal;
925
+ }
926
+ interface ItineraryDaysResource {
927
+ /** `PATCH …/itinerary/days/{dayIndex}` — the card title and the summary line. */
928
+ patch(options: PatchItineraryDayOptions): Promise<KaafilResponse<ItineraryDayResponse>>;
929
+ }
930
+ interface ListItineraryChangeLogOptions {
931
+ readonly tripRef: string;
932
+ /** See `ReadItineraryOptions.since`. */
933
+ readonly since?: string;
934
+ readonly signal?: AbortSignal;
935
+ }
936
+ interface ItineraryChangeLogResource {
937
+ /** `GET …/itinerary/change-log` — the most recent entries, newest first.
938
+ * Not cursor-paginated (no `cursor`/`limit` query params in the spec). */
939
+ list(options: ListItineraryChangeLogOptions): Promise<KaafilResponse<readonly ItineraryChangeLogEntryResponse[]>>;
940
+ }
941
+ interface ItineraryResource {
942
+ read(options: ReadItineraryOptions): Promise<KaafilResponse<ItineraryReadResponse>>;
943
+ readonly items: ItineraryItemsResource;
944
+ readonly days: ItineraryDaysResource;
945
+ readonly changeLog: ItineraryChangeLogResource;
946
+ }
947
+
948
+ /**
949
+ * `pickups` — the stop list, reorder, manifest-by-pickup, assign/board and
950
+ * the close/reopen policy. `architecture/08-sdk.md §4`.
951
+ *
952
+ * `list` and `manifestByPickup` accept a manager session, an agency-admin
953
+ * session OR an API key (three alternatives, never an AND); every WRITE is
954
+ * `managerAuth`-only, the same split `seating`, `rooming` and `itinerary`
955
+ * have and for the same reason — pickups are worked on the ground.
956
+ *
957
+ * Every path parameter is a named field on its options object — never a
958
+ * positional string — so `assign` (a trip ref AND a stop id) can't be called
959
+ * with its arguments swapped.
960
+ *
961
+ * ── ONE `close` METHOD, TWO POLICIES, ONE CODE ──────────────────────────────
962
+ *
963
+ * `close()` is a single operation whichever `Trip.eventType` the trip carries
964
+ * — never two endpoints and never two codes. On a `TRIP`, every `PENDING`
965
+ * traveller must appear in `resolutions[]` with a terminal status, and
966
+ * `confirm` is ignored; on a `TREK`, `resolutions[]` is optional and a short
967
+ * close (`boardedCount < expectedCount`) needs `confirm: true` PLUS
968
+ * `confirmedHeadCount`. Both branches answer the SAME `422 STOP_HAS_PENDING`
969
+ * when they refuse — a TREK short-close refusal additionally carries
970
+ * `details.requiresConfirm: true`, which is what tells a caller to show the
971
+ * confirm sheet rather than the per-traveller resolver, and NOT a second
972
+ * code. `details` is untyped at the transport boundary (the envelope
973
+ * schema's own `additionalProperties: {}`), so read it defensively:
974
+ * `error.details?.requiresConfirm === true`.
975
+ *
976
+ * `confirmedHeadCount` disagreeing with the actual count is FLAGGED
977
+ * (`CloseResponse.headCountMismatch`), never blocking — "a manager on a
978
+ * trailhead can't wait forever."
979
+ */
980
+
981
+ type PickupStopResponse = components['schemas']['PickupStopResponse'];
982
+ type PickupStopDeltaResponse = components['schemas']['PickupStopDeltaResponse'];
983
+ type PickupStopDeleteResponse = components['schemas']['PickupStopDeleteResponse'];
984
+ type ManifestByPickupResponse = components['schemas']['ManifestByPickupResponse'];
985
+ type AssignResponse = components['schemas']['AssignResponse'];
986
+ type BoardResponse = components['schemas']['BoardResponse'];
987
+ type CloseResponse = components['schemas']['CloseResponse'];
988
+ type ReopenResponse = components['schemas']['ReopenResponse'];
989
+ type CreatePickupStopRequest = components['schemas']['CreatePickupStopRequest'];
990
+ type PatchPickupStopRequest = components['schemas']['PatchPickupStopRequest'];
991
+ type ReorderPickupStopsRequest = components['schemas']['ReorderPickupStopsRequest'];
992
+ type AssignPickupTravellerRequest = components['schemas']['AssignPickupTravellerRequest'];
993
+ type BoardPickupTravellerRequest = components['schemas']['BoardPickupTravellerRequest'];
994
+ type ClosePickupStopRequest = components['schemas']['ClosePickupStopRequest'];
995
+ /**
996
+ * One row of `list`'s result: a live stop, or the tombstone for one deleted
997
+ * since the cursor. Aliased off the response type rather than restated, so
998
+ * the union stays whatever the contract says it is. See `../delta/cursor.ts`'s
999
+ * `isTombstone`.
1000
+ */
1001
+ type PickupStopDeltaRow = PickupStopDeltaResponse;
1002
+ interface ListPickupStopsOptions {
1003
+ readonly tripRef: string;
1004
+ /** Narrows to one stop kind. Omitted lists both `PICKUP` and `DROP`. */
1005
+ readonly kind?: components['schemas']['PickupStopResponse']['kind'];
1006
+ /**
1007
+ * The delta cursor: a previous response's own `meta.serverTime`, and
1008
+ * nothing else. Omit it for the full list. Prefer `createDeltaCursor`
1009
+ * (`../delta/cursor.ts`) over passing this by hand — the boundary is the
1010
+ * one thing that silently loses rows, and that module owns it.
1011
+ */
1012
+ readonly since?: string;
1013
+ readonly signal?: AbortSignal;
1014
+ }
1015
+ interface ReadManifestByPickupOptions {
1016
+ readonly tripRef: string;
1017
+ /** See `ListPickupStopsOptions.kind`. */
1018
+ readonly kind?: components['schemas']['PickupStopResponse']['kind'];
1019
+ readonly signal?: AbortSignal;
1020
+ }
1021
+ interface CreatePickupStopOptions extends Omit<CreatePickupStopRequest, 'scheduledTime'> {
1022
+ readonly tripRef: string;
1023
+ readonly scheduledTime: DateTimeInput;
1024
+ readonly idempotencyKey?: string;
1025
+ readonly signal?: AbortSignal;
1026
+ }
1027
+ interface PatchPickupStopOptions extends Omit<PatchPickupStopRequest, 'scheduledTime'> {
1028
+ readonly tripRef: string;
1029
+ readonly pointId: string;
1030
+ readonly scheduledTime?: DateTimeInput;
1031
+ /**
1032
+ * Required, not optional. A missing `If-Match` and a stale one both answer
1033
+ * `409 CONFLICT_VERSION` — identically confusing either way — so this
1034
+ * method makes forgetting it impossible instead of merely discouraged.
1035
+ * Pass the `version` from the stop as last read. The SDK builds the
1036
+ * `If-Match` header itself; never set it in `headers`.
1037
+ *
1038
+ * Changing `kind` while the stop holds assignments is refused with
1039
+ * `422 BUSINESS_RULE_VIOLATION` and `details.assignedCount` — reassign or
1040
+ * clear the stop first.
1041
+ */
1042
+ readonly version: number;
1043
+ readonly signal?: AbortSignal;
1044
+ }
1045
+ interface DeletePickupStopOptions {
1046
+ readonly tripRef: string;
1047
+ readonly pointId: string;
1048
+ /** See `PatchPickupStopOptions.version` — same contract. */
1049
+ readonly version: number;
1050
+ /** Releases every assignment on this stop back to unassigned before
1051
+ * deleting it. Without it, a stop with assignments is refused. */
1052
+ readonly force?: boolean;
1053
+ readonly signal?: AbortSignal;
1054
+ }
1055
+ interface ReorderPickupStopsOptions extends ReorderPickupStopsRequest {
1056
+ readonly tripRef: string;
1057
+ readonly idempotencyKey?: string;
1058
+ readonly signal?: AbortSignal;
1059
+ }
1060
+ interface AssignPickupTravellerOptions extends AssignPickupTravellerRequest {
1061
+ readonly tripRef: string;
1062
+ readonly pointId: string;
1063
+ readonly idempotencyKey?: string;
1064
+ readonly signal?: AbortSignal;
1065
+ }
1066
+ interface BoardPickupTravellerOptions extends BoardPickupTravellerRequest {
1067
+ readonly tripRef: string;
1068
+ readonly pointId: string;
1069
+ readonly idempotencyKey?: string;
1070
+ readonly signal?: AbortSignal;
1071
+ }
1072
+ interface ClosePickupStopOptions extends ClosePickupStopRequest {
1073
+ readonly tripRef: string;
1074
+ readonly pointId: string;
1075
+ readonly idempotencyKey?: string;
1076
+ readonly signal?: AbortSignal;
1077
+ }
1078
+ interface ReopenPickupStopOptions {
1079
+ readonly tripRef: string;
1080
+ readonly pointId: string;
1081
+ readonly idempotencyKey?: string;
1082
+ readonly signal?: AbortSignal;
1083
+ }
1084
+ interface PickupsResource {
1085
+ /** `GET /api/v1/trips/{ref}/pickups`. With `?since=` the rows are
1086
+ * `PickupStopDeltaResponse` — a union with the tombstone, so the drop case
1087
+ * cannot be forgotten. Without one, no tombstone can appear. */
1088
+ list(options: ListPickupStopsOptions): Promise<KaafilResponse<readonly PickupStopDeltaRow[]>>;
1089
+ create(options: CreatePickupStopOptions): Promise<KaafilResponse<PickupStopResponse>>;
1090
+ patch(options: PatchPickupStopOptions): Promise<KaafilResponse<PickupStopResponse>>;
1091
+ remove(options: DeletePickupStopOptions): Promise<KaafilResponse<PickupStopDeleteResponse>>;
1092
+ reorder(options: ReorderPickupStopsOptions): Promise<KaafilResponse<readonly PickupStopResponse[]>>;
1093
+ /**
1094
+ * `POST …/pickups/{pointId}/assign` — puts one traveller on one stop. If
1095
+ * they were already on a different stop of the SAME `kind`, the move is one
1096
+ * atomic delete+create (R1: at most one `PICKUP` and one `DROP` per
1097
+ * traveller, enforced by a composite unique, not merely a service-layer
1098
+ * promise), and the response's `previousPickupPointId` names where they
1099
+ * moved from.
1100
+ */
1101
+ assign(options: AssignPickupTravellerOptions): Promise<KaafilResponse<AssignResponse>>;
1102
+ board(options: BoardPickupTravellerOptions): Promise<KaafilResponse<BoardResponse>>;
1103
+ /** See this file's header for the close policy: one method, two branches
1104
+ * on `Trip.eventType`, one code (`STOP_HAS_PENDING`) either way. */
1105
+ close(options: ClosePickupStopOptions): Promise<KaafilResponse<CloseResponse>>;
1106
+ /** Flips `CLOSED → OPEN` and clears `closedAt`/`closedByManagerId` — there
1107
+ * is no `REOPENED` state. The next `close()` on this stop emits a fresh
1108
+ * CORRECTIVE `pickup.stop_closed` event, `reopened: true`. */
1109
+ reopen(options: ReopenPickupStopOptions): Promise<KaafilResponse<ReopenResponse>>;
1110
+ /** `GET …/manifest-by-pickup` — the roster grouped by stop, for a printed
1111
+ * or on-screen boarding sheet. Renders the same glyph/tone canon as
1112
+ * `rooming`'s and `seating`'s occupants — no second derivation. */
1113
+ manifestByPickup(options: ReadManifestByPickupOptions): Promise<KaafilResponse<ManifestByPickupResponse>>;
1114
+ }
1115
+
1116
+ /**
1117
+ * `rooming` — the board, stay windows, rooms, manual bed assignment and
1118
+ * auto-assign. `architecture/08-sdk.md §4`.
1119
+ *
1120
+ * `board.read` and `stayWindows.list` accept a manager session, an
1121
+ * agency-admin session OR an API key (three alternatives, never an AND); every
1122
+ * WRITE is `managerAuth`-only, the same split `itinerary` has and for the same
1123
+ * reason — rooming is decided on the ground.
1124
+ *
1125
+ * Rooming is a gated capability (`GROUP` trips only, behind the `rooming` plan
1126
+ * flag), so its refusals are ordinary typed errors a caller should expect:
1127
+ * `402` for a flag that is off, `422 CAPABILITY_UNAVAILABLE` on a
1128
+ * `PERSONALIZED` trip, `422 READ_ONLY_ROLE` for a COORDINATOR. See
1129
+ * `../http/errors.ts`.
1130
+ *
1131
+ * Every path parameter is a named field on its options object — never a
1132
+ * positional string — so `rooms.patch` (a trip ref AND a room id) and
1133
+ * `stayWindows.patch` (a trip ref AND a window id) can't be called with their
1134
+ * arguments swapped. The two child ids are named `roomId` and `windowId` after
1135
+ * the thing they identify, never both `id`.
1136
+ *
1137
+ * `HEURISTIC_SEVERITY` and the glyph/tone vocabulary a client needs to RENDER
1138
+ * this module's output are not here — they are not on the wire. See
1139
+ * `../rooming/vocabulary.ts`.
1140
+ */
1141
+
1142
+ type RoomingBoardResponse = components['schemas']['RoomingBoardResponse'];
1143
+ type RoomingStayWindowResponse = components['schemas']['RoomingStayWindowResponse'];
1144
+ type RoomingStayWindowDeltaResponse = components['schemas']['RoomingStayWindowDeltaResponse'];
1145
+ type RoomingRoomResponse = components['schemas']['RoomingRoomResponse'];
1146
+ type RoomingDeleteResponse = components['schemas']['RoomingDeleteResponse'];
1147
+ type RoomingAssignResponse = components['schemas']['RoomingAssignResponse'];
1148
+ type RoomingAutoAssignResponse = components['schemas']['RoomingAutoAssignResponse'];
1149
+ type CreateRoomingStayWindowRequest = components['schemas']['CreateRoomingStayWindowRequest'];
1150
+ type PatchRoomingStayWindowRequest = components['schemas']['PatchRoomingStayWindowRequest'];
1151
+ type CreateRoomingRoomRequest = components['schemas']['CreateRoomingRoomRequest'];
1152
+ type PatchRoomingRoomRequest = components['schemas']['PatchRoomingRoomRequest'];
1153
+ type AssignRoomingBedRequest = components['schemas']['AssignRoomingBedRequest'];
1154
+ type AutoAssignRoomingRequest = components['schemas']['AutoAssignRoomingRequest'];
1155
+ /**
1156
+ * One row of the board's `rooms[]`: a live room, or the tombstone for one
1157
+ * deleted since the cursor. Aliased off the response type, never restated.
1158
+ */
1159
+ type RoomingRoomDeltaRow = RoomingBoardResponse['rooms'][number];
1160
+ /** One traveller's chip, wherever it appears — in a bed, or in `unassigned[]`. */
1161
+ type RoomingOccupant = RoomingBoardResponse['unassigned'][number];
1162
+ /**
1163
+ * `autoAssign`'s rules block, named so a caller can build one and hold it in a
1164
+ * variable. Indexed off the request type, so the three knobs stay whatever the
1165
+ * contract says they are.
1166
+ */
1167
+ type RoomingAutoAssignRules = NonNullable<AutoAssignRoomingRequest['rules']>;
1168
+ interface ReadRoomingBoardOptions {
1169
+ readonly tripRef: string;
1170
+ /** Narrows the board to one stay window. Omitted reads every window. */
1171
+ readonly stayWindowId?: string;
1172
+ /**
1173
+ * The delta cursor: a previous response's own `meta.serverTime`, and nothing
1174
+ * else. Omit it for the full board. Prefer `createDeltaCursor`
1175
+ * (`../delta/cursor.ts`) over passing this by hand — the boundary is the one
1176
+ * thing that silently loses rows, and that module owns it.
1177
+ */
1178
+ readonly since?: string;
1179
+ readonly signal?: AbortSignal;
1180
+ }
1181
+ interface ListRoomingStayWindowsOptions {
1182
+ readonly tripRef: string;
1183
+ /** See `ReadRoomingBoardOptions.since`. */
1184
+ readonly since?: string;
1185
+ readonly signal?: AbortSignal;
1186
+ }
1187
+ interface CreateRoomingStayWindowOptions extends Omit<CreateRoomingStayWindowRequest, 'startDate' | 'endDate'> {
1188
+ readonly tripRef: string;
1189
+ readonly startDate: DateTimeInput;
1190
+ /**
1191
+ * Half-open with `startDate`: a checkout date equal to the next window's
1192
+ * check-in is legal, because multi-hotel trips are contiguous by nature.
1193
+ * Overlap beyond that is refused with `422 BUSINESS_RULE_VIOLATION` and
1194
+ * `details.rule === 'stay_window_overlap'`.
1195
+ */
1196
+ readonly endDate: DateTimeInput;
1197
+ readonly idempotencyKey?: string;
1198
+ readonly signal?: AbortSignal;
1199
+ }
1200
+ interface PatchRoomingStayWindowOptions extends Omit<PatchRoomingStayWindowRequest, 'startDate' | 'endDate'> {
1201
+ readonly tripRef: string;
1202
+ readonly windowId: string;
1203
+ readonly startDate?: DateTimeInput;
1204
+ readonly endDate?: DateTimeInput;
1205
+ /**
1206
+ * Required, not optional. A missing `If-Match` and a stale one both answer
1207
+ * `409 CONFLICT_VERSION` — identically confusing either way — so this method
1208
+ * makes forgetting it impossible instead of merely discouraged. Pass the
1209
+ * `version` from the window as last read. The SDK builds the `If-Match`
1210
+ * header itself; never set it in `headers`.
1211
+ */
1212
+ readonly version: number;
1213
+ readonly signal?: AbortSignal;
1214
+ }
1215
+ interface DeleteRoomingStayWindowOptions {
1216
+ readonly tripRef: string;
1217
+ readonly windowId: string;
1218
+ /** See `PatchRoomingStayWindowOptions.version` — same contract. */
1219
+ readonly version: number;
1220
+ /**
1221
+ * Deleting a window that still has rooms with occupants is REFUSED
1222
+ * (`422 BUSINESS_RULE_VIOLATION`, `details.rule ===
1223
+ * 'stay_window_has_assignments'`) unless this is `true`, which also releases
1224
+ * the beds. A query param and never a body, because a DELETE body is not
1225
+ * reliably carried; an explicit opt-in and never the default, because the
1226
+ * refusal is the whole safety property — a manager who did not mean to empty
1227
+ * a hotel gets told, not obeyed.
1228
+ */
1229
+ readonly force?: boolean;
1230
+ readonly signal?: AbortSignal;
1231
+ }
1232
+ interface RoomingStayWindowsResource {
1233
+ /**
1234
+ * `GET …/rooming/stay-windows`. With `?since=` the rows are
1235
+ * `RoomingStayWindowDeltaResponse` — a union with the tombstone, so the drop
1236
+ * case cannot be forgotten (`../delta/cursor.ts`'s `isTombstone`). Without
1237
+ * one, no tombstone can appear: a cold read has nothing to drop.
1238
+ */
1239
+ list(options: ListRoomingStayWindowsOptions): Promise<KaafilResponse<readonly RoomingStayWindowDeltaResponse[]>>;
1240
+ create(options: CreateRoomingStayWindowOptions): Promise<KaafilResponse<RoomingStayWindowResponse>>;
1241
+ patch(options: PatchRoomingStayWindowOptions): Promise<KaafilResponse<RoomingStayWindowResponse>>;
1242
+ remove(options: DeleteRoomingStayWindowOptions): Promise<KaafilResponse<RoomingDeleteResponse>>;
1243
+ }
1244
+ interface CreateRoomingRoomOptions extends CreateRoomingRoomRequest {
1245
+ readonly tripRef: string;
1246
+ readonly idempotencyKey?: string;
1247
+ readonly signal?: AbortSignal;
1248
+ }
1249
+ interface PatchRoomingRoomOptions extends PatchRoomingRoomRequest {
1250
+ readonly tripRef: string;
1251
+ readonly roomId: string;
1252
+ /** See `PatchRoomingStayWindowOptions.version` — same contract. */
1253
+ readonly version: number;
1254
+ readonly signal?: AbortSignal;
1255
+ }
1256
+ interface DeleteRoomingRoomOptions {
1257
+ readonly tripRef: string;
1258
+ readonly roomId: string;
1259
+ /** See `PatchRoomingStayWindowOptions.version` — same contract. */
1260
+ readonly version: number;
1261
+ /** See `DeleteRoomingStayWindowOptions.force` — same contract, one room's worth. */
1262
+ readonly force?: boolean;
1263
+ readonly signal?: AbortSignal;
1264
+ }
1265
+ interface RoomingRoomsResource {
1266
+ create(options: CreateRoomingRoomOptions): Promise<KaafilResponse<RoomingRoomResponse>>;
1267
+ /**
1268
+ * Beds are synthesised from `capacity` (`A`..`H`, truncated), never stored,
1269
+ * so `beds[]` on the response is derived — dropping `capacity` below the
1270
+ * room's current occupancy is refused with `422 BUSINESS_RULE_VIOLATION` and
1271
+ * `details.rule === 'room_capacity_below_occupancy'` rather than silently
1272
+ * evicting whoever was in the last bed.
1273
+ */
1274
+ patch(options: PatchRoomingRoomOptions): Promise<KaafilResponse<RoomingRoomResponse>>;
1275
+ remove(options: DeleteRoomingRoomOptions): Promise<KaafilResponse<RoomingDeleteResponse>>;
1276
+ }
1277
+ interface AssignRoomingBedOptions extends AssignRoomingBedRequest {
1278
+ readonly tripRef: string;
1279
+ readonly idempotencyKey?: string;
1280
+ readonly signal?: AbortSignal;
1281
+ }
1282
+ interface AutoAssignRoomingOptions extends AutoAssignRoomingRequest {
1283
+ readonly tripRef: string;
1284
+ readonly idempotencyKey?: string;
1285
+ readonly signal?: AbortSignal;
1286
+ }
1287
+ interface RoomingResource {
1288
+ /** `GET /api/v1/trips/{ref}/rooming` — windows, rooms with their beds, the
1289
+ * unassigned roster, and the summary counters. */
1290
+ read(options: ReadRoomingBoardOptions): Promise<KaafilResponse<RoomingBoardResponse>>;
1291
+ /**
1292
+ * `POST …/rooming/assign` — puts one traveller in one bed. `bedLabel: null`
1293
+ * un-assigns them. If the target bed is taken the occupant is displaced, and
1294
+ * the response names them in `displacedTravellerId` rather than leaving the
1295
+ * caller to diff two boards to notice a second person moved.
1296
+ */
1297
+ assign(options: AssignRoomingBedOptions): Promise<KaafilResponse<RoomingAssignResponse>>;
1298
+ /**
1299
+ * `POST …/rooming/auto-assign`. `dryRun: true` returns the plan without
1300
+ * writing it; `dryRun: false` returns the plan it actually applied. The two
1301
+ * are the SAME computation — the solver is pure and the only difference is
1302
+ * whether the result is also written — so a preview and its apply agree by
1303
+ * construction, and a client that stored the preview can diff it against the
1304
+ * applied plan to see whether the rooms moved underneath it.
1305
+ *
1306
+ * Every rule reports an outcome in `perRule`, including rules that had
1307
+ * nothing to do; a relaxation is never silent. How loudly to render each one
1308
+ * is `HEURISTIC_SEVERITY` in `../rooming/vocabulary.ts`. A traveller who got
1309
+ * no bed is in `unassigned[]` WITH a reason — absence from `plan` alone is
1310
+ * ambiguous, because a manually-pinned traveller is also absent and is fine.
1311
+ */
1312
+ autoAssign(options: AutoAssignRoomingOptions): Promise<KaafilResponse<RoomingAutoAssignResponse>>;
1313
+ readonly stayWindows: RoomingStayWindowsResource;
1314
+ readonly rooms: RoomingRoomsResource;
1315
+ }
1316
+
1317
+ /**
1318
+ * `seating` — the transport board, vehicles, manual seat assignment,
1319
+ * auto-assign and the vehicle-manager link. `architecture/08-sdk.md §4`.
1320
+ *
1321
+ * `read` accepts a manager session, an agency-admin session OR an API key
1322
+ * (three alternatives, never an AND); every WRITE is `managerAuth`-only, the
1323
+ * same split `rooming` and `itinerary` have and for the same reason — seating
1324
+ * is decided on the ground.
1325
+ *
1326
+ * Every path parameter is a named field on its options object — never a
1327
+ * positional string — so `vehicles.patch` (a trip ref AND a vehicle id) can't
1328
+ * be called with its arguments swapped.
1329
+ *
1330
+ * `deriveOccupantMark` and `synthesiseBedLabels` — the glyph/tone and
1331
+ * bed-label logic a client needs to RENDER this module's output — are not
1332
+ * here; they are rooming's, imported by the engine's seating module rather
1333
+ * than re-derived (`architecture` 10B notes), and are not on the wire either.
1334
+ * A client renders an occupant's `glyph`/`tone` fields straight off the
1335
+ * response, the same as `rooming`'s occupants.
1336
+ *
1337
+ * ── THE TYPE↔LAYOUT RULE, AND WHY `seatLabel` CANNOT BE STATICALLY TIED TO IT ──
1338
+ *
1339
+ * A vehicle's `layout` is non-null only for `type ∈ {FLIGHT, TRAIN}`
1340
+ * (`Vehicle_layout_requires_seatable_type`, enforced server-side by a CHECK).
1341
+ * `layout: null` means the vehicle carries no seat map at all — most road
1342
+ * vehicles, because "the label grid was a fiction the manager maintained and
1343
+ * the driver ignored" — and on such a vehicle every assignment's `seatLabel`
1344
+ * is `null`, enforced on write. The converse is deliberately NOT an invariant:
1345
+ * a seat-mapped vehicle may still hold `seatLabel: null` rows (a group
1346
+ * confirmed on a flight before the airline issues seat numbers), so `layout`
1347
+ * being non-null does not mean `seatLabel` is required either.
1348
+ *
1349
+ * `assign()` below is intentionally NOT generic over "which vehicle", so
1350
+ * `AssignSeatingOptions.seatLabel` cannot be narrowed to "required when this
1351
+ * vehicle has a layout, forbidden when it doesn't" at the type level: the
1352
+ * call site names a `vehicleId`, not the vehicle object it was read from, and
1353
+ * whether that id currently has a layout is server-side state this SDK does
1354
+ * not — and must not — cache and re-derive locally (the whole point of a
1355
+ * board read is that the server is the source of truth for it). Encoding the
1356
+ * rule here would mean either accepting a vehicle object just to read one
1357
+ * field, which no other write on this surface does, or building a second,
1358
+ * client-side copy of `seatMapped` that could drift from what the server
1359
+ * actually enforces. So `seatLabel` stays `string | null | undefined` on every
1360
+ * vehicle unconditionally, and the rule is enforced where the truth lives:
1361
+ * the server accepts a `seatLabel` for a seat-less vehicle and silently
1362
+ * ignores it into `null`; the honest type-level statement of this surface is
1363
+ * that `seatLabel` is ALWAYS optional and ALWAYS nullable, never that it
1364
+ * varies by vehicle. Read a vehicle's own `seatMapped` (`SeatingBoardResponse`
1365
+ * / `SeatingVehicleResponse`) before deciding whether to show a seat picker.
1366
+ */
1367
+
1368
+ type SeatingBoardResponse = components['schemas']['SeatingBoardResponse'];
1369
+ type SeatingVehicleResponse = components['schemas']['SeatingVehicleResponse'];
1370
+ type SeatingDeleteVehicleResponse = components['schemas']['SeatingDeleteVehicleResponse'];
1371
+ type SeatingAssignResponse = components['schemas']['SeatingAssignResponse'];
1372
+ type SeatingAutoAssignResponse = components['schemas']['SeatingAutoAssignResponse'];
1373
+ type SeatingManagerLinkResponse = components['schemas']['SeatingManagerLinkResponse'];
1374
+ type CreateSeatingVehicleRequest = components['schemas']['CreateSeatingVehicleRequest'];
1375
+ type PatchSeatingVehicleRequest = components['schemas']['PatchSeatingVehicleRequest'];
1376
+ type AssignSeatingRequest = components['schemas']['AssignSeatingRequest'];
1377
+ type AutoAssignSeatingRequest = components['schemas']['AutoAssignSeatingRequest'];
1378
+ type LinkSeatingManagerRequest = components['schemas']['LinkSeatingManagerRequest'];
1379
+ /**
1380
+ * One row of the board's `vehicles[]`: a live vehicle, or the tombstone for
1381
+ * one deleted since the cursor. Aliased off the response type, never
1382
+ * restated.
1383
+ */
1384
+ type SeatingVehicleDeltaRow = SeatingBoardResponse['vehicles'][number];
1385
+ /** One traveller's chip in `unassignedPool[]`, or (per vehicle) in
1386
+ * `occupants[]`/`unseatedOnVehicle[]`/a seat's `occupant`. */
1387
+ type SeatingOccupant = SeatingBoardResponse['unassignedPool'][number];
1388
+ /**
1389
+ * `autoAssign`'s rules block, named so a caller can build one and hold it in
1390
+ * a variable. Indexed off the request type, so the three knobs stay whatever
1391
+ * the contract says they are.
1392
+ */
1393
+ type SeatingAutoAssignRules = NonNullable<AutoAssignSeatingRequest['rules']>;
1394
+ interface ReadSeatingBoardOptions {
1395
+ readonly tripRef: string;
1396
+ /**
1397
+ * The delta cursor: a previous response's own `meta.serverTime`, and
1398
+ * nothing else. Omit it for the full board. Prefer `createDeltaCursor`
1399
+ * (`../delta/cursor.ts`) over passing this by hand — the boundary is the
1400
+ * one thing that silently loses rows, and that module owns it.
1401
+ */
1402
+ readonly since?: string;
1403
+ readonly signal?: AbortSignal;
1404
+ }
1405
+ interface CreateSeatingVehicleOptions extends CreateSeatingVehicleRequest {
1406
+ readonly tripRef: string;
1407
+ readonly idempotencyKey?: string;
1408
+ readonly signal?: AbortSignal;
1409
+ }
1410
+ interface PatchSeatingVehicleOptions extends PatchSeatingVehicleRequest {
1411
+ readonly tripRef: string;
1412
+ readonly vehicleId: string;
1413
+ /**
1414
+ * Required, not optional. A missing `If-Match` and a stale one both answer
1415
+ * `409 CONFLICT_VERSION` — identically confusing either way — so this
1416
+ * method makes forgetting it impossible instead of merely discouraged.
1417
+ * Pass the `version` from the vehicle as last read. The SDK builds the
1418
+ * `If-Match` header itself; never set it in `headers`.
1419
+ *
1420
+ * A capacity-down or a `layout` change that would orphan a recorded seat
1421
+ * label is refused with `422 SEATING_CAPACITY_ORPHAN` rather than silently
1422
+ * dropping whoever held that label — see this file's header for the three
1423
+ * forms of the guard.
1424
+ */
1425
+ readonly version: number;
1426
+ readonly signal?: AbortSignal;
1427
+ }
1428
+ interface DeleteSeatingVehicleOptions {
1429
+ readonly tripRef: string;
1430
+ readonly vehicleId: string;
1431
+ /** See `PatchSeatingVehicleOptions.version` — same contract. */
1432
+ readonly version: number;
1433
+ readonly signal?: AbortSignal;
1434
+ }
1435
+ interface LinkSeatingManagerOptions extends LinkSeatingManagerRequest {
1436
+ readonly tripRef: string;
1437
+ readonly vehicleId: string;
1438
+ readonly idempotencyKey?: string;
1439
+ readonly signal?: AbortSignal;
1440
+ }
1441
+ interface UnlinkSeatingManagerOptions {
1442
+ readonly tripRef: string;
1443
+ readonly vehicleId: string;
1444
+ /** See `PatchSeatingVehicleOptions.version` — same contract. */
1445
+ readonly version: number;
1446
+ readonly signal?: AbortSignal;
1447
+ }
1448
+ interface SeatingVehicleManagerResource {
1449
+ /** `POST …/seating/vehicles/{vehicleId}/manager` — links a manager to this
1450
+ * vehicle. Demotes whichever vehicle that manager was previously linked to,
1451
+ * atomically: a manager only ever rides one vehicle. */
1452
+ link(options: LinkSeatingManagerOptions): Promise<KaafilResponse<SeatingManagerLinkResponse>>;
1453
+ unlink(options: UnlinkSeatingManagerOptions): Promise<KaafilResponse<SeatingManagerLinkResponse>>;
1454
+ }
1455
+ interface SeatingVehiclesResource {
1456
+ create(options: CreateSeatingVehicleOptions): Promise<KaafilResponse<SeatingVehicleResponse>>;
1457
+ patch(options: PatchSeatingVehicleOptions): Promise<KaafilResponse<SeatingVehicleResponse>>;
1458
+ /** Clears every occupant on this vehicle back to the unassigned pool and
1459
+ * unlinks its manager, then deletes it. */
1460
+ remove(options: DeleteSeatingVehicleOptions): Promise<KaafilResponse<SeatingDeleteVehicleResponse>>;
1461
+ readonly manager: SeatingVehicleManagerResource;
1462
+ }
1463
+ interface AssignSeatingOptions extends AssignSeatingRequest {
1464
+ readonly tripRef: string;
1465
+ readonly idempotencyKey?: string;
1466
+ readonly signal?: AbortSignal;
1467
+ }
1468
+ interface AutoAssignSeatingOptions extends AutoAssignSeatingRequest {
1469
+ readonly tripRef: string;
1470
+ readonly idempotencyKey?: string;
1471
+ readonly signal?: AbortSignal;
1472
+ }
1473
+ interface SeatingResource {
1474
+ /** `GET /api/v1/trips/{ref}/seating` — vehicles with their seats and
1475
+ * occupants, the unassigned pool, and the summary counters. */
1476
+ read(options: ReadSeatingBoardOptions): Promise<KaafilResponse<SeatingBoardResponse>>;
1477
+ /**
1478
+ * `POST …/seating/assign` — puts one traveller in one vehicle (and, on a
1479
+ * seat-mapped vehicle, one seat). `vehicleId: null` moves them to the
1480
+ * unassigned pool. If the target seat is taken the occupant is displaced,
1481
+ * and the response names them in `displacedTravellerId` (plus
1482
+ * `displacedVehicleId`/`displacedSeatLabel`) rather than leaving the caller
1483
+ * to diff two boards to notice a second person moved.
1484
+ */
1485
+ assign(options: AssignSeatingOptions): Promise<KaafilResponse<SeatingAssignResponse>>;
1486
+ /**
1487
+ * `POST …/seating/auto-assign`. `dryRun: true` returns the plan without
1488
+ * writing it; `dryRun: false` returns the plan it actually applied. The two
1489
+ * are the SAME computation — the solver is pure and the only difference is
1490
+ * whether the result is also written — so a preview and its apply agree by
1491
+ * construction.
1492
+ *
1493
+ * Every rule reports an outcome in `perRule`: `applied`, `relaxed`, or
1494
+ * `noop` with `noopReason: 'no_seat_map'` — the third outcome seating has
1495
+ * that rooming does not, because `medicalFirst` and `gender` are
1496
+ * seat-adjacency heuristics with nothing to do on a seat-less vehicle. A
1497
+ * `noop` and a relaxed rule are different facts (a heuristic that could not
1498
+ * apply vs. one that applied a weaker version of itself), so both are
1499
+ * reported rather than one masquerading as the other.
1500
+ */
1501
+ autoAssign(options: AutoAssignSeatingOptions): Promise<KaafilResponse<SeatingAutoAssignResponse>>;
1502
+ readonly vehicles: SeatingVehiclesResource;
1503
+ }
1504
+
1505
+ /**
1506
+ * `shareTokens` — create, read, revoke. `architecture/08-sdk.md §3.3, §4`;
1507
+ * all three operations are `apiKeyAuth`-only in the spec (server-side mint,
1508
+ * matching `03-auth-and-tokens.md §1`: browsers only ever hold the *minted*
1509
+ * token, never the key that mints it).
1510
+ */
1511
+
1512
+ type MintShareTokenRequest = components['schemas']['MintShareTokenRequest'];
1513
+ type MintedShareTokenViewResponse = components['schemas']['MintedShareTokenViewResponse'];
1514
+ type ShareTokenViewResponse = components['schemas']['ShareTokenViewResponse'];
1515
+ interface CreateShareTokenOptions extends Omit<MintShareTokenRequest, 'expiresAt'> {
1516
+ /** Optional on the wire (`ttlDays` is the other way to set expiry) — see
1517
+ * `../datetime` for the coercion contract; a full ISO string with an
1518
+ * offset still passes through unchanged. */
1519
+ readonly expiresAt?: DateTimeInput;
1520
+ /** Stable across retries — reused as-is, never re-minted per attempt. */
1521
+ readonly idempotencyKey?: string;
1522
+ readonly signal?: AbortSignal;
1523
+ }
1524
+ interface ReadShareTokenOptions {
1525
+ readonly id: string;
1526
+ readonly signal?: AbortSignal;
1527
+ }
1528
+ interface RevokeShareTokenOptions {
1529
+ readonly id: string;
1530
+ readonly signal?: AbortSignal;
1531
+ }
1532
+ interface ShareTokensResource {
1533
+ /**
1534
+ * `POST /api/v1/share-tokens` — mints a config-scoped, expiring share
1535
+ * token. Expiry is top-level (`ttlDays` OR `expiresAt` on this options
1536
+ * object — never inside `config`); the server clamps it as documented on
1537
+ * `MintShareTokenRequest`. The CRM builds its own link around the returned
1538
+ * `token`; this SDK never constructs one.
1539
+ */
1540
+ create(options: CreateShareTokenOptions): Promise<KaafilResponse<MintedShareTokenViewResponse>>;
1541
+ /** `GET /api/v1/share-tokens/{id}` — a share token's metadata, never its plaintext value. */
1542
+ read(options: ReadShareTokenOptions): Promise<KaafilResponse<ShareTokenViewResponse>>;
1543
+ /** `DELETE /api/v1/share-tokens/{id}`. */
1544
+ revoke(options: RevokeShareTokenOptions): Promise<KaafilResponse<ShareTokenViewResponse>>;
1545
+ }
1546
+
1547
+ /**
1548
+ * `treks` — the boarding-day board, walk-ins, and postpone. `architecture/08-
1549
+ * sdk.md §4`. Deliberately small: board/close/reopen belong to `pickups`, and
1550
+ * this module contributes only trek POLICY on top of them.
1551
+ *
1552
+ * `board` and `walkIns.meta` accept a manager session OR an API key; `postpone`
1553
+ * and `walkIns.create` are `managerAuth`-only — a trek is worked and postponed
1554
+ * on the ground.
1555
+ *
1556
+ * ── THE `active` SENTINEL ────────────────────────────────────────────────────
1557
+ *
1558
+ * `trekRef` in these paths is a dual-mode identity like every other `ref` in
1559
+ * this SDK — Kaafil id or the CRM's external id — PLUS one reserved word,
1560
+ * `ACTIVE_TREK_REF` (`'active'`), which resolves to the manager's CURRENT
1561
+ * trek rather than being looked up as an id at all. `RESERVED_TREK_REF` in the
1562
+ * engine already guards the ingest direction (an external trip id can never
1563
+ * literally be `"active"`); this path position is a second, distinct place
1564
+ * the same reservation applies, and the engine's own docblock says a new path
1565
+ * position "must opt in explicitly" rather than inheriting the ingest guard
1566
+ * for free. Exposed here as a named constant rather than left for a caller to
1567
+ * type the string, so `treks.board({ trekRef: ACTIVE_TREK_REF })` reads as
1568
+ * "whichever trek this manager is on" at the call site instead of a magic
1569
+ * string that happens to mean something special.
1570
+ *
1571
+ * The consequence for a real external trip id: one literally equal to
1572
+ * `"active"` can NEVER be addressed through any of these four routes — it
1573
+ * would always resolve as the sentinel instead. That is a property of the
1574
+ * contract, not a bug in this SDK; a CRM minting external ids should treat
1575
+ * `"active"` as unusable here the same way it would a reserved keyword in any
1576
+ * other system.
1577
+ *
1578
+ * When no trek is currently assigned, `board` still answers `200` with an
1579
+ * `emptyState` block (`{ reason: 'no_trek_assigned', message }`) rather than
1580
+ * `404` — the manager stays where they tapped instead of hitting an error
1581
+ * screen. Every OTHER trek endpoint (`walkIns.*`, `postpone`) answers `404`
1582
+ * for "no such trek" the normal way.
1583
+ *
1584
+ * A trek endpoint reached on an `eventType: 'TRIP'` trip is `422 NOT_A_TREK`
1585
+ * — wrong kind, not missing. Unknown or cross-scope stays `404`, the same
1586
+ * not-found used everywhere else in this SDK (`../http/errors.ts`'s standing
1587
+ * argument for why there is exactly one not-found class applies here too).
1588
+ */
1589
+
1590
+ type TrekBoardResponse = components['schemas']['TrekBoardResponse'];
1591
+ type WalkInMetaResponse = components['schemas']['WalkInMetaResponse'];
1592
+ type WalkInResponse = components['schemas']['WalkInResponse'];
1593
+ type PostponeResponse = components['schemas']['PostponeResponse'];
1594
+ type CreateTrekWalkInRequest = components['schemas']['CreateTrekWalkInRequest'];
1595
+ type PostponeTrekRequest = components['schemas']['PostponeTrekRequest'];
1596
+ /**
1597
+ * The reserved `trekRef` value meaning "the manager's current trek" — see
1598
+ * this file's header. Never a real external trip id: the engine refuses to
1599
+ * ingest one equal to this string (`RESERVED_TREK_REF` in
1600
+ * `shared/ingest/external-ref.resolver.ts`), so no trip that reaches this SDK
1601
+ * can ever collide with it.
1602
+ */
1603
+ declare const ACTIVE_TREK_REF = "active";
1604
+ interface ReadTrekBoardOptions {
1605
+ /** A trek's dual-mode ref, or `ACTIVE_TREK_REF` for the manager's current
1606
+ * one — see this file's header. */
1607
+ readonly trekRef: string;
1608
+ readonly signal?: AbortSignal;
1609
+ }
1610
+ interface ReadTrekWalkInMetaOptions {
1611
+ readonly trekRef: string;
1612
+ readonly signal?: AbortSignal;
1613
+ }
1614
+ interface CreateTrekWalkInOptions extends CreateTrekWalkInRequest {
1615
+ readonly trekRef: string;
1616
+ readonly idempotencyKey?: string;
1617
+ readonly signal?: AbortSignal;
1618
+ }
1619
+ interface TrekWalkInsResource {
1620
+ /** `GET /api/v1/treks/{trekRef}/walk-ins/meta` — the open pickup points a
1621
+ * walk-in may be assigned to, plus field hints for the intake form. */
1622
+ meta(options: ReadTrekWalkInMetaOptions): Promise<KaafilResponse<WalkInMetaResponse>>;
1623
+ /**
1624
+ * `POST /api/v1/treks/{trekRef}/walk-ins` — mints a Kaafil-owned traveller
1625
+ * (no `externalId`) plus a `WalkIn` row, and upserts the manifest so
1626
+ * rooming, seating and this trek's own board see them immediately. Name and
1627
+ * phone live on the traveller, never on the walk-in row — one PII home.
1628
+ * Reconciliation against a later CRM upsert happens by normalised phone and
1629
+ * emits no event of its own; a walk-in on a `CLOSED` stop auto-reopens it
1630
+ * through `pickups.reopen` in-process, so the eventual re-close carries the
1631
+ * corrective event, not this call.
1632
+ */
1633
+ create(options: CreateTrekWalkInOptions): Promise<KaafilResponse<WalkInResponse>>;
1634
+ }
1635
+ interface PostponeTrekOptions extends Omit<PostponeTrekRequest, 'newStartDate' | 'newEndDate'> {
1636
+ readonly trekRef: string;
1637
+ /** Must be in the future AND after the trek's current `startDate` — a
1638
+ * postpone moves the trip forward, never back. `CANCELLED`/`COMPLETED`
1639
+ * trips answer `422 CANNOT_POSTPONE`; a write racing the close-out lock
1640
+ * answers `423 LOCKED`. */
1641
+ readonly newStartDate: DateTimeInput;
1642
+ readonly newEndDate: DateTimeInput;
1643
+ readonly idempotencyKey?: string;
1644
+ readonly signal?: AbortSignal;
1645
+ }
1646
+ interface TreksResource {
1647
+ /** `GET /api/v1/treks/{trekRef}/board` — stops with expected-vs-boarded,
1648
+ * the running head-count, and the current phase. See this file's header
1649
+ * for the sentinel and the no-trek `emptyState`. */
1650
+ board(options: ReadTrekBoardOptions): Promise<KaafilResponse<TrekBoardResponse>>;
1651
+ readonly walkIns: TrekWalkInsResource;
1652
+ /**
1653
+ * `POST /api/v1/treks/{trekRef}/postpone`. Shifts every `ItineraryDay` and
1654
+ * `StayWindow` by the same delta in ONE transaction; `sortOrder` and bed/seat
1655
+ * assignments are untouched throughout. Pickup `scheduledTime` is
1656
+ * DELIBERATELY NOT shifted — stop times are re-confirmed by the manager on
1657
+ * the ground, because they usually change with the new departure — so
1658
+ * `ripple` on the response reports itinerary and stay-window counts only,
1659
+ * never a pickup count. `postponedFromDate` on the response always holds the
1660
+ * FIRST original start date, even across a second postpone.
1661
+ */
1662
+ postpone(options: PostponeTrekOptions): Promise<KaafilResponse<PostponeResponse>>;
1663
+ }
1664
+
1665
+ /**
1666
+ * `trips` — upsert, get, cancel, plus `travellers`, `managers`, `balance`,
1667
+ * and the bulk push. `architecture/08-sdk.md §4`. All operations here are
1668
+ * `apiKeyAuth`-only in the spec (CRM-backend ingest, `03-auth-and-tokens.md
1669
+ * §1`).
1670
+ *
1671
+ * Every path parameter is a named field on its options object — never a
1672
+ * positional string — so a two-parameter route (`travellers.remove` takes a
1673
+ * trip ref AND a traveller ref; `managers.unassign` takes a trip ref AND a
1674
+ * manager ref) can't be called with its arguments swapped.
1675
+ */
1676
+
1677
+ type UpsertTripRequest = components['schemas']['UpsertTripRequest'];
1678
+ type TripUpsertResponse = components['schemas']['TripUpsertResponse'];
1679
+ type TripEchoResponse = components['schemas']['TripEchoResponse'];
1680
+ type TripCancelResponse = components['schemas']['TripCancelResponse'];
1681
+ type UpsertTravellerProfileRequest = components['schemas']['UpsertTravellerProfileRequest'];
1682
+ type TravellerUpsertResponse = components['schemas']['TravellerUpsertResponse'];
1683
+ type PushManifestRequest = components['schemas']['PushManifestRequest'];
1684
+ type ManifestPushResponse = components['schemas']['ManifestPushResponse'];
1685
+ type ManifestRemoveResponse = components['schemas']['ManifestRemoveResponse'];
1686
+ type UpsertManagerRequest = components['schemas']['UpsertManagerRequest'];
1687
+ type ManagerResponse = components['schemas']['ManagerResponse'];
1688
+ type AssignManagerRequest = components['schemas']['AssignManagerRequest'];
1689
+ type TripManagerResponse = components['schemas']['TripManagerResponse'];
1690
+ /** `PushTripBalancesRequest` is itself an array in the spec — there is no wrapper object. */
1691
+ type PushTripBalancesRow = components['schemas']['PushTripBalancesRequest'][number];
1692
+ type BalancePushResponse = components['schemas']['BalancePushResponse'];
1693
+ /** Likewise `PushBulkTripsRequest`. */
1694
+ type PushBulkTripsItem = components['schemas']['PushBulkTripsRequest'][number];
1695
+ type BulkTripsPushResponse = components['schemas']['BulkTripsPushResponse'];
1696
+ type UpsertTripSegment = NonNullable<UpsertTripRequest['segments']>[number];
1697
+ type UpsertTripSegmentInput = Omit<UpsertTripSegment, 'startDate' | 'endDate'> & {
1698
+ readonly startDate: DateTimeInput;
1699
+ readonly endDate: DateTimeInput;
1700
+ };
1701
+ type PushManifestTraveller = PushManifestRequest['travellers'][number];
1702
+ type PushManifestTravellerInput = Omit<PushManifestTraveller, 'sourceUpdatedAt'> & {
1703
+ readonly sourceUpdatedAt: DateTimeInput;
1704
+ };
1705
+ type PushBulkTripsTraveller = NonNullable<PushBulkTripsItem['travellers']>[number];
1706
+ type PushBulkTripsTravellerInput = Omit<PushBulkTripsTraveller, 'sourceUpdatedAt'> & {
1707
+ readonly sourceUpdatedAt: DateTimeInput;
1708
+ };
1709
+ type PushBulkTripsBalanceRow = NonNullable<PushBulkTripsItem['balance']>[number];
1710
+ type PushBulkTripsBalanceRowInput = Omit<PushBulkTripsBalanceRow, 'sourceUpdatedAt'> & {
1711
+ readonly sourceUpdatedAt: DateTimeInput;
1712
+ };
1713
+ type PushBulkTripsManager = NonNullable<PushBulkTripsItem['managers']>[number];
1714
+ type PushBulkTripsManagerInput = Omit<PushBulkTripsManager, 'sourceUpdatedAt'> & {
1715
+ readonly sourceUpdatedAt: DateTimeInput;
1716
+ };
1717
+ type PushBulkTripsItemInput = Omit<PushBulkTripsItem, 'startDate' | 'endDate' | 'sourceUpdatedAt' | 'travellers' | 'balance' | 'managers'> & {
1718
+ readonly startDate: DateTimeInput;
1719
+ readonly endDate: DateTimeInput;
1720
+ readonly sourceUpdatedAt: DateTimeInput;
1721
+ readonly travellers?: readonly PushBulkTripsTravellerInput[];
1722
+ readonly balance?: readonly PushBulkTripsBalanceRowInput[];
1723
+ readonly managers?: readonly PushBulkTripsManagerInput[];
1724
+ };
1725
+ interface UpsertTripOptions extends Omit<UpsertTripRequest, 'startDate' | 'endDate' | 'sourceUpdatedAt' | 'segments'> {
1726
+ readonly startDate: DateTimeInput;
1727
+ readonly endDate: DateTimeInput;
1728
+ /** The CRM's own record-updated timestamp, used for last-write-wins
1729
+ * staleness detection (`06-offline-first.md §6`). Deliberately still
1730
+ * required, with no "defaults to now" — defaulting it would make every
1731
+ * write look like the newest write and silently defeat the staleness
1732
+ * check on out-of-order delivery. Accepting a `Date` here (via
1733
+ * `DateTimeInput`) is the correct amount of help: the caller still
1734
+ * supplies the CRM's real timestamp, just without hand-formatting it. */
1735
+ readonly sourceUpdatedAt: DateTimeInput;
1736
+ readonly segments?: readonly UpsertTripSegmentInput[];
1737
+ readonly idempotencyKey?: string;
1738
+ readonly signal?: AbortSignal;
1739
+ }
1740
+ interface GetTripOptions {
1741
+ readonly tripRef: string;
1742
+ readonly signal?: AbortSignal;
1743
+ }
1744
+ interface CancelTripOptions {
1745
+ readonly tripRef: string;
1746
+ readonly idempotencyKey?: string;
1747
+ readonly signal?: AbortSignal;
1748
+ }
1749
+ interface UpsertTravellerProfileOptions extends Omit<UpsertTravellerProfileRequest, 'sourceUpdatedAt'> {
1750
+ /** The traveller's own ref — dual-mode identity (Kaafil id or the CRM's
1751
+ * `externalTravellerId`); resolution is the engine's job. */
1752
+ readonly travellerRef: string;
1753
+ /** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
1754
+ * required, never defaulted. */
1755
+ readonly sourceUpdatedAt: DateTimeInput;
1756
+ readonly idempotencyKey?: string;
1757
+ readonly signal?: AbortSignal;
1758
+ }
1759
+ interface PushManifestOptions extends Omit<PushManifestRequest, 'travellers'> {
1760
+ readonly tripRef: string;
1761
+ readonly travellers: readonly PushManifestTravellerInput[];
1762
+ readonly idempotencyKey?: string;
1763
+ readonly signal?: AbortSignal;
1764
+ }
1765
+ interface RemoveFromManifestOptions {
1766
+ readonly tripRef: string;
1767
+ readonly travellerRef: string;
1768
+ readonly idempotencyKey?: string;
1769
+ readonly signal?: AbortSignal;
1770
+ }
1771
+ interface TripTravellersResource {
1772
+ upsert(options: UpsertTravellerProfileOptions): Promise<KaafilResponse<TravellerUpsertResponse>>;
1773
+ pushManifest(options: PushManifestOptions): Promise<KaafilResponse<ManifestPushResponse>>;
1774
+ remove(options: RemoveFromManifestOptions): Promise<KaafilResponse<ManifestRemoveResponse>>;
1775
+ }
1776
+ interface UpsertManagerOptions extends Omit<UpsertManagerRequest, 'sourceUpdatedAt'> {
1777
+ /** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
1778
+ * required, never defaulted. */
1779
+ readonly sourceUpdatedAt: DateTimeInput;
1780
+ readonly idempotencyKey?: string;
1781
+ readonly signal?: AbortSignal;
1782
+ }
1783
+ interface AssignManagerOptions extends Omit<AssignManagerRequest, 'sourceUpdatedAt'> {
1784
+ readonly tripRef: string;
1785
+ /** See the comment on `UpsertTripOptions.sourceUpdatedAt` — same contract,
1786
+ * required, never defaulted. */
1787
+ readonly sourceUpdatedAt: DateTimeInput;
1788
+ readonly idempotencyKey?: string;
1789
+ readonly signal?: AbortSignal;
1790
+ }
1791
+ interface UnassignManagerOptions {
1792
+ readonly tripRef: string;
1793
+ readonly managerRef: string;
1794
+ readonly idempotencyKey?: string;
1795
+ readonly signal?: AbortSignal;
1796
+ }
1797
+ interface TripManagersResource {
1798
+ /** Not trip-scoped — `POST /api/v1/managers` upserts the manager entity
1799
+ * itself (dual-mode identity), independent of any one trip assignment. */
1800
+ upsert(options: UpsertManagerOptions): Promise<KaafilResponse<ManagerResponse>>;
1801
+ assign(options: AssignManagerOptions): Promise<KaafilResponse<TripManagerResponse>>;
1802
+ unassign(options: UnassignManagerOptions): Promise<KaafilResponse<TripManagerResponse>>;
1803
+ }
1804
+ type PushTripBalancesRowInput = Omit<PushTripBalancesRow, 'sourceUpdatedAt'> & {
1805
+ readonly sourceUpdatedAt: DateTimeInput;
1806
+ };
1807
+ interface PushTripBalancesOptions {
1808
+ readonly tripRef: string;
1809
+ readonly balances: readonly PushTripBalancesRowInput[];
1810
+ readonly idempotencyKey?: string;
1811
+ readonly signal?: AbortSignal;
1812
+ }
1813
+ interface TripBalanceResource {
1814
+ push(options: PushTripBalancesOptions): Promise<KaafilResponse<BalancePushResponse>>;
1815
+ }
1816
+ interface PushBulkTripsOptions {
1817
+ readonly trips: readonly PushBulkTripsItemInput[];
1818
+ readonly idempotencyKey?: string;
1819
+ readonly signal?: AbortSignal;
1820
+ }
1821
+ interface TripsBulkResource {
1822
+ /** `POST /api/v1/bulk/trips` — a batch of full trip bundles (trip +
1823
+ * manifest + balance + managers) in one call. */
1824
+ push(options: PushBulkTripsOptions): Promise<KaafilResponse<BulkTripsPushResponse>>;
1825
+ }
1826
+ interface TripsResource {
1827
+ upsert(options: UpsertTripOptions): Promise<KaafilResponse<TripUpsertResponse>>;
1828
+ get(options: GetTripOptions): Promise<KaafilResponse<TripEchoResponse>>;
1829
+ cancel(options: CancelTripOptions): Promise<KaafilResponse<TripCancelResponse>>;
1830
+ readonly travellers: TripTravellersResource;
1831
+ readonly managers: TripManagersResource;
1832
+ readonly balance: TripBalanceResource;
1833
+ readonly bulk: TripsBulkResource;
1834
+ }
1835
+
1836
+ /**
1837
+ * `webhooks` — the delivery operations only: list deliveries, read a
1838
+ * delivery, redeliver, replay. `architecture/08-sdk.md §4`, amended `C-028`:
1839
+ * "the delivery-facing operations … and `replayEndpoint` are API-key" —
1840
+ * `configure`/`rotateSecret` (console-session, endpoint CRUD) are a
1841
+ * different auth model entirely and are out of this lane's scope by the
1842
+ * brief's own instruction.
1843
+ *
1844
+ * `listWebhookDeliveries` is cursor-paginated (`cursor`+`limit` query params,
1845
+ * `meta.page.hasNext`); `deliveries.list()` returns a `KaafilPaginator` and
1846
+ * `deliveries.listPage()` is the manual single-page escape hatch for a
1847
+ * caller who wants to drive paging itself (`../pagination/paginator.ts`).
1848
+ */
1849
+
1850
+ type DeliveryResponse = components['schemas']['DeliveryResponse'];
1851
+ type DeliveryDetailResponse = components['schemas']['DeliveryDetailResponse'];
1852
+ type RedeliverResponse = components['schemas']['RedeliverResponse'];
1853
+ type ReplayResponse = components['schemas']['ReplayResponse'];
1854
+ type ReplayWebhookEndpointRequest = components['schemas']['ReplayWebhookEndpointRequest'];
1855
+ type ListWebhookDeliveriesQuery = NonNullable<operations['listWebhookDeliveries']['parameters']['query']>;
1856
+ /** The filterable half of `listWebhookDeliveries`'s query params — `cursor`
1857
+ * and `limit` are the paginator's job, never the caller's to hold. */
1858
+ type WebhookDeliveryFilters = Omit<ListWebhookDeliveriesQuery, 'cursor' | 'limit'>;
1859
+ interface ListWebhookDeliveriesOptions {
1860
+ readonly filters?: WebhookDeliveryFilters;
1861
+ /** Page size sent as the `limit` query param on every page this paginator fetches. */
1862
+ readonly limit?: number;
1863
+ }
1864
+ interface ListWebhookDeliveriesPageOptions extends WebhookDeliveryFilters {
1865
+ readonly cursor?: string;
1866
+ readonly limit?: number;
1867
+ readonly signal?: AbortSignal;
1868
+ }
1869
+ interface ReadWebhookDeliveryOptions {
1870
+ readonly id: string;
1871
+ readonly signal?: AbortSignal;
1872
+ }
1873
+ interface RedeliverWebhookDeliveryOptions {
1874
+ readonly id: string;
1875
+ readonly idempotencyKey?: string;
1876
+ readonly signal?: AbortSignal;
1877
+ }
1878
+ interface ReplayWebhookEndpointOptions extends ReplayWebhookEndpointRequest {
1879
+ /** The endpoint to replay against — `replayWebhookEndpoint`'s path
1880
+ * parameter is the webhook endpoint id, not a delivery id. */
1881
+ readonly endpointId: string;
1882
+ readonly idempotencyKey?: string;
1883
+ readonly signal?: AbortSignal;
1884
+ }
1885
+ interface WebhookDeliveriesResource {
1886
+ /** `GET /api/v1/webhooks/deliveries` as a `KaafilPaginator` — the SDK holds
1887
+ * the cursor; the consumer never sees or stores one. */
1888
+ list(options?: ListWebhookDeliveriesOptions): KaafilPaginator<DeliveryResponse, WebhookDeliveryFilters>;
1889
+ /** The manual single-page escape hatch: fetches exactly one page and
1890
+ * returns it directly, for a caller who wants to drive paging itself
1891
+ * rather than use the paginator `list()` returns. */
1892
+ listPage(options?: ListWebhookDeliveriesPageOptions): Promise<KaafilPagedResponse<readonly DeliveryResponse[]>>;
1893
+ read(options: ReadWebhookDeliveryOptions): Promise<KaafilResponse<DeliveryDetailResponse>>;
1894
+ redeliver(options: RedeliverWebhookDeliveryOptions): Promise<KaafilResponse<RedeliverResponse>>;
1895
+ }
1896
+ interface WebhooksResource {
1897
+ readonly deliveries: WebhookDeliveriesResource;
1898
+ /** `POST /api/v1/webhooks/endpoints/{id}/replay` — re-delivers every event
1899
+ * since `from` to one endpoint. */
1900
+ replay(options: ReplayWebhookEndpointOptions): Promise<KaafilResponse<ReplayResponse>>;
1901
+ }
1902
+
1903
+ /**
1904
+ * `Kaafil` — the server client, exported from `src/index.ts`
1905
+ * (`kaafil-js`). Constructed with the partner API key; carries every
1906
+ * resource group an API key can reach. `architecture/08-sdk.md §2, §3.1`.
1907
+ *
1908
+ * This module is the ONLY place an API-key credential is ever assembled and
1909
+ * checked — `guardApiKeyEnvironment`/`assertApiKeyMatchesEnvironment`
1910
+ * (`./config.ts`) and the `{ kind: 'apiKey', apiKey }` config object below.
1911
+ * `./client-entry.ts` (the browser entry) does not import this file, does
1912
+ * not import those two guards, and builds no credential of this shape —
1913
+ * see that file's header comment for the structural argument, not just a
1914
+ * convention, for why a bundler following only the browser entry's import
1915
+ * graph has no path to this one.
1916
+ */
1917
+
1918
+ interface KaafilOptions {
1919
+ /** `kf_live_…` / `kf_test_…`, checked against `environment` at
1920
+ * construction for a literal key, or on every resolution for a resolver
1921
+ * (`./config.ts`'s `guardApiKeyEnvironment`). */
1922
+ readonly apiKey: Resolvable<string>;
1923
+ readonly environment: Environment;
1924
+ /** Overrides the environment's default base URL outright — see
1925
+ * `./config.ts`'s `resolveBaseUrl` for why the two are never merged. */
1926
+ readonly baseUrl?: string;
1927
+ readonly timeoutMs?: number;
1928
+ readonly maxAttempts?: number;
1929
+ readonly userAgent?: string;
1930
+ readonly random?: () => number;
1931
+ }
1932
+ /**
1933
+ * Thrown by any resource method called after `close()`. Every resource
1934
+ * getter reads through `#requireState()`, so a stale reference to
1935
+ * `kaafil.trips` obtained before `close()` still throws this on its next
1936
+ * call — there is no way to keep using a closed client's credential.
1937
+ */
1938
+ declare class KaafilClosedError extends Error {
1939
+ constructor();
1940
+ }
1941
+ declare class Kaafil {
1942
+ #private;
1943
+ constructor(options: KaafilOptions);
1944
+ get auth(): AuthResource;
1945
+ get shareTokens(): ShareTokensResource;
1946
+ get trips(): TripsResource;
1947
+ get itinerary(): ItineraryResource;
1948
+ get rooming(): RoomingResource;
1949
+ get seating(): SeatingResource;
1950
+ get pickups(): PickupsResource;
1951
+ get treks(): TreksResource;
1952
+ get checklists(): ChecklistsResource;
1953
+ get vendors(): VendorsResource;
1954
+ get journey(): JourneyResource;
1955
+ get webhooks(): WebhooksResource;
1956
+ get events(): EventsResource;
1957
+ get files(): FilesResource;
1958
+ get collections(): CollectionsResource;
1959
+ get expenses(): ExpensesResource;
1960
+ get float(): FloatResource;
1961
+ /**
1962
+ * Total teardown. One assignment, no field to forget — see `KaafilState`
1963
+ * above. Every subsequent call through any resource getter throws
1964
+ * `KaafilClosedError` instead of quietly continuing to work off a
1965
+ * credential this client no longer considers live.
1966
+ */
1967
+ close(): void;
1968
+ /**
1969
+ * Mints an idempotency key outside the normal per-call flow. Every
1970
+ * mutating method already mints and reuses one automatically
1971
+ * (`http/client.ts`) — this exists for the rare case of pinning a key
1972
+ * ahead of time, to replay the exact same logical operation across a
1973
+ * process restart.
1974
+ */
1975
+ static newIdempotencyKey(): string;
1976
+ }
1977
+
1978
+ /**
1979
+ * The `?since=` delta cursor. `architecture/06-offline-first.md §1, §5`.
1980
+ *
1981
+ * ── WHY THE CURSOR IS THE SERVER'S OWN CLOCK, AND NOTHING ELSE ──────────────
1982
+ *
1983
+ * Every delta read on this API answers with `meta.serverTime`. That value —
1984
+ * the timestamp of the response you are holding, taken by the machine that ran
1985
+ * the query — is the ONLY correct cursor to send back as the next `?since=`.
1986
+ * The two tempting alternatives both lose rows, silently, and only under load:
1987
+ *
1988
+ * - **A local clock** (`new Date()` when the response arrived) is skewed
1989
+ * against the server's by an unknown amount and drifts. Skew in the wrong
1990
+ * direction advances the cursor past rows the server had not committed yet
1991
+ * when it answered, and those rows are then never in any window again.
1992
+ * - **A row's own `updatedAt`** (the newest row in the page) is worse: it is a
1993
+ * bound on the rows you RECEIVED, not on the interval the server READ. Any
1994
+ * row committed between the query's snapshot and the response being stamped
1995
+ * sits after your cursor and before the server's, and is lost permanently.
1996
+ * An empty page also leaves you with no cursor at all.
1997
+ *
1998
+ * This module exists so that reasoning happens once, here, instead of at every
1999
+ * integrator's call site. `pull()` takes the cursor from the response's own
2000
+ * `meta.serverTime` and nothing else, and it advances it only after a
2001
+ * successful read — a failed pull leaves the cursor exactly where it was, so
2002
+ * the next attempt re-reads the same interval rather than skipping it.
2003
+ *
2004
+ * ── WHY DUPLICATES ARE EXPECTED, AND WHY THEY ARE FREE ──────────────────────
2005
+ *
2006
+ * The server window is deliberately AT-LEAST-ONCE: it matches
2007
+ * `updatedAt >= since - 5s`, and a cursor in the future clamps to now rather
2008
+ * than erroring. So a pull normally re-delivers rows you already have. That is
2009
+ * the design, not a defect, and the asymmetry is the reason:
2010
+ *
2011
+ * - A DUPLICATED row costs nothing. Every row — live or tombstone — carries
2012
+ * `id` and `version`, so re-applying one is a no-op rebase onto the state
2013
+ * you already hold. `mergeDeltaRows` below does exactly that, by version.
2014
+ * - A SKIPPED row is DATA LOSS. It is a hotel booking, a cancelled activity or
2015
+ * a deleted bed that the offline client never learns about and never asks
2016
+ * for again, because the cursor has already moved past it. Nothing detects
2017
+ * it; the client is simply, quietly wrong.
2018
+ *
2019
+ * A strict `> since` boundary would be exactly that trade in reverse — no
2020
+ * duplicates, and permanent loss from three separate mechanisms (the interval
2021
+ * between reading rows and stamping the response, clock skew between replicas,
2022
+ * and millisecond truncation). The engine chose the overlap; a consumer must
2023
+ * not undo it by narrowing the window on the client side.
2024
+ *
2025
+ * ── WHY A DELTA ROW IS A UNION ──────────────────────────────────────────────
2026
+ *
2027
+ * A delta read returns `Row | DeltaTombstone` in ONE array, never a second
2028
+ * `deleted[]` array, so a consumer cannot forget the drop case: the union does
2029
+ * not typecheck until the tombstone branch is handled. Ordering is
2030
+ * `(updatedAt asc, id asc)`, so each id appears at most once per page and at
2031
+ * its final state — "deleted before or after updated" is already answered
2032
+ * server-side.
2033
+ */
2034
+
2035
+ /**
2036
+ * The drop marker on a delta read.
2037
+ *
2038
+ * Aliased off the generated schema rather than hand-restated. The spec uses
2039
+ * one tombstone shape for every delta read (itinerary items, rooming rooms,
2040
+ * rooming stay windows) and `ItineraryItemTombstoneResponse` is the single
2041
+ * place it appears as a NAMED component — the others inline it inside their
2042
+ * own `anyOf`, so there is no more general name to alias. If the engine ever
2043
+ * gives the shape its own component, this alias moves; the four fields are
2044
+ * still never typed by hand here.
2045
+ */
2046
+ type DeltaTombstone = components['schemas']['ItineraryItemTombstoneResponse'];
2047
+ /** One row from a delta read: the live row, or the marker saying it is gone. */
2048
+ type DeltaRow<TRow> = TRow | DeltaTombstone;
2049
+ /**
2050
+ * Narrows a delta row to the drop case.
2051
+ *
2052
+ * Structural, and provably safe rather than conventional: every response
2053
+ * schema in this contract is `additionalProperties: false`, so a live row can
2054
+ * never carry a `_tombstone` key and the two branches of the union are
2055
+ * disjoint.
2056
+ */
2057
+ declare function isTombstone<TRow extends object>(row: DeltaRow<TRow>): row is DeltaTombstone;
2058
+ /**
2059
+ * What `mergeDeltaRows` requires of a live row, and what makes a duplicate
2060
+ * free: an identity and a version. Both delta variants carry them.
2061
+ */
2062
+ interface DeltaIdentity {
2063
+ readonly id: string;
2064
+ readonly version: number;
2065
+ }
2066
+ /** What one `mergeDeltaRows` call actually changed. */
2067
+ interface DeltaMergeStats {
2068
+ /** Rows written that the store had not seen before. */
2069
+ readonly added: number;
2070
+ /** Rows written over an older version of a row already held. */
2071
+ readonly updated: number;
2072
+ /** Rows dropped because a tombstone arrived for them. */
2073
+ readonly removed: number;
2074
+ /**
2075
+ * Rows the store already held at this version or newer. This is the
2076
+ * at-least-once overlap doing its job — a non-zero count is normal on every
2077
+ * pull after the first and is not worth reporting to a user.
2078
+ */
2079
+ readonly unchanged: number;
2080
+ }
2081
+ /**
2082
+ * Rebases one delta page onto a local store, keyed by `id`.
2083
+ *
2084
+ * Version-guarded in both directions, which is what makes re-applying a
2085
+ * duplicated row a genuine no-op instead of merely a harmless-looking one: a
2086
+ * row whose `version` is not NEWER than the held one is left alone, and a
2087
+ * tombstone whose `version` is older than the held row is ignored rather than
2088
+ * deleting a row that has since been recreated or updated. The store is
2089
+ * mutated in place — a caller's `Map` is its own cache, and returning a fresh
2090
+ * copy per page would make a long offline catch-up quadratic.
2091
+ */
2092
+ declare function mergeDeltaRows<TRow extends DeltaIdentity>(store: Map<string, TRow>, rows: readonly DeltaRow<TRow>[]): DeltaMergeStats;
2093
+ /**
2094
+ * Runs one delta read for a given cursor. A caller supplies this as a thin
2095
+ * closure over a resource method — this module knows nothing about operation
2096
+ * ids, paths or query-param names, only that a read takes a `since` and
2097
+ * answers with an envelope carrying `meta.serverTime`.
2098
+ *
2099
+ * `since` is `undefined` on a cold start, which is a full read: the server
2100
+ * emits no tombstones without a cursor, because a client that has never
2101
+ * synced has nothing to drop.
2102
+ */
2103
+ type FetchDelta<T> = (request: {
2104
+ readonly since: string | undefined;
2105
+ readonly signal: AbortSignal | undefined;
2106
+ }) => Promise<KaafilResponse<T>>;
2107
+ /** One completed pull, with the cursor bookkeeping made visible. */
2108
+ interface DeltaPull<T> {
2109
+ readonly data: T;
2110
+ readonly meta: ResponseMeta;
2111
+ /** The cursor now stored: THIS response's `meta.serverTime`. */
2112
+ readonly cursor: string;
2113
+ /** What was sent as `?since=` — `undefined` on the cold start. */
2114
+ readonly previousCursor: string | undefined;
2115
+ }
2116
+ /** `pull()` was re-entered while a previous pull on the same cursor was in flight. */
2117
+ declare class KaafilDeltaCursorInFlightError extends Error {
2118
+ constructor();
2119
+ }
2120
+ interface KaafilDeltaCursor<T> {
2121
+ /**
2122
+ * The cursor that will be sent as `?since=` on the next `pull()`, or
2123
+ * `undefined` before the first successful one. Read this to PERSIST it; a
2124
+ * durable consumer stores it beside its rows and hands it back to
2125
+ * `restore()` after a restart, so a process death costs one overlapping
2126
+ * window rather than a full resync.
2127
+ */
2128
+ readonly cursor: string | undefined;
2129
+ readonly isLoading: boolean;
2130
+ /** Whether any pull has succeeded yet — i.e. whether the next one is a cold start. */
2131
+ readonly hasSynced: boolean;
2132
+ /**
2133
+ * Reads everything that changed since the stored cursor, then advances the
2134
+ * cursor to this response's own `meta.serverTime`. On failure the cursor is
2135
+ * left untouched, so a retry re-reads the same interval instead of stepping
2136
+ * over it.
2137
+ */
2138
+ pull(options?: {
2139
+ readonly signal?: AbortSignal;
2140
+ }): Promise<DeltaPull<T>>;
2141
+ /**
2142
+ * Adopts a cursor persisted from an earlier session. Takes a `string`
2143
+ * only — deliberately not a `Date` or an epoch number, the two things that
2144
+ * would let a local clock become a cursor, which is the failure this whole
2145
+ * module exists to prevent. Must be a value some response's
2146
+ * `meta.serverTime` actually carried.
2147
+ */
2148
+ restore(cursor: string): void;
2149
+ /**
2150
+ * Discards the cursor, so the next `pull()` is a cold start: a full read,
2151
+ * with no tombstones. The honest recovery when a local store is corrupt or
2152
+ * of unknown provenance — cheaper to re-read the trip than to reconcile a
2153
+ * cache you cannot vouch for.
2154
+ */
2155
+ reset(): void;
2156
+ }
2157
+ interface CreateDeltaCursorConfig<T> {
2158
+ readonly fetchDelta: FetchDelta<T>;
2159
+ /** A cursor persisted from an earlier session, as `restore()` would take it. */
2160
+ readonly cursor?: string;
2161
+ }
2162
+ declare function createDeltaCursor<T>(config: CreateDeltaCursorConfig<T>): KaafilDeltaCursor<T>;
2163
+
2164
+ /**
2165
+ * The rooming vocabulary a client needs to RENDER what
2166
+ * `rooming.autoAssign` and `rooming.read` answer — and which is deliberately
2167
+ * not on the wire.
2168
+ *
2169
+ * Two things live here, for the same reason: each is a property of the RULE or
2170
+ * of the CANON, not of a particular run, so shipping either inside every
2171
+ * response would put a second copy of one judgement in every payload and let
2172
+ * the two disagree after a contract change. The engine states this in
2173
+ * `src/modules/rooming/index.ts`, which re-exports both explicitly "for the
2174
+ * SDK". This file is that re-export's other end, so a client renders
2175
+ * relaxations and traveller chips without inventing its own severity map or
2176
+ * its own colour maths.
2177
+ *
2178
+ * ── HOW THIS STAYS HONEST ────────────────────────────────────────────────────
2179
+ *
2180
+ * These values are not in `openapi.json`, so `gen:check` cannot police them.
2181
+ * What does: both maps are typed as TOTAL `Record`s over GENERATED key types
2182
+ * (`RoomingHeuristicKey` and `Gender`, from `../generated/enums.ts`, which
2183
+ * `scripts/gen-meta.mjs` builds out of the vendored spec's own request enums).
2184
+ * Add a heuristic or a gender to the contract and re-vendor, and this file
2185
+ * stops compiling until someone decides what the new member's severity or hue
2186
+ * family is. A `Record<string, …>` here would have accepted the drift
2187
+ * silently, which is exactly the failure a hand-maintained mirror invites.
2188
+ */
2189
+
2190
+ /**
2191
+ * How loudly to render a rule's relaxation.
2192
+ *
2193
+ * - `warn` — the manager should look: a preference they set was given up.
2194
+ * - `split` — a group was broken up. Louder than `warn`; it is the complaint
2195
+ * the trip will actually generate.
2196
+ * - `info` — a soft preference, worth showing but not worth interrupting for.
2197
+ * - `never` — this rule is never relaxed at all. It exists so the map is TOTAL:
2198
+ * a missing entry would be indistinguishable from a rule nobody had
2199
+ * classified yet.
2200
+ */
2201
+ type RoomingHeuristicSeverity = 'warn' | 'split' | 'info' | 'never';
2202
+ /**
2203
+ * The frozen severity map, mirroring the engine's `HEURISTIC_SEVERITY`.
2204
+ *
2205
+ * `gender` is `'never'` because a gender rule is never relaxed — the traveller
2206
+ * stays unassigned instead, and turns up in `autoAssign`'s `unassigned[]` with
2207
+ * a reason. So a `perRule` entry for `gender` with `outcome: 'relaxed'` is not
2208
+ * something to render loudly; it is a contract violation, and a client is
2209
+ * entitled to treat it as a bug rather than as a user-facing warning.
2210
+ */
2211
+ declare const HEURISTIC_SEVERITY: Readonly<Record<RoomingHeuristicKey, RoomingHeuristicSeverity>>;
2212
+ /**
2213
+ * Whether a string is one of the heuristic keys this SDK knows about.
2214
+ *
2215
+ * Exists because `autoAssign`'s `perRule[].rule` is an open `string` on the
2216
+ * wire, not the closed enum `strategyOrder` is on the request side — so a
2217
+ * response may name a rule a given SDK build has never heard of, and a client
2218
+ * must be able to ask.
2219
+ */
2220
+ declare function isRoomingHeuristicKey(rule: string): rule is RoomingHeuristicKey;
2221
+ /**
2222
+ * How loudly to render one `perRule` entry's relaxation.
2223
+ *
2224
+ * ── WHY THIS FUNCTION EXISTS AND THE MAP IS NOT ENOUGH ──────────────────────
2225
+ *
2226
+ * `HEURISTIC_SEVERITY[entry.rule]` does not compile, and that is correct
2227
+ * rather than an oversight to paper over: `entry.rule` is `string` in the
2228
+ * contract, and a total `Record` over a closed key set legitimately refuses a
2229
+ * `string` index. The two possible fixes are not equivalent. Loosening the map
2230
+ * to `Record<string, …>` would also throw away the compile-time drift gate
2231
+ * described in this file's header, which is the only thing keeping this
2232
+ * hand-maintained mirror honest against a re-vendored spec. So the map stays
2233
+ * total and this accessor takes the wire's `string`.
2234
+ *
2235
+ * Returns `undefined` for a rule this build does not know. A caller should
2236
+ * render that entry rather than drop it — an unrecognised rule that was
2237
+ * RELAXED is still a relaxation the manager needs to see, and hiding it
2238
+ * because the severity lookup missed is the silent-relaxation failure the
2239
+ * `perRule` array exists to prevent. `'info'` is a reasonable default to draw
2240
+ * it at; `undefined` is deliberately not pre-defaulted here, so that choice is
2241
+ * visible at the call site instead of buried in this function.
2242
+ */
2243
+ declare function roomingHeuristicSeverity(rule: string): RoomingHeuristicSeverity | undefined;
2244
+ /**
2245
+ * The hue FAMILIES a `tone` token is built from, keyed by the occupant's
2246
+ * `gender`.
2247
+ *
2248
+ * `null` gender and `UNKNOWN` map to the SAME family: the field is nullable
2249
+ * AND the enum has an `UNKNOWN` member, so unknown-ness arrives two ways and
2250
+ * means one thing. Rendering them in two palettes would show the same fact
2251
+ * twice over as two different facts.
2252
+ */
2253
+ declare const TONE_FAMILIES: Readonly<Record<Gender, string>>;
2254
+ /** The family for a `null` gender. Identical to `UNKNOWN`'s, by the reasoning above. */
2255
+ declare const UNKNOWN_TONE_FAMILY: string;
2256
+ /**
2257
+ * How many shades each family has, so a client's palette knows how many to
2258
+ * define: `shade` in a `tone` token is always `0 .. SHADES_PER_FAMILY - 1`.
2259
+ *
2260
+ * It equals the maximum room capacity, and that is the whole argument. The
2261
+ * promise is that two same-gender travellers IN ONE ROOM still look different —
2262
+ * a room being the only place two chips sit side by side and have to be told
2263
+ * apart. A room holds at most 8 people, so with 8 shades per family a
2264
+ * collision is never FORCED inside that scope. It is frozen rather than
2265
+ * configurable because the mark is a property of the TRAVELLER, not of the
2266
+ * viewer: two agencies with different values would render the same person in
2267
+ * two different shades, and `transport-seating` — which draws the same chip
2268
+ * from the same canon — would have to know whose grid it was on to agree with
2269
+ * the rooming board.
2270
+ */
2271
+ declare const SHADES_PER_FAMILY = 8;
2272
+ /**
2273
+ * The glyph the engine sends for a traveller whose `fullName` has no
2274
+ * renderable characters at all. Reachable, because the name is CRM-supplied,
2275
+ * and an unnameable traveller must still get a chip rather than vanish off the
2276
+ * board.
2277
+ */
2278
+ declare const FALLBACK_GLYPH = "?";
2279
+ /** A parsed `tone` token. */
2280
+ interface RoomingTone {
2281
+ /** One of `TONE_FAMILIES`' values. */
2282
+ readonly family: string;
2283
+ /** `0 .. SHADES_PER_FAMILY - 1`. */
2284
+ readonly shade: number;
2285
+ }
2286
+ /**
2287
+ * Splits an occupant's `tone` into its family and shade.
2288
+ *
2289
+ * ── `tone` IS A TOKEN, NEVER A COLOUR ───────────────────────────────────────
2290
+ *
2291
+ * The engine sends `"male.3"`, not `"#3B82F6"`, and that is deliberate: the
2292
+ * engine must not own brand colour values. If it did, an agency re-theme would
2293
+ * be an engine deploy, and the same traveller would render in two different
2294
+ * palettes depending on which module's copy of the hex table was newer. What
2295
+ * the engine owns is the CONSISTENCY claim — `rooming` and `transport-seating`
2296
+ * handing you the same string for the same person IS the consistency —
2297
+ * and what you paint it is entirely yours. So the shape a consumer wants is
2298
+ * `palette[family][shade]`, with `palette` living in your own design system,
2299
+ * and this function is how you get the two indices out without string-slicing
2300
+ * by hand.
2301
+ *
2302
+ * Returns `undefined` rather than throwing for anything that is not a
2303
+ * recognisable token, because this runs while drawing a board: a chip with an
2304
+ * unreadable tone should fall back to a neutral swatch, not take the screen
2305
+ * down. The family is NOT checked against `TONE_FAMILIES` — a family added to
2306
+ * the contract before your palette knows about it should still parse, so you
2307
+ * can fall back on the family you do not recognise instead of on every chip.
2308
+ */
2309
+ declare function parseRoomingTone(tone: string): RoomingTone | undefined;
2310
+
2311
+ export { ACTIVE_TREK_REF, type AddChecklistItemOptions, type AddItineraryItemOptions, type AdjustFloatOptions, type AssignManagerOptions, type AssignPickupTravellerOptions, type AssignRoomingBedOptions, type AssignSeatingOptions, type AuthResource, type AutoAssignRoomingOptions, type AutoAssignSeatingOptions, type BoardPickupTravellerOptions, type CancelTripOptions, type ChecklistItemDeltaRow, type ChecklistItemsResource, type ChecklistTemplatesResource, type ChecklistsResource, type ClosePickupStopOptions, type CollectionDeltaRow, type CollectionsResource, type ConfirmFileUploadOptions, type CreateDeltaCursorConfig, type CreatePickupStopOptions, type CreateRoomingRoomOptions, type CreateRoomingStayWindowOptions, type CreateSeatingVehicleOptions, type CreateShareTokenOptions, type CreateTrekWalkInOptions, type DateTimeInput, type DeleteChecklistItemOptions, type DeleteItineraryItemOptions, type DeletePickupStopOptions, type DeleteRoomingRoomOptions, type DeleteRoomingStayWindowOptions, type DeleteSeatingVehicleOptions, type DeltaIdentity, type DeltaMergeStats, type DeltaPull, type DeltaRow, type DeltaTombstone, Environment, type EventFilters, type EventsResource, type ExpenseClaimsResource, type ExpenseFilters, type ExpensesResource, FALLBACK_GLYPH, type FetchDelta, type FilesResource, type FloatResource, Gender, type GetTripOptions, HEURISTIC_SEVERITY, type IngestClaimStatusOptions, type IssueFloatOptions, type ItineraryChangeLogResource, type ItineraryDaysResource, type ItineraryItemDeltaRow, type ItineraryItemsResource, type ItineraryResource, JourneyResource, Kaafil, KaafilClosedError, type KaafilDeltaCursor, KaafilDeltaCursorInFlightError, type KaafilOptions, KaafilPagedResponse, KaafilPaginator, KaafilResponse, type LinkExpenseReceiptOptions, type LinkSeatingManagerOptions, type ListChecklistTemplatesOptions, type ListCollectionsOptions, type ListEligibleCollectionsOptions, type ListEventsOptions, type ListEventsPageOptions, type ListExpensesOptions, type ListItineraryChangeLogOptions, type ListPickupStopsOptions, type ListRoomingStayWindowsOptions, type ListWebhookDeliveriesOptions, type ListWebhookDeliveriesPageOptions, type LogExpenseOptions, type MintAgencyAdminTokenOptions, type MintManagerTokenOptions, type PatchChecklistItemOptions, type PatchItineraryDayOptions, type PatchItineraryItemOptions, type PatchPickupStopOptions, type PatchRoomingRoomOptions, type PatchRoomingStayWindowOptions, type PatchSeatingVehicleOptions, type PickupStopDeltaRow, type PickupsResource, type PostponeTrekOptions, type PullChecklistTemplateOptions, type PushBulkTripsOptions, type PushManifestOptions, type PushTripBalancesOptions, type ReadChecklistOptions, type ReadExpenseOptions, type ReadFileOptions, type ReadFileUrlOptions, type ReadFloatLedgerOptions, type ReadFloatSummaryOptions, type ReadItineraryOptions, type ReadManifestByPickupOptions, type ReadRoomingBoardOptions, type ReadSeatingBoardOptions, type ReadShareTokenOptions, type ReadTrekBoardOptions, type ReadTrekWalkInMetaOptions, type ReadWebhookDeliveryOptions, type RecordCollectionOptions, type RedeliverWebhookDeliveryOptions, type RemoveFromManifestOptions, type ReopenPickupStopOptions, type ReorderItineraryItemOptions, type ReorderPickupStopsOptions, type ReplayWebhookEndpointOptions, type RequestFileUploadOptions, Resolvable, ResponseMeta, type ReturnFloatOptions, type RevokeShareTokenOptions, type RoomingAutoAssignRules, RoomingHeuristicKey, type RoomingHeuristicSeverity, type RoomingOccupant, type RoomingResource, type RoomingRoomDeltaRow, type RoomingRoomsResource, type RoomingStayWindowsResource, type RoomingTone, SHADES_PER_FAMILY, type SeatingAutoAssignRules, type SeatingOccupant, type SeatingResource, type SeatingVehicleDeltaRow, type SeatingVehicleManagerResource, type SeatingVehiclesResource, type ShareTokensResource, type SubmitExpenseClaimOptions, TONE_FAMILIES, type ToggleChecklistItemOptions, type TrekWalkInsResource, type TreksResource, type TripBalanceResource, type TripManagersResource, type TripTravellersResource, type TripsBulkResource, type TripsResource, UNKNOWN_TONE_FAMILY, type UnassignManagerOptions, type UnlinkSeatingManagerOptions, type UpsertManagerOptions, type UpsertTravellerProfileOptions, type UpsertTripOptions, VendorsResource, type VoidCollectionOptions, type VoidExpenseOptions, type WebhookDeliveriesResource, type WebhookDeliveryFilters, type WebhooksResource, type WithdrawExpenseClaimOptions, createDeltaCursor, isRoomingHeuristicKey, isTombstone, mergeDeltaRows, parseRoomingTone, roomingHeuristicSeverity };