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.
- package/CHANGELOG.md +542 -0
- package/NOTICE +2 -0
- package/README.md +4 -2
- package/agent-server.d.ts +25 -5
- package/agent-server.js +152 -7
- package/agent-store-backend.d.ts +6 -1
- package/agent-store-backend.js +1068 -68
- package/application-api.d.ts +28 -1
- package/application-api.js +789 -21
- package/approvals.d.ts +2 -0
- package/approvals.js +12 -5
- package/assistant.d.ts +34 -8
- package/assistant.js +81 -26
- package/availability-store.d.ts +193 -0
- package/availability-store.js +566 -0
- package/booking-rules.d.ts +103 -5
- package/booking-rules.js +263 -15
- package/config.d.ts +50 -0
- package/config.js +107 -0
- package/dashboard-assistant-ui.d.ts +1 -1
- package/dashboard-assistant-ui.js +109 -8
- package/dashboard-scope.d.ts +7 -0
- package/dashboard-scope.js +21 -0
- package/dashboard.d.ts +152 -8
- package/dashboard.js +3868 -399
- package/ddl-identity.d.ts +25 -0
- package/ddl-identity.js +881 -0
- package/ddl-scim.d.ts +5 -0
- package/ddl-scim.js +740 -0
- package/ddl-sso.d.ts +15 -0
- package/ddl-sso.js +1033 -0
- package/ddl.d.ts +44 -9
- package/ddl.js +2872 -47
- package/domains.d.ts +117 -0
- package/domains.js +340 -0
- package/event-feed.d.ts +2 -2
- package/event-feed.js +24 -34
- package/holiday-import.d.ts +38 -0
- package/holiday-import.js +56 -0
- package/holidays.d.ts +38 -0
- package/holidays.js +209 -0
- package/hours.d.ts +125 -0
- package/hours.js +224 -0
- package/index.d.ts +24 -5
- package/index.js +17 -3
- package/interop.d.ts +25 -7
- package/interop.js +145 -17
- package/invitation-email.d.ts +26 -0
- package/invitation-email.js +73 -0
- package/lifecycle.d.ts +31 -0
- package/lifecycle.js +115 -0
- package/maintenance.d.ts +26 -2
- package/maintenance.js +70 -2
- package/metrics.js +3 -0
- package/oidc.d.ts +135 -0
- package/oidc.js +452 -0
- package/openapi.json +20172 -9850
- package/package.json +2 -1
- package/ranges.d.ts +28 -0
- package/ranges.js +135 -0
- package/resource-search.d.ts +19 -0
- package/resource-search.js +153 -0
- package/rest-api.js +7 -0
- package/rules.d.ts +2 -0
- package/rules.js +2 -0
- package/scim-server.d.ts +19 -0
- package/scim-server.js +243 -0
- package/scim.d.ts +180 -0
- package/scim.js +416 -0
- package/self-host.d.ts +19 -6
- package/self-host.js +483 -28
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +637 -23
- package/store.js +2529 -147
- package/types.d.ts +230 -0
- package/types.js +68 -1
- package/workspace-domains.d.ts +146 -0
- package/workspace-domains.js +249 -0
- package/workspace-roles.d.ts +47 -0
- package/workspace-roles.js +304 -0
- package/workspace-scim.d.ts +89 -0
- package/workspace-scim.js +157 -0
- package/workspace-sso.d.ts +131 -0
- package/workspace-sso.js +180 -0
- package/workspaces.d.ts +240 -0
- 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
|
-
|
|
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>;
|