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