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.
- package/CHANGELOG.md +305 -0
- package/NOTICE +2 -0
- package/README.md +4 -2
- package/agent-store-backend.js +209 -15
- package/application-api.d.ts +10 -1
- package/application-api.js +213 -2
- package/availability-store.js +13 -5
- package/booking-rules.d.ts +11 -0
- package/booking-rules.js +21 -1
- package/config.d.ts +50 -0
- package/config.js +107 -0
- package/dashboard-scope.d.ts +7 -0
- package/dashboard-scope.js +21 -0
- package/dashboard.d.ts +97 -2
- package/dashboard.js +3068 -284
- 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 +40 -9
- package/ddl.js +2123 -49
- package/domains.d.ts +117 -0
- package/domains.js +340 -0
- package/event-feed.d.ts +1 -1
- package/event-feed.js +6 -34
- package/holiday-import.d.ts +1 -1
- package/holiday-import.js +2 -3
- package/holidays.js +70 -11
- package/index.d.ts +12 -4
- package/index.js +8 -2
- package/invitation-email.d.ts +26 -0
- package/invitation-email.js +73 -0
- package/maintenance.d.ts +7 -2
- package/maintenance.js +15 -1
- package/oidc.d.ts +135 -0
- package/oidc.js +452 -0
- package/openapi.json +1886 -6
- package/package.json +2 -1
- 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 +262 -7
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +273 -2
- package/store.js +984 -72
- package/types.d.ts +33 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,311 @@
|
|
|
3
3
|
All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and semantic
|
|
4
4
|
versioning.
|
|
5
5
|
|
|
6
|
+
## 0.10.0 - 2026-11-11
|
|
7
|
+
|
|
8
|
+
This release gives a workspace its people and an audit trail. It brings six roles read from the
|
|
9
|
+
database on every request, team workspaces with invitations, verified domains that let colleagues
|
|
10
|
+
find and ask to join their team, single sign-on and SCIM provisioning through each team's own
|
|
11
|
+
identity provider, sign-out everywhere and a hash-chained audit log, and lets a
|
|
12
|
+
member be limited to locations or groups (RFC 0004 D1). The change history
|
|
13
|
+
becomes append-only and is kept by plan (D2). A person's data can be exported or erased, and a whole
|
|
14
|
+
tenant exported (D3), from the API, the command line and the dashboard's new Privacy page. It closes
|
|
15
|
+
the four questions the security review left open, and fixes what a review of RFC 0004's T2 changes
|
|
16
|
+
found; that review was not the independent human review GOVERNANCE.md requires. Read the upgrade
|
|
17
|
+
notes first.
|
|
18
|
+
|
|
19
|
+
### Upgrade notes
|
|
20
|
+
|
|
21
|
+
- **Run `slotlock migrate` before serving this release, on every server at once.**
|
|
22
|
+
- The schema becomes version 5, which a 0.9 server refuses (`schema_newer`), so a server still on
|
|
23
|
+
0.9 stops serving once the database is migrated.
|
|
24
|
+
- It adds eight tables (`audit_events`, `dashboard_people`, `dashboard_sessions`, `workspaces`,
|
|
25
|
+
`workspace_memberships`, `workspace_invitations`, `tenant_exports`, `tenant_export_parts`), new
|
|
26
|
+
columns (among them `cutoff_anchor` on bookings and events, and the audit log's chain) and 39
|
|
27
|
+
functions.
|
|
28
|
+
- Verified domains add three more tables (`workspace_domains`, `workspace_join_requests`,
|
|
29
|
+
`workspace_join_blocks`) and single sign-on five (`sso_connections`, `sso_identities`,
|
|
30
|
+
`sso_role_mappings`, `dashboard_session_sso`, `sso_bypasses`) and SCIM four (`scim_tokens`,
|
|
31
|
+
`scim_users`, `scim_groups`, `scim_group_members`), with their functions, unused until turned on.
|
|
32
|
+
- It grants the serving role what this release needs; a library user runs
|
|
33
|
+
`grantApplicationRole` again.
|
|
34
|
+
- **The change history can no longer be edited by the serving role.** It may only add to
|
|
35
|
+
`slotlock.outbox` and read it. Maintenance prunes it through `slotlock.prune_changes`, so code of
|
|
36
|
+
your own that deleted or changed change rows directly must stop.
|
|
37
|
+
- **Everyone signs in again once.** Sessions from before this release carry no session epoch and no
|
|
38
|
+
session record, so the dashboard asks for a new sign-in.
|
|
39
|
+
- **An organization's members become Developers.** They keep read-only and read-write keys of
|
|
40
|
+
their own, but no longer create Full-access keys, add webhooks or revoke others' keys. Keys they
|
|
41
|
+
made before keep working: review the Full-access keys your organization's members created.
|
|
42
|
+
- **Set each resource's hold price before upgrading if its agent holds take payment.** An agent's
|
|
43
|
+
hold now takes its price from the resource's `holdPrice` booking rule (`hold_price`).
|
|
44
|
+
- An agent that names a `payment` the rule does not have is refused `payment_price_mismatch`.
|
|
45
|
+
That includes any payment on a resource without a price.
|
|
46
|
+
- An agent books a priced resource only from a paid hold (`payment_required`).
|
|
47
|
+
- **A key's caps count its pending approvals.** An agent close to its `max_holds` or
|
|
48
|
+
`max_held_hours` may be refused sooner (`hold_limit_reached`, `held_time_limit_reached`).
|
|
49
|
+
- **`@slotlock/sync`:** connect calendars connected before this release again (their seals are
|
|
50
|
+
now bound to their tenant). A library user who reads iCalendar URLs must pass the guarded fetch.
|
|
51
|
+
- **`@slotlock/payments-stripe` with ISK or UGX:** create PaymentIntents for
|
|
52
|
+
`stripeAmount(amountMinor, currency)`. A payment read back is converted with `isoMinorUnits`, and
|
|
53
|
+
one that is not a whole ISO amount is refused.
|
|
54
|
+
- **New and off until set:**
|
|
55
|
+
- team workspaces (`SLOTLOCK_DASHBOARD_TEAM_WORKSPACES=on`);
|
|
56
|
+
- invitations by email (`SLOTLOCK_INVITATION_EMAIL`, `SLOTLOCK_INVITATION_EMAIL_KEY`,
|
|
57
|
+
`SLOTLOCK_INVITATION_EMAIL_FROM`);
|
|
58
|
+
- verified domains, with team workspaces (`SLOTLOCK_VERIFIED_DOMAINS=on`; the server's maintenance
|
|
59
|
+
checks each domain daily, so set it on the server too);
|
|
60
|
+
- single sign-on through each team's provider, with verified domains (`SLOTLOCK_TEAM_SSO=on` and
|
|
61
|
+
`SLOTLOCK_SSO_SECRET`, on whichever serves the dashboard: a server that does not never reads
|
|
62
|
+
them);
|
|
63
|
+
- SCIM provisioning from each team's provider, with verified domains (`SLOTLOCK_TEAM_SCIM=on`, on
|
|
64
|
+
whichever serves the dashboard: its `/scim/v2` is where providers call). On Workers, add the
|
|
65
|
+
`SLOTLOCK_SCIM_RATE_LIMIT` rate limiting binding, which counts providers' calls apart from each
|
|
66
|
+
client's dashboard budget; without it they share that budget, and the Worker logs a warning.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **slotlock.pylota.io, for search engines** ([RFC 0005](docs/rfc/0005-search.md)). A page per
|
|
71
|
+
search intent: `/pricing/`, `/mcp-server/`, `/booking-api/`, `/open-source/`, `/about/`,
|
|
72
|
+
`/use-cases/` with eight industries, `/compare/` (a bookings table of your own, Cal.com, Cronofy,
|
|
73
|
+
Nylas and the Google Calendar API, each claim sourced and dated) and `/blog/` with four articles
|
|
74
|
+
and an RSS feed, each with a Markdown twin listed in `llms.txt`. Every page carries a search
|
|
75
|
+
title and description, an Open Graph image and JSON-LD (Organization, SoftwareApplication with
|
|
76
|
+
the plans' prices, BreadcrumbList, TechArticle, BlogPosting); the sitemap dates each page from
|
|
77
|
+
git; Markdown twins name their HTML page as canonical and agent files are `noindex`; the 404 page
|
|
78
|
+
is `noindex`. `npm run check:seo` fails the site build on a missing canonical, image or sitemap
|
|
79
|
+
entry, and `scripts/check-live-seo.sh` checks the live hosts after each deploy. The site allows
|
|
80
|
+
Cloudflare Web Analytics, which sets no cookie (PRIVACY.md).
|
|
81
|
+
- **Roles in the dashboard.** What each person may do in a workspace is one of six roles (Owner,
|
|
82
|
+
Admin, Developer, Member, Viewer, Billing), read from the database on every request
|
|
83
|
+
(`slotlock.workspace_memberships`, `createSlotlockDashboardWorkspaces`), so a role changed or a
|
|
84
|
+
membership ended applies at the next request. Every dashboard route needs a capability
|
|
85
|
+
(`workspace-roles.ts`); one with none is refused. A person is the Owner of their own workspace;
|
|
86
|
+
in an organization's, GitHub's owners are its Owners and its members Developers.
|
|
87
|
+
- **Confirm it's you.** Full-access keys, webhooks, billing and the assistant's model need a
|
|
88
|
+
sign-in in the last 15 minutes; past it, the dashboard asks the person to sign in again and
|
|
89
|
+
returns to the page.
|
|
90
|
+
- **Sign out everywhere** (Settings, Account) ends every session of the person, on every device
|
|
91
|
+
(a per-person session epoch, `slotlock.dashboard_people`).
|
|
92
|
+
- **An audit log** (`slotlock.audit_events`, 180 days): key, webhook, billing, assistant-model,
|
|
93
|
+
contact and session changes, and every refusal, with where they came from. The serving role may
|
|
94
|
+
only add to it and read it (`SLOTLOCK_APPEND_ONLY_TABLES`); maintenance prunes it.
|
|
95
|
+
- **Team workspaces** (`SLOTLOCK_DASHBOARD_TEAM_WORKSPACES=on`): anyone signed in creates a
|
|
96
|
+
workspace of its own (`team:<id>`) on the Teams page, invites people to it by email with a role,
|
|
97
|
+
and changes their roles, removes them (revoking the keys they made, or keeping them) or leaves on
|
|
98
|
+
Settings, Members. An invitation's link works once, for seven days, only for the address it was
|
|
99
|
+
sent to (verified by Google, or by GitHub when the person confirms it), and opening it consumes
|
|
100
|
+
nothing. A team always keeps an Owner; nobody gives a role above their own. `serve` takes a
|
|
101
|
+
team's keys only with the setting on and while the team exists. Where plans apply, each person
|
|
102
|
+
creates one team on Free, which holds up to 3 people. `SlotlockInvitationSender` sends the
|
|
103
|
+
links by email where a deployment has one; otherwise the person inviting copies it.
|
|
104
|
+
- Google's `email_verified` is accepted as the string `"true"` as well as `true`.
|
|
105
|
+
- **The audit log in the dashboard** (Settings, Audit log): Owners and Admins read who did what,
|
|
106
|
+
refused or done, from where, 50 events a page, filtered by action, and export it as CSV or JSON on
|
|
107
|
+
the Team plan. Each tenant's events form a hash chain (`chain_index`, `prev_hash`, `hash`), with
|
|
108
|
+
the time stamped by the database: an event changed, removed or taken off the chain in the middle
|
|
109
|
+
shows where (`slotlock.verify_audit_chain`, `verifyAuditChain`); exports carry each hash, to keep
|
|
110
|
+
as proof elsewhere. Retention removes only the start of the chain.
|
|
111
|
+
- **Where you're signed in** (Settings, Account): each session's browser, address, start and last
|
|
112
|
+
use, and signing another device out at its next request (`slotlock.dashboard_sessions`, a digest
|
|
113
|
+
of each session's id; `SlotlockDashboardSessions`). A session passes only while its record does,
|
|
114
|
+
so sessions from before this release sign in once more, and a person's 50 most recent are kept.
|
|
115
|
+
- **Team for team workspaces**: a team's Owners and Billing managers buy the Team plan on Usage, as
|
|
116
|
+
an organization's do.
|
|
117
|
+
- **Deleting a team** (Settings, Team): an Owner types its handle; its keys stop (on every server
|
|
118
|
+
within 30 seconds), every member sees the date, and maintenance erases it seven days later unless
|
|
119
|
+
an Owner keeps it (`slotlock.schedule_team_deletion`, `cancel_team_deletion`,
|
|
120
|
+
`erase_deleted_teams`). Refused while the team has a subscription, and never erased while it has
|
|
121
|
+
one.
|
|
122
|
+
- **Invitations by email**: `SLOTLOCK_INVITATION_EMAIL` (`resend` or `postmark`), with
|
|
123
|
+
`SLOTLOCK_INVITATION_EMAIL_KEY` and `SLOTLOCK_INVITATION_EMAIL_FROM`
|
|
124
|
+
(`createSlotlockInvitationEmailSender`). Each person is held to 50 invitations a day across their
|
|
125
|
+
teams, as each team is; sending one again counts as a send. One address is sent at most 5 a day
|
|
126
|
+
from every team together (`recipient_limit`), so open sign-up cannot fill an inbox. Requests to
|
|
127
|
+
the provider carry a User-Agent (Resend refuses one without), and a refusal is logged with the
|
|
128
|
+
provider's status (`SlotlockInvitationEmailError`), never the address.
|
|
129
|
+
- **Requests about one person** (RFC 0004 D3). `POST /v1/subjects/export` (`exportSubject`,
|
|
130
|
+
`slotlock subject export`) lists what the tenant keeps about one person, found by exactly one of
|
|
131
|
+
`email`, `holder`, `contact` or `reference`: the rows that name them, by table
|
|
132
|
+
(`SLOTLOCK_SUBJECT_TABLES`). `POST /v1/subjects/erase` (`eraseSubject`,
|
|
133
|
+
`slotlock subject erase`) takes them out in one transaction: their contacts, attendee entries and
|
|
134
|
+
waitlist entries are deleted, their email leaves events' attendee and organizer lists and
|
|
135
|
+
approvals' requests, and their holder and booking references become one `erased-<32 hex>` name
|
|
136
|
+
in holds, bookings, cancelled bookings, events, approvals and the change history. Bookings stay
|
|
137
|
+
as time taken; nothing goes on the feed; the audit log records `subject.erased` without the
|
|
138
|
+
person.
|
|
139
|
+
- **Privacy in the dashboard** (Settings, Privacy; Owners and Admins, with a sign-in in the last 15
|
|
140
|
+
minutes: the new capability `privacy.manage`). Export the workspace and download it as JSON
|
|
141
|
+
lines; find one person by email, holder, contact or booking reference, see how many rows of each
|
|
142
|
+
table name them, download them as JSON, or erase them once confirmed. Every form posts, so the
|
|
143
|
+
person is never in an address; the audit log records `export.created`, `export.downloaded`,
|
|
144
|
+
`subject.exported` and `subject.erased` without them.
|
|
145
|
+
- **People limited to locations or groups** (RFC 0004 D1). On a team's Members page an Owner, or an
|
|
146
|
+
Admin not limited themselves, limits anyone but an Owner to up to 20 locations and 20 groups
|
|
147
|
+
(new column `workspace_memberships.resource_scope`; definers `slotlock.member_scope`,
|
|
148
|
+
`slotlock.member_scopes` and `slotlock.set_member_scope`). From their next request every
|
|
149
|
+
transaction the dashboard runs for them carries that resource scope (`dashboard-scope.ts`), so
|
|
150
|
+
RFC 0003 G6's policies keep it to those resources; Approvals shows and decides only what asks
|
|
151
|
+
for theirs alone, and their assistant is limited the same way. They keep only reading, the desk
|
|
152
|
+
and the assistant (`SLOTLOCK_SCOPED_CAPABILITIES`, `SLOTLOCK_SCOPED_DASHBOARD_PAGES`,
|
|
153
|
+
`SLOTLOCK_SCOPED_DASHBOARD_POSTS`); the keys they created are revoked unless kept. Audited as
|
|
154
|
+
`member.scope_changed`. Made an Owner by any path, a person's limit is cleared (a trigger on
|
|
155
|
+
`workspace_memberships`), so it does not return if they stop being one. Custom resource adapters
|
|
156
|
+
declare `keepsToScope`; without it a limited person reaches only their own account. Rerun
|
|
157
|
+
`slotlock migrate` and the role grants.
|
|
158
|
+
- **Exporting a whole tenant** (RFC 0004 D3). `POST /v1/tenant/exports` asks for a copy of
|
|
159
|
+
everything the tenant keeps, read through one cursor so at one moment; the server builds it after
|
|
160
|
+
answering into parts of one table's rows (new tables `tenant_exports` and `tenant_export_parts`),
|
|
161
|
+
read with `GET /v1/tenant/exports/{export_id}/parts/{part}` and kept 24 hours. Secrets stay out;
|
|
162
|
+
derived, internal and per-member tables are left out (`SLOTLOCK_TENANT_EXPORT_TABLES`,
|
|
163
|
+
`SLOTLOCK_TENANT_EXPORT_LEFT_OUT`). One at a time, five kept, 512 MB at most; maintenance deletes
|
|
164
|
+
expired exports and builds any left pending. `slotlock tenant export [--out <dir>]` reads the same
|
|
165
|
+
snapshot straight to NDJSON, storing nothing. Recorded in the audit log (`export.created`).
|
|
166
|
+
- **The audit log over the API and the command line** (RFC 0004 D2): `GET /v1/audit` (on the Team
|
|
167
|
+
plan where plans apply, as the dashboard's export) and `GET /v1/audit/verify`;
|
|
168
|
+
`slotlock audit export` and `slotlock audit verify` (exit 1 when the chain breaks).
|
|
169
|
+
- **Verified domains** (`SLOTLOCK_VERIFIED_DOMAINS=on`, with team workspaces): a team's Owners add
|
|
170
|
+
their organization's domain on Settings, Identity and publish a TXT record
|
|
171
|
+
(`_slotlock-challenge.<domain>`, a 128-bit token per claim). The server's maintenance looks it up
|
|
172
|
+
daily over DNS-over-HTTPS, Cloudflare's cross-checked by Google's (`SLOTLOCK_DNS_RESOLVER=system`
|
|
173
|
+
uses the server's own resolver); a record gone on two checks a day apart makes the domain lapse
|
|
174
|
+
in 14 days, with a new token, so a domain that changes hands cannot be revived from DNS history.
|
|
175
|
+
One team holds a domain at a time; public suffixes, special-use names and consumer mail domains
|
|
176
|
+
are refused (the Public Suffix List through `tldts`, pinned). People whose verified address is at
|
|
177
|
+
a team's domain find it on the Teams page and ask to join, approved by an Owner or Admin as
|
|
178
|
+
Viewer, Member or Developer; an Owner may let Google Workspace accounts of the domain join at once
|
|
179
|
+
as a Viewer (an address verified once by Google or GitHub, or one the team removed or declined,
|
|
180
|
+
only asks), or turn joining off. A record behind a CNAME does not count, and 14 days of resolver
|
|
181
|
+
errors lapse a domain too.
|
|
182
|
+
- **Single sign-on through each team's own identity provider** (`SLOTLOCK_TEAM_SSO=on`, with
|
|
183
|
+
verified domains, and `SLOTLOCK_SSO_SECRET`): Okta, Microsoft Entra ID (one tenant), Google
|
|
184
|
+
Workspace or any OpenID Connect provider, set up, tested and turned on by the team's Owners on
|
|
185
|
+
Settings, Identity. ID tokens are checked with WebCrypto on both runtimes (`verifySlotlockIdToken`:
|
|
186
|
+
RS256, PS256 or ES256, never none or HMAC; RSA of 2048 bits or more; exact issuer; audience and
|
|
187
|
+
azp; time with 60 seconds' skew; nonce), keys cached per JWKS URL, an Owner's URLs fetched as
|
|
188
|
+
hostile (https on public names, no redirects, 5 seconds, 256 KB), client secrets sealed with
|
|
189
|
+
AES-256-GCM. Identities are the provider's subject, never an email; a sign-in through a provider
|
|
190
|
+
alone makes a person with no workspace of their own, scoped to the team (no other team, nor the
|
|
191
|
+
person's other sessions); members link their GitHub or Google person by signing in through it,
|
|
192
|
+
with a work account at the verified domain, while signed in. Members at the verified domains
|
|
193
|
+
join at their first sign-in (on by default, as Viewer), groups map to roles (never Owner, and
|
|
194
|
+
nobody who signs in only through a provider is made one), and the team may require single
|
|
195
|
+
sign-on, with an Owner's audited one-hour bypass. Turning a connection off or changing it ends the
|
|
196
|
+
sessions and proofs it gave; deleting it, or erasing the team, erases the people who signed in
|
|
197
|
+
only through it. Google sign-in now goes through the same verifier, which allows 60 seconds of
|
|
198
|
+
clock skew.
|
|
199
|
+
- **SCIM 2.0 provisioning for each team** (`SLOTLOCK_TEAM_SCIM=on`, with verified domains): the
|
|
200
|
+
team's identity provider adds, deactivates and groups its people at the dashboard's
|
|
201
|
+
`/scim/v2/<team id>` (RFC 7643/7644: Users and Groups with GET, POST, PUT, PATCH and DELETE,
|
|
202
|
+
`eq` filters, paging, discovery), with bearer tokens an Owner makes on Settings, Identity (two at
|
|
203
|
+
a time, kept as SHA-256), and Okta's and Entra's requests read as they send them. Users at the
|
|
204
|
+
verified domains only; one becomes a member on signing in with a Google Workspace account of the
|
|
205
|
+
domain or through the team's single sign-on. Deactivation ends that membership at the next
|
|
206
|
+
request and, as the Owner chose when turning SCIM on, revokes the person's keys; reactivation
|
|
207
|
+
needs a new sign-in. Groups give roles (`slotlock-role-<role>`, or as an Owner maps them; never
|
|
208
|
+
Owner), and the directory never changes an Owner. On Workers, the `SLOTLOCK_SCIM_RATE_LIMIT`
|
|
209
|
+
binding counts providers' calls per address apart from the dashboard's per-client budget: a
|
|
210
|
+
provider calls from addresses its other customers share. The dashboard's `scim` option;
|
|
211
|
+
`workspaces.scim`.
|
|
212
|
+
An Owner may also hold invitations to the team's verified domains. New capability
|
|
213
|
+
`identity.manage` (Owners, fresh sign-in); `createSlotlockWorkspaceDomains`,
|
|
214
|
+
`runSlotlockDomainChecks`, `normalizeSlotlockDomain`, `createSlotlockDohResolver`.
|
|
215
|
+
|
|
216
|
+
### Changed
|
|
217
|
+
|
|
218
|
+
- **The change history cannot be edited** (RFC 0004 D2). The serving role may only add changes
|
|
219
|
+
and read them (`SLOTLOCK_APPEND_ONLY_TABLES` includes `outbox`): pruning goes through the
|
|
220
|
+
definer `slotlock.prune_changes`, and the one edit is taking a person out
|
|
221
|
+
(`slotlock.erase_subject_changes`, which writes nothing but an `erased-` name). Both act only for
|
|
222
|
+
the tenant the caller acts in. Rerun `slotlock migrate` and the role grants.
|
|
223
|
+
- **History is kept by plan**: 30 days on Free, 90 on Developer and 400 on Team
|
|
224
|
+
(`SLOTLOCK_CHANGE_RETENTION_BY_PLAN`), or `retentionDays` if longer; a tenant with no plan keeps
|
|
225
|
+
`retentionDays`. The workspace audit log keeps 180 days, or 400 on Team. The privacy notice says
|
|
226
|
+
so, and how an account holder answers a person's request themselves.
|
|
227
|
+
- **The privacy notice lists contacts, busy time from other calendars, and payments for holds**,
|
|
228
|
+
with how long each is kept: a contact until it is deleted; busy time 30 days after it ends; a
|
|
229
|
+
payment record until its resource or account goes. It says hosted Slotlock sends nothing to
|
|
230
|
+
contacts, holds no calendar or payment provider's token, and that the integrations that do run
|
|
231
|
+
under the tenant's own agreements. Erasure lists all three.
|
|
232
|
+
- **An organization's members no longer have every power.** Before, any member could create a
|
|
233
|
+
Full-access key (which reaches contacts, webhooks, history and erasure), revoke anyone's key and
|
|
234
|
+
add webhooks. Members are now Developers: read-only and read-write keys of their own, resources,
|
|
235
|
+
rules and hours. Review the Full-access keys your organization's members created.
|
|
236
|
+
|
|
237
|
+
### Security
|
|
238
|
+
|
|
239
|
+
The four questions the review left open, now closed:
|
|
240
|
+
|
|
241
|
+
- **Moving a booking no longer avoids a late cancellation.** Where the reschedule cutoff was
|
|
242
|
+
shorter than the cancel cutoff, a booking could be moved far ahead and then cancelled free. A
|
|
243
|
+
booking or event moved when cancelling it would already be late now keeps the start it had
|
|
244
|
+
(`cutoff_anchor`), and a later cancellation is judged, and its fee recorded, from it.
|
|
245
|
+
- **An agent no longer sets its own hold price.** The new booking rule `holdPrice` (`hold_price`)
|
|
246
|
+
is the tenant's price: an agent's hold of the resource waits for it whatever the agent asks, a
|
|
247
|
+
`payment` it names that is not the price is refused (`payment_price_mismatch`), and an agent
|
|
248
|
+
books the resource only from a paid hold. Without a price, an agent names none.
|
|
249
|
+
- **A key's caps count its pending approvals.** The time an agent's approvals hold while a person
|
|
250
|
+
decides counts against its key's `max_holds` and `max_held_hours`, so asking for approval never
|
|
251
|
+
holds more than the agent could hold itself (`hold_limit_reached`, `held_time_limit_reached`).
|
|
252
|
+
- **ISK and UGX in `@slotlock/payments-stripe`.** Stripe counts them in hundredths ISO 4217 has
|
|
253
|
+
not, so 1 krona paid read as 100 against a hold's price. The adapter converts
|
|
254
|
+
(`isoMinorUnits`), refuses an amount that is not whole, and `stripeAmount` gives the
|
|
255
|
+
PaymentIntent's.
|
|
256
|
+
|
|
257
|
+
From a review of RFC 0004's T2 changes, in five areas, each finding reproduced against a real
|
|
258
|
+
database first. This was not a human review, so it does not count as the independent review
|
|
259
|
+
GOVERNANCE.md requires.
|
|
260
|
+
|
|
261
|
+
- **Approvals could be got round by an update.** An agent's update that kept its event's time and
|
|
262
|
+
resource skipped every policy. A transparent event made opaque, or a granted booking given a
|
|
263
|
+
recurrence rule, booked that time unasked. A granted approval's idempotency key also booked any
|
|
264
|
+
other bundle. Updates are now judged by the time they would add, and a key answers only the
|
|
265
|
+
request a person approved.
|
|
266
|
+
- **A 129-byte holiday calendar could hang the server.** A daily rule on a day that never comes held
|
|
267
|
+
it for every tenant, again after each restart once subscribed. A 1 MB calendar held it for a
|
|
268
|
+
minute. Such rules are refused, a calendar's work is bounded, and a subscription due for import
|
|
269
|
+
is put off as it is taken. A subscription named after its URL shows the host only.
|
|
270
|
+
- **Payments a hold never used could be kept.** These were not refunded: a late payment for a
|
|
271
|
+
hold already cleared away, a refund Stripe did not make, and a paid hold that expired or was let go
|
|
272
|
+
before its booking. Slotlock now records whether a paid hold was booked. It answers the payment for
|
|
273
|
+
one that ended unbooked with `hold_ended`, and says so on the feed (`payment_unapplied`).
|
|
274
|
+
`@slotlock/payments-stripe` names the tenant on each PaymentIntent and refunds each such payment,
|
|
275
|
+
retrying a refund that failed. It logs at level error each payment it keeps.
|
|
276
|
+
- **An agent could let a booking go past its cutoffs another way:** by making it transparent, or a
|
|
277
|
+
series. Both are now refused as the cancel or move they are.
|
|
278
|
+
- **Busy time from calendars missed a composite's parts, and two booking paths.** A hall's busy time
|
|
279
|
+
is now counted on its rooms and a room's on the hall. A hold's confirmation and a series' roll
|
|
280
|
+
refuse it too. Busy time that ended a month ago goes.
|
|
281
|
+
- **A `@slotlock/sync` connect link could be reused** by anyone who had it for 30 minutes. A link is
|
|
282
|
+
now followed once, and its flow is finished only in the browser that followed it.
|
|
283
|
+
- **Reminders already sent were sent again when a reminder list changed.** Each sent reminder was
|
|
284
|
+
kept by its place in the list, so adding, removing or reordering one sent the earlier ones twice
|
|
285
|
+
(an SMS or email through `@slotlock/notify`). They are now kept by when they were due: a changed
|
|
286
|
+
list sends only what is new, and a moved booking is reminded again as before.
|
|
287
|
+
- **`@slotlock/sync`, hardened further:**
|
|
288
|
+
- One calendar that refuses for good (read only, access revoked) no longer stops write-back for
|
|
289
|
+
the whole tenant: it is that mapping's failure (`failedMappings`), and read-only calendars are
|
|
290
|
+
not offered for write-back.
|
|
291
|
+
- A connection disconnected during a pass no longer comes back with its tokens, nor records busy
|
|
292
|
+
time.
|
|
293
|
+
- Seals are bound to their tenant, and a mapping uses only its own tenant's connection.
|
|
294
|
+
Connections made before this must be connected again.
|
|
295
|
+
- iCalendar feeds are read on core's worker threads, with a deadline.
|
|
296
|
+
- Google connections hold only the scopes they asked for, and one missing a scope is refused.
|
|
297
|
+
- A library user who reads iCalendar URLs must pass the guarded fetch.
|
|
298
|
+
|
|
299
|
+
## 0.9.1 - 2026-11-11
|
|
300
|
+
|
|
301
|
+
### Fixed
|
|
302
|
+
|
|
303
|
+
- **The dashboard asks an owner to upgrade where a plan stops them.** A workspace at a limit of its
|
|
304
|
+
plan (10 of 10 resources on Free, say) saw only a red bar: Usage and the Overview now say
|
|
305
|
+
"On Free, you have 10 of 10 resources: adding another is refused", with **Upgrade**, and the
|
|
306
|
+
Overview's plan card offers it too. The same holds for the plan a workspace is about to move to.
|
|
307
|
+
An add refused at a limit (a resource, a key, a webhook) links to the upgrade, or to Usage for a
|
|
308
|
+
workspace that pays. A quota at 80% offers the upgrade as well as at 100%. A member is told an
|
|
309
|
+
owner can upgrade, and where the deployment neither sells nor arranges plans, no upgrade is named.
|
|
310
|
+
|
|
6
311
|
## 0.9.0 - 2026-11-11
|
|
7
312
|
|
|
8
313
|
### Added
|
package/NOTICE
CHANGED
|
@@ -10,6 +10,8 @@ each retains its own license. This release was built with these versions:
|
|
|
10
10
|
- ical.js 2.2.1, Mozilla Public License 2.0, https://github.com/kewisch/ical.js
|
|
11
11
|
- rrule 2.8.1, BSD 3-Clause License, https://github.com/jakubroztocil/rrule
|
|
12
12
|
- tslib (via rrule), BSD Zero Clause License (0BSD), https://github.com/Microsoft/tslib
|
|
13
|
+
- tldts 7.4.19, MIT License, https://github.com/remusao/tldts
|
|
14
|
+
- tldts-core (via tldts), MIT License, https://github.com/remusao/tldts
|
|
13
15
|
- zod 4.6.7, MIT License, https://github.com/colinhacks/zod
|
|
14
16
|
|
|
15
17
|
Peer dependency, installed and versioned by the application that embeds Slotlock:
|
package/README.md
CHANGED
|
@@ -16,8 +16,10 @@ TypeScript library.
|
|
|
16
16
|
|
|
17
17
|
## Get started
|
|
18
18
|
|
|
19
|
-
The documentation is at [slotlock.pylota.io/docs](https://slotlock.pylota.io/docs/)
|
|
20
|
-
|
|
19
|
+
The documentation is at [slotlock.pylota.io/docs](https://slotlock.pylota.io/docs/), with a page
|
|
20
|
+
each for the [booking MCP server](https://slotlock.pylota.io/mcp-server/), the
|
|
21
|
+
[booking API](https://slotlock.pylota.io/booking-api/), [use cases](https://slotlock.pylota.io/use-cases/)
|
|
22
|
+
and [pricing](https://slotlock.pylota.io/pricing/). There are three ways to run Slotlock:
|
|
21
23
|
|
|
22
24
|
- **[Slotlock Cloud](docs/hosted/README.md)**: nothing to run. Sign in with GitHub at
|
|
23
25
|
`https://app.slotlock.pylota.io/dashboard`, create a key, and connect an agent to
|