fix: bound migration commits and version the OPFS worker protocol

STO-06: the IndexedDB codec migration commit chain runs entirely inside
IndexedDB callbacks, so up to maxRows records could keep executing past the
caller's cooperative deadline. The monotonic budget is now re-checked before
each record's first write; a started record still completes atomically, the
checkpoint advances only to the last safe key, and a clock failure aborts the
transaction rather than committing an unbounded batch.

STO-07: every OPFS worker request and response now carries
OPFS_WORKER_PROTOCOL_VERSION = 2, responses echo their request kind, and the
client validates the envelope and failure shape strictly while remembering the
expected kind per pending request. A page/worker release mismatch or a reply for
a different operation closes as UNSUPPORTED instead of being decoded as a value
of the wrong shape. UNSUPPORTED is used deliberately: the closed browser-data
taxonomy has no INCOMPATIBLE code and none was invented.

SW-10 and the OPFS/Web Push V2 wire rollouts remain deferred: they are
expand/dual-read/drain/contract deployments across releases rather than a single
in-repo change. The ledger records them as DEFERRED_TO_MIGRATION.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-14 00:41:12 +09:00
co-authored by Claude Opus 5
parent fce8e046ea
commit f6098242be
7 changed files with 205 additions and 21 deletions
@@ -15,6 +15,9 @@ import {
isValidOpfsStorageScope,
type OpfsRuntimePolicy,
} from "./opfs-policy.ts";
import {
OPFS_WORKER_PROTOCOL_VERSION,
} from "./opfs-worker-protocol.ts";
import type {
OpfsOrphanCandidateBatch,
OpfsOrphanDeleteResult,
@@ -202,18 +205,23 @@ export function createOpfsWorkerRuntime(
}
switch (request.kind) {
case "CAPABILITIES":
return success(request.requestId, capabilities);
return success(request.requestId, request.kind, capabilities);
case "BEGIN_PUT":
await beginPut(request);
return success(request.requestId);
return success(request.requestId, request.kind);
case "APPEND_CHUNK":
await appendChunk(request);
return success(request.requestId);
return success(request.requestId, request.kind);
case "FINISH_PUT":
return success(request.requestId, await finishPut(request));
return success(
request.requestId,
request.kind,
await finishPut(request),
);
case "ABORT_PUT":
return success(
request.requestId,
request.kind,
await abortPut(
request.scope,
request.transactionId,
@@ -223,11 +231,13 @@ export function createOpfsWorkerRuntime(
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,
@@ -239,10 +249,11 @@ export function createOpfsWorkerRuntime(
request.objectId,
request.generation,
);
return success(request.requestId);
return success(request.requestId, request.kind);
case "CLEANUP_TRANSACTION":
return success(
request.requestId,
request.kind,
await cleanupTransaction(
request.scope,
request.transactionId,
@@ -255,10 +266,11 @@ export function createOpfsWorkerRuntime(
request.transactionId,
request.preparedObject,
);
return success(request.requestId);
return success(request.requestId, request.kind);
case "LIST_ORPHAN_CANDIDATES":
return success(
request.requestId,
request.kind,
await listOrphanCandidates(
request.scope,
request.olderThanEpochMs,
@@ -268,6 +280,7 @@ export function createOpfsWorkerRuntime(
case "DELETE_ORPHAN_CHUNK":
return success(
request.requestId,
request.kind,
await deleteOrphanChunk(
request.scope,
request.digestHex,
@@ -1747,6 +1760,7 @@ function isPreparedObjectSafe(
function success(
requestId: string,
kind: OpfsWorkerRequest["kind"],
value?: OpfsWorkerResponse extends infer _Response
?
| OpfsCapabilities
@@ -1759,15 +1773,33 @@ function success(
: never,
): OpfsWorkerResponse {
return value === undefined
? { requestId, ok: true }
: { requestId, ok: true, value };
? {
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, ok: false, failure: workerFailure };
return {
requestId,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind,
ok: false,
failure: workerFailure,
};
}
function mapRuntimeFailure(error: unknown): OpfsWorkerFailure {
@@ -1829,7 +1861,10 @@ function isWorkerRequest(value: unknown): value is OpfsWorkerRequest {
hasRequestId(value) &&
"kind" in value &&
typeof value.kind === "string" &&
WORKER_REQUEST_KINDS.has(value.kind),
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,
);
}