/** * §16. Generic CPU Web Worker contract. * * The capability is `NOT_SELECTED` by default (§16.2). These types exist so a * selection can be expressed and validated, but no production worker entry is * created until a measured, CPU-bound task with an owner is contributed. This * runtime is separate from the OPFS dedicated worker and the Service Worker. */ export const WEB_WORKER_PROTOCOL = "CA_WEB_WORKER_V1" as const; export const WEB_WORKER_BOUNDS = Object.freeze({ taskGroups: 16, tasksPerGroup: 32, activeGroups: 4, queuedPerGroup: 32, queuedBytesPerGroup: 16 * 1024 * 1024, defaultInputBytes: 1024 * 1024, hardInputBytes: 8 * 1024 * 1024, defaultOutputBytes: 1024 * 1024, hardOutputBytes: 8 * 1024 * 1024, defaultDeadlineMs: 5_000, hardDeadlineMs: 30_000, cancelGraceMs: 250, idleTerminateMs: 60_000, restartsPerWindow: 3, restartWindowMs: 300_000, transferables: 16, mainThreadChunkBudgetMs: 8, mainThreadFallbackInputBytes: 1024 * 1024, }); export interface InstalledWorkerTask { readonly taskId: string; readonly taskVersion: 1; readonly maximumInputBytes: number; readonly maximumOutputBytes: number; readonly deadlineMs: number; } export interface InstalledWebWorkerContribution { readonly taskGroupId: string; readonly tasks: readonly InstalledWorkerTask[]; readonly fallback: "MAIN_THREAD_CHUNKED" | "UNSUPPORTED"; } export type WorkerRequestMessage = Readonly<{ protocol: typeof WEB_WORKER_PROTOCOL; kind: "EXECUTE"; taskId: string; taskVersion: 1; requestId: string; workerGeneration: number; deadlineEpochMs: number; payload: unknown; }>; export type WorkerCancelMessage = Readonly<{ protocol: typeof WEB_WORKER_PROTOCOL; kind: "CANCEL"; requestId: string; workerGeneration: number; }>; export type WorkerFailureCode = | "TASK_UNKNOWN" | "VERSION_UNSUPPORTED" | "INPUT_INVALID" | "INPUT_TOO_LARGE" | "OUTPUT_INVALID" | "OUTPUT_TOO_LARGE" | "QUEUE_FULL" | "DEADLINE_EXCEEDED" | "CANCELLED" | "CRASHED" | "TRANSFER_FAILED" | "STALE_RESULT" | "RUNTIME_PROTOCOL_FAILURE"; export type WorkerResponseMessage = | Readonly<{ protocol: typeof WEB_WORKER_PROTOCOL; kind: "SUCCESS"; requestId: string; workerGeneration: number; payload: unknown; }> | Readonly<{ protocol: typeof WEB_WORKER_PROTOCOL; kind: "FAILURE"; requestId: string; workerGeneration: number; code: WorkerFailureCode; }>; export function validateWebWorkerContributions( contributions: readonly InstalledWebWorkerContribution[], ): readonly InstalledWebWorkerContribution[] { const bounds = WEB_WORKER_BOUNDS; if (contributions.length > bounds.taskGroups) { throw new TypeError("Web Worker task groups exceed their bound."); } const groupIds = new Set(); const taskIds = new Set(); for (const contribution of contributions) { if (!contribution.taskGroupId || groupIds.has(contribution.taskGroupId)) { throw new TypeError("Web Worker task group identity is invalid."); } groupIds.add(contribution.taskGroupId); if ( contribution.tasks.length === 0 || contribution.tasks.length > bounds.tasksPerGroup ) { throw new TypeError("Web Worker task count is out of range."); } for (const task of contribution.tasks) { const qualified = `${contribution.taskGroupId}/${task.taskId}`; if (!task.taskId || taskIds.has(qualified)) { throw new TypeError("Duplicate Web Worker task."); } taskIds.add(qualified); if ( task.taskVersion !== 1 || !Number.isSafeInteger(task.maximumInputBytes) || task.maximumInputBytes < 1 || task.maximumInputBytes > bounds.hardInputBytes || !Number.isSafeInteger(task.maximumOutputBytes) || task.maximumOutputBytes < 1 || task.maximumOutputBytes > bounds.hardOutputBytes || !Number.isSafeInteger(task.deadlineMs) || task.deadlineMs < 1 || task.deadlineMs > bounds.hardDeadlineMs ) { throw new TypeError(`Web Worker task bounds invalid: ${qualified}`); } } } return Object.freeze([...contributions]); }