slotlock 0.3.0 → 0.5.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 +252 -0
- package/README.md +226 -17
- package/agent-server.d.ts +51 -8
- package/agent-server.js +478 -36
- package/agent-store-backend.d.ts +6 -0
- package/agent-store-backend.js +771 -30
- package/application-api.d.ts +22 -1
- package/application-api.js +412 -3
- package/booking-rules.d.ts +84 -0
- package/booking-rules.js +149 -0
- package/config.d.ts +20 -1
- package/config.js +33 -0
- package/dashboard.d.ts +61 -5
- package/dashboard.js +995 -139
- package/ddl.d.ts +5 -5
- package/ddl.js +590 -5
- package/engine.d.ts +5 -0
- package/engine.js +7 -4
- package/event-feed.d.ts +178 -0
- package/event-feed.js +435 -0
- package/index.d.ts +8 -2
- package/index.js +6 -1
- package/interop.d.ts +117 -0
- package/interop.js +356 -0
- package/maintenance.d.ts +14 -2
- package/maintenance.js +35 -3
- package/mcp-app.d.ts +7 -1
- package/mcp-app.js +259 -40
- package/metrics.d.ts +24 -0
- package/metrics.js +167 -0
- package/openapi.json +8346 -3400
- package/package.json +1 -1
- package/rate-limit.d.ts +10 -1
- package/rate-limit.js +25 -2
- package/rest-api.d.ts +22 -0
- package/rest-api.js +160 -15
- package/self-host.d.ts +26 -3
- package/self-host.js +316 -19
- package/slotlock-manifest.schema.json +1 -0
- package/slotlock.manifest.json +2 -1
- package/store.d.ts +317 -8
- package/store.js +1005 -89
- package/types.d.ts +87 -0
- package/types.js +0 -2
- package/webhooks.d.ts +91 -0
- package/webhooks.js +300 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,258 @@
|
|
|
3
3
|
All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and semantic
|
|
4
4
|
versioning.
|
|
5
5
|
|
|
6
|
+
## 0.5.0 - 2026-10-28
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
|
|
10
|
+
- The production checklist advises a `transaction_timeout` on the serving role (PostgreSQL 17+),
|
|
11
|
+
so a caller's transaction a store call runs inside cannot hold its locks and snapshot without
|
|
12
|
+
bound; on 16, `idle_in_transaction_session_timeout` and `statement_timeout`. A test pins the
|
|
13
|
+
setting on the CI's PostgreSQL 17 and 18.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- How long history is kept is a setting: `SLOTLOCK_RETENTION_DAYS` (`retentionDays` in
|
|
18
|
+
`createSlotlockStore`), 1 to 3,650 days, 30 by default. It sets how long the change feed is kept
|
|
19
|
+
and how far back a cursor reaches, and ended waitlist entries; above 30 days, retired idempotency
|
|
20
|
+
answers and booking references too (never under 30). `slotlock maintain` reads it as `serve` does.
|
|
21
|
+
- A feed cursor answers `cursor_expired` exactly when a change after it has been deleted, by this
|
|
22
|
+
server's maintenance or any other (an older server's in a rolling deploy, an operator's): a trigger
|
|
23
|
+
on the feed records how far each tenant's was pruned (`slotlock.outbox_pruned`, created once and
|
|
24
|
+
started at each tenant's oldest kept change). How old a cursor is no longer matters, so a server
|
|
25
|
+
keeping changes longer than the one that pruned them never reads past a gap unseen, and a reader
|
|
26
|
+
that is up to date stays readable. Maintenance prunes the oldest changes in feed order, never past
|
|
27
|
+
one it keeps or a transaction still running, so a reader starting over always reads on.
|
|
28
|
+
- One holder's holds are bounded in time as well as number: `maxHeldHoursPerHolder` in
|
|
29
|
+
`createSlotlockStore` refuses a hold that would bring the holder's live holds past that many
|
|
30
|
+
hours together (`held_time_limit_reached`), and `maxHoldsPerHolder` sets the count that was fixed
|
|
31
|
+
at 20. `slotlock serve` sets them with `SLOTLOCK_LIMITS` (`holder_holds`, 20;
|
|
32
|
+
`holder_held_hours`, 2,160).
|
|
33
|
+
- `slotlock_get_pool_availability` (`GET /v1/pool-availability`): how many of a pool's members are
|
|
34
|
+
free across a window, the window split where that number changes, with coverage certainty, so an
|
|
35
|
+
agent can say "three vans free 9 to 11, one after" before booking with `slotlock_book_pool`.
|
|
36
|
+
- Prometheus metrics: with `SLOTLOCK_METRICS_TOKEN`, `slotlock serve` answers `/metrics` to that
|
|
37
|
+
bearer token: calls by protocol, operation and outcome with their duration, confirmations,
|
|
38
|
+
subscriptions, refusals, maintenance and webhook deliveries, labelled by closed sets only (never a
|
|
39
|
+
tenant, key or client). `createSlotlockMetrics` counts the same from a server's own hooks.
|
|
40
|
+
- The booking rule `requireCertain` (`require_certain`): a booking must lie in time every calendar
|
|
41
|
+
sync recording coverage for the resource has read within a day, or it is refused (`uncertain`,
|
|
42
|
+
`booking_rule_uncertain` to an agent), rather than booked over what a stale or failing sync may
|
|
43
|
+
hide. Searches on such a resource, the store's and the agents', require the same coverage, of the
|
|
44
|
+
slot and its turnaround, within the same day. The dashboard's rules form has it.
|
|
45
|
+
- `DELETE /v1/resources/{id}/coverage/{source}` (`slotlock_delete_coverage`, `deleteCoverage`)
|
|
46
|
+
retires a calendar sync: free/busy and `requireCertain` stop waiting on its coverage.
|
|
47
|
+
|
|
48
|
+
- The dashboard is an app with a sidebar: an Overview with the coming week's bookings, events and
|
|
49
|
+
live holds across every resource and the agents' latest changes, named by key; Resources; API
|
|
50
|
+
keys, with the endpoints; and Settings. `SLOTLOCK_DASHBOARD_NAME` (`name` in
|
|
51
|
+
`createSlotlockDashboard`) names the deployment. Works without JavaScript, light and dark.
|
|
52
|
+
- Dashboard pages for 0.4's features: a page per resource (bookable hours, booking rules, pool, its
|
|
53
|
+
coming bookings and activity), Waitlist (entries and their offers; removing one offers its time
|
|
54
|
+
on) and Webhooks (add with the secret shown once, delete, re-enable, and each endpoint's health:
|
|
55
|
+
delivering, failing since when and why, or disabled). `SlotlockDashboardResources` takes them as
|
|
56
|
+
optional `setRules`, `setPool`, `waitlist` and `webhooks`; a page shows only when its adapter
|
|
57
|
+
offers it.
|
|
58
|
+
- `listAgenda` reads what occupies a tenant's resources in a window, every resource at once:
|
|
59
|
+
bookings, calendar events' occurrences and live holds. `listRecentChanges` reads a tenant's
|
|
60
|
+
latest changes, newest first. `SlotlockDashboardResources` takes both as optional `agenda` and
|
|
61
|
+
`recentChanges`.
|
|
62
|
+
|
|
63
|
+
- Organizations in the dashboard: with `SLOTLOCK_DASHBOARD_ORGS` (GitHub organization ids), a
|
|
64
|
+
tenant is an organization, `github-org:<id>`, that its members share. Sign-in asks for `read:org`
|
|
65
|
+
and reads the person's active memberships as an owner or member (not a billing manager); a member
|
|
66
|
+
of several served organizations switches between them, and someone in none is refused. A form
|
|
67
|
+
works only for the organization it was opened in, and the key list says who made each key.
|
|
68
|
+
Removing an organization from the list signs its sessions out and stops its keys in `slotlock
|
|
69
|
+
serve`, which reads the list whether or not it serves the dashboard itself. `createSlotlockDashboard` takes
|
|
70
|
+
`organizations`.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- A key created in the dashboard records the person who created it (`github:<user id>`) as
|
|
75
|
+
`createdBy`, not the tenant; for a personal tenant the two are the same.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Time an agent frees is offered to the waitlist at once, as the application API's cancels already
|
|
80
|
+
were: an agent releasing its holds (`slotlock_release_slots`), deleting or moving an event, and a
|
|
81
|
+
booking moved through `PUT /v1/reservations`. Before, it waited for the next maintenance pass, up
|
|
82
|
+
to an hour.
|
|
83
|
+
|
|
84
|
+
## 0.4.1 - 2026-10-28
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- The Python client is published to PyPI again. 0.4.0's PyPI job could not build it: hatchling, the
|
|
89
|
+
build backend, now needs `tomlkit`, which the release's pinned build requirements did not list.
|
|
90
|
+
0.4.1 is otherwise 0.4.0; the other packages are released at 0.4.1 with no change.
|
|
91
|
+
|
|
92
|
+
## 0.4.0 - 2026-10-28
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- Waitlists: `POST /v1/waitlist` (`slotlock_join_waitlist`, `joinWaitlist`) waits for a window on a
|
|
97
|
+
resource or in a pool; `GET /v1/waitlist` and `DELETE /v1/waitlist/{entry_id}` list and leave it.
|
|
98
|
+
Freed time is offered in the order entries joined, as a hold for the waiter (`waitlist.offered` on
|
|
99
|
+
the feed): at once after a cancel or release through the API, and by maintenance otherwise, which
|
|
100
|
+
also expires entries whose window started (`waitlist.expired`).
|
|
101
|
+
- Check-in and no-show release: the booking rule `checkInMinutes` (`check_in_minutes`) releases a
|
|
102
|
+
booking nobody claimed that long after its start (`reservation.no_show`), for bookings made once
|
|
103
|
+
the rule is set; `POST /v1/reservations/{reservation_ref}/check-in` (`slotlock_check_in_reservation`,
|
|
104
|
+
`checkInReservation`) claims one from an hour before its start (`reservation.checked_in`); a
|
|
105
|
+
move to another start clears it, and a walk-in's window runs from when it was booked. Maintenance results gain `noShows`
|
|
106
|
+
and `waitlistOffers` (`no_shows`, `waitlist_offers`). Run `slotlock migrate`.
|
|
107
|
+
- Calendar interchange: `GET`/`PUT /v1/resources/{resource_id}/availability/ical`
|
|
108
|
+
(`slotlock_export_resource_availability`, `slotlock_import_resource_availability`) write and read
|
|
109
|
+
a resource's weekly bookable hours as an iCalendar VAVAILABILITY (RFC 7953); an import must be in
|
|
110
|
+
the resource's zone or floating, and anything weekly hours cannot say answers 400
|
|
111
|
+
`invalid_availability`. `GET /v1/tenant/events/{event_id}/jscalendar`
|
|
112
|
+
(`slotlock_export_tenant_event`) answers an event as a JSCalendar Event (RFC 8984). The library
|
|
113
|
+
exports `emitVAvailability`, `parseVAvailability` and `toJSCalendarEvent`.
|
|
114
|
+
- `npm run bench` (`scripts/bench.mjs`, `bench/*.sql`): RFC 0002's pgbench workloads, Slotlock's
|
|
115
|
+
range constraint against one row per 15-minute slot, reproducible on any scratch database. A new
|
|
116
|
+
Scaling page covers partitioning by resource (PostgreSQL 16, 17+), Citus, a home region per tenant,
|
|
117
|
+
read replicas, tracing and SLOs, backups and point-in-time recovery, and committing a solver's
|
|
118
|
+
plan through Slotlock.
|
|
119
|
+
- Shared request budgets: `SLOTLOCK_SHARED_LIMITS=on` makes every server of a deployment draw a
|
|
120
|
+
tenant's `tenant_*` and `app_*` budgets from one token bucket in PostgreSQL
|
|
121
|
+
(`slotlock.take_rate_token`, keyed by a digest), after its own buckets pass; a database that
|
|
122
|
+
cannot be reached leaves each server's own budgets in charge. `createSlotlockRequestLimits` takes
|
|
123
|
+
`shared`. Run `slotlock migrate`.
|
|
124
|
+
- Feed health: `GET /v1/resources/{resource_id}/coverage` (`slotlock_get_coverage`, `listCoverage`)
|
|
125
|
+
shows each source's window, last read, last error and whether it is stale;
|
|
126
|
+
`POST /v1/resources/{resource_id}/coverage/{source}/failure` (`slotlock_record_coverage_failure`,
|
|
127
|
+
`recordCoverageFailure`) records a failed read. Maintenance puts a source that has gone stale on the
|
|
128
|
+
feed once (`coverage.stale`, `reportStaleCoverage`) and counts `staleSources` (`stale_sources`).
|
|
129
|
+
Unread time stays unproven, never free. Run `slotlock migrate`.
|
|
130
|
+
- Time zone rule changes: each calendar event keeps the wall time it was booked for in its zone,
|
|
131
|
+
and maintenance reports a future one-off event whose wall time now falls at another instant
|
|
132
|
+
(`event.timezone_shifted` on the feed, once), or with `followZoneRules` moves it there as a new
|
|
133
|
+
revision unless that overlaps (`event.timezone_conflict`). `store.followZoneRules` runs it alone.
|
|
134
|
+
Maintenance results gain `zoneShifts` (`zone_shifts` in the API); `/healthz` gains `tzdata`. Run
|
|
135
|
+
`slotlock migrate`.
|
|
136
|
+
- History: every change to a booking, hold or event records who made it (the API key or agent:
|
|
137
|
+
`withTenant`'s new `actor`, which the agent and application backends set to the principal's
|
|
138
|
+
subject) and, for a change of an existing record, what it was before. Read it newest first with
|
|
139
|
+
`GET /v1/resources/{resource_id}/history`, `GET /v1/reservations/{reservation_ref}/history` and
|
|
140
|
+
`GET /v1/tenant/events/{event_id}/history`, or `listHistory`. A backend of your own implements
|
|
141
|
+
`getHistory`. The feed and history now keep changes 30 days. Run `slotlock migrate`.
|
|
142
|
+
- Pools: interchangeable resources sharing a class (a vehicle class, a room type).
|
|
143
|
+
`PUT /v1/resources/{resource_id}/pool` (`slotlock_set_resource_pool`, `setResourcePool`) puts a
|
|
144
|
+
resource in one; `GET /v1/pools` lists them with their capacity. `slotlock_book_pool` (REST
|
|
145
|
+
`POST /v1/pool-bookings`) books the first member free and allowed in one transaction, so a class
|
|
146
|
+
sells out exactly at its size however many race (`pool_full`), and replays to the member it
|
|
147
|
+
booked. `slotlock_list_resources` shows each resource's `pool` and takes `pool` to list one's
|
|
148
|
+
members. Resources in the application API have `pool`. A backend of your own implements
|
|
149
|
+
`setResourcePool` and `listPools`, and the optional `bookPool`. Run `slotlock migrate`.
|
|
150
|
+
- Bookings across resources: `slotlock_book_together` (REST `POST /v1/bundles`) books the same
|
|
151
|
+
time on 2-20 resources in one transaction, all or none, and its refusal names the `resource_id`
|
|
152
|
+
that refused; bundles lock in one order, so racing ones never deadlock. It is a write that
|
|
153
|
+
`SLOTLOCK_CONFIRM_WRITES` can guard. `slotlock_find_available_slots` takes `require_free` for
|
|
154
|
+
times when every resource, or any n, is free together, each slot naming them in `resource_ids`.
|
|
155
|
+
A backend of your own implements the optional `bookTogether`, else `bundles_unavailable`.
|
|
156
|
+
- `SlotlockAgentOperationError` takes `details` (at most five short fields, such as
|
|
157
|
+
`resource_id`), which every protocol carries beside `code`.
|
|
158
|
+
- An event feed and webhooks. Every committed change to a tenant's holds, bookings and calendar
|
|
159
|
+
events is recorded in the same transaction by trigger: ids, times and states, never titles or
|
|
160
|
+
attendees. `GET /v1/changes` (`slotlock_list_changes`, `listChanges`) lists them as CloudEvents
|
|
161
|
+
by a cursor that no late-committing transaction can be skipped behind; changes are kept 30 days.
|
|
162
|
+
`POST /v1/webhooks` (`slotlock_create_webhook` and list, delete, enable) sends them in order,
|
|
163
|
+
signed as Standard Webhooks, at least once per `webhook-id`, retried with backoff for a day and
|
|
164
|
+
then disabled (`webhook.disabled` on the feed). `slotlock serve` delivers (`SLOTLOCK_WEBHOOKS`),
|
|
165
|
+
never to a private network address. Maintenance prunes the feed. A backend of your own
|
|
166
|
+
implements the five new `SlotlockApplicationBackend` methods. Run `slotlock migrate`.
|
|
167
|
+
- Booking rules per resource (`SlotlockBookingRules`): minimum notice, how far ahead, the step
|
|
168
|
+
starts fall on in the resource's time zone, a turnaround kept free after each booking (as its
|
|
169
|
+
trailing buffer, which the exclusion constraint enforces), and whether bookings must lie inside
|
|
170
|
+
its hours. Holds, reservations, moves and agents' events are refused with the rule they break
|
|
171
|
+
(`booking_rule_violated`; to an agent `booking_rule_min_notice`, `_max_advance`, `_slot_step`,
|
|
172
|
+
`_outside_hours`), by the database's clock; searches offer only what the rules allow. Set them
|
|
173
|
+
with `slotlock rules set`, `PUT /v1/resources/{resource_id}/booking-rules`
|
|
174
|
+
(`slotlock_set_resource_booking_rules`) or `setResourceBookingRules`. `putCalendarEvent` checks
|
|
175
|
+
them with `enforceBookingRules`; an imported event is not checked. `createSlotlockStore` takes
|
|
176
|
+
`defaultAvailabilityRules` for resources without hours of their own (`slotlock serve` passes
|
|
177
|
+
`SLOTLOCK_AVAILABILITY`). Run `slotlock migrate`.
|
|
178
|
+
- Agent holds: `slotlock_hold_slots` (REST `POST /v1/slot-holds`) holds up to five candidate slots
|
|
179
|
+
for 30-900 seconds as one group while a person chooses, listing the taken ones by index;
|
|
180
|
+
`slotlock_create_event`'s `hold_id` books one, releasing the others in the same transaction; and
|
|
181
|
+
`slotlock_release_slots` (`DELETE /v1/slot-holds/{hold_id}`) releases a group at once. A hold is
|
|
182
|
+
its agent's alone, and one agent may have 20 live (`SLOTLOCK_MAX_HOLDS_PER_HOLDER`). In the
|
|
183
|
+
library, `acquireHold` takes `holderRef` and `groupId`, `releaseHold` a `holderRef`, and
|
|
184
|
+
`putCalendarEvent` `fromHold` (refused with `hold_not_found` or `hold_mismatch`). A backend of
|
|
185
|
+
your own implements the optional `holdSlots` and `releaseSlots`; without them the tools answer
|
|
186
|
+
`holds_unavailable`. Run `slotlock migrate`. In an MCP Apps host the calendar view renders the
|
|
187
|
+
held slots, with when they expire, and its message names the hold, so the person picks from time
|
|
188
|
+
nobody can take while they decide.
|
|
189
|
+
- `slotlock_find_available_slots` (REST `GET /v1/available-slots`) lists up to 50 certain slots of
|
|
190
|
+
a duration, earliest first across up to 20 resources, each found the way
|
|
191
|
+
`slotlock_find_next_available` finds one: free of busy time, inside the resource's hours, and
|
|
192
|
+
only when every resource's coverage is certain.
|
|
193
|
+
- The MCP Apps calendar view lets a person pick one of those slots: a strip of days, each day's times
|
|
194
|
+
by part of the day, and *Book this time*. It sends the agent a chat message naming the slot
|
|
195
|
+
(`ui/message`), so the agent books it through the normal, confirmable path, and shows the text to
|
|
196
|
+
send when the host refuses.
|
|
197
|
+
- The view follows the host: its theme and its standard style variables, which it now applies (it
|
|
198
|
+
only read them before, so it looked the same, and light, in every host). It shows times in the
|
|
199
|
+
person's locale, clock and time zone, and reports its size to the host.
|
|
200
|
+
|
|
201
|
+
### Changed
|
|
202
|
+
|
|
203
|
+
- REST refusals are RFC 9457 problem details (`application/problem+json`): `type` (a tag URI, one per
|
|
204
|
+
code), `title`, `status` and `code`, with `error.code` kept so existing clients read them as
|
|
205
|
+
before, and a `Link: rel="describedby"` header to the error docs. An `overlap` names the
|
|
206
|
+
`resource_id`, `start` and `end` it refused.
|
|
207
|
+
- The same idempotency key with another request answers REST `422 idempotency_conflict` (was 409),
|
|
208
|
+
as draft-ietf-httpapi-idempotency-key-header-08 says. MCP and A2A answer the same code.
|
|
209
|
+
- Every REST write that keeps an `idempotency_key` (the agent tools' as well as the application's)
|
|
210
|
+
takes it as the `Idempotency-Key` header, as a Structured Field String (`"…"`) or bare; a write
|
|
211
|
+
that requires one and gets neither answers `400 idempotency_key_required`. Keys are kept 30 days
|
|
212
|
+
for event writes and while the hold could be live for holds; the docs publish the policy.
|
|
213
|
+
|
|
214
|
+
- The application API's resource has `booking_rules` (`null` when it has none) and `pool` (`null`
|
|
215
|
+
when it is in none).
|
|
216
|
+
- `SlotlockApplicationBackend` has twenty more methods, one per new application operation:
|
|
217
|
+
`setResourceBookingRules`, `setResourcePool`, `listPools`, `getResourceHistory`,
|
|
218
|
+
`getReservationHistory`, `getTenantEventHistory`, `getCoverage`, `recordCoverageFailure`,
|
|
219
|
+
`listChanges`, `createWebhook`, `listWebhooks`, `deleteWebhook`, `enableWebhook`,
|
|
220
|
+
`exportResourceAvailability`, `importResourceAvailability`, `exportTenantEvent`,
|
|
221
|
+
`checkInReservation`, `joinWaitlist`, `listWaitlist` and `leaveWaitlist`. A backend of your own
|
|
222
|
+
implements them, or wraps `createSlotlockStoreApplicationBackend`, which has them all. The agent
|
|
223
|
+
backend's new methods (`holdSlots`, `releaseSlots`, `bookTogether`, `bookPool`) are optional: on
|
|
224
|
+
a backend without one, its tool answers `409` with `holds_unavailable`, `bundles_unavailable` or
|
|
225
|
+
`pools_unavailable`.
|
|
226
|
+
- Maintenance results (`SlotlockMaintenanceResult`, and `POST /v1/maintenance`) have more counts:
|
|
227
|
+
`zoneShifts`, `staleSources`, `noShows` and `waitlistOffers` (`zone_shifts`, `stale_sources`,
|
|
228
|
+
`no_shows`, `waitlist_offers`).
|
|
229
|
+
- The three write tools carry `_meta.ui.visibility: ["model"]`: MCP Apps hosts no longer let a view
|
|
230
|
+
call them (the default is model and app).
|
|
231
|
+
|
|
232
|
+
### Fixed
|
|
233
|
+
|
|
234
|
+
- Retrying a recurring event's create (or any put) with the same idempotency key and arguments
|
|
235
|
+
after UTC midnight replays the first result again. The command's hash included the window its
|
|
236
|
+
occurrences are stored over, which for a series is the rolling horizon starting at the current
|
|
237
|
+
UTC midnight, so the next day's retry was refused as `idempotency_conflict`. A command stored by
|
|
238
|
+
0.3.0 still replays: a one-off event's always, a series' on the day it was stored.
|
|
239
|
+
- A hold's expiry is judged at each statement (`statement_timestamp()`), not at the start of the
|
|
240
|
+
transaction (`now()`). A store made from a caller's transaction runs in savepoints of it, so a hold
|
|
241
|
+
that expired while that transaction ran still looked live there and could be confirmed after its
|
|
242
|
+
expiry. Its time to live also starts when it is asked for, not when the caller's transaction did.
|
|
243
|
+
- An accepted write confirmation runs its write once. `slotlock serve` records each acceptance's
|
|
244
|
+
nonce in Postgres until it expires, on every server sharing the database, and refuses the same
|
|
245
|
+
acceptance sent again (`confirmation_invalid`); before, it could be sent again until it expired
|
|
246
|
+
(the write's idempotency key or revision then decided). `SlotlockAgentConfirmationOptions` takes
|
|
247
|
+
`consumeAcceptance` for this, and `createSlotlockAcceptanceRecords(sql)` implements it. Run
|
|
248
|
+
`slotlock migrate`: `serve` refuses to start with `SLOTLOCK_CONFIRM_WRITES` on a schema that cannot
|
|
249
|
+
record them.
|
|
250
|
+
- The production checklist's erasure item names `slotlock tenant erase` and `eraseTenant`, which
|
|
251
|
+
delete everything a tenant owns in one transaction, instead of a table-by-table delete.
|
|
252
|
+
|
|
253
|
+
- The OpenAPI document declares the `404` and `409` the agent-tool routes answer: `404` on
|
|
254
|
+
free/busy, next available and reading or listing events, and `404` and `409` (`overlap`, a stale
|
|
255
|
+
revision) on creating, changing and cancelling one. Clients generated from it now type an overlap
|
|
256
|
+
instead of falling to `default`.
|
|
257
|
+
|
|
6
258
|
## 0.3.0 - 2026-10-27
|
|
7
259
|
|
|
8
260
|
0.3.0 fixes the double bookings 0.2.0 could accept around daylight-saving changes and makes
|