slotlock 0.6.1 → 0.6.3

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 (63) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +13 -6
  3. package/SECURITY.md +15 -4
  4. package/agent-server.js +1 -5
  5. package/agent-store-backend.d.ts +5 -6
  6. package/agent-store-backend.js +41 -67
  7. package/api-keys.js +1 -1
  8. package/application-api.d.ts +3 -3
  9. package/application-api.js +16 -24
  10. package/assistant.d.ts +18 -27
  11. package/assistant.js +36 -53
  12. package/booking-rules.d.ts +30 -41
  13. package/booking-rules.js +17 -25
  14. package/change-watch.d.ts +1 -0
  15. package/change-watch.js +1 -1
  16. package/cli.js +3 -4
  17. package/config.d.ts +17 -2
  18. package/config.js +44 -6
  19. package/dashboard.js +14 -1
  20. package/ddl.d.ts +20 -27
  21. package/ddl.js +166 -108
  22. package/engine.d.ts +6 -9
  23. package/engine.js +5 -9
  24. package/event-feed.d.ts +23 -31
  25. package/event-feed.js +30 -45
  26. package/event-schema.js +6 -9
  27. package/hooks.d.ts +2 -4
  28. package/hooks.js +2 -4
  29. package/ical-worker-thread.js +3 -4
  30. package/ical-worker.d.ts +8 -10
  31. package/ical-worker.js +7 -9
  32. package/interop.js +1 -4
  33. package/maintenance.d.ts +14 -10
  34. package/maintenance.js +43 -26
  35. package/mcp-app.d.ts +7 -9
  36. package/mcp-app.js +7 -9
  37. package/mcp-modern.d.ts +14 -16
  38. package/mcp-modern.js +23 -27
  39. package/metrics.js +0 -1
  40. package/node-server.d.ts +9 -9
  41. package/node-server.js +25 -34
  42. package/package-version.js +2 -4
  43. package/package.json +1 -1
  44. package/rate-limit.d.ts +6 -1
  45. package/rate-limit.js +24 -8
  46. package/rest-api.d.ts +13 -14
  47. package/rest-api.js +11 -12
  48. package/rules.d.ts +6 -7
  49. package/rules.js +30 -42
  50. package/self-host.d.ts +16 -2
  51. package/self-host.js +42 -21
  52. package/slotlock.manifest.json +1 -1
  53. package/store.d.ts +109 -161
  54. package/store.js +253 -371
  55. package/sync.d.ts +44 -59
  56. package/sync.js +325 -469
  57. package/timezone.d.ts +25 -33
  58. package/timezone.js +37 -52
  59. package/types.d.ts +35 -56
  60. package/webhooks.d.ts +22 -25
  61. package/webhooks.js +35 -40
  62. package/weekly-hours.d.ts +3 -0
  63. package/weekly-hours.js +1 -1
package/store.d.ts CHANGED
@@ -5,15 +5,13 @@ import { SLOTLOCK_CALENDAR_HORIZON_DAYS } from './rules.js';
5
5
  import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
6
6
  export { SLOTLOCK_CALENDAR_HORIZON_DAYS };
7
7
  /**
8
- * Longest one-off event the store accepts (ten years and some leap days): long enough for any lease
9
- * or long-term rental, short enough that a unit error cannot occupy a resource for ever. A recurring
10
- * series is bounded by its 367-day materialization window instead.
8
+ * Longest one-off event (ten years and some leap days); a recurring series is bounded by its
9
+ * materialization window instead.
11
10
  */
12
11
  export declare const SLOTLOCK_MAX_EVENT_DURATION_DAYS = 3660;
13
12
  /**
14
- * Longest window a recurring series is stored (materialized) for: the 367-day search window plus
15
- * two days. The rolling horizon starts a day before the current UTC day and must reach 367 days
16
- * past its end, so that a search of the full 367 days from any moment of the day is complete.
13
+ * The 367-day search window plus two days: the rolling horizon starts a day before the current UTC
14
+ * day and must reach 367 days past its end.
17
15
  */
18
16
  export declare const SLOTLOCK_MATERIALIZATION_WINDOW_DAYS: number;
19
17
  /** Trusted provider/maintenance namespace used by backwards-compatible store callers. */
@@ -38,8 +36,8 @@ export declare const SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS = 30;
38
36
  /** Store-owned coverage source for bounded, locally materialized recurring events. */
39
37
  export declare const SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE = "slotlock:local-recurrence-materialization";
40
38
  /**
41
- * Stable daily target shared by the event writer, maintenance worker, and readiness proof: from a
42
- * day before the current UTC day to 367 days past its end (SLOTLOCK_MATERIALIZATION_WINDOW_DAYS).
39
+ * From a day before the current UTC day to SLOTLOCK_MATERIALIZATION_WINDOW_DAYS past its end;
40
+ * shared by the writer, maintenance and readiness.
43
41
  */
44
42
  export declare function calendarEventRollingHorizon(now?: Date): Interval;
45
43
  /** What `createSlotlockStore` accepts: a `postgres` client, or a transaction/savepoint from one. */
@@ -48,9 +46,9 @@ type StoreSql = SlotlockSql;
48
46
  /** How a migration (applySchema, applyTenantRls, grantApplicationRole) waits and how long it runs. */
49
47
  export interface SlotlockMigrationOptions {
50
48
  /**
51
- * The longest it waits for any one lock, in milliseconds (1-600,000; default 1,000). While a
52
- * migration waits for a lock on a table, every query that comes after it on that table waits
53
- * behind it; past this it gives up, lets them through, and tries again.
49
+ * The longest it waits for any one lock, in milliseconds (1-600,000; default 1,000). Queries on a
50
+ * table queue behind a migration waiting for its lock, so past this it gives up, lets them
51
+ * through, and retries.
54
52
  */
55
53
  lockTimeoutMs?: number;
56
54
  /** The longest any one of its statements runs, in milliseconds (1-86,400,000; default 900,000). */
@@ -59,21 +57,18 @@ export interface SlotlockMigrationOptions {
59
57
  attempts?: number;
60
58
  }
61
59
  /**
62
- * The schema version recorded by the last migration, or `null` when there is none this role can
63
- * read: no schema, a schema from 0.2 or earlier, or a role not yet granted the function (run
64
- * `slotlock migrate`, which grants it).
60
+ * The recorded schema version, or `null` when this role cannot read one: no schema, 0.2 or earlier,
61
+ * or the function not yet granted (`slotlock migrate` grants it).
65
62
  */
66
63
  export declare function readSlotlockSchemaVersion(sql: SlotlockSql): Promise<number | null>;
67
64
  /**
68
- * Refuse to serve a schema another release migrated: `schema_outdated` when it is older than this
69
- * release (or unversioned), so `slotlock migrate` must run first; `schema_newer` when a newer release
70
- * migrated it, whose schema this release's code does not know. Both carry `installed` and `expected`.
65
+ * Refuse a schema another release migrated: `schema_outdated` (older or unversioned: run `slotlock
66
+ * migrate`) or `schema_newer`. Both carry `installed` and `expected`.
71
67
  */
72
68
  export declare function assertSlotlockSchemaCurrent(sql: SlotlockSql): Promise<void>;
73
69
  /**
74
- * What makes a put the same request: its identity and the event it asks for. Not the window its
75
- * occurrences are stored over, which for a series is the rolling horizon from the current UTC
76
- * midnight: with it in the hash, a retry after midnight was refused as a different request.
70
+ * A put's identity and the event it asks for, not its storage window: a series' window moves with
71
+ * the UTC day, so a retry after midnight must still match.
77
72
  */
78
73
  declare function putCommandHash(params: {
79
74
  command: PutCalendarEventCommand;
@@ -81,9 +76,8 @@ declare function putCommandHash(params: {
81
76
  event: CalendarEventContent;
82
77
  }): string;
83
78
  /**
84
- * The hash 0.3.0 and earlier stored, with the window in it, so a command stored before the upgrade
85
- * still replays: always for a one-off event, whose window is its own span, and on the same UTC day
86
- * for a series. Remove once no stored command can be that old (commands are pruned after 30 days).
79
+ * The hash 0.3.0 and earlier stored (window included), so an older stored command still replays.
80
+ * Remove once no stored command can be that old (pruned after 30 days).
87
81
  */
88
82
  export declare function legacyPutCommandHash(params: Parameters<typeof putCommandHash>[0] & {
89
83
  window: Interval;
@@ -95,52 +89,38 @@ export declare const SLOTLOCK_MAX_POOL_RULE_DEFAULTS = 1000;
95
89
  export declare function isPoolName(value: unknown): value is string;
96
90
  export interface SlotlockStore extends SlotlockEventFeed {
97
91
  /**
98
- * Apply the Slotlock DDL (idempotent). Owns the `slotlock` schema on the connected DB. Refuses
99
- * (`unsafe_slotlock_schema`, with the `reasons` found) a `slotlock` schema that a role other than the
100
- * connected one (superusers aside) owns or can create objects in, that holds an object such a
101
- * role owns (an extension included), or whose tables such a role may add triggers to or have a
102
- * trigger running a function such a role owns. Ownership through a group role counts as another
103
- * role's: deploy as the owning role itself (`SET ROLE`). Refuses (`schema_newer`) a schema a newer
104
- * release migrated, changing nothing. Records SLOTLOCK_SCHEMA_VERSION. A schema this release
105
- * already migrated, unchanged since, is left as it is without a lock on any table. Constraints an
106
- * upgrade adds NOT VALID are validated in a second transaction, whose locks no serving read or
107
- * write waits for (one inside a caller's transaction shares its locks until it ends); one that old
108
- * rows still break stays NOT VALID, and each later migration validates it again. Each lock it
109
- * waits for is bounded by `lockTimeoutMs`, after which it tries again (`schema_lock_timeout` once
92
+ * Apply the Slotlock DDL (idempotent). Refuses (`unsafe_slotlock_schema`, with `reasons`) a
93
+ * `slotlock` schema that another role (superusers aside) owns, can create objects in, owns an
94
+ * object in, or can add triggers to; ownership through a group role counts, so deploy as the
95
+ * owning role itself (`SET ROLE`). Refuses (`schema_newer`) a schema a newer release migrated. An
96
+ * unchanged schema is left without locking any table. Constraints added NOT VALID are validated
97
+ * in a second transaction; one old rows still break stays NOT VALID and is retried on each
98
+ * migration. Each lock wait is bounded by `lockTimeoutMs` and retried (`schema_lock_timeout` once
110
99
  * out of attempts).
111
100
  */
112
101
  applySchema(options?: SlotlockMigrationOptions): Promise<void>;
113
102
  /**
114
- * Atomically enable forced tenant RLS using this store's configured context setting. Refuses the
115
- * same `slotlock` schemas as `applySchema()`. Tables already forced under exactly the policies it
116
- * installs, bound to this setting, are left as they are without a lock on any table.
103
+ * Atomically enable forced tenant RLS using the store's context setting. Refuses the same schemas
104
+ * as `applySchema()`. Tables already set up exactly so are left without a lock.
117
105
  */
118
106
  applyTenantRls(options?: {
119
107
  allowRebind?: boolean;
120
108
  } & SlotlockMigrationOptions): Promise<void>;
121
109
  /**
122
110
  * Grant an existing application role exactly what the store needs at runtime: USAGE on the
123
- * `slotlock` schema and DML on its tables. Run as the deployment role after every `applySchema()`.
124
- * Refuses (`unsafe_application_role`, with the `reasons` found) a role that has one of the known
125
- * ways around forced tenant RLS or the exclusion arbiter, itself, through a role it belongs to or
126
- * through PUBLIC: superuser, BYPASSRLS, REPLICATION or CREATEROLE, a server file or program role,
127
- * ownership of the database, of anything in it (its temporary objects and its own default
128
- * privileges aside) or of `btree_gist`, CREATE on the database or on any schema, TRUNCATE,
129
- * REFERENCES or TRIGGER on a Slotlock table (or by default on new ones), or a right on a server
130
- * setting. The check runs when the grant is made, so a right added later is not
131
- * detected until it runs again, and it is not exhaustive (SECURITY.md lists what it does not read).
111
+ * `slotlock` schema and DML on its tables. Run as the deployment role after every
112
+ * `applySchema()`. Refuses (`unsafe_application_role`, with `reasons`) a role with a known way
113
+ * around forced RLS or the exclusion arbiter, directly, by membership or through PUBLIC
114
+ * (SECURITY.md lists the checks and what they do not cover). The check runs only at grant time.
132
115
  */
133
116
  grantApplicationRole(role: string, options?: SlotlockMigrationOptions): Promise<void>;
134
117
  /**
135
118
  * Run store operations with one transaction-local tenant context on the same connection. On a
136
- * client it opens a READ COMMITTED transaction, whatever the role's default. Inside a caller's
137
- * transaction of another level its writes are refused (`invalid_transaction_isolation`), since
138
- * that snapshot can miss a suspension, and its reads go on; with `isolation: 'read committed'`
139
- * such a transaction is refused outright. The serving role needs EXECUTE on
140
- * slotlock.tenant_suspended(text): without it this refuses with `application_role_not_granted`
141
- * (grantApplicationRole grants it), and on a schema without it with `schema_outdated`. With the
142
- * store's `maxTenantTransactions`, an aborted `signal` takes a call still waiting for its turn out
143
- * of the line, refused with the signal's reason.
119
+ * client it opens a READ COMMITTED transaction. Inside a caller's transaction of another level,
120
+ * writes are refused (`invalid_transaction_isolation`), since that snapshot can miss a
121
+ * suspension. Needs EXECUTE on slotlock.tenant_suspended(text) (`application_role_not_granted`
122
+ * without it; grantApplicationRole grants it). With `maxTenantTransactions`, an aborted `signal`
123
+ * removes a waiting call from the line.
144
124
  */
145
125
  withTenant<T>(tenantRef: string, callback: (store: SlotlockTenantStore) => Promise<T>, options?: {
146
126
  isolation?: 'read committed';
@@ -162,14 +142,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
162
142
  * INVARIANT (erasure completeness): a tenant integration must use a stable, erasable business
163
143
  * identifier for externalRef and include this schema in its own data-erasure workflow.
164
144
  *
165
- * With `maxTenantResources` (1-100,000, tenant-owned resources only), or the store's `resources`
166
- * limit (the lower of the two when both are set), a new resource of a tenant is refused
167
- * (`resource_limit_reached`, with the `limit`) once the tenant has that many; an existing
168
- * `externalRef` is still updated. Capped creates for one tenant run one at a time under a database lock, so racing ones
169
- * on any number of servers stop at the cap. The count needs READ COMMITTED: a capped create opens
170
- * one itself, and inside a caller's transaction of another level it refuses
171
- * (`invalid_transaction_isolation`). Uncapped creates do not wait for the lock and are not
172
- * counted against a cap until they commit.
145
+ * With `maxTenantResources` (1-100,000) or the store's `resources` limit (the lower applies), a
146
+ * new resource is refused (`resource_limit_reached`) once the tenant has that many; an existing
147
+ * `externalRef` is still updated. Capped creates serialize on a database lock, so racing servers
148
+ * stop at the cap; this needs READ COMMITTED (`invalid_transaction_isolation` inside another
149
+ * level).
173
150
  */
174
151
  createResource(params: {
175
152
  externalRef?: string;
@@ -298,13 +275,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
298
275
  includeArchived?: boolean;
299
276
  }): Promise<SlotlockResource[]>;
300
277
  /**
301
- * Insert a confirmed reservation with the EXCLUDE constraint as the overlap arbiter. Never
302
- * throws for the expected outcomes: an invalid window or a lost race come back structured.
303
- * Expired holds overlapping the window are lazily reaped in the same transaction — an
304
- * expired-but-unpurged hold can never block (no cron required). On a tenant's resource the
305
- * booking takes the tenant, and its own id as reference, so cancel and reschedule reach it. With
306
- * `tenantRef`, only a resource of that tenant (`resource_not_found` otherwise, whatever the
307
- * connection's row-level security).
278
+ * Insert a confirmed reservation with the EXCLUDE constraint as the overlap arbiter; expected
279
+ * outcomes (invalid window, lost race) come back structured. Expired holds overlapping the window
280
+ * are reaped in the same transaction, so they never block. On a tenant's resource the booking
281
+ * takes the tenant and its own id as reference. With `tenantRef`, only that tenant's resource
282
+ * (`resource_not_found` otherwise, whatever the connection's RLS).
308
283
  */
309
284
  createReservation(params: {
310
285
  resourceId: string;
@@ -328,11 +303,9 @@ export interface SlotlockStore extends SlotlockEventFeed {
328
303
  revision: number;
329
304
  }): Promise<CancelExternalReservationResult>;
330
305
  /**
331
- * Occupy a window WITHOUT committing: the hold blocks rivals under the same EXCLUDE arbiter
332
- * until `expiresAt` (DB clock — the statement's time + ttl, never the app clock), then frees via
333
- * lazy reap.
334
- * With `tenantRef`, only a resource of that tenant (`resource_not_found` otherwise, whatever the
335
- * connection's row-level security).
306
+ * Occupy a window without committing: the hold blocks rivals under the same EXCLUDE arbiter until
307
+ * `expiresAt` (database clock: statement time + ttl), then frees via lazy reap. With `tenantRef`,
308
+ * only that tenant's resource (`resource_not_found` otherwise, whatever the connection's RLS).
336
309
  */
337
310
  acquireHold(params: {
338
311
  resourceId: string;
@@ -344,21 +317,18 @@ export interface SlotlockStore extends SlotlockEventFeed {
344
317
  /** Turnaround kept busy after `end`, 0 to 30 days; the confirmed booking keeps it. */
345
318
  bufferAfterMs?: number;
346
319
  /**
347
- * A retry identity, 1-500 UTF-8 bytes, unique inside `tenantRef` and `principalRef` (both
348
- * required with it): a request repeated with the same key and body answers the hold the first
349
- * made (`idempotent: true`), and one with another body is `idempotency_conflict`. The answer is
350
- * kept until the hold would have expired, released or confirmed; a retry after that makes a new
351
- * hold.
320
+ * A retry identity, 1-500 UTF-8 bytes, unique within `tenantRef` and `principalRef` (both
321
+ * required): the same key and body answer the first hold (`idempotent: true`); another body is
322
+ * `idempotency_conflict`. Kept until the hold would have expired, released or confirmed.
352
323
  */
353
324
  idempotencyKey?: string;
354
325
  /** Whose keys `idempotencyKey` is among, 1-500 UTF-8 bytes (an API key's subject, say). */
355
326
  principalRef?: string;
356
327
  /**
357
- * Who the hold belongs to, 1-500 UTF-8 bytes, inside `tenantRef` (required with it): an agent
358
- * acting for one person, say. `releaseHold` and `putCalendarEvent`'s `fromHold` with a
359
- * `holderRef` take only their own, and a holder may have `maxHoldsPerHolder` (20) live at once,
360
- * holding at most `maxHeldHoursPerHolder` together when that is set. Without it the hold has no
361
- * holder and no cap: an application holding for its customers.
328
+ * Who the hold belongs to, 1-500 UTF-8 bytes, within `tenantRef` (required): `releaseHold` and
329
+ * `fromHold` with a `holderRef` take only their own, and a holder may have `maxHoldsPerHolder`
330
+ * live at once (and `maxHeldHoursPerHolder` together, when set). Without it the hold has no
331
+ * holder and no cap.
362
332
  */
363
333
  holderRef?: string;
364
334
  /**
@@ -416,12 +386,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
416
386
  };
417
387
  }): Promise<SlotlockReservation[]>;
418
388
  /**
419
- * Flip an unexpired hold to a confirmed booking (expiry cleared). On a tenant's resource the
420
- * booking takes the tenant, and the hold's id as its reference at revision 1, so cancel and
421
- * reschedule reach it like any other (`reservation_cancelled` when a cancel of that reference
422
- * came first). Confirming it again answers the booking as it now is. With `tenantRef`, only a
423
- * hold on one of that tenant's resources counts: another tenant's is `hold_not_found` whatever
424
- * the connection's row-level security.
389
+ * Turn an unexpired hold into a confirmed booking. On a tenant's resource the booking takes the
390
+ * tenant and the hold's id as reference at revision 1 (`reservation_cancelled` when a cancel of
391
+ * that reference came first). Confirming again answers the booking as it now is. With
392
+ * `tenantRef`, another tenant's hold is `hold_not_found` whatever the connection's RLS.
425
393
  */
426
394
  confirmHold(holdId: string, options?: SlotlockHoldTenantOptions): Promise<ConfirmHoldResult>;
427
395
  /**
@@ -511,12 +479,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
511
479
  expectedResourceId?: string;
512
480
  }): Promise<CancelCalendarEventResult>;
513
481
  /**
514
- * Atomically rematerialize due recurring masters into one rolling window without changing their
515
- * semantic revision. A collision rolls back that master and leaves its old coverage untouched.
516
- * A master is rolled from the window's start through the later of its stored end and the
517
- * window's, so its end never moves back. One that already covers the window, or whose end lies
518
- * too far past it for the 367-day cap from the window's start, is not due: it is left as it is
519
- * and not counted.
482
+ * Rematerialize due recurring masters into one rolling window without changing their revision. A
483
+ * collision rolls back that master and keeps its old coverage. A master is rolled from the
484
+ * window's start through the later of its stored end and the window's; one already covering the
485
+ * window, or too far past it for the 367-day cap, is not due and not counted.
520
486
  */
521
487
  rollCalendarEventHorizon(params: {
522
488
  tenantRef: string;
@@ -526,12 +492,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
526
492
  after?: CalendarHorizonRollCursor;
527
493
  }): Promise<CalendarHorizonRollResult>;
528
494
  /**
529
- * Delete one bounded batch of expired event bookkeeping for a tenant, which is what returns agent
530
- * quota: idempotency commands older than the replay window (every owner), then agent-owned
531
- * cancellation tombstones older than it with no remaining command. Active events and `internal`
532
- * (provider/sync) tombstones are never deleted. Rows another transaction holds are skipped, not
533
- * waited on. After the window a retried command is evaluated afresh under its expected revision,
534
- * and a pruned agent identity can be created again. Call until `hasMore` is false.
495
+ * Delete one bounded batch of expired event bookkeeping, which returns agent quota: commands
496
+ * older than the replay window (every owner), then agent-owned tombstones older than it with no
497
+ * remaining command. Active events and `internal` tombstones are never deleted. Locked rows are
498
+ * skipped. Afterwards a retried command is evaluated afresh. Call until `hasMore` is false.
535
499
  */
536
500
  pruneCalendarEventRetention(params: {
537
501
  tenantRef: string;
@@ -541,21 +505,17 @@ export interface SlotlockStore extends SlotlockEventFeed {
541
505
  retentionDays?: number;
542
506
  }): Promise<CalendarEventRetentionResult>;
543
507
  /**
544
- * Delete one bounded batch of a tenant's dead booking rows: holds past their expiry and the
545
- * answers kept for keyed ones (at once), and references retired by a cancel no booking held,
546
- * older than the retention window. A cancelled booking's tombstone is never deleted here (only by
547
- * erasing the tenant), so its reference is never booked again. Rows another transaction holds
548
- * are skipped, not waited on. It also gives a confirmed booking a 0.2 server wrote without its
549
- * tenant the tenant and its own id as reference, as the migration does. Call until `hasMore` is
550
- * false.
508
+ * Delete one bounded batch of a tenant's dead booking rows: expired holds and their keyed
509
+ * answers, and references retired with no booking past retention. A cancelled booking's tombstone
510
+ * is only deleted by erasure, so its reference is never booked again. Locked rows are skipped.
511
+ * Also gives a confirmed booking a 0.2 server wrote without a tenant its tenant and reference, as
512
+ * the migration does. Call until `hasMore` is false.
551
513
  */
552
514
  /**
553
- * Time zone rules that changed since an event was booked (a new tzdata): a future one-off event
554
- * whose wall-clock time in its zone now falls at another instant. Each is reported once on the
555
- * feed (`event.timezone_shifted`, with the instant it has and the one its wall time now means).
556
- * With `move`, it is moved there instead (a new revision, `event.updated` naming `slotlock:tzdata`),
557
- * unless that overlaps another booking: then it stays and `event.timezone_conflict` reports it,
558
- * for a person or an agent to decide. Up to `limit` (1-1,000, default 100) events a call.
515
+ * Find future one-off events whose wall-clock time now falls at another instant (new tzdata),
516
+ * each reported once (`event.timezone_shifted`). With `move`, it is moved (a new revision,
517
+ * `event.updated` by `slotlock:tzdata`) unless that overlaps another booking, which
518
+ * `event.timezone_conflict` reports instead. Up to `limit` (1-1,000, default 100) a call.
559
519
  */
560
520
  followZoneRules(params: {
561
521
  tenantRef: string;
@@ -740,12 +700,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
740
700
  limit?: number;
741
701
  }): Promise<number>;
742
702
  /**
743
- * Offer freed time to the waitlist, in the order entries joined: each waiting entry whose window
744
- * is free (on its resource, or on the first free member of its pool) gets a hold for its holder
745
- * and the feed says so (`waitlist.offered`, with the hold). Entries whose window has started are
746
- * expired (`waitlist.expired`); finished ones older than the change feed's retention are deleted.
747
- * One tenant's offers run one at a time. One page of `limit` (1-100, default 50) entries: pass
748
- * `next` back as `after` for the next, until it is null.
703
+ * Offer freed time to the waitlist in join order: each waiting entry whose window is free (on its
704
+ * resource, or the first free pool member) gets a hold for its holder (`waitlist.offered`).
705
+ * Started windows expire (`waitlist.expired`); finished entries past retention are deleted. One
706
+ * tenant's offers run one at a time. Pages of `limit` (1-100, default 50): pass `next` back as
707
+ * `after` until null.
749
708
  */
750
709
  offerWaitlist(params: {
751
710
  tenantRef: string;
@@ -773,11 +732,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
773
732
  retentionDays?: number;
774
733
  }): Promise<BookingRetentionResult>;
775
734
  /**
776
- * Suspend a tenant: from when this returns, every withTenant transaction for it (as every server
777
- * runs its requests) may read, and its writes are refused (`tenant_suspended`); one in flight is
778
- * waited for. Its keys still authenticate, but it gets no new key or secret. Maintenance and
779
- * erasure go on. True when it was not suspended already. Only the deployment role, which owns the
780
- * schema, may suspend (`tenant_suspension_not_permitted` for another).
735
+ * Suspend a tenant: from when this returns, its withTenant transactions may read but writes are
736
+ * refused (`tenant_suspended`); one in flight is waited for. Its keys still authenticate but it
737
+ * gets no new key. Maintenance and erasure go on. True when it was not suspended already.
738
+ * Deployment role only (`tenant_suspension_not_permitted`).
781
739
  */
782
740
  suspendTenant(params: {
783
741
  tenantRef: string;
@@ -797,11 +755,9 @@ export interface SlotlockStore extends SlotlockEventFeed {
797
755
  tenantRef: string;
798
756
  }): Promise<boolean>;
799
757
  /**
800
- * Erase a tenant: delete every row it owns (resources, holds, bookings, events with their
801
- * attendees and reminders, cancellations, idempotency records, coverage, API keys, and a personal
802
- * tenant's dashboard records) in one transaction, waiting for its transactions in flight. API key
803
- * digests are retired so no erased key works again; a suspension stays. Erasing again deletes
804
- * nothing.
758
+ * Erase a tenant: delete every row it owns in one transaction, waiting for its transactions in
759
+ * flight. API key digests are retired so no erased key works again; a suspension stays. Erasing
760
+ * again deletes nothing.
805
761
  */
806
762
  eraseTenant(params: {
807
763
  tenantRef: string;
@@ -817,12 +773,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
817
773
  coverageMaxAgeMs?: number;
818
774
  }): Promise<CalendarFreeBusy>;
819
775
  /**
820
- * The first slot of `durationMs` inside the hours, whose time and turnaround buffer are free:
821
- * getFreeBusy × expandRules (in the resource's stored timezone) → findNextAvailable. Null when no
822
- * slot is free, and when one cannot be proven free: the coverage of the window (with the buffer's
823
- * tail) is not complete, because a recurring series is not stored that far or a required provider
824
- * calendar is not read that far. Another tenant's resource, an unknown id or one the store never
825
- * issues is `resource_not_found`. A search window longer than 367 days is `invalid_window`.
776
+ * The first slot of `durationMs` inside the hours whose time and turnaround buffer are free
777
+ * (getFreeBusy × expandRules in the resource's timezone → findNextAvailable). Null when no slot
778
+ * is free or one cannot be proven free (coverage incomplete: a series not stored that far, or a
779
+ * required provider not read that far). Unknown or another tenant's resource is
780
+ * `resource_not_found`; a window over 367 days is `invalid_window`.
826
781
  */
827
782
  findNextAvailableFor(params: {
828
783
  /** The resource's tenant; `null` for a resource that has none (the legacy namespace). */
@@ -926,20 +881,16 @@ export interface SlotlockStoreOptions {
926
881
  /** Hard retained-command ceiling per non-internal owner. Defaults to 10,000; cannot be disabled. */
927
882
  agentOwnerCommandQuota?: number;
928
883
  /**
929
- * What one tenant may store, for a database tenants share (none by default). A write that would
930
- * pass a limit is refused with it (`*_limit_reached`); what the tenant has can still be revised
931
- * (a move adds no booking or event, so it is not refused), cancelled and released. Counted under
932
- * a lock per tenant and limit, so racing writers never pass it, in READ COMMITTED transactions
933
- * only (`invalid_transaction_isolation` otherwise). Bookings and events count while they are still
934
- * to come, and for 30 days after they were made, whatever time they hold; history older than that
935
- * does not count, and a count stops reading at the limit.
884
+ * What one tenant may store, for a shared database (none by default). A write that would pass a
885
+ * limit is refused (`*_limit_reached`); existing rows can still be revised, cancelled and
886
+ * released. Counted under a per-tenant lock in READ COMMITTED transactions only. Bookings and
887
+ * events count while still to come and for 30 days after they were made.
936
888
  */
937
889
  tenantLimits?: SlotlockTenantLimits;
938
890
  /**
939
- * How many withTenant transactions of one tenant run at once on this store (1-1,000; no limit by
940
- * default). The rest wait in this process, in order, so one tenant's transactions waiting on its
941
- * own locks hold at most this many of a pool's connections. Not counted inside a caller's
942
- * transaction, which holds its connection already.
891
+ * How many withTenant transactions of one tenant run at once on this store (1-1,000; unlimited by
892
+ * default). The rest wait in order, so one tenant waiting on its own locks holds at most this
893
+ * many pool connections. Not counted inside a caller's transaction.
943
894
  */
944
895
  maxTenantTransactions?: number;
945
896
  /**
@@ -949,11 +900,9 @@ export interface SlotlockStoreOptions {
949
900
  */
950
901
  tenantLockTimeoutMs?: number;
951
902
  /**
952
- * How many days history is kept, 1-3,650 (default 30): the change feed (and how far back a cursor
953
- * reaches) and waitlist entries that ended; and, when longer than 30 days, retired idempotency
954
- * answers, booking references and past events' commands (never shorter, so a delayed retry is still
955
- * answered as the first try was). Maintenance prunes what is older. A cursor before a change any
956
- * store has pruned answers `cursor_expired`, so stores set differently never skip a change.
903
+ * Days history is kept, 1-3,650 (default 30): the change feed (and cursor reach) and ended
904
+ * waitlist entries; and, when longer than 30 days, retired idempotency answers, booking
905
+ * references and past events' commands. A cursor before a pruned change answers `cursor_expired`.
957
906
  */
958
907
  retentionDays?: number;
959
908
  /**
@@ -972,10 +921,9 @@ export interface SlotlockTenantLimits {
972
921
  /** Resources, archived ones included; the lower of this and a call's `maxTenantResources`. */
973
922
  resources?: number;
974
923
  /**
975
- * Holds on the tenant's resources, expired ones included until they are deleted. A keyed hold
976
- * keeps its place until it would have expired, released or confirmed, while its answer is kept
977
- * for retries. A hold at the limit first deletes the tenant's expired holds and forgets their
978
- * answers, as maintenance does, so only live holds keep a new one out.
924
+ * Holds, expired ones included until deleted; a keyed hold keeps its place while its answer is
925
+ * kept. At the limit, expired holds and their answers are deleted first, so only live holds keep
926
+ * a new one out.
979
927
  */
980
928
  holds?: number;
981
929
  /**
@@ -984,9 +932,9 @@ export interface SlotlockTenantLimits {
984
932
  */
985
933
  reservations?: number;
986
934
  /**
987
- * Calendar events of every owner still to come (a series until it is known to have ended) or made
988
- * in the last 30 days, cancellations of every owner made in the last 30 days, and agent-owned
989
- * cancellations until maintenance prunes them. Each event stores at most 2,000 occurrences.
935
+ * Events of every owner still to come (a series until known to have ended) or made in the last 30
936
+ * days, cancellations made in the last 30 days, and agent cancellations until pruned. Each event
937
+ * stores at most 2,000 occurrences.
990
938
  */
991
939
  events?: number;
992
940
  /** Provider coverage rows: one per resource and source. */