@rubytech/create-maxy-code 0.1.475 → 0.1.477
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 +3 -3
- 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/scheduling/PLUGIN.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js +150 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js +248 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js.map +1 -0
- 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 +184 -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 +211 -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 +411 -141
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/scripts/cpu-triage.sh +313 -0
- package/payload/server/{chunk-Y6V45R6M.js → chunk-JECAP3Z2.js} +296 -63
- 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-mEKP4pDi.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BwSaZ88d.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-CN4uQ80w.js} +1 -1
- package/payload/server/public/assets/activity-CIQ8ozNZ.js +1 -0
- package/payload/server/public/assets/{admin-B4LpTCur.js → admin-BVhgfgs1.js} +1 -1
- package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-D-HMmHcM.js} +1 -1
- package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-tGWvFyFn.js} +1 -1
- package/payload/server/public/assets/chat-DkttVxAz.js +1 -0
- package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +1 -0
- package/payload/server/public/assets/data-P-mcmnNi.js +1 -0
- package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-qnBleOE6.js} +1 -1
- package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-zEw610xK.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-BlTjXT_Y.js} +1 -1
- package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-C7oIw2PG.js} +1 -1
- package/payload/server/public/assets/{page-DxnWgPtd.js → page-CTP7OFZa.js} +1 -1
- package/payload/server/public/assets/{page-CkhFj8GQ.js → page-DL6Zsdvk.js} +1 -1
- package/payload/server/public/assets/{public-D5TUfQ3I.js → public-r1A9dqR_.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-CzkkKx4-.js} +1 -1
- package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-fIJwYFWG.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-j669DgXj.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-BPgCgIef.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-BmLa2aes.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-DnnM7A8-.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-C_E8h07P.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-u2dwZfen.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 +358 -189
- package/payload/server/public/assets/activity-Mz1BhgB1.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-CFa6ZAT1.js} +0 -0
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-schema-migration.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-schema-migration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector-sync-provision.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/connector-sync-provision.test.ts"],"names":[],"mappings":""}
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// Task 1809 — the connector-sync provisioning gate.
|
|
2
|
+
//
|
|
3
|
+
// The defect these tests pin had two stacked causes. `readAdminPhone` read a
|
|
4
|
+
// top-level `adminPhones` key that exists on no account (the authoritative key
|
|
5
|
+
// is `whatsapp.adminPhones`), so the gate never opened anywhere; and the gate
|
|
6
|
+
// was on a reply destination rather than on the work's own input, so an account
|
|
7
|
+
// with a calendar and no WhatsApp number could never sync even once the field
|
|
8
|
+
// path was right.
|
|
9
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
10
|
+
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, existsSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { detectCalendarConnectors, resolveDispatchDestination, shouldEmitProvisionLine, provisionSignature, emitLabel, readProvisionState, writeProvisionState, } from "../connector-sync-provision.js";
|
|
14
|
+
const HOUSE = "11111111-1111-1111-1111-111111111111";
|
|
15
|
+
/** A temp accounts dir holding one account with the given account.json. */
|
|
16
|
+
function accountsDirWith(accountId, config) {
|
|
17
|
+
const dir = mkdtempSync(join(tmpdir(), "cal-sync-prov-"));
|
|
18
|
+
const accountDir = join(dir, accountId);
|
|
19
|
+
mkdirSync(accountDir, { recursive: true });
|
|
20
|
+
writeFileSync(join(accountDir, "account.json"), JSON.stringify(config));
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
function writeConnector(accountDir, rel, key) {
|
|
24
|
+
const dir = join(accountDir, "secrets", ...rel, key);
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
writeFileSync(join(dir, "tokens.enc"), "cipher");
|
|
27
|
+
}
|
|
28
|
+
describe("detectCalendarConnectors", () => {
|
|
29
|
+
it("reports google from an authorised google account token", () => {
|
|
30
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
31
|
+
const accountDir = join(dir, HOUSE);
|
|
32
|
+
writeConnector(accountDir, ["google", "accounts"], "someone_gmail.com");
|
|
33
|
+
expect(detectCalendarConnectors(accountDir)).toEqual(["google"]);
|
|
34
|
+
});
|
|
35
|
+
it("reports m365 from an authorised outlook mailbox token", () => {
|
|
36
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
37
|
+
const accountDir = join(dir, HOUSE);
|
|
38
|
+
writeConnector(accountDir, ["outlook", "mailboxes"], "60273abb803be05b");
|
|
39
|
+
expect(detectCalendarConnectors(accountDir)).toEqual(["m365"]);
|
|
40
|
+
});
|
|
41
|
+
it("reports both, sorted, when both are authorised", () => {
|
|
42
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
43
|
+
const accountDir = join(dir, HOUSE);
|
|
44
|
+
writeConnector(accountDir, ["google", "accounts"], "someone_gmail.com");
|
|
45
|
+
writeConnector(accountDir, ["outlook", "mailboxes"], "60273abb803be05b");
|
|
46
|
+
expect(detectCalendarConnectors(accountDir)).toEqual(["google", "m365"]);
|
|
47
|
+
});
|
|
48
|
+
it("reports none when the connector directory exists but holds no token", () => {
|
|
49
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
50
|
+
const accountDir = join(dir, HOUSE);
|
|
51
|
+
mkdirSync(join(accountDir, "secrets", "google", "accounts", "half-registered"), { recursive: true });
|
|
52
|
+
expect(detectCalendarConnectors(accountDir)).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
it("reports none when the account has no secrets directory at all", () => {
|
|
55
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
56
|
+
expect(detectCalendarConnectors(join(dir, HOUSE))).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
// An unreadable connector directory means the gate's input is UNKNOWN, not
|
|
59
|
+
// empty. Reporting it as `not-applicable` would dress a real fault as the
|
|
60
|
+
// boring expected state — the exact reading that let 7,868 `skipped` lines
|
|
61
|
+
// pass as benign for months.
|
|
62
|
+
it("throws rather than reporting none when the directory exists but cannot be read", () => {
|
|
63
|
+
const dir = accountsDirWith(HOUSE, {});
|
|
64
|
+
const accountDir = join(dir, HOUSE);
|
|
65
|
+
const connectorDir = join(accountDir, "secrets", "google", "accounts");
|
|
66
|
+
mkdirSync(connectorDir, { recursive: true });
|
|
67
|
+
chmodSync(connectorDir, 0o000);
|
|
68
|
+
try {
|
|
69
|
+
expect(() => detectCalendarConnectors(accountDir)).toThrow(/unreadable/);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
chmodSync(connectorDir, 0o755);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("resolveDispatchDestination", () => {
|
|
77
|
+
// The house is the socket-owning authority for WhatsApp, so a house account's
|
|
78
|
+
// own `whatsapp.adminPhones` is the authoritative list and validates.
|
|
79
|
+
it("resolves the house's first registered WhatsApp admin phone", () => {
|
|
80
|
+
const dir = accountsDirWith(HOUSE, {
|
|
81
|
+
role: "house",
|
|
82
|
+
whatsapp: { adminPhones: ["+447504472444"] },
|
|
83
|
+
});
|
|
84
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
85
|
+
destination: { channel: "whatsapp", destination: "+447504472444" },
|
|
86
|
+
reason: null,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// THE GATE-IS-GONE TEST. Before Task 1809 an account with no WhatsApp number
|
|
90
|
+
// was skipped outright, whatever else it had. A Telegram admin is a perfectly
|
|
91
|
+
// good exception destination and must now provision.
|
|
92
|
+
it("falls back to Telegram when the account has no WhatsApp admin phone", () => {
|
|
93
|
+
const dir = accountsDirWith(HOUSE, {
|
|
94
|
+
role: "house",
|
|
95
|
+
telegram: { adminUsers: [123456789] },
|
|
96
|
+
});
|
|
97
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
98
|
+
destination: { channel: "telegram", destination: "123456789" },
|
|
99
|
+
reason: null,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// Task 1812 must not silently move existing accounts onto a new surface.
|
|
103
|
+
it("prefers WhatsApp when both channels are configured", () => {
|
|
104
|
+
const dir = accountsDirWith(HOUSE, {
|
|
105
|
+
role: "house",
|
|
106
|
+
whatsapp: { adminPhones: ["+447504472444"] },
|
|
107
|
+
telegram: { adminUsers: [123456789] },
|
|
108
|
+
});
|
|
109
|
+
expect(resolveDispatchDestination(dir, HOUSE).destination?.channel).toBe("whatsapp");
|
|
110
|
+
});
|
|
111
|
+
// THE FIELD-PATH TEST. `readAdminPhone` read this key. It exists on no
|
|
112
|
+
// account on the install, which is why every tick logged `skipped`. Reading
|
|
113
|
+
// it must not resolve a destination — otherwise the fix is cosmetic.
|
|
114
|
+
it("does not resolve from the legacy top-level adminPhones key", () => {
|
|
115
|
+
const dir = accountsDirWith(HOUSE, {
|
|
116
|
+
role: "house",
|
|
117
|
+
adminPhones: ["+447504472444"],
|
|
118
|
+
});
|
|
119
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
120
|
+
destination: null,
|
|
121
|
+
reason: "none-configured",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
it("resolves nothing when neither channel is configured", () => {
|
|
125
|
+
const dir = accountsDirWith(HOUSE, { role: "house" });
|
|
126
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
127
|
+
destination: null,
|
|
128
|
+
reason: "none-configured",
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// A destination the standing audit would flag as stale on the very next tick
|
|
132
|
+
// must never be armed: provisioning and the audit share one authority. The
|
|
133
|
+
// reason must survive to the caller — an operator told `unconfigured` at an
|
|
134
|
+
// account whose account.json visibly holds a phone number has no path from
|
|
135
|
+
// the log line to the cause.
|
|
136
|
+
it("refuses a WhatsApp number that is registered to no account, and says why", () => {
|
|
137
|
+
const dir = accountsDirWith(HOUSE, {
|
|
138
|
+
role: "house",
|
|
139
|
+
whatsapp: { adminPhones: ["not-a-phone"] },
|
|
140
|
+
});
|
|
141
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
142
|
+
destination: null,
|
|
143
|
+
reason: "not-registered",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
// A number registered to a DIFFERENT account is a distinct, more actionable
|
|
147
|
+
// diagnosis than "not registered", and validateAgentDestination computes it.
|
|
148
|
+
it("reports cross-account rather than collapsing it into not-registered", () => {
|
|
149
|
+
const SUB = "22222222-2222-2222-2222-222222222222";
|
|
150
|
+
const OTHER = "33333333-3333-3333-3333-333333333333";
|
|
151
|
+
const dir = accountsDirWith(HOUSE, {
|
|
152
|
+
role: "house",
|
|
153
|
+
whatsapp: { adminPhones: [], accountManagers: { "+447700900123": OTHER } },
|
|
154
|
+
});
|
|
155
|
+
const subDir = join(dir, SUB);
|
|
156
|
+
mkdirSync(subDir, { recursive: true });
|
|
157
|
+
writeFileSync(join(subDir, "account.json"), JSON.stringify({ role: "client", whatsapp: { adminPhones: ["+447700900123"] } }));
|
|
158
|
+
expect(resolveDispatchDestination(dir, SUB)).toEqual({
|
|
159
|
+
destination: null,
|
|
160
|
+
reason: "cross-account",
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
// main() enumerates accounts by the presence of account.json, so by the time
|
|
164
|
+
// this runs "malformed" is the likely cause and "absent" the unlikely one.
|
|
165
|
+
// Reporting a corrupt config as "no destination configured" tells the
|
|
166
|
+
// operator to add a phone number when the real fact is far larger.
|
|
167
|
+
it("distinguishes an unreadable account.json from an unconfigured one", () => {
|
|
168
|
+
const dir = accountsDirWith(HOUSE, { role: "house" });
|
|
169
|
+
writeFileSync(join(dir, HOUSE, "account.json"), "{not json");
|
|
170
|
+
expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
|
|
171
|
+
destination: null,
|
|
172
|
+
reason: "unreadable-config",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe("shouldEmitProvisionLine", () => {
|
|
177
|
+
const HOUR = 60 * 60 * 1000;
|
|
178
|
+
const HEARTBEAT = 6 * HOUR;
|
|
179
|
+
const entry = (outcome, loggedAtMs, detail) => ({
|
|
180
|
+
outcome,
|
|
181
|
+
signature: provisionSignature(outcome, detail),
|
|
182
|
+
loggedAtMs,
|
|
183
|
+
});
|
|
184
|
+
it("emits when there is no prior state for the account", () => {
|
|
185
|
+
expect(shouldEmitProvisionLine(undefined, "not-applicable", 1_000, HEARTBEAT)).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
it("emits when the outcome changed", () => {
|
|
188
|
+
expect(shouldEmitProvisionLine(entry("unconfigured", 1_000), "provisioned", 1_000 + HOUR, HEARTBEAT)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
// The whole point: 5,688 identical lines is noise that hides its own meaning.
|
|
191
|
+
it("stays silent on an unchanged outcome inside the heartbeat window", () => {
|
|
192
|
+
expect(shouldEmitProvisionLine(entry("already-provisioned", 1_000), "already-provisioned", 1_000 + HOUR, HEARTBEAT)).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
it("emits again once the heartbeat window has elapsed", () => {
|
|
195
|
+
expect(shouldEmitProvisionLine(entry("already-provisioned", 1_000), "already-provisioned", 1_000 + HEARTBEAT, HEARTBEAT)).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
// The heartbeat is the only signal separating "steady state" from "this loop
|
|
198
|
+
// stopped running". An NTP step backwards on an RTC-less Pi must not mute it.
|
|
199
|
+
it("emits rather than muting the heartbeat when the clock steps backwards", () => {
|
|
200
|
+
expect(shouldEmitProvisionLine(entry("already-provisioned", 10 * HOUR), "already-provisioned", 1_000, HEARTBEAT)).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
// A persistent Neo4j fault must not log at tick rate forever — that is the
|
|
203
|
+
// volume profile this task exists to remove — but a CHANGED error is news.
|
|
204
|
+
it("treats an unchanged error as a steady state and a changed one as news", () => {
|
|
205
|
+
const prev = entry("error", 1_000, "Neo4j unreachable");
|
|
206
|
+
expect(shouldEmitProvisionLine(prev, provisionSignature("error", "Neo4j unreachable"), 1_000 + HOUR, HEARTBEAT)).toBe(false);
|
|
207
|
+
expect(shouldEmitProvisionLine(prev, provisionSignature("error", "constraint violation"), 1_000 + HOUR, HEARTBEAT)).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
// Recovery must be visible. Before errors entered the state map, an account
|
|
210
|
+
// returning to health read as "unchanged" and went silent for six hours.
|
|
211
|
+
it("reports recovery from an error immediately", () => {
|
|
212
|
+
const prev = entry("error", 1_000, "Neo4j unreachable");
|
|
213
|
+
expect(shouldEmitProvisionLine(prev, provisionSignature("already-provisioned"), 1_000 + HOUR, HEARTBEAT)).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe("emitLabel", () => {
|
|
217
|
+
// A first observation is not a change. Labelling it as one means any state
|
|
218
|
+
// loss produces a synchronised fleet-wide burst of `emit=change`, which is
|
|
219
|
+
// indistinguishable from every account genuinely flipping at once.
|
|
220
|
+
it("labels a first observation `first`, never `change`", () => {
|
|
221
|
+
expect(emitLabel(undefined, "not-applicable")).toBe("first");
|
|
222
|
+
});
|
|
223
|
+
it("labels a genuine outcome change `change`", () => {
|
|
224
|
+
expect(emitLabel({ outcome: "unconfigured", signature: "unconfigured", loggedAtMs: 1 }, "provisioned")).toBe("change");
|
|
225
|
+
});
|
|
226
|
+
it("labels an unchanged outcome `heartbeat`", () => {
|
|
227
|
+
expect(emitLabel({ outcome: "provisioned", signature: "provisioned", loggedAtMs: 1 }, "provisioned")).toBe("heartbeat");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe("provision state round-trip", () => {
|
|
231
|
+
let dir;
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
dir = mkdtempSync(join(tmpdir(), "cal-sync-state-"));
|
|
234
|
+
});
|
|
235
|
+
const ENTRY = { outcome: "provisioned", signature: "provisioned", loggedAtMs: 42 };
|
|
236
|
+
it("round-trips per-account outcomes", () => {
|
|
237
|
+
const path = join(dir, "cal-sync-provision-state.json");
|
|
238
|
+
writeProvisionState(path, { [HOUSE]: ENTRY });
|
|
239
|
+
expect(readProvisionState(path)).toEqual({ [HOUSE]: ENTRY });
|
|
240
|
+
});
|
|
241
|
+
it("leaves no temp file behind, so the write is atomic", () => {
|
|
242
|
+
const path = join(dir, "cal-sync-provision-state.json");
|
|
243
|
+
writeProvisionState(path, { [HOUSE]: ENTRY });
|
|
244
|
+
expect(existsSync(`${path}.tmp`)).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
it("reads an absent state file as empty and silently — that is the first run", () => {
|
|
247
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
248
|
+
expect(readProvisionState(join(dir, "missing.json"))).toEqual({});
|
|
249
|
+
expect(spy).not.toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
// A corrupt state file triggers a fleet-wide `emit=first` burst. That burst
|
|
252
|
+
// must carry its own explanation, or it reads as every account flipping at once.
|
|
253
|
+
it("reads a corrupt state file as empty but says so", () => {
|
|
254
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
255
|
+
const bad = join(dir, "bad.json");
|
|
256
|
+
writeFileSync(bad, "{not json");
|
|
257
|
+
expect(readProvisionState(bad)).toEqual({});
|
|
258
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining("result=unreadable"));
|
|
259
|
+
});
|
|
260
|
+
// Every realistic cause of a write failure is persistent, so the failure
|
|
261
|
+
// degrades emit-on-change back to per-tick logging indefinitely. Silence
|
|
262
|
+
// here would make that degradation undetectable.
|
|
263
|
+
it("reports a failed write and names the impact rather than swallowing it", () => {
|
|
264
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
265
|
+
const readOnly = join(dir, "read-only");
|
|
266
|
+
mkdirSync(readOnly);
|
|
267
|
+
chmodSync(readOnly, 0o500);
|
|
268
|
+
try {
|
|
269
|
+
writeProvisionState(join(readOnly, "state.json"), { [HOUSE]: ENTRY });
|
|
270
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining("result=write-failed"));
|
|
271
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining("impact=emit-on-change-degraded-to-per-tick"));
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
chmodSync(readOnly, 0o755);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
// The mutation check the task calls for. A permanently unconfigured account is
|
|
279
|
+
// the case most at risk of being silenced by the very change that quietens the
|
|
280
|
+
// per-tick noise. This asserts the outcome reaches the operator on change, and
|
|
281
|
+
// the guard below proves the assertion has teeth.
|
|
282
|
+
describe("an unconfigured account is reported, not swallowed", () => {
|
|
283
|
+
const HEARTBEAT = 6 * 60 * 60 * 1000;
|
|
284
|
+
let emitted;
|
|
285
|
+
beforeEach(() => {
|
|
286
|
+
emitted = [];
|
|
287
|
+
});
|
|
288
|
+
afterEach(() => {
|
|
289
|
+
vi.restoreAllMocks();
|
|
290
|
+
});
|
|
291
|
+
/** The emit decision exactly as reconcileAccount applies it. */
|
|
292
|
+
function tick(prevState, nowMs) {
|
|
293
|
+
const outcome = "unconfigured";
|
|
294
|
+
const signature = provisionSignature(outcome, "none-configured");
|
|
295
|
+
if (shouldEmitProvisionLine(prevState[HOUSE], signature, nowMs, HEARTBEAT)) {
|
|
296
|
+
emitted.push(outcome);
|
|
297
|
+
prevState[HOUSE] = { outcome, signature, loggedAtMs: nowMs };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
it("emits once on first observation and stays silent for the next 100 ticks", () => {
|
|
301
|
+
const state = {};
|
|
302
|
+
// 101 ticks at 120s is under 3.4h, inside the 6h heartbeat window.
|
|
303
|
+
for (let i = 0; i < 101; i++)
|
|
304
|
+
tick(state, i * 120_000);
|
|
305
|
+
// One line, not 101 — and crucially not zero.
|
|
306
|
+
expect(emitted).toEqual(["unconfigured"]);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
//# sourceMappingURL=connector-sync-provision.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector-sync-provision.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/connector-sync-provision.test.ts"],"names":[],"mappings":"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"}
|