import type { OpfsCapabilities, OpfsChunkReference, OpfsCleanupEffect, OpfsPhysicalGenerationId, OpfsPreparedObject, OpfsStorageScope, } from "../../../application/ports/browser-file-storage/opfs-ports.ts"; import { assertValidStoragePolicy, type BrowserDataFailureCode, } from "../../../application/ports/browser-file-storage/shared.ts"; import { resolveOpfsRuntimePolicy, isValidOpfsStorageScope, type OpfsRuntimePolicy, } from "./opfs-policy.ts"; import { OPFS_WORKER_PROTOCOL_VERSION, } from "./opfs-worker-protocol.ts"; import type { OpfsOrphanCandidateBatch, OpfsOrphanDeleteResult, OpfsWorkerFailure, OpfsWorkerRequest, OpfsWorkerResponse, } from "./opfs-worker-protocol.ts"; export interface OpfsMutationLease { release(): void; } export interface OpfsMutationLeaseManager { acquire(signal?: AbortSignal): Promise; } export interface OpfsWorkerMessageHost { addEventListener( type: "message", listener: (event: MessageEvent) => void, ): void; postMessage( message: OpfsWorkerResponse, transfer?: readonly Transferable[], ): void; } export type BrowserOpfsWorkerDependencies = Readonly<{ storageManager: StorageManager; lockManager?: LockManager; crypto: Crypto; dedicatedWorker: boolean; supportsSynchronousAccessHandles: boolean; policy?: Partial; }>; type ActivePut = { readonly transactionId: string; readonly scope: OpfsPreparedObject["descriptor"]["scope"]; readonly objectId: string; readonly generation: number; readonly physicalGenerationId: OpfsPhysicalGenerationId; readonly declaredByteLength: number; readonly mediaType: string; readonly createdAtEpochMs: number; readonly storagePolicy: OpfsPreparedObject["descriptor"]["storagePolicy"]; readonly chunkSizeBytes: number; readonly lease: OpfsMutationLease; readonly abortController: AbortController; readonly chunks: OpfsChunkReference[]; operationTail: Promise; state: "ACTIVE" | "FINISHING" | "ABORTED"; totalBytes: number; sawShortChunk: boolean; }; type SyncAccessHandleLike = { write(data: ArrayBufferView, options?: { at?: number }): number; truncate(newSize: number): void; flush(): void; close(): void; }; type SyncCapableFileHandle = FileSystemFileHandle & { createSyncAccessHandle?: () => Promise; }; type LockManagerLike = { request( name: string, options: Readonly<{ mode: "exclusive"; signal?: AbortSignal }>, callback: (lock: unknown) => Promise, ): Promise; }; class OpfsRuntimeFailure extends Error { readonly code: BrowserDataFailureCode; readonly retryable: boolean; constructor(code: BrowserDataFailureCode, retryable = false) { super("OPFS operation failed."); this.name = "OpfsRuntimeFailure"; this.code = code; this.retryable = retryable; } } export interface OpfsWorkerRuntime { handleRequest(request: unknown): Promise; } export async function createBrowserOpfsWorkerRuntime( dependencies: BrowserOpfsWorkerDependencies, ): Promise { const policy = resolveOpfsRuntimePolicy(dependencies.policy); const originRoot = await dependencies.storageManager.getDirectory(); const root = await originRoot.getDirectoryHandle(policy.rootDirectoryName, { create: true, }); const leaseManager = dependencies.lockManager ? createWebLockLeaseManager( dependencies.lockManager as unknown as LockManagerLike, policy.mutationLockName, ) : null; return createOpfsWorkerRuntime({ root, crypto: dependencies.crypto, policy, leaseManager, dedicatedWorker: dependencies.dedicatedWorker, supportsSynchronousAccessHandles: dependencies.supportsSynchronousAccessHandles, }); } export async function startBrowserOpfsDedicatedWorker( host: OpfsWorkerMessageHost, dependencies: Readonly<{ storageManager: StorageManager; lockManager?: LockManager; crypto: Crypto; policy?: Partial; }>, ): Promise { const supportsSynchronousAccessHandles = typeof FileSystemFileHandle !== "undefined" && "createSyncAccessHandle" in FileSystemFileHandle.prototype; const runtimePromise = createBrowserOpfsWorkerRuntime({ ...dependencies, dedicatedWorker: true, supportsSynchronousAccessHandles, }); // Install before awaiting OPFS initialization. Worker messages posted while // getDirectory() is pending must not be dropped during bootstrap. host.addEventListener("message", (event) => { if (!hasRequestId(event.data)) return; const requestData = event.data; // NS-05. The envelope's correlation is captured once, up front. Answering a // bootstrap failure with a default `CAPABILITIES` kind made the client see // an expected-kind mismatch and overwrite the real cause — a `BLOCKED` or // `QUOTA_EXCEEDED` outage was reported to operators as `UNSUPPORTED`. const correlation = requestCorrelation(requestData); void runtimePromise .then((runtime) => runtime.handleRequest(requestData)) .then((response) => postWorkerResponse(host, response)) .catch((error: unknown) => { host.postMessage( failure( correlation.requestId, mapRuntimeFailure(error), correlation.kind, ), ); }); }); await runtimePromise; } export function createOpfsWorkerRuntime( dependencies: Readonly<{ root: FileSystemDirectoryHandle; crypto: Crypto; policy: OpfsRuntimePolicy; leaseManager: OpfsMutationLeaseManager | null; dedicatedWorker: boolean; supportsSynchronousAccessHandles: boolean; }>, ): OpfsWorkerRuntime { const activePuts = new Map(); const pendingBegins = new Map(); const cancellationTombstones = new Set(); const requiredCapabilitiesAvailable = dependencies.dedicatedWorker && dependencies.leaseManager !== null && (dependencies.supportsSynchronousAccessHandles || dependencies.policy.allowAsyncWritableChunkFallback); const capabilities: OpfsCapabilities = Object.freeze({ available: requiredCapabilitiesAvailable, dedicatedWorkerRequired: true, crossContextMutationLockAvailable: dependencies.leaseManager !== null, synchronousAccessHandleAvailable: dependencies.dedicatedWorker && dependencies.supportsSynchronousAccessHandles, }); return Object.freeze({ async handleRequest(request: unknown) { if (!hasRequestId(request)) return null; if (!isWorkerRequest(request)) { // STO-RR-02. Only an envelope this runtime could not read produces a // protocol-level failure. Everything below answers its own request. return failure( request.requestId, mapRuntimeFailure(new OpfsRuntimeFailure("INVALID_INPUT")), ); } try { switch (request.kind) { case "CAPABILITIES": return success(request.requestId, request.kind, capabilities); case "BEGIN_PUT": await beginPut(request); return success(request.requestId, request.kind); case "APPEND_CHUNK": await appendChunk(request); return success(request.requestId, request.kind); case "FINISH_PUT": return success( request.requestId, request.kind, await finishPut(request), ); case "ABORT_PUT": return success( request.requestId, request.kind, await abortPut( request.scope, request.transactionId, request.physicalGenerationId, ), ); case "VERIFY_OBJECT": return success( request.requestId, request.kind, await verifyObject(request.preparedObject), ); case "READ_CHUNK": return success( request.requestId, request.kind, await readVerifiedChunk( request.preparedObject, request.sequence, ), ); case "REMOVE_OBJECT": await removeObject( request.scope, request.objectId, request.generation, ); return success(request.requestId, request.kind); case "CLEANUP_TRANSACTION": return success( request.requestId, request.kind, await cleanupTransaction( request.scope, request.transactionId, true, request.physicalGenerationId, ), ); case "FINALIZE_PUT": await finalizePut( request.transactionId, request.preparedObject, ); return success(request.requestId, request.kind); case "LIST_ORPHAN_CANDIDATES": return success( request.requestId, request.kind, await listOrphanCandidates( request.scope, request.olderThanEpochMs, request.maxEntries, ), ); case "DELETE_ORPHAN_CHUNK": return success( request.requestId, request.kind, await deleteOrphanChunk( request.scope, request.digestHex, request.olderThanEpochMs, ), ); } } catch (error) { // STO-RR-02. The kind travels with the failure so the client's // expected-kind check cannot mistake a quota, integrity or abort // failure for a protocol breach. return failure( request.requestId, mapRuntimeFailure(error), request.kind, ); } }, }); async function beginPut( request: Extract, ): Promise { assertWorkerAvailable(); if ( !SAFE_TRANSACTION_ID.test(request.transactionId) || !isValidOpfsStorageScope(request.scope) || !dependencies.policy.isObjectIdAllowed(request.objectId) || !Number.isSafeInteger(request.generation) || request.generation < 1 || !isPhysicalGenerationId(request.physicalGenerationId) || !Number.isSafeInteger(request.declaredByteLength) || request.declaredByteLength < 0 || request.declaredByteLength > dependencies.policy.maxObjectBytes || !dependencies.policy.isMediaTypeAllowed(request.mediaType) || !Number.isSafeInteger(request.createdAtEpochMs) || request.createdAtEpochMs < 0 || request.chunkSizeBytes !== dependencies.policy.chunkSizeBytes ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } try { assertValidStoragePolicy(request.storagePolicy); } catch { throw new OpfsRuntimeFailure("POLICY_REJECTED"); } if (request.storagePolicy.namespace !== request.scope.namespace) { throw new OpfsRuntimeFailure("POLICY_REJECTED"); } const transactionKey = scopedTransactionKey( request.scope, request.transactionId, ); if (activePuts.has(transactionKey)) { throw new OpfsRuntimeFailure("CONFLICT"); } if (cancellationTombstones.has(transactionKey)) { throw new OpfsRuntimeFailure("ABORTED"); } const beginAbort = new AbortController(); pendingBegins.set(transactionKey, beginAbort); let lease: OpfsMutationLease; try { lease = await dependencies.leaseManager!.acquire(beginAbort.signal); } finally { pendingBegins.delete(transactionKey); } if ( beginAbort.signal.aborted || cancellationTombstones.has(transactionKey) ) { lease.release(); throw new OpfsRuntimeFailure("ABORTED"); } const put: ActivePut = { transactionId: request.transactionId, scope: request.scope, objectId: request.objectId, generation: request.generation, physicalGenerationId: request.physicalGenerationId, declaredByteLength: request.declaredByteLength, mediaType: request.mediaType, createdAtEpochMs: request.createdAtEpochMs, storagePolicy: request.storagePolicy, chunkSizeBytes: request.chunkSizeBytes, lease, abortController: beginAbort, chunks: [], operationTail: Promise.resolve(), state: "ACTIVE", totalBytes: 0, sawShortChunk: false, }; activePuts.set(transactionKey, put); try { await writeReceipt(put); if ( beginAbort.signal.aborted || cancellationTombstones.has(transactionKey) ) { if (activePuts.get(transactionKey) === put) { activePuts.delete(transactionKey); lease.release(); } await cleanupTransaction(request.scope, request.transactionId); throw new OpfsRuntimeFailure("ABORTED"); } } catch (error) { if (activePuts.get(transactionKey) === put) { activePuts.delete(transactionKey); lease.release(); } throw error; } } async function appendChunk( request: Extract, ): Promise { if (!isValidOpfsStorageScope(request.scope)) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const transactionKey = scopedTransactionKey( request.scope, request.transactionId, ); const put = activePuts.get(transactionKey); if (!put) throw new OpfsRuntimeFailure("INVALID_INPUT"); try { await runActivePutOperation(transactionKey, put, async () => { if ( request.sequence !== put.chunks.length || !(request.bytes instanceof ArrayBuffer) || request.bytes.byteLength === 0 || request.bytes.byteLength > put.chunkSizeBytes || put.sawShortChunk || put.chunks.length >= dependencies.policy.maxChunkCount ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const nextTotal = put.totalBytes + request.bytes.byteLength; if ( !Number.isSafeInteger(nextTotal) || nextTotal > put.declaredByteLength || nextTotal > dependencies.policy.maxObjectBytes ) { throw new OpfsRuntimeFailure("LIMIT_EXCEEDED"); } const bytes = new Uint8Array(request.bytes); const digestHex = await sha256Hex(dependencies.crypto, bytes); assertActivePut(transactionKey, put); await writeImmutableChunk(put.scope, digestHex, bytes); assertActivePut(transactionKey, put); put.chunks.push( Object.freeze({ sequence: request.sequence, byteLength: bytes.byteLength, digestHex, }), ); put.totalBytes = nextTotal; put.sawShortChunk = bytes.byteLength < put.chunkSizeBytes; await writeReceipt(put); assertActivePut(transactionKey, put); }); } catch (error) { if (put.state !== "ABORTED") { await failActivePut(transactionKey, put); } throw error; } } async function finishPut( request: Extract, ): Promise { if (!isValidOpfsStorageScope(request.scope)) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const transactionKey = scopedTransactionKey( request.scope, request.transactionId, ); const put = activePuts.get(transactionKey); if (!put) throw new OpfsRuntimeFailure("INVALID_INPUT"); try { const preparedObject = await runActivePutOperation( transactionKey, put, async () => { if (put.state !== "ACTIVE") { throw new OpfsRuntimeFailure("CONFLICT"); } put.state = "FINISHING"; if ( put.totalBytes !== put.declaredByteLength || put.chunks.length !== Math.ceil(put.declaredByteLength / put.chunkSizeBytes) ) { throw new OpfsRuntimeFailure("INTEGRITY_FAILED"); } const rootDigestHex = await treeDigestHex( dependencies.crypto, put.chunkSizeBytes, put.declaredByteLength, put.chunks, ); assertActivePut(transactionKey, put, true); const prepared: OpfsPreparedObject = Object.freeze({ physicalSchemaVersion: 2, physicalGenerationId: put.physicalGenerationId, descriptor: Object.freeze({ objectId: put.objectId, scope: put.scope, generation: put.generation, byteLength: put.declaredByteLength, mediaType: put.mediaType, createdAtEpochMs: put.createdAtEpochMs, integrity: Object.freeze({ algorithm: "SHA-256-TREE-V1", rootDigestHex, chunkSizeBytes: put.chunkSizeBytes, }), storagePolicy: put.storagePolicy, }), chunks: Object.freeze([...put.chunks]), }); await writeJsonAtomic(manifestPath(prepared), prepared); assertActivePut(transactionKey, put, true); await writeJsonAtomic(receiptPath(put.scope, put.transactionId), { schemaVersion: 1, phase: "FILES_READY", preparedObject: prepared, }); assertActivePut(transactionKey, put, true); return prepared; }, ); if (activePuts.get(transactionKey) === put) { activePuts.delete(transactionKey); put.lease.release(); } return preparedObject; } catch (error) { if (put.state !== "ABORTED") { await failActivePut(transactionKey, put); } throw error; } } async function abortPut( scope: OpfsPreparedObject["descriptor"]["scope"], transactionId: string, physicalGenerationId?: OpfsPhysicalGenerationId, ): Promise { if ( !isValidOpfsStorageScope(scope) || !SAFE_TRANSACTION_ID.test(transactionId) || (physicalGenerationId !== undefined && !isPhysicalGenerationId(physicalGenerationId)) ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const transactionKey = scopedTransactionKey(scope, transactionId); rememberCancellation(transactionKey); pendingBegins.get(transactionKey)?.abort(); const active = activePuts.get(transactionKey); if (active) { active.state = "ABORTED"; active.abortController.abort(); await active.operationTail; if (activePuts.get(transactionKey) === active) { activePuts.delete(transactionKey); } try { // STO-01. The mutation lease is held through the physical delete and // the staging cleanup; releasing it earlier would let a new transaction // race this compensation. await removePhysicalGeneration( active.scope, active.objectId, active.generation, active.physicalGenerationId, ); return await cleanupTransactionLocked( scope, transactionId, true, physicalGenerationId ?? active.physicalGenerationId, ); } finally { active.lease.release(); } } return await cleanupTransaction( scope, transactionId, true, physicalGenerationId, ); } async function runActivePutOperation( transactionKey: string, put: ActivePut, operation: () => Promise, ): Promise { const previous = put.operationTail; let release: (() => void) | undefined; put.operationTail = new Promise((resolve) => { release = resolve; }); await previous; try { assertActivePut(transactionKey, put, true); return await operation(); } finally { release?.(); } } function assertActivePut( transactionKey: string, put: ActivePut, allowFinishing = false, ): void { if ( activePuts.get(transactionKey) !== put || put.abortController.signal.aborted || put.state === "ABORTED" || (!allowFinishing && put.state !== "ACTIVE") ) { throw new OpfsRuntimeFailure("ABORTED"); } } async function failActivePut( transactionKey: string, put: ActivePut, ): Promise { put.state = "ABORTED"; put.abortController.abort(); if (activePuts.get(transactionKey) === put) { activePuts.delete(transactionKey); } try { await removePhysicalGeneration( put.scope, put.objectId, put.generation, put.physicalGenerationId, ); await cleanupTransactionLocked( put.scope, put.transactionId, true, put.physicalGenerationId, ); } finally { put.lease.release(); } } async function removePhysicalGeneration( scope: OpfsStorageScope, objectId: string, generation: number, physicalGenerationId: OpfsPhysicalGenerationId | undefined, ): Promise { try { const objectDirectory = await getDirectory( dependencies.root, [ ...scopeRootPath(scope), "objects", objectId.slice(0, 2), objectId, ], false, ); await removeEntryIfPresent( objectDirectory, generationSegmentFor(generation, physicalGenerationId), true, ); } catch (error) { if (!isNotFound(error)) throw error; } } async function verifyObject( expected: OpfsPreparedObject, ): Promise { if (!isPreparedObjectSafe(expected, dependencies.policy)) return false; let stored: unknown; try { stored = await readJson(manifestPath(expected)); } catch (error) { if (isNotFound(error)) return false; throw error; } if ( !isPreparedObjectSafe(stored, dependencies.policy) || stableJson(stored) !== stableJson(expected) ) { return false; } const rootDigestHex = await treeDigestHex( dependencies.crypto, expected.descriptor.integrity.chunkSizeBytes, expected.descriptor.byteLength, expected.chunks, ); if (rootDigestHex !== expected.descriptor.integrity.rootDigestHex) { return false; } for (const chunk of expected.chunks) { let bytes: Uint8Array; try { bytes = await readFile( chunkPath(expected.descriptor.scope, chunk.digestHex), ); } catch (error) { if (isNotFound(error)) return false; throw error; } if ( bytes.byteLength !== chunk.byteLength || (await sha256Hex(dependencies.crypto, bytes)) !== chunk.digestHex ) { return false; } } return true; } async function readVerifiedChunk( expected: OpfsPreparedObject, sequence: number, ): Promise { if ( !isPreparedObjectSafe(expected, dependencies.policy) || !Number.isSafeInteger(sequence) || sequence < 0 ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const reference = expected.chunks[sequence]; if (!reference || reference.sequence !== sequence) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const bytes = await readFile( chunkPath(expected.descriptor.scope, reference.digestHex), ); if ( bytes.byteLength !== reference.byteLength || (await sha256Hex(dependencies.crypto, bytes)) !== reference.digestHex ) { throw new OpfsRuntimeFailure("INTEGRITY_FAILED"); } const copy = Uint8Array.from(bytes); return copy.buffer as ArrayBuffer; } async function removeObject( scope: OpfsPreparedObject["descriptor"]["scope"], objectId: string, generation: number, ): Promise { assertWorkerAvailable(); if ( !isValidOpfsStorageScope(scope) || !dependencies.policy.isObjectIdAllowed(objectId) || !Number.isSafeInteger(generation) || generation < 1 ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const lease = await dependencies.leaseManager!.acquire(); try { const prefixDirectory = await getDirectory( dependencies.root, [ ...scopeRootPath(scope), "objects", objectId.slice(0, 2), ], false, ); await removeEntryIfPresent(prefixDirectory, objectId, true); } catch (error) { if (!isNotFound(error)) throw error; } finally { lease.release(); } } /** * STO-01. Cleanup deletes exactly one transaction's physical generation while * holding the origin mutation lease, and reports whether the effect actually * happened. `ALREADY_CLEAN` means there was nothing left to delete. */ async function cleanupTransaction( scope: OpfsPreparedObject["descriptor"]["scope"], transactionId: string, removePreparedGeneration = true, physicalGenerationId?: OpfsPhysicalGenerationId, ): Promise { if ( !isValidOpfsStorageScope(scope) || !SAFE_TRANSACTION_ID.test(transactionId) ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } // Probe before locking. A transaction that never reached staging has // nothing to delete, and waiting for the mutation lease here would deadlock // against the very BEGIN this compensation is cancelling. try { await getDirectory( dependencies.root, [...scopeRootPath(scope), "staging", transactionId], false, ); } catch (error) { if (isNotFound(error)) return CLEANUP_ALREADY_CLEAN; throw error; } // Every destructive step below runs while the lease is held. const lease = await dependencies.leaseManager!.acquire(); try { return await cleanupTransactionLocked( scope, transactionId, removePreparedGeneration, physicalGenerationId, ); } finally { lease.release(); } } /** * Callers that already hold the origin mutation lease use this directly, so * an abort never releases the lease between fencing and physical deletion. */ async function cleanupTransactionLocked( scope: OpfsPreparedObject["descriptor"]["scope"], transactionId: string, removePreparedGeneration = true, physicalGenerationId?: OpfsPhysicalGenerationId, ): Promise { if ( !isValidOpfsStorageScope(scope) || !SAFE_TRANSACTION_ID.test(transactionId) || (physicalGenerationId !== undefined && !isPhysicalGenerationId(physicalGenerationId)) ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } let staging: FileSystemDirectoryHandle; try { staging = await getDirectory( dependencies.root, [...scopeRootPath(scope), "staging"], false, ); } catch (error) { if (isNotFound(error)) return CLEANUP_ALREADY_CLEAN; throw error; } { if (removePreparedGeneration) { let receipt: unknown; try { receipt = await readJson(receiptPath(scope, transactionId)); } catch (error) { if (isNotFound(error)) { await removeEntryIfPresent(staging, transactionId, true); return CLEANUP_ALREADY_CLEAN; } // Keep unreadable staging in place so orphan GC fails closed. throw error; } const target = extractReceiptPhysicalTarget(receipt, scope); if (!target) { throw new OpfsRuntimeFailure("CORRUPT_DATA"); } // A caller-supplied token wins: a stale compensation must not widen its // target to whatever the receipt now says. await removePhysicalGeneration( scope, target.objectId, target.generation, physicalGenerationId ?? target.physicalGenerationId, ); } await removeEntryIfPresent(staging, transactionId, true); return CLEANUP_CLEANED; } } async function finalizePut( transactionId: string, preparedObject: OpfsPreparedObject, ): Promise { assertWorkerAvailable(); if ( !SAFE_TRANSACTION_ID.test(transactionId) || !isPreparedObjectSafe(preparedObject, dependencies.policy) ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const lease = await dependencies.leaseManager!.acquire(); try { const descriptor = preparedObject.descriptor; const objectDirectory = await getDirectory( dependencies.root, [ ...scopeRootPath(descriptor.scope), "objects", descriptor.objectId.slice(0, 2), descriptor.objectId, ], false, ); for await (const [name, handle] of objectDirectory.entries()) { if ( handle.kind === "directory" && isGenerationSegment(name) && name !== preparedGenerationSegment(preparedObject) ) { await objectDirectory.removeEntry(name, { recursive: true }); } } // STO-RR-01. This path already holds the origin mutation lease, and a // Web Lock is not reentrant: asking for it again here never returns, so // an ordinary PUT would stop for good at FINALIZE. await cleanupTransactionLocked(descriptor.scope, transactionId, false); } catch (error) { if (!isNotFound(error)) throw error; await cleanupTransactionLocked( preparedObject.descriptor.scope, transactionId, false, ); } finally { lease.release(); } } async function listOrphanCandidates( scope: OpfsPreparedObject["descriptor"]["scope"], olderThanEpochMs: number, maxEntries: number, ): Promise { assertWorkerAvailable(); if ( !isValidOpfsStorageScope(scope) || !Number.isSafeInteger(olderThanEpochMs) || olderThanEpochMs < 0 || !Number.isSafeInteger(maxEntries) || maxEntries < 1 || maxEntries > dependencies.policy.orphanGcBatchSize ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const lease = await dependencies.leaseManager!.acquire(); try { const staged = await stagedChunkDigests(scope); if (!staged.safeToSweep) { return Object.freeze({ safeToSweep: false, digests: Object.freeze([]), moreAvailable: false, }); } let shaRoot: FileSystemDirectoryHandle; try { shaRoot = await getDirectory( dependencies.root, [...scopeRootPath(scope), "chunks", "sha256"], false, ); } catch (error) { if (isNotFound(error)) { return Object.freeze({ safeToSweep: true, digests: Object.freeze([]), moreAvailable: false, }); } throw error; } const candidates: string[] = []; outer: for await (const [, prefixHandle] of shaRoot.entries()) { if (prefixHandle.kind !== "directory") continue; const prefixDirectory = prefixHandle as FileSystemDirectoryHandle; for await (const [name, handle] of prefixDirectory.entries()) { if (handle.kind !== "file") continue; const digestHex = name.endsWith(".bin") ? name.slice(0, -4) : ""; if ( !SHA256_HEX.test(digestHex) || staged.digests.has(digestHex) ) { continue; } const file = await ( handle as FileSystemFileHandle ).getFile(); if (file.lastModified > olderThanEpochMs) continue; candidates.push(digestHex); if (candidates.length > maxEntries) break outer; } } return Object.freeze({ safeToSweep: true, digests: Object.freeze(candidates.slice(0, maxEntries)), moreAvailable: candidates.length > maxEntries, }); } finally { lease.release(); } } async function deleteOrphanChunk( scope: OpfsPreparedObject["descriptor"]["scope"], digestHex: string, olderThanEpochMs: number, ): Promise { assertWorkerAvailable(); if ( !isValidOpfsStorageScope(scope) || !SHA256_HEX.test(digestHex) || !Number.isSafeInteger(olderThanEpochMs) || olderThanEpochMs < 0 ) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const lease = await dependencies.leaseManager!.acquire(); try { const staged = await stagedChunkDigests(scope); if (!staged.safeToSweep) { return Object.freeze({ deleted: false, skippedUnsafe: true }); } if (staged.digests.has(digestHex)) { return Object.freeze({ deleted: false, skippedUnsafe: false }); } const path = chunkPath(scope, digestHex); try { const { directory, fileName } = await resolveFileParent( dependencies.root, path, false, ); const handle = await directory.getFileHandle(fileName); const file = await handle.getFile(); if (file.lastModified > olderThanEpochMs) { return Object.freeze({ deleted: false, skippedUnsafe: false }); } await directory.removeEntry(fileName); return Object.freeze({ deleted: true, skippedUnsafe: false }); } catch (error) { if (isNotFound(error)) { return Object.freeze({ deleted: false, skippedUnsafe: false }); } throw error; } } finally { lease.release(); } } async function stagedChunkDigests( scope: OpfsPreparedObject["descriptor"]["scope"], ): Promise; }>> { const digests = new Set(); let staging: FileSystemDirectoryHandle; try { staging = await getDirectory( dependencies.root, [...scopeRootPath(scope), "staging"], false, ); } catch (error) { if (isNotFound(error)) { return { safeToSweep: true, digests }; } throw error; } for await (const [transactionId, handle] of staging.entries()) { if ( handle.kind !== "directory" || !SAFE_TRANSACTION_ID.test(transactionId) ) { return { safeToSweep: false, digests }; } let receipt: unknown; try { receipt = await readJson(receiptPath(scope, transactionId)); } catch { return { safeToSweep: false, digests }; } if (!receiptBelongsToScope(receipt, scope)) { return { safeToSweep: false, digests }; } const receiptDigests = extractReceiptDigests(receipt); if (!receiptDigests) { return { safeToSweep: false, digests }; } for (const digest of receiptDigests) digests.add(digest); } return { safeToSweep: true, digests }; } function extractReceiptPhysicalTarget( receipt: unknown, scope: OpfsStorageScope, ): Readonly<{ objectId: string; generation: number; physicalGenerationId: OpfsPhysicalGenerationId | undefined; }> | null { if (!receiptBelongsToScope(receipt, scope)) return null; const record = receipt as Record; if (record.phase === "PREPARING") { return typeof record.objectId === "string" && dependencies.policy.isObjectIdAllowed(record.objectId) && typeof record.generation === "number" && Number.isSafeInteger(record.generation) && record.generation > 0 ? { objectId: record.objectId, generation: record.generation, physicalGenerationId: isPhysicalGenerationId( record.physicalGenerationId, ) ? record.physicalGenerationId : undefined, } : null; } if ( record.phase === "FILES_READY" && isPreparedObjectSafe(record.preparedObject, dependencies.policy) && sameScope(record.preparedObject.descriptor.scope, scope) ) { const prepared = record.preparedObject; return { objectId: prepared.descriptor.objectId, generation: prepared.descriptor.generation, physicalGenerationId: prepared.physicalSchemaVersion === 2 ? prepared.physicalGenerationId : undefined, }; } return null; } function rememberCancellation(transactionId: string): void { cancellationTombstones.add(transactionId); while ( cancellationTombstones.size > dependencies.policy.maxCancellationTombstones ) { const oldest = cancellationTombstones.values().next().value; if (typeof oldest !== "string") break; cancellationTombstones.delete(oldest); } } async function writeReceipt(put: ActivePut): Promise { await writeJsonAtomic(receiptPath(put.scope, put.transactionId), { schemaVersion: 2, phase: "PREPARING", scope: put.scope, objectId: put.objectId, generation: put.generation, physicalGenerationId: put.physicalGenerationId, declaredByteLength: put.declaredByteLength, chunks: put.chunks, }); } async function writeImmutableChunk( scope: OpfsPreparedObject["descriptor"]["scope"], digestHex: string, bytes: Uint8Array, ): Promise { const path = chunkPath(scope, digestHex); try { const existing = await readFile(path); if ( existing.byteLength !== bytes.byteLength || (await sha256Hex(dependencies.crypto, existing)) !== digestHex ) { const { directory, fileName } = await resolveFileParent( dependencies.root, path, false, ); await directory.removeEntry(fileName); } else { return; } } catch (error) { if (!isNotFound(error)) throw error; } const { directory, fileName } = await resolveFileParent( dependencies.root, path, true, ); const fileHandle = (await directory.getFileHandle(fileName, { create: true, })) as SyncCapableFileHandle; if ( dependencies.dedicatedWorker && typeof fileHandle.createSyncAccessHandle === "function" ) { try { await writeWithSyncAccessHandle(fileHandle, bytes); return; } catch (error) { if ( !dependencies.policy.allowAsyncWritableChunkFallback || !isSyncUnsupported(error) ) { throw error; } } } if (!dependencies.policy.allowAsyncWritableChunkFallback) { throw new OpfsRuntimeFailure("UNSUPPORTED"); } await writeWithAtomicWritable(fileHandle, bytes); } async function writeJsonAtomic( path: readonly string[], value: unknown, ): Promise { const bytes = new TextEncoder().encode(JSON.stringify(value)); const { directory, fileName } = await resolveFileParent( dependencies.root, path, true, ); const handle = await directory.getFileHandle(fileName, { create: true }); await writeWithAtomicWritable(handle, bytes); } async function readJson(path: readonly string[]): Promise { const bytes = await readFile(path); if (bytes.byteLength > MAX_MANIFEST_BYTES) { throw new OpfsRuntimeFailure("CORRUPT_DATA"); } try { return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); } catch { throw new OpfsRuntimeFailure("CORRUPT_DATA"); } } async function readFile(path: readonly string[]): Promise { const { directory, fileName } = await resolveFileParent( dependencies.root, path, false, ); const handle = await directory.getFileHandle(fileName); const file = await handle.getFile(); return new Uint8Array(await file.arrayBuffer()); } function assertWorkerAvailable(): void { if (!requiredCapabilitiesAvailable) { throw new OpfsRuntimeFailure("UNSUPPORTED"); } } } export function installOpfsWorkerMessageHandler( host: OpfsWorkerMessageHost, runtime: OpfsWorkerRuntime, ): void { host.addEventListener("message", (event) => { void runtime.handleRequest(event.data).then((response) => { postWorkerResponse(host, response); }); }); } function postWorkerResponse( host: OpfsWorkerMessageHost, response: OpfsWorkerResponse | null, ): void { if (!response) return; if ( response.ok && response.value instanceof ArrayBuffer ) { host.postMessage(response, [response.value]); return; } host.postMessage(response); } export function createWebLockLeaseManager( lockManager: LockManagerLike, lockName: string, ): OpfsMutationLeaseManager { if (lockName.length === 0) { throw new TypeError("OPFS mutation lock name is required."); } return Object.freeze({ async acquire(signal?: AbortSignal) { if (signal?.aborted) { throw new DOMException("The operation was aborted.", "AbortError"); } let releaseHold: (() => void) | undefined; let released = false; const hold = new Promise((resolve) => { releaseHold = resolve; }); let acquiredResolve: ((lease: OpfsMutationLease) => void) | undefined; let acquiredReject: ((error: unknown) => void) | undefined; const acquired = new Promise((resolve, reject) => { acquiredResolve = resolve; acquiredReject = reject; }); void lockManager .request(lockName, { mode: "exclusive", signal }, async (lock) => { if (!lock) throw new OpfsRuntimeFailure("BLOCKED", true); const lease: OpfsMutationLease = Object.freeze({ release() { if (released) return; released = true; releaseHold?.(); }, }); acquiredResolve?.(lease); await hold; }) .catch((error: unknown) => acquiredReject?.(error)); return await acquired; }, }); } /** * Exported for a focused lifecycle test. Partial writes are retried and every * acquired handle is closed, including failure paths. */ export async function writeWithSyncAccessHandle( fileHandle: SyncCapableFileHandle, bytes: Uint8Array, ): Promise { if (typeof fileHandle.createSyncAccessHandle !== "function") { throw new OpfsRuntimeFailure("UNSUPPORTED"); } let accessHandle: SyncAccessHandleLike | undefined; try { accessHandle = await fileHandle.createSyncAccessHandle(); accessHandle.truncate(0); let offset = 0; while (offset < bytes.byteLength) { const written = accessHandle.write(bytes.subarray(offset), { at: offset }); if ( !Number.isSafeInteger(written) || written <= 0 || written > bytes.byteLength - offset ) { throw new OpfsRuntimeFailure("NOT_READABLE", true); } offset += written; } accessHandle.truncate(bytes.byteLength); accessHandle.flush(); } finally { accessHandle?.close(); } } async function writeWithAtomicWritable( fileHandle: FileSystemFileHandle, bytes: Uint8Array, ): Promise { const writable = await fileHandle.createWritable({ keepExistingData: false }); let closed = false; try { const copy = Uint8Array.from(bytes); await writable.write(copy); await writable.close(); closed = true; } finally { if (!closed) { try { await writable.abort(); } catch { // Preserve the original write error. } } } } async function resolveFileParent( root: FileSystemDirectoryHandle, path: readonly string[], create: boolean, ): Promise> { if (path.length < 1 || path.some((segment) => !SAFE_PATH_SEGMENT.test(segment))) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } const fileName = path.at(-1)!; const directory = await getDirectory(root, path.slice(0, -1), create); return { directory, fileName }; } async function getDirectory( root: FileSystemDirectoryHandle, path: readonly string[], create: boolean, ): Promise { let current = root; for (const segment of path) { if (!SAFE_PATH_SEGMENT.test(segment)) { throw new OpfsRuntimeFailure("INVALID_INPUT"); } current = await current.getDirectoryHandle(segment, { create }); } return current; } async function removeEntryIfPresent( directory: FileSystemDirectoryHandle, name: string, recursive: boolean, ): Promise { try { await directory.removeEntry(name, { recursive }); } catch (error) { if (!isNotFound(error)) throw error; } } const CLEANUP_CLEANED: OpfsCleanupEffect = Object.freeze({ kind: "CLEANED" }); const CLEANUP_ALREADY_CLEAN: OpfsCleanupEffect = Object.freeze({ kind: "ALREADY_CLEAN", }); const PHYSICAL_GENERATION_ID = /^[0-9a-f]{32}$/u; const V1_GENERATION_SEGMENT = /^\d+$/u; const V2_GENERATION_SEGMENT = /^g\d+-[0-9a-f]{32}$/u; export function isPhysicalGenerationId( value: unknown, ): value is OpfsPhysicalGenerationId { return typeof value === "string" && PHYSICAL_GENERATION_ID.test(value); } /** * STO-01. v1 wrote `objects////`, which two * transactions can legitimately share. v2 writes * `objects///g-/` so a late compensation can * only ever delete its own transaction's directory. v1 segments stay readable * through the rollback window. */ function generationSegmentFor( generation: number, physicalGenerationId: OpfsPhysicalGenerationId | undefined, ): string { return physicalGenerationId === undefined ? String(generation) : `g${generation}-${physicalGenerationId}`; } function preparedGenerationSegment( preparedObject: OpfsPreparedObject, ): string { return generationSegmentFor( preparedObject.descriptor.generation, preparedObject.physicalSchemaVersion === 2 ? preparedObject.physicalGenerationId : undefined, ); } function isGenerationSegment(name: string): boolean { return V1_GENERATION_SEGMENT.test(name) || V2_GENERATION_SEGMENT.test(name); } function manifestPath(preparedObject: OpfsPreparedObject): readonly string[] { const descriptor = preparedObject.descriptor; return [ ...scopeRootPath(descriptor.scope), "objects", descriptor.objectId.slice(0, 2), descriptor.objectId, preparedGenerationSegment(preparedObject), "manifest.json", ]; } function chunkPath( scope: OpfsStorageScope, digestHex: string, ): readonly string[] { return [ ...scopeRootPath(scope), "chunks", "sha256", digestHex.slice(0, 2), `${digestHex}.bin`, ]; } function receiptPath( scope: OpfsStorageScope, transactionId: string, ): readonly string[] { return [ ...scopeRootPath(scope), "staging", transactionId, "receipt.json", ]; } function scopeRootPath(scope: OpfsStorageScope): readonly string[] { return [ "authorities", scope.authorityToken, scope.namespaceToken, scope.partitionToken, ]; } function scopedTransactionKey( scope: OpfsStorageScope, transactionId: string, ): string { return [ scope.authorityToken, scope.namespaceToken, scope.partitionToken, transactionId, ].join("|"); } function sameScope( left: OpfsStorageScope, right: OpfsStorageScope, ): boolean { return ( left.namespace === right.namespace && left.authorityToken === right.authorityToken && left.namespaceToken === right.namespaceToken && left.partitionToken === right.partitionToken ); } function receiptBelongsToScope( receipt: unknown, scope: OpfsStorageScope, ): boolean { if (!receipt || typeof receipt !== "object") return false; const record = receipt as Record; if ( // v1 receipts stay readable through the rollback window. (record.schemaVersion !== 1 && record.schemaVersion !== 2) || (record.phase !== "PREPARING" && record.phase !== "FILES_READY") ) { return false; } if (record.phase === "PREPARING") { return Boolean( record.scope && typeof record.scope === "object" && isValidOpfsStorageScope(record.scope as OpfsStorageScope) && sameScope(record.scope as OpfsStorageScope, scope), ); } if ( !record.preparedObject || typeof record.preparedObject !== "object" || !("descriptor" in record.preparedObject) || !record.preparedObject.descriptor || typeof record.preparedObject.descriptor !== "object" || !("scope" in record.preparedObject.descriptor) ) { return false; } const preparedScope = record.preparedObject.descriptor.scope; return Boolean( preparedScope && typeof preparedScope === "object" && isValidOpfsStorageScope(preparedScope as OpfsStorageScope) && sameScope(preparedScope as OpfsStorageScope, scope), ); } async function sha256Hex( crypto: Crypto, bytes: Uint8Array, ): Promise { const copy = Uint8Array.from(bytes); const digest = await crypto.subtle.digest("SHA-256", copy); return bytesToHex(new Uint8Array(digest)); } async function treeDigestHex( crypto: Crypto, chunkSizeBytes: number, byteLength: number, chunks: readonly OpfsChunkReference[], ): Promise { const canonical = [ "sha-256-tree-v1", `chunk-size:${chunkSizeBytes}`, `byte-length:${byteLength}`, ...chunks.map( (chunk) => `${chunk.sequence}:${chunk.byteLength}:${chunk.digestHex}`, ), "", ].join("\n"); return await sha256Hex(crypto, new TextEncoder().encode(canonical)); } function bytesToHex(bytes: Uint8Array): string { let hex = ""; for (const byte of bytes) hex += byte.toString(16).padStart(2, "0"); return hex; } function stableJson(value: unknown): string { if ( value === null || typeof value === "string" || typeof value === "boolean" ) { return JSON.stringify(value); } if (typeof value === "number") { if (!Number.isFinite(value)) throw new OpfsRuntimeFailure("CORRUPT_DATA"); return JSON.stringify(value); } if (Array.isArray(value)) { return `[${value.map(stableJson).join(",")}]`; } if (typeof value === "object") { const record = value as Record; return `{${Object.keys(record) .sort() .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`) .join(",")}}`; } throw new OpfsRuntimeFailure("CORRUPT_DATA"); } /** * STO-01 expand phase. v1 prepared objects stay readable through the rollback * window; v2 additionally carries a transaction-unique physical fencing token. */ function isSupportedPhysicalSchema(value: object): boolean { const record = value as Record; if (record.physicalSchemaVersion === 1) return true; return ( record.physicalSchemaVersion === 2 && typeof record.physicalGenerationId === "string" && /^[0-9a-f]{32}$/u.test(record.physicalGenerationId) ); } function isPreparedObjectSafe( value: unknown, policy: OpfsRuntimePolicy, ): value is OpfsPreparedObject { if ( !value || typeof value !== "object" || !("physicalSchemaVersion" in value) || !isSupportedPhysicalSchema(value) || !("descriptor" in value) || !value.descriptor || typeof value.descriptor !== "object" || !("chunks" in value) || !Array.isArray(value.chunks) ) { return false; } const descriptor = value.descriptor as Record; const integrity = descriptor.integrity && typeof descriptor.integrity === "object" ? (descriptor.integrity as Record) : null; if ( typeof descriptor.objectId !== "string" || !policy.isObjectIdAllowed(descriptor.objectId) || !("scope" in descriptor) || !descriptor.scope || typeof descriptor.scope !== "object" || !isValidOpfsStorageScope( descriptor.scope as OpfsPreparedObject["descriptor"]["scope"], ) || (descriptor.scope as OpfsPreparedObject["descriptor"]["scope"]) .namespace !== (descriptor.storagePolicy as | OpfsPreparedObject["descriptor"]["storagePolicy"] | undefined)?.namespace || typeof descriptor.generation !== "number" || !Number.isSafeInteger(descriptor.generation) || descriptor.generation < 1 || typeof descriptor.byteLength !== "number" || !Number.isSafeInteger(descriptor.byteLength) || descriptor.byteLength < 0 || descriptor.byteLength > policy.maxObjectBytes || typeof descriptor.mediaType !== "string" || !policy.isMediaTypeAllowed(descriptor.mediaType) || typeof descriptor.createdAtEpochMs !== "number" || !Number.isSafeInteger(descriptor.createdAtEpochMs) || descriptor.createdAtEpochMs < 0 || !integrity || integrity.algorithm !== "SHA-256-TREE-V1" || typeof integrity.rootDigestHex !== "string" || !SHA256_HEX.test(integrity.rootDigestHex) || integrity.chunkSizeBytes !== policy.chunkSizeBytes || !("storagePolicy" in descriptor) || !descriptor.storagePolicy || typeof descriptor.storagePolicy !== "object" || value.chunks.length > policy.maxChunkCount ) { return false; } try { assertValidStoragePolicy( descriptor.storagePolicy as OpfsPreparedObject["descriptor"]["storagePolicy"], ); } catch { return false; } let total = 0; for (let index = 0; index < value.chunks.length; index += 1) { const chunk = value.chunks[index] as unknown; if ( !chunk || typeof chunk !== "object" || !("sequence" in chunk) || chunk.sequence !== index || !("byteLength" in chunk) || typeof chunk.byteLength !== "number" || !Number.isSafeInteger(chunk.byteLength) || chunk.byteLength < 1 || chunk.byteLength > policy.chunkSizeBytes || !("digestHex" in chunk) || typeof chunk.digestHex !== "string" || !SHA256_HEX.test(chunk.digestHex) ) { return false; } if (index < value.chunks.length - 1 && chunk.byteLength !== policy.chunkSizeBytes) { return false; } total += chunk.byteLength; } return ( total === descriptor.byteLength && value.chunks.length === Math.ceil(descriptor.byteLength / policy.chunkSizeBytes) ); } function success( requestId: string, kind: OpfsWorkerRequest["kind"], value?: OpfsWorkerResponse extends infer _Response ? | OpfsCapabilities | OpfsCleanupEffect | OpfsPreparedObject | ArrayBuffer | boolean | OpfsOrphanCandidateBatch | OpfsOrphanDeleteResult : never, ): OpfsWorkerResponse { return value === undefined ? { requestId, protocolVersion: OPFS_WORKER_PROTOCOL_VERSION, kind, ok: true, } : { requestId, protocolVersion: OPFS_WORKER_PROTOCOL_VERSION, kind, ok: true, value, }; } function failure( requestId: string, workerFailure: OpfsWorkerFailure, kind: OpfsWorkerRequest["kind"] = "CAPABILITIES", ): OpfsWorkerResponse { return { requestId, protocolVersion: OPFS_WORKER_PROTOCOL_VERSION, kind, ok: false, failure: workerFailure, }; } function mapRuntimeFailure(error: unknown): OpfsWorkerFailure { if (error instanceof OpfsRuntimeFailure) { return { code: error.code, retryable: error.retryable }; } if (error instanceof DOMException) { if (error.name === "QuotaExceededError") { return { code: "QUOTA_EXCEEDED", retryable: true }; } if (error.name === "NotFoundError") { return { code: "NOT_FOUND", retryable: false }; } if ( error.name === "NoModificationAllowedError" || error.name === "InvalidStateError" ) { return { code: "BLOCKED", retryable: true }; } if (error.name === "NotAllowedError" || error.name === "SecurityError") { return { code: "PERMISSION_DENIED", retryable: false }; } if (error.name === "AbortError") { return { code: "ABORTED", retryable: false }; } if (error.name === "NotSupportedError") { return { code: "UNSUPPORTED", retryable: false }; } } return { code: "UNAVAILABLE", retryable: true }; } function isSyncUnsupported(error: unknown): boolean { return ( error instanceof TypeError || (error instanceof DOMException && error.name === "NotSupportedError") ); } function isNotFound(error: unknown): boolean { return error instanceof DOMException && error.name === "NotFoundError"; } function hasRequestId( value: unknown, ): value is Readonly<{ requestId: string }> { return Boolean( value && typeof value === "object" && "requestId" in value && typeof value.requestId === "string" && value.requestId.length > 0 && value.requestId.length <= 128, ); } /** * NS-05. Reads a request envelope's correlation exactly once, through its own * data descriptors, so a reply can name the request it answers even when the * runtime that would have handled it never came up. An envelope whose kind * cannot be read stays a protocol-level failure rather than borrowing an * unrelated kind. */ function requestCorrelation( value: Readonly<{ requestId: string }>, ): Readonly<{ requestId: string; kind: OpfsWorkerRequest["kind"] }> { let kind: unknown; try { kind = Object.getOwnPropertyDescriptor(value, "kind")?.value; } catch { kind = undefined; } return { requestId: value.requestId, kind: typeof kind === "string" && WORKER_REQUEST_KINDS.has(kind) ? (kind as OpfsWorkerRequest["kind"]) : "CAPABILITIES", }; } function isWorkerRequest(value: unknown): value is OpfsWorkerRequest { return Boolean( hasRequestId(value) && "kind" in value && typeof value.kind === "string" && WORKER_REQUEST_KINDS.has(value.kind) && // STO-07. A page from another release must not be served. "protocolVersion" in value && value.protocolVersion === OPFS_WORKER_PROTOCOL_VERSION, ); } const MAX_MANIFEST_BYTES = 1024 * 1024; const SAFE_TRANSACTION_ID = /^[A-Za-z0-9_-]{8,128}$/u; const SAFE_PATH_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/u; const SHA256_HEX = /^[a-f0-9]{64}$/u; const WORKER_REQUEST_KINDS = new Set([ "CAPABILITIES", "BEGIN_PUT", "APPEND_CHUNK", "FINISH_PUT", "ABORT_PUT", "VERIFY_OBJECT", "READ_CHUNK", "REMOVE_OBJECT", "CLEANUP_TRANSACTION", "FINALIZE_PUT", "LIST_ORPHAN_CANDIDATES", "DELETE_ORPHAN_CHUNK", ]); function extractReceiptDigests( receipt: unknown, ): readonly string[] | null { if (!receipt || typeof receipt !== "object") return null; const record = receipt as Record; let chunks: unknown; if (Array.isArray(record.chunks)) { chunks = record.chunks; } else if ( record.preparedObject && typeof record.preparedObject === "object" && "chunks" in record.preparedObject ) { chunks = record.preparedObject.chunks; } else { return null; } if (!Array.isArray(chunks)) return null; const digests: string[] = []; for (const chunk of chunks) { if ( !chunk || typeof chunk !== "object" || !("digestHex" in chunk) || typeof chunk.digestHex !== "string" || !SHA256_HEX.test(chunk.digestHex) ) { return null; } digests.push(chunk.digestHex); } return digests; }