import { describe, expect, it, vi } from "vitest"; import type { OpfsJournalPage, OpfsJournalPort, OpfsJournalTransaction, OpfsPreparedObject, OpfsStorageScope, } from "../../src/application/ports/browser-file-storage/opfs-ports.ts"; import type { BrowserDataResult, BrowserStoragePolicy, ByteSource, } from "../../src/application/ports/browser-file-storage/shared.ts"; import { createOpfsByteStoreAdapter, type OpfsMaintenanceAuthorityConsumer, type OpfsMaintenanceAuthorityProvider, } from "../../src/adapters/storage/opfs/opfs-byte-store-adapter.ts"; import { resolveOpfsRuntimePolicy, } from "../../src/adapters/storage/opfs/opfs-policy.ts"; import type { OpfsWorkerGateway, PreparePhysicalObjectRequest, } from "../../src/adapters/storage/opfs/opfs-worker-protocol.ts"; import { writeWithSyncAccessHandle, } from "../../src/adapters/storage/opfs/opfs-worker-runtime.ts"; import { browserDataFailure, browserDataSuccess, } from "../../src/adapters/browser-file-storage/result.ts"; const storagePolicy: BrowserStoragePolicy = Object.freeze({ owner: "test-owner", namespace: "durable-objects", classification: "PERSONAL", authority: "LOCAL_FIRST", accountScope: "OPAQUE_PARTITION", retention: Object.freeze({ kind: "EXPLICIT_DELETE" }), softBudgetBytes: 1024 * 1024, hardBudgetBytes: 2 * 1024 * 1024, evictionPriority: "USER_AUTHORED", logoutAction: "EXPORT_THEN_PURGE", accountDeletionAction: "PURGE_PARTITION", pressureAction: "RETAIN", unavailableFallback: "EXPORT_REQUIRED", }); const scope: OpfsStorageScope = Object.freeze({ namespace: "durable-objects", authorityToken: "authority_12345678", namespaceToken: "namespace_12345678", partitionToken: "partition_12345678", }); function sourceFrom(bytes: Uint8Array): ByteSource { return { byteLength: bytes.byteLength, async *stream(signal: AbortSignal) { if (signal.aborted) { yield browserDataFailure("ABORTED", "OBJECT_READ"); return; } yield browserDataSuccess(Uint8Array.from(bytes)); }, }; } function preparedFrom( request: PreparePhysicalObjectRequest, ): OpfsPreparedObject { return Object.freeze({ physicalSchemaVersion: 1, descriptor: Object.freeze({ ...request.descriptor, integrity: Object.freeze({ algorithm: "SHA-256-TREE-V1", rootDigestHex: "a".repeat(64), chunkSizeBytes: 64 * 1024, }), }), chunks: Object.freeze([ Object.freeze({ sequence: 0, byteLength: request.descriptor.byteLength, digestHex: "b".repeat(64), }), ]), }); } function committedObject( objectId: string, objectPolicy: BrowserStoragePolicy, createdAtEpochMs: number, byteLength = 1, ): OpfsPreparedObject { return { physicalSchemaVersion: 1, descriptor: { objectId, scope, generation: 1, byteLength, mediaType: "application/octet-stream", createdAtEpochMs, integrity: { algorithm: "SHA-256-TREE-V1", rootDigestHex: "a".repeat(64), chunkSizeBytes: 64 * 1024, }, storagePolicy: objectPolicy, }, chunks: [ { sequence: 0, byteLength, digestHex: "b".repeat(64), }, ], }; } function createJournal(): OpfsJournalPort & { readonly transactions: Map; readonly objects: Map; } { const transactions = new Map(); const objects = new Map(); return { transactions, objects, async getCommittedObject(_scope, objectId) { return browserDataSuccess(objects.get(objectId) ?? null); }, async begin(input) { const current = objects.get(input.objectId); if ( (input.expectedGeneration === null && current) || (input.expectedGeneration !== null && current?.descriptor.generation !== input.expectedGeneration) ) { return browserDataFailure("CONFLICT", "INDEXEDDB_WRITE"); } const transaction: OpfsJournalTransaction = { ...input, fencingToken: "fencing_12345678", phase: "PREPARING", budgetReservation: { namespace: input.scope.namespace, reservedBytes: input.targetByteLength, hardBudgetBytes: input.targetStoragePolicy.hardBudgetBytes, }, }; transactions.set(input.transactionId, transaction); return browserDataSuccess(transaction); }, async markFilesReady(transactionId, fencingToken, preparedObject) { const current = transactions.get(transactionId); if (!current || current.fencingToken !== fencingToken) { return browserDataFailure("CONFLICT", "INDEXEDDB_WRITE"); } const updated: OpfsJournalTransaction = { ...current, phase: "FILES_READY", preparedObject, }; transactions.set(transactionId, updated); return browserDataSuccess(updated); }, async commitPut(transactionId, fencingToken) { const current = transactions.get(transactionId); if ( !current || current.fencingToken !== fencingToken || !current.preparedObject ) { return browserDataFailure("CONFLICT", "INDEXEDDB_WRITE"); } objects.set(current.objectId, current.preparedObject); const updated: OpfsJournalTransaction = { ...current, phase: "COMMITTED", }; transactions.set(transactionId, updated); return browserDataSuccess(updated); }, async commitDelete(transactionId, fencingToken) { const current = transactions.get(transactionId); if (!current || current.fencingToken !== fencingToken) { return browserDataFailure("CONFLICT", "INDEXEDDB_WRITE"); } objects.delete(current.objectId); const updated: OpfsJournalTransaction = { ...current, phase: "COMMITTED", }; transactions.set(transactionId, updated); return browserDataSuccess(updated); }, async complete(transactionId, fencingToken) { const current = transactions.get(transactionId); if (!current || current.fencingToken !== fencingToken) { return browserDataFailure("CONFLICT", "INDEXEDDB_WRITE"); } transactions.delete(transactionId); return browserDataSuccess(undefined); }, async rollback(transactionId) { transactions.delete(transactionId); return browserDataSuccess(undefined); }, async listIncomplete(limit) { const all = [...transactions.values()]; const page: OpfsJournalPage = { transactions: all.slice(0, limit), moreAvailable: all.length > limit, }; return browserDataSuccess(page); }, async listCommittedObjects(request) { const values = [...objects.values()].filter( (object) => object.descriptor.scope.namespaceToken === request.scope.namespaceToken && object.descriptor.scope.partitionToken === request.scope.partitionToken, ); return browserDataSuccess({ objects: values.slice(0, request.limit), nextObjectId: null, moreAvailable: values.length > request.limit, }); }, async isChunkReferenced(_scope, digestHex) { return browserDataSuccess( [...objects.values()].some((object) => object.chunks.some((chunk) => chunk.digestHex === digestHex), ), ); }, }; } function createWorker( overrides: Partial = {}, ): OpfsWorkerGateway { const physical = new Map(); const worker: OpfsWorkerGateway = { async capabilities() { return browserDataSuccess({ available: true, dedicatedWorkerRequired: true, crossContextMutationLockAvailable: true, synchronousAccessHandleAvailable: true, }); }, async preparePut(request) { let count = 0; for await (const chunk of request.source.stream( request.signal ?? new AbortController().signal, )) { if (!chunk.ok) return chunk; count += chunk.value.byteLength; } if (count !== request.descriptor.byteLength) { return browserDataFailure("INTEGRITY_FAILED", "OBJECT_WRITE"); } const prepared = preparedFrom(request); physical.set(request.descriptor.objectId, prepared); return browserDataSuccess(prepared); }, async verifyObject(preparedObject) { return browserDataSuccess( physical.has(preparedObject.descriptor.objectId), ); }, async openObject(preparedObject) { return browserDataSuccess( sourceFrom( new Uint8Array(preparedObject.descriptor.byteLength).fill(7), ), ); }, async removeObject(_scope, objectId) { physical.delete(objectId); return browserDataSuccess(undefined); }, async cleanupTransaction() { return browserDataSuccess(undefined); }, async finalizePut() { return browserDataSuccess(undefined); }, async listOrphanCandidates() { return browserDataSuccess({ safeToSweep: true, digests: [], moreAvailable: false, }); }, async deleteOrphanChunk() { return browserDataSuccess({ deleted: false, skippedUnsafe: false, }); }, close() {}, }; return Object.assign(worker, overrides); } describe("OPFS byte-store coordinator", () => { it("publishes bytes only after FILES_READY and an atomic journal commit", async () => { const journal = createJournal(); const observations: unknown[] = []; const adapter = createOpfsByteStoreAdapter({ journal, worker: createWorker(), scope, storagePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: () => "transaction_12345678", now: () => 100, observer: (event) => observations.push(event), }); const result = await adapter.objects.put({ objectId: "object_12345678", expectedGeneration: null, mediaType: "application/octet-stream", source: sourceFrom(new Uint8Array([1, 2, 3])), }); expect(result).toMatchObject({ ok: true, value: { objectId: "object_12345678", generation: 1 }, }); expect(journal.objects.get("object_12345678")).toBeDefined(); expect(journal.transactions.size).toBe(0); expect(JSON.stringify(observations)).not.toContain("object_12345678"); }); it("deep-snapshots scope and policy at composition against caller mutation", async () => { const journal = createJournal(); const mutableScope = { ...scope }; const mutableRetention = { kind: "TTL" as const, maxAgeMs: 60_000, }; const mutablePolicy = { ...storagePolicy, retention: mutableRetention, }; const adapter = createOpfsByteStoreAdapter({ journal, worker: createWorker(), scope: mutableScope, storagePolicy: mutablePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: () => "transaction_snapshot_1234", now: () => 100, }); mutableScope.namespace = "mutated-namespace"; mutableScope.partitionToken = "partition_mutated_1234"; mutablePolicy.namespace = "mutated-namespace"; mutableRetention.maxAgeMs = 1; const result = await adapter.objects.put({ objectId: "object_snapshot_1234", expectedGeneration: null, mediaType: "application/octet-stream", source: sourceFrom(new Uint8Array([1, 2, 3])), }); expect(result).toMatchObject({ ok: true, value: { scope, storagePolicy: { namespace: scope.namespace, retention: { kind: "TTL", maxAgeMs: 60_000 }, }, }, }); expect(result.ok && Object.isFrozen(result.value.scope)).toBe(true); expect( result.ok && Object.isFrozen(result.value.storagePolicy.retention), ).toBe(true); }); it("snapshots a put request at entry and dependency methods at composition", async () => { const journal = createJournal(); const originalGetCommittedObject = journal.getCommittedObject.bind(journal); let markJournalEntered!: () => void; let releaseJournal!: () => void; const journalEntered = new Promise((resolve) => { markJournalEntered = resolve; }); const journalRelease = new Promise((resolve) => { releaseJournal = resolve; }); journal.getCommittedObject = async (...args) => { markJournalEntered(); await journalRelease; return await originalGetCommittedObject(...args); }; const worker = createWorker(); const originalPreparePut = worker.preparePut.bind(worker); const preparePut = vi.fn(originalPreparePut); worker.preparePut = preparePut; const adapter = createOpfsByteStoreAdapter({ journal, worker, scope, storagePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: () => "transaction_request_snapshot", now: () => 100, }); const mutableSource = { byteLength: 3, async *stream(_signal: AbortSignal) { yield browserDataSuccess(new Uint8Array([1, 2, 3])); }, }; const mutableRequest: { objectId: string; expectedGeneration: number | null; mediaType: string; source: typeof mutableSource; } = { objectId: "object_request_snapshot", expectedGeneration: null, mediaType: "application/octet-stream", source: mutableSource, }; const pending = adapter.objects.put(mutableRequest); await journalEntered; mutableRequest.objectId = "mutated_object_request"; mutableRequest.expectedGeneration = 99; mutableRequest.mediaType = "text/html"; mutableSource.byteLength = 999; mutableSource.stream = async function* (_signal: AbortSignal) { yield browserDataSuccess(new Uint8Array([9])); }; const replacementJournalMethod = vi.fn(async () => browserDataFailure("UNAVAILABLE", "INDEXEDDB_READ"), ); const replacementWorkerMethod = vi.fn(async () => browserDataFailure("UNAVAILABLE", "OBJECT_WRITE"), ); journal.getCommittedObject = replacementJournalMethod; worker.preparePut = replacementWorkerMethod; releaseJournal(); const result = await pending; expect(result).toMatchObject({ ok: true, value: { objectId: "object_request_snapshot", byteLength: 3, mediaType: "application/octet-stream", }, }); expect(preparePut).toHaveBeenCalledWith( expect.objectContaining({ descriptor: expect.objectContaining({ objectId: "object_request_snapshot", byteLength: 3, mediaType: "application/octet-stream", }), }), ); expect(replacementJournalMethod).not.toHaveBeenCalled(); expect(replacementWorkerMethod).not.toHaveBeenCalled(); }); it("keeps a committed journal row for reconciliation when cleanup fails", async () => { const journal = createJournal(); const worker = createWorker({ async finalizePut() { return browserDataFailure("UNAVAILABLE", "OBJECT_RECONCILE", { retryable: true, recovery: "RETRY", }); }, }); const adapter = createOpfsByteStoreAdapter({ journal, worker, scope, storagePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: () => "transaction_12345678", now: () => 100, }); const result = await adapter.objects.put({ objectId: "object_12345678", expectedGeneration: null, mediaType: "application/octet-stream", source: sourceFrom(new Uint8Array([1])), }); expect(result.ok).toBe(true); expect( journal.transactions.get("transaction_12345678")?.phase, ).toBe("COMMITTED"); }); it("closes a sync access handle even when flush fails", async () => { const calls: string[] = []; const fakeFileHandle = { async createSyncAccessHandle() { return { truncate: (size: number) => calls.push(`truncate:${size}`), write: (part: ArrayBufferView) => { calls.push(`write:${part.byteLength}`); return part.byteLength; }, flush: () => { calls.push("flush"); throw new Error("disk failure"); }, close: () => calls.push("close"), }; }, }; await expect( writeWithSyncAccessHandle( fakeFileHandle as never, new Uint8Array([1, 2, 3]), ), ).rejects.toThrow("disk failure"); expect(calls.at(-1)).toBe("close"); }); it("enforces TTL and logout policy within the runtime-bound partition", async () => { const journal = createJournal(); journal.objects.set( "expired_12345678", committedObject( "expired_12345678", { ...storagePolicy, retention: { kind: "TTL", maxAgeMs: 10 }, }, 1, 7, ), ); journal.objects.set( "logout_12345678", committedObject( "logout_12345678", { ...storagePolicy, retention: { kind: "TTL", maxAgeMs: 10 }, }, 95, 9, ), ); const removed: string[] = []; const adapter = createOpfsByteStoreAdapter({ journal, worker: createWorker({ async removeObject(_scope, objectId) { removed.push(objectId); return browserDataSuccess(undefined); }, }), scope, storagePolicy: { ...storagePolicy, retention: { kind: "TTL", maxAgeMs: 10 }, }, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: (() => { let sequence = 0; return () => `transaction_policy_${++sequence}_1234`; })(), now: () => 100, requestMaintenanceAuthority: async ({ reason }) => reason === "LOGOUT" ? { authorized: true, proofToken: "receipt_1234567890", expiresAtEpochMs: 200, } : { authorized: false }, consumeMaintenanceAuthority: async ({ reason, proofToken, expiresAtEpochMs, }) => reason === "LOGOUT" && proofToken === "receipt_1234567890" && expiresAtEpochMs === 200, }); expect( await adapter.maintenance.enforcePolicies({ reason: "TTL", }), ).toMatchObject({ ok: true, value: { removedObjects: 1, releasedBytes: 7 }, }); expect(removed).toEqual(["expired_12345678"]); expect( await adapter.maintenance.enforcePolicies({ reason: "LOGOUT", }), ).toMatchObject({ ok: true, value: { removedObjects: 1, releasedBytes: 9 }, }); expect(removed).toEqual([ "expired_12345678", "logout_12345678", ]); }); it("pressure eviction skips user-authored data and rejects invalid maintenance budgets", async () => { const journal = createJournal(); const reconstructablePolicy: BrowserStoragePolicy = { owner: "test-owner", namespace: scope.namespace, classification: "PUBLIC", authority: "RECONSTRUCTABLE", accountScope: "ORIGIN_SHARED", retention: { kind: "TTL", maxAgeMs: 60_000 }, softBudgetBytes: 100, hardBudgetBytes: 200, evictionPriority: "RECONSTRUCTABLE", logoutAction: "KEEP_ORIGIN_SHARED", accountDeletionAction: "KEEP_ORIGIN_SHARED", pressureAction: "EVICT_RECONSTRUCTABLE", unavailableFallback: "ONLINE_ONLY", }; journal.objects.set( "cacheable_12345678", committedObject( "cacheable_12345678", reconstructablePolicy, 1, 12, ), ); journal.objects.set( "authored_12345678", committedObject( "authored_12345678", storagePolicy, 2, 20, ), ); const removed: string[] = []; const adapter = createOpfsByteStoreAdapter({ journal, worker: createWorker({ async removeObject(_scope, objectId) { removed.push(objectId); return browserDataSuccess(undefined); }, }), scope, storagePolicy: reconstructablePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, reconciliationBudgetMs: 10, reconciliationBatchSize: 1, }, createTransactionId: () => "transaction_pressure_1234", now: () => 100, }); expect( await adapter.maintenance.enforcePolicies({ reason: "PRESSURE", targetBytesToRelease: 1, }), ).toMatchObject({ ok: true, value: { removedObjects: 1, releasedBytes: 12 }, }); expect(removed).toEqual(["cacheable_12345678"]); expect(journal.objects.has("authored_12345678")).toBe(true); for (const invalid of [0, -1, Number.NaN]) { expect( await adapter.maintenance.reconcile({ budgetMs: invalid }), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); expect( await adapter.maintenance.enforcePolicies({ reason: "TTL", maxObjects: invalid, }), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); } expect( await adapter.maintenance.reconcile({ budgetMs: 11 }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect( await adapter.maintenance.enforcePolicies({ reason: "PRESSURE", targetBytesToRelease: 1, maxObjects: 2, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); }); it("runs bounded orphan GC and fails closed when staging is unreadable", async () => { const digestHex = "c".repeat(64); const listOrphans = vi.fn(async () => browserDataSuccess({ safeToSweep: true, digests: [digestHex], moreAvailable: false, }), ); const deleteOrphan = vi.fn(async () => browserDataSuccess({ deleted: true, skippedUnsafe: false, }), ); const adapter = createOpfsByteStoreAdapter({ journal: createJournal(), worker: createWorker({ listOrphanCandidates: listOrphans, deleteOrphanChunk: deleteOrphan, }), scope, storagePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, now: () => 100_000, }); expect(await adapter.maintenance.reconcile()).toMatchObject({ ok: true, value: { inspectedOrphanChunks: 1, deletedOrphanChunks: 1, orphanGcStatus: "COMPLETED", }, }); expect(listOrphans).toHaveBeenCalledWith( scope, expect.any(Number), expect.any(Number), undefined, ); expect(deleteOrphan).toHaveBeenCalledWith( scope, digestHex, expect.any(Number), undefined, ); const failClosed = createOpfsByteStoreAdapter({ journal: createJournal(), worker: createWorker({ async listOrphanCandidates() { return browserDataSuccess({ safeToSweep: false, digests: [], moreAvailable: false, }); }, }), scope, storagePolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, now: () => 100_000, }); expect(await failClosed.maintenance.reconcile()).toMatchObject({ ok: true, value: { orphanGcStatus: "STAGING_STATE_UNREADABLE", deletedOrphanChunks: 0, }, }); }); it("does not scan and reports remaining work when authority consumes the maintenance budget", async () => { const syncedPolicy: BrowserStoragePolicy = { ...storagePolicy, authority: "SERVER", retention: { kind: "UNTIL_SYNCED" }, evictionPriority: "SYNCED_COPY", unavailableFallback: "READ_ONLY", }; const journal = createJournal(); journal.objects.set( "synced_budget_1234", committedObject("synced_budget_1234", syncedPolicy, 1, 11), ); const listCommittedObjects = vi.spyOn( journal, "listCommittedObjects", ); const worker = createWorker(); const removeObject = vi.spyOn(worker, "removeObject"); let epochMs = 100; const adapter = createOpfsByteStoreAdapter({ journal, worker, scope, storagePolicy: syncedPolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, reconciliationBudgetMs: 10, reconciliationBatchSize: 1, }, now: () => epochMs, requestMaintenanceAuthority: async () => { epochMs = 110; return { authorized: true, proofToken: "budget_1234567890", expiresAtEpochMs: 200, }; }, consumeMaintenanceAuthority: async () => true, }); expect( await adapter.maintenance.enforcePolicies({ reason: "UNTIL_SYNCED", }), ).toEqual({ ok: true, value: { inspectedObjects: 0, removedObjects: 0, releasedBytes: 0, moreObjectsAvailable: true, deadlineReached: true, }, }); expect(listCommittedObjects).not.toHaveBeenCalled(); expect(removeObject).not.toHaveBeenCalled(); expect(journal.objects.has("synced_budget_1234")).toBe(true); }); it("obtains and atomically consumes composition authority for sensitive maintenance", async () => { const syncedPolicy: BrowserStoragePolicy = { ...storagePolicy, authority: "SERVER", retention: { kind: "UNTIL_SYNCED" }, evictionPriority: "SYNCED_COPY", unavailableFallback: "READ_ONLY", }; const createAdapter = ( authority: Readonly<{ requestMaintenanceAuthority?: OpfsMaintenanceAuthorityProvider; consumeMaintenanceAuthority?: OpfsMaintenanceAuthorityConsumer; }> = {}, ) => { const journal = createJournal(); journal.objects.set( "synced_12345678", committedObject( "synced_12345678", syncedPolicy, 1, 11, ), ); const removed: string[] = []; return { journal, removed, adapter: createOpfsByteStoreAdapter({ journal, worker: createWorker({ async removeObject(_scope, objectId) { removed.push(objectId); return browserDataSuccess(undefined); }, }), scope, storagePolicy: syncedPolicy, policy: { ...resolveOpfsRuntimePolicy(), chunkSizeBytes: 64 * 1024, maxObjectBytes: 64 * 1024, maxChunkCount: 1, }, createTransactionId: () => "transaction_proof_1234", now: () => 100, ...authority, }), }; }; const noAuthority = createAdapter(); expect( await noAuthority.adapter.maintenance.enforcePolicies({ reason: "UNTIL_SYNCED", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(noAuthority.removed).toEqual([]); expect( await noAuthority.adapter.maintenance.enforcePolicies({ reason: "LOGOUT", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(noAuthority.removed).toEqual([]); const deniedConsumer = vi.fn(async () => true); const denied = createAdapter({ requestMaintenanceAuthority: async () => ({ authorized: false }), consumeMaintenanceAuthority: deniedConsumer, }); expect( await denied.adapter.maintenance.enforcePolicies({ reason: "UNTIL_SYNCED", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(denied.removed).toEqual([]); expect(deniedConsumer).not.toHaveBeenCalled(); let markProviderEntered!: () => void; let releaseProvider!: () => void; const providerEntered = new Promise((resolve) => { markProviderEntered = resolve; }); const providerRelease = new Promise((resolve) => { releaseProvider = resolve; }); const providerImplementation: OpfsMaintenanceAuthorityProvider = async ({ reason }) => { markProviderEntered(); await providerRelease; return reason === "UNTIL_SYNCED" ? { authorized: true, proofToken: "receipt_1234567890", expiresAtEpochMs: 200, } : { authorized: false }; }; const provider = vi.fn(providerImplementation); const consumedProofs = new Set(); const consumerImplementation: OpfsMaintenanceAuthorityConsumer = async (request) => { if ( request.reason !== "UNTIL_SYNCED" || request.scope.partitionToken !== scope.partitionToken || request.storagePolicy.retention.kind !== "UNTIL_SYNCED" || request.expiresAtEpochMs !== 200 || consumedProofs.has(request.proofToken) ) { return false; } consumedProofs.add(request.proofToken); return true; }; const consumer = vi.fn(consumerImplementation); const authorized = createAdapter({ requestMaintenanceAuthority: provider, consumeMaintenanceAuthority: consumer, }); const mutableMaintenanceRequest = { reason: "UNTIL_SYNCED" as const, maxObjects: 1, }; const pendingMaintenance = authorized.adapter.maintenance.enforcePolicies( mutableMaintenanceRequest, ); await providerEntered; const mutationTarget = mutableMaintenanceRequest as { reason: string; maxObjects: number; }; mutationTarget.reason = "ACCOUNT_DELETION"; mutationTarget.maxObjects = 999; releaseProvider(); expect(await pendingMaintenance).toMatchObject({ ok: true, value: { removedObjects: 1, releasedBytes: 11 }, }); expect(provider).toHaveBeenCalledWith( expect.objectContaining({ reason: "UNTIL_SYNCED", scope, storagePolicy: syncedPolicy, }), ); expect(consumer).toHaveBeenCalledWith( expect.objectContaining({ reason: "UNTIL_SYNCED", proofToken: "receipt_1234567890", expiresAtEpochMs: 200, scope, storagePolicy: syncedPolicy, }), ); expect(authorized.removed).toEqual(["synced_12345678"]); authorized.journal.objects.set( "synced_12345678", committedObject("synced_12345678", syncedPolicy, 1, 11), ); expect( await authorized.adapter.maintenance.enforcePolicies({ reason: "UNTIL_SYNCED", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(authorized.removed).toEqual(["synced_12345678"]); const expiredConsumer = vi.fn(async () => true); const expired = createAdapter({ requestMaintenanceAuthority: async () => ({ authorized: true, proofToken: "expired_1234567890", expiresAtEpochMs: 100, }), consumeMaintenanceAuthority: expiredConsumer, }); expect( await expired.adapter.maintenance.enforcePolicies({ reason: "UNTIL_SYNCED", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(expiredConsumer).not.toHaveBeenCalled(); expect(expired.removed).toEqual([]); const accountDeletionDenied = createAdapter({ requestMaintenanceAuthority: async () => ({ authorized: false }), consumeMaintenanceAuthority: async () => true, }); expect( await accountDeletionDenied.adapter.maintenance.enforcePolicies({ reason: "ACCOUNT_DELETION", }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(accountDeletionDenied.removed).toEqual([]); }); });