import { describe, expect, it } from "bun:test"; import { StorageError } from "./errors"; import type { EnqueueBackgroundJob, JsonValue, Lease } from "./model"; import type { StorageAdapter } from "./port"; type Factory = () => StorageAdapter | Promise; function id(label: string): string { return `${label}-${crypto.randomUUID()}`; } async function opened(factory: Factory): Promise { let storage = await factory(); await storage.migrate(); return storage; } async function userAndChannel(storage: StorageAdapter): Promise<{ userId: string; sessionId: string; channelId: string; repositoryId: string; lease: Lease; }> { let now = new Date("2026-01-02T03:04:05.000Z"); let userId = id("user"); let sessionId = id("session"); let channelId = id("channel"); let repositoryId = id("repository"); await storage.users.put({ id: userId, login: "octocat", avatarUrl: "https://example.test/a", now, }); await storage.sessions.create({ id: sessionId, userId, expiresAt: new Date("2026-02-02T03:04:05.000Z"), createdAt: now, }); await storage.channels.create({ id: channelId, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Release plan", createdBy: userId, now, }); let lease = await storage.leases.acquire( id("channel-writer"), id("instance"), 60_000, ); if (!lease) throw new Error("test could not acquire its storage lease"); return { userId, sessionId, channelId, repositoryId, lease }; } function backgroundJob( channelId: string, lease: Lease, overrides: Partial = {}, ): EnqueueBackgroundJob { let now = overrides.now ?? new Date(); return { id: id("job"), channelId, type: "document-summary", version: 1, origin: "scheduler", targetKey: "document-summary", idempotencyKey: id("enqueue"), fingerprint: id("fingerprint"), input: { revision: 1 }, availableAt: now, now, lease, ...overrides, }; } /** The behavioral gate every built-in storage adapter must pass. */ export function storageContract(name: string, factory: Factory): void { describe(`${name} storage`, () => { it("keeps only process-lifetime registry metadata for a login session", async () => { let storage = await opened(factory); try { let now = new Date("2026-01-02T03:04:05.000Z"); let userId = id("user"); let sessionId = id("session"); await storage.users.put({ id: userId, login: "mona", avatarUrl: "https://example.test/mona", now, }); await storage.sessions.create({ id: sessionId, userId, expiresAt: new Date("2026-01-02T04:04:05.000Z"), createdAt: now, }); let active = await storage.sessions.get( sessionId, new Date("2026-01-02T03:30:00.000Z"), ); expect(active).toMatchObject({ id: sessionId, userId }); expect( await storage.sessions.get(sessionId, new Date("2026-01-02T04:04:05.000Z")), ).toBeUndefined(); expect(await storage.sessions.deleteExpired(new Date("2026-01-02T05:00:00.000Z"))) .toBe(1); expect(await storage.sessions.get(sessionId, now)).toBeUndefined(); } finally { await storage.close(); } }); it("deletes all session registries while preserving durable agent context", async () => { let storage = await opened(factory); try { let { sessionId, channelId, lease } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); let claimed = await storage.channels.claimAgentOwner(channelId, sessionId, now); await storage.channels.updateAgentContext({ channelId, ownerSessionId: sessionId, generation: claimed.generation, summary: "durable summary", transcriptCursor: 7, status: "ready", now, }); let reset = await storage.sessions.deleteAll(now, lease, 60_000); expect(reset.deleted).toBeGreaterThan(0); expect(reset.lease.fencing).toBe(lease.fencing); expect(await storage.sessions.get(sessionId, now)).toBeUndefined(); let saved = await storage.collaboration.load(channelId, now); expect(saved!.agent).toMatchObject({ generation: claimed.generation, summary: "durable summary", transcriptCursor: 7, status: "unavailable", }); expect(saved!.agent!.ownerSessionId).toBeUndefined(); } finally { await storage.close(); } }); it("reports whether adding a project changed navigation and keeps its original order", async () => { let storage = await opened(factory); try { let now = new Date("2026-01-02T03:04:05.000Z"); let userId = id("user"); await storage.users.put({ id: userId, login: "mona", avatarUrl: "https://example.test/mona", now, }); let first = await storage.navigation.addProject({ userId, repositoryId: "R_first", repositoryOwner: "githubnext", repositoryName: "chopin", now, }); let second = await storage.navigation.addProject({ userId, repositoryId: "R_second", repositoryOwner: "githubnext", repositoryName: "second", now: new Date(now.getTime() + 1), }); let repeated = await storage.navigation.addProject({ userId, repositoryId: "R_first", repositoryOwner: "githubnext", repositoryName: "chopin", now: new Date(now.getTime() + 2), }); expect(first.added).toBe(true); expect(second.added).toBe(true); expect(repeated).toEqual({ project: first.project, added: false }); expect((await storage.navigation.projects(userId)).map(project => project.repositoryId)) .toEqual(["R_first", "R_second"]); } finally { await storage.close(); } }); it("stores the last document idempotently", async () => { let storage = await opened(factory); try { let now = new Date("2026-01-02T03:04:05.000Z"); let { userId, channelId } = await userAndChannel(storage); let first = await storage.navigation.setLastDocument(userId, channelId, now); let repeated = await storage.navigation.setLastDocument(userId, channelId, now); expect(repeated).toEqual(first); expect(await storage.navigation.get(userId)).toEqual(first); } finally { await storage.close(); } }); it("rejects a last document that does not exist", async () => { let storage = await opened(factory); try { let { userId } = await userAndChannel(storage); await expect(storage.navigation.setLastDocument( userId, id("missing-channel"), new Date("2026-01-02T03:04:05.000Z"), )).rejects.toMatchObject({ failure: "missing" }); expect(await storage.navigation.get(userId)).toBeUndefined(); } finally { await storage.close(); } }); it("records a deep-link visit without adding a project when its document is missing", async () => { let storage = await opened(factory); try { let { userId } = await userAndChannel(storage); await expect(storage.navigation.recordVisit({ userId, repositoryId: "R_missing", repositoryOwner: "githubnext", repositoryName: "missing", documentId: id("missing-channel"), now: new Date("2026-01-02T03:04:05.000Z"), })).rejects.toMatchObject({ failure: "missing" }); expect(await storage.navigation.projects(userId)).toEqual([]); expect(await storage.navigation.get(userId)).toBeUndefined(); } finally { await storage.close(); } }); it("rejects a deep-link visit whose document belongs to another repository", async () => { let storage = await opened(factory); try { let { userId, channelId } = await userAndChannel(storage); await expect(storage.navigation.recordVisit({ userId, repositoryId: id("other-repository"), repositoryOwner: "githubnext", repositoryName: "other", documentId: channelId, now: new Date("2026-01-02T03:04:05.000Z"), })).rejects.toMatchObject({ failure: "missing" }); expect(await storage.navigation.projects(userId)).toEqual([]); expect(await storage.navigation.get(userId)).toBeUndefined(); } finally { await storage.close(); } }); it("adds a project and records a deep-link visit together", async () => { let storage = await opened(factory); try { let { userId, channelId, repositoryId } = await userAndChannel(storage); let saved = await storage.navigation.recordVisit({ userId, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", documentId: channelId, now: new Date("2026-01-02T03:04:05.000Z"), }); expect(saved.lastDocumentId).toBe(channelId); expect(await storage.navigation.projects(userId)).toMatchObject([{ repositoryId }]); } finally { await storage.close(); } }); it("lists only a repository's channels in stable order", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let other = id("repository"); await storage.channels.create({ id: id("other-channel"), repositoryId: other, repositoryOwner: "elsewhere", repositoryName: "private", title: "Not this repository", createdBy: userId, now: new Date("2026-01-03T03:04:05.000Z"), }); let page = await storage.channels.list(repositoryId, 20); expect(page.channels).toHaveLength(1); expect(page.channels[0]!.repositoryId).toBe(repositoryId); expect(page.next).toBeUndefined(); } finally { await storage.close(); } }); it("renames channel metadata without advancing its collaboration revision", async () => { let storage = await opened(factory); try { let { channelId, lease, repositoryId } = await userAndChannel(storage); let before = await storage.channels.get(channelId); let now = new Date("2026-01-03T03:04:05.000Z"); let renamed = await storage.channels.rename({ id: channelId, title: "Launch plan", now, }); expect(renamed).toMatchObject({ changed: true, channel: { title: "Launch plan" } }); expect(renamed.channel.updatedAt).toEqual(now); expect(renamed.channel.revision).toBe(before!.revision); expect((await storage.channels.list(repositoryId, 20, undefined, "launch")).channels) .toHaveLength(1); expect((await storage.channels.list(repositoryId, 20, undefined, "release")).channels) .toEqual([]); await storage.collaboration.commit({ channelId, lease, expectedRevision: 0, operationId: id("operation"), epoch: "epoch-1", events: [], now: new Date("2026-01-02T12:00:00.000Z"), }); expect((await storage.channels.get(channelId))!.updatedAt).toEqual(now); let repeated = await storage.channels.rename({ id: channelId, title: "Launch plan", now: new Date("2026-01-04T03:04:05.000Z"), }); expect(repeated.changed).toBe(false); expect(repeated.channel.updatedAt).toEqual(now); } finally { await storage.close(); } }); it("keeps renamed document titles unique within their repository", async () => { let storage = await opened(factory); try { let { userId, channelId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); let other = await storage.channels.create({ id: id("channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Launch plan", createdBy: userId, now, }); expect(storage.channels.rename({ id: channelId, title: "launch PLAN", now, })).rejects.toBeInstanceOf(StorageError); let recased = await storage.channels.rename({ id: other.id, title: "LAUNCH PLAN", now, }); expect(recased.channel.title).toBe("LAUNCH PLAN"); } finally { await storage.close(); } }); it("keeps document titles unique within a repository without regard to case", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); await storage.channels.create({ id: id("channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Release notes", createdBy: userId, now, }); expect(storage.channels.create({ id: id("channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "release NOTES", createdBy: userId, now, })).rejects.toBeInstanceOf(StorageError); } finally { await storage.close(); } }); it("resolves canonical and historical document slugs without rebinding aliases", async () => { let storage = await opened(factory); try { let { userId, channelId, repositoryId } = await userAndChannel(storage); let original = await storage.channels.get(channelId); expect(original?.slug).toBe("release-plan"); expect((await storage.channels.resolve(repositoryId, "release-plan"))?.id) .toBe(channelId); let renamed = await storage.channels.rename({ id: channelId, title: "Résumé 計画", now: new Date("2026-01-03T03:04:05.000Z"), }); expect(renamed.channel.slug).toBe("résumé-計画"); expect((await storage.channels.resolve(repositoryId, "release-plan"))?.id) .toBe(channelId); expect((await storage.channels.resolve(repositoryId, "résumé-計画"))?.id) .toBe(channelId); let restored = await storage.channels.rename({ id: channelId, title: "Release plan", now: new Date("2026-01-04T03:04:05.000Z"), }); expect(restored.channel.slug).toBe("release-plan"); await storage.channels.rename({ id: channelId, title: "Launch plan", now: new Date("2026-01-05T03:04:05.000Z"), }); let replacement = await storage.channels.create({ id: id("replacement-channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Release plan", createdBy: userId, now: new Date("2026-01-06T03:04:05.000Z"), }); expect(replacement.slug).toBe("release-plan-2"); expect((await storage.channels.resolve(repositoryId, "release-plan"))?.id) .toBe(channelId); expect((await storage.channels.resolve(repositoryId, "release-plan-2"))?.id) .toBe(replacement.id); expect(await storage.channels.resolve(repositoryId, "missing")) .toBeUndefined(); } finally { await storage.close(); } }); it("atomically disambiguates different titles with the same document slug", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); let create = (channelId: string, title: string) => storage.channels.create({ id: channelId, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title, createdBy: userId, now, }); let channels = await Promise.all([ create(id("channel"), "Bright road"), create(id("channel"), "Bright-road!"), ]); expect(channels.map(channel => channel.slug).sort()).toEqual([ "bright-road", "bright-road-2", ]); } finally { await storage.close(); } }); it("atomically reserves one title for concurrent creators", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); let create = (id: string) => storage.channels.create({ id, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "bright-road", createdBy: userId, now, }); let results = await Promise.allSettled([create(id("channel")), create(id("channel"))]); expect(results.filter(result => result.status === "fulfilled")).toHaveLength(1); expect(results.filter(result => result.status === "rejected")).toHaveLength(1); } finally { await storage.close(); } }); it("paginates a case-insensitive title search independently from the full list", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); for (let title of ["Draft map", "Draft release", "Roadmap"]) { await storage.channels.create({ id: id("channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title, createdBy: userId, now, }); } let first = await storage.channels.list(repositoryId, 1, undefined, "DRAFT"); expect(first.channels.map(channel => channel.title)).toHaveLength(1); expect(first.channels[0]!.title).toMatch(/^Draft/); let second = await storage.channels.list(repositoryId, 1, first.next, "draft"); expect(second.channels.map(channel => channel.title)).toHaveLength(1); expect(second.channels[0]!.title).toMatch(/^Draft/); expect(second.next).toBeUndefined(); } finally { await storage.close(); } }); it("treats title-search punctuation as literal text", async () => { let storage = await opened(factory); try { let { userId, repositoryId } = await userAndChannel(storage); let now = new Date("2026-01-03T03:04:05.000Z"); for (let title of ["Budget 100%_\\plan", "Budget 100abcplan"]) { await storage.channels.create({ id: id("channel"), repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title, createdBy: userId, now, }); } let page = await storage.channels.list(repositoryId, 20, undefined, "100%_\\plan"); expect(page.channels.map(channel => channel.title)).toEqual(["Budget 100%_\\plan"]); } finally { await storage.close(); } }); it("scans repository channels through updates without changing its creation cursor", async () => { let storage = await opened(factory); try { let { userId, channelId, repositoryId, lease } = await userAndChannel(storage); let second = id("channel"); let third = id("channel"); await storage.channels.create({ id: second, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Second plan", createdBy: userId, now: new Date("2026-01-03T03:04:05.000Z"), }); await storage.channels.create({ id: third, repositoryId, repositoryOwner: "octo-org", repositoryName: "score", title: "Third plan", createdBy: userId, now: new Date("2026-01-04T03:04:05.000Z"), }); let first = await storage.channels.scan(repositoryId, 2); await storage.collaboration.commit({ channelId, lease, expectedRevision: 0, operationId: id("operation"), epoch: "epoch-1", events: [], now: new Date("2026-01-05T03:04:05.000Z"), }); let next = await storage.channels.scan(repositoryId, 2, first.next); expect(first.channels.map(channel => channel.id)).toEqual([third, second]); expect(next.channels.map(channel => channel.id)).toEqual([channelId]); expect(next.next).toBeUndefined(); } finally { await storage.close(); } }); it("publishes an initialized channel and checkpoint atomically", async () => { let storage = await opened(factory); try { let now = new Date("2026-01-02T03:04:05.000Z"); let userId = id("user"); let channelId = id("channel"); let sidecar = { version: 1, revision: 0, origin: { idempotencyKey: "create-plan-1", fingerprint: "request-1" }, }; await storage.users.put({ id: userId, login: "octocat", avatarUrl: "https://example.test/a", now, }); await storage.channels.create({ id: channelId, repositoryId: id("repository"), repositoryOwner: "octo-org", repositoryName: "score", title: "Created plan", createdBy: userId, now, initial: { generation: id("generation"), epoch: "epoch-created", source: "# Created\n", sourceHash: "sha256:created", document: new Uint8Array([1, 2, 3]), sidecar, }, }); let stored = await storage.collaboration.load(channelId, now); expect(stored).toBeDefined(); expect(stored!.latestSequence).toBe(0); expect(stored!.sidecar).toEqual(sidecar); expect(stored!.snapshot).toMatchObject({ channelId, revision: 0, throughSequence: 0, epoch: "epoch-created", source: "# Created\n", createdAt: now, }); expect([...stored!.snapshot!.document]).toEqual([1, 2, 3]); } finally { await storage.close(); } }); it("assigns the first active session as the agent owner", async () => { let storage = await opened(factory); try { let { userId, sessionId, channelId } = await userAndChannel(storage); let second = id("session"); let now = new Date("2026-01-03T03:04:05.000Z"); await storage.sessions.create({ id: second, userId, expiresAt: new Date("2026-02-03T03:04:05.000Z"), createdAt: now, }); let first = await storage.channels.claimAgentOwner(channelId, sessionId, now); expect(first.ownerSessionId).toBe(sessionId); expect(await storage.channels.readAgent(channelId, now)).toMatchObject({ channel: { id: channelId }, agent: { ownerSessionId: sessionId, generation: first.generation }, }); expect(await storage.channels.readAgent(id("missing-channel"), now)).toBeUndefined(); expect((await storage.channels.claimAgentOwner(channelId, second, now)).ownerSessionId) .toBe(sessionId); expect(await storage.channels.clearAgentOwner(channelId, second, first.generation, now)) .toBe(false); expect( await storage.channels.clearAgentOwner(channelId, sessionId, first.generation, now), ).toBe(true); let reclaimed = await storage.channels.claimAgentOwner(channelId, sessionId, now); expect(reclaimed.generation).toBeGreaterThan(first.generation); expect( await storage.channels.clearAgentOwner(channelId, sessionId, first.generation, now), ).toBe(false); expect( await storage.channels.clearAgentOwner( channelId, sessionId, reclaimed.generation, now, ), ).toBe(true); expect((await storage.channels.claimAgentOwner(channelId, second, now)).ownerSessionId) .toBe(second); } finally { await storage.close(); } }); it("revokes agent ownership when its login session expires", async () => { let storage = await opened(factory); try { let { sessionId, channelId } = await userAndChannel(storage); let claimed = await storage.channels.claimAgentOwner( channelId, sessionId, new Date("2026-01-03T03:04:05.000Z"), ); await storage.sessions.deleteExpired(new Date("2026-03-03T03:04:05.000Z")); let stored = await storage.collaboration.load( channelId, new Date("2026-03-03T03:04:05.000Z"), ); expect(stored!.agent!.ownerSessionId).toBeUndefined(); expect(stored!.agent!.status).toBe("unavailable"); try { await storage.channels.updateAgentContext({ channelId, ownerSessionId: sessionId, generation: claimed.generation, summary: "stale", transcriptCursor: 1, status: "ready", now: new Date("2026-03-03T03:04:05.000Z"), }); expect.unreachable(); } catch (err) { expect(err).toBeInstanceOf(StorageError); expect((err as StorageError).failure).toBe("conflict"); } } finally { await storage.close(); } }); it("commits updates, sidecar state and events once", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); let operationId = id("operation"); let sidecar: JsonValue = { questions: [{ id: "question-1", status: "open" }] }; let input = { channelId, lease, expectedRevision: 0, operationId, epoch: "epoch-1", update: new Uint8Array([99, 100]), sidecar, events: [{ id: id("event"), kind: "chat:message", payload: { text: "hello" }, createdAt: new Date("2026-01-04T03:04:05.000Z"), }], now: new Date("2026-01-04T03:04:05.000Z"), }; let committed = await storage.collaboration.commit(input); expect(committed).toEqual({ revision: 1, sequence: 1, repeated: false }); expect(await storage.collaboration.commit(input)).toEqual({ revision: 1, sequence: 1, repeated: true, }); let stored = await storage.collaboration.load( channelId, new Date("2026-01-04T03:04:05.000Z"), ); expect(stored!.channel.revision).toBe(1); expect([...stored!.updates[0]!.update]).toEqual([99, 100]); expect(stored!.events).toHaveLength(1); expect(stored!.sidecar).toEqual(sidecar); try { await storage.collaboration.commit({ ...input, expectedRevision: 1, operationId: id("duplicate-event"), }); expect.unreachable(); } catch (err) { expect(err).toBeInstanceOf(StorageError); expect((err as StorageError).failure).toBe("conflict"); } try { await storage.collaboration.commit({ ...input, operationId: id("stale") }); expect.unreachable(); } catch (err) { expect(err).toBeInstanceOf(StorageError); expect((err as StorageError).failure).toBe("conflict"); } } finally { await storage.close(); } }); it("replays only updates newer than the checkpoint", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); await storage.collaboration.commit({ channelId, lease, expectedRevision: 0, operationId: id("operation"), epoch: "epoch-1", update: new Uint8Array([1]), events: [], now: new Date("2026-01-04T03:04:05.000Z"), }); let checkpoint = { channelId, lease, expectedRevision: 1, generation: id("generation"), revision: 1, throughSequence: 1, epoch: "epoch-1", source: "# Plan\n", sourceHash: "hash-1", document: new Uint8Array([2, 3]), sidecar: { questions: [] }, createdAt: new Date("2026-01-04T03:05:05.000Z"), }; try { await storage.collaboration.checkpoint({ ...checkpoint, throughSequence: 2 }); expect.unreachable(); } catch (err) { expect(err).toBeInstanceOf(StorageError); expect((err as StorageError).failure).toBe("conflict"); } await storage.collaboration.checkpoint(checkpoint); let stored = await storage.collaboration.load( channelId, new Date("2026-01-04T03:05:05.000Z"), ); expect(stored!.updates).toEqual([]); expect(stored!.latestSequence).toBe(1); expect([...stored!.snapshot!.document]).toEqual([2, 3]); expect(stored!.snapshot!.source).toBe("# Plan\n"); let next = await storage.collaboration.commit({ channelId, lease, expectedRevision: 1, operationId: id("operation"), epoch: "epoch-1", update: new Uint8Array([4]), events: [], now: new Date("2026-01-04T03:06:05.000Z"), }); expect(next.sequence).toBe(2); expect((await storage.collaboration.load(channelId, new Date()))!.latestSequence).toBe(2); } finally { await storage.close(); } }); it("replaces an epoch and its sidecar atomically", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); let input = { channelId, lease, expectedRevision: 0, operationId: id("replacement"), generation: id("generation"), epoch: "epoch-2", source: "# Replacement\n", sourceHash: "sha256:replacement", document: new Uint8Array([7, 8, 9]), sidecar: { version: 1, revision: 4 }, now: new Date("2026-01-05T03:04:05.000Z"), }; expect(await storage.collaboration.replace(input)).toEqual({ revision: 1, sequence: 1, repeated: false, }); let stored = await storage.collaboration.load(channelId, input.now); expect(stored!.snapshot).toMatchObject({ epoch: "epoch-2", throughSequence: 1 }); expect([...stored!.snapshot!.document]).toEqual([7, 8, 9]); expect(stored!.sidecar).toEqual(input.sidecar); expect(stored!.updates).toEqual([]); expect(await storage.collaboration.replace(input)).toMatchObject({ repeated: true }); } finally { await storage.close(); } }); it("enqueues background targets idempotently without collaboration side effects", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); let beforeChannel = await storage.channels.get(channelId); let beforeCollaboration = await storage.collaboration.load(channelId, new Date()); expect(await storage.jobs.list(channelId, 20)).toEqual({ revision: 0, jobs: [] }); await expect(storage.jobs.enqueue(backgroundJob(channelId, lease, { id: "" }))) .rejects.toMatchObject({ failure: "conflict" }); expect((await storage.jobs.list(channelId, 20))!.revision).toBe(0); let input = backgroundJob(channelId, lease, { id: id("first-job"), idempotencyKey: id("first-enqueue"), fingerprint: "sha256:first", input: { revision: 1, nested: ["source"] }, }); let first = await storage.jobs.enqueue(input); expect(first.repeated).toBe(false); expect(first.job).toMatchObject({ state: "pending", targetGeneration: 1, revision: 1, }); let repeated = await storage.jobs.enqueue({ ...input, id: id("ignored-job") }); expect(repeated).toEqual({ job: first.job, repeated: true }); expect((await storage.jobs.list(channelId, 20))!.revision).toBe(1); await expect(storage.jobs.enqueue({ ...input, id: id("conflicting-job"), fingerprint: "sha256:changed", })).rejects.toMatchObject({ failure: "conflict" }); let second = await storage.jobs.enqueue(backgroundJob(channelId, lease, { fingerprint: "sha256:second", input: { revision: 2 }, now: new Date(input.now.getTime() + 1), })); expect(second.job).toMatchObject({ targetGeneration: 2, revision: 2 }); expect((await storage.jobs.get(channelId, first.job.id))!.job.state).toBe("superseded"); let independent = await storage.jobs.enqueue(backgroundJob(channelId, lease, { targetKey: "research:question-1", type: "research-question", origin: "user", now: new Date(input.now.getTime() + 2), })); expect(independent.job).toMatchObject({ targetGeneration: 1, revision: 3 }); let page = await storage.jobs.list(channelId, 2); expect(page!.jobs.map(job => job.id)).toEqual([independent.job.id, second.job.id]); expect(page!.next).toBeDefined(); expect("input" in page!.jobs[0]!).toBe(false); let remainder = await storage.jobs.list(channelId, 2, page!.next); expect(remainder!.jobs.map(job => job.id)).toEqual([first.job.id]); let detail = await storage.jobs.get(channelId, second.job.id); (detail!.job.input as { revision: number }).revision = 99; expect((await storage.jobs.get(channelId, second.job.id))!.job.input) .toEqual({ revision: 2 }); expect(await storage.channels.get(channelId)).toEqual(beforeChannel); expect(await storage.collaboration.load(channelId, new Date())).toEqual( beforeCollaboration, ); } finally { await storage.close(); } }); it("fences claims and publishes a background artifact atomically", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); let now = new Date(); let queued = (await storage.jobs.enqueue(backgroundJob(channelId, lease, { now }))).job; let [claimed] = await storage.jobs.claim({ channelId, claimOwner: "worker-1", count: 1, ttlMs: 10_000, now: new Date(now.getTime() + 1), lease, }); expect(claimed).toMatchObject({ id: queued.id, state: "running", attempts: 1, claimGeneration: 1, revision: 2, }); let renewed = await storage.jobs.renew({ channelId, jobId: claimed!.id, claimOwner: "worker-1", claimGeneration: claimed!.claimGeneration, expectedRevision: claimed!.revision, claimBinding: { ownerSessionId: "session-1", ownerGeneration: 3 }, ttlMs: 10_000, now: new Date(now.getTime() + 2), lease, }); expect(renewed).toMatchObject({ revision: 3, claimBinding: { ownerSessionId: "session-1" }, }); await expect(storage.jobs.renew({ channelId, jobId: claimed!.id, claimOwner: "worker-1", claimGeneration: claimed!.claimGeneration, expectedRevision: claimed!.revision, claimBinding: { ownerSessionId: "stale-session" }, ttlMs: 20_000, now: new Date(now.getTime() + 3), lease, })).rejects.toMatchObject({ failure: "conflict" }); await expect(storage.jobs.settle({ channelId, jobId: claimed!.id, claimOwner: "worker-1", claimGeneration: claimed!.claimGeneration + 1, artifact: { abstract: "wrong" }, now: new Date(now.getTime() + 3), lease, })).rejects.toMatchObject({ failure: "conflict" }); expect((await storage.jobs.get(channelId, claimed!.id))!.artifact).toBeUndefined(); let requeued = await storage.jobs.requeue({ channelId, jobId: claimed!.id, claimOwner: "worker-1", claimGeneration: claimed!.claimGeneration, availableAt: new Date(now.getTime() + 4), reason: "retry", countFailure: true, now: new Date(now.getTime() + 3), lease, }); expect(requeued).toMatchObject({ state: "pending", revision: 4, reason: "retry", failures: 1, }); let [secondClaim] = await storage.jobs.claim({ channelId, claimOwner: "worker-2", count: 1, ttlMs: 10_000, now: new Date(now.getTime() + 5), lease, }); expect(secondClaim).toMatchObject({ attempts: 2, claimGeneration: 2, revision: 5 }); let paused = await storage.jobs.pause({ channelId, jobId: secondClaim!.id, expectedRevision: secondClaim!.revision, reason: "owner-unavailable", now: new Date(now.getTime() + 6), lease, }); expect(paused).toMatchObject({ state: "paused", revision: 6 }); await expect(storage.jobs.fail({ channelId, jobId: secondClaim!.id, claimOwner: "worker-2", claimGeneration: secondClaim!.claimGeneration, reason: "late", now: new Date(now.getTime() + 7), lease, })).rejects.toMatchObject({ failure: "conflict" }); let resumed = await storage.jobs.resume({ channelId, jobId: paused.id, expectedRevision: paused.revision, availableAt: new Date(now.getTime() + 8), now: new Date(now.getTime() + 8), lease, }); expect(resumed).toMatchObject({ state: "pending", revision: 7 }); let [finalClaim] = await storage.jobs.claim({ channelId, claimOwner: "worker-3", count: 1, ttlMs: 10_000, now: new Date(now.getTime() + 9), lease, }); let completed = await storage.jobs.settle({ channelId, jobId: finalClaim!.id, claimOwner: "worker-3", claimGeneration: finalClaim!.claimGeneration, artifact: { abstract: "Current summary", sources: [] }, now: new Date(now.getTime() + 10), lease, }); expect(completed.job).toMatchObject({ state: "completed", revision: 9 }); expect(completed.artifact).toMatchObject({ revision: 9, value: { abstract: "Current summary", sources: [] }, }); (completed.artifact!.value as { abstract: string }).abstract = "mutated"; expect((await storage.jobs.get(channelId, completed.job.id))!.artifact!.value) .toMatchObject({ abstract: "Current summary" }); await expect(storage.jobs.cancel({ channelId, jobId: completed.job.id, expectedRevision: completed.job.revision, now: new Date(now.getTime() + 11), lease, })).rejects.toMatchObject({ failure: "conflict" }); } finally { await storage.close(); } }); it("reclaims expired background claims and rejects a fenced writer", async () => { let storage = await opened(factory); try { let { channelId, lease } = await userAndChannel(storage); let now = new Date(); let queued = (await storage.jobs.enqueue(backgroundJob(channelId, lease, { now }))).job; let [first] = await storage.jobs.claim({ channelId, claimOwner: "old-worker", count: 1, ttlMs: 1, now, lease, }); let [replacement] = await storage.jobs.claim({ channelId, claimOwner: "new-worker", count: 1, ttlMs: 10_000, now: new Date(now.getTime() + 2), lease, }); expect(replacement).toMatchObject({ id: queued.id, attempts: 2, claimGeneration: 2, }); await expect(storage.jobs.fail({ channelId, jobId: queued.id, claimOwner: "old-worker", claimGeneration: first!.claimGeneration, reason: "stale", now: new Date(now.getTime() + 3), lease, })).rejects.toMatchObject({ failure: "conflict" }); let failed = await storage.jobs.fail({ channelId, jobId: queued.id, claimOwner: "new-worker", claimGeneration: replacement!.claimGeneration, reason: "attempts-exhausted", now: new Date(now.getTime() + 4), lease, }); expect(failed.state).toBe("failed"); expect(await storage.leases.release(lease)).toBe(true); let nextLease = await storage.leases.acquire(lease.name, "replacement", 60_000); expect(nextLease).toBeDefined(); await expect(storage.jobs.enqueue(backgroundJob(channelId, lease))) .rejects.toMatchObject({ failure: "conflict" }); } finally { await storage.close(); } }); it("fences competing lease owners", async () => { let storage = await opened(factory); try { let name = id("writer"); let first = await storage.leases.acquire(name, "one", 30_000); expect(first).toBeDefined(); expect(await storage.leases.acquire(name, "two", 30_000)).toBeUndefined(); let renewed = await storage.leases.renew(first!, 30_000); expect(renewed!.fencing).toBe(first!.fencing); expect(await storage.leases.release(renewed!)).toBe(true); let second = await storage.leases.acquire(name, "two", 30_000); expect(second!.fencing).toBeGreaterThan(first!.fencing); } finally { await storage.close(); } }); it("refuses a commit from an expired lease holder", async () => { let storage = await opened(factory); try { let { channelId } = await userAndChannel(storage); let name = id("short-writer"); let first = await storage.leases.acquire(name, "one", 5); expect(first).toBeDefined(); await Bun.sleep(20); let second = await storage.leases.acquire(name, "two", 30_000); expect(second).toBeDefined(); try { await storage.collaboration.commit({ channelId, lease: first!, expectedRevision: 0, operationId: id("stale-writer"), epoch: "epoch-1", update: new Uint8Array([1]), events: [], now: new Date(), }); expect.unreachable(); } catch (err) { expect(err).toBeInstanceOf(StorageError); expect((err as StorageError).failure).toBe("conflict"); } } finally { await storage.close(); } }); }); }