import { describe, expect, it, vi } from "vitest"; import { createIndexedDbRuntime } from "../../src/adapters/storage/indexeddb/indexeddb-runtime.ts"; import { indexedDbPhysicalDatabaseName } from "../../src/adapters/storage/indexeddb/indexeddb-governance.ts"; import type { IndexedDbObservation, IndexedDbRuntimeDependencies, } from "../../src/adapters/storage/indexeddb/indexeddb-types.ts"; import { mapIndexedDbException } from "../../src/adapters/storage/indexeddb/indexeddb-failure.ts"; import { MemoryIndexedDbFactory } from "../helpers/memory-indexeddb.ts"; type Item = Readonly<{ label: string; rank: number; }>; type Query = Readonly<{ limit: number; }>; const TEST_SCOPE = Object.freeze({ authorityToken: "authoritytoken_001", namespaceToken: "namespacetoken_001", partitionToken: "partitiontoken_001", accountScope: "ORIGIN_SHARED" as const, }); const TEST_STORAGE_POLICY = Object.freeze({ owner: "platform-storage", namespace: "indexeddb-runtime-test", classification: "INTERNAL" as const, authority: "SERVER" as const, accountScope: "ORIGIN_SHARED" as const, retention: Object.freeze({ kind: "EXPLICIT_DELETE" as const }), softBudgetBytes: 1_000_000, hardBudgetBytes: 2_000_000, evictionPriority: "SYNCED_COPY" as const, logoutAction: "KEEP_ORIGIN_SHARED" as const, accountDeletionAction: "KEEP_ORIGIN_SHARED" as const, pressureAction: "RETAIN" as const, unavailableFallback: "ONLINE_ONLY" as const, }); async function sha256Fingerprint(value: unknown): Promise { const bytes = new TextEncoder().encode(JSON.stringify(value)); const digest = await globalThis.crypto.subtle.digest( "SHA-256", bytes, ); return Array.from( new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"), ).join(""); } function dependencies( memory: MemoryIndexedDbFactory, overrides: Partial< IndexedDbRuntimeDependencies > = {}, ): IndexedDbRuntimeDependencies { return { scope: TEST_SCOPE, storagePolicy: TEST_STORAGE_POLICY, schemaVersion: 1, recordStore: "records", governanceStore: "governance", retentionStore: "retention", retentionEligibilityIndex: "by-eligibility", lifecycleMetadataStores: [], idempotencyStore: "receipts", idempotencyExpiryIndex: "by-expiry", receiptRetentionMs: 60_000, maxIdempotencyReceipts: 1_000, migrations: [ { id: "schema-v1", fromVersion: 0, toVersion: 1, operations: [ { kind: "CREATE_STORE", name: "records", keyPath: "key", indexes: [ { name: "by-rank", keyPath: "payload.rank", }, ], }, { kind: "CREATE_STORE", name: "governance", keyPath: "bindingKey", }, { kind: "CREATE_STORE", name: "retention", keyPath: "recordKey", indexes: [ { name: "by-eligibility", keyPath: "eligibleAtEpochMs", }, ], }, { kind: "CREATE_STORE", name: "receipts", keyPath: "idempotencyKey", indexes: [ { name: "by-expiry", keyPath: "expiresAtEpochMs", }, ], }, ], }, ], codec: { currentVersion: 1, encode: (value) => ({ ok: true, value }), measureStoredBytes: (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength, decode: (version, value) => version === 1 && value !== null && typeof value === "object" && typeof (value as Partial).label === "string" && typeof (value as Partial).rank === "number" ? { ok: true, value: value as Item } : { ok: false }, fingerprint: (value) => sha256Fingerprint([value.rank, value.label]), }, queryPolicy: { plan: (query) => ({ index: "by-rank", direction: "next", limit: query.limit, }), }, factory: memory.factory, nowEpochMilliseconds: () => 1_000, authorizeLifecycle: () => ({ authorized: true, proofToken: "authorityproof_001", }), ...overrides, }; } async function waitForTransaction( memory: MemoryIndexedDbFactory, ): Promise { for (let attempt = 0; attempt < 20; attempt += 1) { if (memory.lastTransaction?.mode === "readwrite") return; await new Promise((resolve) => { globalThis.setTimeout(resolve, 0); }); } throw new Error("Timed out waiting for a fake readwrite transaction."); } describe("IndexedDB runtime", () => { it("migrates, applies CAS atomically, replays idempotency keys, and pages by policy", async () => { const memory = new MemoryIndexedDbFactory(); const observations: IndexedDbObservation[] = []; const statuses: string[] = []; const runtime = createIndexedDbRuntime( dependencies(memory, { observe: (event) => observations.push(event), }), ); runtime.subscribeStatus((status) => statuses.push(status.kind)); expect(await runtime.open()).toEqual({ ok: true, value: undefined }); expect(runtime.getStatus()).toEqual({ kind: "READY", schemaVersion: 1, }); const first = { key: "record-alpha", value: { label: "alpha", rank: 2 }, expectedRevision: null, idempotencyKey: "put-alpha-v1", } as const; expect(await runtime.compareAndSwap(first)).toEqual({ ok: true, value: { key: "record-alpha", revision: 1, replayed: false, }, }); const storedFingerprint = ( memory.readRaw( "receipts", "put-alpha-v1", ) as Readonly<{ fingerprint: string }> ).fingerprint; expect(storedFingerprint).toMatch(/^[a-f0-9]{64}$/u); expect(storedFingerprint).not.toContain("alpha"); expect(await runtime.compareAndSwap(first)).toEqual({ ok: true, value: { key: "record-alpha", revision: 1, replayed: true, }, }); expect( await runtime.compareAndSwap({ ...first, value: { label: "changed", rank: 2 }, }), ).toMatchObject({ ok: false, error: { code: "CONFLICT" }, }); expect( await runtime.compareAndSwap({ key: "record-beta", value: { label: "beta", rank: 1 }, expectedRevision: null, idempotencyKey: "put-beta-v1", }), ).toMatchObject({ ok: true, value: { revision: 1, replayed: false }, }); expect( await runtime.compareAndSwap({ key: "record-alpha", value: { label: "alpha-v2", rank: 3 }, expectedRevision: 1, idempotencyKey: "put-alpha-v2", }), ).toMatchObject({ ok: true, value: { revision: 2, replayed: false }, }); expect( await runtime.compareAndSwap({ key: "record-alpha", value: { label: "stale", rank: 4 }, expectedRevision: 1, idempotencyKey: "stale-alpha", }), ).toMatchObject({ ok: false, error: { code: "CONFLICT" }, }); expect(await runtime.read("record-alpha")).toEqual({ ok: true, value: { value: { label: "alpha-v2", rank: 3 }, revision: 2, }, }); const firstPage = await runtime.query({ limit: 1 }); expect(firstPage).toMatchObject({ ok: true, value: { items: [{ label: "beta", rank: 1 }], }, }); expect(firstPage.ok && firstPage.value.nextCursor).not.toBeNull(); const secondPage = await runtime.query( { limit: 1 }, firstPage.ok ? firstPage.value.nextCursor : null, ); expect(secondPage).toEqual({ ok: true, value: { items: [{ label: "alpha-v2", rank: 3 }], nextCursor: null, }, }); const deletion = { key: "record-alpha", expectedRevision: 2, idempotencyKey: "delete-alpha-v2", } as const; expect(await runtime.remove(deletion)).toMatchObject({ ok: true, value: { revision: 3, replayed: false }, }); expect(await runtime.remove(deletion)).toMatchObject({ ok: true, value: { revision: 3, replayed: true }, }); expect(await runtime.read("record-alpha")).toEqual({ ok: true, value: null, }); expect(statuses).toEqual(["OPENING", "READY"]); expect(JSON.stringify(observations)).not.toMatch( /record-alpha|record-beta|alpha-v2|put-alpha/, ); expect(observations).toContainEqual({ operation: "INDEXEDDB_MIGRATE", outcome: "SUCCESS", schemaVersion: 1, countBucket: "1", }); runtime.close(); expect(runtime.getStatus()).toEqual({ kind: "DISPOSED" }); }); it("waits for transaction complete and rolls request successes back on commit failure", async () => { const memory = new MemoryIndexedDbFactory(); const runtime = createIndexedDbRuntime(dependencies(memory)); expect(await runtime.open()).toMatchObject({ ok: true }); memory.failNextWriteCommit( new DOMException("Do not expose this value.", "QuotaExceededError"), ); expect( await runtime.compareAndSwap({ key: "rollback-target", value: { label: "never-committed", rank: 1 }, expectedRevision: null, idempotencyKey: "rollback-operation", }), ).toEqual({ ok: false, error: { code: "QUOTA_EXCEEDED", operation: "INDEXEDDB_WRITE", retryable: false, recovery: "READ_ONLY", }, }); expect(await runtime.read("rollback-target")).toEqual({ ok: true, value: null, }); }); it("aborts the whole in-flight transaction and cannot commit a late request", async () => { const memory = new MemoryIndexedDbFactory(); const runtime = createIndexedDbRuntime(dependencies(memory)); expect(await runtime.open()).toMatchObject({ ok: true }); memory.pauseTransactions(); const controller = new AbortController(); const pending = runtime.compareAndSwap({ key: "abort-target", value: { label: "late", rank: 1 }, expectedRevision: null, idempotencyKey: "abort-operation", signal: controller.signal, }); await waitForTransaction(memory); controller.abort(); expect(await pending).toEqual({ ok: false, error: { code: "ABORTED", operation: "INDEXEDDB_WRITE", retryable: false, recovery: "NONE", }, }); memory.resumeTransactions(); await Promise.resolve(); expect(await runtime.read("abort-target")).toEqual({ ok: true, value: null, }); }); it("shares one native open request across concurrent callers", async () => { const memory = new MemoryIndexedDbFactory(); const nativeOpen = vi.spyOn(memory.factory, "open"); const runtime = createIndexedDbRuntime(dependencies(memory)); const results = await Promise.all([ runtime.open(), runtime.open(), runtime.open(), ]); expect(nativeOpen).toHaveBeenCalledOnce(); expect(results).toEqual([ { ok: true, value: undefined }, { ok: true, value: undefined }, { ok: true, value: undefined }, ]); }); it("isolates a caller abort from the shared native open request", async () => { const memory = new MemoryIndexedDbFactory(); memory.blockNextOpen(); const nativeOpen = vi.spyOn(memory.factory, "open"); const runtime = createIndexedDbRuntime(dependencies(memory)); const cancelledCaller = new AbortController(); const surviving = runtime.open(); const cancelled = runtime.open(cancelledCaller.signal); await Promise.resolve(); cancelledCaller.abort(); expect(await cancelled).toMatchObject({ ok: false, error: { code: "ABORTED", operation: "INDEXEDDB_OPEN" }, }); expect(nativeOpen).toHaveBeenCalledOnce(); memory.releaseBlockedOpen(); expect(await surviving).toEqual({ ok: true, value: undefined }); expect(runtime.getStatus()).toEqual({ kind: "READY", schemaVersion: 1 }); }); it("times out a blocked upgrade, then closes a late successful connection", async () => { const memory = new MemoryIndexedDbFactory(); memory.blockNextOpen(); let timeout: (() => void) | undefined; const runtime = createIndexedDbRuntime( dependencies(memory, { blockedTimeoutMs: 25, scheduler: { setTimeout: (callback) => { timeout = callback; return "blocked-timer"; }, clearTimeout: () => undefined, }, }), ); const pending = runtime.open(); await Promise.resolve(); expect(runtime.getStatus()).toEqual({ kind: "BLOCKED", currentVersion: 0, targetVersion: 1, }); timeout?.(); expect(await pending).toMatchObject({ ok: false, error: { code: "BLOCKED", recovery: "RELOAD_OTHER_CONTEXTS", }, }); memory.releaseBlockedOpen(); await Promise.resolve(); expect(memory.isConnectionClosed()).toBe(true); expect(runtime.getStatus()).toEqual({ kind: "CLOSED", reason: "NOT_OPENED", }); }); it("retains native open ownership after a blocked timeout until late success", async () => { const memory = new MemoryIndexedDbFactory(); memory.blockNextOpen(); const nativeOpen = vi.spyOn(memory.factory, "open"); let timeout: (() => void) | undefined; const runtime = createIndexedDbRuntime( dependencies(memory, { blockedTimeoutMs: 25, scheduler: { setTimeout: (callback) => { timeout = callback; return "blocked-timer"; }, clearTimeout: () => undefined, }, }), ); const first = runtime.open(); await Promise.resolve(); timeout?.(); await expect(first).resolves.toMatchObject({ ok: false, error: { code: "BLOCKED" }, }); const second = runtime.open(); await Promise.resolve(); const nativeOpenCountBeforeLateSuccess = nativeOpen.mock.calls.length; memory.releaseBlockedOpen(); await expect(second).resolves.toMatchObject({ ok: false, error: { code: "BLOCKED" }, }); await Promise.resolve(); expect(nativeOpenCountBeforeLateSuccess).toBe(1); expect(memory.isConnectionClosed()).toBe(true); expect(runtime.getStatus()).toEqual({ kind: "CLOSED", reason: "NOT_OPENED", }); }); it("closes immediately on versionchange and isolates listener failures", async () => { const memory = new MemoryIndexedDbFactory(); const onVersionChange = vi.fn(() => { throw new Error("listener failure"); }); const runtime = createIndexedDbRuntime( dependencies(memory, { onVersionChange }), ); runtime.subscribeStatus(() => { throw new Error("subscriber failure"); }); expect(await runtime.open()).toMatchObject({ ok: true }); memory.triggerVersionChange(2); expect(runtime.getStatus()).toEqual({ kind: "CLOSED", reason: "VERSION_CHANGE", }); expect(memory.isConnectionClosed()).toBe(true); expect(onVersionChange).toHaveBeenCalledOnce(); expect(await runtime.read("after-versionchange")).toMatchObject({ ok: false, error: { code: "UNAVAILABLE", recovery: "REOPEN" }, }); }); it("uses a closed, cross-realm-safe DOMException mapping", () => { expect( mapIndexedDbException( { name: "QuotaExceededError", message: "private-record-key" }, "INDEXEDDB_WRITE", ), ).toEqual({ ok: false, error: { code: "QUOTA_EXCEEDED", operation: "INDEXEDDB_WRITE", retryable: false, recovery: "READ_ONLY", }, }); expect( JSON.stringify( mapIndexedDbException( { name: "SomethingNew", message: "private-record-key" }, "INDEXEDDB_READ", ), ), ).not.toContain("private-record-key"); }); it("replays only inside the configured receipt window and expires safely", async () => { const memory = new MemoryIndexedDbFactory(); let currentTime = 1_000; const runtime = createIndexedDbRuntime( dependencies(memory, { receiptRetentionMs: 100, nowEpochMilliseconds: () => currentTime, }), ); expect(await runtime.open()).toMatchObject({ ok: true }); const operation = { key: "retention-record", value: { label: "retained", rank: 1 }, expectedRevision: null, idempotencyKey: "retention-operation", } as const; expect(await runtime.compareAndSwap(operation)).toMatchObject({ ok: true, value: { replayed: false }, }); expect( memory.readRaw("receipts", "retention-operation"), ).toMatchObject({ expiresAtEpochMs: 1_100 }); currentTime = 1_099; expect(await runtime.compareAndSwap(operation)).toMatchObject({ ok: true, value: { replayed: true }, }); currentTime = 1_100; expect(await runtime.compareAndSwap(operation)).toMatchObject({ ok: false, error: { code: "CONFLICT" }, }); expect( memory.readRaw("receipts", "retention-operation"), ).toBeDefined(); expect(await runtime.read("retention-record")).toMatchObject({ ok: true, value: { revision: 1 }, }); }); it("rejects non-digest fingerprints before storing PII in a receipt", async () => { const memory = new MemoryIndexedDbFactory(); const base = dependencies(memory); const runtime = createIndexedDbRuntime({ ...base, codec: { ...base.codec, fingerprint: () => "private-user-label", }, }); expect(await runtime.open()).toMatchObject({ ok: true }); expect( await runtime.compareAndSwap({ key: "private-key", value: { label: "private-user-label", rank: 1 }, expectedRevision: null, idempotencyKey: "private-operation", }), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); expect( memory.readRaw("receipts", "private-operation"), ).toBeUndefined(); }); it("rejects adversarial cursor/range keys before native conversion", async () => { const memory = new MemoryIndexedDbFactory(); let rangeKey: unknown = "safe"; const runtime = createIndexedDbRuntime( dependencies(memory, { queryPolicy: { plan: () => ({ limit: 1, range: { kind: "ONLY", value: rangeKey, } as never, }), }, }), ); expect(await runtime.open()).toMatchObject({ ok: true }); rangeKey = new ArrayBuffer(4_097); expect(await runtime.query({ limit: 1 })).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); rangeKey = Array.from({ length: 65 }, (_, index) => index); expect(await runtime.query({ limit: 1 })).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); let nested: unknown = "leaf"; for (let depth = 0; depth < 10; depth += 1) { nested = [nested]; } expect( await runtime.query( { limit: 1 }, { indexKey: nested, primaryKey: "primary", } as never, ), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); }); it("rejects destructive DDL in the additive-only migration planner", () => { const memory = new MemoryIndexedDbFactory(); const base = dependencies(memory); expect(() => createIndexedDbRuntime({ ...base, migrations: [ { id: "destructive-v1", fromVersion: 0, toVersion: 1, operations: [ { kind: "DELETE_STORE", name: "records", } as never, ], }, ], }), ).toThrowError( expect.objectContaining({ name: "InvalidStateError" }), ); }); it("fails closed when the migrated schema omits a required runtime store", async () => { const memory = new MemoryIndexedDbFactory(); const base = dependencies(memory); const runtime = createIndexedDbRuntime({ ...base, migrations: [ { id: "incomplete-v1", fromVersion: 0, toVersion: 1, operations: [ { kind: "CREATE_STORE", name: "records", keyPath: "key", }, ], }, ], }); expect(await runtime.open()).toEqual({ ok: false, error: { code: "MIGRATION_FAILED", operation: "INDEXEDDB_MIGRATE", retryable: false, recovery: "READ_ONLY", }, }); expect(memory.isConnectionClosed()).toBe(true); }); it("derives physical identity only from opaque scope and rejects an override", () => { const memory = new MemoryIndexedDbFactory(); const physicalName = indexedDbPhysicalDatabaseName(TEST_SCOPE); expect(physicalName).toBe( "ca-idb-v1:authoritytoken_001.namespacetoken_001.partitiontoken_001", ); expect(physicalName).not.toContain( TEST_STORAGE_POLICY.namespace, ); expect(() => createIndexedDbRuntime( dependencies(memory, { databaseNameAssertion: "customer-account-42", }), ), ).toThrow(TypeError); }); it("fails closed when the same physical scope is rebound to another policy", async () => { const memory = new MemoryIndexedDbFactory(); const first = createIndexedDbRuntime(dependencies(memory)); expect(await first.open()).toMatchObject({ ok: true }); first.close(); const rebound = createIndexedDbRuntime( dependencies(memory, { storagePolicy: { ...TEST_STORAGE_POLICY, namespace: "different-dataset-policy", }, }), ); expect(await rebound.open()).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED", operation: "INDEXEDDB_OPEN", }, }); }); it("snapshots governance and enforces TTL logically before bounded deletion", async () => { const memory = new MemoryIndexedDbFactory(); let epochMs = 1_000; const authorizeLifecycle = vi.fn((_request: unknown) => ({ authorized: true as const, proofToken: "ttl_authority_proof_001", })); const mutableScope = structuredClone(TEST_SCOPE); const mutablePolicy = { ...structuredClone(TEST_STORAGE_POLICY), retention: { kind: "TTL" as const, maxAgeMs: 10 }, }; const mutablePlan = { index: "by-rank", direction: "next" as const, limit: 10, }; const observations: IndexedDbObservation[] = []; const mutableConfiguration = dependencies(memory, { scope: mutableScope, storagePolicy: mutablePolicy, keyRange: memory.keyRange, nowEpochMilliseconds: () => epochMs, nowMonotonicMilliseconds: () => 0, authorizeLifecycle, observe: (event) => observations.push(event), queryPolicy: { plan: () => mutablePlan }, }); const runtime = createIndexedDbRuntime(mutableConfiguration); ( mutableScope as unknown as { authorityToken: string } ).authorityToken = "mutated_business_id"; ( mutablePolicy as unknown as { namespace: string; retention: { maxAgeMs: number }; } ).namespace = "mutated-policy"; ( mutablePolicy as unknown as { retention: { maxAgeMs: number }; } ).retention.maxAgeMs = 100_000; ( mutableConfiguration as unknown as { recordStore: string; schemaVersion: number; maxIdempotencyReceipts: number; } ).recordStore = "mutated-record-store"; ( mutableConfiguration as unknown as { schemaVersion: number; } ).schemaVersion = 99; ( mutableConfiguration as unknown as { maxIdempotencyReceipts: number; } ).maxIdempotencyReceipts = 0; ( mutableConfiguration.codec as unknown as { measureStoredBytes: () => number; } ).measureStoredBytes = () => Number.NaN; expect(await runtime.open()).toMatchObject({ ok: true }); expect( await runtime.compareAndSwap({ key: "ttl-record", value: { label: "expires", rank: 1 }, expectedRevision: null, idempotencyKey: "ttl-create", }), ).toMatchObject({ ok: true }); const pendingQuery = runtime.query({ limit: 10 }); mutablePlan.limit = 0; ( mutablePlan as unknown as { index: string } ).index = "mutated-index"; expect(await pendingQuery).toMatchObject({ ok: true, value: { items: [{ label: "expires", rank: 1 }] }, }); mutablePlan.limit = 10; mutablePlan.index = "by-rank"; epochMs = 1_011; expect(await runtime.read("ttl-record")).toMatchObject({ ok: false, error: { code: "EXPIRED_RESOURCE" }, }); expect(await runtime.query({ limit: 10 })).toEqual({ ok: true, value: { items: [], nextCursor: null }, }); expect( await runtime.enforceLifecycleBatch({ action: "RETENTION_SWEEP", maxRows: 10, maxDurationMs: 1_000, }), ).toMatchObject({ ok: true, value: { state: "COMPLETE", deletedRows: 1 }, }); expect(authorizeLifecycle).toHaveBeenCalledOnce(); expect(authorizeLifecycle.mock.calls[0]?.[0]).toMatchObject({ scope: TEST_SCOPE, storagePolicy: { namespace: TEST_STORAGE_POLICY.namespace, retention: { kind: "TTL", maxAgeMs: 10 }, }, }); expect( JSON.stringify([ memory.readRaw("governance", "dataset-binding"), memory.readRaw("governance", "dataset-budget"), memory.readRaw("receipts", "ttl-create"), observations, ]), ).not.toContain("ttl_authority_proof_001"); }); it("requires lifecycle authority and only deletes sync-confirmed rows", async () => { const memory = new MemoryIndexedDbFactory(); const authorizeLifecycle = vi .fn() .mockReturnValueOnce({ authorized: false }) .mockReturnValue({ authorized: true, proofToken: "sync_authority_proof_001", }); const runtime = createIndexedDbRuntime( dependencies(memory, { scope: { ...TEST_SCOPE, accountScope: "OPAQUE_PARTITION", }, storagePolicy: { ...TEST_STORAGE_POLICY, accountScope: "OPAQUE_PARTITION", retention: { kind: "UNTIL_SYNCED" }, logoutAction: "EXPORT_THEN_PURGE", accountDeletionAction: "PURGE_PARTITION", }, keyRange: memory.keyRange as NonNullable< IndexedDbRuntimeDependencies["keyRange"] >, authorizeLifecycle, nowMonotonicMilliseconds: () => 0, }), ); expect(await runtime.open()).toMatchObject({ ok: true }); expect( await runtime.compareAndSwap({ key: "sync-record", value: { label: "pending", rank: 1 }, expectedRevision: null, idempotencyKey: "sync-pending", synchronization: "PENDING", }), ).toMatchObject({ ok: true }); expect( await runtime.enforceLifecycleBatch({ action: "RETENTION_SWEEP", maxRows: 10, maxDurationMs: 1_000, }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(memory.readRaw("records", "sync-record")).toBeDefined(); expect( await runtime.compareAndSwap({ key: "sync-record", value: { label: "confirmed", rank: 1 }, expectedRevision: 1, idempotencyKey: "sync-confirmed", synchronization: "CONFIRMED", }), ).toMatchObject({ ok: true }); expect( await runtime.enforceLifecycleBatch({ action: "RETENTION_SWEEP", maxRows: 10, maxDurationMs: 1_000, }), ).toMatchObject({ ok: true, value: { deletedRows: 1 }, }); }); it("purges records, orphan metadata, receipts, and counters in bounded lifecycle batches", async () => { const memory = new MemoryIndexedDbFactory(); const base = dependencies(memory); const authorizeLifecycle = vi.fn(() => ({ authorized: true as const, proofToken: "session_authority_proof_001", })); const runtime = createIndexedDbRuntime({ ...base, storagePolicy: { ...TEST_STORAGE_POLICY, retention: { kind: "SESSION" }, }, lifecycleMetadataStores: ["lifecycle-metadata"], migrations: [ { ...base.migrations[0]!, operations: [ ...base.migrations[0]!.operations, { kind: "CREATE_STORE", name: "lifecycle-metadata", keyPath: "metadataKey", }, ], }, ], authorizeLifecycle, nowMonotonicMilliseconds: () => 0, }); expect(await runtime.open()).toMatchObject({ ok: true }); memory.seed("lifecycle-metadata", { metadataKey: "migration-checkpoint", lastRecordKey: "session-record", }); expect( await runtime.compareAndSwap({ key: "session-record", value: { label: "session", rank: 1 }, expectedRevision: null, idempotencyKey: "session-create", }), ).toMatchObject({ ok: true }); const receipts = []; for (let batch = 0; batch < 5; batch += 1) { const receipt = await runtime.enforceLifecycleBatch({ action: "SESSION_END", maxRows: 1, maxDurationMs: 1_000, }); receipts.push(receipt); if (receipt.ok && receipt.value.state === "COMPLETE") break; } expect(receipts).toHaveLength(3); expect(receipts.at(-1)).toMatchObject({ ok: true, value: { state: "COMPLETE", deletedRows: 1 }, }); expect(authorizeLifecycle).toHaveBeenCalledTimes(3); expect(memory.readRaw("records", "session-record")).toBeUndefined(); expect(memory.readRaw("retention", "session-record")).toBeUndefined(); expect(memory.readRaw("receipts", "session-create")).toBeUndefined(); expect( memory.readRaw( "lifecycle-metadata", "migration-checkpoint", ), ).toBeUndefined(); expect( memory.readRaw("governance", "dataset-budget"), ).toMatchObject({ usedBytes: 0, receiptCount: 0 }); }); it("rejects invalid measurements, byte overage, and receipt-count overage atomically", async () => { const invalidMemory = new MemoryIndexedDbFactory(); const invalidBase = dependencies(invalidMemory); const invalid = createIndexedDbRuntime({ ...invalidBase, codec: { ...invalidBase.codec, measureStoredBytes: () => Number.NaN, }, }); expect(await invalid.open()).toMatchObject({ ok: true }); expect( await invalid.compareAndSwap({ key: "invalid-measure", value: { label: "invalid", rank: 1 }, expectedRevision: null, idempotencyKey: "invalid-measure-create", }), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); const boundedMemory = new MemoryIndexedDbFactory(); const boundedBase = dependencies(boundedMemory); const bounded = createIndexedDbRuntime({ ...boundedBase, maxIdempotencyReceipts: 1, storagePolicy: { ...TEST_STORAGE_POLICY, softBudgetBytes: 500, hardBudgetBytes: 620, }, codec: { ...boundedBase.codec, measureStoredBytes: () => 10, }, }); expect(await bounded.open()).toMatchObject({ ok: true }); expect( await bounded.compareAndSwap({ key: "a", value: { label: "fits", rank: 1 }, expectedRevision: null, idempotencyKey: "first-receipt", }), ).toMatchObject({ ok: true }); expect( await bounded.compareAndSwap({ key: "b", value: { label: "receipt-cap", rank: 2 }, expectedRevision: null, idempotencyKey: "second-receipt", }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(boundedMemory.readRaw("records", "b")).toBeUndefined(); const hardMemory = new MemoryIndexedDbFactory(); const hardBase = dependencies(hardMemory); const hardBounded = createIndexedDbRuntime({ ...hardBase, storagePolicy: { ...TEST_STORAGE_POLICY, softBudgetBytes: 500, hardBudgetBytes: 520, }, codec: { ...hardBase.codec, measureStoredBytes: () => 10, }, }); expect(await hardBounded.open()).toMatchObject({ ok: true }); expect( await hardBounded.compareAndSwap({ key: "x", value: { label: "too-large", rank: 3 }, expectedRevision: null, idempotencyKey: "hard-limit-receipt", }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(hardMemory.readRaw("records", "x")).toBeUndefined(); }); });