@rubytech/create-maxy-code 0.1.474 → 0.1.476
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/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +3 -2
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +3 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -9
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +13 -3
- package/payload/platform/plugins/docs/references/calendar-booking.md +2 -2
- package/payload/platform/plugins/docs/references/google-guide.md +15 -6
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/google/PLUGIN.md +60 -12
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js +229 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +9 -9
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js +17 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js +146 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js +19 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js +41 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts +44 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js +112 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts +17 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js +143 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +9 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/index.js +73 -5
- package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js +37 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +20 -4
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +19 -9
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/google/references/auth.md +93 -14
- package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +158 -23
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js +200 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +309 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +219 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +94 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +106 -22
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts +109 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +213 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +80 -8
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +202 -8
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +6 -4
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +334 -142
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +21 -3
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js +6 -2
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +43 -5
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +9 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +78 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
- package/payload/server/{chunk-Y6V45R6M.js → chunk-LBCMFD4O.js} +173 -29
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-B44KbEuC.js → AdminLoginScreens-DximwPlS.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BehbH-Oa.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-DM-eHqOS.js} +1 -1
- package/payload/server/public/assets/activity-hDwUeQd6.js +1 -0
- package/payload/server/public/assets/{admin-B4LpTCur.js → admin-CAkDnGdk.js} +1 -1
- package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-DXl8hOoi.js} +1 -1
- package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-DP6hn4-6.js} +1 -1
- package/payload/server/public/assets/chat-DhOfVGYM.js +1 -0
- package/payload/server/public/assets/chevron-left-DYNBoLii.js +1 -0
- package/payload/server/public/assets/data-bq8fTo_s.js +1 -0
- package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-CHcYoEJ5.js} +1 -1
- package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-CZykslZM.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-31AZEcMS.js} +1 -1
- package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-BGGpdsO_.js} +1 -1
- package/payload/server/public/assets/{page-CkhFj8GQ.js → page-BloC6ygA.js} +1 -1
- package/payload/server/public/assets/{page-DxnWgPtd.js → page-DOeiiqbR.js} +1 -1
- package/payload/server/public/assets/{public-D5TUfQ3I.js → public-CGgO6IZv.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-BlAi6iFg.js} +1 -1
- package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-C1giFRRL.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-CiUUQdq7.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-CVqKuLoa.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-ksyUe_g1.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-BPrBkdTd.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-K4kYeN6i.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-CTXP8oTW.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +547 -423
- package/payload/server/public/assets/activity-CdipMTDp.js +0 -1
- package/payload/server/public/assets/chat-5tJdo9i_.js +0 -1
- package/payload/server/public/assets/chevron-left-BJllrPBj.js +0 -1
- package/payload/server/public/assets/data-Du79EgR-.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-CBlKgwms.js → useSubAccountSwitcher-DzgzTRRn.js} +0 -0
|
@@ -133,17 +133,40 @@ Either reading `false` on a brand that ships both is the mis-stamp signature.
|
|
|
133
133
|
|
|
134
134
|
### Connecting an account, once the brand ships a client
|
|
135
135
|
|
|
136
|
-
`google-account-register` returns a consent URL. The user opens it on any
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
136
|
+
`google-account-register` returns a consent URL. The user opens it on any device
|
|
137
|
+
and approves. What happens next depends on **where** they approved, and both
|
|
138
|
+
paths are supported.
|
|
139
|
+
|
|
140
|
+
**On the install's own machine.** Register binds a single-shot listener on
|
|
141
|
+
`127.0.0.1` at an OS-assigned port and puts that port in the consent URL, so
|
|
142
|
+
Google's redirect reaches it. The listener verifies `state`, completes the
|
|
143
|
+
exchange in-process, and renders a page naming the connected account. Nothing is
|
|
144
|
+
pasted. The page asks the user to return to the chat and say so, because the
|
|
145
|
+
agent has no callback and learns the outcome from
|
|
146
|
+
`google-account-register-poll`.
|
|
147
|
+
|
|
148
|
+
**On another device.** A phone cannot route to the install's loopback, so the
|
|
149
|
+
redirect fails to load exactly as before. The user copies the whole address-bar
|
|
150
|
+
URL and passes it to `google-account-register-submit`, which exchanges it
|
|
151
|
+
in-process. This path is not a degraded mode; it is the designed route for the
|
|
152
|
+
Pi deployment, where the admin always consents from a phone.
|
|
153
|
+
|
|
154
|
+
That failed-page step remains a real UX cost on the cross-device path, priced
|
|
155
|
+
and accepted: some mobile browsers hide the URL of a failed navigation. It is
|
|
156
|
+
the price of a client type whose secret can legitimately ship to customer
|
|
157
|
+
devices, and it cannot be removed — the phone has no route to the install.
|
|
158
|
+
|
|
159
|
+
**The port is no longer fixed**, and the consent URL is no longer deterministic.
|
|
160
|
+
RFC 8252 §7.3 is what permits this: Google ignores the port when matching a
|
|
161
|
+
loopback redirect, so binding `:0` is free. It also retires a latent collision,
|
|
162
|
+
since a fixed port is shared by every brand and two co-resident brands would
|
|
163
|
+
contend for it. `http://127.0.0.1:8571` survives as the fallback when the bind
|
|
164
|
+
fails, which keeps an install that cannot bind working exactly as it did before.
|
|
165
|
+
|
|
166
|
+
Because the port varies per registration, the pending entry stores the redirect
|
|
167
|
+
it issued and the exchange sends that stored value back. Google compares the two
|
|
168
|
+
byte for byte, so a registration whose listener bound a port cannot be completed
|
|
169
|
+
against the constant.
|
|
147
170
|
|
|
148
171
|
---
|
|
149
172
|
|
|
@@ -318,7 +341,60 @@ Two divergences from Outlook are load-bearing:
|
|
|
318
341
|
credentials.
|
|
319
342
|
|
|
320
343
|
Refresh runs **in-process** against Google, within 5 minutes of access-token
|
|
321
|
-
expiry.
|
|
344
|
+
expiry. No `platform/ui` code mints, exchanges or refreshes a credential; its
|
|
345
|
+
only Google surface is the standing pending audit below, which reads the age of
|
|
346
|
+
`pending-oauth.enc` and never decrypts it.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Standing audits
|
|
351
|
+
|
|
352
|
+
Both states these cover are unobservable by any event: an abandoned registration
|
|
353
|
+
fires nothing, and a refresh token expiring is a date passing, not an action.
|
|
354
|
+
`runAccountRegisterPoll` emits `op=pending-stranded` only when somebody polls —
|
|
355
|
+
and the case worth catching is the one nobody polls.
|
|
356
|
+
|
|
357
|
+
- **Stranded pending.** In-process in `platform/ui`
|
|
358
|
+
(`server/google-pending-audit.ts`), every 5 minutes, over every account uuid
|
|
359
|
+
under `data/accounts`. Any `pending-oauth.enc` at or past
|
|
360
|
+
`PENDING_LIFETIME_SEC` (600s) emits
|
|
361
|
+
`[google-oauth] op=pending-stranded account=<id> ageSec=<N>`. It runs in-process
|
|
362
|
+
rather than in the spawned script precisely so the signal does not depend on a
|
|
363
|
+
spawn being healthy — the same rationale as Outlook's `devicecode-stranded`
|
|
364
|
+
audit. A pending file that vanishes mid-scan (a concurrent submit clearing it)
|
|
365
|
+
is skipped per-account; it must never abort the cycle, because that would
|
|
366
|
+
suppress the heartbeat and read as the audit being dead.
|
|
367
|
+
- **Refresh TTL.** Spawned `mcp/dist/scripts/account-audit.js`, hourly, one
|
|
368
|
+
`[google-account-audit] account=<id> email=<addr> refreshTtlDays=<D> status=<…>`
|
|
369
|
+
per connected Google account. It is a spawned script, not in-process, because
|
|
370
|
+
classifying a token means decrypting the blob with the per-account AES key and
|
|
371
|
+
`TokenStore` is the only holder of that contract. An unreadable blob is skipped,
|
|
372
|
+
never fatal — one corrupt store must not blind the audit to every other account.
|
|
373
|
+
|
|
374
|
+
**Heartbeats, and why they are not optional.** Each audit emits a heartbeat every
|
|
375
|
+
cycle, including empty ones. Without it, an audit that has stopped running looks
|
|
376
|
+
exactly like an audit reporting all-clear, and the operator learns nothing until
|
|
377
|
+
an account has already fallen off. The absence of a heartbeat across more than
|
|
378
|
+
one interval is the failure signal. A pending cycle that throws logs
|
|
379
|
+
`op=pending-audit-error` — a deliberately distinct token, so a broken cycle is
|
|
380
|
+
never miscounted as a healthy heartbeat by a reader grepping `op=pending-audit`.
|
|
381
|
+
|
|
382
|
+
**What `refreshTtlDays` is, exactly.** Days remaining on Google's 180-day
|
|
383
|
+
inactivity revocation clock, computed as `180 - daysSince(lastRefresh)` and
|
|
384
|
+
clamped so a clock that ran ahead cannot report more than 180. `TokenBlob` stores
|
|
385
|
+
no refresh-token expiry because Google issues none, so this inactivity clock is
|
|
386
|
+
the only bounded quantity available locally. It does **not** predict the other
|
|
387
|
+
revocation causes — the user revoking access, a password change, the app being
|
|
388
|
+
unpublished — none of which are visible from disk. `refreshToken: null` is
|
|
389
|
+
reported `expired` with zero days however recently it was written, since such a
|
|
390
|
+
connection dies with its access token about an hour out.
|
|
391
|
+
|
|
392
|
+
Diagnostic path:
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
~/<brand>-code/platform/scripts/logs-read.sh --tail server 200 \
|
|
396
|
+
| grep -E '\[google-oauth\]|\[google-account-audit\]'
|
|
397
|
+
```
|
|
322
398
|
|
|
323
399
|
---
|
|
324
400
|
|
|
@@ -330,8 +406,11 @@ The earlier hosted-callback model built a hosted HTTPS callback at
|
|
|
330
406
|
Desktop client cannot register an HTTPS redirect, so all of it is deleted:
|
|
331
407
|
`platform/ui/server/routes/google-oauth.ts` and its test, the
|
|
332
408
|
`/api/google/oauth/callback` allowlist entry, the mount in `server/index.ts`,
|
|
333
|
-
and the `MAXY_UI_INTERNAL_PORT` dependency.
|
|
334
|
-
`platform/ui
|
|
409
|
+
and the `MAXY_UI_INTERNAL_PORT` dependency. No OAuth surface remains in
|
|
410
|
+
`platform/ui` — no route, no exchange, no credential — and a test asserts the
|
|
411
|
+
public host still refuses that path. The one `platform/ui` file that mentions
|
|
412
|
+
Google is `server/google-pending-audit.ts`, which reads the age of a pending file
|
|
413
|
+
and is not reachable over HTTP at all.
|
|
335
414
|
|
|
336
415
|
**Devices touched by the earlier plugin may still hold
|
|
337
416
|
`platform/config/google-house.env`, containing the retired Web client's live
|
|
@@ -182,13 +182,13 @@ Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDisp
|
|
|
182
182
|
|
|
183
183
|
## Reconciling public bookings (D1 → graph)
|
|
184
184
|
|
|
185
|
-
`scheduling/mcp/dist/scripts/reconcile-bookings.js` turns public bookings into calendar meetings. The booking site (deployed by the cloudflare `calendar-site` skill) writes each submission to a Cloudflare D1 `bookings` table at the edge, so capture survives a brief Pi outage. This binary pulls accepted
|
|
185
|
+
`scheduling/mcp/dist/scripts/reconcile-bookings.js` turns public bookings into calendar meetings. The booking site (deployed by the cloudflare `calendar-site` skill) writes each submission to a Cloudflare D1 `bookings` table at the edge, so capture survives a brief Pi outage. This binary pulls eligible rows (`status='accepted' AND kind='booking'`) from each account's D1, claims the ones with no matching `:Meeting`, writes one `:Meeting {source:'booking', uid:'booking-<id>'}` per row (idempotent — a re-run writes no duplicate), links the booker as a `:Person` via `[:ATTENDED]` (respecting the global email-uniqueness skip), notifies the admin (email; WhatsApp only when a thread already exists, never cold), then marks the row swept. A row is claimed on the absence of its meeting rather than on `swept`, so a meeting that is later deleted or lost is rewritten on the next run; `swept` records only that a write was attempted. `kind='block'` rows reserve a slot and are never reconciled.
|
|
186
186
|
|
|
187
187
|
It is **armed by the always-running `platform/ui` server on an interval**, because the Pi has no other periodic surface (the same gap `check-due-events` has). A PID lock at `<configDir>/reconcile-bookings.lock` prevents overlapping runs. It registers **no agent tool** — there is nothing for the agent to call — so the tool-surface registries are untouched.
|
|
188
188
|
|
|
189
189
|
Env: `PLATFORM_ROOT`, `NEO4J_URI` (and `NEO4J_PASSWORD` or `config/.neo4j-password`). Per account it reads `data/accounts/<id>/calendar-availability.json` (for `bookingDbName`). The Cloudflare credential is read house-only from `config/cloudflare-house.env`; that credential is a minter, so the calendar mints a `calendar-d1` (D1-only) token from it via `cf-token.sh` and shells `wrangler` for the D1 reads/writes on that scoped token, never the minter directly.
|
|
190
190
|
|
|
191
|
-
Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… pendingUnreconciled=… orphanMeetings=…` audit line per account per run. A `pendingUnreconciled` that persists across runs means
|
|
191
|
+
Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row.
|
|
192
192
|
|
|
193
193
|
## Ingesting calendar archives (.ics → graph)
|
|
194
194
|
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// account always resolves or creates its own Person for the booking
|
|
6
6
|
// email, never a foreign account's).
|
|
7
7
|
import { describe, it, expect, vi } from "vitest";
|
|
8
|
-
import {
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { reconcileOne, auditCounts, bookingUid, ensureConnectorSyncEvent, classifyPublishedDrift, selectUnreconciled, fetchBookingMeetingUids, ELIGIBLE_BOOKING_PREDICATE, ELIGIBLE_BOOKING_SELECT, BOOKING_SCHEMA_MIGRATION, isMissingColumnError, isDuplicateColumnError, } from "../booking-reconcile.js";
|
|
9
10
|
const booking = {
|
|
10
11
|
bookingId: "abc",
|
|
11
12
|
slotStart: "2026-06-22T09:00:00Z",
|
|
@@ -52,20 +53,82 @@ describe("reconcileOne", () => {
|
|
|
52
53
|
expect(r.created).toBe(false);
|
|
53
54
|
});
|
|
54
55
|
});
|
|
56
|
+
describe("eligibility predicate", () => {
|
|
57
|
+
// Asserting the constant equals its own literal would be a tautology. What
|
|
58
|
+
// matters is that it qualifies on BOTH axes: status alone counted block rows
|
|
59
|
+
// forever (1806), and kind alone would sweep cancelled rows.
|
|
60
|
+
it("qualifies on status and on kind", () => {
|
|
61
|
+
expect(ELIGIBLE_BOOKING_PREDICATE).toMatch(/status\s*=\s*'accepted'/);
|
|
62
|
+
expect(ELIGIBLE_BOOKING_PREDICATE).toMatch(/kind\s*=\s*'booking'/);
|
|
63
|
+
});
|
|
64
|
+
// The load-bearing property of 1806: there is ONE definition, and the read
|
|
65
|
+
// both call sites use is built from it rather than restating it.
|
|
66
|
+
it("builds the sweep/audit read from that predicate and asks for attempts", () => {
|
|
67
|
+
expect(ELIGIBLE_BOOKING_SELECT).toContain(ELIGIBLE_BOOKING_PREDICATE);
|
|
68
|
+
expect(ELIGIBLE_BOOKING_SELECT).toMatch(/\battempts\b/);
|
|
69
|
+
expect(ELIGIBLE_BOOKING_SELECT).toMatch(/ORDER BY id/);
|
|
70
|
+
});
|
|
71
|
+
// 1807 — the sweep must not consult swept. A predicate carrying it would make
|
|
72
|
+
// a booking whose meeting went missing unreachable again.
|
|
73
|
+
it("does not filter on swept", () => {
|
|
74
|
+
expect(ELIGIBLE_BOOKING_SELECT).not.toMatch(/WHERE[\s\S]*\bswept\b/);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe("selectUnreconciled", () => {
|
|
78
|
+
const rows = [{ bookingId: "reconciled" }, { bookingId: "lost" }];
|
|
79
|
+
// Task 1807 — the Rob Mead case in fixture form. This row is swept=1 in D1;
|
|
80
|
+
// the sweep must claim it anyway, because its meeting is absent. The old
|
|
81
|
+
// swept=0 predicate returns the empty set here.
|
|
82
|
+
it("claims an eligible row whose meeting is absent", () => {
|
|
83
|
+
const claimed = selectUnreconciled(rows, new Set([bookingUid("reconciled")]));
|
|
84
|
+
expect(claimed.map((r) => r.bookingId)).toEqual(["lost"]);
|
|
85
|
+
});
|
|
86
|
+
// The mutation guard the task requires. A predicate that returned every
|
|
87
|
+
// accepted row would pass the test above and fail this one.
|
|
88
|
+
it("does not claim a row whose meeting is present", () => {
|
|
89
|
+
const claimed = selectUnreconciled(rows, new Set([bookingUid("reconciled"), bookingUid("lost")]));
|
|
90
|
+
expect(claimed).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
it("claims everything when the graph holds no booking meetings", () => {
|
|
93
|
+
expect(selectUnreconciled(rows, new Set()).map((r) => r.bookingId)).toEqual(["reconciled", "lost"]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
55
96
|
describe("auditCounts", () => {
|
|
56
|
-
it("counts pending (D1 ahead) and orphans (graph ahead)",
|
|
57
|
-
const
|
|
58
|
-
|
|
97
|
+
it("counts pending (D1 ahead) and orphans (graph ahead)", () => {
|
|
98
|
+
const counts = auditCounts(["a", "c"], ["booking-a", "booking-b"]);
|
|
99
|
+
// 'c' eligible in D1 but no meeting -> pending 1; 'booking-b' meeting with no D1 row -> orphan 1.
|
|
100
|
+
expect(counts).toEqual({ eligibleExamined: 2, pendingUnreconciled: 1, orphanMeetings: 1 });
|
|
101
|
+
});
|
|
102
|
+
it("is clean when D1 and graph agree", () => {
|
|
103
|
+
expect(auditCounts(["a", "b"], ["booking-a", "booking-b"])).toEqual({
|
|
104
|
+
eligibleExamined: 2,
|
|
105
|
+
pendingUnreconciled: 0,
|
|
106
|
+
orphanMeetings: 0,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// Task 1806 observability — an audit that returns the same value for "clean"
|
|
110
|
+
// and "no input" is not an audit. eligibleExamined is what separates them.
|
|
111
|
+
it("distinguishes nothing-to-do from verified-clean", () => {
|
|
112
|
+
const empty = auditCounts([], []);
|
|
113
|
+
const clean = auditCounts(["a"], ["booking-a"]);
|
|
114
|
+
expect(empty.pendingUnreconciled).toBe(clean.pendingUnreconciled);
|
|
115
|
+
expect(empty.eligibleExamined).toBe(0);
|
|
116
|
+
expect(clean.eligibleExamined).toBe(1);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("fetchBookingMeetingUids", () => {
|
|
120
|
+
it("reads this account's booking meeting uids", async () => {
|
|
121
|
+
const run = vi.fn(async (_cypher, _params) => ({
|
|
122
|
+
records: [{ get: () => ["booking-a"] }],
|
|
59
123
|
}));
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
expect(
|
|
63
|
-
expect(
|
|
124
|
+
const uids = await fetchBookingMeetingUids({ run }, "acct-1");
|
|
125
|
+
expect(uids).toEqual(["booking-a"]);
|
|
126
|
+
expect(run.mock.calls[0][0]).toMatch(/source: 'booking'/);
|
|
127
|
+
expect(run.mock.calls[0][1]).toEqual({ accountId: "acct-1" });
|
|
64
128
|
});
|
|
65
|
-
it("
|
|
66
|
-
const run = vi.fn(async () => ({ records: [
|
|
67
|
-
|
|
68
|
-
expect(counts).toEqual({ pendingUnreconciled: 0, orphanMeetings: 0 });
|
|
129
|
+
it("returns an empty list when the account has no booking meetings", async () => {
|
|
130
|
+
const run = vi.fn(async () => ({ records: [] }));
|
|
131
|
+
expect(await fetchBookingMeetingUids({ run }, "acct-1")).toEqual([]);
|
|
69
132
|
});
|
|
70
133
|
});
|
|
71
134
|
describe("ensureConnectorSyncEvent", () => {
|
|
@@ -84,10 +147,11 @@ describe("ensureConnectorSyncEvent", () => {
|
|
|
84
147
|
};
|
|
85
148
|
return { session: session, calls };
|
|
86
149
|
}
|
|
150
|
+
const WA = { channel: "whatsapp", destination: "+44 7504 472444" };
|
|
87
151
|
it("creates the recurring sync event when none exists", async () => {
|
|
88
152
|
const { session, calls } = sessionReturning(0);
|
|
89
|
-
const res = await ensureConnectorSyncEvent(session, "acct-1",
|
|
90
|
-
expect(res).toBe("
|
|
153
|
+
const res = await ensureConnectorSyncEvent(session, "acct-1", WA);
|
|
154
|
+
expect(res).toBe("provisioned");
|
|
91
155
|
const merge = calls.find((c) => /CREATE \(e:Event/.test(c.cypher));
|
|
92
156
|
expect(merge).toBeTruthy();
|
|
93
157
|
expect(merge.params.accountId).toBe("acct-1");
|
|
@@ -96,28 +160,99 @@ describe("ensureConnectorSyncEvent", () => {
|
|
|
96
160
|
expect(merge.params.recurrence).toBe("*/30 * * * *");
|
|
97
161
|
expect(String(merge.params.agentPrompt)).toMatch(/connector/i);
|
|
98
162
|
});
|
|
163
|
+
// Task 1809 — the channel is whatever the caller resolved, not a hardcoded
|
|
164
|
+
// 'whatsapp'. An account reachable only on Telegram provisions on Telegram.
|
|
165
|
+
it("writes the resolved channel verbatim rather than assuming WhatsApp", async () => {
|
|
166
|
+
const { session, calls } = sessionReturning(0);
|
|
167
|
+
const res = await ensureConnectorSyncEvent(session, "acct-1", {
|
|
168
|
+
channel: "telegram",
|
|
169
|
+
destination: "123456789",
|
|
170
|
+
});
|
|
171
|
+
expect(res).toBe("provisioned");
|
|
172
|
+
const merge = calls.find((c) => /CREATE \(e:Event/.test(c.cypher));
|
|
173
|
+
expect(merge.params.agentChannel).toBe("telegram");
|
|
174
|
+
expect(merge.params.agentDestination).toBe("123456789");
|
|
175
|
+
});
|
|
99
176
|
it("is a no-op when the sync event already exists", async () => {
|
|
100
177
|
const { session, calls } = sessionReturning(1);
|
|
101
|
-
const res = await ensureConnectorSyncEvent(session, "acct-1",
|
|
102
|
-
expect(res).toBe("
|
|
178
|
+
const res = await ensureConnectorSyncEvent(session, "acct-1", WA);
|
|
179
|
+
expect(res).toBe("already-provisioned");
|
|
103
180
|
expect(calls.some((c) => /CREATE \(e:Event/.test(c.cypher))).toBe(false);
|
|
104
181
|
});
|
|
105
182
|
});
|
|
106
|
-
describe("
|
|
183
|
+
describe("booking schema migration", () => {
|
|
184
|
+
it("adds both columns and backfills known machine-written block rows", () => {
|
|
185
|
+
expect(BOOKING_SCHEMA_MIGRATION).toEqual([
|
|
186
|
+
"ALTER TABLE bookings ADD COLUMN kind TEXT NOT NULL DEFAULT 'booking'",
|
|
187
|
+
"ALTER TABLE bookings ADD COLUMN attempts INTEGER NOT NULL DEFAULT 0",
|
|
188
|
+
"UPDATE bookings SET kind='block' WHERE note='Auto-block: Google Calendar'",
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
// The backfill runs once, at column creation. Running control flow keys on
|
|
192
|
+
// `kind`, never on the note — the string appears here and nowhere else. This
|
|
193
|
+
// is the boundary Task 1806 drew when it rejected note-matching as a
|
|
194
|
+
// predicate, pinned mechanically rather than promised in a comment.
|
|
195
|
+
it("reads the note in the migration only", async () => {
|
|
196
|
+
const src = await readFile(new URL("../booking-reconcile.ts", import.meta.url), "utf8");
|
|
197
|
+
expect(src.split("Auto-block: Google Calendar").length - 1).toBe(1);
|
|
198
|
+
});
|
|
199
|
+
it("recognises a missing-column failure so the migration can be triggered", () => {
|
|
200
|
+
expect(isMissingColumnError("D1_ERROR: no such column: kind at offset 62")).toBe(true);
|
|
201
|
+
expect(isMissingColumnError("no such table: bookings")).toBe(false);
|
|
202
|
+
expect(isMissingColumnError("Authentication error [code: 10000]")).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
it("recognises a duplicate-column failure so a partial migration is a no-op", () => {
|
|
205
|
+
expect(isDuplicateColumnError("duplicate column name: kind")).toBe(true);
|
|
206
|
+
expect(isDuplicateColumnError("no such column: kind")).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
describe("classifyPublishedDrift", () => {
|
|
107
210
|
const busy = [{ start: "2026-06-22T10:00:00Z", end: "2026-06-22T11:00:00Z" }];
|
|
108
|
-
it("
|
|
211
|
+
it("reports verified with the conflict when a published slot overlaps busy", () => {
|
|
109
212
|
const published = [
|
|
110
213
|
{ start: "2026-06-22T10:30:00Z", end: "2026-06-22T11:30:00Z" }, // overlaps
|
|
111
214
|
{ start: "2026-06-22T12:00:00Z", end: "2026-06-22T13:00:00Z" }, // clear
|
|
112
215
|
];
|
|
113
|
-
expect(
|
|
216
|
+
expect(classifyPublishedDrift(published, busy)).toEqual({
|
|
217
|
+
outcome: "verified",
|
|
218
|
+
published: 2,
|
|
219
|
+
busy: 1,
|
|
220
|
+
conflicts: 1,
|
|
221
|
+
});
|
|
114
222
|
});
|
|
115
|
-
it("
|
|
223
|
+
it("reports verified with 0 conflicts when both sets are non-empty and disjoint", () => {
|
|
116
224
|
const published = [{ start: "2026-06-22T09:00:00Z", end: "2026-06-22T10:00:00Z" }]; // touches edge, no overlap
|
|
117
|
-
expect(
|
|
225
|
+
expect(classifyPublishedDrift(published, busy)).toEqual({
|
|
226
|
+
outcome: "verified",
|
|
227
|
+
published: 1,
|
|
228
|
+
busy: 1,
|
|
229
|
+
conflicts: 0,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
it("reports no-busy-data when there is nothing to compare published slots against", () => {
|
|
233
|
+
const published = [{ start: "2026-06-22T10:30:00Z", end: "2026-06-22T11:30:00Z" }];
|
|
234
|
+
expect(classifyPublishedDrift(published, [])).toEqual({
|
|
235
|
+
outcome: "no-busy-data",
|
|
236
|
+
published: 1,
|
|
237
|
+
busy: 0,
|
|
238
|
+
conflicts: 0,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
it("reports no-published-slots when nothing has been published", () => {
|
|
242
|
+
expect(classifyPublishedDrift([], busy)).toEqual({
|
|
243
|
+
outcome: "no-published-slots",
|
|
244
|
+
published: 0,
|
|
245
|
+
busy: 1,
|
|
246
|
+
conflicts: 0,
|
|
247
|
+
});
|
|
118
248
|
});
|
|
119
|
-
it("
|
|
120
|
-
expect(
|
|
249
|
+
it("reports no-published-slots when both sets are empty", () => {
|
|
250
|
+
expect(classifyPublishedDrift([], [])).toEqual({
|
|
251
|
+
outcome: "no-published-slots",
|
|
252
|
+
published: 0,
|
|
253
|
+
busy: 0,
|
|
254
|
+
conflicts: 0,
|
|
255
|
+
});
|
|
121
256
|
});
|
|
122
257
|
});
|
|
123
258
|
//# sourceMappingURL=booking-reconcile.test.js.map
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-reconcile.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-reconcile.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"booking-reconcile.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-reconcile.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-schema-migration.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-schema-migration.test.ts"],"names":[],"mappings":""}
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Task 1806/1807 — the bookings schema migration, executed against a real
|
|
2
|
+
// SQLite database built from the calendar-site template's own schema.sql, the
|
|
3
|
+
// way `wrangler d1 execute` applies it to D1.
|
|
4
|
+
//
|
|
5
|
+
// A hand-written fake table would model the shape we are trying to prove we
|
|
6
|
+
// produce, which is the failure the sibling cloudflare suite records: 1689
|
|
7
|
+
// shipped a UNIQUE-violating upload because its fake accepted what the real
|
|
8
|
+
// schema rejected. The same argument applies here with more force — the whole
|
|
9
|
+
// defect class this task closes is "the code's idea of the population and the
|
|
10
|
+
// database's idea of it disagree".
|
|
11
|
+
//
|
|
12
|
+
// This also pins the cross-plugin contract: the template schema (cloudflare
|
|
13
|
+
// plugin) and the eligibility predicate (scheduling plugin) must agree on the
|
|
14
|
+
// column names, and nothing else in the tree checks that.
|
|
15
|
+
import { describe, it, expect } from "vitest";
|
|
16
|
+
import { execFile } from "node:child_process";
|
|
17
|
+
import { promisify } from "node:util";
|
|
18
|
+
import { mkdtemp } from "node:fs/promises";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { resolve, dirname } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { BOOKING_SCHEMA_MIGRATION, ELIGIBLE_BOOKING_SELECT, isMissingColumnError, isDuplicateColumnError, } from "../booking-reconcile.js";
|
|
23
|
+
import { createD1Client } from "../d1-command.js";
|
|
24
|
+
const run = promisify(execFile);
|
|
25
|
+
const TEMPLATE_SCHEMA = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../../cloudflare/skills/calendar-site/template/schema.sql");
|
|
26
|
+
/** A real SQLite database. `legacy: true` builds the table as it exists on an
|
|
27
|
+
* already-provisioned install — the template minus the two columns this task
|
|
28
|
+
* adds — because those installs never re-run schema.sql. */
|
|
29
|
+
async function db(dir, opts) {
|
|
30
|
+
const root = await mkdtemp(resolve(tmpdir(), dir));
|
|
31
|
+
const path = resolve(root, "bookings.db");
|
|
32
|
+
if (opts.legacy) {
|
|
33
|
+
await run("sqlite3", [
|
|
34
|
+
path,
|
|
35
|
+
`CREATE TABLE bookings (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, bookingId TEXT UNIQUE NOT NULL,
|
|
37
|
+
slotStart TEXT NOT NULL, slotEnd TEXT NOT NULL, name TEXT NOT NULL,
|
|
38
|
+
email TEXT NOT NULL, note TEXT, status TEXT NOT NULL DEFAULT 'accepted',
|
|
39
|
+
createdAt TEXT NOT NULL, swept INTEGER NOT NULL DEFAULT 0);`,
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// `.read` because schema.sql opens with a `--` comment, which sqlite3 would
|
|
44
|
+
// otherwise parse as a CLI option.
|
|
45
|
+
await run("sqlite3", [path, `.read ${TEMPLATE_SCHEMA}`]);
|
|
46
|
+
}
|
|
47
|
+
const exec = async (sql) => {
|
|
48
|
+
try {
|
|
49
|
+
return (await run("sqlite3", [path, sql])).stdout.trim();
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
// sqlite3 reports SQL errors on stderr with a non-zero exit, the same
|
|
53
|
+
// shape wrangler surfaces and d1Query now propagates.
|
|
54
|
+
throw new Error(String(e.stderr ?? e.message).trim());
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return { exec };
|
|
58
|
+
}
|
|
59
|
+
/** The live maxy population, from the device read on 2026-07-19: 24 genuine
|
|
60
|
+
* bookings, 3 Google Calendar block rows, 1 admin block row. Plus Rob Mead —
|
|
61
|
+
* the sitedesk row whose meeting went missing, which is the case 1807 exists
|
|
62
|
+
* for and is byte-identical in D1 to a block row. */
|
|
63
|
+
function seed() {
|
|
64
|
+
const rows = [];
|
|
65
|
+
for (let i = 0; i < 24; i++) {
|
|
66
|
+
rows.push(`('josh-${i}','s','e','Josh Goff','josh.goff@gethumantra.com','','accepted','t',1)`);
|
|
67
|
+
}
|
|
68
|
+
for (let i = 0; i < 3; i++) {
|
|
69
|
+
rows.push(`('gcal-${i}','s','e','Google Calendar block','gcal-block@x.test','Auto-block: Google Calendar','accepted','t',1)`);
|
|
70
|
+
}
|
|
71
|
+
rows.push(`('admin-0','s','e','Joel Smalley','joel@x.test','Blocked by admin','accepted','t',1)`);
|
|
72
|
+
rows.push(`('rob-mead','s','e','Rob Mead','rob@x.test','','accepted','t',1)`);
|
|
73
|
+
return `INSERT INTO bookings (bookingId,slotStart,slotEnd,name,email,note,status,createdAt,swept) VALUES ${rows.join(",")};`;
|
|
74
|
+
}
|
|
75
|
+
describe("bookings schema migration against real SQLite", () => {
|
|
76
|
+
it("the template already satisfies the eligibility predicate, so a fresh install needs no migration", async () => {
|
|
77
|
+
const d = await db("bookings-fresh-", { legacy: false });
|
|
78
|
+
await d.exec(seed());
|
|
79
|
+
// Pins the cross-plugin contract: if the template drops or renames either
|
|
80
|
+
// column, or the predicate names a column the template lacks, this fails.
|
|
81
|
+
expect(await d.exec(`${ELIGIBLE_BOOKING_SELECT};`)).toContain("josh-0");
|
|
82
|
+
});
|
|
83
|
+
it("a legacy database fails the eligible read, and the failure is the one the migration answers", async () => {
|
|
84
|
+
const d = await db("bookings-legacy-", { legacy: true });
|
|
85
|
+
await d.exec(seed());
|
|
86
|
+
await expect(d.exec(`${ELIGIBLE_BOOKING_SELECT};`)).rejects.toThrow();
|
|
87
|
+
const err = await d.exec(`${ELIGIBLE_BOOKING_SELECT};`).catch((e) => e.message);
|
|
88
|
+
// The classifier must match the real sqlite/D1 wording, not a wording we
|
|
89
|
+
// imagined. Matching the wrong string means the migration never fires.
|
|
90
|
+
expect(isMissingColumnError(err)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it("migrating a legacy database classifies machine-written block rows and nothing else", async () => {
|
|
93
|
+
const d = await db("bookings-migrate-", { legacy: true });
|
|
94
|
+
await d.exec(seed());
|
|
95
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION)
|
|
96
|
+
await d.exec(`${sql};`);
|
|
97
|
+
expect(await d.exec("SELECT kind, count(*) FROM bookings GROUP BY kind ORDER BY kind;")).toBe("block|3\nbooking|26");
|
|
98
|
+
// The admin block row keeps kind='booking' on purpose. It already has a
|
|
99
|
+
// meeting, so dropping it from the eligible population would turn a false
|
|
100
|
+
// pendingUnreconciled into a false orphanMeetings.
|
|
101
|
+
expect(await d.exec("SELECT kind FROM bookings WHERE bookingId='admin-0';")).toBe("booking");
|
|
102
|
+
// Rob Mead is indistinguishable from a block row on every column except the
|
|
103
|
+
// note. He must survive as eligible, or 1807's whole case is lost.
|
|
104
|
+
expect(await d.exec("SELECT kind FROM bookings WHERE bookingId='rob-mead';")).toBe("booking");
|
|
105
|
+
expect(await d.exec("SELECT attempts FROM bookings WHERE bookingId='rob-mead';")).toBe("0");
|
|
106
|
+
});
|
|
107
|
+
it("no Google Calendar block row is left eligible after migrating", async () => {
|
|
108
|
+
const d = await db("bookings-noblock-", { legacy: true });
|
|
109
|
+
await d.exec(seed());
|
|
110
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION)
|
|
111
|
+
await d.exec(`${sql};`);
|
|
112
|
+
const eligible = await d.exec(`${ELIGIBLE_BOOKING_SELECT};`);
|
|
113
|
+
expect(eligible).not.toContain("gcal-");
|
|
114
|
+
expect(eligible).toContain("rob-mead");
|
|
115
|
+
});
|
|
116
|
+
it("re-running completes a partial migration rather than leaving block rows eligible", async () => {
|
|
117
|
+
// The Critical case. If the ALTERs land and the backfill fails, the columns
|
|
118
|
+
// now exist, so the eligible read succeeds and a missing-column trigger
|
|
119
|
+
// would never fire again — leaving every block row eligible for the sweep
|
|
120
|
+
// to reconcile into a meeting. The dispatcher therefore gates on a marker
|
|
121
|
+
// written only after all three statements succeed; this asserts the
|
|
122
|
+
// property that gate depends on, namely that a re-run finishes the job.
|
|
123
|
+
const d = await db("bookings-partial-", { legacy: true });
|
|
124
|
+
await d.exec(seed());
|
|
125
|
+
await d.exec(`${BOOKING_SCHEMA_MIGRATION[0]};`);
|
|
126
|
+
await d.exec(`${BOOKING_SCHEMA_MIGRATION[1]};`);
|
|
127
|
+
// Backfill did not run: every block row is still eligible.
|
|
128
|
+
expect(await d.exec("SELECT count(*) FROM bookings WHERE kind='block';")).toBe("0");
|
|
129
|
+
expect(await d.exec(`${ELIGIBLE_BOOKING_SELECT};`)).toContain("gcal-0");
|
|
130
|
+
// Re-run the whole migration, tolerating duplicate-column exactly as the
|
|
131
|
+
// dispatcher does.
|
|
132
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION) {
|
|
133
|
+
await d.exec(`${sql};`).catch((e) => {
|
|
134
|
+
if (!isDuplicateColumnError(e.message))
|
|
135
|
+
throw e;
|
|
136
|
+
return "";
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
expect(await d.exec("SELECT count(*) FROM bookings WHERE kind='block';")).toBe("3");
|
|
140
|
+
expect(await d.exec(`${ELIGIBLE_BOOKING_SELECT};`)).not.toContain("gcal-0");
|
|
141
|
+
});
|
|
142
|
+
it("a duplicate-column ALTER is recognised, so a re-run is a no-op not an abort", async () => {
|
|
143
|
+
const d = await db("bookings-rerun-", { legacy: true });
|
|
144
|
+
await d.exec(seed());
|
|
145
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION)
|
|
146
|
+
await d.exec(`${sql};`);
|
|
147
|
+
const err = await d.exec(`${BOOKING_SCHEMA_MIGRATION[0]};`).catch((e) => e.message);
|
|
148
|
+
expect(isDuplicateColumnError(err)).toBe(true);
|
|
149
|
+
expect(isMissingColumnError(err)).toBe(false);
|
|
150
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION) {
|
|
151
|
+
await d.exec(`${sql};`).catch((e) => {
|
|
152
|
+
if (!isDuplicateColumnError(e.message))
|
|
153
|
+
throw e;
|
|
154
|
+
return "";
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
expect(await d.exec("SELECT kind, count(*) FROM bookings GROUP BY kind ORDER BY kind;")).toBe("block|3\nbooking|26");
|
|
158
|
+
});
|
|
159
|
+
// The migration only fires if the transport surfaces D1's own error text. Task
|
|
160
|
+
// 1805 replaced the wrangler spawn with the HTTP API mid-sprint, so the string
|
|
161
|
+
// this classifier reads now comes from a different layer entirely. If a future
|
|
162
|
+
// transport summarises the failure instead of relaying it, the migration
|
|
163
|
+
// silently never runs and every block row becomes sweep-eligible — the exact
|
|
164
|
+
// regression this task exists to prevent, reintroduced from underneath.
|
|
165
|
+
it("the D1 HTTP transport relays a missing-column error the classifier can read", async () => {
|
|
166
|
+
const body = JSON.stringify({
|
|
167
|
+
result: [],
|
|
168
|
+
success: false,
|
|
169
|
+
errors: [{ code: 7500, message: "no such column: attempts at offset 62" }],
|
|
170
|
+
});
|
|
171
|
+
const fetchFn = async (url) => {
|
|
172
|
+
// The name lookup succeeds; the statement is what fails.
|
|
173
|
+
const ok = url.includes("/d1/database?");
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
status: 200,
|
|
177
|
+
chunks: (async function* () {
|
|
178
|
+
yield new TextEncoder().encode(ok ? JSON.stringify({ success: true, result: [{ uuid: "db-uuid", name: "maxy-bookings" }] }) : body);
|
|
179
|
+
})(),
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
const d1 = createD1Client({ accountId: "acct", token: "t", fetchFn });
|
|
183
|
+
// Resolving is itself a failure here: the statement must reject, or the
|
|
184
|
+
// dispatcher would treat a missing column as an empty booking set.
|
|
185
|
+
const err = await d1.query("maxy-bookings", ELIGIBLE_BOOKING_SELECT).then(() => "", (e) => e.message);
|
|
186
|
+
expect(err).not.toBe("");
|
|
187
|
+
expect(isMissingColumnError(err)).toBe(true);
|
|
188
|
+
expect(isDuplicateColumnError(err)).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
it("attempts survives the migration as a counter the sweep can increment", async () => {
|
|
191
|
+
const d = await db("bookings-attempts-", { legacy: true });
|
|
192
|
+
await d.exec(seed());
|
|
193
|
+
for (const sql of BOOKING_SCHEMA_MIGRATION)
|
|
194
|
+
await d.exec(`${sql};`);
|
|
195
|
+
await d.exec("UPDATE bookings SET attempts = attempts + 1 WHERE bookingId='rob-mead';");
|
|
196
|
+
await d.exec("UPDATE bookings SET attempts = attempts + 1 WHERE bookingId='rob-mead';");
|
|
197
|
+
expect(await d.exec("SELECT attempts FROM bookings WHERE bookingId='rob-mead';")).toBe("2");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=booking-schema-migration.test.js.map
|