Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

495 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { readFileSync, readdirSync } from "node:fs";
3import { DatabaseSync } from "node:sqlite";
4import { test } from "node:test";
5import { build } from "esbuild";
6
7// Execute the real service code against SQLite, with only the Cloudflare host replaced.
8async function service(path) {
9 const bundle = await build({
10 entryPoints: [path],
11 bundle: true,
12 write: false,
13 format: "esm",
14 platform: "node",
15 plugins: [
16 {
17 name: "worker-host",
18 setup(build) {
19 build.onResolve({ filter: /^yjs$/ }, () => ({ path: import.meta.resolve("yjs"), external: true }));
20 build.onResolve({ filter: /^cloudflare:workers$/ }, () => ({ path: "host", namespace: "host" }));
21 build.onLoad({ filter: /.*/, namespace: "host" }, () => ({
22 contents: "export class DurableObject { constructor(ctx, env) { this.ctx = ctx; this.env = env; } }",
23 }));
24 },
25 },
26 ],
27 });
28 return import(`data:text/javascript;base64,${Buffer.from(bundle.outputFiles[0].text).toString("base64")}`);
29}
30
31function database(service) {
32 const raw = new DatabaseSync(":memory:");
33 const dir = new URL(`../services/${service}/migrations/`, import.meta.url);
34 for (const name of readdirSync(dir).sort()) raw.exec(readFileSync(new URL(name, dir), "utf8"));
35 raw.exec("PRAGMA foreign_keys = ON");
36 const run = (s) => ({ meta: { changes: Number(raw.prepare(s.sql).run(...s.params).changes) } });
37 const prepare = (sql, params = []) => ({
38 sql,
39 params,
40 bind: (...p) => prepare(sql, p),
41 first: async () => raw.prepare(sql).get(...params) ?? null,
42 all: async () => ({ results: raw.prepare(sql).all(...params) }),
43 run: async () => run({ sql, params }),
44 });
45 return {
46 raw,
47 prepare,
48 batch: async (statements) => {
49 raw.exec("BEGIN");
50 try {
51 const result = statements.map(run);
52 raw.exec("COMMIT");
53 return result;
54 } catch (e) {
55 raw.exec("ROLLBACK");
56 throw e;
57 }
58 },
59 };
60}
61const namespace = (get) => ({ idFromName: (id) => id, get });
62const AT = "2026-10-14T00:00:00.000Z";
63
64test("identity purge work is atomic, idempotent and a prior restore prevents scheduling", () => {
65 const db = database("identity").raw;
66 const enqueue = db.prepare(readFileSync(new URL("../services/identity/src/purge_job.sql", import.meta.url), "utf8"));
67 db.prepare("INSERT INTO users (id, username, password_hash, created_at, deleted_at, purge_after) VALUES ('usr_fixture', 'fixture', 'fixture', ?, ?, ?)").run(
68 AT,
69 AT,
70 AT,
71 );
72 const args = { "?1": "usr_fixture", "?2": "user", "?3": "CHAT", "?4": "{}", "?5": AT };
73 db.exec("BEGIN");
74 enqueue.run(args);
75 db.exec("DELETE FROM users WHERE id = 'usr_fixture'");
76 db.exec("ROLLBACK");
77 assert.equal(db.prepare("SELECT count(*) n FROM purge_jobs").get().n, 0);
78 assert.ok(db.prepare("SELECT 1 FROM users WHERE id = 'usr_fixture'").get());
79 db.exec("UPDATE users SET deleted_at = NULL, purge_after = NULL WHERE id = 'usr_fixture'");
80 enqueue.run(args);
81 assert.equal(db.prepare("SELECT count(*) n FROM purge_jobs").get().n, 0);
82 db.prepare("UPDATE users SET deleted_at = ?, purge_after = ? WHERE id = 'usr_fixture'").run(AT, AT);
83 db.exec("BEGIN");
84 enqueue.run(args);
85 enqueue.run(args);
86 db.exec("DELETE FROM users WHERE id = 'usr_fixture'; COMMIT");
87 assert.equal(db.prepare("SELECT count(*) n FROM purge_jobs").get().n, 1);
88 assert.equal(db.prepare("SELECT completed_at FROM purge_jobs").get().completed_at, null);
89});
90
91test("chat purges the private AI conversation, preserves other tenants, retries failed rooms and rejects late writes", async () => {
92 const { purgePersonalData } = await service("services/chat/src/erasure.ts");
93 const db = database("chat");
94 const { G1T_KEY } = await service("packages/contracts/src/chat.ts");
95 for (const [id, person] of [
96 ["mine", "usr_a"],
97 ["other", "usr_b"],
98 ]) {
99 db.raw.prepare("INSERT INTO channels (id, workspace_id, kind, created_by, created_at) VALUES (?, ?, 'dm', ?, ?)").run(id, id, `user:${person}`, AT);
100 for (const key of [`user:${person}`, G1T_KEY])
101 db.raw.prepare("INSERT INTO channel_members (channel_id, principal, joined_at) VALUES (?, ?, ?)").run(id, key, AT);
102 db.raw.prepare("INSERT INTO messages (id, channel_id, author, body, created_at) VALUES (?, ?, ?, 'private prompt', ?)").run(id, id, `user:${person}`, AT);
103 }
104 let fail = true;
105 const env = {
106 DB: db,
107 ROOMS: namespace(() => ({
108 erase: async () => {
109 if (fail) throw Error("unavailable");
110 },
111 })),
112 AVATARS: { delete: async () => {} },
113 };
114 await assert.rejects(purgePersonalData(env, { kind: "user", id: "usr_a" }));
115 assert.equal(db.raw.prepare("SELECT count(*) n FROM messages").get().n, 2);
116 fail = false;
117 assert.deepEqual(await purgePersonalData(env, { kind: "user", id: "usr_a" }), { ok: true });
118 assert.deepEqual(await purgePersonalData(env, { kind: "user", id: "usr_a" }), { ok: true });
119 assert.equal(db.raw.prepare("SELECT count(*) n FROM messages").get().n, 1);
120 assert.equal(db.raw.prepare("SELECT body FROM messages WHERE id = 'other'").get().body, "private prompt");
121 db.raw.prepare("INSERT INTO messages (id, channel_id, author, body, created_at) VALUES ('late', 'mine', 'user:usr_a', 'late', ?)").run(AT);
122 assert.equal(db.raw.prepare("SELECT count(*) n FROM messages").get().n, 1);
123});
124
125test("artifacts keep retry references when file deletion fails, then remove private state with FK enforcement", async () => {
126 const { purgePersonalData } = await service("services/artifacts/src/erasure.ts");
127 const db = database("artifacts");
128 for (const [id, owner] of [
129 ["mine", "usr_a"],
130 ["other", "usr_b"],
131 ]) {
132 db.raw
133 .prepare(
134 "INSERT INTO folios (id, workspace_id, kind, owner, position, acl_root, path, created_by, created_at, updated_at, edited_at) VALUES (?, ?, 'doc', ?, 1, ?, ?, ?, ?, ?, ?)",
135 )
136 .run(id, id, `user:${owner}`, id, `/${id}/`, `user:${owner}`, AT, AT, AT);
137 db.raw
138 .prepare(
139 "INSERT INTO folio_files (id, workspace_id, folio_id, key, name, content_type, bytes, created_by, created_at) VALUES (?, ?, ?, ?, 'private.txt', 'text/plain', 10, ?, ?)",
140 )
141 .run(id, id, id, id, `user:${owner}`, AT);
142 }
143 let fail = true;
144 const deleted = [],
145 rooms = [];
146 const env = {
147 DB: db,
148 FOLIOS: namespace((id) => ({
149 forgetPerson: async () => {},
150 destroy: async () => {
151 rooms.push(id);
152 },
153 })),
154 FILES: {
155 delete: async (key) => {
156 if (fail) throw Error("storage down");
157 deleted.push(key);
158 },
159 },
160 };
161 await assert.rejects(purgePersonalData(env, { kind: "user", id: "usr_a" }));
162 assert.equal(db.raw.prepare("SELECT count(*) n FROM folio_files").get().n, 2);
163 fail = false;
164 assert.deepEqual(await purgePersonalData(env, { kind: "user", id: "usr_a" }), { ok: true });
165 assert.deepEqual(await purgePersonalData(env, { kind: "user", id: "usr_a" }), { ok: true });
166 assert.deepEqual(deleted, ["docs/mine"]);
167 assert.equal(db.raw.prepare("SELECT count(*) n FROM folios").get().n, 1);
168 assert.equal(db.raw.prepare("SELECT id FROM folio_files").get().id, "other");
169 assert.ok(rooms.every((id) => id === "mine"));
170 assert.deepEqual(await purgePersonalData(env, { kind: "workspace", id: "other" }), { ok: true });
171 assert.equal(db.raw.prepare("SELECT count(*) n FROM folios").get().n, 0);
172});
173
174function objectState() {
175 const raw = new DatabaseSync(":memory:");
176 const kv = new Map();
177 const sql = {
178 exec(query, ...params) {
179 const stmt = raw.prepare(query);
180 if (stmt.columns().length) {
181 const rows = stmt.all(...params);
182 return { toArray: () => rows, one: () => rows[0], [Symbol.iterator]: () => rows[Symbol.iterator]() };
183 }
184 const result = stmt.run(...params);
185 return { toArray: () => [], rowsWritten: Number(result.changes) };
186 },
187 };
188 return {
189 raw,
190 blockConcurrencyWhile: (fn) => fn(),
191 storage: {
192 sql,
193 get: async (key) => kv.get(key),
194 put: async (key, value) => {
195 kv.set(key, value);
196 },
197 delete: async (key) => kv.delete(key),
198 deleteAlarm: async () => {},
199 transactionSync(fn) {
200 raw.exec("BEGIN");
201 try {
202 fn();
203 raw.exec("COMMIT");
204 } catch (e) {
205 raw.exec("ROLLBACK");
206 throw e;
207 }
208 },
209 },
210 getWebSockets: () => [],
211 setWebSocketAutoResponse: () => {},
212 waitUntil: () => {},
213 };
214}
215
216test("notification erasure retries presence cleanup, clears credentials and permanently blocks late delivery", async () => {
217 globalThis.WebSocketRequestResponsePair = class {};
218 const { Feed } = await service("services/notify/src/feed.ts");
219 const state = objectState();
220 let fail = true,
221 calls = 0;
222 const feed = new Feed(state, {
223 ROOMS: namespace(() => ({
224 forget: async () => {
225 calls++;
226 if (fail) throw Error("offline");
227 },
228 })),
229 });
230 state.raw.exec(
231 `INSERT INTO meta VALUES ('workspaces', '["acme"]'); INSERT INTO devices VALUES ('token', 'ios', 'phone', 1); INSERT INTO notifications VALUES ('n', 1, '{}');`,
232 );
233 await assert.rejects(feed.erase("usr_a"));
234 // A late delivery is dropped quietly, never refused: senders would log and retry it.
235 assert.deepEqual(await feed.deliver([{ workspace: "acme", notification: { id: "late", created_at: AT } }]), { ok: true });
236 assert.deepEqual(await feed.notify({ id: "late", kind: "mention", workspace: "acme", created_at: AT }), { ok: true });
237 fail = false;
238 assert.deepEqual(await feed.erase("usr_a"), { ok: true });
239 assert.equal(calls, 2);
240 for (const table of ["devices", "notifications", "counts", "subscriptions", "visits", "watching"])
241 assert.equal(state.raw.prepare(`SELECT count(*) n FROM ${table}`).get().n, 0);
242 assert.deepEqual(
243 state.raw
244 .prepare("SELECT key FROM meta")
245 .all()
246 .map((r) => r.key),
247 ["erased"],
248 );
249 await assert.rejects(feed.registerDevice({ token: "late", platform: "ios" }), /Account erased/);
250 assert.equal((await feed.fetch(new Request("https://test/live"))).status, 410);
251 assert.deepEqual(await feed.erase("usr_a"), { ok: true });
252});
253
254test("destroyed document rooms reject late ensure calls and keep only a tombstone", async () => {
255 globalThis.WebSocketRequestResponsePair = class {};
256 for (const [file, name, init] of [
257 ["services/artifacts/src/room.ts", "PageRoom", { page_id: "p", workspace_slug: "acme", markdown: "private" }],
258 ["services/artifacts/src/folios/room.ts", "FolioRoom", { folio_id: "f", kind: "doc", workspace_slug: "acme", text: "private" }],
259 ]) {
260 const module = await service(file),
261 state = objectState();
262 const room = new module[name](state, {});
263 state.raw.exec("INSERT INTO meta VALUES ('workspace_slug', '\"acme\"'); INSERT INTO snapshot VALUES (1, x'01');");
264 await room.destroy();
265 await room.destroy();
266 assert.equal(state.raw.prepare("SELECT count(*) n FROM snapshot").get().n, 0);
267 assert.deepEqual(
268 state.raw
269 .prepare("SELECT key FROM meta")
270 .all()
271 .map((r) => r.key),
272 ["erased"],
273 );
274 await assert.rejects(room.ensure(init), /Document erased/);
275 assert.equal((await room.fetch(new Request("https://test/live"))).status, 410);
276 assert.deepEqual(
277 state.raw
278 .prepare("SELECT key FROM meta")
279 .all()
280 .map((r) => r.key),
281 ["erased"],
282 );
283 }
284});
285
286test("erasing one member of a shared channel leaves everyone else's conversation alone", async () => {
287 const { purgePersonalData } = await service("services/chat/src/erasure.ts");
288 const db = database("chat");
289 db.raw
290 .prepare("INSERT INTO channels (id, workspace_id, kind, name, created_by, created_at) VALUES ('general', 'ws', 'channel', 'general', 'user:usr_a', ?)")
291 .run(AT);
292 for (const person of ["usr_a", "usr_b"])
293 db.raw.prepare("INSERT INTO channel_members (channel_id, principal, joined_at) VALUES ('general', ?, ?)").run(`user:${person}`, AT);
294 db.raw
295 .prepare(
296 "INSERT INTO messages (id, channel_id, author, body, created_at) VALUES ('m1', 'general', 'user:usr_a', 'from a', ?), ('m2', 'general', 'user:usr_b', 'from b', ?)",
297 )
298 .run(AT, AT);
299 db.raw
300 .prepare("INSERT INTO reactions (message_id, principal, emoji, created_at) VALUES ('m2', 'user:usr_a', 'ok', ?), ('m2', 'user:usr_b', 'ok', ?)")
301 .run(AT, AT);
302 const erased = [];
303 const env = {
304 DB: db,
305 ROOMS: namespace(() => ({
306 erase: async (id, principal) => {
307 erased.push([id, principal]);
308 },
309 })),
310 AVATARS: { delete: async () => {} },
311 };
312 assert.deepEqual(await purgePersonalData(env, { kind: "user", id: "usr_a" }), { ok: true });
313 // The room shuts out only the erased person; it is never cleared for everyone.
314 assert.deepEqual(erased, [["general", "user:usr_a"]]);
315 assert.deepEqual(
316 db.raw
317 .prepare("SELECT id, author, body FROM messages ORDER BY id")
318 .all()
319 .map((r) => ({ ...r })),
320 [
321 { id: "m1", author: "user:usr_ghost", body: "from a" },
322 { id: "m2", author: "user:usr_b", body: "from b" },
323 ],
324 );
325 assert.deepEqual(
326 db.raw
327 .prepare("SELECT principal FROM channel_members")
328 .all()
329 .map((r) => r.principal),
330 ["user:usr_b"],
331 );
332 assert.deepEqual(
333 db.raw
334 .prepare("SELECT principal FROM reactions")
335 .all()
336 .map((r) => r.principal),
337 ["user:usr_b"],
338 );
339 assert.equal(db.raw.prepare("SELECT created_by FROM channels").get().created_by, "user:usr_ghost");
340});
341
342test("a shared channel's room shuts out only the erased member and tells nobody it was cleared", async () => {
343 globalThis.WebSocketRequestResponsePair = class {};
344 const { ChannelRoom } = await service("services/chat/src/room.ts");
345 const state = objectState();
346 const socket = (who) => ({
347 who,
348 sent: [],
349 closed: null,
350 send(text) {
351 this.sent.push(text);
352 },
353 close(code) {
354 this.closed = code;
355 },
356 });
357 const sockets = [socket("user:usr_a"), socket("user:usr_b")];
358 state.getWebSockets = (tag) => sockets.filter((s) => !tag || s.who === tag);
359 await state.storage.put("job", { message: { id: "answer_for_b" } });
360 const deletes = [];
361 const room = new ChannelRoom(state, {
362 DB: {
363 prepare: () => ({
364 bind: (id) => ({
365 run: async () => {
366 deletes.push(id);
367 },
368 }),
369 }),
370 },
371 });
372 await room.erase("general", "user:usr_a");
373 assert.equal(sockets[0].closed, 4403);
374 assert.equal(sockets[1].closed, null);
375 assert.deepEqual(sockets[1].sent, []);
376 // Someone else's pending answer carries on.
377 assert.deepEqual(await state.storage.get("job"), { message: { id: "answer_for_b" } });
378 assert.deepEqual(deletes, []);
379 assert.equal((await room.fetch(new Request("https://test/live"))).status, 426);
380});
381
382test("a workspace's large channel is deleted in chunks across calls, and nothing of it comes back", async () => {
383 const { purgePersonalData } = await service("services/chat/src/erasure.ts");
384 const db = database("chat");
385 db.raw
386 .prepare(
387 "INSERT INTO channels (id, workspace_id, kind, name, created_by, created_at) VALUES ('big', 'ws', 'channel', 'big', 'user:usr_a', ?), ('kept', 'other', 'channel', 'kept', 'user:usr_b', ?)",
388 )
389 .run(AT, AT);
390 const insert = db.raw.prepare("INSERT INTO messages (id, channel_id, author, body, created_at) VALUES (?, ?, 'user:usr_a', 'x', ?)");
391 for (let i = 0; i < 5600; i++) insert.run(`m${String(i).padStart(5, "0")}`, "big", AT);
392 insert.run("other", "kept", AT);
393 const env = { DB: db, ROOMS: namespace(() => ({ erase: async () => {} })), AVATARS: { delete: async () => {} } };
394 assert.deepEqual(await purgePersonalData(env, { kind: "workspace", id: "ws" }), { ok: false, pending: true });
395 assert.ok(db.raw.prepare("SELECT count(*) n FROM messages WHERE channel_id = 'big'").get().n > 0);
396 assert.deepEqual(await purgePersonalData(env, { kind: "workspace", id: "ws" }), { ok: true });
397 assert.equal(db.raw.prepare("SELECT count(*) n FROM messages WHERE channel_id = 'big'").get().n, 0);
398 assert.equal(db.raw.prepare("SELECT count(*) n FROM channels WHERE workspace_id = 'ws'").get().n, 0);
399 assert.equal(db.raw.prepare("SELECT body FROM messages WHERE id = 'other'").get().body, "x");
400 db.raw
401 .prepare("INSERT INTO channels (id, workspace_id, kind, name, created_by, created_at) VALUES ('late', 'ws', 'channel', 'late', 'user:usr_b', ?)")
402 .run(AT);
403 db.raw.prepare("INSERT INTO chat_settings (workspace_id) VALUES ('ws')").run();
404 assert.equal(db.raw.prepare("SELECT count(*) n FROM channels WHERE workspace_id = 'ws'").get().n, 0);
405 assert.equal(db.raw.prepare("SELECT count(*) n FROM chat_settings").get().n, 0);
406});
407
408test("a purged workspace's slug can be made again: only what was said before the purge is dropped", async () => {
409 globalThis.WebSocketRequestResponsePair = class {};
410 const { Feed } = await service("services/notify/src/feed.ts");
411 const { Room } = await service("services/notify/src/room.ts");
412 const state = objectState();
413 const feed = new Feed(state, {});
414 const before = new Date(Date.now() - 60_000).toISOString();
415 state.raw.prepare("INSERT INTO notifications VALUES ('old', 1, ?)").run(JSON.stringify({ id: "old", workspace: "alice", created_at: before }));
416 state.raw.prepare("INSERT INTO meta VALUES ('complete:alice', '1'), ('forgot:alice:ch_1', ?), ('forgot:alicex:ch_1', ?)").run(before, before);
417 assert.deepEqual(await feed.eraseWorkspace("alice"), { ok: true });
418 assert.equal(state.raw.prepare("SELECT count(*) n FROM notifications").get().n, 0);
419 assert.deepEqual(
420 state.raw
421 .prepare("SELECT key FROM meta WHERE key LIKE 'forgot:%' OR key LIKE 'complete:%'")
422 .all()
423 .map((r) => r.key),
424 ["forgot:alicex:ch_1"],
425 );
426 // A late notification from before the purge is dropped; one from the workspace made again is kept.
427 await feed.notify({ id: "late", kind: "mention", workspace: "alice", title: "t", url: "/alice", created_at: before });
428 await feed.notify({ id: "new", kind: "mention", workspace: "alice", title: "t", url: "/alice", created_at: new Date(Date.now() + 1000).toISOString() });
429 assert.deepEqual(
430 state.raw
431 .prepare("SELECT id FROM notifications")
432 .all()
433 .map((r) => r.id),
434 ["new"],
435 );
436
437 const room = new Room(objectState(), {});
438 await room.report("alice", { user_id: "usr_b", at: 1, presence: "online" });
439 assert.deepEqual(await room.eraseMembers(), ["usr_b"]);
440 await room.dropPerson("usr_b");
441 assert.equal(await room.report("alice", { user_id: "usr_c", at: 2, presence: "online" }), null);
442 await room.report("alice", { user_id: "usr_b", at: Date.now() + 1000, presence: "online" });
443 assert.deepEqual(
444 (await room.people()).map((p) => p.user_id),
445 ["usr_b"],
446 );
447});
448
449test("vectors written while a document was being erased are taken back", async () => {
450 const { dropIfErased, erasedSource } = await service("services/artifacts/src/indexer.ts");
451 const db = database("artifacts");
452 const deleted = [];
453 const store = {
454 delete: async (ids) => {
455 deleted.push(...ids);
456 },
457 };
458 await dropIfErased(db, store, "folio", "f1", "ws", ["f1:0"]);
459 assert.deepEqual(deleted, []);
460 db.raw.prepare("INSERT INTO erased_documents (id, kind) VALUES ('f1', 'folio')").run();
461 assert.equal(await erasedSource(db, "folio", "f1", "ws"), true);
462 assert.equal(await erasedSource(db, "page", "f1", "ws"), false);
463 await dropIfErased(db, store, "folio", "f1", "ws", ["f1:0", "f1:1"]);
464 assert.deepEqual(deleted, ["f1:0", "f1:1"]);
465 db.raw.prepare("INSERT INTO erased_subjects VALUES ('ws2', 'workspace')").run();
466 assert.equal(await erasedSource(db, null, "repo_file", "ws2"), true);
467});
468
469test("a workspace's uploads are swept a page at a time, resuming after a failure", async () => {
470 const { purgePersonalData } = await service("services/artifacts/src/erasure.ts");
471 const db = database("artifacts");
472 const insert = db.raw.prepare(
473 "INSERT INTO files (id, workspace_id, page_id, key, name, content_type, bytes, created_by, created_at) VALUES (?, ?, NULL, ?, 'f', 'text/plain', 1, 'user:usr_a', ?)",
474 );
475 for (let i = 0; i < 2100; i++) insert.run(`f${i}`, "ws", `k${i}`, AT);
476 insert.run("kept", "other", "kept", AT);
477 let fail = 3;
478 const deleted = new Set();
479 const env = {
480 DB: db,
481 FILES: {
482 delete: async (key) => {
483 if (key === "docs/k5" && fail-- > 0) throw Error("storage down");
484 deleted.add(key);
485 },
486 },
487 };
488 await assert.rejects(purgePersonalData(env, { kind: "workspace", id: "ws" }));
489 let result;
490 for (let i = 0; i < 6 && !result?.ok; i++) result = await purgePersonalData(env, { kind: "workspace", id: "ws" }).catch(() => null);
491 assert.deepEqual(result, { ok: true });
492 assert.equal(deleted.size, 2100);
493 assert.ok(!deleted.has("docs/kept"));
494 assert.equal(db.raw.prepare("SELECT id FROM files").get().id, "kept");
495});