slotlock 0.8.0 → 0.10.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 (86) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/NOTICE +2 -0
  3. package/README.md +4 -2
  4. package/agent-server.d.ts +25 -5
  5. package/agent-server.js +152 -7
  6. package/agent-store-backend.d.ts +6 -1
  7. package/agent-store-backend.js +1068 -68
  8. package/application-api.d.ts +28 -1
  9. package/application-api.js +789 -21
  10. package/approvals.d.ts +2 -0
  11. package/approvals.js +12 -5
  12. package/assistant.d.ts +34 -8
  13. package/assistant.js +81 -26
  14. package/availability-store.d.ts +193 -0
  15. package/availability-store.js +566 -0
  16. package/booking-rules.d.ts +103 -5
  17. package/booking-rules.js +263 -15
  18. package/config.d.ts +50 -0
  19. package/config.js +107 -0
  20. package/dashboard-assistant-ui.d.ts +1 -1
  21. package/dashboard-assistant-ui.js +109 -8
  22. package/dashboard-scope.d.ts +7 -0
  23. package/dashboard-scope.js +21 -0
  24. package/dashboard.d.ts +152 -8
  25. package/dashboard.js +3868 -399
  26. package/ddl-identity.d.ts +25 -0
  27. package/ddl-identity.js +881 -0
  28. package/ddl-scim.d.ts +5 -0
  29. package/ddl-scim.js +740 -0
  30. package/ddl-sso.d.ts +15 -0
  31. package/ddl-sso.js +1033 -0
  32. package/ddl.d.ts +44 -9
  33. package/ddl.js +2872 -47
  34. package/domains.d.ts +117 -0
  35. package/domains.js +340 -0
  36. package/event-feed.d.ts +2 -2
  37. package/event-feed.js +24 -34
  38. package/holiday-import.d.ts +38 -0
  39. package/holiday-import.js +56 -0
  40. package/holidays.d.ts +38 -0
  41. package/holidays.js +209 -0
  42. package/hours.d.ts +125 -0
  43. package/hours.js +224 -0
  44. package/index.d.ts +24 -5
  45. package/index.js +17 -3
  46. package/interop.d.ts +25 -7
  47. package/interop.js +145 -17
  48. package/invitation-email.d.ts +26 -0
  49. package/invitation-email.js +73 -0
  50. package/lifecycle.d.ts +31 -0
  51. package/lifecycle.js +115 -0
  52. package/maintenance.d.ts +26 -2
  53. package/maintenance.js +70 -2
  54. package/metrics.js +3 -0
  55. package/oidc.d.ts +135 -0
  56. package/oidc.js +452 -0
  57. package/openapi.json +20172 -9850
  58. package/package.json +2 -1
  59. package/ranges.d.ts +28 -0
  60. package/ranges.js +135 -0
  61. package/resource-search.d.ts +19 -0
  62. package/resource-search.js +153 -0
  63. package/rest-api.js +7 -0
  64. package/rules.d.ts +2 -0
  65. package/rules.js +2 -0
  66. package/scim-server.d.ts +19 -0
  67. package/scim-server.js +243 -0
  68. package/scim.d.ts +180 -0
  69. package/scim.js +416 -0
  70. package/self-host.d.ts +19 -6
  71. package/self-host.js +483 -28
  72. package/slotlock.manifest.json +1 -1
  73. package/store.d.ts +637 -23
  74. package/store.js +2529 -147
  75. package/types.d.ts +230 -0
  76. package/types.js +68 -1
  77. package/workspace-domains.d.ts +146 -0
  78. package/workspace-domains.js +249 -0
  79. package/workspace-roles.d.ts +47 -0
  80. package/workspace-roles.js +304 -0
  81. package/workspace-scim.d.ts +89 -0
  82. package/workspace-scim.js +157 -0
  83. package/workspace-sso.d.ts +131 -0
  84. package/workspace-sso.js +180 -0
  85. package/workspaces.d.ts +240 -0
  86. package/workspaces.js +433 -0
package/types.d.ts CHANGED
@@ -5,6 +5,13 @@ export interface Interval {
5
5
  start: Date;
6
6
  end: Date;
7
7
  }
8
+ /** Hours a resource without its own inherits, as the database resolves them (RFC 0004 B2). */
9
+ export interface SlotlockInheritedAvailability {
10
+ source: 'location' | 'tenant';
11
+ /** The location whose hours apply, when `source` is `location`. */
12
+ location?: string;
13
+ rules: WeeklyAvailabilityRule[];
14
+ }
8
15
  /** A schedulable resource (vehicle, room, worker, machine, or any other reservable asset). */
9
16
  export interface SlotlockResource {
10
17
  id: string;
@@ -22,6 +29,11 @@ export interface SlotlockResource {
22
29
  * uses the default (`SLOTLOCK_AVAILABILITY`, or the backend's `availabilityRules`).
23
30
  */
24
31
  availabilityRules?: WeeklyAvailabilityRule[];
32
+ /**
33
+ * The hours it inherits when it has none of its own (RFC 0004 B2): its location's, the nearest
34
+ * up the chain, else its tenant's. Absent when it has its own, or inherits the server's.
35
+ */
36
+ inheritedAvailability?: SlotlockInheritedAvailability;
25
37
  /**
26
38
  * `shared` resources are listed to and booked by agents; `private` ones are invisible to agents.
27
39
  * Fixed at creation; absent means shared.
@@ -89,6 +101,11 @@ export interface SlotlockApprovalPolicy {
89
101
  /** How long a person has to decide, before the booking's start less `leadMinutes`. */
90
102
  approvalTtlMinutes: number;
91
103
  leadMinutes: number;
104
+ /**
105
+ * It also holds the tenant's own bookings through the application API (bundles and pool
106
+ * bookings); without it, it holds only agents' (RFC 0004 B7).
107
+ */
108
+ includeApplication: boolean;
92
109
  createdAt: Date;
93
110
  updatedAt: Date;
94
111
  }
@@ -148,6 +165,12 @@ export interface SlotlockReservation {
148
165
  /** Monotonic command revision; legacy, hold, and shadow rows start at 1. */
149
166
  revision: number;
150
167
  source: string | null;
168
+ /** Whom to tell about it (RFC 0004 P3), when it names someone. */
169
+ contactRef?: string;
170
+ /** Present, and false, when no one should be notified about it. */
171
+ notify?: false;
172
+ /** Minutes before its start it is reminded, latest first, when it is. */
173
+ reminderMinutes?: number[];
151
174
  }
152
175
  export interface ExternalReservationCommand {
153
176
  tenantRef: string;
@@ -158,6 +181,34 @@ export interface ExternalReservationCommand {
158
181
  bufferAfterMs: number;
159
182
  revision: number;
160
183
  source?: string;
184
+ /**
185
+ * An admin's reason for moving the booking past its reschedule cutoff (RFC 0004 B5), kept on the
186
+ * feed. Without it, a move past the cutoff is refused.
187
+ */
188
+ overrideReason?: string;
189
+ /**
190
+ * The wall time the booking starts at in its resource's zone, `YYYY-MM-DDTHH:MM[:SS]` (RFC 0004
191
+ * B9): it must name `start` now, and if zone rules later move it, maintenance says so or follows
192
+ * it (`followZoneRules`). Left out, the booking keeps its instant only.
193
+ */
194
+ localStart?: string;
195
+ /**
196
+ * Whom to tell about it (RFC 0004 P3): a contact's reference (`putContact`), never the person.
197
+ * Put on the feed with each of its changes; a sender looks the person up by it.
198
+ */
199
+ contactRef?: string;
200
+ /** False: no sender should notify anyone about it (default true). On the feed only when false. */
201
+ notify?: boolean;
202
+ /**
203
+ * Minutes before its start to remind whom it names (RFC 0004 P3): up to five, each 0 to a year,
204
+ * said on the feed once each as `reminder.due` by maintenance, and again if the booking moves.
205
+ */
206
+ reminderMinutes?: number[];
207
+ }
208
+ /** What a cancellation records for the integrator to charge (RFC 0004 B5). Slotlock moves no money. */
209
+ export interface SlotlockRecordedFee {
210
+ amountMinor: number;
211
+ currency: string;
161
212
  }
162
213
  export type ExternalReservationResult = {
163
214
  ok: true;
@@ -166,6 +217,14 @@ export type ExternalReservationResult = {
166
217
  } | {
167
218
  ok: false;
168
219
  code: 'invalid_window';
220
+ }
221
+ /** `localStart` is not a wall time, or does not name `start` in the resource's zone. */
222
+ | {
223
+ ok: false;
224
+ code: 'invalid_local_start';
225
+ } | {
226
+ ok: false;
227
+ code: 'invalid_contact_ref' | 'invalid_notify' | 'invalid_reminders';
169
228
  } | {
170
229
  ok: false;
171
230
  code: 'invalid_buffer';
@@ -227,6 +286,13 @@ export type CancelExternalReservationResult = {
227
286
  ok: true;
228
287
  cancelled: boolean;
229
288
  revision: number;
289
+ cancellationFee?: SlotlockRecordedFee;
290
+ }
291
+ /** The booking's cancel cutoff has passed (RFC 0004 B5): an admin may override with a reason. */
292
+ | {
293
+ ok: false;
294
+ code: 'booking_rule_violated';
295
+ rule: 'cancel_cutoff';
230
296
  } | {
231
297
  ok: false;
232
298
  code: 'invalid_identity';
@@ -317,6 +383,21 @@ export interface SlotlockHold {
317
383
  source: string | null;
318
384
  /** The alternatives it was held with (`groupId` when acquired); absent when held alone. */
319
385
  groupId?: string;
386
+ /** Present when it waits for payment (RFC 0004 C3): it is confirmed only once paid. */
387
+ paymentRequired?: true;
388
+ /** The payment's reference at the integrator's processor, once paid. */
389
+ paymentRef?: string | null;
390
+ paidAt?: Date | null;
391
+ /** What the payment must be, when the hold names it. */
392
+ paymentAmount?: {
393
+ amountMinor: number;
394
+ currency: string;
395
+ } | null;
396
+ /** What was received, once paid and when the payer said. */
397
+ paidAmount?: {
398
+ amountMinor: number;
399
+ currency: string;
400
+ } | null;
320
401
  }
321
402
  /** One entry of a tenant's agenda (`listAgenda`): what occupies a resource, and from when. */
322
403
  export interface SlotlockAgendaEntry {
@@ -345,6 +426,16 @@ export type AcquireHoldResult =
345
426
  } | {
346
427
  ok: false;
347
428
  code: 'invalid_ttl';
429
+ }
430
+ /** `payment` without `paymentRequired`, or not an amount and an ISO 4217 currency. */
431
+ | {
432
+ ok: false;
433
+ code: 'invalid_payment';
434
+ }
435
+ /** An agent's `payment` that is not the resource's hold price, or where it sets none. */
436
+ | {
437
+ ok: false;
438
+ code: 'payment_price_mismatch';
348
439
  } | {
349
440
  ok: false;
350
441
  code: 'invalid_buffer';
@@ -412,6 +503,16 @@ export type ConfirmHoldResult = {
412
503
  ok: false;
413
504
  code: 'hold_expired';
414
505
  }
506
+ /** The hold waits for payment (RFC 0004 C3) and none is recorded yet (markHoldPaid). */
507
+ | {
508
+ ok: false;
509
+ code: 'payment_required';
510
+ }
511
+ /** A calendar reported its time busy (RFC 0004 C1) after the hold was made. */
512
+ | {
513
+ ok: false;
514
+ code: 'overlap';
515
+ }
415
516
  /** A cancellation of the hold's reference (its id) arrived first and retired it. */
416
517
  | {
417
518
  ok: false;
@@ -437,6 +538,77 @@ export type ReleaseHoldResult = {
437
538
  ok: true;
438
539
  released: boolean;
439
540
  };
541
+ /**
542
+ * A person a booking may name to be told about it (RFC 0004 P3), by the tenant's own reference: how
543
+ * to reach them, and which channels they agreed to. A sender reads it; Slotlock sends nothing.
544
+ */
545
+ export interface SlotlockContact {
546
+ ref: string;
547
+ name: string | null;
548
+ email: string | null;
549
+ /** E.164, for SMS. */
550
+ phone: string | null;
551
+ /** E.164, for WhatsApp. */
552
+ whatsapp: string | null;
553
+ /** Each channel the person said yes or no to; a channel left out has no answer recorded. */
554
+ consent: SlotlockContactConsent;
555
+ /** When each answer in `consent` was given (kept while it stands): the proof of it. */
556
+ consentAt: Partial<Record<keyof SlotlockContactConsent, Date>>;
557
+ /** BCP 47, for the language of what is sent. */
558
+ locale: string | null;
559
+ createdAt: Date;
560
+ updatedAt: Date;
561
+ }
562
+ export interface SlotlockContactConsent {
563
+ email?: boolean;
564
+ sms?: boolean;
565
+ whatsapp?: boolean;
566
+ }
567
+ export type PutContactResult = {
568
+ ok: true;
569
+ contact: SlotlockContact;
570
+ created: boolean;
571
+ } | {
572
+ ok: false;
573
+ code: 'invalid_contact_ref' | 'invalid_contact';
574
+ } | {
575
+ ok: false;
576
+ code: 'contact_limit_reached';
577
+ limit: number;
578
+ };
579
+ /** Recording a hold's payment (RFC 0004 C3); `idempotent` when it was recorded already. */
580
+ export type MarkHoldPaidResult = {
581
+ ok: true;
582
+ hold: SlotlockHold;
583
+ idempotent: boolean;
584
+ } | {
585
+ ok: false;
586
+ code: 'invalid_identity' | 'invalid_payment_ref' | 'invalid_payment';
587
+ }
588
+ /** Less than the hold's payment, in another currency, or not said when the hold names one. */
589
+ | {
590
+ ok: false;
591
+ code: 'payment_amount_mismatch';
592
+ } | {
593
+ ok: false;
594
+ code: 'hold_not_found' | 'hold_expired';
595
+ }
596
+ /** Paid, but the hold ended without a booking: the payment was never applied. */
597
+ | {
598
+ ok: false;
599
+ code: 'hold_ended';
600
+ }
601
+ /** The hold does not wait for payment. */
602
+ | {
603
+ ok: false;
604
+ code: 'payment_not_required';
605
+ }
606
+ /** It was already paid by another payment, `paymentRef`. */
607
+ | {
608
+ ok: false;
609
+ code: 'payment_conflict';
610
+ paymentRef: string;
611
+ };
440
612
  /** RFC 5545 TRANSP projection. Transparent events remain visible but never occupy a resource. */
441
613
  export type CalendarTransparency = 'opaque' | 'transparent';
442
614
  export type CalendarEventStatus = 'confirmed' | 'tentative';
@@ -543,6 +715,11 @@ export interface PutCalendarEventCommand extends CalendarEventContent {
543
715
  holdId: string;
544
716
  holderRef: string;
545
717
  };
718
+ /**
719
+ * False: no sender should notify anyone about it (RFC 0004 P3), and its reminders are not put on
720
+ * the feed. Default true; as sent with each revision.
721
+ */
722
+ notify?: boolean;
546
723
  }
547
724
  export type PutCalendarEventResult = {
548
725
  ok: true;
@@ -604,6 +781,11 @@ export type PutCalendarEventResult = {
604
781
  ok: false;
605
782
  code: 'hold_expired';
606
783
  }
784
+ /** `fromHold`'s hold waits for payment (RFC 0004 C3) and none is recorded yet. */
785
+ | {
786
+ ok: false;
787
+ code: 'payment_required';
788
+ }
607
789
  /** `fromHold`'s hold is on another resource, or does not cover the event. */
608
790
  | {
609
791
  ok: false;
@@ -644,6 +826,8 @@ export type CancelCalendarEventResult = {
644
826
  externalRef: string;
645
827
  revision: number;
646
828
  idempotent: boolean;
829
+ /** What a late cancellation costs, recorded for the integrator to charge (RFC 0004 B5). */
830
+ cancellationFee?: SlotlockRecordedFee;
647
831
  } | {
648
832
  ok: false;
649
833
  code: 'invalid_identity';
@@ -664,6 +848,12 @@ export type CancelCalendarEventResult = {
664
848
  ok: false;
665
849
  code: 'revision_conflict';
666
850
  currentRevision: number;
851
+ }
852
+ /** The event's cancel cutoff has passed (RFC 0004 B5). */
853
+ | {
854
+ ok: false;
855
+ code: 'booking_rule_violated';
856
+ rule: 'cancel_cutoff';
667
857
  };
668
858
  /** A place in the roll's order of due masters: the last one a batch examined. */
669
859
  export interface CalendarHorizonRollCursor {
@@ -763,6 +953,30 @@ export type CalendarCoverageResult = {
763
953
  code: 'coverage_limit_reached';
764
954
  limit: number;
765
955
  };
956
+ /**
957
+ * What a calendar that gives busy time but not its events says (Google freeBusy, Microsoft
958
+ * getSchedule; RFC 0004 C1): its coverage of the window, and the busy time in it, which replaces
959
+ * what the source said of that window before.
960
+ */
961
+ export interface SourceBusyCommand extends CalendarCoverageCommand {
962
+ /** At most SLOTLOCK_SOURCE_BUSY_MAX intervals; clipped to the window and merged. */
963
+ busy: Interval[];
964
+ }
965
+ export type SourceBusyResult = {
966
+ ok: true;
967
+ revision: number;
968
+ /** A revision already recorded: nothing changed. */
969
+ idempotent: boolean;
970
+ /** How many intervals are kept, merged and clipped. */
971
+ busy: number;
972
+ /** Bookings the source newly reports in the way of, each put on the feed as coverage.conflict. */
973
+ conflicts: number;
974
+ } | Exclude<CalendarCoverageResult, {
975
+ ok: true;
976
+ }> | {
977
+ ok: false;
978
+ code: 'invalid_busy';
979
+ };
766
980
  export interface CalendarCoverageAssessment {
767
981
  state: 'complete' | 'partial' | 'unknown';
768
982
  /** Required sources that are absent, stale, or do not fully cover the requested window. */
@@ -772,3 +986,19 @@ export interface CalendarFreeBusy {
772
986
  busy: Interval[];
773
987
  coverage: CalendarCoverageAssessment;
774
988
  }
989
+ /**
990
+ * The tables a subject export lists and an erasure counts (RFC 0004 D3), in that order: what can
991
+ * name one person.
992
+ */
993
+ export declare const SLOTLOCK_SUBJECT_TABLES: readonly ["contacts", "calendar_event_attendees", "calendar_events", "reservations", "reservation_tombstones", "reservation_conflict_archive", "calendar_event_tombstones", "calendar_event_commands", "waitlist_entries", "approvals", "outbox"];
994
+ export type SlotlockSubjectTable = (typeof SLOTLOCK_SUBJECT_TABLES)[number];
995
+ /**
996
+ * What a tenant export holds (RFC 0004 D3), in this order: each table's rows as kept, without the
997
+ * tenant and without secrets (a webhook's signing secret, the assistant's sealed provider key, API
998
+ * key digests: `api_keys` is each key's name, prefix, scopes and dates). Every other tenant table
999
+ * is in SLOTLOCK_TENANT_EXPORT_LEFT_OUT, with why.
1000
+ */
1001
+ export declare const SLOTLOCK_TENANT_EXPORT_TABLES: readonly ["resources", "locations", "resource_groups", "resource_group_members", "pool_settings", "booking_rule_defaults", "availability_defaults", "availability_exceptions", "holiday_calendars", "reservations", "reservation_tombstones", "reservation_conflict_archive", "hold_payments", "calendar_events", "calendar_event_attendees", "calendar_event_reminders", "calendar_event_exceptions", "calendar_event_tombstones", "calendar_coverage", "source_busy", "waitlist_entries", "approval_policies", "approvals", "contacts", "webhook_endpoints", "assistant_settings", "api_keys", "outbox", "audit_events"];
1002
+ export type SlotlockTenantExportTable = (typeof SLOTLOCK_TENANT_EXPORT_TABLES)[number];
1003
+ /** The tenant tables an export leaves out, and why: each is derived, internal or one member's own. */
1004
+ export declare const SLOTLOCK_TENANT_EXPORT_LEFT_OUT: Readonly<Record<string, string>>;
package/types.js CHANGED
@@ -1 +1,68 @@
1
- export {};
1
+ /**
2
+ * The tables a subject export lists and an erasure counts (RFC 0004 D3), in that order: what can
3
+ * name one person.
4
+ */
5
+ export const SLOTLOCK_SUBJECT_TABLES = Object.freeze([
6
+ 'contacts',
7
+ 'calendar_event_attendees',
8
+ 'calendar_events',
9
+ 'reservations',
10
+ 'reservation_tombstones',
11
+ 'reservation_conflict_archive',
12
+ 'calendar_event_tombstones',
13
+ 'calendar_event_commands',
14
+ 'waitlist_entries',
15
+ 'approvals',
16
+ 'outbox',
17
+ ]);
18
+ /**
19
+ * What a tenant export holds (RFC 0004 D3), in this order: each table's rows as kept, without the
20
+ * tenant and without secrets (a webhook's signing secret, the assistant's sealed provider key, API
21
+ * key digests: `api_keys` is each key's name, prefix, scopes and dates). Every other tenant table
22
+ * is in SLOTLOCK_TENANT_EXPORT_LEFT_OUT, with why.
23
+ */
24
+ export const SLOTLOCK_TENANT_EXPORT_TABLES = Object.freeze([
25
+ 'resources',
26
+ 'locations',
27
+ 'resource_groups',
28
+ 'resource_group_members',
29
+ 'pool_settings',
30
+ 'booking_rule_defaults',
31
+ 'availability_defaults',
32
+ 'availability_exceptions',
33
+ 'holiday_calendars',
34
+ 'reservations',
35
+ 'reservation_tombstones',
36
+ 'reservation_conflict_archive',
37
+ 'hold_payments',
38
+ 'calendar_events',
39
+ 'calendar_event_attendees',
40
+ 'calendar_event_reminders',
41
+ 'calendar_event_exceptions',
42
+ 'calendar_event_tombstones',
43
+ 'calendar_coverage',
44
+ 'source_busy',
45
+ 'waitlist_entries',
46
+ 'approval_policies',
47
+ 'approvals',
48
+ 'contacts',
49
+ 'webhook_endpoints',
50
+ 'assistant_settings',
51
+ 'api_keys',
52
+ 'outbox',
53
+ 'audit_events',
54
+ ]);
55
+ /** The tenant tables an export leaves out, and why: each is derived, internal or one member's own. */
56
+ export const SLOTLOCK_TENANT_EXPORT_LEFT_OUT = Object.freeze({
57
+ calendar_event_occurrences: "derived from each event's time and recurrence",
58
+ calendar_event_commands: 'records of retried writes',
59
+ hold_commands: 'records of retried writes',
60
+ outbox_pruned: 'where the history was last pruned to',
61
+ composite_claims: 'derived from bookings',
62
+ reminder_dispatches: 'which reminders were put on the feed',
63
+ reservation_reminder_dispatches: 'which reminders were put on the feed',
64
+ assistant_conversations: "each member's own conversations with the assistant",
65
+ assistant_chats: "each member's own conversations with the assistant",
66
+ tenant_exports: 'the exports themselves',
67
+ tenant_export_parts: 'the exports themselves',
68
+ });
@@ -0,0 +1,146 @@
1
+ import { type SlotlockDnsResolver, type SlotlockDomainCheck } from './domains.js';
2
+ import type { SlotlockAuditEventInput, SlotlockSql } from './store.js';
3
+ import { type SlotlockWorkspaceRole } from './workspace-roles.js';
4
+ /** Why a change to a team's domains, or a request to join, was refused. */
5
+ export type SlotlockDomainChangeRefusal = 'invalid_domain' | 'public_suffix' | 'consumer_domain' | 'not_found' | 'not_allowed' | 'already_claimed' | 'domain_taken' | 'domain_limit' | 'too_soon' | 'expired' | 'restricted' | 'no_verified_domain' | 'already_member' | 'pending' | 'request_limit' | 'member_cap' | 'role_not_allowed' | 'domain_not_verified';
6
+ /**
7
+ * How the addresses a person brings were verified: by a Google Workspace account of their domain
8
+ * (Google's `hd` claim; the company controls it), or by a Google or GitHub account that verified the
9
+ * address once and never checks again. Only the first may join at once.
10
+ */
11
+ export type SlotlockJoinEvidence = 'google_workspace' | 'google' | 'github';
12
+ /** What a domain lets the people whose verified address is at it do. */
13
+ export type SlotlockDomainJoinPolicy = 'off' | 'request' | 'auto';
14
+ /** A team's domain as its Identity page shows it. */
15
+ export interface SlotlockWorkspaceDomain {
16
+ id: string;
17
+ ascii: string;
18
+ unicode: string;
19
+ /** `pending` (with `expired` past its seven days), `verified`, or `lapsing` (its record has gone). */
20
+ status: 'pending' | 'verified' | 'lapsing';
21
+ expired: boolean;
22
+ /** The token the record must hold: public, since it is published. */
23
+ token: string;
24
+ joinPolicy: SlotlockDomainJoinPolicy;
25
+ createdAt: Date;
26
+ verifiedAt: Date | null;
27
+ lastCheckedAt: Date | null;
28
+ missingSince: Date | null;
29
+ errorSince: Date | null;
30
+ lapseAfter: Date | null;
31
+ }
32
+ /** A team a person may ask to join, or join, through a verified address. */
33
+ export interface SlotlockDiscoverableWorkspace {
34
+ tenantRef: string;
35
+ handle: string;
36
+ name: string;
37
+ /** As people read it. */
38
+ domain: string;
39
+ /** As addresses are matched on it (ASCII). */
40
+ domainKey: string;
41
+ joinPolicy: 'request' | 'auto';
42
+ requested: boolean;
43
+ }
44
+ /** A request waiting on a team. */
45
+ export interface SlotlockJoinRequest {
46
+ id: string;
47
+ person: string;
48
+ display: string | null;
49
+ email: string;
50
+ domain: string;
51
+ createdAt: Date;
52
+ expiresAt: Date;
53
+ evidence: SlotlockJoinEvidence;
54
+ }
55
+ /** What one claim's check did, for the audit log and the people told. */
56
+ export interface SlotlockDomainCheckOutcome {
57
+ domainId: string;
58
+ tenantRef: string | null;
59
+ domain: string | null;
60
+ /**
61
+ * `verified`, `restored` (a lapsing domain's new record found), `seen` (still there), `missing`
62
+ * (gone once), `lapsing` (gone a day: a new token issued), `lapsed`, `absent` (a pending claim's
63
+ * record not there yet), `unchanged`, `error`; or a refusal: `domain_taken`, `too_soon`,
64
+ * `expired`, `not_found`, `not_allowed`.
65
+ */
66
+ outcome: string;
67
+ check: SlotlockDomainCheck | null;
68
+ }
69
+ export interface SlotlockWorkspaceDomains {
70
+ /** Claim a domain for a team (its Owner), with a new token. The domain as typed: normalised here. */
71
+ claim(tenantRef: string, actor: string, typed: string): Promise<{
72
+ id: string;
73
+ } | {
74
+ refusal: SlotlockDomainChangeRefusal;
75
+ }>;
76
+ list(tenantRef: string): Promise<SlotlockWorkspaceDomain[]>;
77
+ /** An Owner's "Verify now": the record looked up and the outcome applied (at most once a minute). */
78
+ verifyNow(tenantRef: string, actor: string, id: string, resolvers: readonly SlotlockDnsResolver[]): Promise<SlotlockDomainCheckOutcome>;
79
+ /**
80
+ * The daily check: up to `limit` claims due, each looked up and applied. `seen` is told each claim's
81
+ * missing_since before its check (null when its record was there at the last one).
82
+ */
83
+ checkDue(resolvers: readonly SlotlockDnsResolver[], limit?: number, seen?: (id: string, missingSince: Date | null) => void): Promise<SlotlockDomainCheckOutcome[]>;
84
+ release(tenantRef: string, actor: string, id: string): Promise<SlotlockDomainChangeRefusal | null>;
85
+ restrictInvites(tenantRef: string, actor: string, on: boolean): Promise<SlotlockDomainChangeRefusal | null>;
86
+ /** Whether a team's invitations are held to its verified domains. */
87
+ invitesRestricted(tenantRef: string): Promise<boolean>;
88
+ setJoinPolicy(tenantRef: string, actor: string, id: string, policy: SlotlockDomainJoinPolicy): Promise<SlotlockDomainChangeRefusal | null>;
89
+ /** The teams a person's verified addresses let them ask to join. */
90
+ discoverable(person: string, emails: readonly string[]): Promise<SlotlockDiscoverableWorkspace[]>;
91
+ requestToJoin(tenantRef: string, person: string, emails: readonly string[], evidence: SlotlockJoinEvidence, memberCap: number | null): Promise<{
92
+ outcome: 'joined' | 'requested';
93
+ } | {
94
+ refusal: SlotlockDomainChangeRefusal;
95
+ }>;
96
+ requests(tenantRef: string): Promise<SlotlockJoinRequest[]>;
97
+ decide(params: {
98
+ tenantRef: string;
99
+ actor: string;
100
+ id: string;
101
+ decision: 'approve' | 'decline';
102
+ role: SlotlockWorkspaceRole | null;
103
+ memberCap: number | null;
104
+ }): Promise<{
105
+ person: string | null;
106
+ refusal: SlotlockDomainChangeRefusal | null;
107
+ }>;
108
+ /** Members a domain let in: with the domain, and whether the team still holds it. */
109
+ domainMembers(tenantRef: string): Promise<Map<string, {
110
+ domain: string;
111
+ status: string;
112
+ }>>;
113
+ prune(): Promise<number>;
114
+ }
115
+ /**
116
+ * The addresses as matched: lowercase, the domain in its ASCII form, the domain claimable at all
117
+ * (a consumer address matches no team). At most ten.
118
+ */
119
+ export declare function slotlockMatchableEmails(emails: readonly string[]): string[];
120
+ /** Through the functions of ddl-identity.ts. */
121
+ export declare function createSlotlockWorkspaceDomains(sql: SlotlockSql): SlotlockWorkspaceDomains;
122
+ /** The role a request may be approved as: Viewer, Member or Developer. */
123
+ export declare function slotlockJoinRole(value: string | null): SlotlockWorkspaceRole | null;
124
+ /** The audit log's actions for a team's domains (its Identity group). */
125
+ export declare const SLOTLOCK_DOMAIN_AUDIT_ACTIONS: readonly ["domain.claimed", "domain.verified", "domain.record_missing", "domain.lapsing", "domain.restored", "domain.lapsed", "domain.released", "domain.join_policy_changed", "workspace.invites_restricted", "workspace.invites_unrestricted"];
126
+ /**
127
+ * The audit event a check that changed something records: which domain, and what each resolver said
128
+ * (public DNS data). A domain gone for the first time is recorded once (`missing` while it had been
129
+ * seen); repeats and checks that changed nothing are not.
130
+ */
131
+ export declare function slotlockDomainCheckEvent(found: SlotlockDomainCheckOutcome, firstMissing: boolean): {
132
+ action: string;
133
+ targetType: 'domain';
134
+ targetRef: string;
135
+ metadata: Record<string, unknown>;
136
+ } | null;
137
+ /**
138
+ * The daily check, from the server's maintenance: the claims due looked up and applied, each change
139
+ * recorded in its team's audit log as Slotlock's. How many were checked.
140
+ */
141
+ export declare function runSlotlockDomainChecks(params: {
142
+ domains: SlotlockWorkspaceDomains;
143
+ resolvers: readonly SlotlockDnsResolver[];
144
+ record?: (tenantRef: string, event: SlotlockAuditEventInput) => Promise<void>;
145
+ limit?: number;
146
+ }): Promise<number>;