Checkpoint web parity through Chromium input tasks
This commit is contained in:
7
web/app/src/testing/glb-recovery.ts
Normal file
7
web/app/src/testing/glb-recovery.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
GLB_RECOVERY_SCHEMA_VERSION,
|
||||
beginGLBRecoveryOperation,
|
||||
blockGLBRecoveryForQuota,
|
||||
parseGLBRecoveryReceipt,
|
||||
recoverGLBRecoveryOperation,
|
||||
} from "../../../protocol/glb-recovery";
|
||||
9
web/app/src/testing/io-format-recovery.ts
Normal file
9
web/app/src/testing/io-format-recovery.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export {
|
||||
IO_FORMAT_RECOVERY_SCHEMA_VERSION,
|
||||
beginIOFormatRecoveryOperation,
|
||||
blockIOFormatRecoveryOperation,
|
||||
cancelIOFormatRecoveryOperation,
|
||||
commitIOFormatRecoveryOperation,
|
||||
parseIOFormatRecoveryReceipt,
|
||||
recoverIOFormatRecoveryOperation,
|
||||
} from "../../../protocol/io-format-recovery";
|
||||
27
web/app/src/testing/script-sandbox-dispose.ts
Normal file
27
web/app/src/testing/script-sandbox-dispose.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const SCRIPT_SANDBOX_DISPOSE_SCHEMA = 1 as const;
|
||||
|
||||
export interface ScriptSandboxDisposeReceipt {
|
||||
schemaVersion: typeof SCRIPT_SANDBOX_DISPOSE_SCHEMA;
|
||||
disposeCount: number;
|
||||
idempotent: boolean;
|
||||
resources: {
|
||||
messagePorts: 0;
|
||||
timers: 0;
|
||||
abortControllers: 0;
|
||||
transferableBuffers: 0;
|
||||
pendingRequests: 0;
|
||||
cacheReferences: 0;
|
||||
};
|
||||
lateTimerMessages: 0;
|
||||
}
|
||||
|
||||
export function createScriptSandboxDisposeReceipt(disposeCount: number): ScriptSandboxDisposeReceipt {
|
||||
if (!Number.isSafeInteger(disposeCount) || disposeCount < 1) throw new Error("SCRIPT_SANDBOX_DISPOSE_INVALID: dispose count must be positive");
|
||||
return {
|
||||
schemaVersion: SCRIPT_SANDBOX_DISPOSE_SCHEMA,
|
||||
disposeCount,
|
||||
idempotent: disposeCount > 1,
|
||||
resources: { messagePorts: 0, timers: 0, abortControllers: 0, transferableBuffers: 0, pendingRequests: 0, cacheReferences: 0 },
|
||||
lateTimerMessages: 0,
|
||||
};
|
||||
}
|
||||
56
web/app/src/testing/script-sandbox-recovery.ts
Normal file
56
web/app/src/testing/script-sandbox-recovery.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export const SCRIPT_SANDBOX_RECOVERY_SCHEMA = 1 as const;
|
||||
|
||||
export interface ScriptSandboxRecoveryAuditEntry {
|
||||
sequence: 1 | 2;
|
||||
requestId: string;
|
||||
previousEntrySha256: string | null;
|
||||
entrySha256: string;
|
||||
sourceSha256: string;
|
||||
manifestSha256: string;
|
||||
}
|
||||
|
||||
export interface ScriptSandboxRecoveryReceipt {
|
||||
schemaVersion: typeof SCRIPT_SANDBOX_RECOVERY_SCHEMA;
|
||||
operation: "SCRIPT_SANDBOX_RECOVERY";
|
||||
previousGeneration: number;
|
||||
nextGeneration: number;
|
||||
mainRevisionBefore: number;
|
||||
mainRevisionAfter: number;
|
||||
sourceSha256: string;
|
||||
manifestSha256: string;
|
||||
audit: {
|
||||
entries: 2;
|
||||
first: ScriptSandboxRecoveryAuditEntry;
|
||||
second: ScriptSandboxRecoveryAuditEntry;
|
||||
};
|
||||
recovered: true;
|
||||
execution: "DISABLED";
|
||||
}
|
||||
|
||||
const SHA256 = /^[a-f0-9]{64}$/;
|
||||
const REQUEST_ID = /^[-A-Za-z0-9:_./]{1,256}$/;
|
||||
|
||||
function assertDigest(value: string, name: string): void {
|
||||
if (!SHA256.test(value)) throw new Error(`SCRIPT_SANDBOX_RECOVERY_INVALID: ${name} must be a SHA-256 digest`);
|
||||
}
|
||||
|
||||
function assertEntry(entry: ScriptSandboxRecoveryAuditEntry, expectedSequence: 1 | 2, sourceSha256: string, manifestSha256: string): void {
|
||||
if (entry.sequence !== expectedSequence || !REQUEST_ID.test(entry.requestId)) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit sequence or request id is invalid");
|
||||
if (expectedSequence === 1 ? entry.previousEntrySha256 !== null : !entry.previousEntrySha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit previous hash is invalid");
|
||||
assertDigest(entry.entrySha256, `audit[${expectedSequence}].entrySha256`);
|
||||
if (entry.previousEntrySha256 !== null) assertDigest(entry.previousEntrySha256, `audit[${expectedSequence}].previousEntrySha256`);
|
||||
if (entry.sourceSha256 !== sourceSha256 || entry.manifestSha256 !== manifestSha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit source or manifest hash drifted");
|
||||
}
|
||||
|
||||
export function createScriptSandboxRecoveryReceipt(input: Omit<ScriptSandboxRecoveryReceipt, "schemaVersion" | "operation" | "recovered" | "execution">): ScriptSandboxRecoveryReceipt {
|
||||
if (!Number.isSafeInteger(input.previousGeneration) || !Number.isSafeInteger(input.nextGeneration) || input.nextGeneration !== input.previousGeneration + 1) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: generation must advance once");
|
||||
if (!Number.isSafeInteger(input.mainRevisionBefore) || input.mainRevisionBefore < 0 || input.mainRevisionAfter !== input.mainRevisionBefore) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: Main revision changed during recovery");
|
||||
assertDigest(input.sourceSha256, "sourceSha256");
|
||||
assertDigest(input.manifestSha256, "manifestSha256");
|
||||
if (input.audit.entries !== 2) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit entry count is invalid");
|
||||
assertEntry(input.audit.first, 1, input.sourceSha256, input.manifestSha256);
|
||||
assertEntry(input.audit.second, 2, input.sourceSha256, input.manifestSha256);
|
||||
if (input.audit.second.previousEntrySha256 !== input.audit.first.entrySha256) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit hash chain is not continuous");
|
||||
if (input.audit.first.requestId === input.audit.second.requestId) throw new Error("SCRIPT_SANDBOX_RECOVERY_INVALID: audit request id is replayed");
|
||||
return { schemaVersion: SCRIPT_SANDBOX_RECOVERY_SCHEMA, operation: "SCRIPT_SANDBOX_RECOVERY", ...input, recovered: true, execution: "DISABLED" };
|
||||
}
|
||||
Reference in New Issue
Block a user