slotlock 0.8.0 → 0.9.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/agent-server.d.ts +25 -5
  3. package/agent-server.js +152 -7
  4. package/agent-store-backend.d.ts +6 -1
  5. package/agent-store-backend.js +861 -55
  6. package/application-api.d.ts +19 -1
  7. package/application-api.js +578 -21
  8. package/approvals.d.ts +2 -0
  9. package/approvals.js +12 -5
  10. package/assistant.d.ts +34 -8
  11. package/assistant.js +81 -26
  12. package/availability-store.d.ts +193 -0
  13. package/availability-store.js +558 -0
  14. package/booking-rules.d.ts +92 -5
  15. package/booking-rules.js +243 -15
  16. package/dashboard-assistant-ui.d.ts +1 -1
  17. package/dashboard-assistant-ui.js +109 -8
  18. package/dashboard.d.ts +55 -6
  19. package/dashboard.js +790 -105
  20. package/ddl.d.ts +5 -1
  21. package/ddl.js +760 -9
  22. package/event-feed.d.ts +1 -1
  23. package/event-feed.js +18 -0
  24. package/holiday-import.d.ts +38 -0
  25. package/holiday-import.js +57 -0
  26. package/holidays.d.ts +38 -0
  27. package/holidays.js +150 -0
  28. package/hours.d.ts +125 -0
  29. package/hours.js +224 -0
  30. package/index.d.ts +14 -3
  31. package/index.js +10 -2
  32. package/interop.d.ts +25 -7
  33. package/interop.js +145 -17
  34. package/lifecycle.d.ts +31 -0
  35. package/lifecycle.js +115 -0
  36. package/maintenance.d.ts +19 -0
  37. package/maintenance.js +55 -1
  38. package/metrics.js +3 -0
  39. package/openapi.json +16237 -7795
  40. package/package.json +1 -1
  41. package/ranges.d.ts +28 -0
  42. package/ranges.js +135 -0
  43. package/resource-search.d.ts +19 -0
  44. package/resource-search.js +153 -0
  45. package/rest-api.js +7 -0
  46. package/rules.d.ts +2 -0
  47. package/rules.js +2 -0
  48. package/self-host.d.ts +1 -1
  49. package/self-host.js +221 -21
  50. package/slotlock.manifest.json +1 -1
  51. package/store.d.ts +367 -24
  52. package/store.js +1567 -97
  53. package/types.d.ts +198 -0
package/store.d.ts CHANGED
@@ -3,10 +3,13 @@ import { type SlotlockUsageRow } from './usage.js';
3
3
  import { type SlotlockChangeOrigin, type SlotlockEventFeed } from './event-feed.js';
4
4
  import { type SlotlockBookingRules } from './booking-rules.js';
5
5
  import type { Sql, TransactionSql } from 'postgres';
6
- import { SLOTLOCK_CALENDAR_HORIZON_DAYS } from './rules.js';
7
- import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockBookingDescription, SlotlockApproval, SlotlockApprovalPolicy, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
6
+ import { SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX } from './rules.js';
7
+ import type { AcquireHoldResult, MarkHoldPaidResult, PutContactResult, SlotlockContact, SlotlockContactConsent, CalendarCoverageCommand, CalendarCoverageResult, SourceBusyCommand, SourceBusyResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockBookingDescription, SlotlockApproval, SlotlockApprovalPolicy, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
8
+ import { type SlotlockAvailabilityDefaults, type SlotlockAvailabilityDefaultsResult, type SlotlockAvailabilityExceptionInput, type SlotlockOverrideResources, type SlotlockHolidayCalendar, type SlotlockHolidayImport } from './availability-store.js';
9
+ import type { SlotlockAvailabilityException, SlotlockRankedException } from './hours.js';
10
+ import type { SlotlockHoliday } from './holidays.js';
8
11
  import { type PutApprovalPolicyParams } from './approvals.js';
9
- export { SLOTLOCK_CALENDAR_HORIZON_DAYS };
12
+ export { SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX };
10
13
  /**
11
14
  * Longest one-off event (ten years and some leap days); a recurring series is bounded by its
12
15
  * materialization window instead.
@@ -69,6 +72,10 @@ export declare function readSlotlockSchemaVersion(sql: SlotlockSql): Promise<num
69
72
  * migrate`) or `schema_newer`. Both carry `installed` and `expected`.
70
73
  */
71
74
  export declare function assertSlotlockSchemaCurrent(sql: SlotlockSql): Promise<void>;
75
+ /** The most contacts a tenant keeps (RFC 0004 P3). */
76
+ export declare const SLOTLOCK_MAX_CONTACTS_PER_TENANT = 100000;
77
+ /** The longest a hold that waits for payment may live (RFC 0004 C3): 30 minutes. */
78
+ export declare const SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS: number;
72
79
  /**
73
80
  * A put's identity and the event it asks for, not its storage window: a series' window moves with
74
81
  * the UTC day, so a retry after midnight must still match.
@@ -405,11 +412,132 @@ export interface SlotlockStore extends SlotlockEventFeed {
405
412
  pool: string;
406
413
  resourceId: string;
407
414
  }): Promise<void>;
408
- /** Set (or clear, with `null`) the bookable hours of every resource the tenant has; returns how many. */
415
+ /**
416
+ * Store (or clear, with `null`) the tenant's weekly hours (RFC 0004 B2): every resource without its
417
+ * own and with no location hours above it uses them, new ones too. Nothing is copied: a resource
418
+ * keeps hours of its own unless `overrideResources` clears them (`all`), or clears those that are
419
+ * the same as the tenant's were or as these (`matching`). Refuses `invalid_availability`.
420
+ */
421
+ storeTenantAvailability(params: {
422
+ tenantRef: string;
423
+ rules: readonly WeeklyAvailabilityRule[] | null;
424
+ overrideResources?: SlotlockOverrideResources;
425
+ }): Promise<SlotlockAvailabilityDefaultsResult>;
426
+ /**
427
+ * Every resource the tenant has uses these weekly hours (or the server's, with `null`): as in 0.8.0,
428
+ * whatever hours of their own they had are gone. Now done by storing them as the tenant's, which
429
+ * resources added later inherit too. Returns how many resources in use they now apply to.
430
+ *
431
+ * @deprecated Use storeTenantAvailability, which can keep the hours resources have of their own
432
+ * and says how many do.
433
+ */
409
434
  setTenantAvailability(params: {
410
435
  tenantRef: string;
411
436
  rules: readonly WeeklyAvailabilityRule[] | null;
412
437
  }): Promise<number>;
438
+ /**
439
+ * Store (or clear) a location's weekly hours: resources at it or inside it without their own use
440
+ * them, unless a nearer location has its own. Null for a location the tenant does not have.
441
+ */
442
+ setLocationAvailability(params: {
443
+ tenantRef: string;
444
+ location: string;
445
+ rules: readonly WeeklyAvailabilityRule[] | null;
446
+ }): Promise<SlotlockAvailabilityDefaultsResult | null>;
447
+ /** The tenant's stored hours, and each location's that has them. */
448
+ getAvailabilityDefaults(params: {
449
+ tenantRef: string;
450
+ }): Promise<SlotlockAvailabilityDefaults>;
451
+ /**
452
+ * Add a closure or a day of special hours (RFC 0004 B1) for the tenant, a location or a resource;
453
+ * an import's `sourceUid` already stored at that scope is updated instead. Refuses
454
+ * `invalid_availability_exception`, `location_not_found`, `resource_not_found` and
455
+ * `availability_exceptions_limit_reached`.
456
+ */
457
+ createAvailabilityException(params: SlotlockAvailabilityExceptionInput): Promise<SlotlockAvailabilityException>;
458
+ /**
459
+ * The tenant's exceptions by first day: those that apply to `resourceId`, or a `location`'s own,
460
+ * or all; only those reaching `from` to `to` when given.
461
+ */
462
+ listAvailabilityExceptions(params: {
463
+ tenantRef: string;
464
+ resourceId?: string;
465
+ location?: string;
466
+ from?: string;
467
+ to?: string;
468
+ limit?: number;
469
+ after?: string;
470
+ }): Promise<SlotlockAvailabilityException[]>;
471
+ deleteAvailabilityException(params: {
472
+ tenantRef: string;
473
+ id: string;
474
+ }): Promise<boolean>;
475
+ /**
476
+ * Replace the exceptions an import of one resource's VAVAILABILITY made (source `vavailability`)
477
+ * with `ranges`; returns how many it keeps.
478
+ */
479
+ replaceImportedExceptions(params: {
480
+ tenantRef: string;
481
+ resourceId: string;
482
+ ranges: readonly {
483
+ startsOn: string;
484
+ endsOn: string;
485
+ outcome: 'closed' | 'open';
486
+ hours?: {
487
+ startMinutes: number;
488
+ durationMinutes: number;
489
+ }[];
490
+ }[];
491
+ }): Promise<number>;
492
+ /**
493
+ * Import a holiday calendar's days off as closures at its scope, as the calendar named `name`
494
+ * there (RFC 0004 B1): holidays added or updated, and those it made before and no longer names
495
+ * in `span` deleted. With `url`, a subscription maintenance imports again. See
496
+ * availability-store.ts importHolidayCalendar for its refusals.
497
+ */
498
+ importHolidayCalendar(params: {
499
+ tenantRef: string;
500
+ location?: string;
501
+ resourceId?: string;
502
+ name: string;
503
+ url?: string;
504
+ holidays: readonly SlotlockHoliday[];
505
+ span: {
506
+ from: string;
507
+ to: string;
508
+ };
509
+ }): Promise<SlotlockHolidayImport>;
510
+ /** The tenant's holiday calendars. */
511
+ listHolidayCalendars(params: {
512
+ tenantRef: string;
513
+ }): Promise<SlotlockHolidayCalendar[]>;
514
+ /** Delete a holiday calendar and its closures; null for one the tenant does not have. */
515
+ deleteHolidayCalendar(params: {
516
+ tenantRef: string;
517
+ id: string;
518
+ }): Promise<{
519
+ closures: number;
520
+ } | null>;
521
+ /** Subscriptions due to be imported again, the longest waiting first. */
522
+ dueHolidayCalendars(params: {
523
+ tenantRef: string;
524
+ limit: number;
525
+ }): Promise<SlotlockHolidayCalendar[]>;
526
+ /** Note why a subscription's import failed; it is tried again a day later. */
527
+ noteHolidayCalendarFailure(params: {
528
+ tenantRef: string;
529
+ id: string;
530
+ error: string;
531
+ }): Promise<void>;
532
+ /**
533
+ * Each resource's exceptions that reach `window`, in the order the evaluator applies them, by
534
+ * resource id.
535
+ */
536
+ availabilityExceptionsFor(params: {
537
+ tenantRef: string;
538
+ resourceIds: readonly string[];
539
+ window: Interval;
540
+ }): Promise<Map<string, SlotlockRankedException[]>>;
413
541
  /** Bounded tenant resource discovery, ordered and keyset-paginated by immutable UUID. */
414
542
  listResources(params: {
415
543
  tenantRef: string;
@@ -483,6 +611,12 @@ export interface SlotlockStore extends SlotlockEventFeed {
483
611
  tenantRef: string;
484
612
  externalRef: string;
485
613
  revision: number;
614
+ /**
615
+ * An admin's reason for cancelling past the booking's cancel cutoff (RFC 0004 B5), 1-500
616
+ * characters, kept on its tombstone and the feed. Without it, a cancellation past the cutoff is
617
+ * refused.
618
+ */
619
+ overrideReason?: string;
486
620
  }): Promise<CancelExternalReservationResult>;
487
621
  /**
488
622
  * Occupy a window without committing: the hold blocks rivals under the same EXCLUDE arbiter until
@@ -526,7 +660,77 @@ export interface SlotlockStore extends SlotlockEventFeed {
526
660
  * `fromHold` releases the rest, and releasing one with a holder releases them all.
527
661
  */
528
662
  groupId?: string;
663
+ /**
664
+ * The hold waits for payment (RFC 0004 C3): it cannot be confirmed until `markHoldPaid`
665
+ * records the payment's reference, and lives at most SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS
666
+ * (`invalid_ttl` beyond). Slotlock records payment state; it never moves money.
667
+ */
668
+ paymentRequired?: boolean;
669
+ /**
670
+ * What the payment must be (with `paymentRequired`): markHoldPaid refuses one for less, or in
671
+ * another currency (`payment_amount_mismatch`). Without it, any recorded payment counts.
672
+ */
673
+ payment?: {
674
+ amountMinor: number;
675
+ currency: string;
676
+ };
529
677
  }): Promise<AcquireHoldResult>;
678
+ /**
679
+ * Record that a hold waiting for payment is paid (RFC 0004 C3), by the payment's reference at the
680
+ * integrator's processor: the integrator says so, or an adapter that verified it. Once paid it
681
+ * may be confirmed. The same reference again answers the same hold, whatever became of it since
682
+ * (confirmed, booked, expired: the payment is kept apart); another is `payment_conflict`. The feed says so (`hold.paid`). A hold that does not wait for payment is
683
+ * `payment_not_required`; an expired one `hold_expired`; one confirmed, released or unknown
684
+ * `hold_not_found`.
685
+ */
686
+ markHoldPaid(params: {
687
+ tenantRef: string;
688
+ holdId: string;
689
+ paymentRef: string;
690
+ /** What was received, as the processor says: needed when the hold names its payment. */
691
+ received?: {
692
+ amountMinor: number;
693
+ currency: string;
694
+ };
695
+ }): Promise<MarkHoldPaidResult>;
696
+ /**
697
+ * Keep a person a booking may name (`contactRef`) to be told about it (RFC 0004 P3): how to reach
698
+ * them and which channels they agreed to. Replaces what was kept under the reference. A sender
699
+ * reads it to send; Slotlock sends nothing. At most SLOTLOCK_MAX_CONTACTS_PER_TENANT.
700
+ */
701
+ putContact(params: {
702
+ tenantRef: string;
703
+ ref: string;
704
+ name?: string | null;
705
+ email?: string | null;
706
+ phone?: string | null;
707
+ whatsapp?: string | null;
708
+ consent?: SlotlockContactConsent;
709
+ locale?: string | null;
710
+ }): Promise<PutContactResult>;
711
+ /** The tenant's contact with this reference, or null. */
712
+ getContact(params: {
713
+ tenantRef: string;
714
+ ref: string;
715
+ }): Promise<SlotlockContact | null>;
716
+ /** The tenant's contacts by reference, after `after`, up to `limit` (1-100, default 50). */
717
+ listContacts(params: {
718
+ tenantRef: string;
719
+ limit?: number;
720
+ after?: string;
721
+ }): Promise<SlotlockContact[]>;
722
+ /**
723
+ * Forget a person (RFC 0004 P3): their contact, and their reference on every booking that named
724
+ * it, in one transaction. `deleted` is false when there was no contact; `bookings` counts the
725
+ * bookings that no longer name them.
726
+ */
727
+ deleteContact(params: {
728
+ tenantRef: string;
729
+ ref: string;
730
+ }): Promise<{
731
+ deleted: boolean;
732
+ bookings: number;
733
+ }>;
530
734
  /** One of the tenant's live holds, or null (none, expired, confirmed, or another tenant's). */
531
735
  getHold(params: {
532
736
  tenantRef: string;
@@ -814,6 +1018,8 @@ export interface SlotlockStore extends SlotlockEventFeed {
814
1018
  * another, checked under the event's lock.
815
1019
  */
816
1020
  expectedResourceId?: string;
1021
+ /** Hold a one-off event to its resource's cancel cutoff (RFC 0004 B5), as agents' cancels are. */
1022
+ enforceBookingRules?: boolean;
817
1023
  }): Promise<CancelCalendarEventResult>;
818
1024
  /**
819
1025
  * The tenant's cancellation of one of its events by id, whichever owner wrote it: an agent key's
@@ -829,6 +1035,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
829
1035
  expectedRevision: number;
830
1036
  /** As for cancelCalendarEvent: `event_not_found` when the event is on another resource. */
831
1037
  expectedResourceId?: string;
1038
+ /**
1039
+ * An admin's reason for cancelling a one-off event past its cancel cutoff (RFC 0004 B5), kept on
1040
+ * the feed; without it, that cancellation is refused.
1041
+ */
1042
+ overrideReason?: string;
832
1043
  }): Promise<CancelCalendarEventResult>;
833
1044
  /**
834
1045
  * Rematerialize due recurring masters into one rolling window without changing their revision. A
@@ -921,6 +1132,17 @@ export interface SlotlockStore extends SlotlockEventFeed {
921
1132
  maxAgeMs?: number;
922
1133
  limit?: number;
923
1134
  }): Promise<number>;
1135
+ /**
1136
+ * Put each event reminder that has come due on the feed once (RFC 0004 P3), as `reminder.due`:
1137
+ * an occurrence's reminder, its minutes before the start, of an event that may be notified about
1138
+ * (`notify`), due within the last day and not started yet. Each is recorded in
1139
+ * `reminder_dispatches` in the same transaction, so no later pass says it again. The change's id
1140
+ * is the key a sender sends it once by. Up to `limit` (default 500); how many.
1141
+ */
1142
+ dueReminders(params: {
1143
+ tenantRef: string;
1144
+ limit?: number;
1145
+ }): Promise<number>;
924
1146
  /**
925
1147
  * Claim a booking by its reference (check-in), so a resource whose booking rules ask for check-in
926
1148
  * (`checkInMinutes`) does not release it. Check-in opens `SLOTLOCK_CHECK_IN_OPENS_MINUTES` before
@@ -932,6 +1154,15 @@ export interface SlotlockStore extends SlotlockEventFeed {
932
1154
  tenantRef: string;
933
1155
  externalRef: string;
934
1156
  }): Promise<CheckInReservationResult>;
1157
+ /**
1158
+ * Say on the feed that bookings have ended (RFC 0004 B4): each confirmed booking whose end passed
1159
+ * within the last week and was not said yet gets one `reservation.completed`, its row unchanged
1160
+ * but for when it was said. Up to `limit` (default 500); returns how many.
1161
+ */
1162
+ completeEndedBookings(params: {
1163
+ tenantRef: string;
1164
+ limit?: number;
1165
+ }): Promise<number>;
935
1166
  /**
936
1167
  * Release up to `limit` (default 100) bookings left unclaimed: on a resource whose booking rules
937
1168
  * ask for check-in, made since they did, not checked in `checkInMinutes` after the start, and not
@@ -956,12 +1187,44 @@ export interface SlotlockStore extends SlotlockEventFeed {
956
1187
  end: Date;
957
1188
  holderRef: string;
958
1189
  offerTtlMs?: number;
1190
+ /**
1191
+ * Wait for any slot of this many milliseconds within the window (RFC 0004 B6), a whole minute
1192
+ * from one minute to the window's length; left out, the whole window.
1193
+ */
1194
+ durationMs?: number;
959
1195
  /**
960
1196
  * A retry identity, kept as long as the entry: the same key and request answer the entry the
961
1197
  * first made (`replayed`), another request `idempotency_conflict`.
962
1198
  */
963
1199
  idempotencyKey?: string;
964
1200
  }): Promise<JoinWaitlistResult>;
1201
+ /**
1202
+ * One holder's live holds in a group, by resource id (RFC 0004 B7). With `alignExpiry`, every one
1203
+ * expires when the earliest does, so the set has one time to live.
1204
+ */
1205
+ holdGroup(params: {
1206
+ tenantRef: string;
1207
+ holderRef: string;
1208
+ groupId: string;
1209
+ alignExpiry?: boolean;
1210
+ }): Promise<{
1211
+ holdId: string;
1212
+ resourceId: string;
1213
+ start: Date;
1214
+ end: Date;
1215
+ expiresAt: Date;
1216
+ source: string | null;
1217
+ }[]>;
1218
+ /**
1219
+ * Decline an entry's offer (RFC 0004 B6): its hold is let go and the entry is `declined`, so the
1220
+ * time is offered to the next. With `holderRef`, only that holder's. Null when there is no such
1221
+ * offer.
1222
+ */
1223
+ declineWaitlistOffer(params: {
1224
+ tenantRef: string;
1225
+ id: string;
1226
+ holderRef?: string;
1227
+ }): Promise<SlotlockWaitlistEntry | null>;
965
1228
  /** The tenant's waitlist entries, oldest first, optionally of one resource, pool or holder. */
966
1229
  listWaitlist(params: {
967
1230
  tenantRef: string;
@@ -1018,42 +1281,72 @@ export interface SlotlockStore extends SlotlockEventFeed {
1018
1281
  from: string;
1019
1282
  to: string;
1020
1283
  }): Promise<boolean>;
1021
- /** A member's live assistant conversation, or null when none is (or it expired). */
1022
- getAssistantConversation(params: {
1284
+ /**
1285
+ * A member's conversations with the assistant, the most recent first (at most `limit`, default
1286
+ * 100): what the Chats list shows. Expired ones are left out.
1287
+ */
1288
+ listAssistantChats(params: {
1023
1289
  tenantRef: string;
1024
1290
  memberRef: string;
1025
- }): Promise<SlotlockAssistantConversationRecord | null>;
1291
+ limit?: number;
1292
+ }): Promise<SlotlockAssistantChatSummary[]>;
1293
+ /** One of a member's conversations, or null when there is none by that id (or it expired). */
1294
+ getAssistantChat(params: {
1295
+ tenantRef: string;
1296
+ memberRef: string;
1297
+ chatId: string;
1298
+ }): Promise<SlotlockAssistantChatRecord | null>;
1026
1299
  /**
1027
- * Store a member's conversation for `ttlMs` (1 second to a day) from now, replacing its messages.
1300
+ * Store one of a member's conversations, creating it (titled `title`) or replacing its messages.
1028
1301
  * Its actions become those stored already that `keep` names, in their stored state
1029
1302
  * (settleAssistantAction changes it), and the `added` ones not stored already: an action is never
1030
- * put back into a conversation that no longer has it. The tenant's expired conversations are
1031
- * deleted with it, and the tenant is listed for sweepAssistantConversations.
1303
+ * put back into a conversation that no longer has it. It is kept until deleted, or for `ttlMs`
1304
+ * (1 second to 3,650 days) from now when given, and then the tenant is listed for
1305
+ * sweepAssistantConversations. The member keeps at most SLOTLOCK_ASSISTANT_MAX_CHATS: past it,
1306
+ * the least recently used go (the member's writes take turns, so two at once keep the bound).
1307
+ * True when it was written; false when `create` is false and it was not there. Run it in a
1308
+ * transaction (withTenant does) so its lock lasts until the trim commits.
1032
1309
  */
1033
- putAssistantConversation(params: {
1310
+ putAssistantChat(params: {
1034
1311
  tenantRef: string;
1035
1312
  memberRef: string;
1313
+ chatId: string;
1314
+ title: string;
1036
1315
  messages: unknown[];
1037
1316
  added: Record<string, Record<string, unknown>>;
1038
1317
  keep: readonly string[];
1039
- ttlMs: number;
1040
- }): Promise<void>;
1318
+ ttlMs: number | null;
1319
+ /**
1320
+ * False to only change a conversation that is still there: one the member deleted while a turn
1321
+ * on it ran is not written back. True (the default) creates it when it is not.
1322
+ */
1323
+ create?: boolean;
1324
+ }): Promise<boolean>;
1325
+ /** Give one of a member's conversations a new title (1-120 characters). False when there is none. */
1326
+ renameAssistantChat(params: {
1327
+ tenantRef: string;
1328
+ memberRef: string;
1329
+ chatId: string;
1330
+ title: string;
1331
+ }): Promise<boolean>;
1041
1332
  /**
1042
- * Change one proposed action, only while its `status` is `from`: its fields are merged with
1043
- * `patch`. The action as changed, or null when it is not in that state (or not there): of two
1044
- * racing approvals, one wins.
1333
+ * Change one proposed action of a conversation, only while its `status` is `from`: its fields are
1334
+ * merged with `patch`. The action as changed, or null when it is not in that state (or not
1335
+ * there): of two racing approvals, one wins.
1045
1336
  */
1046
1337
  settleAssistantAction(params: {
1047
1338
  tenantRef: string;
1048
1339
  memberRef: string;
1340
+ chatId: string;
1049
1341
  actionId: string;
1050
1342
  from: string;
1051
1343
  patch: Record<string, unknown>;
1052
1344
  }): Promise<Record<string, unknown> | null>;
1053
- /** Forget a member's conversation. False when there was none. */
1054
- deleteAssistantConversation(params: {
1345
+ /** Delete one of a member's conversations. False when there was none. */
1346
+ deleteAssistantChat(params: {
1055
1347
  tenantRef: string;
1056
1348
  memberRef: string;
1349
+ chatId: string;
1057
1350
  }): Promise<boolean>;
1058
1351
  /** Delete up to `limit` (default 1,000) of the tenant's expired assistant conversations: how many. */
1059
1352
  pruneAssistantConversations(params: {
@@ -1132,6 +1425,39 @@ export interface SlotlockStore extends SlotlockEventFeed {
1132
1425
  }): Promise<SlotlockTenantErasure>;
1133
1426
  /** Record the bounded, complete source window represented by a provider cursor/revision. */
1134
1427
  recordCalendarCoverage(params: CalendarCoverageCommand): Promise<CalendarCoverageResult>;
1428
+ /**
1429
+ * A calendar that gives busy time but not its events (RFC 0004 C1): records its coverage of the
1430
+ * window at the revision, as recordCalendarCoverage does, and in the same transaction replaces
1431
+ * the busy time it reported for that window (what it said outside the window stays). Busy time
1432
+ * counts in free/busy and is refused to every booking Slotlock makes, as an event's would be;
1433
+ * a confirmed booking the source newly reports busy time over is put on the feed as
1434
+ * coverage.conflict, once, and left as it is. A revision already recorded changes nothing.
1435
+ */
1436
+ replaceSourceBusy(params: SourceBusyCommand): Promise<SourceBusyResult>;
1437
+ /**
1438
+ * A source says a booking it was given (a sync's write-back) was changed or removed in its
1439
+ * calendar (RFC 0004 C4): put on the feed as `coverage.conflict`, named by the source's
1440
+ * `source_id`, with the booking and what the calendar now says. The booking is left as it is:
1441
+ * Slotlock stays the source of truth, and someone decides.
1442
+ */
1443
+ reportCoverageConflict(params: {
1444
+ tenantRef: string;
1445
+ resourceId: string;
1446
+ source: string;
1447
+ booking: {
1448
+ reservationRef: string;
1449
+ } | {
1450
+ eventId: string;
1451
+ };
1452
+ reason: 'changed_in_calendar' | 'removed_from_calendar';
1453
+ /** Where the calendar now has it, when it was moved. */
1454
+ calendar?: Interval;
1455
+ }): Promise<{
1456
+ ok: true;
1457
+ } | {
1458
+ ok: false;
1459
+ code: 'invalid_identity' | 'invalid_window' | 'coverage_not_found' | 'reservation_not_found' | 'event_not_found';
1460
+ }>;
1135
1461
  /** Return merged busy intervals plus explicit completeness for caller-required provider sources. */
1136
1462
  getFreeBusy(params: {
1137
1463
  tenantRef: string;
@@ -1178,12 +1504,21 @@ export interface SlotlockAssistantSettingsRecord {
1178
1504
  updatedAt: Date;
1179
1505
  }
1180
1506
  /** One member's live conversation with the dashboard assistant, as assistant.ts keeps it. */
1181
- export interface SlotlockAssistantConversationRecord {
1507
+ /** One of a member's conversations with the assistant, as the Chats list shows it. */
1508
+ export interface SlotlockAssistantChatSummary {
1509
+ /** 16 URL-safe characters. */
1510
+ id: string;
1511
+ title: string;
1512
+ updatedAt: Date;
1513
+ }
1514
+ /** One of a member's conversations with the assistant, whole. */
1515
+ export interface SlotlockAssistantChatRecord extends SlotlockAssistantChatSummary {
1182
1516
  messages: unknown[];
1183
1517
  /** The writes it proposed, by id. */
1184
1518
  actions: Record<string, Record<string, unknown>>;
1185
- updatedAt: Date;
1186
- expiresAt: Date;
1519
+ createdAt: Date;
1520
+ /** When it is deleted unless used again; null when it is kept until the member deletes it. */
1521
+ expiresAt: Date | null;
1187
1522
  }
1188
1523
  /** One entry of a tenant's waitlist (joinWaitlist). */
1189
1524
  export interface SlotlockWaitlistEntry {
@@ -1195,12 +1530,20 @@ export interface SlotlockWaitlistEntry {
1195
1530
  end: Date;
1196
1531
  holderRef: string;
1197
1532
  offerTtlMs: number;
1198
- /** `waiting`, `offered` (a hold was made: `holdId` on `offeredResourceId`) or `expired`. */
1199
- status: 'waiting' | 'offered' | 'expired';
1533
+ /**
1534
+ * `waiting`; `offered` (a hold was made: `holdId` on `offeredResourceId`); then `accepted` (the
1535
+ * hold was confirmed), `declined` (let go) or `lapsed` (it expired); or `expired` (no slot could
1536
+ * be offered before the window closed).
1537
+ */
1538
+ status: 'waiting' | 'offered' | 'accepted' | 'declined' | 'lapsed' | 'expired';
1200
1539
  holdId: string | null;
1201
1540
  offeredResourceId: string | null;
1202
1541
  offeredAt: Date | null;
1203
1542
  createdAt: Date;
1543
+ /** The length it waits for, any slot of it within the window (RFC 0004 B6); null: the whole window. */
1544
+ durationMs: number | null;
1545
+ /** When its offer was accepted, declined or lapsed. */
1546
+ decidedAt: Date | null;
1204
1547
  }
1205
1548
  export type JoinWaitlistResult = {
1206
1549
  ok: true;
@@ -1323,4 +1666,4 @@ export declare function createSlotlockStore(sql: StoreSql, options?: SlotlockSto
1323
1666
  * Every store method that writes for a tenant: what a suspended tenant's withTenant store refuses.
1324
1667
  * Maintenance (rolling series, pruning, purging) and erasure are not among them.
1325
1668
  */
1326
- export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "updateResourceProfile", "putLocation", "updateLocation", "deleteLocation", "putGroup", "deleteGroup", "addGroupMember", "removeGroupMember", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setResourceBookingRules", "setBookingRuleDefaults", "setResourcePool", "setResourceParent", "setPoolOrder", "notePoolBooking", "putApprovalPolicy", "deleteApprovalPolicy", "requestApproval", "beginApprovalGrant", "finishApprovalGrant", "declineApproval", "resetTestTenant", "fastForwardTestTenant", "setTenantAvailability", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage", "createWebhookEndpoint", "deleteWebhookEndpoint", "enableWebhookEndpoint", "recordCoverageFailure", "deleteCoverage", "checkInReservation", "joinWaitlist", "leaveWaitlist", "putAssistantSettings", "deleteAssistantSettings", "resealAssistantKey", "putAssistantConversation", "settleAssistantAction", "deleteAssistantConversation"];
1669
+ export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "updateResourceProfile", "putLocation", "updateLocation", "deleteLocation", "putGroup", "deleteGroup", "addGroupMember", "removeGroupMember", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setResourceBookingRules", "setBookingRuleDefaults", "setResourcePool", "setResourceParent", "setPoolOrder", "notePoolBooking", "putApprovalPolicy", "deleteApprovalPolicy", "requestApproval", "beginApprovalGrant", "finishApprovalGrant", "declineApproval", "resetTestTenant", "fastForwardTestTenant", "storeTenantAvailability", "setTenantAvailability", "setLocationAvailability", "createAvailabilityException", "deleteAvailabilityException", "replaceImportedExceptions", "importHolidayCalendar", "deleteHolidayCalendar", "noteHolidayCalendarFailure", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "markHoldPaid", "putContact", "deleteContact", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage", "replaceSourceBusy", "reportCoverageConflict", "createWebhookEndpoint", "deleteWebhookEndpoint", "enableWebhookEndpoint", "recordCoverageFailure", "deleteCoverage", "checkInReservation", "joinWaitlist", "leaveWaitlist", "declineWaitlistOffer", "putAssistantSettings", "deleteAssistantSettings", "resealAssistantKey", "putAssistantChat", "renameAssistantChat", "settleAssistantAction", "deleteAssistantChat"];