slotlock 0.6.3 → 0.7.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 (70) hide show
  1. package/CHANGELOG.md +467 -0
  2. package/CONTRIBUTING.md +12 -5
  3. package/README.md +50 -1551
  4. package/SECURITY.md +5 -2
  5. package/SPEC.md +1 -1
  6. package/agent-server.d.ts +160 -8
  7. package/agent-server.js +559 -44
  8. package/agent-store-backend.d.ts +34 -1
  9. package/agent-store-backend.js +1399 -38
  10. package/api-keys.d.ts +69 -3
  11. package/api-keys.js +217 -28
  12. package/application-api.d.ts +56 -3
  13. package/application-api.js +826 -15
  14. package/approvals.d.ts +88 -0
  15. package/approvals.js +293 -0
  16. package/assistant.d.ts +40 -0
  17. package/assistant.js +115 -0
  18. package/billing.d.ts +114 -0
  19. package/billing.js +258 -0
  20. package/config.d.ts +25 -4
  21. package/config.js +72 -3
  22. package/dashboard-icon.d.ts +1 -0
  23. package/dashboard-icon.js +5 -0
  24. package/dashboard.d.ts +203 -7
  25. package/dashboard.js +1545 -64
  26. package/ddl.d.ts +51 -12
  27. package/ddl.js +2566 -54
  28. package/event-feed.d.ts +30 -4
  29. package/event-feed.js +171 -19
  30. package/index.d.ts +15 -4
  31. package/index.js +8 -2
  32. package/maintenance.d.ts +10 -0
  33. package/maintenance.js +16 -2
  34. package/metrics.d.ts +41 -0
  35. package/metrics.js +22 -0
  36. package/openapi.json +14675 -5957
  37. package/otel.d.ts +86 -0
  38. package/otel.js +273 -0
  39. package/overage.d.ts +33 -0
  40. package/overage.js +115 -0
  41. package/package.json +1 -1
  42. package/partner-billing.d.ts +81 -0
  43. package/partner-billing.js +333 -0
  44. package/partner-names.d.ts +8 -0
  45. package/partner-names.js +10 -0
  46. package/partners.d.ts +147 -0
  47. package/partners.js +375 -0
  48. package/plans.d.ts +92 -0
  49. package/plans.js +115 -0
  50. package/quotas.d.ts +108 -0
  51. package/quotas.js +258 -0
  52. package/rate-limit.d.ts +21 -1
  53. package/rate-limit.js +82 -15
  54. package/resource-profile.d.ts +135 -0
  55. package/resource-profile.js +263 -0
  56. package/rest-api.d.ts +2 -0
  57. package/rest-api.js +68 -3
  58. package/sandbox.d.ts +70 -0
  59. package/sandbox.js +295 -0
  60. package/self-host.d.ts +33 -5
  61. package/self-host.js +950 -22
  62. package/slotlock.manifest.json +1 -1
  63. package/store.d.ts +373 -4
  64. package/store.js +1212 -118
  65. package/teams.d.ts +25 -0
  66. package/teams.js +63 -0
  67. package/timezone.js +13 -1
  68. package/types.d.ts +95 -1
  69. package/usage.d.ts +106 -0
  70. package/usage.js +238 -0
package/dashboard.d.ts CHANGED
@@ -1,19 +1,28 @@
1
- import type { SlotlockApiKeyStore } from './api-keys.js';
2
- import { type SlotlockAgentCalendarBackend, type SlotlockAgentOperation, type SlotlockAgentOperationDispatchOptions, type SlotlockAgentPrincipal } from './agent-server.js';
1
+ import type { SlotlockDashboardTeams } from './teams.js';
2
+ import { type SlotlockApiKeyStore } from './api-keys.js';
3
+ import { type SlotlockAgentCalendarBackend, type SlotlockAgentOperation, type SlotlockAgentOperationDispatchOptions, type SlotlockAgentPrincipal, type SlotlockAgentUsage } from './agent-server.js';
3
4
  import { type SlotlockAssistantStore } from './assistant.js';
4
5
  import { type SlotlockRate } from './config.js';
5
6
  import { type SlotlockBookingRules } from './booking-rules.js';
7
+ import { type SlotlockTenantPlan } from './plans.js';
8
+ import { type SlotlockLocation, type SlotlockResourceGroup, type SlotlockResourceProfileInput } from './resource-profile.js';
9
+ import { type SlotlockUsageRow } from './usage.js';
6
10
  import { type CreateWebhookEndpointResult, type SlotlockChange, type SlotlockWebhookEndpoint } from './event-feed.js';
7
11
  import type { SlotlockSql, SlotlockStore, SlotlockWaitlistEntry } from './store.js';
8
- import type { Interval, SlotlockAgendaEntry, SlotlockResource, SlotlockBookingRuleDefaults, WeeklyAvailabilityRule } from './types.js';
12
+ import { type SlotlockSandboxScenario } from './sandbox.js';
13
+ import type { Interval, SlotlockAgendaEntry, SlotlockApproval, SlotlockResource, SlotlockBookingRuleDefaults, WeeklyAvailabilityRule } from './types.js';
9
14
  /** Resources one dashboard tenant may hold. */
10
- export declare const SLOTLOCK_DASHBOARD_MAX_RESOURCES = 100;
15
+ export declare const SLOTLOCK_DASHBOARD_MAX_RESOURCES = 500;
11
16
  /**
12
17
  * A tenant's calendar resources, as the dashboard lists and adds them. Each write takes, last, the
13
18
  * person who made it (`github:<user id>`) as `actor`, to record on the event feed.
14
19
  */
15
20
  export interface SlotlockDashboardResources {
16
21
  list(tenantRef: string): Promise<SlotlockResource[]>;
22
+ /** How many resources the tenant has, archived ones included, as a resource limit counts them. */
23
+ count?(tenantRef: string): Promise<number>;
24
+ /** The tenant's metered usage from `from` to `to`, UTC days inclusive (usage.ts). */
25
+ usage?(tenantRef: string, from: string, to: string): Promise<SlotlockUsageRow[]>;
17
26
  /**
18
27
  * Create the resource, or re-zone the tenant's resource with that reference. Refuse a new one
19
28
  * (an error with `code: 'resource_limit_reached'`) once the tenant has
@@ -39,8 +48,84 @@ export interface SlotlockDashboardResources {
39
48
  setRules?(tenantRef: string, id: string, rules: SlotlockBookingRules | null, actor?: string): Promise<SlotlockResource | null>;
40
49
  /** The tenant's default booking rules and its pools', which a resource's own rules overlay. */
41
50
  ruleDefaults?(tenantRef: string): Promise<SlotlockBookingRuleDefaults>;
51
+ /**
52
+ * Change one of the tenant's resources' profile (resource-profile.ts): the fields given, keeping
53
+ * the rest. As setRules; `code: 'invalid_resource_profile'`, with the `field`.
54
+ */
55
+ setProfile?(tenantRef: string, id: string, profile: SlotlockResourceProfileInput, actor?: string): Promise<SlotlockResource | null>;
56
+ /**
57
+ * Make one of the tenant's resources a part of a composite (RFC 0003 G8), or no part. A refusal
58
+ * carries the store's `code`: `parent_not_found`, `composite_too_deep`, `composite_parts_limit`
59
+ * or `composite_busy`.
60
+ */
61
+ setParent?(tenantRef: string, id: string, parentId: string | null, actor?: string): Promise<SlotlockResource | null>;
42
62
  /** Put one of the tenant's resources in a pool, or none: as setRules; `code: 'invalid_pool'`. */
43
63
  setPool?(tenantRef: string, id: string, pool: string | null, actor?: string): Promise<SlotlockResource | null>;
64
+ /**
65
+ * The tenant's locations and groups (RFC 0003 G2, G3), and where each resource is in them. A
66
+ * refusal carries the store's `code`: `invalid_location` or `invalid_group` (with the `field`),
67
+ * `location_not_found`, `location_too_deep`, `location_cycle`, `location_limit_reached`,
68
+ * `location_in_use`, `group_not_found`, `group_limit_reached` or `resource_group_limit_reached`.
69
+ */
70
+ grouping?: {
71
+ /** Each under its parent, depth first, with how many resources are at each. */
72
+ locations(tenantRef: string): Promise<SlotlockLocation[]>;
73
+ /** Create the location with this reference, or change its name, parent and time zone. */
74
+ putLocation(tenantRef: string, location: {
75
+ ref: string;
76
+ name: string;
77
+ parentRef: string | null;
78
+ timezone: string | null;
79
+ }, actor?: string): Promise<SlotlockLocation>;
80
+ /** False when the tenant has no such location. */
81
+ deleteLocation(tenantRef: string, ref: string, actor?: string): Promise<boolean>;
82
+ /** By reference, with how many resources are in each. */
83
+ groups(tenantRef: string): Promise<SlotlockResourceGroup[]>;
84
+ /** Create the group with this reference, or change its name and description. */
85
+ putGroup(tenantRef: string, group: {
86
+ ref: string;
87
+ name: string;
88
+ description: string | null;
89
+ }, actor?: string): Promise<SlotlockResourceGroup>;
90
+ /** Delete a group, its resources left as they are: false when the tenant has no such group. */
91
+ deleteGroup(tenantRef: string, ref: string, actor?: string): Promise<boolean>;
92
+ /**
93
+ * Put one of the tenant's resources at a location (or none) and in exactly these groups, in one
94
+ * transaction: the resource as it then is, or null when it is not the tenant's.
95
+ */
96
+ place(tenantRef: string, id: string, place: {
97
+ location: string | null;
98
+ groups: readonly string[];
99
+ }, actor?: string): Promise<SlotlockResource | null>;
100
+ };
101
+ /**
102
+ * The tenant's approvals (RFC 0004 A4), newest first, and deciding one: a grant makes the booking
103
+ * as the agent asked for it; a refusal says why (approval_not_pending, overlap, a booking rule).
104
+ */
105
+ approvals?: {
106
+ list(tenantRef: string, limit: number): Promise<SlotlockApproval[]>;
107
+ decide(tenantRef: string, id: string, decision: 'grant' | 'decline', actor?: string, reason?: string): Promise<{
108
+ ok: true;
109
+ } | {
110
+ ok: false;
111
+ code: string;
112
+ }>;
113
+ };
114
+ /**
115
+ * The sandbox (RFC 0004 A6), for a test environment only: fill it with a scenario, empty it, and
116
+ * make what would lapse within some minutes lapse now. Each refuses a live tenant.
117
+ */
118
+ sandbox?: {
119
+ seed(tenantRef: string, scenario: SlotlockSandboxScenario): Promise<{
120
+ resources: number;
121
+ bookings: number;
122
+ }>;
123
+ reset(tenantRef: string): Promise<void>;
124
+ fastForward(tenantRef: string, minutes: number): Promise<{
125
+ holds: number;
126
+ approvals: number;
127
+ }>;
128
+ };
44
129
  /** The tenant's waitlist, oldest first, and leaving it (false when the entry is not the tenant's). */
45
130
  waitlist?: {
46
131
  list(tenantRef: string, limit: number): Promise<SlotlockWaitlistEntry[]>;
@@ -91,6 +176,18 @@ export interface SlotlockDashboardState {
91
176
  export interface SlotlockDashboardAssistantOptions {
92
177
  /** Seals each tenant's API key (SLOTLOCK_ASSISTANT_SECRET): 32+ random characters. */
93
178
  secret: string;
179
+ /**
180
+ * Secrets keys may still be sealed under (SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS), newest first, at
181
+ * most SLOTLOCK_ASSISTANT_MAX_PREVIOUS_SECRETS: a key one of them opens is used, and sealed again
182
+ * under `secret` in the same request, so changing the secret makes no owner enter a key again.
183
+ * Drop an old secret once every tenant has used the assistant since the change, or keys still
184
+ * sealed under it stop opening.
185
+ */
186
+ previousSecrets?: readonly string[];
187
+ /** Each operation the assistant runs, for usage metering (the agent server's `onUsage`). */
188
+ onUsage?: (usage: SlotlockAgentUsage) => void;
189
+ /** Each message a member sends that the assistant answers, for usage metering. Never awaited. */
190
+ onMessage?: (tenantRef: string) => void;
94
191
  backend: SlotlockAgentCalendarBackend;
95
192
  /**
96
193
  * As the agent server's: false refuses an operation. Every offered operation by default. It also
@@ -135,11 +232,70 @@ export interface SlotlockDashboardAssistantOptions {
135
232
  /** How long a conversation is kept after its last message: 2 hours by default. */
136
233
  conversationTtlMs?: number;
137
234
  }
235
+ /**
236
+ * Hosted Slotlock's plans (plans.ts) and their billing: a Plan page with the tenant's plan and use,
237
+ * Upgrade (to a checkout page) and Manage (to a billing portal). Off unless set.
238
+ */
239
+ export interface SlotlockDashboardBilling {
240
+ /** The tenant's plan (its own, or the default), or null when it has none (the server's limits apply). */
241
+ plan(tenantRef: string): Promise<SlotlockTenantPlan | null>;
242
+ /**
243
+ * Puts a tenant new to plans on the default plan before it adds a key, resource or webhook
244
+ * endpoint, while the default does not yet apply to every tenant (plans.ts).
245
+ */
246
+ enrol?(tenantRef: string): Promise<void>;
247
+ /** A checkout URL for the Developer plan, or the Team plan for an organization (with `teamPrices`). */
248
+ checkout(input: {
249
+ tenantRef: string;
250
+ interval: 'month' | 'year';
251
+ plan?: 'developer' | 'team';
252
+ customer: string | null;
253
+ successUrl: string;
254
+ cancelUrl: string;
255
+ }): Promise<string>;
256
+ /** A billing portal URL for the tenant's customer. */
257
+ portal(input: {
258
+ customer: string;
259
+ returnUrl: string;
260
+ }): Promise<string>;
261
+ /** The Developer plan's prices as shown, such as `$10`. */
262
+ prices: {
263
+ month: string;
264
+ year: string;
265
+ };
266
+ /**
267
+ * The Team plan's prices as shown (Teams on Cloud). With them, an organization's workspace is
268
+ * offered Team instead of Developer.
269
+ */
270
+ teamPrices?: {
271
+ month: string;
272
+ year: string;
273
+ };
274
+ /**
275
+ * Metered overage on Developer (SLOTLOCK_OVERAGE, overage.ts): its price as shown, such as `$2
276
+ * per 10,000 operations`, and the percent of the quota where use is refused.
277
+ */
278
+ overage?: {
279
+ price: string;
280
+ stopPercent: number;
281
+ };
282
+ /** Where a Custom (self-hosted) enquiry goes: an https or mailto URL. */
283
+ contact: string;
284
+ /** The https origins Upgrade and Manage redirect to, allowed in the policy's form-action. */
285
+ formOrigins: readonly string[];
286
+ }
138
287
  export interface SlotlockDashboardOptions {
139
288
  /** Where people reach the server; the dashboard lives at `<publicUrl>/dashboard`. */
140
289
  publicUrl: string;
141
290
  /** What the deployment is called in the dashboard: 1-60 characters. Defaults to `Slotlock`. */
142
291
  name?: string;
292
+ /**
293
+ * The terms people accept by signing in, an https URL. The sign-in page links them beside the
294
+ * button; without it, the page names no terms.
295
+ */
296
+ termsUrl?: string;
297
+ /** The privacy notice the sign-in page links, an https URL. */
298
+ privacyUrl?: string;
143
299
  /**
144
300
  * Where agents reach the server's MCP and A2A endpoints, which the dashboard shows, when it is
145
301
  * served from another origin (on Cloudflare Workers, with the server elsewhere). Defaults to
@@ -158,9 +314,25 @@ export interface SlotlockDashboardOptions {
158
314
  userUrl?: string;
159
315
  membershipsUrl?: string;
160
316
  };
317
+ /**
318
+ * Sign-in with Google (OpenID Connect), beside GitHub, for people without a GitHub account: each
319
+ * is their own tenant, `google:<subject>`. Not offered with a fixed list of `organizations`,
320
+ * which are GitHub's. Its redirect URI is the same `<publicUrl>/dashboard/callback`.
321
+ */
322
+ google?: {
323
+ clientId: string;
324
+ clientSecret: string;
325
+ authorizeUrl?: string;
326
+ tokenUrl?: string;
327
+ /** Google's signing keys (JWKS); https://www.googleapis.com/oauth2/v3/certs by default. */
328
+ jwksUrl?: string;
329
+ };
161
330
  /** Seals sessions and sign-in state and derives CSRF tokens: 32+ random characters. */
162
331
  sessionSecret: string;
163
- /** GitHub user ids (numbers, as strings) that may sign in, or `'*'` for every GitHub account. */
332
+ /**
333
+ * GitHub user ids (numbers, as strings) and Google accounts (`google:<subject>`) that may sign in,
334
+ * or `'*'` for every account.
335
+ */
164
336
  allowedUsers: '*' | readonly string[];
165
337
  /**
166
338
  * GitHub organization ids (numbers, as strings) this dashboard serves. With them, a tenant is an
@@ -171,8 +343,14 @@ export interface SlotlockDashboardOptions {
171
343
  * ends access at the next sign-in, at most a session (12 hours) later, and removing an
172
344
  * organization from this list signs its sessions out at once. Without it, each person is their
173
345
  * own tenant (`github:<id>`).
346
+ *
347
+ * `'enabled'` (Teams on Cloud, with `teams`): each person signs in to their own tenant and may
348
+ * switch to any organization they belong to that an owner turned on, on the Teams page; turning
349
+ * one off ends its use at once.
174
350
  */
175
- organizations?: readonly string[];
351
+ organizations?: readonly string[] | 'enabled';
352
+ /** The organizations turned on, with `organizations: 'enabled'` (teams.ts). */
353
+ teams?: SlotlockDashboardTeams;
176
354
  keys: SlotlockApiKeyStore;
177
355
  resources: SlotlockDashboardResources;
178
356
  /** Shared by every server behind this origin: see SlotlockDashboardState. */
@@ -181,6 +359,18 @@ export interface SlotlockDashboardOptions {
181
359
  availability?: readonly WeeklyAvailabilityRule[];
182
360
  /** The chat assistant; off unless set. */
183
361
  assistant?: SlotlockDashboardAssistantOptions;
362
+ /** Plans and billing (hosted Slotlock); off unless set. */
363
+ billing?: SlotlockDashboardBilling;
364
+ /**
365
+ * The deployment meters usage (SLOTLOCK_USAGE=on): the Plan page shows this month's, from the
366
+ * resources adapter's `usage`.
367
+ */
368
+ usage?: boolean;
369
+ /**
370
+ * The deployment holds tenants to their plans' monthly quotas (SLOTLOCK_QUOTAS=on): the Plan page
371
+ * shows this month's use against them, and warns from 80%. Needs `usage`.
372
+ */
373
+ quotas?: boolean;
184
374
  fetch?: typeof fetch;
185
375
  now?: () => number;
186
376
  /**
@@ -204,7 +394,13 @@ export interface SlotlockDashboard {
204
394
  * The dashboard's resources, in the store under each tenant's row-level security. Adds hold the cap
205
395
  * in the database (a READ COMMITTED transaction, under a per-tenant lock).
206
396
  */
207
- export declare function createSlotlockDashboardResources(store: SlotlockStore): SlotlockDashboardResources;
397
+ export declare function createSlotlockDashboardResources(store: SlotlockStore, options?: {
398
+ /**
399
+ * The agent backend (`createSlotlockStoreAgentBackend`) a granted approval's booking is made
400
+ * through: with it, the dashboard lists approvals and decides them.
401
+ */
402
+ agentBackend?: SlotlockAgentCalendarBackend;
403
+ }): SlotlockDashboardResources;
208
404
  /**
209
405
  * Records accepted write confirmations (`SlotlockAgentConfirmationOptions.consumeAcceptance`) in
210
406
  * Postgres, shared by every server on the database: true the first time a confirmation's nonce is