/** * WebGPUDeterminismHarness — cross-adapter replay-determinism harness * for Paper 3 (CRDT) Property 4 empirical closure (path a). * * Pre-registered protocol: * research/2026-04-20_webgpu-determinism-protocol.md (ai-ecosystem) * * This harness runs a small real WebGPU compute kernel that folds a CAEL * trace into a canonical u32 state vector. The kernel is intentionally * narrow: it proves browser/device acquisition, WGSL compilation, * storage-buffer upload, compute dispatch, readback, and deterministic * final-state hashing without pretending to be the full production solver. * * The same-adapter replay path is already tested in * packages/engine/src/simulation/__tests__/paper-multi-agent-crdt.test.ts * (Experiment 3: Dispute resolution via CAEL replay) * but runs in Node.js via CAELReplayer (no WebGPU). This harness lifts * that replay into a WebGPU compute context so the cross-adapter * comparison the audit called for can actually be measured. * * Why the split exists: * - Node-side CAELReplayer: fast, CPU-bound, for correctness testing * of the replay *logic* (hash chain, event ordering, state * reconstruction). Already has 27-test coverage per paper-3 * commit c185c11. * - Browser-side WebGPUDeterminismHarness (this module): slow, * GPU-bound, specifically for measuring whether compute-shader * reduction order is stable across the WebGPU adapters listed * in the protocol's vendor matrix. * * The Node-side replay always produces bit-identical results (IEEE-754 * CPU math is deterministic per platform). The browser-side replay * is what the audit actually challenges, because WebGPU's reduction * order is implementation-defined. * * **Mock mode (CI / wiring):** set `WEBGPU_HARNESS_MOCK=1` (Node) or * `globalThis.__WEBGPU_HARNESS_MOCK__ = true` (browser) to emit a * structurally valid `HarnessArtifact` with SHA-256 digests derived from * the trace (no GPU). All replications share the same digest so * self-consistency checks pass. Mock mode is rejected when * `productionEvidence` is true. */ import type { CAELTrace } from '../simulation/CAELTrace'; declare const HARNESS_WORKGROUP_SIZE = 64; declare const HARNESS_KERNEL_NAME = "cael-trace-fold-v1"; export declare const HARNESS_OUTPUT_WORDS = 16; export declare const HARNESS_WGSL = "\nstruct TraceRow {\n a: u32,\n b: u32,\n c: u32,\n d: u32,\n};\n\nstruct Params {\n traceLength: u32,\n scenarioSalt: u32,\n replication: u32,\n _pad: u32,\n};\n\n@group(0) @binding(0) var traceRows: array;\n@group(0) @binding(1) var finalState: array, 16>;\n@group(0) @binding(2) var params: Params;\n\nfn mix32(input: u32) -> u32 {\n var x = input;\n x = x ^ (x >> 16u);\n x = x * 0x7feb352du;\n x = x ^ (x >> 15u);\n x = x * 0x846ca68bu;\n x = x ^ (x >> 16u);\n return x;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3) {\n let i = gid.x;\n if (i >= params.traceLength) {\n return;\n }\n\n let row = traceRows[i];\n var v = mix32(row.a ^ params.scenarioSalt);\n v = mix32(v + row.b + (i * 0x85ebca6bu));\n v = mix32(v ^ row.c);\n v = mix32(v + row.d);\n\n // Keep operation domains disjoint. XOR reductions use slots 0..7 and\n // additive counters use slots 8..15, so every contended slot is updated\n // by one associative/commutative u32 operation only.\n atomicXor(&finalState[i % 8u], v);\n atomicAdd(&finalState[8u + (i % 8u)], mix32(v ^ 0xc2b2ae35u));\n}\n"; /** One of the vendor matrix rows from the protocol. */ export type AdapterTag = 'intel-uhd' | 'nvidia-rtx3060' | 'apple-m' | 'amd-rdna' | 'qualcomm-adreno' | 'swiftshader'; /** Serialized adapter identity as captured at run time (for the JSON artifact). */ export interface AdapterIdentity { /** Label matching the protocol's vendor matrix row. */ readonly tag: AdapterTag; /** `GPUAdapterInfo.vendor` at run time (may be empty string). */ readonly vendor: string; /** `GPUAdapterInfo.device` / description string at run time. */ readonly device: string; /** Driver version string if the UA exposes it; empty if unknown. */ readonly driver: string; /** Browser UA string at run time. */ readonly userAgent: string; } /** WebGPU kernel metadata captured with each evidence artifact. */ export interface HarnessKernelMetadata { readonly name: typeof HARNESS_KERNEL_NAME; readonly workgroupSize: typeof HARNESS_WORKGROUP_SIZE; readonly wgslBytes: number; } /** Digest + timing for one replay of one scenario on one adapter. */ export interface ReplicationResult { /** SHA-256 of the canonical final-state byte stream (protocol §Primary DV). */ readonly finalStateDigest: string; /** Wall-clock ms for the replay (not including init). */ readonly wallMs: number; /** WGSL compile time ms captured by the harness. */ readonly wgslCompileMs: number; /** Optional field-wise final state, for semantic-tolerance (H2) path. */ readonly finalStateFields?: Readonly>; } /** One scenario's full result: N replications. */ export interface ScenarioResult { readonly scenario: string; readonly traceLength: number; readonly replications: readonly ReplicationResult[]; } /** The top-level artifact the harness emits — matches protocol §Reporting format. */ export interface HarnessArtifact { readonly protocol: '2026-04-20_webgpu-determinism-protocol'; readonly protocolCommit: string; readonly executionMode: 'webgpu' | 'mock'; readonly browser: string; readonly host: string; readonly adapter: AdapterIdentity; readonly kernel: HarnessKernelMetadata; readonly scenarios: Readonly>; /** UNIX ms timestamp at artifact creation. */ readonly collectedAtMs: number; } /** Input knob for running the harness from a test page / Playwright driver. */ export interface HarnessConfig { /** Traces to replay, keyed by scenario name (matches protocol §Design). */ readonly traces: Readonly>; /** Replications per adapter (protocol default: 5). */ readonly replications: number; /** Adapter tag label; the harness uses this to label the artifact, not to select. */ readonly adapterTag: AdapterTag; /** Host label (e.g. 'founder-laptop-H1'). */ readonly host: string; /** Whether to capture per-field final state (for H2 semantic-tolerance path). */ readonly captureFields: boolean; /** Commit hash of the protocol doc at time of run (for artifact integrity). */ readonly protocolCommit: string; /** Production paper evidence must fail if mock mode is enabled. */ readonly productionEvidence?: boolean; } /** * The harness entry point a test page invokes. Returns the structured * artifact that a Playwright driver reads back via `window.__result__`. * * Contract: * 1. Acquire a WebGPU adapter + device via `navigator.gpu.requestAdapter()` * with `powerPreference: 'high-performance'`. If unavailable, throw * `WebGPUUnavailableError` — driver should fail fast and skip this row. * 2. Capture adapter identity into `AdapterIdentity` from * `adapter.requestAdapterInfo()` (if available) + `navigator.userAgent`. * 3. For each scenario in `config.traces`: * For each of `config.replications`: * 3a. Reset device state (fresh command encoder, fresh buffers). * 3b. Compile the fixed `cael-trace-fold-v1` WGSL kernel. * 3c. Project each CAEL entry into canonical u32 trace rows. * 3d. Dispatch the kernel and await device queue completion. * 3e. Read back final state; compute canonical-byte SHA-256. * 3f. If `captureFields`, keep JSON-safe per-field numeric arrays. * 4. Assemble `HarnessArtifact` and return. * * Determinism invariants the harness MUST enforce (regardless of adapter): * - Same RNG seed per replication within a scenario (so inter-run * variance at same adapter is zero; this is the self-consistency * check the protocol requires before any cross-adapter claim). * - Same workgroup/subgroup sizes across adapters (don't size by * adapter limits — the whole point is to isolate reduction-order * variance, not dispatch-shape variance). * - Same buffer binding order, same dispatch order, same field- * serialization order. * * Anything that varies across adapters must be the adapter's own * choice (reduction order, subgroup width chosen by the compiler, * memory layout), not something the harness introduces. */ export declare function isHarnessMockMode(): boolean; export declare function runDeterminismHarness(config: HarnessConfig): Promise; /** * Cross-adapter comparison helper: given artifacts from N adapters for * the same scenario set, determine whether H0 (bit-identical) holds or * H2 (epsilon-equivalent) is needed. * * Pure function — can run in Node after the in-browser harness has * dumped JSON artifacts. */ export declare function compareAdapterArtifacts(artifacts: readonly HarnessArtifact[]): CrossAdapterVerdict; export interface CrossAdapterVerdict { readonly verdict: 'H0_HOLDS' | 'H0_REJECTED_H2_PENDING' | 'H2_HOLDS' | 'H2_REJECTED' | 'HARNESS_BUG'; readonly reason: string; readonly selfConsistencyFailures: ReadonlyArray<{ adapter: AdapterTag; scenario: string; }>; readonly perScenarioH0: Readonly>; readonly perScenarioH2: Readonly>; readonly h0FailureScenarios?: readonly string[]; } export declare class WebGPUUnavailableError extends Error { constructor(message?: string); } export declare class WebGPUProductionEvidenceMockError extends Error { constructor(message: string); } export declare class WebGPUHarnessNotImplementedError extends Error { constructor(message: string); } export {}; //# sourceMappingURL=WebGPUDeterminismHarness.d.ts.map