slotlock 0.0.0-stage → 0.1.1

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 (59) hide show
  1. package/CHANGELOG.md +314 -0
  2. package/CODE_OF_CONDUCT.md +24 -0
  3. package/CONTRIBUTING.md +93 -0
  4. package/GOVERNANCE.md +52 -0
  5. package/LICENSE +201 -0
  6. package/MAINTAINERS.md +23 -0
  7. package/NOTICE +20 -0
  8. package/README.md +995 -2
  9. package/SECURITY.md +99 -0
  10. package/SPEC.md +240 -0
  11. package/agent-server.d.ts +367 -0
  12. package/agent-server.js +2684 -0
  13. package/agent-store-backend.d.ts +20 -0
  14. package/agent-store-backend.js +519 -0
  15. package/api-keys.d.ts +125 -0
  16. package/api-keys.js +280 -0
  17. package/cli.d.ts +2 -0
  18. package/cli.js +22 -0
  19. package/config.d.ts +35 -0
  20. package/config.js +138 -0
  21. package/dashboard.d.ts +108 -0
  22. package/dashboard.js +1200 -0
  23. package/ddl.d.ts +52 -0
  24. package/ddl.js +1789 -0
  25. package/engine.d.ts +20 -0
  26. package/engine.js +59 -0
  27. package/ical-worker-thread.d.ts +1 -0
  28. package/ical-worker-thread.js +29 -0
  29. package/ical-worker.d.ts +71 -0
  30. package/ical-worker.js +191 -0
  31. package/index.d.ts +24 -0
  32. package/index.js +18 -0
  33. package/mcp-app.d.ts +30 -0
  34. package/mcp-app.js +161 -0
  35. package/mcp-modern.d.ts +89 -0
  36. package/mcp-modern.js +299 -0
  37. package/node-server.d.ts +56 -0
  38. package/node-server.js +355 -0
  39. package/package-version.d.ts +2 -0
  40. package/package-version.js +20 -0
  41. package/package.json +64 -4
  42. package/rest-api.d.ts +40 -0
  43. package/rest-api.js +208 -0
  44. package/rules.d.ts +8 -0
  45. package/rules.js +157 -0
  46. package/self-host.d.ts +93 -0
  47. package/self-host.js +896 -0
  48. package/slotlock-manifest.schema.json +76 -0
  49. package/slotlock.manifest.json +43 -0
  50. package/store.d.ts +236 -0
  51. package/store.js +2235 -0
  52. package/sync.d.ts +181 -0
  53. package/sync.js +1941 -0
  54. package/timezone.d.ts +44 -0
  55. package/timezone.js +155 -0
  56. package/types.d.ts +394 -0
  57. package/types.js +3 -0
  58. package/weekly-hours.d.ts +38 -0
  59. package/weekly-hours.js +156 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,314 @@
1
+ # Changelog
2
+
3
+ All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and semantic
4
+ versioning.
5
+
6
+ ## 0.1.1 - 2026-10-22
7
+
8
+ ### Changed
9
+
10
+ - The README and the site install Slotlock from npm (`npm install slotlock`), now that 0.1.0 is
11
+ published there, instead of building it from a clone.
12
+
13
+ ### Added
14
+
15
+ - Releases are cut from GitHub Actions: "Release a version" prepares the version on a branch, merges
16
+ it once CI passes, and tags it; the tag stages the package on npm for a maintainer's approval and
17
+ publishes the image and the MCP Registry entry.
18
+
19
+ ## 0.1.0 - 2026-10-22
20
+
21
+ First public release. Nothing was published before it; the entries below describe the release as
22
+ shipped, including changes made during pre-release hardening. Slotlock was developed inside Pylota's
23
+ private monorepo and imported into this repository from PILOTAAI/pylota@4808094c8; the release pull
24
+ request replaces "Unreleased" with the release date.
25
+
26
+ ### Added for the standalone release
27
+
28
+ - The package is published as `slotlock`, requires Node.js 22.12 or newer, and is tested on
29
+ Node.js 22 and 24.
30
+ - Developer Certificate of Origin sign-off, a licence commitment in GOVERNANCE.md, and a trade mark
31
+ policy in TRADEMARKS.md.
32
+ - API keys: `slotlock key create|list|rotate|revoke` and `createSlotlockApiKeyStore`. A key is
33
+ `slk_` plus 46 base62 characters with a checksum, is shown once and stored only as its SHA-256,
34
+ belongs to one tenant, and carries the `read` and/or `write` scope. Keys can expire, rotate in
35
+ place (keeping their id and the events they booked), are revoked at once, record their last use
36
+ to the minute, and are capped at 100 active and 1,000 kept per tenant. A revoked, rotated-out or
37
+ erased key is never accepted again. The serving role reaches keys only through SECURITY DEFINER
38
+ functions and holds no right on their tables. Key lookups are bounded so made-up keys cannot take
39
+ the database pool. `SLOTLOCK_AUTH_TOKEN` is now optional for `serve`.
40
+ - A principal may carry `scopes`; the agent server then refuses, before `authorize`, every
41
+ operation whose scope (`slotlockAgentOperationScope`: `read` or `write`) it lacks.
42
+ - `createICalendarWorkerPool`: `parseICalendarChanges` and `parseTrustedICalendarEvents` on worker
43
+ threads, for feeds someone else controls. The calling thread never runs ical.js on the feed; a
44
+ worker past its deadline (3 s by default) or heap (128 MB) is stopped and the feed refused as
45
+ `CalendarExpansionLimitError`; a full queue answers `ICalendarWorkerBusyError`. Results and errors
46
+ are the parsers' own. `CalendarExpansionLimitError` is now exported.
47
+ - A REST API (`rest: true`, or `SLOTLOCK_REST_API=on` for `serve`): one route per tool under `/v1`,
48
+ described by an OpenAPI 3.1 document at `/openapi.json` built from the tools' own schemas, behind
49
+ the same authentication, scopes, `authorize` and rate limit. A write that waits for a person
50
+ answers 428 `confirmation_required`. `onEvent` reports each call's operation and status.
51
+ - The dashboard as a Cloudflare Worker (`workers/dashboard`): the same pages at an origin of their
52
+ own, over Hyperdrive with caching off, one database client per request, and two required rate
53
+ limiting bindings that bound sign-ins across every isolate. The Dashboard workflow only bundles
54
+ it; deployments use their own configuration and token. `createSlotlockDashboard` takes
55
+ `serviceUrl`, the server whose MCP and A2A URLs it shows when it is served from elsewhere.
56
+ - The image carries the label `service="slotlock"`, which Kamal requires of an image it pulls rather
57
+ than builds, so a release image can be deployed with Kamal as the service `slotlock`.
58
+ - A dashboard at `<SLOTLOCK_PUBLIC_URL>/dashboard` (`createSlotlockDashboard`), served by `serve`
59
+ when `SLOTLOCK_GITHUB_CLIENT_ID` is set: GitHub sign-in (OAuth with state and PKCE, no scope), a
60
+ personal tenant per GitHub user, and pages to create, rotate and revoke API keys (each shown
61
+ once), add resources and copy the MCP and A2A URLs. Sessions are signed HttpOnly cookies checked
62
+ against `SLOTLOCK_DASHBOARD_USERS` on every request; forms carry CSRF tokens and must come from
63
+ the server's origin; no inline script runs. Finished sign-ins, sent forms and signed-out sessions
64
+ are recorded in Postgres (`createSlotlockDashboardState`; digests only, until the cookie or form
65
+ expires by the database's clock), so they hold across servers whatever their clocks, and the
66
+ 100-resource cap is held in the database. Forms carry a single-use value bound to the session,
67
+ and one person may have 1,000 recorded at once (429 past that). `serve` refuses to start the
68
+ dashboard until `migrate` has granted its functions.
69
+ - `createResource` takes `maxTenantResources`, a per-tenant cap enforced under a database lock, and
70
+ `withTenant` takes `{ isolation: 'read committed' }`.
71
+ - Bookable hours per resource: `setResourceAvailability` and `setTenantAvailability` store a
72
+ resource's own weekly rules (`null` uses the default, `[]` closes), and the agent backend uses
73
+ them before `availabilityRules`. Set them on the dashboard's hours page (a week of windows, for
74
+ one resource or all) or with `slotlock hours show|set|clear`. `SLOTLOCK_AVAILABILITY` is now
75
+ the default for resources without hours of their own.
76
+ - `projectSlotlockCalendarEvent`: a stored event in the shape the event tools return, for an
77
+ application that writes events through the store and shows them to agents beside the tools'
78
+ own results.
79
+
80
+ ### Fixed for the standalone release
81
+
82
+ - The Compose quickstart did not start: with Postgres 18, the database container exited on a new,
83
+ empty volume, because the 18 image refuses a mount at `/var/lib/postgresql/data`. The volume is
84
+ now mounted at `/var/lib/postgresql`, and the 18 image keeps its data in `18/docker` under it.
85
+ - A write's confirmation question could show a different time, resource or number of bookings from
86
+ the ones the write makes. It now:
87
+ - names the resource by its own reference and shows times in the resource's zone (through the
88
+ new optional backend method `describeResource`, which the store backend has), and names an
89
+ event by its current time; in any other zone each time carries its own UTC offset;
90
+ - puts the agent's own text, and an event's stored title, after every fact, with anything that
91
+ reads as a double quote (curly, full-width, primes) turned into a single one;
92
+ - describes a repeating booking from the store's own expansion: how many times it books, the
93
+ first and the last, every moved occurrence, and what the rule says past the window the store
94
+ keeps booked (a moved or padded rule could book dates the question never named); a change to a
95
+ series says it covers every repeat;
96
+ - shows seconds when a time has them, and bounds each part, so no change is cut off the end;
97
+ - reads only what the caller's scopes and `authorize` allow, and gives up on lookups after 2
98
+ seconds, so a slow backend delays the question but cannot withhold it.
99
+ - Agents may only send recurrence rules RFC 5545 allows (known parts, each once, with valid values,
100
+ and none it forbids for the frequency) that repeat daily or less often and can occur. ical.js read
101
+ `COUNT=0` as no end, `COUNT=3X` as 3 and a repeated `FREQ` as the last one, and searched forever
102
+ for a daily rule that cannot occur (`BYMONTH=2;BYMONTHDAY=30`), holding the whole process.
103
+ - A rate limit is charged before a write's confirmation is asked for, not only when it runs.
104
+ - The sealed confirmation state compares its MAC as text: three other spellings of the same MAC
105
+ (the last base64url character's two spare bits) were accepted.
106
+ - `slotlock_list_resources` with a `cursor` that is not a resource id answers the `invalid_cursor`
107
+ tool error (HTTP 400) instead of an internal error (HTTP 500, JSON-RPC -32603).
108
+ - The legacy-schema upgrade test creates `btree_gist` itself, so the database suites pass on a fresh
109
+ PostgreSQL 16 database rather than only on one that already had the extension installed.
110
+ - Availability rules are held to the subset SPEC.md describes: `FREQ=WEEKLY`, `BYDAY`, `INTERVAL=1`
111
+ and a UTC `UNTIL`. Anything else, such as `BYHOUR`, a `DTSTART`, `BYDAY` twice or an ordinal
112
+ weekday, is refused by the store, the CLI and `SLOTLOCK_AVAILABILITY`, and `expandRules` reads it
113
+ as no hours. Before, rrule honoured some of these: a `DTSTART` zone moved Monday's hours to
114
+ Sunday, and `BYHOUR`/`BYMINUTE`/`BYSECOND` took most of a second per rule to check.
115
+ - An availability rule whose `UNTIL` is not a real date, such as `UNTIL=20270231T000000Z`
116
+ (31 February), is refused. Before, rrule read it with `Date.UTC`, which carries the overflow
117
+ into the next unit, so the rule stayed bookable until 3 March, after the date it was written to
118
+ end.
119
+ - The dashboard's GitHub requests ask for redirects to be returned rather than refused outright
120
+ (`redirect: 'manual'`, still refused as not `ok`). Cloudflare Workers reject `redirect: 'error'`
121
+ with a TypeError, so no one could sign in to a dashboard running there.
122
+ - Every dashboard form but sign-out must carry its single-use value. A form sent without the value
123
+ skipped the resend check and the per-person form limit.
124
+ - A feed's series no longer take each other's exceptions. ical.js relates every VEVENT with a
125
+ RECURRENCE-ID in a calendar to every series, whatever its UID, by local time, so one series'
126
+ cancelled or moved 09:00 cancelled or moved every other series' 09:00 that day, and
127
+ `parseICalendarChanges` reported that time as free.
128
+ - Converting a wall-clock time builds one Intl formatter per zone rather than one per conversion. A
129
+ feed of 20,000 dates now reads in about 0.3 seconds instead of 0.95 of its one-second limit, and a
130
+ stored daily series' window in about 50 milliseconds instead of 160.
131
+ - A recurrence rule that never occurs, such as `FREQ=DAILY;BYMONTH=2;BYMONTHDAY=30` or
132
+ `FREQ=DAILY;INTERVAL=7;BYDAY=TU` from a Monday, no longer freezes the process. Before, ical.js
133
+ 2.2.1 searched for its next occurrence without limit, whether the rule came from a feed
134
+ (`parseICalendarChanges`), a write (`putCalendarEvent`), a library call
135
+ (`expandCalendarEventOccurrences`) or a stored series being rolled. The same held for a feed's own
136
+ time zone: ical.js expands a VTIMEZONE's rule to read a time in a zone Intl does not know, so one
137
+ such rule froze `parseICalendarChanges` or `parseTrustedICalendarEvents` on the first event,
138
+ repeating or not. A huge `DURATION` did the same with no rule at all, since ical.js adds a span to
139
+ a time a month at a time. Reading or expanding a calendar now has these limits, and one that
140
+ needs more is refused as `invalid_icalendar` (`invalid_event` on a write):
141
+ - a feed, and a stored series as maintenance rolls it: 50,000 search steps (its time zones
142
+ included) and one second for the whole read, from the text on;
143
+ - one event as it is written or asked about (`putCalendarEvent`,
144
+ `expandCalendarEventOccurrences`): half of that, so a daily or rarer series written today keeps
145
+ rolling for decades as its search grows;
146
+ - a SECONDLY to WEEKLY step, a `DURATION`, a DTEND after its DTSTART, or an occurrence moved by an
147
+ exception: about a century at most (36,600 days);
148
+ - 1,000 RDATE and EXDATE values per event;
149
+ - per feed: 100 VTIMEZONEs (counted in the text before ical.js parses it, and again after),
150
+ 1,000 observances and 2,000 RDATE values in them, and 20,000 RDATE and EXDATE values in its
151
+ events. Before, a zone of 40,000 RDATE values (expanded again for each later year read) ran a
152
+ feed out of memory, an exception for every later occurrence (`RANGE=THISANDFUTURE`) read a slow
153
+ zone for minutes outside the search limit, and a TZID none of 20,000 zones defined held a read
154
+ for 31 seconds;
155
+ - per property: 8 KB of name and parameters, and 100 parameters, before its value. ical.js reads
156
+ each parameter by searching to the end of the line, and 490,000 of them on one line held a read
157
+ for 7.9 seconds before any other limit applied.
158
+
159
+ ical.js also expands a feed's own zone again, from its start, each time it reads a later year,
160
+ appending every change to those it had. A zone at those limits, read for 1,000 later years inside
161
+ one call, took up to 35 seconds and 650 MB. Each expansion now keeps one copy, and the time limit
162
+ is checked before each.
163
+
164
+ `rollCalendarEventHorizon` skips a stored series that goes past these limits, which includes a
165
+ daily series about 11 years after its start (4,000 occurrences). It counts the series in the new
166
+ `refused` field and lists it in `refusedEventIds`, keeps its old coverage, and still rolls the
167
+ series after it. Before, such a series made every roll for its tenant throw. A series Slotlock
168
+ cannot read at all still fails the roll. The maintenance example sums `refused`.
169
+ - A resource id the store never issues, such as an external reference (`vehicle-42`), the nil UUID
170
+ or a version 7 UUID, made free/busy, next-available, list-events, create and update answer a
171
+ server error: HTTP 500 and JSON-RPC `-32603` over MCP (so an SDK client threw), 500
172
+ `internal_error` over REST, `-32603` over A2A, and 500 from `resources/read`. The store backend
173
+ now answers `resource_not_found` for it, as for any id that names no resource of the caller's.
174
+ Nothing new is accepted: the store still refuses such ids.
175
+ - A failure an agent sees only as `internal_error` was logged nowhere. The agent server and
176
+ `invokeSlotlockAgentOperation` take `onError(error, { operation })`, called with an exception that
177
+ is not a `SlotlockAgentOperationError` and for a result outside the operation's output schema,
178
+ never with the arguments or the request; `serve` logs it as `agent_error`, with its message and
179
+ code and every configured secret redacted.
180
+ - The server reported version 1.0.0 (`SLOTLOCK_AGENT_SERVER_VERSION`, `/healthz`, MCP `serverInfo`,
181
+ the agent card, the manifest and the OpenAPI document) while the package was 0.1.0. It now reads
182
+ the version from package.json, as `slotlock --version` does, so there is one place to change it.
183
+ `SLOTLOCK_AGENT_SERVER_VERSION` is typed `string`.
184
+ - `serve` connected as whatever role `DATABASE_URL` named, so a superuser or `BYPASSRLS` role
185
+ served every tenant with row-level security skipped, silently. It now refuses to start as one
186
+ (`unsafe_serving_role`) and says how to set up a serving role; `grantApplicationRole` already
187
+ refused one.
188
+ - `slotlock --help` did not list `SLOTLOCK_REST_API`. A test now checks that it lists every
189
+ variable the commands read.
190
+ - The Compose stack passed none of the dashboard's settings to the server, so the dashboard could
191
+ not be turned on. `docker-compose.yml` passes `SLOTLOCK_GITHUB_CLIENT_ID`,
192
+ `SLOTLOCK_GITHUB_CLIENT_SECRET`, `SLOTLOCK_SESSION_SECRET` and `SLOTLOCK_DASHBOARD_USERS`, empty
193
+ (dashboard off) unless `.env` sets them, and `.env.example` documents them.
194
+ - The README's way to run without Docker (`slotlock migrate`, then `slotlock serve`) did not work
195
+ from a clone, where `slotlock` is not on the `PATH`; it now gives the `node dist/cli.js` commands
196
+ with a serving role and the owner's URL for `migrate`. The quickstart states that the confirmation
197
+ in step 6 needs an MCP client on protocol 2026-07-28, and the README no longer mentions a hosted
198
+ Slotlock.
199
+
200
+ ### Added
201
+
202
+ - Deterministic interval merge, subtraction, and next-available search.
203
+ - Timezone-aware weekly availability expansion with explicit fail-closed rule support.
204
+ - PostgreSQL resources, reservations, atomic holds, conflict arbitration, and idempotent external
205
+ reservation commands.
206
+ - Configurable forced row-level-security DDL with a consumer-neutral tenant context.
207
+ - Transaction-scoped tenant store callbacks and exact installed-policy drift fingerprints.
208
+ - Bounded iCalendar parsing, normalization, recurrence expansion, and deterministic export.
209
+ - Tenant-scoped event CRUD with optimistic revisions, command idempotency, cancellation tombstones,
210
+ transparent events, and conflict-safe occurrence materialization through the reservation arbiter.
211
+ - Bounded recurrence exceptions, organizer/attendee RSVP state, relative reminders, trusted full-event
212
+ parsing, RFC 5545/iTIP serialization, and coverage-aware free/busy certainty.
213
+ - Compiled ESM and declaration output, a clean npm artifact contract, and provenance-ready release
214
+ automation.
215
+ - A normative agent-calendar specification and schema-validated machine-readable capability
216
+ manifest.
217
+ - A consumer-neutral authenticated MCP 2025-11-25 and A2A 1.0 HTTP server projected from one strict
218
+ operation registry, plus a tenant-bound adapter for the PostgreSQL event and free/busy store.
219
+ - A bounded Node.js HTTP listener adapter with canonical request origins, disconnect propagation,
220
+ execution deadlines, sanitized failures, and graceful shutdown that force-closes after its deadline.
221
+ - Intrinsic local-recurrence coverage certainty and a 367-day protocol read/search window bound.
222
+ - A stable MCP Apps calendar resource for read-only tool results, with a self-contained network-dark
223
+ interface and unchanged text/structured results for non-App MCP clients.
224
+ - `store.pruneCalendarEventRetention` and `SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS`: bounded deletion of
225
+ idempotency commands past the replay window and of command-free agent tombstones, which returns
226
+ per-principal quota. `internal` tombstones are never pruned.
227
+ - `store.grantApplicationRole`, `createSlotlockApplicationRoleGrantsDdl` and `SLOTLOCK_TENANT_TABLES`:
228
+ least-privilege grants for the application role. The grant refuses, with the `reasons` it found,
229
+ a role that could get around forced RLS or the exclusion arbiter, held itself, through a role it
230
+ belongs to or through `PUBLIC`: superuser, `BYPASSRLS`, `REPLICATION`, `CREATEROLE`, the server
231
+ file and program roles, ownership of the database, anything in it or `btree_gist`, `CREATE` on the
232
+ database or any schema, `TRUNCATE`, `REFERENCES` or `TRIGGER` on a Slotlock table (or by default on
233
+ new ones), or a right on a server setting.
234
+ - MCP `ping`, negotiation of MCP 2025-06-18 alongside 2025-11-25
235
+ (`SLOTLOCK_MCP_SUPPORTED_PROTOCOL_VERSIONS`), and optional RFC 9728 protected-resource metadata with
236
+ a `resource_metadata` challenge on the MCP `401` (`oauth` option).
237
+ - `resolveSlotlockAgentOperation`, `slotlockMcpToolResult`, `SLOTLOCK_AGENT_OPERATION_LEGACY_NAMES`,
238
+ `SlotlockSql` and `SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE` exports.
239
+ - Typechecked, executed examples quoted verbatim by the README.
240
+ - MCP 2026-07-28, stateless, beside 2025-11-25 and 2025-06-18: `server/discover`; every request's
241
+ `_meta` envelope and its mirrored `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers are
242
+ validated in the specification's order (`-32602`, `-32020`, `-32022`); results carry `resultType`
243
+ and the server's identity; an unknown method is HTTP 404 `-32601` and a rate limit HTTP 429
244
+ `SLOTLOCK_MCP_RATE_LIMITED_ERROR_CODE` (`-31029`). `initialize` still selects a 2025 revision
245
+ (`SLOTLOCK_MCP_LEGACY_PROTOCOL_VERSION`). Tested with the official TypeScript SDK 2.1.0 and 1.30.1.
246
+ - Human confirmation before writes (`confirmation`): on 2026-07-28 a listed write answers
247
+ `input_required` with a form elicitation describing the change and runs only on a retry carrying
248
+ the person's acceptance and a `requestState` HMAC-sealed to the caller, the tool, the exact
249
+ arguments and an expiry; a client without form elicitation gets `-32021`, and where no
250
+ confirmation round exists (2025 revisions, A2A) the write fails with `confirmation_required`.
251
+ - Calendar resources on 2026-07-28 (`slotlock://resources/{resource_id}`,
252
+ `SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE`, `slotlockCalendarResourceUri`, `resourceWindowDays`):
253
+ `resources/list`, `resources/templates/list` and `resources/read` (free/busy and coverage).
254
+ - Live calendar updates through `subscriptions/listen` (`subscriptions` options): an SSE stream
255
+ that acknowledges the resources this caller may read, then sends `notifications/resources/updated`
256
+ on every change, found by re-reading with fresh authentication and authorization; keep-alives,
257
+ per-principal and server-wide caps, a maximum duration, graceful closure, and `shutdown()`.
258
+ - Cache hints (`ttlMs`, `cacheScope`) on 2026-07-28 discovery, lists and reads, and validated W3C
259
+ trace context (`traceparent`, `tracestate`, `baggage`) handed to the backend as `context.trace`.
260
+ - `onEvent`: confirmation outcomes and subscription opens and closes, without arguments or
261
+ identities.
262
+ - All-day (`VALUE=DATE`) iCalendar events, series included, imported as whole local days of the
263
+ calendar's timezone (the `calendarTimezone` argument, else `X-WR-TIMEZONE`).
264
+ - One-off events of up to 3,660 days (`SLOTLOCK_MAX_EVENT_DURATION_DAYS`), so a lease or a long
265
+ rental is one event; `slotlock_find_next_available` can look for a slot as long as its 367-day
266
+ window.
267
+
268
+ ### Changed during pre-release
269
+
270
+ - Operations are named `slotlock_<verb>`: portable across Claude, OpenAI and MCP hosts, and
271
+ namespaced by service so an agent that sees tools from many servers can tell which calendar it is
272
+ calling. The names of earlier builds, the dotted `calendar.<verb>` and the underscored
273
+ `calendar_<verb>`, still resolve on every entry point and are never advertised, so agents written
274
+ against either keep working.
275
+ `authorize`, `consumeRateLimit` and the backend always receive the current name, and
276
+ `isSlotlockAgentOperation` accepts current names only (`resolveSlotlockAgentOperation` maps any).
277
+ - The bundled MCP App reads a refused call's error code from its text content, since failures no
278
+ longer carry `structuredContent`.
279
+ - OAuth issuer identifiers are published exactly as configured (RFC 8414 compares them as strings)
280
+ and must be canonical URLs without credentials, query or fragment: `https`, or loopback `http` in
281
+ `allowInsecureLocalhost` mode.
282
+ - MCP tool failures are `isError` results with `{"error":{"code"}}` text and no `structuredContent`,
283
+ which MCP SDKs validate against the tool's output schema; server faults are JSON-RPC `-32603`.
284
+ - The A2A agent card follows a2a.proto (`securityRequirements` schemes, skill invocation examples,
285
+ no skill `metadata`) and is also served at the origin root; A2A JSON-RPC errors use an HTTP 200
286
+ envelope with `google.rpc.ErrorInfo` details, `VersionNotSupportedError`,
287
+ `PushNotificationNotSupportedError` and `UnsupportedOperationError`; `GetTask` and `CancelTask`
288
+ answer `TaskNotFoundError` and `ListTasks` an empty page, after refusing unknown fields, another
289
+ tenant, mistyped values, a timestamp that names no instant and unissued page tokens
290
+ (`SLOTLOCK_A2A_TASK_PAGE_SIZE`, `SLOTLOCK_A2A_TASK_STATES`, `parseSlotlockA2ATimestamp`); a field
291
+ without presence at its proto3 default is unset; skill failures are the reply's data part.
292
+ - Requests outside the public base path are not served; loopback IPv6 is accepted in
293
+ `allowInsecureLocalhost` mode.
294
+ - Deployment SQL resolves names only in `pg_catalog`: `applySchema`, `applyTenantRls` and
295
+ `grantApplicationRole` pin `search_path` for their transaction and restore it; `SLOTLOCK_CORE_DDL`
296
+ and `createSlotlockTenantRlsDdl()` pin it while they run and restore the caller's at their end
297
+ (apply each in one transaction); Slotlock's functions pin their own. Policy fingerprints use core
298
+ `sha256` (the same values as pgcrypto's `digest` in a UTF-8 database), a missing `btree_gist` is
299
+ created in schema `slotlock`, and `pgcrypto` is no longer required.
300
+ - `applySchema`, `applyTenantRls` and the exported DDL refuse (`unsafe_slotlock_schema`; SQLSTATE
301
+ `42501` from the DDL) a `slotlock` schema that another role owns or can create objects in, that
302
+ holds an object another role owns (an extension included), or whose tables another role may add
303
+ triggers to or have a trigger running a function another role owns, superusers aside.
304
+ - `postgres` is a peer dependency (`^3.4.5`) so an application's own client type-checks against the
305
+ store; the declarations keep their `@types/node` reference for `"types": []` consumers.
306
+ - License and notices name TREFT LTD as copyright holder and list every runtime dependency.
307
+ - `SLOTLOCK_MCP_PROTOCOL_VERSION` is `2026-07-28`; `initialize` answers the client's own 2025 revision,
308
+ else 2025-11-25.
309
+ - `applySchema` replaces the event materialization-window check with
310
+ `slotlock_calendar_events_materialization_window_bounded` (added `NOT VALID`, so existing rows are
311
+ not rescanned), which lets a one-off event's window match the event however long it is.
312
+ - `createSlotlockNodeServer` calls its target's `shutdown()` when closing and closes keep-alive
313
+ connections whose response finished during shutdown, so open streams drain within the grace
314
+ period.
@@ -0,0 +1,24 @@
1
+ # Code of Conduct
2
+
3
+ We are committed to a welcoming, harassment-free project for everyone, regardless of background,
4
+ identity, experience, or role.
5
+
6
+ Be respectful, assume good intent while addressing concrete impact, give actionable feedback, and
7
+ keep technical disagreement focused on the work. Harassment, threats, discrimination, doxxing, and
8
+ the publication of private calendar or customer data are not acceptable.
9
+
10
+ ## Reporting
11
+
12
+ Report conduct concerns privately by email to
13
+ [clever.eziogor@pylota.io](mailto:clever.eziogor@pylota.io), which reaches the maintainers listed in
14
+ [MAINTAINERS.md](./MAINTAINERS.md). If you would rather not use email, open a GitHub issue that asks
15
+ a maintainer to contact you and contains no details of the concern; a maintainer will arrange a
16
+ private conversation. A concern about a maintainer goes to another maintainer where there is one.
17
+
18
+ Maintainers may remove content, restrict participation, or ban contributors when needed to protect
19
+ the community. Reports are handled as confidentially as practical, and retaliation is prohibited.
20
+
21
+ ## Scope
22
+
23
+ This policy applies in project repositories, issue trackers, reviews, discussions, community events,
24
+ and when representing the project elsewhere.
@@ -0,0 +1,93 @@
1
+ # Contributing to Slotlock
2
+
3
+ Slotlock accepts focused issues and pull requests that preserve its resource-first, deterministic
4
+ contract. By contributing you agree to the [Code of Conduct](./CODE_OF_CONDUCT.md); maintainers are
5
+ listed in [MAINTAINERS.md](./MAINTAINERS.md).
6
+
7
+ ## Development
8
+
9
+ You need Node.js 22.12 or newer and, for the database suites, PostgreSQL 16. From the repository
10
+ root:
11
+
12
+ ```sh
13
+ npm ci
14
+ npm run typecheck # the package, then examples/ as a consumer compiles them
15
+ npm test # tests, build, then the packed-artifact verification
16
+ ```
17
+
18
+ Database suites (`*.integration.test.ts`) run when `DATABASE_URL` points at a PostgreSQL 16
19
+ database whose connecting role can create roles and extensions, and are skipped otherwise:
20
+
21
+ ```sh
22
+ DATABASE_URL=postgresql://slotlock_dev:dev-only-password@localhost:5432/slotlock_test npm test
23
+ ```
24
+
25
+ Use a disposable database: the suites apply the schema and forced RLS, and create short-lived
26
+ roles. Never point them at production or shared customer data. Connect as any role except one named
27
+ `slotlock`, and with a password of 12 or more characters that does not contain "slot" or "lock". A
28
+ role named `slotlock` is the package's schema, which `"$user"` puts first on the search path. The
29
+ command-line tool redacts the database password from everything it prints, so a short or
30
+ product-like password rewrites the output the tests read.
31
+
32
+ ## Sign off your commits
33
+
34
+ Slotlock uses the [Developer Certificate of Origin](https://developercertificate.org/) instead of a
35
+ contributor licence agreement. Every commit in a pull request must carry a `Signed-off-by:` line
36
+ with your name and email, which certifies that you wrote the change or have the right to submit it
37
+ under the project's licence:
38
+
39
+ ```sh
40
+ git commit -s -m "Explain the change"
41
+ ```
42
+
43
+ A check on each pull request fails when a commit lacks the line. To fix earlier commits, run
44
+ `git rebase --signoff main` and force-push your branch.
45
+
46
+ ## Documentation
47
+
48
+ Every TypeScript block in README.md is quoted from a region of `examples/*.ts`
49
+ (`// #region <name>` … `// #endregion <name>`) marked `<!-- example: examples/<file>#<name> -->`.
50
+ Edit the example, not the README copy: `readme-examples.test.ts` fails when they differ, and
51
+ `examples.integration.test.ts` runs the examples against PostgreSQL. The website and docs live in
52
+ `site/`.
53
+
54
+ ## Pull requests
55
+
56
+ - Add a regression test that fails without the change.
57
+ - Preserve half-open interval semantics and structured expected outcomes.
58
+ - Document public API changes in `CHANGELOG.md`, and keep SPEC.md normative.
59
+ - Run typecheck, tests, build, and package verification.
60
+ - Call out database, tenancy, privacy, sync, or supply-chain risk explicitly.
61
+ - Do not add provider credentials, product-specific authorization, or network calls to the core.
62
+
63
+ Changes to exclusion constraints, RLS, identity/idempotency, retention, iCalendar trust boundaries,
64
+ protocol error contracts, or release automation require a security review; while the project has
65
+ one maintainer, GOVERNANCE.md's single-maintainer rule applies.
66
+
67
+ ## Releases
68
+
69
+ Add each change's entry under `## Unreleased` in `CHANGELOG.md`, in the pull request that makes it:
70
+ that section becomes the release notes.
71
+
72
+ To release, run **Actions > Release a version** with the version (for example `0.1.1`). It sets the
73
+ version in `package.json`, `package-lock.json`, `server.json` and `slotlock.manifest.json` and dates
74
+ the `## Unreleased` heading (`scripts/prepare-release.sh`) and opens the pull request. GitHub holds
75
+ the checks of a pull request a workflow opens until someone with write access approves them: open it
76
+ and click **Approve workflows to run**. The run then merges it once CI and DCO pass on it, and starts
77
+ `tag-release.yml`, which tags the release commit. The tag starts
78
+ `release.yml` (the suite, then npm and the GitHub release) and `image.yml` (the image, then the MCP
79
+ Registry). npm holds each staged version until a maintainer approves it with two-factor
80
+ authentication (GOVERNANCE.md); the Release run's summary gives the command. A version merged to
81
+ `main` by hand, with its heading dated, is released the same way. After a failed run, run it again
82
+ with the same version: it keeps the release branch the failed run left when that already holds this
83
+ release, so checks approved there still count, replaces it otherwise, and reuses its pull request
84
+ (`scripts/open-release-pr.sh`).
85
+
86
+ GitHub lets a workflow open pull requests only once an admin allows it (Settings > Actions >
87
+ General > Workflow permissions); `scripts/configure-github.sh` sets it, and the run says so when it
88
+ is off.
89
+
90
+ When a run needs a step npm or GitHub keeps for a person (the first npm version, the npm trusted
91
+ publisher, the image's visibility, and the deploy key `tag-release.yml` pushes tags with), the
92
+ failing job names `scripts/bootstrap-release.sh`. One run of it does that step, re-runs the failed
93
+ jobs and waits for them.
package/GOVERNANCE.md ADDED
@@ -0,0 +1,52 @@
1
+ # Governance
2
+
3
+ Slotlock uses a maintainer-led, review-based model. The maintainers are listed in
4
+ [MAINTAINERS.md](./MAINTAINERS.md).
5
+
6
+ ## Roles
7
+
8
+ - Contributors propose issues, documentation, tests, and code.
9
+ - Maintainers triage work, review changes, cut releases, and enforce project policy.
10
+ - Security maintainers receive private reports and may coordinate embargoed fixes.
11
+
12
+ Maintainer status is earned through sustained, constructive contributions and is granted by
13
+ consensus of the existing maintainers. Inactive maintainers may move to emeritus status.
14
+
15
+ ## Decisions
16
+
17
+ Routine changes require one approving maintainer and green required checks. Changes to public API,
18
+ data model, scheduling semantics, tenancy, security boundaries, or governance require an issue or
19
+ RFC and two maintainer approvals. Security-sensitive changes also require an independent security
20
+ review. A maintainer with a material conflict of interest must recuse themselves.
21
+
22
+ ### While there is one maintainer
23
+
24
+ Until a second maintainer is appointed, a change that requires two maintainer approvals instead
25
+ requires the maintainer's approval **and** an independent security review of that change by someone
26
+ other than its author, recorded on the pull request. A change the maintainer authored still needs
27
+ that independent review before it merges. An automated or AI-generated review does not count as
28
+ that independent review. This rule ends when MAINTAINERS.md lists two maintainers.
29
+
30
+ ## Licence commitment
31
+
32
+ The Slotlock engine, its specification, its MCP and A2A server, and every package this repository
33
+ publishes stay under the Apache License 2.0 or another licence approved by the Open Source
34
+ Initiative. Contributions are accepted under the Developer Certificate of Origin with no contributor
35
+ licence agreement, so every contribution arrives under the same Apache-2.0 terms it leaves under and
36
+ no single party can relicense the contributions of others. Once the project has a maintainer from
37
+ outside TREFT LTD, changing this commitment also needs that maintainer's approval.
38
+
39
+ ## Releases
40
+
41
+ Releases follow semantic versioning and are built only by the repository release workflow from a
42
+ protected `v<version>` tag on `main`. The tag must match `package.json` and a `CHANGELOG.md`
43
+ heading. The workflow stages the tested artifact to npm with trusted publishing (short-lived OIDC
44
+ credentials and provenance); a maintainer then approves the staged version with two-factor
45
+ authentication. Long-lived npm tokens are not accepted. Release artifacts must pass the
46
+ package-content and consumer checks.
47
+
48
+ ## Project changes
49
+
50
+ License changes, transfer to another organization, archival, or changes to this governance model
51
+ require a public proposal, a minimum seven-day comment period, and unanimous active-maintainer
52
+ approval, and are bound by the licence commitment above.