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.
- package/CHANGELOG.md +301 -0
- package/CODE_OF_CONDUCT.md +24 -0
- package/CONTRIBUTING.md +76 -0
- package/GOVERNANCE.md +52 -0
- package/LICENSE +201 -0
- package/MAINTAINERS.md +23 -0
- package/NOTICE +20 -0
- package/README.md +997 -2
- package/SECURITY.md +99 -0
- package/SPEC.md +240 -0
- package/agent-server.d.ts +367 -0
- package/agent-server.js +2684 -0
- package/agent-store-backend.d.ts +20 -0
- package/agent-store-backend.js +519 -0
- package/api-keys.d.ts +125 -0
- package/api-keys.js +280 -0
- package/cli.d.ts +2 -0
- package/cli.js +22 -0
- package/config.d.ts +35 -0
- package/config.js +138 -0
- package/dashboard.d.ts +108 -0
- package/dashboard.js +1200 -0
- package/ddl.d.ts +52 -0
- package/ddl.js +1789 -0
- package/engine.d.ts +20 -0
- package/engine.js +59 -0
- package/ical-worker-thread.d.ts +1 -0
- package/ical-worker-thread.js +29 -0
- package/ical-worker.d.ts +71 -0
- package/ical-worker.js +191 -0
- package/index.d.ts +24 -0
- package/index.js +18 -0
- package/mcp-app.d.ts +30 -0
- package/mcp-app.js +161 -0
- package/mcp-modern.d.ts +89 -0
- package/mcp-modern.js +299 -0
- package/node-server.d.ts +56 -0
- package/node-server.js +355 -0
- package/package-version.d.ts +2 -0
- package/package-version.js +20 -0
- package/package.json +64 -4
- package/rest-api.d.ts +40 -0
- package/rest-api.js +208 -0
- package/rules.d.ts +8 -0
- package/rules.js +157 -0
- package/self-host.d.ts +93 -0
- package/self-host.js +896 -0
- package/slotlock-manifest.schema.json +76 -0
- package/slotlock.manifest.json +43 -0
- package/store.d.ts +236 -0
- package/store.js +2235 -0
- package/sync.d.ts +181 -0
- package/sync.js +1941 -0
- package/timezone.d.ts +44 -0
- package/timezone.js +155 -0
- package/types.d.ts +394 -0
- package/types.js +3 -0
- package/weekly-hours.d.ts +38 -0
- 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.
|
package/CONTRIBUTING.md
ADDED
|
@@ -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.
|