import { assertValidStoragePolicy, isValidByteLength, type BrowserDataFailureCode, type BrowserDataOperation, type BrowserStoragePolicy, } from "../../../application/ports/browser-file-storage/shared.ts"; import type { OpfsStorageScope } from "../../../application/ports/browser-file-storage/opfs-ports.ts"; export type OpfsRuntimePolicy = Readonly<{ rootDirectoryName: string; mutationLockName: string; chunkSizeBytes: number; maxObjectBytes: number; maxChunkCount: number; rpcTimeoutMs: number; reconciliationBudgetMs: number; reconciliationBatchSize: number; orphanGracePeriodMs: number; orphanGcBatchSize: number; maxCancellationTombstones: number; allowAsyncWritableChunkFallback: boolean; isObjectIdAllowed: (objectId: string) => boolean; isMediaTypeAllowed: (mediaType: string) => boolean; }>; export type OpfsSafeObservation = Readonly<{ operation: BrowserDataOperation; /** * NS-04. `DEGRADED` is a committed effect whose bookkeeping is unsettled: * the payload is durable but a reconciler still owns the transaction. */ outcome: "STARTED" | "SUCCEEDED" | "FAILED" | "DEGRADED"; failureCode?: BrowserDataFailureCode; byteBucket?: "0" | "1B_1MiB" | "1MiB_16MiB" | "16MiB_256MiB" | "GT_256MiB"; transactionBucket?: "0" | "1_10" | "11_100" | "GT_100"; }>; export type OpfsSafeObserver = (observation: OpfsSafeObservation) => void; const SAFE_SEGMENT = /^[a-z0-9][a-z0-9._-]{0,63}$/u; const OPAQUE_OBJECT_ID = /^[A-Za-z0-9_-]{8,128}$/u; const MEDIA_TYPE = /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+(?:\s*;.*)?$/iu; export const DEFAULT_OPFS_RUNTIME_POLICY: OpfsRuntimePolicy = Object.freeze({ rootDirectoryName: "ca-frontend-opfs-v1", mutationLockName: "ca-frontend-opfs-v1:mutation", chunkSizeBytes: 4 * 1024 * 1024, maxObjectBytes: 2 * 1024 * 1024 * 1024, maxChunkCount: 512, rpcTimeoutMs: 60_000, reconciliationBudgetMs: 5_000, reconciliationBatchSize: 100, orphanGracePeriodMs: 24 * 60 * 60 * 1_000, orphanGcBatchSize: 100, maxCancellationTombstones: 1_024, allowAsyncWritableChunkFallback: true, isObjectIdAllowed: (objectId) => OPAQUE_OBJECT_ID.test(objectId), isMediaTypeAllowed: (mediaType) => MEDIA_TYPE.test(mediaType), }); export function resolveOpfsRuntimePolicy( policy: Partial = {}, ): OpfsRuntimePolicy { const resolved: OpfsRuntimePolicy = Object.freeze({ ...DEFAULT_OPFS_RUNTIME_POLICY, ...policy, }); assertOpfsRuntimePolicy(resolved); return resolved; } export function assertOpfsRuntimePolicy(policy: OpfsRuntimePolicy): void { if ( !SAFE_SEGMENT.test(policy.rootDirectoryName) || policy.mutationLockName.length === 0 || !Number.isSafeInteger(policy.chunkSizeBytes) || policy.chunkSizeBytes < 64 * 1024 || policy.chunkSizeBytes > 64 * 1024 * 1024 || !isValidByteLength(policy.maxObjectBytes) || policy.maxObjectBytes < policy.chunkSizeBytes || !Number.isSafeInteger(policy.maxChunkCount) || policy.maxChunkCount < 1 || policy.maxObjectBytes > policy.chunkSizeBytes * policy.maxChunkCount || !Number.isSafeInteger(policy.rpcTimeoutMs) || policy.rpcTimeoutMs < 1_000 || !Number.isSafeInteger(policy.reconciliationBudgetMs) || policy.reconciliationBudgetMs < 1 || policy.reconciliationBudgetMs > 60_000 || !Number.isSafeInteger(policy.reconciliationBatchSize) || policy.reconciliationBatchSize < 1 || policy.reconciliationBatchSize > 1_000 || !Number.isSafeInteger(policy.orphanGracePeriodMs) || policy.orphanGracePeriodMs < 60_000 || !Number.isSafeInteger(policy.orphanGcBatchSize) || policy.orphanGcBatchSize < 1 || policy.orphanGcBatchSize > 1_000 || !Number.isSafeInteger(policy.maxCancellationTombstones) || policy.maxCancellationTombstones < 16 || policy.maxCancellationTombstones > 10_000 || typeof policy.isObjectIdAllowed !== "function" || typeof policy.isMediaTypeAllowed !== "function" ) { throw new TypeError("OPFS runtime policy is invalid."); } } export function validateObjectWriteInput( input: Readonly<{ scope: OpfsStorageScope; objectId: string; expectedGeneration: number | null; mediaType: string; byteLength: number | null; storagePolicy: Parameters[0]; }>, policy: OpfsRuntimePolicy, ): boolean { try { assertValidStoragePolicy(input.storagePolicy); } catch { return false; } return ( isValidOpfsStorageScope(input.scope) && input.scope.namespace === input.storagePolicy.namespace && policy.isObjectIdAllowed(input.objectId) && policy.isMediaTypeAllowed(input.mediaType) && (input.expectedGeneration === null || (Number.isSafeInteger(input.expectedGeneration) && input.expectedGeneration > 0)) && input.byteLength !== null && isValidByteLength(input.byteLength) && input.byteLength <= policy.maxObjectBytes && Math.ceil(input.byteLength / policy.chunkSizeBytes) <= policy.maxChunkCount ); } export function isValidOpfsStorageScope( scope: OpfsStorageScope, ): boolean { return ( scope.namespace.length > 0 && scope.namespace.length <= 64 && OPAQUE_OBJECT_ID.test(scope.authorityToken) && OPAQUE_OBJECT_ID.test(scope.namespaceToken) && OPAQUE_OBJECT_ID.test(scope.partitionToken) ); } /** * Captures the registry binding at composition time. Callers may own mutable * config objects, so no OPFS operation is allowed to retain those references. */ export function snapshotOpfsStorageScope( input: OpfsStorageScope, ): OpfsStorageScope { try { const snapshot: OpfsStorageScope = Object.freeze({ namespace: input.namespace, authorityToken: input.authorityToken, namespaceToken: input.namespaceToken, partitionToken: input.partitionToken, }); if (!isValidOpfsStorageScope(snapshot)) throw new TypeError(); return snapshot; } catch { throw new TypeError("OPFS storage scope is invalid."); } } /** * Deep enough for the closed BrowserStoragePolicy contract: retention is the * only nested value. Fields are copied explicitly so later caller mutation or * extension properties cannot alter the bound policy fingerprint. */ export function snapshotOpfsStoragePolicy( input: BrowserStoragePolicy, ): BrowserStoragePolicy { try { const retention: BrowserStoragePolicy["retention"] = input.retention.kind === "TTL" ? Object.freeze({ kind: "TTL", maxAgeMs: input.retention.maxAgeMs, }) : Object.freeze({ kind: input.retention.kind }); const snapshot: BrowserStoragePolicy = Object.freeze({ owner: input.owner, namespace: input.namespace, classification: input.classification, authority: input.authority, accountScope: input.accountScope, retention, softBudgetBytes: input.softBudgetBytes, hardBudgetBytes: input.hardBudgetBytes, evictionPriority: input.evictionPriority, logoutAction: input.logoutAction, accountDeletionAction: input.accountDeletionAction, pressureAction: input.pressureAction, unavailableFallback: input.unavailableFallback, }); assertValidStoragePolicy(snapshot); return snapshot; } catch { throw new TypeError("OPFS storage policy is invalid."); } } export function byteBucket( byteLength: number, ): NonNullable { if (byteLength === 0) return "0"; if (byteLength <= 1024 * 1024) return "1B_1MiB"; if (byteLength <= 16 * 1024 * 1024) return "1MiB_16MiB"; if (byteLength <= 256 * 1024 * 1024) return "16MiB_256MiB"; return "GT_256MiB"; } export function transactionBucket( count: number, ): NonNullable { if (count === 0) return "0"; if (count <= 10) return "1_10"; if (count <= 100) return "11_100"; return "GT_100"; } export function observeOpfsSafely( observer: OpfsSafeObserver | undefined, observation: OpfsSafeObservation, ): void { try { observer?.(Object.freeze({ ...observation })); } catch { // Persistence behavior never depends on observability. } }