Checkpoint web parity through Chromium input tasks
This commit is contained in:
73
web/protocol/io-format-recovery.ts
Normal file
73
web/protocol/io-format-recovery.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export const IO_FORMAT_RECOVERY_SCHEMA_VERSION = 1 as const;
|
||||
export type IOFormat = "OBJ" | "STL" | "PLY";
|
||||
export type IOFormatRecoveryStatus = "RUNNING" | "CANCELLED" | "BLOCKED" | "COMMITTED" | "RECOVERED";
|
||||
export type IOFormatRecoveryErrorCode = "IO_FORMAT_OPERATION_CANCELLED" | "IO_FORMAT_OOM" | "IO_FORMAT_WORKER_RESTARTED" | "IO_FORMAT_RECOVERY_INVALID";
|
||||
|
||||
export interface IOFormatRecoveryReceipt {
|
||||
schemaVersion: typeof IO_FORMAT_RECOVERY_SCHEMA_VERSION;
|
||||
operationId: string;
|
||||
format: IOFormat;
|
||||
operation: "IMPORT" | "EXPORT";
|
||||
status: IOFormatRecoveryStatus;
|
||||
workerGeneration: number;
|
||||
baseRevision: number;
|
||||
candidateRevision: number;
|
||||
inputBytes: number;
|
||||
inputSha256: string;
|
||||
outputBytes: number;
|
||||
outputSha256: string | null;
|
||||
temporaryBytes: number;
|
||||
liveRequests: number;
|
||||
publishedResults: number;
|
||||
committed: boolean;
|
||||
errorCode?: IOFormatRecoveryErrorCode;
|
||||
}
|
||||
|
||||
const HASH = /^[a-f0-9]{64}$/;
|
||||
const ID = /^[A-Za-z0-9_-]{1,96}$/;
|
||||
|
||||
function validate(receipt: IOFormatRecoveryReceipt): void {
|
||||
if (receipt.schemaVersion !== 1 || !ID.test(receipt.operationId) || !["OBJ", "STL", "PLY"].includes(receipt.format) || !["IMPORT", "EXPORT"].includes(receipt.operation) || !["RUNNING", "CANCELLED", "BLOCKED", "COMMITTED", "RECOVERED"].includes(receipt.status) || !Number.isSafeInteger(receipt.workerGeneration) || receipt.workerGeneration < 1 || !Number.isSafeInteger(receipt.baseRevision) || receipt.baseRevision < 0 || !Number.isSafeInteger(receipt.candidateRevision) || receipt.candidateRevision < receipt.baseRevision || !Number.isSafeInteger(receipt.inputBytes) || receipt.inputBytes <= 0 || !HASH.test(receipt.inputSha256) || !Number.isSafeInteger(receipt.outputBytes) || receipt.outputBytes < 0 || (receipt.outputSha256 !== null && !HASH.test(receipt.outputSha256)) || !Number.isSafeInteger(receipt.temporaryBytes) || receipt.temporaryBytes < 0 || !Number.isSafeInteger(receipt.liveRequests) || receipt.liveRequests < 0 || !Number.isSafeInteger(receipt.publishedResults) || receipt.publishedResults < 0 || typeof receipt.committed !== "boolean") {
|
||||
throw new Error("IO_FORMAT_RECOVERY_INVALID: receipt fields are malformed");
|
||||
}
|
||||
}
|
||||
|
||||
function clone(receipt: IOFormatRecoveryReceipt): IOFormatRecoveryReceipt { validate(receipt); return { ...receipt }; }
|
||||
|
||||
export function beginIOFormatRecoveryOperation(input: { operationId: string; format: IOFormat; operation: "IMPORT" | "EXPORT"; workerGeneration: number; baseRevision: number; inputBytes: number; inputSha256: string }): IOFormatRecoveryReceipt {
|
||||
const receipt: IOFormatRecoveryReceipt = { schemaVersion: 1, operationId: input.operationId, format: input.format, operation: input.operation, status: "RUNNING", workerGeneration: input.workerGeneration, baseRevision: input.baseRevision, candidateRevision: input.baseRevision + 1, inputBytes: input.inputBytes, inputSha256: input.inputSha256, outputBytes: 0, outputSha256: null, temporaryBytes: input.inputBytes, liveRequests: 1, publishedResults: 0, committed: false };
|
||||
validate(receipt); return receipt;
|
||||
}
|
||||
|
||||
export function commitIOFormatRecoveryOperation(receipt: IOFormatRecoveryReceipt, output: { bytes: number; sha256: string }): IOFormatRecoveryReceipt {
|
||||
const next = clone(receipt);
|
||||
if (next.status !== "RUNNING" || !Number.isSafeInteger(output.bytes) || output.bytes <= 0 || !HASH.test(output.sha256)) throw new Error("IO_FORMAT_RECOVERY_INVALID: operation cannot commit");
|
||||
next.status = "COMMITTED"; next.outputBytes = output.bytes; next.outputSha256 = output.sha256; next.temporaryBytes = 0; next.liveRequests = 0; next.publishedResults = 1; next.committed = true; validate(next); return next;
|
||||
}
|
||||
|
||||
export function cancelIOFormatRecoveryOperation(receipt: IOFormatRecoveryReceipt): IOFormatRecoveryReceipt {
|
||||
const next = clone(receipt);
|
||||
if (next.status !== "RUNNING") throw new Error("IO_FORMAT_RECOVERY_INVALID: operation is not running");
|
||||
next.status = "CANCELLED"; next.errorCode = "IO_FORMAT_OPERATION_CANCELLED"; next.temporaryBytes = 0; next.liveRequests = 0; next.publishedResults = 0; next.committed = false; validate(next); return next;
|
||||
}
|
||||
|
||||
export function blockIOFormatRecoveryOperation(receipt: IOFormatRecoveryReceipt): IOFormatRecoveryReceipt {
|
||||
const next = clone(receipt);
|
||||
if (next.status !== "RUNNING") throw new Error("IO_FORMAT_RECOVERY_INVALID: operation is not running");
|
||||
next.status = "BLOCKED"; next.errorCode = "IO_FORMAT_OOM"; next.temporaryBytes = 0; next.liveRequests = 0; next.publishedResults = 0; next.committed = false; validate(next); return next;
|
||||
}
|
||||
|
||||
export function recoverIOFormatRecoveryOperation(receipt: IOFormatRecoveryReceipt, workerGeneration: number): IOFormatRecoveryReceipt {
|
||||
const next = clone(receipt);
|
||||
if (next.status !== "COMMITTED" || !Number.isSafeInteger(workerGeneration) || workerGeneration <= next.workerGeneration) throw new Error("IO_FORMAT_RECOVERY_INVALID: only a committed operation can recover");
|
||||
next.status = "RECOVERED"; next.workerGeneration = workerGeneration; next.errorCode = "IO_FORMAT_WORKER_RESTARTED"; validate(next); return next;
|
||||
}
|
||||
|
||||
export function parseIOFormatRecoveryReceipt(value: unknown): IOFormatRecoveryReceipt {
|
||||
if (!value || typeof value !== "object") throw new Error("IO_FORMAT_RECOVERY_INVALID: receipt is not an object");
|
||||
const receipt = value as IOFormatRecoveryReceipt; validate(receipt);
|
||||
if (receipt.status === "CANCELLED" && receipt.errorCode !== "IO_FORMAT_OPERATION_CANCELLED") throw new Error("IO_FORMAT_RECOVERY_INVALID: cancellation code");
|
||||
if (receipt.status === "BLOCKED" && receipt.errorCode !== "IO_FORMAT_OOM") throw new Error("IO_FORMAT_RECOVERY_INVALID: oom code");
|
||||
if ((receipt.status === "COMMITTED" || receipt.status === "RECOVERED") && (!receipt.committed || receipt.outputBytes <= 0 || !receipt.outputSha256 || receipt.publishedResults !== 1)) throw new Error("IO_FORMAT_RECOVERY_INVALID: committed receipt");
|
||||
return { ...receipt };
|
||||
}
|
||||
Reference in New Issue
Block a user