slotlock 0.9.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 (61) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/NOTICE +2 -0
  3. package/README.md +4 -2
  4. package/agent-store-backend.js +209 -15
  5. package/application-api.d.ts +10 -1
  6. package/application-api.js +213 -2
  7. package/availability-store.js +13 -5
  8. package/booking-rules.d.ts +11 -0
  9. package/booking-rules.js +21 -1
  10. package/config.d.ts +50 -0
  11. package/config.js +107 -0
  12. package/dashboard-scope.d.ts +7 -0
  13. package/dashboard-scope.js +21 -0
  14. package/dashboard.d.ts +97 -2
  15. package/dashboard.js +3068 -284
  16. package/ddl-identity.d.ts +25 -0
  17. package/ddl-identity.js +881 -0
  18. package/ddl-scim.d.ts +5 -0
  19. package/ddl-scim.js +740 -0
  20. package/ddl-sso.d.ts +15 -0
  21. package/ddl-sso.js +1033 -0
  22. package/ddl.d.ts +40 -9
  23. package/ddl.js +2123 -49
  24. package/domains.d.ts +117 -0
  25. package/domains.js +340 -0
  26. package/event-feed.d.ts +1 -1
  27. package/event-feed.js +6 -34
  28. package/holiday-import.d.ts +1 -1
  29. package/holiday-import.js +2 -3
  30. package/holidays.js +70 -11
  31. package/index.d.ts +12 -4
  32. package/index.js +8 -2
  33. package/invitation-email.d.ts +26 -0
  34. package/invitation-email.js +73 -0
  35. package/maintenance.d.ts +7 -2
  36. package/maintenance.js +15 -1
  37. package/oidc.d.ts +135 -0
  38. package/oidc.js +452 -0
  39. package/openapi.json +1886 -6
  40. package/package.json +2 -1
  41. package/scim-server.d.ts +19 -0
  42. package/scim-server.js +243 -0
  43. package/scim.d.ts +180 -0
  44. package/scim.js +416 -0
  45. package/self-host.d.ts +19 -6
  46. package/self-host.js +262 -7
  47. package/slotlock.manifest.json +1 -1
  48. package/store.d.ts +273 -2
  49. package/store.js +984 -72
  50. package/types.d.ts +33 -1
  51. package/types.js +68 -1
  52. package/workspace-domains.d.ts +146 -0
  53. package/workspace-domains.js +249 -0
  54. package/workspace-roles.d.ts +47 -0
  55. package/workspace-roles.js +304 -0
  56. package/workspace-scim.d.ts +89 -0
  57. package/workspace-scim.js +157 -0
  58. package/workspace-sso.d.ts +131 -0
  59. package/workspace-sso.js +180 -0
  60. package/workspaces.d.ts +240 -0
  61. package/workspaces.js +433 -0
package/store.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { type SlotlockResourceKind, type SlotlockResourceProfileInput, type SlotlockLocation, type SlotlockPoolOrder, type SlotlockResourceGroup, type SlotlockResourceScope } from './resource-profile.js';
2
2
  import { type SlotlockUsageRow } from './usage.js';
3
+ import { type SlotlockWorkspaceRole } from './workspace-roles.js';
3
4
  import { type SlotlockChangeOrigin, type SlotlockEventFeed } from './event-feed.js';
4
5
  import { type SlotlockBookingRules } from './booking-rules.js';
5
6
  import type { Sql, TransactionSql } from 'postgres';
6
7
  import { SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX } from './rules.js';
7
8
  import type { AcquireHoldResult, MarkHoldPaidResult, PutContactResult, SlotlockContact, SlotlockContactConsent, CalendarCoverageCommand, CalendarCoverageResult, SourceBusyCommand, SourceBusyResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockBookingDescription, SlotlockApproval, SlotlockApprovalPolicy, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
9
+ import { type SlotlockSubjectTable, type SlotlockTenantExportTable } from './types.js';
8
10
  import { type SlotlockAvailabilityDefaults, type SlotlockAvailabilityDefaultsResult, type SlotlockAvailabilityExceptionInput, type SlotlockOverrideResources, type SlotlockHolidayCalendar, type SlotlockHolidayImport } from './availability-store.js';
9
11
  import type { SlotlockAvailabilityException, SlotlockRankedException } from './hours.js';
10
12
  import type { SlotlockHoliday } from './holidays.js';
@@ -518,7 +520,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
518
520
  }): Promise<{
519
521
  closures: number;
520
522
  } | null>;
521
- /** Subscriptions due to be imported again, the longest waiting first. */
523
+ /**
524
+ * Subscriptions due to be imported again, each put off a day before it is read (maintenance):
525
+ * importing it sets its next import, so one a pass never finishes is not tried again at once.
526
+ */
522
527
  dueHolidayCalendars(params: {
523
528
  tenantRef: string;
524
529
  limit: number;
@@ -674,12 +679,19 @@ export interface SlotlockStore extends SlotlockEventFeed {
674
679
  amountMinor: number;
675
680
  currency: string;
676
681
  };
682
+ /**
683
+ * An agent's hold (RFC 0004 C3): its price is the resource's `holdPrice` booking rule, never its
684
+ * own. On a resource with one, it waits for that payment whatever it asks (and lives at most
685
+ * SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS); a `payment` other than that price is refused
686
+ * (`payment_price_mismatch`), as is any `payment` where the resource sets none.
687
+ */
688
+ priceFromRules?: boolean;
677
689
  }): Promise<AcquireHoldResult>;
678
690
  /**
679
691
  * Record that a hold waiting for payment is paid (RFC 0004 C3), by the payment's reference at the
680
692
  * integrator's processor: the integrator says so, or an adapter that verified it. Once paid it
681
693
  * may be confirmed. The same reference again answers the same hold, whatever became of it since
682
- * (confirmed, booked, expired: the payment is kept apart); another is `payment_conflict`. The feed says so (`hold.paid`). A hold that does not wait for payment is
694
+ * (confirmed or booked from: the payment is kept apart), unless the hold ended without a booking (`hold_ended`: never applied, so refunded); another is `payment_conflict`, naming the one recorded. The feed says so (`hold.paid`). A hold that does not wait for payment is
683
695
  * `payment_not_required`; an expired one `hold_expired`; one confirmed, released or unknown
684
696
  * `hold_not_found`.
685
697
  */
@@ -731,6 +743,98 @@ export interface SlotlockStore extends SlotlockEventFeed {
731
743
  deleted: boolean;
732
744
  bookings: number;
733
745
  }>;
746
+ /**
747
+ * What the tenant keeps about one person (RFC 0004 D3, an access request): the rows that name
748
+ * them, as kept, by table (SLOTLOCK_SUBJECT_TABLES), up to `limit` (1-10,000, default 1,000) a
749
+ * table. It lists what eraseSubject would delete or take them out of; an event lists its time and
750
+ * title, and an approval its decision, never the other people they name.
751
+ */
752
+ exportSubject(params: {
753
+ tenantRef: string;
754
+ subject: SlotlockSubject;
755
+ limit?: number;
756
+ }): Promise<SlotlockSubjectExport>;
757
+ /**
758
+ * Take one person out of the tenant's data (RFC 0004 D3, an erasure request), in one transaction.
759
+ * Their contacts, attendee entries and waitlist entries are deleted; their email leaves events'
760
+ * attendee and organizer lists and approvals' requests; their holder reference and booking
761
+ * reference become one `erased-` name wherever they were (bookings, holds, ended bookings, the
762
+ * change history, approvals), and a contact's reference leaves them. Bookings stay, as time taken.
763
+ * Counts, by table, the rows deleted or changed.
764
+ */
765
+ eraseSubject(params: {
766
+ tenantRef: string;
767
+ subject: SlotlockSubject;
768
+ }): Promise<SlotlockSubjectErasure>;
769
+ /**
770
+ * Ask for a copy of everything the tenant keeps (RFC 0004 D3): a pending export, which
771
+ * buildTenantExport builds. Refused while another is pending (`export_in_progress`) or when the
772
+ * tenant keeps SLOTLOCK_MAX_TENANT_EXPORTS already (`export_limit_reached`).
773
+ */
774
+ createTenantExport(params: {
775
+ tenantRef: string;
776
+ requestedBy?: string;
777
+ }): Promise<SlotlockTenantExport>;
778
+ /**
779
+ * Build a pending export: every table of SLOTLOCK_TENANT_EXPORT_TABLES read through one cursor,
780
+ * so in one snapshot, into parts of one table's rows. Null when it is not pending, or another is
781
+ * building it. Throws `export_too_large` past SLOTLOCK_TENANT_EXPORT_MAX_BYTES, writing nothing.
782
+ */
783
+ buildTenantExport(params: {
784
+ tenantRef: string;
785
+ exportId: string;
786
+ }): Promise<SlotlockTenantExport | null>;
787
+ /** Mark a pending export failed, with why (`export_too_large`, `export_failed`). */
788
+ failTenantExport(params: {
789
+ tenantRef: string;
790
+ exportId: string;
791
+ error: string;
792
+ }): Promise<void>;
793
+ /** One of the tenant's exports, or null. */
794
+ getTenantExport(params: {
795
+ tenantRef: string;
796
+ exportId: string;
797
+ }): Promise<SlotlockTenantExport | null>;
798
+ /** The tenant's exports, newest first. */
799
+ listTenantExports(params: {
800
+ tenantRef: string;
801
+ }): Promise<SlotlockTenantExport[]>;
802
+ /** One part (1 to its `parts`) of a ready export. */
803
+ getTenantExportPart(params: {
804
+ tenantRef: string;
805
+ exportId: string;
806
+ part: number;
807
+ }): Promise<SlotlockTenantExportPartResult>;
808
+ /** Delete one of the tenant's exports at once. False when there was none. */
809
+ deleteTenantExport(params: {
810
+ tenantRef: string;
811
+ exportId: string;
812
+ }): Promise<boolean>;
813
+ /**
814
+ * Delete the tenant's exports past their time; and name those left pending for more than two
815
+ * minutes (their server stopped before building them), for maintenance to build.
816
+ */
817
+ pruneTenantExports(params: {
818
+ tenantRef: string;
819
+ }): Promise<{
820
+ deleted: number;
821
+ pending: string[];
822
+ }>;
823
+ /**
824
+ * Read everything the tenant keeps, as an export holds it, in one snapshot, without storing it
825
+ * (`slotlock tenant export`): each part (one table's rows) to `onPart` as it is read.
826
+ */
827
+ streamTenantExport(params: {
828
+ tenantRef: string;
829
+ onPart: (part: {
830
+ table: SlotlockTenantExportTable;
831
+ lines: string[];
832
+ }) => Promise<void>;
833
+ }): Promise<{
834
+ rows: number;
835
+ bytes: number;
836
+ tables: Record<string, number>;
837
+ }>;
734
838
  /** One of the tenant's live holds, or null (none, expired, confirmed, or another tenant's). */
735
839
  getHold(params: {
736
840
  tenantRef: string;
@@ -843,6 +947,19 @@ export interface SlotlockStore extends SlotlockEventFeed {
843
947
  holdId: string;
844
948
  holderRef: string;
845
949
  };
950
+ /**
951
+ * The agent it holds time for, and its key's caps (RFC 0004 A5): its pending approvals' holds
952
+ * count against its live-hold and held-hour caps with its own holds, so asking for approval
953
+ * never holds more than the agent could hold itself (`hold_limit_reached`,
954
+ * `held_time_limit_reached`). Kept on the approval's requester as `holder_ref`.
955
+ */
956
+ holder?: {
957
+ ref: string;
958
+ caps?: {
959
+ maxHolds?: number;
960
+ maxHeldHours?: number;
961
+ };
962
+ };
846
963
  }): Promise<{
847
964
  ok: true;
848
965
  approval: SlotlockApproval;
@@ -851,6 +968,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
851
968
  ok: false;
852
969
  code: 'overlap' | 'approval_limit_reached' | 'idempotency_conflict';
853
970
  limit?: number;
971
+ } | {
972
+ ok: false;
973
+ code: 'hold_limit_reached' | 'held_time_limit_reached';
974
+ limit: number;
854
975
  }>;
855
976
  /** The approval a request key made, or null. */
856
977
  findApproval(params: {
@@ -1348,6 +1469,51 @@ export interface SlotlockStore extends SlotlockEventFeed {
1348
1469
  memberRef: string;
1349
1470
  chatId: string;
1350
1471
  }): Promise<boolean>;
1472
+ /**
1473
+ * Add an event to the tenant's audit log (slotlock.audit_events). The log is append-only for the
1474
+ * serving role: nothing here changes or removes an event. Text past its column's bound is cut.
1475
+ */
1476
+ recordAuditEvent(params: {
1477
+ tenantRef: string;
1478
+ event: SlotlockAuditEventInput;
1479
+ }): Promise<void>;
1480
+ /**
1481
+ * The tenant's audit events, newest first: up to `limit` (1-200, default 50), before the event
1482
+ * `before` names, and only those of `action` or `actorRef` when given.
1483
+ */
1484
+ listAuditEvents(params: {
1485
+ tenantRef: string;
1486
+ limit?: number;
1487
+ before?: {
1488
+ occurredAt: Date;
1489
+ id: string;
1490
+ };
1491
+ /** After the event with this id, at its exact time (the API's cursor); none if it was pruned. */
1492
+ beforeId?: string;
1493
+ action?: string;
1494
+ actorRef?: string;
1495
+ }): Promise<SlotlockAuditEvent[]>;
1496
+ /** The tenant's hosted plan and its standing (slotlock.tenant_plan), or null when none applies. */
1497
+ tenantPlan(params: {
1498
+ tenantRef: string;
1499
+ }): Promise<{
1500
+ plan: string;
1501
+ status: string;
1502
+ } | null>;
1503
+ /** Delete the tenant's audit events older than SLOTLOCK_AUDIT_RETENTION_DAYS; how many went. */
1504
+ pruneAuditEvents(params: {
1505
+ tenantRef: string;
1506
+ }): Promise<number>;
1507
+ /**
1508
+ * Check the tenant's audit chain from its oldest event kept (slotlock.verify_audit_chain): how many
1509
+ * events held, and the number of the first that does not (null when every one does).
1510
+ */
1511
+ verifyAuditChain(params: {
1512
+ tenantRef: string;
1513
+ }): Promise<{
1514
+ checked: number;
1515
+ brokenAt: number | null;
1516
+ }>;
1351
1517
  /** Delete up to `limit` (default 1,000) of the tenant's expired assistant conversations: how many. */
1352
1518
  pruneAssistantConversations(params: {
1353
1519
  tenantRef: string;
@@ -1360,6 +1526,14 @@ export interface SlotlockStore extends SlotlockEventFeed {
1360
1526
  sweepAssistantConversations(params?: {
1361
1527
  limit?: number;
1362
1528
  }): Promise<number>;
1529
+ /**
1530
+ * Delete team workspace invitations finished (accepted, revoked or expired) more than 30 days ago,
1531
+ * with the addresses they were sent to (slotlock.prune_workspace_invitations), requests to join
1532
+ * decided a month ago and domain claims ended three months ago (slotlock.prune_workspace_domains), and dashboard
1533
+ * sessions ended or expired a day ago (slotlock.prune_dashboard_sessions), and erase the team
1534
+ * workspaces whose deletion came due (slotlock.erase_deleted_teams): how many.
1535
+ */
1536
+ sweepDashboardRecords(): Promise<number>;
1363
1537
  /**
1364
1538
  * Offer freed time to the waitlist in join order: each waiting entry whose window is free (on its
1365
1539
  * resource, or the first free pool member) gets a hold for its holder (`waitlist.offered`).
@@ -1520,6 +1694,96 @@ export interface SlotlockAssistantChatRecord extends SlotlockAssistantChatSummar
1520
1694
  /** When it is deleted unless used again; null when it is kept until the member deletes it. */
1521
1695
  expiresAt: Date | null;
1522
1696
  }
1697
+ /** What an audit event records (slotlock.audit_events): who did what, to what, from where. */
1698
+ export interface SlotlockAuditEventInput {
1699
+ /** `<area>.<verb>`, such as `api_key.created` or `authz.denied`. */
1700
+ action: string;
1701
+ actorType: 'person' | 'api_key' | 'assistant' | 'system';
1702
+ /** `github:<id>`, `google:<subject>`, `api_key:<id>`: a reference, never a name or an email. */
1703
+ actorRef: string;
1704
+ /** The actor's role in the workspace when it acted. */
1705
+ actorRole?: SlotlockWorkspaceRole | null;
1706
+ targetType?: string;
1707
+ targetRef?: string;
1708
+ outcome?: 'success' | 'denied';
1709
+ reason?: string;
1710
+ ipAddress?: string;
1711
+ userAgent?: string;
1712
+ /** Before-and-after values and the like: never a secret, a token or a key. */
1713
+ metadata?: Record<string, unknown>;
1714
+ }
1715
+ /** An audit event as the API and `slotlock audit export` give it: snake_case, nulls kept. */
1716
+ export declare function slotlockAuditEventRecord(event: SlotlockAuditEvent): Record<string, unknown>;
1717
+ /**
1718
+ * Whom an access or erasure request is about (RFC 0004 D3): one person, by exactly one of the
1719
+ * identifiers Slotlock keeps for them. `email` matches contacts, event attendees and organizers
1720
+ * (ignoring case); `holder` the holder reference on holds, bookings and waitlist entries;
1721
+ * `contact` a contact's reference; `reference` a booking's or an event's reference.
1722
+ */
1723
+ export type SlotlockSubject = {
1724
+ email: string;
1725
+ } | {
1726
+ holder: string;
1727
+ } | {
1728
+ contact: string;
1729
+ } | {
1730
+ reference: string;
1731
+ };
1732
+ /** What the tenant keeps about one person (exportSubject). */
1733
+ export interface SlotlockSubjectExport {
1734
+ /** Each table's rows that name them, as kept (snake_case, without the tenant). */
1735
+ records: Record<SlotlockSubjectTable, Record<string, unknown>[]>;
1736
+ /** True when a table had more rows than the limit: its first ones are listed. */
1737
+ truncated: boolean;
1738
+ }
1739
+ /** What an erasure did (eraseSubject): by table, the rows deleted or taken the person out of. */
1740
+ export interface SlotlockSubjectErasure {
1741
+ erased: Record<SlotlockSubjectTable, number>;
1742
+ }
1743
+ /** Hours a tenant export is kept once ready, or since it was asked for if it never is. */
1744
+ export declare const SLOTLOCK_TENANT_EXPORT_HOURS = 24;
1745
+ /** The most a tenant export holds: past it, the export fails (`export_too_large`). */
1746
+ export declare const SLOTLOCK_TENANT_EXPORT_MAX_BYTES: number;
1747
+ /** The most exports a tenant keeps at once. */
1748
+ export declare const SLOTLOCK_MAX_TENANT_EXPORTS = 5;
1749
+ /** A part of an export: one table's rows, at most this many, or about this many bytes. */
1750
+ export declare const SLOTLOCK_TENANT_EXPORT_PART_ROWS = 5000;
1751
+ /** A copy of everything the tenant keeps (createTenantExport), as built or being built. */
1752
+ export interface SlotlockTenantExport {
1753
+ id: string;
1754
+ /** `pending` until it is built, then `ready`, or `failed` (`error` says why). */
1755
+ status: 'pending' | 'ready' | 'failed';
1756
+ requestedBy: string | null;
1757
+ createdAt: Date;
1758
+ finishedAt: Date | null;
1759
+ /** When maintenance deletes it. */
1760
+ expiresAt: Date;
1761
+ parts: number;
1762
+ rows: number;
1763
+ bytes: number;
1764
+ /** Rows by table (SLOTLOCK_TENANT_EXPORT_TABLES), for the tables with any. */
1765
+ tables: Record<string, number>;
1766
+ error: string | null;
1767
+ }
1768
+ /** One part of an export: one table's rows, each a JSON object as kept (NDJSON lines). */
1769
+ export type SlotlockTenantExportPartResult = {
1770
+ ok: true;
1771
+ table: SlotlockTenantExportTable;
1772
+ lines: string[];
1773
+ } | {
1774
+ ok: false;
1775
+ code: 'export_not_found' | 'export_not_ready' | 'export_part_not_found';
1776
+ };
1777
+ /** An audit event as the log keeps it. */
1778
+ export interface SlotlockAuditEvent extends SlotlockAuditEventInput {
1779
+ id: string;
1780
+ occurredAt: Date;
1781
+ outcome: 'success' | 'denied';
1782
+ metadata: Record<string, unknown>;
1783
+ /** Its place in the tenant's chain, and its SHA-256 (hex) over the one before and itself. */
1784
+ chainIndex: number | null;
1785
+ hash: string | null;
1786
+ }
1523
1787
  /** One entry of a tenant's waitlist (joinWaitlist). */
1524
1788
  export interface SlotlockWaitlistEntry {
1525
1789
  id: string;
@@ -1661,6 +1925,13 @@ export interface SlotlockHoldTenantOptions {
1661
1925
  * waiter out of the line, rejecting with its reason. Each holder calls the release it gets once.
1662
1926
  */
1663
1927
  export declare function createTenantSlots(max: number): (tenantRef: string, signal?: AbortSignal) => Promise<() => void>;
1928
+ /**
1929
+ * Build a pending tenant export (RFC 0004 D3) in its own tenant transaction, and mark it failed if
1930
+ * that fails (`export_too_large`, else `export_failed`). Null when it was not pending, or another
1931
+ * server is building it. Never throws: a server builds it after answering, and maintenance builds
1932
+ * one left pending.
1933
+ */
1934
+ export declare function buildSlotlockTenantExport(store: SlotlockStore, tenantRef: string, exportId: string): Promise<SlotlockTenantExport | null>;
1664
1935
  export declare function createSlotlockStore(sql: StoreSql, options?: SlotlockStoreOptions): SlotlockStore;
1665
1936
  /**
1666
1937
  * Every store method that writes for a tenant: what a suspended tenant's withTenant store refuses.