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
@@ -93,8 +93,8 @@ Rollout state starts at `NOT_STARTED`; documented-unimplemented items start at
| STO-03 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | composition rejection of an existing policy | Red 4 cases across STO-03..05 → green 21/21; `check:types` PASS; `check:browser-file-storage-boundaries` PASS; `lint` PASS |
| STO-04 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | restage loop or bandwidth spike | — |
| STO-05 | Public cache not composed | `corepack pnpm exec vitest run tests/unit/public-response-cache.test.ts` | `fix: make public cache staging repairable` | `FIXED_NOT_RELEASED` | activation permitted without required capability | — |
| STO-06 | IndexedDB maintenance | `corepack pnpm exec vitest run tests/unit/indexeddb-maintenance.test.ts` | — | `NOT_STARTED` | migration checkpoint stall | — |
| STO-07 | OPFS worker protocol | `corepack pnpm exec vitest run tests/unit/opfs-worker-runtime.test.ts` | — | `NOT_STARTED` | page/worker `INCOMPATIBLE` spike | — |
| STO-06 | IndexedDB maintenance | `corepack pnpm exec vitest run tests/unit/indexeddb-maintenance.test.ts` | `fix: bound migration commits and version the OPFS worker protocol` | `FIXED_NOT_RELEASED` | migration checkpoint stall | Red commit-phase deadline case → green 13/13; the monotonic budget is re-checked before each record's first write, a started record still finishes atomically, and a clock failure aborts the transaction |
| STO-07 | OPFS worker protocol | `corepack pnpm exec vitest run tests/unit/opfs-worker-runtime.test.ts` | `fix: bound migration commits and version the OPFS worker protocol` | `FIXED_NOT_RELEASED` | page/worker `INCOMPATIBLE` spike | Green 23/23 across the OPFS suites; every envelope carries `OPFS_WORKER_PROTOCOL_VERSION = 2` and the response echoes its request kind, with a strict failure-shape decoder. A kind or version mismatch closes as `UNSUPPORTED` — the closed taxonomy has no `INCOMPATIBLE` code and none was invented |
| STO-08 | Hypothesis; browser characterization required | `corepack pnpm exec playwright test --config playwright.capabilities.config.ts tests/browser-capabilities/browser-files.spec.ts` | none (source unchanged) | `UNVERIFIED` | n/a until characterized | chromium 2/2 PASS; webkit could not launch (`libevent-2.1-7t64`, `libavif16` missing — environmental). The existing spec does not exercise `Window.showOpenFilePicker`/`showSaveFilePicker`, which need a user gesture and a native dialog, so the receiver-binding hypothesis is **neither reproduced nor refuted**. No `SystemPickerHost` was introduced: the plan forbids implementing an uncharacterized hypothesis as a defect. |
| GAP-01 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
| GAP-02 | Documented unimplemented (VD-15) | promotion evidence, not a red test | — | `PROMOTION_BLOCKED` | n/a | — |
@@ -148,7 +148,7 @@ Rollout state starts at `NOT_STARTED`; documented-unimplemented items start at
| SW-07 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | activation blocked with zero clients | — |
| SW-08 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | per-client failure escalation | — |
| SW-09 | Composed when capability selected | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | late install work observed | — |
| SW-10 | Protocol V2 migration | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `NOT_STARTED` | V1/V2 mismatch fail-close | |
| SW-10 | Protocol V2 migration | `corepack pnpm exec vitest run tests/unit/service-worker-runtime.test.ts` | — | `DEFERRED_TO_MIGRATION` | V1/V2 mismatch fail-close | Not closed here. Full-identity protocol V2 is an expand → dual-read → old-writer drain → contract deployment that spans releases; the prerequisite shared manifest codec and canonical digest landed with SW-05. |
| WP-01 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | `fix: bind Web Push mutations to exact authority` | `FIXED_NOT_RELEASED` | CAS receipt rejection | Red stale/skipped/huge revision receipts → green; write and remove share one exact-next-revision validator |
| WP-02 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-fence-store.test.ts` | — | `DEFERRED_TO_MIGRATION` | `RECONCILIATION_REQUIRED` backlog | Deferred to the Task 16 versioned-migration PR: `MUTATION_OUTCOME_UNKNOWN` and the `RECONCILIATION_REQUIRED` lifecycle are part of the same wire/data migration as WP-03. |
| WP-03 | Web Push `NOT_SELECTED` | `corepack pnpm exec vitest run tests/unit/web-push-subscription-adapter.test.ts` | — | `DEFERRED_TO_MIGRATION` | backend receipt mismatch | Deferred to the Task 16 versioned-migration PR: the V2 receipt requires server request-shape negotiation before a client rollout. |
@@ -972,6 +972,7 @@ export function createIndexedDbMaintenance<WireValue>(
prepared: readonly PreparedRecord<WireValue>[],
scan: ScanBatch,
budgetExhausted: boolean,
deadline: number,
signal: AbortSignal | undefined,
): Promise<
BrowserDataResult<IndexedDbMaintenanceBatchReceipt>
@@ -1048,6 +1049,22 @@ export function createIndexedDbMaintenance<WireValue>(
finishWithCheckpoint();
return;
}
// STO-06. The commit chain runs entirely inside IndexedDB callbacks,
// so without this check up to `maxRows` records keep executing past
// the caller's invocation deadline. The budget is only ever checked
// before a record's first write, so a started record still finishes
// atomically and the checkpoint stays exactly at the last safe key.
const currentTime = clock();
if (!currentTime.ok) {
// A broken clock aborts rather than committing an unbounded batch.
context.fail(currentTime);
return;
}
if (currentTime.value >= deadline) {
budgetExhausted = true;
finishWithCheckpoint();
return;
}
let request: IDBRequest<unknown>;
try {
request = records.get(preparedRecord.source.key);
@@ -1337,6 +1354,7 @@ export function createIndexedDbMaintenance<WireValue>(
prepared.value.records,
scan.value,
prepared.value.budgetExhausted,
deadline,
input.signal,
);
return observeResult(
@@ -14,6 +14,9 @@ import {
browserDataSuccess,
} from "../../browser-file-storage/result.ts";
import type { OpfsRuntimePolicy } from "./opfs-policy.ts";
import {
OPFS_WORKER_PROTOCOL_VERSION,
} from "./opfs-worker-protocol.ts";
import type {
AbortPreparedPutRequest,
OpfsWorkerGateway,
@@ -51,6 +54,7 @@ export type OwnedOpfsWorkerClient = Readonly<{
}>;
type PendingRequest = Readonly<{
expectedKind: OpfsWorkerRequest["kind"];
resolve: (response: OpfsWorkerResponse) => void;
reject: (error: OpfsRpcError) => void;
timeout: ReturnType<typeof setTimeout>;
@@ -93,6 +97,14 @@ export function createOpfsWorkerGateway(
pending.delete(event.data.requestId);
clearTimeout(request.timeout);
request.removeAbortListener();
if (event.data.kind !== request.expectedKind) {
// STO-07. A reply for a different operation is a protocol breach, not a
// value: close it as INCOMPATIBLE instead of decoding it.
// UNSUPPORTED is the closed-taxonomy code for "this runtime
// cannot serve this"; no new failure code is invented.
request.reject(new OpfsRpcError("UNSUPPORTED"));
return;
}
request.resolve(event.data);
};
const onWorkerFailure = (): void => {
@@ -121,7 +133,11 @@ export function createOpfsWorkerGateway(
) {
throw new OpfsRpcError("UNAVAILABLE");
}
const message = { ...request, requestId } as OpfsWorkerRequest;
const message = {
...request,
requestId,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
} as OpfsWorkerRequest;
return await new Promise<OpfsWorkerResponse>((resolve, reject) => {
const abort = (): void => {
@@ -141,6 +157,9 @@ export function createOpfsWorkerGateway(
reject(new OpfsRpcError("UNAVAILABLE"));
}, dependencies.policy.rpcTimeoutMs);
pending.set(requestId, {
// STO-07. The expected kind is stored so a reply for a different
// operation can never satisfy this request.
expectedKind: request.kind,
resolve,
reject,
timeout,
@@ -677,13 +696,35 @@ function parseOrphanDeleteResult(
return value as OpfsOrphanDeleteResult;
}
/**
* STO-07. A response is only admitted when it carries the negotiated protocol
* version, echoes a known request kind and, on failure, a closed failure code.
* Accepting `{requestId, ok}` alone let a malformed or cross-release reply be
* decoded as a value of the wrong shape.
*/
function isWorkerResponse(value: unknown): value is OpfsWorkerResponse {
if (
!value ||
typeof value !== "object" ||
!("requestId" in value) ||
typeof value.requestId !== "string" ||
!("ok" in value) ||
typeof value.ok !== "boolean" ||
!("protocolVersion" in value) ||
value.protocolVersion !== OPFS_WORKER_PROTOCOL_VERSION ||
!("kind" in value) ||
typeof value.kind !== "string"
) {
return false;
}
if (value.ok) return true;
const failure = (value as { failure?: unknown }).failure;
return Boolean(
value &&
typeof value === "object" &&
"requestId" in value &&
typeof value.requestId === "string" &&
"ok" in value &&
typeof value.ok === "boolean",
failure &&
typeof failure === "object" &&
"code" in failure &&
typeof (failure as { code?: unknown }).code === "string" &&
"retryable" in failure &&
typeof (failure as { retryable?: unknown }).retryable === "boolean",
);
}
@@ -14,13 +14,27 @@ import type {
TransferProgress,
} from "../../../application/ports/browser-file-storage/shared.ts";
/**
* STO-07. Every request and response carries the protocol version and the
* response echoes its request kind, so a page/worker release mismatch or a
* malformed reply closes as `INCOMPATIBLE` instead of being decoded as a
* successful value of the wrong shape.
*/
export const OPFS_WORKER_PROTOCOL_VERSION = 2 as const;
export type OpfsWorkerRequestEnvelope = Readonly<{
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
}>;
export type OpfsWorkerRequest =
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "CAPABILITIES";
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "BEGIN_PUT";
transactionId: string;
scope: OpfsStorageScope;
@@ -36,6 +50,7 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "APPEND_CHUNK";
scope: OpfsStorageScope;
transactionId: string;
@@ -44,12 +59,14 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "FINISH_PUT";
scope: OpfsStorageScope;
transactionId: string;
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "ABORT_PUT";
scope: OpfsStorageScope;
transactionId: string;
@@ -57,17 +74,20 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "VERIFY_OBJECT";
preparedObject: OpfsPreparedObject;
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "READ_CHUNK";
preparedObject: OpfsPreparedObject;
sequence: number;
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "REMOVE_OBJECT";
scope: OpfsStorageScope;
objectId: string;
@@ -75,6 +95,7 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "CLEANUP_TRANSACTION";
scope: OpfsStorageScope;
transactionId: string;
@@ -87,12 +108,14 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "FINALIZE_PUT";
transactionId: string;
preparedObject: OpfsPreparedObject;
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "LIST_ORPHAN_CANDIDATES";
scope: OpfsStorageScope;
olderThanEpochMs: number;
@@ -100,6 +123,7 @@ export type OpfsWorkerRequest =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: "DELETE_ORPHAN_CHUNK";
scope: OpfsStorageScope;
digestHex: string;
@@ -109,7 +133,7 @@ export type OpfsWorkerRequest =
export type OpfsWorkerRequestBody =
OpfsWorkerRequest extends infer Request
? Request extends OpfsWorkerRequest
? Omit<Request, "requestId">
? Omit<Request, "requestId" | "protocolVersion">
: never
: never;
@@ -132,6 +156,8 @@ export type OpfsOrphanDeleteResult = Readonly<{
export type OpfsWorkerResponse =
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: OpfsWorkerRequest["kind"];
ok: true;
value?:
| OpfsCapabilities
@@ -144,6 +170,8 @@ export type OpfsWorkerResponse =
}>
| Readonly<{
requestId: string;
protocolVersion: typeof OPFS_WORKER_PROTOCOL_VERSION;
kind: OpfsWorkerRequest["kind"];
ok: false;
failure: OpfsWorkerFailure;
}>;
@@ -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,
);
}
+31
View File
@@ -596,6 +596,37 @@ describe("IndexedDB bounded codec maintenance", () => {
});
});
it("stops codec migration commit at the cooperative deadline", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "row-a", "first");
seedLegacy(memory, "row-b", "second");
const policy = defaultPolicy();
// STO-06. The clock only advances past the deadline once the commit
// transaction is already open, so the stop must happen inside the commit
// chain rather than before transform.
let calls = 0;
const maintenance = createMaintenance(memory, policy, {
now: () => {
calls += 1;
// Scan, prepare and the first commit record stay inside the budget.
return calls <= 6 ? 0 : 5_000;
},
});
const result = await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 1_000,
});
expect(result.ok).toBe(true);
if (!result.ok) return;
// The batch is incomplete and says so; it never claims a full pass.
expect(result.value.state).toBe("MORE");
expect(result.value.budgetExhausted).toBe(true);
expect(result.value.checkpointedRows).toBeLessThan(2);
});
it("fails closed when a historical payload cannot be transformed", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
+31
View File
@@ -15,6 +15,7 @@ import {
createOpfsWorkerGateway,
type OpfsWorkerLike,
} from "../../src/adapters/storage/opfs/opfs-worker-client.ts";
import { OPFS_WORKER_PROTOCOL_VERSION } from "../../src/adapters/storage/opfs/opfs-worker-protocol.ts";
import type {
OpfsWorkerRequest,
OpfsWorkerResponse,
@@ -181,6 +182,7 @@ function beginRequest(
): OpfsWorkerRequest {
return {
requestId,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "BEGIN_PUT",
transactionId,
scope,
@@ -268,6 +270,7 @@ describe("OPFS dedicated worker runtime", () => {
await Promise.resolve();
const aborted = await runtime.handleRequest({
requestId: "request_abort_1234",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "ABORT_PUT",
scope: scopeA,
transactionId: "transaction_12345678",
@@ -319,6 +322,7 @@ describe("OPFS dedicated worker runtime", () => {
const appending = runtime.handleRequest({
requestId: "request_append_5678",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "APPEND_CHUNK",
scope: scopeA,
transactionId: "transaction_56785678",
@@ -328,6 +332,7 @@ describe("OPFS dedicated worker runtime", () => {
await Promise.resolve();
const aborting = runtime.handleRequest({
requestId: "request_abort_5678",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "ABORT_PUT",
scope: scopeA,
transactionId: "transaction_56785678",
@@ -374,6 +379,7 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: `request_append_iso_${index}`,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "APPEND_CHUNK",
scope: targetScope,
transactionId,
@@ -386,6 +392,7 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_abort_iso_a",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "ABORT_PUT",
scope: scopeA,
transactionId,
@@ -393,6 +400,7 @@ describe("OPFS dedicated worker runtime", () => {
).toMatchObject({ ok: true });
const finished = await runtime.handleRequest({
requestId: "request_finish_iso_b",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "FINISH_PUT",
scope: scopeB,
transactionId,
@@ -402,11 +410,14 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_verify_iso_b",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "VERIFY_OBJECT",
preparedObject,
}),
).toEqual({
requestId: "request_verify_iso_b",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: expect.any(String),
ok: true,
value: true,
});
@@ -435,6 +446,7 @@ describe("OPFS dedicated worker runtime", () => {
);
await runtime.handleRequest({
requestId: `request_gc_append_${index}`,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "APPEND_CHUNK",
scope: targetScope,
transactionId,
@@ -444,6 +456,7 @@ describe("OPFS dedicated worker runtime", () => {
const object = preparedValue(
await runtime.handleRequest({
requestId: `request_gc_finish_${index}`,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "FINISH_PUT",
scope: targetScope,
transactionId,
@@ -452,6 +465,7 @@ describe("OPFS dedicated worker runtime", () => {
preparedByScope.set(targetScope.authorityToken, object);
await runtime.handleRequest({
requestId: `request_gc_finalize_${index}`,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "FINALIZE_PUT",
transactionId,
preparedObject: object,
@@ -464,6 +478,7 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_gc_list_a",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "LIST_ORPHAN_CANDIDATES",
scope: scopeA,
olderThanEpochMs: cutoff,
@@ -473,6 +488,7 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_gc_delete_a",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "DELETE_ORPHAN_CHUNK",
scope: scopeA,
digestHex,
@@ -483,22 +499,28 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_gc_verify_a",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "VERIFY_OBJECT",
preparedObject: preparedByScope.get(scopeA.authorityToken)!,
}),
).toEqual({
requestId: "request_gc_verify_a",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: expect.any(String),
ok: true,
value: false,
});
expect(
await runtime.handleRequest({
requestId: "request_gc_verify_b",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "VERIFY_OBJECT",
preparedObject: preparedByScope.get(scopeB.authorityToken)!,
}),
).toEqual({
requestId: "request_gc_verify_b",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: expect.any(String),
ok: true,
value: true,
});
@@ -516,10 +538,13 @@ describe("OPFS dedicated worker runtime", () => {
expect(
await runtime.handleRequest({
requestId: "request_caps_1234",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "CAPABILITIES",
}),
).toEqual({
requestId: "request_caps_1234",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: expect.any(String),
ok: true,
value: {
available: false,
@@ -569,6 +594,8 @@ describe("OPFS worker client lifecycle", () => {
listener?.({
data: {
requestId: "request_collision_1234",
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: "CAPABILITIES",
ok: true,
value: {
available: true,
@@ -633,11 +660,15 @@ describe("OPFS worker client lifecycle", () => {
message.kind === "VERIFY_OBJECT"
? {
requestId: message.requestId,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: message.kind,
ok: true,
value: true,
}
: {
requestId: message.requestId,
protocolVersion: OPFS_WORKER_PROTOCOL_VERSION,
kind: message.kind,
ok: true,
value: new Uint8Array([4, 2]).buffer,
};