Checkpoint web parity through Chromium input tasks
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-19 10:39:03 -04:00
parent 5a11045ca5
commit 380cbed4ff
634 changed files with 41862 additions and 212 deletions

View File

@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/device-budget.ts");
const source = fs.readFileSync(sourcePath, "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const budget = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
const base = { schemaVersion: 1, identitySha256: "a".repeat(64), webgl2: { status: "PASS", renderer: "ANGLE NVIDIA", vendor: "NVIDIA" }, webgpu: { status: "PASS", description: "NVIDIA RTX", isFallbackAdapter: false }, hardwareConcurrency: 16, deviceMemory: 16 };
test("M14-04A selects HIGH only from trusted observed capability", () => {
const result = budget.selectDeviceBudget(base);
assert.equal(result.tier, "HIGH");
assert.equal(result.reason, "HIGH_CAPABILITY");
assert.equal(result.limits.maxTextureGPUBytes, 1024 * 1024 * 1024);
});
test("M14-04A fails closed for missing WebGPU, SwiftShader, fallback and invalid identity", () => {
for (const observation of [
{ ...base, webgpu: { status: "BLOCKED" } },
{ ...base, webgl2: { ...base.webgl2, renderer: "ANGLE SwiftShader" } },
{ ...base, webgpu: { ...base.webgpu, isFallbackAdapter: true } },
{ ...base, deviceMemory: null },
]) assert.equal(budget.selectDeviceBudget(observation).tier, "CONSERVATIVE");
assert.throws(() => budget.selectDeviceBudget({ ...base, identitySha256: "bad" }), /DEVICE_BUDGET_IDENTITY_INVALID/);
});
test("M14-04A exposes fixed limits and never expands an unknown tier", () => {
assert.equal(budget.deviceBudgetLimits("CONSERVATIVE").maxLights, 8);
assert.throws(() => budget.deviceBudgetLimits("UNKNOWN"), /DEVICE_BUDGET_TIER_INVALID/);
});

View File

@@ -0,0 +1,63 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "glb-desktop-import-unit-"));
const sourcePath = path.join(root, "web/protocol/glb-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "glb-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const report = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-06A/desktop-fixtures.json"), "utf8"));
const fixtureRoot = path.join(root, "tests/files/web/m12_glb_desktop_v1");
const arrayBuffer = (bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
test("M12-06B imports every desktop fixture with exact Web semantics", () => {
assert.deepEqual(report.fixtures.map((fixture) => fixture.id), ["mesh", "pbr", "uv", "skin", "animation"]);
for (const fixture of report.fixtures) {
const bytes = fs.readFileSync(path.join(fixtureRoot, fixture.file));
assert.equal(crypto.createHash("sha256").update(bytes).digest("hex"), fixture.sha256);
const imported = protocol.importGLBDesktopFixtureSemantics(arrayBuffer(bytes));
assert.deepEqual(protocol.compareGLBDesktopFixtureSemantics(fixture.semantic, imported), { compatible: true, mismatches: [] }, fixture.id);
}
});
test("M12-06B exposes topology, attributes, materials, nodes and animations separately", () => {
const imported = Object.fromEntries(report.fixtures.map((fixture) => {
const bytes = fs.readFileSync(path.join(fixtureRoot, fixture.file));
return [fixture.id, protocol.importGLBDesktopFixtureSemantics(arrayBuffer(bytes))];
}));
assert.deepEqual(Object.keys(imported.mesh.meshes[0].primitives[0].attributes), ["COLOR_0", "NORMAL", "POSITION"]);
assert.equal(imported.mesh.meshes[0].primitives[0].indices.count, 6);
assert.equal(Number(imported.pbr.materials[0].pbr.metallicFactor.toFixed(3)), 0.72);
assert.ok(imported.uv.meshes[0].primitives[0].attributes.TEXCOORD_0);
assert.equal(imported.uv.materials[0].pbr.baseColorTexture.index, 0);
assert.deepEqual(imported.skin.nodeNames, ["Tip", "Root", "M12 Skin Fixture", "M12 Skin Armature"]);
assert.equal(imported.skin.skins[0].inverseBindMatrices.type, "MAT4");
assert.deepEqual(imported.animation.animations[0].channels.map((channel) => channel.target.path), ["translation", "rotation"]);
assert.equal(imported.animation.animations[0].samplers[0].input.count, 25);
});
test("M12-06B reports a field-level semantic mismatch", () => {
const fixture = report.fixtures.find((candidate) => candidate.id === "pbr");
const bytes = fs.readFileSync(path.join(fixtureRoot, fixture.file));
const imported = protocol.importGLBDesktopFixtureSemantics(arrayBuffer(bytes));
const expected = structuredClone(fixture.semantic);
expected.materials[0].pbr.metallicFactor = 0.5;
const comparison = protocol.compareGLBDesktopFixtureSemantics(expected, imported);
assert.equal(comparison.compatible, false);
assert.deepEqual(comparison.mismatches, ["$.materials[0].pbr.metallicFactor: expected 0.5 got 0.7200000286102295"]);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "glb-loss-report-unit-"));
const sourcePath = path.join(root, "web/protocol/glb-loss-report.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "glb-loss-report.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
test("M12-06D produces deterministic sorted machine loss entries", () => {
const snapshot = {
sceneId: "scene:test",
revision: 7,
nodes: [{}, {}],
meshes: [{}],
materials: [{}, {}],
images: [{}],
animations: [{}],
nonMeshData: [{}],
};
const report = protocol.createGLBLossReport(snapshot, {
canExport: false,
warnings: [
{ code: "Z_LOSS", severity: "warning", message: "z", id: "id:z" },
{ code: "A_BLOCK", severity: "error", message: "a", id: "id:a" },
{ code: "A_BLOCK", severity: "warning", message: "b", id: undefined },
],
});
assert.deepEqual(report, {
schemaVersion: 1,
operation: "GLB_EXPORT_LOSS_REPORT",
sceneId: "scene:test",
sourceRevision: 7,
canExport: false,
errorCount: 1,
warningCount: 2,
losses: [
{ code: "A_BLOCK", severity: "error", message: "a", id: "id:a" },
{ code: "A_BLOCK", severity: "warning", message: "b", id: null },
{ code: "Z_LOSS", severity: "warning", message: "z", id: "id:z" },
],
surface: { nodeCount: 2, meshCount: 1, materialCount: 2, imageCount: 1, animationCount: 1, nonMeshCount: 1 },
});
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "glb-negative-unit-"));
const sourcePath = path.join(root, "web/protocol/glb-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "glb-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const source = fs.readFileSync(path.join(root, "tests/files/web/m12_glb_desktop_v1/mesh.glb"));
function arrayBuffer(bytes) {
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
function rewriteJson(mutator) {
const jsonLength = source.readUInt32LE(12);
const document = JSON.parse(source.subarray(20, 20 + jsonLength).toString("utf8").trim());
mutator(document);
const json = Buffer.from(JSON.stringify(document));
const paddedLength = (json.length + 3) & ~3;
const output = Buffer.alloc(12 + 8 + paddedLength + (source.length - (20 + jsonLength)));
output.writeUInt32LE(0x46546c67, 0);
output.writeUInt32LE(2, 4);
output.writeUInt32LE(output.length, 8);
output.writeUInt32LE(paddedLength, 12);
output.writeUInt32LE(0x4e4f534a, 16);
json.copy(output, 20);
output.fill(0x20, 20 + json.length, 20 + paddedLength);
output.writeUInt32LE(source.readUInt32LE(20 + jsonLength), 20 + paddedLength);
output.writeUInt32LE(source.readUInt32LE(24 + jsonLength), 24 + paddedLength);
source.subarray(28 + jsonLength).copy(output, 28 + paddedLength);
return output;
}
test("M12-06F rejects sparse accessors, extensions and external URIs", () => {
assert.throws(() => protocol.importGLBSemantics(arrayBuffer(rewriteJson((document) => { document.accessors[0].sparse = { count: 1 }; }))), /GLB_SPARSE_ACCESSOR_UNSUPPORTED/);
assert.throws(() => protocol.importGLBSemantics(arrayBuffer(rewriteJson((document) => { document.extensionsUsed = ["KHR_draco_mesh_compression"]; }))), /GLB_EXTENSION_UNSUPPORTED/);
assert.throws(() => protocol.importGLBSemantics(arrayBuffer(rewriteJson((document) => { document.buffers[0].uri = "external.bin"; }))), /GLB_EXTERNAL_URI_BLOCKED/);
});
test("M12-06F rejects over-budget GLB bytes and table counts", () => {
const oversized = Buffer.alloc(protocol.GLB_IMPORT_BUDGET.maxBytes + 1);
source.copy(oversized);
assert.throws(() => protocol.importGLBSemantics(arrayBuffer(oversized)), /GLB_IMPORT_BUDGET_EXCEEDED/);
assert.throws(() => protocol.importGLBSemantics(arrayBuffer(rewriteJson((document) => { document.bufferViews = Array.from({ length: protocol.GLB_IMPORT_BUDGET.maxBufferViews + 1 }, () => ({ buffer: 0, byteLength: 0 })); }))), /GLB_IMPORT_BUDGET_EXCEEDED/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "glb-recovery-unit-"));
const sourcePath = path.join(root, "web/protocol/glb-recovery.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "glb-recovery.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const hash = "a".repeat(64);
const outputHash = "b".repeat(64);
test("M12-06G keeps cancellation and quota failure fail-closed", () => {
const running = protocol.beginGLBRecoveryOperation({
operationId: "import-1",
operation: "IMPORT",
workerGeneration: 1,
baseRevision: 7,
inputBytes: 128,
inputSha256: hash,
});
assert.equal(running.status, "RUNNING");
assert.equal(running.candidateRevision, 8);
const cancelled = protocol.cancelGLBRecoveryOperation(running);
assert.deepEqual(protocol.parseGLBRecoveryReceipt(cancelled), cancelled);
assert.equal(cancelled.errorCode, "GLB_OPERATION_CANCELLED");
assert.equal(cancelled.temporaryBytes, 0);
assert.equal(cancelled.committed, false);
assert.throws(() => protocol.commitGLBRecoveryOperation(cancelled, { bytes: 1, sha256: outputHash }), /GLB_RECOVERY_INVALID/);
assert.equal(protocol.blockGLBRecoveryForQuota(running).errorCode, "GLB_OPFS_QUOTA");
});
test("M12-06G binds committed output and worker-generation recovery", () => {
const running = protocol.beginGLBRecoveryOperation({
operationId: "export-1",
operation: "EXPORT",
workerGeneration: 1,
baseRevision: 11,
inputBytes: 256,
inputSha256: hash,
});
const committed = protocol.commitGLBRecoveryOperation(running, { bytes: 512, sha256: outputHash });
assert.equal(committed.status, "COMMITTED");
assert.equal(committed.committed, true);
assert.equal(committed.liveRequests, 0);
const recovered = protocol.recoverGLBRecoveryOperation(committed, 2);
assert.equal(recovered.status, "RECOVERED");
assert.equal(recovered.workerGeneration, 2);
assert.equal(recovered.errorCode, "GLB_WORKER_RESTARTED");
assert.throws(() => protocol.recoverGLBRecoveryOperation(committed, 1), /GLB_RECOVERY_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/ime-composition.ts");
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const ime = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
test("M14-04D blocks operators through composition start/update and reopens after end", () => {
let state = ime.createIMECompositionState();
assert.equal(ime.shouldBlockOperatorShortcuts(state), false);
state = ime.reduceIMEComposition(state, { type: "compositionstart", data: "n" });
assert.equal(ime.shouldBlockOperatorShortcuts(state), true);
state = ime.reduceIMEComposition(state, { type: "compositionupdate", data: "ni" });
assert.deepEqual([state.composing, state.pendingText, state.lastEvent], [true, "ni", "UPDATE"]);
state = ime.reduceIMEComposition(state, { type: "compositionend", data: "你" });
assert.equal(ime.shouldBlockOperatorShortcuts(state), false);
assert.equal(ime.shouldBlockOperatorShortcuts(state, true), true);
});
test("M14-04D ignores stray updates and rejects malformed state", () => {
let state = ime.createIMECompositionState();
assert.equal(ime.reduceIMEComposition(state, { type: "compositionupdate", data: "x" }), state);
assert.throws(() => ime.shouldBlockOperatorShortcuts({ schemaVersion: 2 }), /IME_STATE_INVALID/);
});

View File

@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/input-modal.ts");
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const modal = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
test("M14-04F cancels touch modal without commit and starts one two-finger navigation revision", () => {
let state = modal.createInputModalState();
state = modal.beginTouch(state, 4);
state = modal.beginTouch(state, 2);
assert.deepEqual([state.kind, state.activePointerIds, state.navigationRevision, state.mainCommitCount], ["TOUCH_NAVIGATION", [2, 4], 1, 0]);
state = modal.cancelInputModal(state);
assert.deepEqual([state.kind, state.activePointerIds, state.cancelled, state.mainCommitCount], ["NONE", [], true, 0]);
});
test("M14-04F commits pen stroke once and ignores late up/cancel", () => {
let state = modal.beginPenStroke(modal.createInputModalState(), 9);
state = modal.commitPenStroke(state, 9);
state = modal.commitPenStroke(state, 9);
assert.deepEqual([state.kind, state.mainCommitCount, state.activePointerIds], ["NONE", 1, []]);
});

View File

@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-capability-matrix-unit-"));
const sourcePath = path.join(root, "web/protocol/io-format-capability-matrix.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "io-format-capability-matrix.mjs"), transpiled.outputText);
const matrix = await import(pathToFileURL(path.join(temporary, "io-format-capability-matrix.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-05B/manifest.json"), "utf8"));
const source = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-05B/capability-matrix.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
test("M12-05B binds the capability matrix and runtime inventory artifacts", () => {
assert.equal(manifest.task, "M12-05B");
assert.equal(manifest.parentTask, "M12-05A");
assert.equal(manifest.nextTask, "M12-05C");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-05B accepts the seven-format matrix and keeps the bounded GLB export route explicit", () => {
const parsed = matrix.parseIOFormatCapabilityMatrix(source);
assert.deepEqual(parsed.formats.map((entry) => entry.format), ["GLTF", "GLB", "OBJ", "STL", "PLY", "USD", "ALEMBIC"]);
assert.equal(parsed.formats.find((entry) => entry.format === "GLB").operations.EXPORT.local.status, "READY");
assert.equal(parsed.formats.find((entry) => entry.format === "GLB").operations.EXPORT.local.execution, "LOCAL");
assert.equal(parsed.formats.find((entry) => entry.format === "GLB").operations.IMPORT.local.status, "BLOCKED");
assert.ok(parsed.formats.filter((entry) => entry.operations.IMPORT.local.status === "BLOCKED").length >= 6);
});
test("M12-05B rejects duplicate formats, ready routes without a real executor, and unverified ready features", () => {
assert.throws(() => matrix.parseIOFormatCapabilityMatrix({ ...source, formats: [...source.formats.slice(0, 6), source.formats[0]] }), { code: "ASSET_MANIFEST_INVALID" });
const badRoute = structuredClone(source);
badRoute.formats.find((entry) => entry.format === "OBJ").operations.IMPORT.local = { status: "READY", execution: "LOCAL", code: null };
assert.throws(() => matrix.parseIOFormatCapabilityMatrix(badRoute), { code: "ASSET_MANIFEST_INVALID" });
const badFeature = structuredClone(source);
badFeature.formats.find((entry) => entry.format === "GLB").operations.EXPORT.geometry.status = "UNVERIFIED";
assert.throws(() => matrix.parseIOFormatCapabilityMatrix(badFeature), { code: "ASSET_MANIFEST_INVALID" });
const badCode = structuredClone(source);
badCode.formats.find((entry) => entry.format === "PLY").operations.EXPORT.local.code = null;
assert.throws(() => matrix.parseIOFormatCapabilityMatrix(badCode), { code: "IO_FORMAT_UNSUPPORTED" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,33 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-receipt-binding-unit-"));
const sourcePath = path.join(root, "web/protocol/io-format-receipt-binding.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []); fs.writeFileSync(path.join(temporary, "protocol.mjs"), transpiled.outputText);
const protocol = await import(pathToFileURL(path.join(temporary, "protocol.mjs")));
const bound = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-05E/bound-runtime-receipts.json"), "utf8"));
test("M12-05E accepts receipts with all three identity hashes", () => {
const parsed = protocol.validateIOFormatBoundReceiptSet(bound, bound.parentReceiptSetSha256, bound.inventorySha256);
assert.match(protocol.resolveBoundReceipt(parsed, "GLB", "EXPORT").sourceSha256, /^[a-f0-9]{64}$/);
assert.match(protocol.resolveBoundReceipt(parsed, "GLB", "EXPORT").settingsSha256, /^[a-f0-9]{64}$/);
assert.match(protocol.resolveBoundReceipt(parsed, "GLB", "EXPORT").runtimeSha256, /^[a-f0-9]{64}$/);
});
test("M12-05E rejects source, settings, runtime and parent hash drift", () => {
for (const field of ["sourceSha256", "settingsSha256", "runtimeSha256"]) {
const mutated = structuredClone(bound); mutated.receipts[0][field] = "invalid-hash";
assert.throws(() => protocol.validateIOFormatBoundReceiptSet(mutated, mutated.parentReceiptSetSha256, mutated.inventorySha256), { code: "IO_FORMAT_UNSUPPORTED" });
}
const stale = structuredClone(bound); stale.parentReceiptSetSha256 = "0".repeat(64);
assert.throws(() => protocol.validateIOFormatBoundReceiptSet(stale, bound.parentReceiptSetSha256, bound.inventorySha256), { code: "IO_FORMAT_UNSUPPORTED" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,86 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-receipt-freshness-unit-"));
const stableValue = (value) => Array.isArray(value) ? value.map(stableValue) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])])) : value;
const stableSha256 = (value) => crypto.createHash("sha256").update(JSON.stringify(stableValue(value))).digest("hex");
function transpile(sourcePath, outputName, replacements = []) {
const result = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(result.diagnostics, []);
let output = result.outputText;
for (const [from, to] of replacements) output = output.replaceAll(from, to);
const outputPath = path.join(temporary, outputName);
fs.writeFileSync(outputPath, output);
return outputPath;
}
const parentPath = path.join(root, "tests/golden/M12-05E/bound-runtime-receipts.json");
const freshnessPath = path.join(root, "tests/golden/M12-05F/fresh-runtime-receipts.json");
const parentBytes = fs.readFileSync(parentPath);
const bound = JSON.parse(parentBytes);
const freshness = JSON.parse(fs.readFileSync(freshnessPath, "utf8"));
const protocolPath = path.join(root, "web/protocol/io-format-receipt-freshness.ts");
transpile(path.join(root, "web/protocol/io-format-runtime-receipt.ts"), "io-format-runtime-receipt.mjs");
transpile(path.join(root, "web/protocol/io-format-receipt-binding.ts"), "io-format-receipt-binding.mjs");
const protocol = await import(pathToFileURL(transpile(protocolPath, "io-format-receipt-freshness.mjs", [["./io-format-receipt-binding\"", "./io-format-receipt-binding.mjs\""], ["./io-format-runtime-receipt\"", "./io-format-runtime-receipt.mjs\""]])));
const expected = {
parentBindingSha256: crypto.createHash("sha256").update(parentBytes).digest("hex"),
parentReceiptSetSha256: bound.parentReceiptSetSha256,
inventorySha256: bound.inventorySha256,
boundReceiptSetSha256: stableSha256(bound),
runtimeSha256: stableSha256(bound.runtime),
runtime: bound.runtime,
receiptIdentities: bound.receipts,
};
test("M12-05F artifact is deterministic and bound to M12-05E", () => {
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-05F/manifest.json"), "utf8"));
assert.equal(manifest.task, "M12-05F");
assert.equal(manifest.parentTask, "M12-05E");
assert.equal(manifest.nextTask, "M12-06A");
for (const artifact of Object.values(manifest.artifacts)) {
assert.equal(crypto.createHash("sha256").update(fs.readFileSync(path.join(root, artifact.path))).digest("hex"), artifact.sha256, artifact.path);
}
assert.equal(freshness.parentBindingSha256, expected.parentBindingSha256);
assert.equal(freshness.boundReceiptSetSha256, expected.boundReceiptSetSha256);
assert.equal(freshness.runtimeSha256, expected.runtimeSha256);
});
test("M12-05F accepts only the exact trusted runtime receipt", async () => {
const parsed = await protocol.verifyIOFormatReceiptFreshness(freshness, expected);
assert.equal(parsed.bound.receipts.length, 14);
assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(freshness, expected, { format: "GLB", operation: "EXPORT" }).status, "READY");
assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(freshness, expected, { format: "USD", operation: "EXPORT" }).status, "BLOCKED");
});
test("M12-05F rejects forged content before route execution", async () => {
const forged = structuredClone(freshness);
forged.bound.receipts[0].operator = "forged.operator";
await assert.rejects(() => protocol.verifyIOFormatReceiptFreshness(forged, expected), (error) => error.reason === "RECEIPT_FORGED");
const malformed = structuredClone(freshness);
delete malformed.boundReceiptSetSha256;
assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(malformed, expected, { format: "GLB", operation: "EXPORT" }).reason, "RECEIPT_FORGED");
});
test("M12-05F rejects stale parent identity and cross-version runtime", () => {
const stale = structuredClone(freshness);
stale.bound.inventorySha256 = "a".repeat(64);
assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(stale, expected, { format: "GLB", operation: "EXPORT" }).reason, "RECEIPT_STALE");
const crossVersion = structuredClone(freshness);
crossVersion.bound.runtime.versionTuple = [5, 3, 0];
assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(crossVersion, expected, { format: "GLB", operation: "EXPORT" }).reason, "RECEIPT_CROSS_VERSION");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,40 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-recovery-unit-"));
const sourcePath = path.join(root, "web/protocol/io-format-recovery.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "io-format-recovery.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const hash = "a".repeat(64);
const outputHash = "b".repeat(64);
test("M12-07J keeps cancellation and OOM receipts unpublished", () => {
const running = protocol.beginIOFormatRecoveryOperation({ operationId: "ply-cancel-1", format: "PLY", operation: "IMPORT", workerGeneration: 1, baseRevision: 3, inputBytes: 32, inputSha256: hash });
const cancelled = protocol.cancelIOFormatRecoveryOperation(running);
assert.deepEqual(cancelled, { ...running, status: "CANCELLED", errorCode: "IO_FORMAT_OPERATION_CANCELLED", temporaryBytes: 0, liveRequests: 0, publishedResults: 0, committed: false });
const oomRunning = protocol.beginIOFormatRecoveryOperation({ operationId: "obj-oom-1", format: "OBJ", operation: "IMPORT", workerGeneration: 1, baseRevision: 3, inputBytes: 512 * 1024 + 1, inputSha256: hash });
const blocked = protocol.blockIOFormatRecoveryOperation(oomRunning);
assert.equal(blocked.errorCode, "IO_FORMAT_OOM");
assert.equal(blocked.publishedResults, 0);
assert.throws(() => protocol.commitIOFormatRecoveryOperation(cancelled, { bytes: 1, sha256: outputHash }), /IO_FORMAT_RECOVERY_INVALID/);
});
test("M12-07J binds output identity across restart and rejects stale generation", () => {
const running = protocol.beginIOFormatRecoveryOperation({ operationId: "stl-restart-1", format: "STL", operation: "EXPORT", workerGeneration: 1, baseRevision: 7, inputBytes: 64, inputSha256: hash });
const committed = protocol.commitIOFormatRecoveryOperation(running, { bytes: 128, sha256: outputHash });
const recovered = protocol.recoverIOFormatRecoveryOperation(committed, 2);
assert.deepEqual(recovered, { ...committed, status: "RECOVERED", workerGeneration: 2, errorCode: "IO_FORMAT_WORKER_RESTARTED" });
assert.equal(protocol.parseIOFormatRecoveryReceipt(recovered).outputSha256, outputHash);
assert.throws(() => protocol.recoverIOFormatRecoveryOperation(committed, 1), /IO_FORMAT_RECOVERY_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-runtime-receipt-unit-"));
const sourcePath = path.join(root, "web/protocol/io-format-runtime-receipt.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "protocol.mjs"), transpiled.outputText);
const protocol = await import(pathToFileURL(path.join(temporary, "protocol.mjs")));
const receiptSet = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-05D/runtime-receipts.json"), "utf8"));
const inventorySha256 = receiptSet.inventorySha256;
test("M12-05D resolves capability from the runtime operator receipt", () => {
const parsed = protocol.validateIOFormatRuntimeReceiptSet(receiptSet, inventorySha256);
assert.equal(protocol.resolveIOFormatRuntimeRoute(parsed, { format: "GLB", operation: "EXPORT" }).status, "READY");
assert.equal(protocol.resolveIOFormatRuntimeRoute(parsed, { format: "USD", operation: "EXPORT" }).status, "BLOCKED");
});
test("M12-05D does not infer capability from a filename extension", () => {
const mutated = structuredClone(receiptSet);
const receipt = mutated.receipts.find((candidate) => candidate.format === "GLB" && candidate.operation === "EXPORT");
receipt.extensions = [".usd"];
const parsed = protocol.validateIOFormatRuntimeReceiptSet(mutated, inventorySha256);
assert.deepEqual(protocol.resolveIOFormatRuntimeRoute(parsed, { format: "GLB", operation: "EXPORT" }).status, "READY");
assert.deepEqual(protocol.resolveIOFormatRuntimeRoute(parsed, { format: "USD", operation: "EXPORT" }).status, "BLOCKED");
});
test("M12-05D rejects receipt identity drift and consumes explicit receipt status", () => {
const stale = structuredClone(receiptSet);
stale.inventorySha256 = "0".repeat(64);
assert.throws(() => protocol.validateIOFormatRuntimeReceiptSet(stale, inventorySha256), { code: "IO_FORMAT_UNSUPPORTED" });
const forged = structuredClone(receiptSet);
const usd = forged.receipts.find((candidate) => candidate.format === "USD" && candidate.operation === "EXPORT");
usd.runtimeStatus = "AVAILABLE";
usd.registered = true;
usd.rnaIdentifier = "WM_OT_usd_export";
usd.buildOptionEnabled = true;
assert.doesNotThrow(() => protocol.parseIOFormatRuntimeReceiptSet(forged));
assert.equal(protocol.resolveIOFormatRuntimeRoute(protocol.parseIOFormatRuntimeReceiptSet(forged), { format: "USD", operation: "EXPORT" }).status, "READY");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,53 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-ui-gate-unit-"));
const sourcePath = path.join(root, "web/protocol/io-format-ui-gate.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "io-format-ui-gate.mjs"), transpiled.outputText);
const gate = await import(pathToFileURL(path.join(temporary, "io-format-ui-gate.mjs")));
const registry = JSON.parse(fs.readFileSync(path.join(root, "web/app/src/capabilities/io-format-ui-registry.json"), "utf8"));
test("M12-05C keeps only matrix-declared executable routes in UI", () => {
const parsed = gate.parseIOFormatUIRegistry(registry);
assert.deepEqual(parsed.importRoutes, []);
assert.deepEqual(parsed.exportRoutes.map((route) => route.format), ["GLB"]);
const commands = [
{ id: "file.export-glb", ioFormat: { format: "GLB", operation: "EXPORT", execution: "LOCAL" } },
{ id: "file.import-obj", ioFormat: { format: "OBJ", operation: "IMPORT", execution: "LOCAL" } },
{ id: "file.export-usd", ioFormat: { format: "USD", operation: "EXPORT", execution: "SERVER" } },
{ id: "edit.undo" },
];
assert.deepEqual(gate.filterIOFormatOperatorCommands(commands, parsed).map((command) => command.id), ["file.export-glb", "edit.undo"]);
});
test("M12-05C file selection is project-only while import routes are blocked", () => {
const parsed = gate.parseIOFormatUIRegistry(registry);
assert.equal(gate.ioFormatUIAccept(parsed), ".blend,application/octet-stream");
assert.deepEqual(gate.gateIOFormatFileSelection("scene.blend", parsed), { status: "READY", kind: "BLEND" });
assert.deepEqual(gate.gateIOFormatFileSelection("mesh.obj", parsed), { status: "BLOCKED", code: "IO_FORMAT_UNSUPPORTED", extension: ".obj" });
assert.deepEqual(gate.gateIOFormatFileSelection("scene.glb", parsed), { status: "BLOCKED", code: "IO_FORMAT_UNSUPPORTED", extension: ".glb" });
});
test("M12-05C rejects malformed route metadata", () => {
const bad = structuredClone(registry);
bad.importRoutes = [{ format: "OBJ", operation: "IMPORT", execution: "LOCAL", extensions: [".obj"] }];
assert.doesNotThrow(() => gate.parseIOFormatUIRegistry(bad));
const parsed = gate.parseIOFormatUIRegistry(bad);
assert.equal(gate.filterIOFormatOperatorCommands([{ id: "import-obj", ioFormat: { format: "OBJ", operation: "IMPORT", execution: "LOCAL" } }], parsed).length, 1);
bad.importRoutes[0].extensions = [".not-obj"];
assert.throws(() => gate.parseIOFormatUIRegistry(bad), { code: "IO_FORMAT_UNSUPPORTED" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/keyboard-contract.ts");
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const keyboard = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
test("M14-04E preserves layout character, physical code, location and modifiers", () => {
assert.deepEqual(keyboard.observeKeyboardEvent({ key: "ä", code: "Quote", location: 0, shiftKey: true, ctrlKey: false, altKey: true, metaKey: false, repeat: true, isComposing: false }), { schemaVersion: 1, key: "ä", code: "Quote", location: 0, shiftKey: true, ctrlKey: false, altKey: true, metaKey: false, repeat: true, isComposing: false, deadKey: false });
assert.equal(keyboard.observeKeyboardEvent({ key: "Dead", code: "Quote", location: 0 }).deadKey, true);
});
test("M14-04E rejects malformed key identity", () => {
assert.throws(() => keyboard.observeKeyboardEvent({ key: "", code: "KeyA", location: 0 }), /KEY_IDENTITY_INVALID/);
assert.throws(() => keyboard.observeKeyboardEvent({ key: "a", code: "KeyA", location: 4 }), /KEY_LOCATION_INVALID/);
});

View File

@@ -0,0 +1,64 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-append-wasm-unit-"));
const sourcePath = path.join(root, "web/protocol/library-main-append.ts");
const identityPath = path.join(root, "web/protocol/library-operation-identity.ts");
const identityTranspiled = ts.transpileModule(fs.readFileSync(identityPath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: identityPath,
reportDiagnostics: true,
});
assert.deepEqual(identityTranspiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-operation-identity.mjs"), identityTranspiled.outputText);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-main-append.mjs"), transpiled.outputText.replaceAll("./library-operation-identity\"", "./library-operation-identity.mjs\""));
const append = await import(pathToFileURL(path.join(temporary, "library-main-append.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03N/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const closure = {
object: "Object/M12 Append Object",
mesh: "Mesh/M12 Append Mesh",
material: "Material/M12 Append Material",
image: "Image/M12 Append Image",
};
test("M12-03N binds the independent append WASM command", () => {
assert.equal(manifest.task, "M12-03N");
assert.equal(manifest.nextTask, "M12-04A");
assert.equal(sha256("web/protocol/library-main-append.ts"), manifest.artifacts.appendWasmProtocol.sha256);
});
test("M12-03N validates the append closure and one-transaction receipt in the WASM lane", async () => {
assert.deepEqual(append.parseLibraryAppendClosure(closure), closure);
const request = {
baseRevision: 11,
binding: {
source: { sourceLibraryId: "library:" + "a".repeat(64) },
sourceDataBlockId: closure.object,
dependencyClosureSha256: await append.computeLibraryAppendClosureSha256(closure),
},
expectedClosure: closure,
};
const receipt = append.createLibraryMainAppendReceipt(request, 12);
assert.equal(receipt.operation, "APPEND");
assert.equal(receipt.transactionCount, 1);
assert.equal(receipt.baseRevision, 11);
assert.equal(receipt.nextRevision, 12);
assert.equal(receipt.mapping.length, 4);
assert(receipt.mapping.every((item) => item.owner === "LOCAL_MAIN" && item.readOnly === false && item.source === item.local));
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,59 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-archive-budget-unit-"));
for (const name of ["asset-path.ts", "capability-gates.ts", "error.ts", "asset-library-io.ts"]) {
const sourcePath = path.join(root, "web/protocol", name);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
let output = transpiled.outputText;
output = output.replaceAll('from "./asset-path"', 'from "./asset-path.mjs"').replaceAll('from "./capability-gates"', 'from "./capability-gates.mjs"').replaceAll('from "./error"', 'from "./error.mjs"');
fs.writeFileSync(path.join(temporary, name.replace(".ts", ".mjs")), output);
}
const io = await import(pathToFileURL(path.join(temporary, "asset-library-io.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04F/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const base = { format: "GLB", operation: "IMPORT", externalUris: [], archiveEntries: [] };
const entry = (pathName, compressedBytes = 2, uncompressedBytes = 2) => ({ path: pathName, compressedBytes, uncompressedBytes });
test("M12-04F binds archive budget constants and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04F");
assert.equal(manifest.parentTask, "M12-04E");
assert.equal(manifest.nextTask, "M12-04G");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
assert.equal(io.ASSET_LIBRARY_BUDGET.maxArchivePathDepth, 64);
assert.equal(io.ASSET_LIBRARY_BUDGET.maxArchiveFileNameBytes, 255);
});
test("M12-04F accepts entries at the per-entry, total, depth and filename limits", () => {
const fileName = "é".repeat(125) + ".bin";
const deepPath = `${Array.from({ length: io.ASSET_LIBRARY_BUDGET.maxArchivePathDepth }, (_, index) => `d${index}`).join("/")}/file.bin`;
const result = io.parseIORequest({ ...base, byteLength: 10, archiveEntries: [entry("a.bin"), entry(deepPath), entry(fileName)] });
assert.equal(result.archiveEntries.length, 3);
});
test("M12-04F rejects per-entry, total, count, depth and UTF-8 filename overflow", () => {
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: [entry("huge.bin", 1, io.ASSET_LIBRARY_BUDGET.maxEntryBytes + 1)] }), { code: "ASSET_BUDGET_EXCEEDED" });
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: [entry("a.bin", io.ASSET_LIBRARY_BUDGET.maxEntryBytes, io.ASSET_LIBRARY_BUDGET.maxEntryBytes), entry("b.bin", io.ASSET_LIBRARY_BUDGET.maxEntryBytes, io.ASSET_LIBRARY_BUDGET.maxEntryBytes), entry("c.bin", 1, 1)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: [entry(`${Array.from({ length: io.ASSET_LIBRARY_BUDGET.maxArchivePathDepth + 1 }, () => "d").join("/")}/file.bin`)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: [entry("é".repeat(128))] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: Array.from({ length: io.ASSET_LIBRARY_BUDGET.maxArchiveEntries + 1 }, (_, index) => entry(`f${index}.bin`)) }), { code: "ASSET_BUDGET_EXCEEDED" });
});
test("M12-04F keeps compression and declared source-byte budgets fail-closed", () => {
assert.throws(() => io.parseIORequest({ ...base, archiveEntries: [entry("bomb.bin", 1, io.ASSET_LIBRARY_BUDGET.maxCompressionRatio + 1)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => io.parseIORequest({ ...base, byteLength: 1, archiveEntries: [entry("source.bin", 2, 2)] }), { code: "IO_ARCHIVE_UNSAFE" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,209 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-archive-cancellation-unit-"));
const sourcePath = path.join(root, "web/protocol/archive-extraction-transaction.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "archive-extraction-transaction.mjs"), transpiled.outputText);
const extraction = await import(pathToFileURL(path.join(temporary, "archive-extraction-transaction.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04H/manifest.json"), "utf8"));
const fileSha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const bytesSha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
class DirectoryExtractionStorage {
constructor(directory, committed) {
this.directory = directory;
this.committed = committed;
fs.mkdirSync(path.join(directory, "committed"), { recursive: true });
fs.writeFileSync(path.join(directory, "committed", "project.blend"), Buffer.from("old-project"));
}
async readCommitted() { return { ...this.committed }; }
async createStaging(transactionId) {
fs.mkdirSync(path.join(this.directory, "staging", transactionId), { recursive: true });
}
async writeStaging(transactionId, entryPath, bytes) {
const target = path.join(this.directory, "staging", transactionId, entryPath);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, bytes);
}
async countStagingEntries(transactionId) {
const staging = path.join(this.directory, "staging", transactionId);
if (!fs.existsSync(staging)) return 0;
const visit = (directory) => fs.readdirSync(directory, { withFileTypes: true }).reduce(
(count, entry) => count + (entry.isDirectory() ? visit(path.join(directory, entry.name)) : 1), 0,
);
return visit(staging);
}
async discardStaging(transactionId) {
const count = await this.countStagingEntries(transactionId);
fs.rmSync(path.join(this.directory, "staging", transactionId), { recursive: true, force: true });
return count;
}
async commitStaging(transactionId, expected, candidate) {
assert.deepEqual(this.committed, expected);
const source = path.join(this.directory, "staging", transactionId, "project.blend");
const target = path.join(this.directory, "committed", "project.blend");
fs.renameSync(source, target);
await this.discardStaging(transactionId);
this.committed = { ...candidate };
return { ...this.committed };
}
}
const oldBytes = Buffer.from("old-project");
const newBytes = Buffer.from("new-project");
const metadataBytes = Buffer.from("metadata");
const committed = { projectId: "project:m12-04h", revision: 8, sha256: bytesSha256(oldBytes) };
const candidate = { projectId: committed.projectId, revision: 9, sha256: bytesSha256(newBytes) };
const request = {
schemaVersion: 1,
transactionId: "transaction:m12-04h",
archiveId: "archive:m12-04h",
committed,
candidate,
entries: [
{ path: "metadata/index.json", uncompressedBytes: metadataBytes.byteLength, sha256: bytesSha256(metadataBytes) },
{ path: "project.blend", uncompressedBytes: newBytes.byteLength, sha256: bytesSha256(newBytes) },
],
};
function createStorage(name) {
const directory = path.join(temporary, name);
fs.mkdirSync(directory, { recursive: true });
return new DirectoryExtractionStorage(directory, committed);
}
test("M12-04H binds cancellation rollback and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04H");
assert.equal(manifest.parentTask, "M12-04G");
assert.equal(manifest.nextTask, "M12-04I");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(fileSha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04H removes partially written staging and preserves the committed project on cancellation", async () => {
const storage = createStorage("cancel-after-write");
const controller = new AbortController();
let reads = 0;
const receipt = await extraction.runArchiveExtractionTransaction(request, storage, async (entry) => {
reads++;
if (reads === 2) controller.abort();
return entry.path === "project.blend" ? newBytes : metadataBytes;
}, controller.signal);
assert.deepEqual(receipt, {
status: "CANCELLED",
code: "IO_ARCHIVE_CANCELLED",
transactionId: request.transactionId,
committedBefore: committed,
committedAfter: committed,
removedStagingEntries: 1,
stagingEntriesAfter: 0,
publishedProjects: 0,
});
assert.equal(fs.readFileSync(path.join(storage.directory, "committed", "project.blend"), "utf8"), "old-project");
assert.equal(await storage.countStagingEntries(request.transactionId), 0);
});
test("M12-04H cancels before staging and after the last staged write without publishing", async () => {
const beforeStorage = createStorage("cancel-before-stage");
const beforeController = new AbortController();
beforeController.abort();
const before = await extraction.runArchiveExtractionTransaction(request, beforeStorage, async () => newBytes, beforeController.signal);
assert.equal(before.status, "CANCELLED");
assert.equal(before.removedStagingEntries, 0);
const finalStorage = createStorage("cancel-after-last-write");
const finalController = new AbortController();
const originalWrite = finalStorage.writeStaging.bind(finalStorage);
finalStorage.writeStaging = async (transactionId, entryPath, bytes) => {
await originalWrite(transactionId, entryPath, bytes);
if (entryPath === "project.blend") finalController.abort();
};
const after = await extraction.runArchiveExtractionTransaction(request, finalStorage, async (entry) =>
entry.path === "project.blend" ? newBytes : metadataBytes, finalController.signal);
assert.equal(after.status, "CANCELLED");
assert.equal(after.removedStagingEntries, 2);
assert.deepEqual(await finalStorage.readCommitted(), committed);
assert.equal(fs.readFileSync(path.join(finalStorage.directory, "committed", "project.blend"), "utf8"), "old-project");
});
test("M12-04H commits only after all staged payloads pass identity checks", async () => {
const storage = createStorage("commit");
const receipt = await extraction.runArchiveExtractionTransaction(request, storage, async (entry) =>
entry.path === "project.blend" ? newBytes : metadataBytes, new AbortController().signal);
assert.deepEqual(receipt, {
status: "COMMITTED",
transactionId: request.transactionId,
committed: candidate,
stagingEntriesAfter: 0,
});
assert.equal(fs.readFileSync(path.join(storage.directory, "committed", "project.blend"), "utf8"), "new-project");
});
test("M12-04H cleans staging on payload failure and detects rollback identity drift", async () => {
const failedStorage = createStorage("payload-failure");
await assert.rejects(
extraction.runArchiveExtractionTransaction(request, failedStorage, async () => Buffer.from("wrong"), new AbortController().signal),
{ code: "ASSET_SOURCE_HASH_MISMATCH" },
);
assert.equal(await failedStorage.countStagingEntries(request.transactionId), 0);
assert.deepEqual(await failedStorage.readCommitted(), committed);
const driftStorage = createStorage("rollback-drift");
const originalDiscard = driftStorage.discardStaging.bind(driftStorage);
driftStorage.discardStaging = async (transactionId) => {
const removed = await originalDiscard(transactionId);
driftStorage.committed = { ...committed, revision: committed.revision + 1 };
return removed;
};
const controller = new AbortController();
await assert.rejects(
extraction.runArchiveExtractionTransaction(request, driftStorage, async (entry) => {
const bytes = entry.path === "project.blend" ? newBytes : metadataBytes;
queueMicrotask(() => controller.abort());
return bytes;
}, controller.signal),
{ code: "STORAGE_TRANSACTION" },
);
});
test("M12-04H rejects stale commits, non-canonical paths, prefix conflicts, and undeclared fields", async () => {
const storage = createStorage("invalid-requests");
await assert.rejects(
extraction.runArchiveExtractionTransaction({ ...request, committed: { ...committed, revision: 7 } }, storage, async () => newBytes, new AbortController().signal),
{ code: "REVISION_CONFLICT" },
);
for (const unsafePath of ["../project.blend", "C:/project.blend", "https:project.blend", "dir\\project.blend"]) {
await assert.rejects(
extraction.runArchiveExtractionTransaction({ ...request, entries: [{ ...request.entries[0], path: unsafePath }] }, storage, async () => metadataBytes, new AbortController().signal),
{ code: "IO_ARCHIVE_UNSAFE" },
);
}
await assert.rejects(
extraction.runArchiveExtractionTransaction({ ...request, entries: [request.entries[0], { ...request.entries[1], path: "metadata" }] }, storage, async () => metadataBytes, new AbortController().signal),
{ code: "IO_ARCHIVE_UNSAFE" },
);
await assert.rejects(
extraction.runArchiveExtractionTransaction({ ...request, future: true }, storage, async () => newBytes, new AbortController().signal),
{ code: "IO_ARCHIVE_UNSAFE" },
);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-archive-conflicts-unit-"));
const sourcePath = path.join(root, "web/protocol/archive-conflicts.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "archive-conflicts.mjs"), transpiled.outputText);
const conflicts = await import(pathToFileURL(path.join(temporary, "archive-conflicts.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04G/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const range = (pathName, compressedOffset, compressedBytes = 2, uncompressedBytes = 2) => ({ path: pathName, compressedOffset, compressedBytes, uncompressedBytes });
const valid = { schemaVersion: 1, byteLength: 10, ranges: [range("a.bin", 0), range("dir/b.bin", 2, 3, 3)] };
test("M12-04G binds range/conflict validation and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04G");
assert.equal(manifest.parentTask, "M12-04F");
assert.equal(manifest.nextTask, "M12-04H");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04G accepts deterministic non-overlapping ranges and reports totals", () => {
assert.deepEqual(conflicts.validateArchiveConflicts(valid), { status: "VALID", totalCompressedBytes: 5, totalUncompressedBytes: 5, nonOverlapping: true, uniquePaths: true, noPrefixConflicts: true });
});
test("M12-04G rejects compression bombs, overlaps, duplicate paths, and prefix conflicts", () => {
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [range("bomb.bin", 0, 1, conflicts.ARCHIVE_CONFLICT_BUDGET.maxCompressionRatio + 1)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [range("a.bin", 0, 4), range("b.bin", 3, 2)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [range("same.bin", 0), range("same.bin", 2)] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [range("folder", 0), range("folder/file.bin", 2)] }), { code: "IO_ARCHIVE_UNSAFE" });
});
test("M12-04G rejects range/source bounds and undeclared fields", () => {
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, byteLength: 4 }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [{ ...valid.ranges[0], future: true }] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => conflicts.validateArchiveConflicts({ ...valid, ranges: [{ ...valid.ranges[0], compressedOffset: 9, compressedBytes: 2 }] }), { code: "IO_ARCHIVE_UNSAFE" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,163 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-archive-recovery-unit-"));
for (const sourceName of ["archive-extraction-transaction.ts", "archive-extraction-recovery.ts"]) {
const sourcePath = path.join(root, "web/protocol", sourceName);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(
path.join(temporary, sourceName.replace(".ts", ".mjs")),
transpiled.outputText.replaceAll('from "./archive-extraction-transaction"', 'from "./archive-extraction-transaction.mjs"'),
);
}
const extraction = await import(pathToFileURL(path.join(temporary, "archive-extraction-recovery.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04I/manifest.json"), "utf8"));
const fileSha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const bytesSha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
class FaultingDirectoryStorage {
constructor(directory, committed) {
this.directory = directory;
this.committed = { ...committed };
this.fault = null;
this.partialBytes = 0;
fs.mkdirSync(path.join(directory, "committed"), { recursive: true });
fs.writeFileSync(path.join(directory, "committed", "project.blend"), Buffer.from("old-project"));
}
async readCommitted() { return { ...this.committed }; }
async createStaging(transactionId) {
fs.mkdirSync(path.join(this.directory, "staging", transactionId), { recursive: true });
}
async writeStaging(transactionId, entryPath, bytes) {
const target = path.join(this.directory, "staging", transactionId, entryPath);
fs.mkdirSync(path.dirname(target), { recursive: true });
if (this.fault) {
const partial = bytes.subarray(0, Math.max(1, Math.floor(bytes.byteLength / 2)));
fs.writeFileSync(target, partial);
this.partialBytes += partial.byteLength;
const fault = this.fault;
this.fault = null;
throw fault;
}
fs.writeFileSync(target, bytes);
}
async countStagingEntries(transactionId) {
const staging = path.join(this.directory, "staging", transactionId);
if (!fs.existsSync(staging)) return 0;
const visit = (directory) => fs.readdirSync(directory, { withFileTypes: true }).reduce(
(count, entry) => count + (entry.isDirectory() ? visit(path.join(directory, entry.name)) : 1), 0,
);
return visit(staging);
}
async discardStaging(transactionId) {
const count = await this.countStagingEntries(transactionId);
fs.rmSync(path.join(this.directory, "staging", transactionId), { recursive: true, force: true });
return count;
}
async commitStaging(transactionId, expected, candidate) {
assert.deepEqual(this.committed, expected);
const source = path.join(this.directory, "staging", transactionId, "project.blend");
const target = path.join(this.directory, "committed", "project.blend");
fs.renameSync(source, target);
await this.discardStaging(transactionId);
this.committed = { ...candidate };
return { ...this.committed };
}
}
const oldBytes = Buffer.from("old-project");
const largeBytes = Buffer.alloc(4096, 0x51);
const smallBytes = Buffer.from("small-project");
const committed = { projectId: "project:m12-04i", revision: 9, sha256: bytesSha256(oldBytes) };
function request(transactionId, base, bytes) {
return {
schemaVersion: 1,
transactionId,
archiveId: `archive:${transactionId}`,
committed: base,
candidate: { projectId: base.projectId, revision: base.revision + 1, sha256: bytesSha256(bytes) },
entries: [{ path: "project.blend", uncompressedBytes: bytes.byteLength, sha256: bytesSha256(bytes) }],
};
}
function createStorage(name) {
const directory = path.join(temporary, name);
fs.mkdirSync(directory, { recursive: true });
return new FaultingDirectoryStorage(directory, committed);
}
async function faultThenRecover(name, fault, expectedCode) {
const storage = createStorage(name);
storage.fault = fault;
const failedRequest = request(`transaction:${name}:large`, committed, largeBytes);
await assert.rejects(
extraction.runArchiveExtractionTransaction(failedRequest, storage, async () => largeBytes, new AbortController().signal),
{ code: expectedCode },
);
assert.ok(storage.partialBytes > 0);
assert.equal(await storage.countStagingEntries(failedRequest.transactionId), 0);
assert.deepEqual(await storage.readCommitted(), committed);
assert.equal(fs.readFileSync(path.join(storage.directory, "committed", "project.blend"), "utf8"), "old-project");
const recoveryRequest = request(`transaction:${name}:small`, committed, smallBytes);
const receipt = await extraction.runArchiveExtractionTransaction(
recoveryRequest,
storage,
async () => smallBytes,
new AbortController().signal,
);
assert.deepEqual(receipt, {
status: "COMMITTED",
transactionId: recoveryRequest.transactionId,
committed: recoveryRequest.candidate,
stagingEntriesAfter: 0,
});
assert.equal(await storage.countStagingEntries(recoveryRequest.transactionId), 0);
assert.equal(fs.readFileSync(path.join(storage.directory, "committed", "project.blend"), "utf8"), "small-project");
}
test("M12-04I binds quota/OOM cleanup and recovery evidence", () => {
assert.equal(manifest.task, "M12-04I");
assert.equal(manifest.parentTask, "M12-04H");
assert.equal(manifest.nextTask, "M12-04J");
assert.deepEqual(manifest.assertions.faultCodes, ["STORAGE_QUOTA", "WASM_OUT_OF_MEMORY"]);
for (const artifact of Object.values(manifest.artifacts)) assert.equal(fileSha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04I releases partial staging after quota and accepts a small archive in the same storage", async () => {
await faultThenRecover("quota", new DOMException("quota exhausted", "QuotaExceededError"), "STORAGE_QUOTA");
});
test("M12-04I releases partial staging after OOM and accepts a small archive in the same storage", async () => {
await faultThenRecover("oom", Object.assign(new Error("deterministic allocation failure"), { code: "WASM_OUT_OF_MEMORY" }), "WASM_OUT_OF_MEMORY");
});
test("M12-04I maps only declared resource faults", () => {
assert.equal(extraction.archiveExtractionResourceFaultCode(new DOMException("full", "QuotaExceededError")), "STORAGE_QUOTA");
assert.equal(extraction.archiveExtractionResourceFaultCode(Object.assign(new Error("fault"), { code: "STORAGE_QUOTA" })), "STORAGE_QUOTA");
assert.equal(extraction.archiveExtractionResourceFaultCode(Object.assign(new Error("fault"), { code: "WASM_OUT_OF_MEMORY" })), "WASM_OUT_OF_MEMORY");
assert.equal(extraction.archiveExtractionResourceFaultCode(Object.assign(new Error("fault"), { name: "OutOfMemoryError" })), "WASM_OUT_OF_MEMORY");
assert.equal(extraction.archiveExtractionResourceFaultCode(new RangeError("array length is invalid")), undefined);
assert.equal(extraction.archiveExtractionResourceFaultCode(new Error("ordinary IO failure")), undefined);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,88 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-link-safety-unit-"));
const sourcePath = path.join(root, "web/protocol/archive-link-safety.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "archive-link-safety.mjs"), transpiled.outputText);
const safety = await import(pathToFileURL(path.join(temporary, "archive-link-safety.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04D/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const valid = {
schemaVersion: 1,
temporaryRootId: "staging:archive-1",
entries: [
{ path: "payload/data.bin", type: "FILE", target: null },
{ path: "payload/data-alias.bin", type: "SYMLINK", target: "./data.bin" },
{ path: "payload/data-hard.bin", type: "HARDLINK", target: "payload/data.bin" },
{ path: "payload", type: "DIRECTORY", target: null },
{ path: "alias-dir", type: "SYMLINK", target: "payload" },
],
};
test("M12-04D binds the archive link gate and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04D");
assert.equal(manifest.parentTask, "M12-04C");
assert.equal(manifest.nextTask, "M12-04E");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04D resolves symlinks relative to their parent and hardlinks from the archive root", () => {
const result = safety.resolveArchiveLinkEntries(valid);
assert.equal(result.status, "READY");
assert.equal(result.entries.find((entry) => entry.path === "payload/data-alias.bin").resolvedPath, "payload/data.bin");
assert.equal(result.entries.find((entry) => entry.path === "payload/data-hard.bin").resolvedPath, "payload/data.bin");
assert.equal(result.entries.find((entry) => entry.path === "alias-dir").resolvedPath, "payload");
assert.ok(result.entries.every((entry) => entry.withinTemporaryRoot === true));
});
test("M12-04D rejects absolute, drive, URI and traversal targets before writing", () => {
for (const target of ["/outside", "\\\\server\\share", "C:/outside", "https://evil.example/a", "../../outside", "payload/../../outside"]) {
assert.throws(() => safety.resolveArchiveLinkEntries({
...valid,
entries: [{ path: "payload/link", type: "SYMLINK", target }, { path: "payload", type: "DIRECTORY", target: null }],
}), { code: "IO_ARCHIVE_UNSAFE" });
}
});
test("M12-04D rejects missing targets, cycles and hardlinks to directories", () => {
assert.throws(() => safety.resolveArchiveLinkEntries({
...valid,
entries: [{ path: "link", type: "SYMLINK", target: "missing.bin" }],
}), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => safety.resolveArchiveLinkEntries({
...valid,
entries: [
{ path: "a", type: "SYMLINK", target: "b" },
{ path: "b", type: "HARDLINK", target: "a" },
],
}), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => safety.resolveArchiveLinkEntries({
...valid,
entries: [
{ path: "dir", type: "DIRECTORY", target: null },
{ path: "dir-hard", type: "HARDLINK", target: "dir" },
],
}), { code: "IO_ARCHIVE_UNSAFE" });
});
test("M12-04D rejects duplicate members and undeclared fields", () => {
assert.throws(() => safety.parseArchiveLinkRequest({ ...valid, entries: [{ ...valid.entries[0], target: null }, { ...valid.entries[0], target: null }] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => safety.parseArchiveLinkRequest({ ...valid, future: true }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => safety.parseArchiveLinkRequest({ ...valid, entries: [{ ...valid.entries[0], mode: 0o644 }] }), { code: "IO_ARCHIVE_UNSAFE" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,107 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-linked-missing-unit-"));
const sourcePath = path.join(root, "web/protocol/library-linked-missing.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-linked-missing.mjs"), transpiled.outputText);
const missing = await import(pathToFileURL(path.join(temporary, "library-linked-missing.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03I/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const library = (value) => `library:${String(value).repeat(64).slice(0, 64)}`;
const digest = (value) => crypto.createHash("sha256").update(String(value)).digest("hex");
const reference = (sourceLibraryId, generation, revision, marker, status = "AVAILABLE") => ({
sourceLibraryId,
sourceLocator: `project://libraries/${marker}.blend`,
sourceSha256: digest(`${marker}-source`),
sourceGeneration: generation,
sourceRevision: revision,
dataBlockIds: [`Object/${marker}`, `Mesh/${marker}`],
status,
placeholder: status === "MISSING" ? {
kind: "MISSING_LIBRARY",
sourceLibraryId,
dataBlockIds: [`Object/${marker}`, `Mesh/${marker}`],
} : null,
});
test("M12-03I binds the missing-library protocol and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03I");
assert.equal(manifest.parentTask, "M12-03H");
assert.equal(manifest.nextTask, "M12-03J");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03I marks only the matching reference missing and preserves its original source", () => {
const sourceLibraryId = library("a");
const otherLibraryId = library("b");
const current = reference(sourceLibraryId, 4, 9, "primary");
const other = reference(otherLibraryId, 1, 2, "other");
const state = { schemaVersion: 1, references: [current, other] };
const decision = missing.markLinkedLibraryMissing(state, {
schemaVersion: 1,
operation: "MARK_MISSING",
sourceLibraryId,
sourceLocator: current.sourceLocator,
sourceSha256: current.sourceSha256,
expectedGeneration: 4,
expectedRevision: 9,
});
assert.equal(decision.status, "MARKED");
assert.equal(decision.code, null);
assert.deepEqual(decision.state.references[0], { ...current, status: "MISSING", placeholder: {
kind: "MISSING_LIBRARY", sourceLibraryId, dataBlockIds: current.dataBlockIds,
} });
assert.deepEqual(decision.state.references[1], other);
assert.deepEqual(state.references, [current, other]);
});
test("M12-03I preserves the reference on stale generation and source hash drift", () => {
const sourceLibraryId = library("c");
const current = reference(sourceLibraryId, 2, 5, "stable");
const state = { schemaVersion: 1, references: [current] };
const base = {
schemaVersion: 1,
operation: "MARK_MISSING",
sourceLibraryId,
sourceLocator: current.sourceLocator,
sourceSha256: current.sourceSha256,
expectedGeneration: 2,
expectedRevision: 5,
};
assert.equal(missing.markLinkedLibraryMissing(state, { ...base, expectedRevision: 6 }).code, "REVISION_CONFLICT");
assert.equal(missing.markLinkedLibraryMissing(state, { ...base, sourceSha256: digest("different") }).code, "ASSET_SOURCE_HASH_MISMATCH");
assert.deepEqual(state.references, [current]);
});
test("M12-03I rejects undeclared fields, duplicate identities, and invalid placeholders", () => {
const sourceLibraryId = library("d");
const current = reference(sourceLibraryId, 1, 1, "invalid");
assert.throws(() => missing.parseLinkedMissingRequest({
schemaVersion: 1,
operation: "MARK_MISSING",
sourceLibraryId,
sourceLocator: current.sourceLocator,
sourceSha256: current.sourceSha256,
expectedGeneration: 1,
expectedRevision: 1,
future: true,
}), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => missing.parseLinkedMissingState({ schemaVersion: 1, references: [current, current] }), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => missing.parseLinkedLibraryReference({ ...current, status: "MISSING", placeholder: null }), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => missing.parseLinkedLibraryReference({ ...current, status: "AVAILABLE", placeholder: { kind: "MISSING_LIBRARY", sourceLibraryId, dataBlockIds: current.dataBlockIds } }), { code: "TASK_VALIDATION_FAILED" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-linked-mutation-unit-"));
const transpile = (sourceName, outputName, replacements = []) => {
const sourcePath = path.join(root, "web/protocol", sourceName);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, outputName), replacements.reduce((source, [from, to]) => source.replaceAll(from, to), transpiled.outputText));
};
transpile("capability-gates.ts", "capability-gates.mjs");
transpile("library-linked-mutation.ts", "library-linked-mutation.mjs", [["from \"./capability-gates\"", "from \"./capability-gates.mjs\""]]);
const linked = await import(pathToFileURL(path.join(temporary, "library-linked-mutation.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03G/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const base = (operation = "MESH_GEOMETRY") => ({
schemaVersion: 1,
operation,
dataBlockId: "mesh:M12 Link Mesh",
baseRevision: 7,
owner: "SOURCE_LIBRARY",
linkedLibrary: true,
readOnly: true,
});
test("M12-03G binds the linked writer protocol and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03G");
assert.equal(manifest.parentTask, "M12-03F");
assert.equal(manifest.nextTask, "M12-03H");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03G blocks every linked data writer with the stable mutation code", () => {
assert.equal(linked.LINKED_DATA_WRITER_OPERATIONS.length, 6);
for (const operation of linked.LINKED_DATA_WRITER_OPERATIONS) {
const gate = linked.gateLinkedDataMutation(base(operation), 7);
assert.equal(gate.status, "BLOCKED");
assert.deepEqual(gate.issues.map((issue) => issue.code), ["LINKED_DATA_MUTATION_BLOCKED"]);
assert.equal(gate.issues[0].recoverable, false);
assert.deepEqual(linked.parseLinkedDataMutation(base(operation)), { ...base(operation) });
}
});
test("M12-03G rejects stale, malformed, and ownership-substituted writes before Main", () => {
assert.equal(linked.gateLinkedDataMutation(base(), 8).issues[0].code, "REVISION_CONFLICT");
assert.equal(linked.gateLinkedDataMutation({ ...base(), owner: "LOCAL_MAIN", linkedLibrary: false, readOnly: false }, 7).issues[0].code, "LINKED_DATA_MUTATION_BLOCKED");
assert.equal(linked.gateLinkedDataMutation({ ...base(), future: true }, 7).issues[0].code, "TASK_VALIDATION_FAILED");
assert.equal(linked.gateLinkedDataMutation({ ...base(), operation: "UNKNOWN" }, 7).issues[0].code, "TASK_VALIDATION_FAILED");
});

View File

@@ -0,0 +1,111 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-linked-reload-unit-"));
const sourcePath = path.join(root, "web/protocol/library-linked-reload.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-linked-reload.mjs"), transpiled.outputText);
const reload = await import(pathToFileURL(path.join(temporary, "library-linked-reload.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03H/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const library = (value) => `library:${String(value).repeat(64).slice(0, 64)}`;
const digest = (value) => crypto.createHash("sha256").update(String(value)).digest("hex");
const snapshot = (sourceLibraryId, generation, revision, marker) => ({
sourceLibraryId,
sourceGeneration: generation,
sourceRevision: revision,
dependencyClosureSha256: digest(marker),
graphSha256: digest(`${marker}-graph`),
dataBlocks: [
{ dataBlockId: `Object/${marker}`, owner: "SOURCE_LIBRARY", readOnly: true },
{ dataBlockId: `Mesh/${marker}`, owner: "SOURCE_LIBRARY", readOnly: true },
],
});
test("M12-03H binds the reload protocol and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03H");
assert.equal(manifest.parentTask, "M12-03G");
assert.equal(manifest.nextTask, "M12-03I");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03H replaces only the matching library generation and preserves every other snapshot", () => {
const sourceLibraryId = library("a");
const otherLibraryId = library("b");
const current = snapshot(sourceLibraryId, 3, 7, "old");
const otherGeneration = snapshot(sourceLibraryId, 9, 2, "future");
const otherLibrary = snapshot(otherLibraryId, 1, 4, "other");
const state = { schemaVersion: 1, snapshots: [current, otherGeneration, otherLibrary] };
const replacement = snapshot(sourceLibraryId, 4, 8, "new");
const decision = reload.reloadMatchingLinkedSnapshot(state, {
schemaVersion: 1,
operation: "RELOAD",
sourceLibraryId,
expectedGeneration: 3,
expectedRevision: 7,
replacement,
});
assert.equal(decision.status, "REPLACED");
assert.equal(decision.code, null);
assert.deepEqual(decision.state.snapshots, [replacement, otherGeneration, otherLibrary]);
assert.deepEqual(state.snapshots, [current, otherGeneration, otherLibrary]);
assert(decision.state.snapshots.every((item) => item.dataBlocks.every((block) => block.owner === "SOURCE_LIBRARY" && block.readOnly)));
});
test("M12-03H rejects stale generations and keeps state byte-for-byte equivalent", () => {
const sourceLibraryId = library("c");
const state = { schemaVersion: 1, snapshots: [snapshot(sourceLibraryId, 5, 11, "stable")] };
const request = {
schemaVersion: 1,
operation: "RELOAD",
sourceLibraryId,
expectedGeneration: 4,
expectedRevision: 10,
replacement: snapshot(sourceLibraryId, 5, 12, "late"),
};
const decision = reload.reloadMatchingLinkedSnapshot(state, request);
assert.deepEqual(decision, {
status: "STALE",
code: "REVISION_CONFLICT",
sourceLibraryId,
replacedGeneration: 4,
replacementGeneration: 5,
state,
});
});
test("M12-03H fails closed for malformed, substituted, duplicate, and non-adjacent reloads", () => {
const sourceLibraryId = library("d");
const current = snapshot(sourceLibraryId, 1, 1, "current");
const baseRequest = {
schemaVersion: 1,
operation: "RELOAD",
sourceLibraryId,
expectedGeneration: 1,
expectedRevision: 1,
replacement: snapshot(sourceLibraryId, 2, 2, "replacement"),
};
assert.throws(() => reload.parseLinkedReloadRequest({ ...baseRequest, future: true }), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => reload.parseLinkedReloadRequest({ ...baseRequest, replacement: { ...baseRequest.replacement, sourceLibraryId: library("e") } }), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => reload.parseLinkedReloadState({ schemaVersion: 1, snapshots: [current, current] }), { code: "TASK_VALIDATION_FAILED" });
const decision = reload.reloadMatchingLinkedSnapshot({ schemaVersion: 1, snapshots: [current] }, {
...baseRequest,
replacement: snapshot(sourceLibraryId, 3, 3, "skip"),
});
assert.equal(decision.status, "STALE");
assert.equal(decision.code, "REVISION_CONFLICT");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,67 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-metadata-first-unit-"));
const sourcePath = path.join(root, "web/protocol/archive-metadata-first.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "archive-metadata-first.mjs"), transpiled.outputText);
const metadata = await import(pathToFileURL(path.join(temporary, "archive-metadata-first.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04E/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const zip = { schemaVersion: 1, archiveId: "archive:zip-1", format: "ZIP", archiveByteLength: 4096, metadataOffset: 3072, metadataByteLength: 512 };
const tar = { schemaVersion: 1, archiveId: "archive:tar-1", format: "TAR", archiveByteLength: 4096, metadataOffset: 0, metadataByteLength: 1024 };
test("M12-04E binds metadata-first planning and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04E");
assert.equal(manifest.parentTask, "M12-04D");
assert.equal(manifest.nextTask, "M12-04F");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04E plans ZIP central-directory and TAR manifest reads without payload ranges", () => {
assert.deepEqual(metadata.planArchiveMetadataRead(zip), {
status: "METADATA_ONLY", schemaVersion: 1, archiveId: "archive:zip-1", format: "ZIP",
firstRead: { kind: "CENTRAL_DIRECTORY", byteOffset: 3072, byteLength: 512 }, payloadReads: [],
});
assert.deepEqual(metadata.planArchiveMetadataRead(tar).firstRead, { kind: "MANIFEST", byteOffset: 0, byteLength: 1024 });
});
test("M12-04E accepts a trace only when metadata is the first exact read", () => {
assert.deepEqual(metadata.validateArchiveReadTrace(zip, {
schemaVersion: 1, archiveId: "archive:zip-1", reads: [
{ sequence: 0, kind: "CENTRAL_DIRECTORY", byteOffset: 3072, byteLength: 512 },
{ sequence: 1, kind: "PAYLOAD", byteOffset: 32, byteLength: 128 },
],
}), { status: "VALID", metadataFirst: true, payloadReadsAfterMetadata: true });
assert.deepEqual(metadata.validateArchiveReadTrace(tar, {
schemaVersion: 1, archiveId: "archive:tar-1", reads: [{ sequence: 0, kind: "MANIFEST", byteOffset: 0, byteLength: 1024 }],
}).metadataFirst, true);
});
test("M12-04E rejects payload-first, wrong-range, duplicate-metadata and invalid ranges", () => {
assert.throws(() => metadata.validateArchiveReadTrace(zip, { schemaVersion: 1, archiveId: "archive:zip-1", reads: [
{ sequence: 0, kind: "PAYLOAD", byteOffset: 0, byteLength: 16 },
{ sequence: 1, kind: "CENTRAL_DIRECTORY", byteOffset: 3072, byteLength: 512 },
] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => metadata.validateArchiveReadTrace(zip, { schemaVersion: 1, archiveId: "archive:zip-1", reads: [{ sequence: 0, kind: "CENTRAL_DIRECTORY", byteOffset: 3000, byteLength: 512 }] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => metadata.validateArchiveReadTrace(zip, { schemaVersion: 1, archiveId: "archive:zip-1", reads: [
{ sequence: 0, kind: "CENTRAL_DIRECTORY", byteOffset: 3072, byteLength: 512 },
{ sequence: 1, kind: "CENTRAL_DIRECTORY", byteOffset: 3072, byteLength: 512 },
] }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => metadata.planArchiveMetadataRead({ ...zip, metadataOffset: 4000, metadataByteLength: 200 }), { code: "IO_ARCHIVE_UNSAFE" });
assert.throws(() => metadata.planArchiveMetadataRead({ ...zip, metadataByteLength: 64 * 1024 * 1024 + 1 }), { code: "IO_ARCHIVE_UNSAFE" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,56 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-negative-cases-unit-"));
const sourcePath = path.join(root, "web/protocol/library-negative-cases.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-negative-cases.mjs"), transpiled.outputText);
const negatives = await import(pathToFileURL(path.join(temporary, "library-negative-cases.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03M/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const a = "library:" + "a".repeat(64);
const b = "library:" + "b".repeat(64);
const c = "library:" + "c".repeat(64);
const valid = {
schemaVersion: 1,
libraries: [{ libraryId: a, dependencyIds: [b] }, { libraryId: b, dependencyIds: [] }, { libraryId: c, dependencyIds: [] }],
dataBlocks: [{ dataBlockId: "Object/A", sourceLibraryId: a }, { dataBlockId: "Object/B", sourceLibraryId: b }],
crossReferences: [],
reloads: [{ sourceLibraryId: a, generation: 1 }, { sourceLibraryId: a, generation: 2 }],
};
test("M12-03M binds the library negative-case protocol and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03M");
assert.equal(manifest.parentTask, "M12-03L");
assert.equal(manifest.nextTask, "M12-03N");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03M accepts an acyclic graph with unique data-block and reload identities", () => {
assert.deepEqual(negatives.validateLibraryNegativeInput(valid), { status: "VALID" });
});
test("M12-03M rejects dependency and cross-library cycles", () => {
assert.throws(() => negatives.validateLibraryNegativeInput({ ...valid, libraries: [{ libraryId: a, dependencyIds: [b] }, { libraryId: b, dependencyIds: [a] }, { libraryId: c, dependencyIds: [] }] }), { code: "LIBRARY_DEPENDENCY_CYCLE" });
assert.throws(() => negatives.validateLibraryNegativeInput({ ...valid, crossReferences: [{ fromLibraryId: a, toLibraryId: b }, { fromLibraryId: b, toLibraryId: a }] }), { code: "LIBRARY_DEPENDENCY_CYCLE" });
});
test("M12-03M rejects ID collision, duplicate reload and missing library references", () => {
assert.throws(() => negatives.validateLibraryNegativeInput({ ...valid, dataBlocks: [{ dataBlockId: "Object/A", sourceLibraryId: a }, { dataBlockId: "Object/A", sourceLibraryId: b }] }), { code: "TASK_VALIDATION_FAILED" });
assert.throws(() => negatives.validateLibraryNegativeInput({ ...valid, reloads: [{ sourceLibraryId: a, generation: 1 }, { sourceLibraryId: a, generation: 1 }] }), { code: "REVISION_CONFLICT" });
assert.throws(() => negatives.validateLibraryNegativeInput({ ...valid, dataBlocks: [{ dataBlockId: "Object/A", sourceLibraryId: "library:" + "d".repeat(64) }] }), { code: "ASSET_SOURCE_HASH_MISMATCH" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,86 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-override-freshness-unit-"));
const sourcePath = path.join(root, "web/protocol/library-override-freshness.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-override-freshness.mjs"), transpiled.outputText);
const freshness = await import(pathToFileURL(path.join(temporary, "library-override-freshness.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03L/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const library = "library:" + "a".repeat(64);
const digest = "b".repeat(64);
const token = "override-token:" + "c".repeat(64);
const state = (revision = 7) => ({
schemaVersion: 1,
sourceLibraryId: library,
sourceGeneration: 4,
sourceRevision: revision,
dependencyClosureSha256: digest,
invalidationToken: token,
localDataBlockId: "Object/M12 Override Object",
referenceSourceDataBlockId: "Object/M12 Override Object",
hierarchyRootDataBlockId: "Object/M12 Override Object",
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
});
const request = (overrides = {}) => ({
schemaVersion: 1,
operation: "COMMIT_OVERRIDE",
sourceLibraryId: library,
sourceGeneration: 4,
sourceRevision: 7,
dependencyClosureSha256: digest,
invalidationToken: token,
baseRevision: 7,
localDataBlockId: "Object/M12 Override Object",
referenceSourceDataBlockId: "Object/M12 Override Object",
hierarchyRootDataBlockId: "Object/M12 Override Object",
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
...overrides,
});
test("M12-03L binds the override freshness gate and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03L");
assert.equal(manifest.parentTask, "M12-03K");
assert.equal(manifest.nextTask, "M12-03M");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03L allows only a fully matching source generation/revision commit", () => {
assert.deepEqual(freshness.gateOverrideFreshness(state(), request()), { status: "READY", code: null });
});
test("M12-03L blocks stale generation, revision, closure, token, and identity before Main", () => {
for (const overrides of [
{ sourceGeneration: 3 },
{ sourceRevision: 6, baseRevision: 6 },
{ dependencyClosureSha256: "d".repeat(64) },
{ invalidationToken: "override-token:" + "e".repeat(64) },
]) assert.deepEqual(freshness.gateOverrideFreshness(state(), request(overrides)), { status: "BLOCKED", code: "REVISION_CONFLICT" });
assert.deepEqual(freshness.gateOverrideFreshness(state(), request({ localDataBlockId: "Object/Other" })), { status: "BLOCKED", code: "ASSET_SOURCE_HASH_MISMATCH" });
});
test("M12-03L rejects linked ownership, alternate operations, malformed tokens, and extra fields", () => {
assert.equal(freshness.gateOverrideFreshness(state(), request({ owner: "SOURCE_LIBRARY", readOnly: true })).code, "LINKED_DATA_MUTATION_BLOCKED");
assert.equal(freshness.gateOverrideFreshness(state(), request({ operation: "SET_LOCATION" })).code, "TASK_VALIDATION_FAILED");
assert.equal(freshness.gateOverrideFreshness(state(), request({ invalidationToken: "bad" })).code, "TASK_VALIDATION_FAILED");
assert.equal(freshness.gateOverrideFreshness(state(), { ...request(), future: true }).code, "TASK_VALIDATION_FAILED");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,82 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-override-writer-unit-"));
const sourcePath = path.join(root, "web/protocol/library-override-writer.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, "library-override-writer.mjs"), transpiled.outputText);
const writer = await import(pathToFileURL(path.join(temporary, "library-override-writer.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03K/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const state = (revision = 3, value = 2.5) => ({
schemaVersion: 1,
revision,
localDataBlockId: "Object/M12 Override Object",
referenceSourceDataBlockId: "Object/M12 Override Object",
hierarchyRootDataBlockId: "Object/M12 Override Object",
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
propertyPath: '["m12_override_value"]',
value,
});
const request = (baseRevision = 3, value = 4.5) => ({
schemaVersion: 1,
operation: "SET_M12_OVERRIDE_VALUE",
baseRevision,
localDataBlockId: "Object/M12 Override Object",
referenceSourceDataBlockId: "Object/M12 Override Object",
hierarchyRootDataBlockId: "Object/M12 Override Object",
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
propertyPath: '["m12_override_value"]',
value,
});
test("M12-03K binds the single-property writer and evidence artifacts", () => {
assert.equal(manifest.task, "M12-03K");
assert.equal(manifest.parentTask, "M12-03J");
assert.equal(manifest.nextTask, "M12-03L");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-03K applies exactly the verified override property and advances one revision", () => {
const result = writer.applyOverrideWriter(state(), request());
assert.equal(result.status, "APPLIED");
assert.equal(result.code, null);
assert.equal(result.state.revision, 4);
assert.equal(result.state.value, 4.5);
assert.equal(result.state.propertyPath, writer.LIBRARY_OVERRIDE_PROPERTY_PATH);
assert.equal(result.state.owner, "LOCAL_OVERRIDE");
assert.equal(result.state.referenceReadOnly, true);
});
test("M12-03K blocks stale, linked-owner, identity, and second-property writes", () => {
assert.equal(writer.applyOverrideWriter(state(3), request(2)).code, "REVISION_CONFLICT");
assert.equal(writer.applyOverrideWriter(state(), { ...request(), owner: "SOURCE_LIBRARY", readOnly: true, referenceReadOnly: true }).code, "LINKED_DATA_MUTATION_BLOCKED");
assert.equal(writer.applyOverrideWriter(state(), { ...request(), localDataBlockId: "Object/Other" }).code, "ASSET_SOURCE_HASH_MISMATCH");
assert.equal(writer.applyOverrideWriter(state(), { ...request(), propertyPath: "location" }).code, "EDITOR_WRITER_UNAVAILABLE");
assert.equal(writer.applyOverrideWriter(state(), { ...request(), operation: "SET_LOCATION" }).code, "TASK_VALIDATION_FAILED");
});
test("M12-03K rejects malformed values and undeclared fields before the writer", () => {
assert.equal(writer.applyOverrideWriter(state(), { ...request(), future: true }).code, "TASK_VALIDATION_FAILED");
assert.equal(writer.applyOverrideWriter(state(), { ...request(), value: Number.NaN }).code, "TASK_VALIDATION_FAILED");
assert.throws(() => writer.parseOverrideWriterState({ ...state(), propertyPath: "location" }), { code: "EDITOR_WRITER_UNAVAILABLE" });
assert.throws(() => writer.parseOverrideWriterRequest({ ...request(), value: 1e9 }), { code: "TASK_VALIDATION_FAILED" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,67 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-path-normalization-unit-"));
for (const name of ["asset-path.ts", "library-source-origin.ts"]) {
const sourcePath = path.join(root, "web/protocol", name);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, name.replace(".ts", ".mjs")), transpiled.outputText.replaceAll('from "./asset-path"', 'from "./asset-path.mjs"'));
}
const paths = await import(pathToFileURL(path.join(temporary, "asset-path.mjs")));
const origin = await import(pathToFileURL(path.join(temporary, "library-source-origin.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04B/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const policy = { schemaVersion: 1, declaredHttpsOrigins: ["https://assets.example.test"] };
test("M12-04B binds the shared path normalizer and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04B");
assert.equal(manifest.nextTask, "M12-04C");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04B canonicalizes POSIX and Windows separators with dot segments", () => {
const aliases = [
"libraries/characters/main.blend",
"libraries\\characters\\.\\hero\\..\\main.blend",
"libraries//characters/models/../main.blend",
"//libraries/characters/./main.blend",
];
for (const alias of aliases) assert.equal(paths.normalizeProjectAssetPath(alias), "libraries/characters/main.blend");
assert.equal(paths.normalizeProjectAssetPath(paths.normalizeProjectAssetPath(aliases[1])), "libraries/characters/main.blend");
});
test("M12-04B decodes percent octets and NFC-normalizes Unicode names", () => {
const canonical = "libraries/角色/caf\u00e9.blend";
const aliases = [
"libraries/%E8%A7%92%E8%89%B2/caf%65%CC%81.blend",
"libraries%2F%E8%A7%92%E8%89%B2%5Ccafe%CC%81.blend",
"libraries/角色/cafe\u0301.blend",
];
for (const alias of aliases) assert.equal(paths.normalizeProjectAssetPath(alias), canonical);
assert.deepEqual(origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "PROJECT_ASSET", path: aliases[1] }), {
status: "READY",
kind: "PROJECT_ASSET",
canonicalLocator: `project-assets/${canonical}`,
});
});
test("M12-04B resolves encoded dot segments once and fails closed on escape or re-decoding", () => {
assert.equal(paths.normalizeProjectAssetPath("libraries/temp/%2E%2E/main.blend"), "libraries/main.blend");
assert.throws(() => paths.normalizeProjectAssetPath("libraries/%2E%2E/%2E%2E/outside.blend"), /ASSET_PATH_OUTSIDE_PROJECT/);
assert.throws(() => paths.normalizeProjectAssetPath("libraries/%252E%252E/outside.blend"), /ASSET_PATH_OUTSIDE_PROJECT/);
assert.throws(() => paths.normalizeProjectAssetPath("libraries/%GG/outside.blend"), /ASSET_PATH_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,84 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-path-security-unit-"));
for (const name of ["asset-path.ts", "library-source-origin.ts"]) {
const sourcePath = path.join(root, "web/protocol", name);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, name.replace(".ts", ".mjs")), transpiled.outputText.replaceAll('from "./asset-path"', 'from "./asset-path.mjs"'));
}
const paths = await import(pathToFileURL(path.join(temporary, "asset-path.mjs")));
const origin = await import(pathToFileURL(path.join(temporary, "library-source-origin.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04C/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const policy = { schemaVersion: 1, declaredHttpsOrigins: ["https://assets.example.test"] };
const source = (pathValue) => ({ schemaVersion: 1, kind: "PROJECT_ASSET", path: pathValue });
const remote = (url) => ({ schemaVersion: 1, kind: "HTTPS_ORIGIN", url });
test("M12-04C binds the path security gate and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04C");
assert.equal(manifest.parentTask, "M12-04B");
assert.equal(manifest.nextTask, "M12-04D");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04C rejects absolute, UNC, drive, NUL, control, and origin-style project paths", () => {
for (const value of [
"/tmp/library.blend",
"\\\\server\\share\\library.blend",
"\\server\\library.blend",
"C:\\libraries\\main.blend",
"C:/libraries/main.blend",
"libraries/\u0000main.blend",
"libraries/\u0001main.blend",
"libraries/%00main.blend",
"//https://evil.example.test/library.blend",
]) assert.throws(() => paths.normalizeProjectAssetPath(value), /ASSET_PATH_OUTSIDE_PROJECT|ASSET_PATH_INVALID/);
for (const value of ["/tmp/library.blend", "\\\\server\\share\\library.blend", "C:/libraries/main.blend", "libraries/\u0000main.blend", "libraries/%00main.blend"]) {
assert.throws(() => origin.acceptLibrarySource(policy, source(value)), { code: "IO_EXTERNAL_URI_BLOCKED" });
}
});
test("M12-04C rejects raw and encoded unsafe HTTPS URI characters before admission", () => {
for (const value of [
"https://assets.example.test\\evil.example.test/main.blend",
"https://assets.example.test/\nmain.blend",
"https://assets.example.test/%00main.blend",
"https://assets.example.test/%01main.blend",
"https://assets.example.test/%GGmain.blend",
"https://user:pass@evil.example.test/main.blend",
"https://evil.example.test/main.blend",
]) assert.throws(() => origin.acceptLibrarySource(policy, remote(value)), { code: "IO_EXTERNAL_URI_BLOCKED" });
assert.deepEqual(origin.acceptLibrarySource(policy, remote("https://assets.example.test/library/main.blend")), {
status: "READY",
kind: "HTTPS_ORIGIN",
canonicalLocator: "https://assets.example.test/library/main.blend",
});
});
test("M12-04C fails closed on policy origin smuggling and duplicate declarations", () => {
for (const value of [
"https://assets.example.test\\evil.example.test",
"https://assets.example.test/%00",
"https://assets.example.test/%2e",
"https://assets.example.test/..",
"https://assets.example.test/library",
"https://assets.example.test/?scope=library",
"https://assets.example.test/#library",
]) assert.throws(() => origin.parseLibrarySourcePolicy({ schemaVersion: 1, declaredHttpsOrigins: [value] }), { code: "IO_EXTERNAL_URI_BLOCKED" });
assert.throws(() => origin.parseLibrarySourcePolicy({ schemaVersion: 1, declaredHttpsOrigins: ["https://assets.example.test", "https://assets.example.test/"] }), { code: "ASSET_MANIFEST_INVALID" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-source-origin-unit-"));
const paths = ["asset-path.ts", "library-source-origin.ts"];
for (const name of paths) {
const sourcePath = path.join(root, "web/protocol", name);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
fs.writeFileSync(path.join(temporary, name.replace(".ts", ".mjs")), transpiled.outputText.replaceAll('from "./asset-path"', 'from "./asset-path.mjs"'));
}
const origin = await import(pathToFileURL(path.join(temporary, "library-source-origin.mjs")));
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-04A/manifest.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const policy = { schemaVersion: 1, declaredHttpsOrigins: ["https://assets.example.test"] };
const digest = "a".repeat(64);
test("M12-04A binds the declared source-origin protocol and evidence artifacts", () => {
assert.equal(manifest.task, "M12-04A");
assert.equal(manifest.nextTask, "M12-04B");
for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path);
});
test("M12-04A accepts declared HTTPS, project asset, and user-selected file sources", () => {
assert.deepEqual(origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "HTTPS_ORIGIN", url: "https://assets.example.test/library/main.blend" }), { status: "READY", kind: "HTTPS_ORIGIN", canonicalLocator: "https://assets.example.test/library/main.blend" });
assert.deepEqual(origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "PROJECT_ASSET", path: "libraries/main.blend" }), { status: "READY", kind: "PROJECT_ASSET", canonicalLocator: "project-assets/libraries/main.blend" });
assert.deepEqual(origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "USER_SELECTED_FILE", selectionId: "file-selection:pick-1", fileName: "main.blend", byteLength: 128, sourceSha256: digest }), { status: "READY", kind: "USER_SELECTED_FILE", canonicalLocator: "user-file/file-selection:pick-1/main.blend" });
});
test("M12-04A rejects undeclared origins, credentials, unsafe paths, and missing policy declarations", () => {
assert.throws(() => origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "HTTPS_ORIGIN", url: "https://other.example.test/main.blend" }), { code: "IO_EXTERNAL_URI_BLOCKED" });
assert.throws(() => origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "HTTPS_ORIGIN", url: "https://user:pass@assets.example.test/main.blend" }), { code: "IO_EXTERNAL_URI_BLOCKED" });
assert.throws(() => origin.acceptLibrarySource(policy, { schemaVersion: 1, kind: "PROJECT_ASSET", path: "../outside.blend" }), { code: "IO_EXTERNAL_URI_BLOCKED" });
assert.throws(() => origin.parseLibrarySourcePolicy({ schemaVersion: 1, declaredHttpsOrigins: [] }), { code: "PROTOCOL_MISMATCH" });
});
test("M12-04A rejects malformed user-file identity and undeclared fields", () => {
assert.throws(() => origin.parseLibrarySourceRequest({ schemaVersion: 1, kind: "USER_SELECTED_FILE", selectionId: "bad", fileName: "main.blend", byteLength: 1, sourceSha256: digest }), { code: "ASSET_MANIFEST_INVALID" });
assert.throws(() => origin.parseLibrarySourceRequest({ schemaVersion: 1, kind: "USER_SELECTED_FILE", selectionId: "file-selection:pick-1", fileName: "../main.blend", byteLength: 1, sourceSha256: digest }), { code: "ASSET_MANIFEST_INVALID" });
assert.throws(() => origin.parseLibrarySourceRequest({ schemaVersion: 1, kind: "PROJECT_ASSET", path: "main.blend", future: true }), { code: "ASSET_MANIFEST_INVALID" });
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,51 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "obj-import-unit-"));
const sourcePath = path.join(root, "web/protocol/obj-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "obj-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const obj = new TextEncoder().encode(`# test\nmtllib materials.mtl\no Test\nv 0 0 0\nv 1 0 0\nv 0 1 0\nvt 0 0\nvt 1 0\nvt 0 1\nvn 0 0 1\ng TestGroup\nusemtl TestMaterial\nf -3/-3/-1 -2/-2/-1 -1/-1/-1\n`).buffer;
const mtl = new TextEncoder().encode("newmtl TestMaterial\nmap_Kd texture.png\n").buffer;
test("M12-07C resolves negative indices and serializes deterministic OBJ", () => {
const imported = protocol.importOBJ(obj, mtl);
assert.deepEqual(imported.faces[0].vertices.map((vertex) => vertex.position), [1, 2, 3]);
assert.deepEqual(imported.faces[0].vertices.map((vertex) => vertex.texcoord), [1, 2, 3]);
assert.deepEqual(imported.materials, [{ name: "TestMaterial", mapKd: "texture.png" }]);
const serialized = protocol.serializeOBJ(imported);
assert.match(serialized.obj, /f 1\/1\/1 2\/2\/1 3\/3\/1/);
assert.equal(serialized.mtl, "# Web Blender MTL export\n# schema 1\nnewmtl TestMaterial\nmap_Kd texture.png\n");
});
test("M12-07C reports unresolved texture origin without blocking geometry", () => {
const imported = protocol.importOBJ(obj, mtl);
const missing = protocol.createOBJLossReport(imported);
assert.equal(missing.canRoundTrip, true);
assert.deepEqual(missing.warnings.map((warning) => warning.code), ["OBJ_TEXTURE_ORIGIN_UNRESOLVED"]);
const bound = protocol.createOBJLossReport(imported, ["texture.png"]);
assert.deepEqual(bound.warnings, []);
});
test("M12-07C rejects malformed face arity and out-of-range indices", () => {
const malformed = new TextEncoder().encode("v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2\n").buffer;
assert.throws(() => protocol.importOBJ(malformed), /OBJ_FACE_ARITY_INVALID/);
const outOfRange = new TextEncoder().encode("v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 4\n").buffer;
assert.throws(() => protocol.importOBJ(outOfRange), /OBJ_INDEX_OUT_OF_RANGE/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,51 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "ply-import-unit-"));
const sourcePath = path.join(root, "web/protocol/ply-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "ply-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const fixtureRoot = path.join(root, "tests/files/web/m12_ply_mapping_v1");
const bytes = (name) => { const value = fs.readFileSync(path.join(fixtureRoot, name)); return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength); };
test("M12-07H maps ASCII and binary little-endian PLY fields", () => {
const ascii = protocol.importPLY(bytes("mapping-ascii.ply"), { format: "ascii" });
const binary = protocol.importPLY(bytes("mapping-binary-le.ply"), { format: "binary_little_endian" });
assert.equal(ascii.vertices.length, 4);
assert.equal(ascii.faces.length, 2);
assert.deepEqual(ascii.vertices[0].position, [-1, 0, 1]);
assert.deepEqual(ascii.vertices[0].normal, [0, 1, 0]);
assert.deepEqual(ascii.vertices[0].color, [254 / 255, 0, 0, 1]);
assert.deepEqual(ascii.vertices[3].customProperties, { label: 4, temperature: 40 });
assert.deepEqual(binary.vertices, ascii.vertices);
assert.deepEqual(binary.faces, ascii.faces);
assert.deepEqual(protocol.createPLYLossReport(ascii), { schemaVersion: 1, operation: "PLY_IMPORT_LOSS_REPORT", canImport: true, warningCount: 0, warnings: [] });
});
test("M12-07H reports unknown list properties without dropping mapped fields", () => {
const imported = protocol.importPLY(bytes("unknown-property-ascii.ply"), { format: "ascii" });
assert.equal(imported.vertices.length, 4);
assert.equal(imported.vertices[0].customProperties.temperature, 10);
assert.deepEqual(protocol.createPLYLossReport(imported).warnings.map((warning) => warning.code), ["PLY_UNKNOWN_PROPERTY"]);
assert.equal(protocol.createPLYLossReport(imported).warnings[0].property, "unknown_values");
});
test("M12-07H serializes mapped data and rejects an explicit format mismatch", () => {
const document = protocol.importPLY(bytes("mapping-ascii.ply"));
const output = protocol.serializePLYAscii(document);
const reopened = protocol.importPLY(output, { format: "ascii" });
assert.deepEqual(reopened.faces, document.faces);
assert.deepEqual(reopened.vertices.map((vertex) => vertex.customProperties), document.vertices.map((vertex) => vertex.customProperties));
assert.throws(() => protocol.importPLY(bytes("mapping-ascii.ply"), { format: "binary_little_endian" }), /PLY_FORMAT_MISMATCH/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "ply-negative-unit-"));
const sourcePath = path.join(root, "web/protocol/ply-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "ply-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const fixtureRoot = path.join(root, "tests/files/web/m12_ply_negative_v1");
const bytes = (name) => { const value = fs.readFileSync(path.join(fixtureRoot, name)); return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength); };
test("M12-07I blocks big-endian PLY with a stable format code", () => {
assert.throws(() => protocol.importPLY(bytes("big-endian.ply")), /PLY_FORMAT_UNSUPPORTED/);
});
test("M12-07I blocks malformed lists and oversized element counts", () => {
assert.throws(() => protocol.importPLY(bytes("malformed-list-ascii.ply")), /PLY_DATA_TRUNCATED/);
assert.throws(() => protocol.importPLY(bytes("oversized-count-ascii.ply")), /PLY_IMPORT_BUDGET_EXCEEDED: vertex/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/pointer-contract.ts");
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const pointer = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
test("M14-04C preserves pointer identity and bounded pen fields", () => {
assert.deepEqual(pointer.observePointerEvent({ type: "pointerdown", pointerType: "pen", pointerId: 7, pressure: 1.4, tiltX: 120, tiltY: -100, button: 0, buttons: 1 }), { schemaVersion: 1, pointerType: "pen", pointerId: 7, pressure: 1, tiltX: 90, tiltY: -90, button: 0, buttons: 1, cancelled: false });
assert.equal(pointer.observePointerEvent({ type: "pointercancel", pointerType: "touch", pointerId: 2, pressure: 0.4, button: 0, buttons: 0 }).cancelled, true);
});
test("M14-04C rejects unknown pointer and invalid id", () => {
assert.throws(() => pointer.observePointerEvent({ pointerType: "trackpad", pointerId: 1 }), /POINTER_TYPE_UNSUPPORTED/);
assert.throws(() => pointer.observePointerEvent({ pointerType: "mouse", pointerId: -1 }), /POINTER_ID_INVALID/);
});

View File

@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-audit-integrity-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const digest = "a".repeat(64);
const manifest = { schemaVersion: 1, scripts: [{ id: "script:audit", name: "Audit", entryPath: "scripts/audit.py", sourceByteLength: 32, sourceSha256: digest, publisher: "local", signature: "b".repeat(128), keyId: "key:local", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false }] };
test("M13-05H accepts a strictly ordered, chained audit log", async () => {
const first = await protocol.createScriptExecutionAudit(manifest, "script:audit", new Set(), { requestId: "audit:first", requestedAt: "2026-08-19T00:00:00.000Z" });
const second = await protocol.createScriptExecutionAudit(manifest, "script:audit", new Set(), { requestId: "audit:second", requestedAt: "2026-08-19T00:00:01.000Z" });
const log = await protocol.appendScriptExecutionAudit(await protocol.appendScriptExecutionAudit({ schemaVersion: 1, entries: [] }, first), second);
assert.deepEqual(log.entries.map((entry) => entry.sequence), [1, 2]);
assert.deepEqual(log.entries.map((entry) => entry.audit.requestId), ["audit:first", "audit:second"]);
assert.equal(log.entries[0].previousEntrySha256, null);
assert.equal(log.entries[1].previousEntrySha256, log.entries[0].entrySha256);
assert.deepEqual((await protocol.parseScriptExecutionAuditLog(log)).entries, log.entries);
});
test("M13-05H rejects replay, time, sequence and hash-chain drift", async () => {
const first = await protocol.createScriptExecutionAudit(manifest, "script:audit", new Set(), { requestId: "audit:one", requestedAt: "2026-08-19T00:00:00.000Z" });
const second = await protocol.createScriptExecutionAudit(manifest, "script:audit", new Set(), { requestId: "audit:two", requestedAt: "2026-08-19T00:00:01.000Z" });
const log = await protocol.appendScriptExecutionAudit(await protocol.appendScriptExecutionAudit({ schemaVersion: 1, entries: [] }, first), second);
await assert.rejects(protocol.appendScriptExecutionAudit(log, first), /SCRIPT_MANIFEST_INVALID/);
const earlier = await protocol.createScriptExecutionAudit(manifest, "script:audit", new Set(), { requestId: "audit:earlier", requestedAt: "2026-08-18T23:59:59.000Z" });
await assert.rejects(protocol.appendScriptExecutionAudit(log, earlier), /SCRIPT_MANIFEST_INVALID/);
await assert.rejects(protocol.parseScriptExecutionAuditLog({ ...log, entries: [{ ...log.entries[0], sequence: 2 }, log.entries[1]] }), /SCRIPT_MANIFEST_INVALID/);
await assert.rejects(protocol.parseScriptExecutionAuditLog({ ...log, entries: [{ ...log.entries[0], entrySha256: "c".repeat(64) }, log.entries[1]] }), /SCRIPT_MANIFEST_INVALID/);
await assert.rejects(protocol.parseScriptExecutionAuditLog({ ...log, entries: [log.entries[0], { ...log.entries[1], previousEntrySha256: "d".repeat(64) }] }), /SCRIPT_MANIFEST_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-host-call-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")').replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const digest = "a".repeat(64);
const permissions = new Set(protocol.SCRIPT_PERMISSIONS);
const call = (name, parameters) => ({ schemaVersion: 1, requestId: `host:${name.toLowerCase()}`, scriptId: "clean", call: name, permission: name, parameters });
test("M13-03C parses all allowlisted host calls with structured parameters", () => {
const inputs = [
call("READ_MAIN", { revision: 3 }),
call("READ_ASSET", { path: "//assets/model.bin", expectedSha256: digest }),
call("WRITE_MAIN", { revision: 3, operation: "object.transform", payload: { objectId: "obj:1", x: 1 } }),
call("WRITE_ASSET", { path: "assets/out.bin", byteLength: 4, sha256: digest }),
call("SUBMIT_SERVER_JOB", { inputBlendSha256: digest, settingsSha256: digest }),
];
const parsed = inputs.map((input) => protocol.parseScriptHostCall(input, permissions));
assert.deepEqual(parsed.map((item) => item.call), ["READ_MAIN", "READ_ASSET", "WRITE_MAIN", "WRITE_ASSET", "SUBMIT_SERVER_JOB"]);
assert.equal(parsed[1].parameters.path, "assets/model.bin");
assert.equal(parsed[2].execution, "DISABLED");
});
test("M13-03C rejects non-allowlisted calls, permission confusion and unknown fields", () => {
assert.throws(() => protocol.parseScriptHostCall(call("EXECUTE", {}), permissions), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptHostCall({ ...call("READ_MAIN", { revision: 3 }), permission: "WRITE_MAIN" }, permissions), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptHostCall(call("READ_MAIN", { revision: 3, extra: true }), permissions), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptHostCall(call("READ_ASSET", { path: "../escape", expectedSha256: digest }), permissions), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptHostCall(call("WRITE_MAIN", { revision: 3, operation: "x", payload: [] }), permissions), /SCRIPT_MANIFEST_INVALID/);
});
test("M13-03C requires the declared permission set", () => {
assert.throws(() => protocol.parseScriptHostCall(call("WRITE_MAIN", { revision: 0, operation: "x", payload: {} }), new Set(["READ_MAIN"])), /SCRIPT_POLICY_DENIED/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-manifest-budgets-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
fileName: `${name}.ts`,
}).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const digest = "a".repeat(64);
const signature = "b".repeat(128);
const script = (id, overrides = {}) => ({
id,
name: id,
entryPath: `scripts/${id}.py`,
sourceByteLength: 128,
sourceSha256: digest,
publisher: "local",
signature,
keyId: "key:local",
permissions: ["READ_MAIN"],
dependencies: [],
module: false,
cpuMs: 1000,
memoryBytes: 1024 * 1024,
wallMs: 5000,
network: false,
autorun: false,
driverExpressions: false,
addonInstall: false,
...overrides,
});
const manifest = (scripts = [script("clean")]) => ({ schemaVersion: 1, scripts });
test("M13-02A accepts bounded manifest fields and normalizes project paths", () => {
const parsed = protocol.parseScriptingManifest(manifest([
script("base", { entryPath: "//scripts/../scripts/base.py" }),
script("clean", { dependencies: [{ id: "base", sourceSha256: digest, sourcePath: "//deps/base.py" }] }),
]));
assert.equal(parsed.scripts.length, 2);
assert.equal(parsed.scripts[0].entryPath, "scripts/base.py");
assert.equal(parsed.scripts[1].dependencies[0].sourcePath, "deps/base.py");
assert.equal(parsed.scripts.reduce((total, item) => total + item.sourceByteLength, 0), 256);
assert.equal(parsed.scripts.every((item) => item.module === false), true);
});
test("M13-02A rejects text count and aggregate source byte budget overflow", () => {
assert.throws(
() => protocol.parseScriptingManifest(manifest(Array.from({ length: protocol.SCRIPTING_BUDGET.maxScripts + 1 }, (_, index) => script(`script-${index}`)))),
/SCRIPT_BUDGET_EXCEEDED/,
);
assert.throws(
() => protocol.parseScriptingManifest(manifest([script("large", { sourceByteLength: protocol.SCRIPTING_BUDGET.maxSourceBytes }), script("overflow", { sourceByteLength: 1 })])),
/SCRIPT_BUDGET_EXCEEDED/,
);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("missing-bytes", { sourceByteLength: undefined })])), /SCRIPT_BUDGET_EXCEEDED/);
});
test("M13-02A rejects module execution, unsafe paths, and malformed dependencies", () => {
assert.throws(() => protocol.parseScriptingManifest(manifest([script("module", { module: true })])), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("escape", { entryPath: "../escape.py" })])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("dependency-escape", { dependencies: [{ id: "base", sourceSha256: digest, sourcePath: "//../escape.py" }] }), script("base")])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("duplicate-dependency", { dependencies: [{ id: "base", sourceSha256: digest, sourcePath: "deps/a.py" }, { id: "base", sourceSha256: digest, sourcePath: "deps/b.py" }] }), script("base")])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("too-many-dependencies", { dependencies: Array.from({ length: protocol.SCRIPTING_BUDGET.maxDependencies + 1 }, (_, index) => ({ id: `dep-${index}`, sourceSha256: digest, sourcePath: `deps/${index}.py` })) })])), /SCRIPT_BUDGET_EXCEEDED/);
});
test("M13-02A rejects unknown or over-budget permissions", () => {
assert.throws(() => protocol.parseScriptingManifest(manifest([script("unknown-permission", { permissions: ["EXECUTE"] })])), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptingManifest(manifest([script("permission-budget", { permissions: new Array(protocol.SCRIPTING_BUDGET.maxPermissions + 1).fill("READ_MAIN") })])), /SCRIPT_POLICY_DENIED/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,80 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-manifest-canonical-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
fileName: `${name}.ts`,
}).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const digest = "a".repeat(64);
const signature = "b".repeat(128);
const script = (id, overrides = {}) => ({
id,
name: id,
entryPath: `scripts/${id}.py`,
sourceByteLength: 128,
sourceSha256: digest,
publisher: "local",
signature,
keyId: "key:local",
permissions: ["READ_MAIN"],
dependencies: [],
module: false,
cpuMs: 1000,
memoryBytes: 1024 * 1024,
wallMs: 5000,
network: false,
autorun: false,
driverExpressions: false,
addonInstall: false,
...overrides,
});
test("M13-02B canonical serialization is invariant to manifest/script array order", () => {
const base = {
schemaVersion: 1,
scripts: [
script("zeta", { permissions: ["WRITE_ASSET", "READ_MAIN"], dependencies: [{ id: "alpha", sourceSha256: digest, sourcePath: "deps/alpha.py" }, { id: "beta", sourceSha256: digest, sourcePath: "deps/beta.py" }] }),
script("alpha", { permissions: ["SUBMIT_SERVER_JOB", "READ_ASSET"] }),
script("beta"),
],
};
const reordered = {
schemaVersion: 1,
scripts: [
{ ...base.scripts[1], permissions: [...base.scripts[1].permissions].reverse(), ignored: "removed" },
{ ...base.scripts[0], permissions: [...base.scripts[0].permissions].reverse(), dependencies: [...base.scripts[0].dependencies].reverse() },
base.scripts[2],
],
};
const first = protocol.serializeScriptingManifest(base);
const second = protocol.serializeScriptingManifest(reordered);
assert.equal(first, second);
assert.match(first, /^\{"schemaVersion":1,"scripts":\[/);
assert.equal(Object.keys(protocol.canonicalizeScriptingManifest(reordered).scripts[0]).includes("ignored"), false);
});
test("M13-02B canonical serialization includes security-relevant declaration fields", () => {
const base = { schemaVersion: 1, scripts: [script("clean")] };
const changedBytes = { schemaVersion: 1, scripts: [script("clean", { sourceByteLength: 129 })] };
const changedPermission = { schemaVersion: 1, scripts: [script("clean", { permissions: ["READ_ASSET"] })] };
assert.notEqual(protocol.serializeScriptingManifest(base), protocol.serializeScriptingManifest(changedBytes));
assert.notEqual(protocol.serializeScriptingManifest(base), protocol.serializeScriptingManifest(changedPermission));
assert.throws(() => protocol.serializeScriptingManifest({ ...base, schemaVersion: 2 }), /PROTOCOL_MISMATCH/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,36 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-permission-policy-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const script = (permissions = ["READ_MAIN"]) => ({ id: "clean", name: "clean", entryPath: "scripts/clean.py", sourceByteLength: 128, sourceSha256: "a".repeat(64), publisher: "Team", signature: "b".repeat(128), keyId: "key:new", permissions, dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024 * 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false });
const manifest = (permissions) => ({ schemaVersion: 1, scripts: [script(permissions)] });
test("M13-02E grants no undeclared permission by default", () => {
assert.deepEqual(protocol.resolveScriptPermissions(manifest(["READ_MAIN"]), "clean"), { status: "ALLOWED", code: "SCRIPT_PERMISSIONS_ALLOWED", scriptId: "clean", declared: ["READ_MAIN"], requested: [], granted: [] });
assert.deepEqual(protocol.resolveScriptPermissions(manifest(["WRITE_ASSET", "READ_MAIN"]), "clean", ["READ_MAIN"]), { status: "ALLOWED", code: "SCRIPT_PERMISSIONS_ALLOWED", scriptId: "clean", declared: ["READ_MAIN", "WRITE_ASSET"], requested: ["READ_MAIN"], granted: ["READ_MAIN"] });
});
test("M13-02E blocks escalation, unknown and duplicate permission requests", () => {
assert.equal(protocol.resolveScriptPermissions(manifest(["READ_MAIN"]), "clean", ["WRITE_MAIN"]).code, "SCRIPT_POLICY_DENIED");
assert.equal(protocol.resolveScriptPermissions(manifest(["READ_MAIN"]), "clean", ["EXECUTE"]).code, "SCRIPT_POLICY_DENIED");
assert.equal(protocol.resolveScriptPermissions(manifest(["READ_MAIN"]), "clean", ["READ_MAIN", "READ_MAIN"]).code, "SCRIPT_POLICY_DENIED");
assert.throws(() => protocol.parseScriptingManifest(manifest(["EXECUTE"])), /SCRIPT_POLICY_DENIED/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-policy-codes-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText.replace('require("./asset-path")', 'require("./asset-path.cjs")').replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const base = { schemaVersion: 1, scripts: [{ id: "demo", name: "Demo", entryPath: "scripts/demo.py", sourceByteLength: 128, sourceSha256: "a".repeat(64), publisher: "local", signature: "b".repeat(128), keyId: "key", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 64 * 1024 * 1024, wallMs: 2000, network: false, autorun: false, driverExpressions: false, addonInstall: false }] };
test("M13-01C returns stable default-deny codes for autorun, driver and add-on execution", () => {
assert.throws(() => protocol.parseScriptingManifest({ ...base, scripts: [{ ...base.scripts[0], autorun: true }] }), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptingManifest({ ...base, scripts: [{ ...base.scripts[0], driverExpressions: true }] }), /DRIVER_EXECUTION_BLOCKED/);
assert.throws(() => protocol.parseScriptingManifest({ ...base, scripts: [{ ...base.scripts[0], addonInstall: true }] }), /ADDON_INSTALL_BLOCKED/);
const gate = protocol.gateScriptExecution(base, "demo", new Set(["key"]));
assert.equal(gate.status, "BLOCKED");
assert.equal(gate.issues[0].code, "SCRIPT_SANDBOX_UNAVAILABLE");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-budget-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")').replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const budget = { schemaVersion: 1, cpuMs: 1000, wallMs: 5000, memoryBytes: 1024 * 1024, maxMessageBytes: 4096, maxOutputBytes: 8192 };
test("M13-03B accepts bounded CPU, wall, memory, message and output budgets", () => {
assert.deepEqual(protocol.parseScriptSandboxBudget(budget), budget);
});
test("M13-03B rejects every budget overflow and schema drift", () => {
for (const [field, limit] of Object.entries({ cpuMs: protocol.SCRIPT_SANDBOX_BUDGET.maxCpuMs, wallMs: protocol.SCRIPT_SANDBOX_BUDGET.maxWallMs, memoryBytes: protocol.SCRIPT_SANDBOX_BUDGET.maxMemoryBytes, maxMessageBytes: protocol.SCRIPT_SANDBOX_BUDGET.maxMessageBytes, maxOutputBytes: protocol.SCRIPT_SANDBOX_BUDGET.maxOutputBytes })) assert.throws(() => protocol.parseScriptSandboxBudget({ ...budget, [field]: limit + 1 }), /SCRIPT_BUDGET_EXCEEDED/);
assert.throws(() => protocol.parseScriptSandboxBudget({ ...budget, schemaVersion: 2 }), /PROTOCOL_MISMATCH/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,32 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-cancellation-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")').replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const running = { schemaVersion: 1, jobId: "sandbox:cancel", workerGeneration: 5, baseRevision: 11, mainRevisionBefore: 11, status: "RUNNING" };
test("M13-03E cancellation receipt blocks late message and cache publication", () => {
const cancelled = protocol.terminateScriptSandboxJob(running, "CANCEL");
assert.deepEqual({ status: cancelled.status, errorCode: cancelled.errorCode, mainRevisionAfter: cancelled.mainRevisionAfter, temporaryBytes: cancelled.temporaryBytes, publishedResults: cancelled.publishedResults, lateResults: cancelled.lateResults, committed: cancelled.committed }, { status: "CANCELLED", errorCode: "SCRIPT_SANDBOX_CANCELLED", mainRevisionAfter: 11, temporaryBytes: 0, publishedResults: 0, lateResults: 0, committed: false });
assert.throws(() => protocol.rejectLateScriptSandboxResult(cancelled), /SCRIPT_SANDBOX_LATE_RESULT/);
});
test("M13-03E rejects a late result that is not tied to a terminated receipt", () => {
assert.throws(() => protocol.rejectLateScriptSandboxResult({ ...running, status: "RUNNING" }), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.terminateScriptSandboxJob({ ...running, baseRevision: 10 }, "CANCEL"), /SCRIPT_MANIFEST_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,25 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-dispose-"));
const source = fs.readFileSync(path.join(root, "web/app/src/testing/script-sandbox-dispose.ts"), "utf8");
fs.writeFileSync(path.join(temporary, "script-sandbox-dispose.cjs"), ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: "script-sandbox-dispose.ts" }).outputText);
const protocol = createRequire(import.meta.url)(path.join(temporary, "script-sandbox-dispose.cjs"));
test("M13-03F emits zero-resource disposal receipts", () => {
assert.deepEqual(protocol.createScriptSandboxDisposeReceipt(1), { schemaVersion: 1, disposeCount: 1, idempotent: false, resources: { messagePorts: 0, timers: 0, abortControllers: 0, transferableBuffers: 0, pendingRequests: 0, cacheReferences: 0 }, lateTimerMessages: 0 });
assert.deepEqual(protocol.createScriptSandboxDisposeReceipt(2), { schemaVersion: 1, disposeCount: 2, idempotent: true, resources: { messagePorts: 0, timers: 0, abortControllers: 0, transferableBuffers: 0, pendingRequests: 0, cacheReferences: 0 }, lateTimerMessages: 0 });
});
test("M13-03F rejects an invalid disposal count", () => {
assert.throws(() => protocol.createScriptSandboxDisposeReceipt(0), /SCRIPT_SANDBOX_DISPOSE_INVALID/);
assert.throws(() => protocol.createScriptSandboxDisposeReceipt(1.5), /SCRIPT_SANDBOX_DISPOSE_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-isolation-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")').replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const running = { schemaVersion: 1, jobId: "sandbox:1", workerGeneration: 4, baseRevision: 9, mainRevisionBefore: 9, status: "RUNNING" };
test("M13-03D crash and timeout terminate jobs without changing Main revision", () => {
const crashed = protocol.terminateScriptSandboxJob(running, "CRASH");
const timedOut = protocol.terminateScriptSandboxJob(running, "TIMEOUT");
assert.deepEqual({ status: crashed.status, errorCode: crashed.errorCode, mainRevisionAfter: crashed.mainRevisionAfter, temporaryBytes: crashed.temporaryBytes, publishedResults: crashed.publishedResults, committed: crashed.committed }, { status: "CRASHED", errorCode: "SCRIPT_SANDBOX_CRASHED", mainRevisionAfter: 9, temporaryBytes: 0, publishedResults: 0, committed: false });
assert.deepEqual({ status: timedOut.status, errorCode: timedOut.errorCode, mainRevisionAfter: timedOut.mainRevisionAfter, temporaryBytes: timedOut.temporaryBytes, publishedResults: timedOut.publishedResults, committed: timedOut.committed }, { status: "TIMED_OUT", errorCode: "SCRIPT_SANDBOX_TIMEOUT", mainRevisionAfter: 9, temporaryBytes: 0, publishedResults: 0, committed: false });
});
test("M13-03D cancellation and late results are fail-closed", () => {
const cancelled = protocol.terminateScriptSandboxJob(running, "CANCEL");
assert.equal(cancelled.errorCode, "SCRIPT_SANDBOX_CANCELLED");
assert.throws(() => protocol.rejectLateScriptSandboxResult(cancelled), /SCRIPT_SANDBOX_LATE_RESULT/);
assert.throws(() => protocol.terminateScriptSandboxJob({ ...running, baseRevision: 8 }, "CRASH"), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.terminateScriptSandboxJob({ ...running, status: "CRASHED" }, "TIMEOUT"), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.rejectLateScriptSandboxResult(running), /SCRIPT_MANIFEST_INVALID/);
});
test("M13-03D rejects malformed termination receipts", () => {
assert.throws(() => protocol.terminateScriptSandboxJob({ ...running, workerGeneration: 0 }, "CRASH"), /SCRIPT_BUDGET_EXCEEDED/);
assert.throws(() => protocol.terminateScriptSandboxJob({ ...running, mainRevisionBefore: 10 }, "TIMEOUT"), /SCRIPT_MANIFEST_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,32 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-recovery-"));
const source = fs.readFileSync(path.join(root, "web/app/src/testing/script-sandbox-recovery.ts"), "utf8");
fs.writeFileSync(path.join(temporary, "script-sandbox-recovery.cjs"), ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: "script-sandbox-recovery.ts" }).outputText);
const protocol = createRequire(import.meta.url)(path.join(temporary, "script-sandbox-recovery.cjs"));
const digest = "a".repeat(64);
const entry = (sequence, requestId, previousEntrySha256, entrySha256) => ({ sequence, requestId, previousEntrySha256, entrySha256, sourceSha256: digest, manifestSha256: "b".repeat(64) });
test("M13-03G accepts one-generation recovery with a continuous audit chain", () => {
const receipt = protocol.createScriptSandboxRecoveryReceipt({ previousGeneration: 4, nextGeneration: 5, mainRevisionBefore: 11, mainRevisionAfter: 11, sourceSha256: digest, manifestSha256: "b".repeat(64), audit: { entries: 2, first: entry(1, "sandbox-recovery:g4", null, digest), second: entry(2, "sandbox-recovery:g5", digest, "c".repeat(64)) } });
assert.equal(receipt.recovered, true);
assert.equal(receipt.execution, "DISABLED");
assert.equal(receipt.audit.second.previousEntrySha256, receipt.audit.first.entrySha256);
});
test("M13-03G rejects generation, revision, request and hash-chain drift", () => {
const base = { previousGeneration: 4, nextGeneration: 5, mainRevisionBefore: 11, mainRevisionAfter: 11, sourceSha256: digest, manifestSha256: "b".repeat(64), audit: { entries: 2, first: entry(1, "sandbox-recovery:g4", null, digest), second: entry(2, "sandbox-recovery:g5", digest, "c".repeat(64)) } };
assert.throws(() => protocol.createScriptSandboxRecoveryReceipt({ ...base, nextGeneration: 7 }), /generation/);
assert.throws(() => protocol.createScriptSandboxRecoveryReceipt({ ...base, mainRevisionAfter: 12 }), /revision/);
assert.throws(() => protocol.createScriptSandboxRecoveryReceipt({ ...base, audit: { ...base.audit, second: entry(2, "sandbox-recovery:g5", "d".repeat(64), "c".repeat(64)) } }), /hash chain/);
assert.throws(() => protocol.createScriptSandboxRecoveryReceipt({ ...base, audit: { ...base.audit, second: entry(2, "sandbox-recovery:g4", digest, "c".repeat(64)) } }), /replayed/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-sandbox-scope-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const deniedScope = { schemaVersion: 1, dom: false, hostWorker: false, opfs: false, indexedDB: false, network: false };
test("M13-03A accepts only the all-deny sandbox scope", () => {
assert.deepEqual(protocol.parseScriptSandboxScope(deniedScope), deniedScope);
for (const capability of ["dom", "hostWorker", "opfs", "indexedDB", "network"]) {
assert.throws(() => protocol.parseScriptSandboxScope({ ...deniedScope, [capability]: true }), /SCRIPT_POLICY_DENIED/);
}
});
test("M13-03A rejects unknown scope versions and missing declarations", () => {
assert.throws(() => protocol.parseScriptSandboxScope({ ...deniedScope, schemaVersion: 2 }), /PROTOCOL_MISMATCH/);
assert.throws(() => protocol.parseScriptSandboxScope({ schemaVersion: 1 }), /SCRIPT_POLICY_DENIED/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,44 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-signature-negative-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const publicKey = "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8";
const signature = "fc396c6c68e6f6eb38a18c147becfaec1621a167f6db0a0d76874209accf3cb80dfa1fac1528ebc1bc6b090801a3ad397cae18e6ddb41740766678711c0a8804";
const script = (id = "clean", overrides = {}) => ({ id, name: id, entryPath: `scripts/${id}.py`, sourceByteLength: 128, sourceSha256: "a".repeat(64), publisher: "Team", signature, keyId: "key:new", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024 * 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false, ...overrides });
const manifest = (scripts = [script()]) => ({ schemaVersion: 1, scripts });
const key = (overrides = {}) => ({ keyId: "key:new", publisher: "Team", algorithm: "ED25519", publicKey, status: "ACTIVE", notBefore: "2026-01-01T00:00:00.000Z", notAfter: "2027-01-01T00:00:00.000Z", ...overrides });
const policy = (overrides = {}) => ({ schemaVersion: 1, issuer: "web-trust", issuedAt: "2026-01-01T00:00:00.000Z", expiresAt: "2027-01-01T00:00:00.000Z", maxClockSkewMs: 300000, keys: [key()], ...overrides });
test("M13-02F blocks missing, expired, not-yet-valid and publisher-confused signers", async () => {
const at = "2026-08-18T12:00:00.000Z";
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [] }), at)).code, "SCRIPT_POLICY_DENIED");
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ expiresAt: "2026-06-01T00:00:00.000Z" }), at)).code, "SCRIPT_POLICY_DENIED");
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ notBefore: "2026-09-01T00:00:00.000Z" })] }), at)).code, "SCRIPT_POLICY_DENIED");
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ notAfter: "2026-06-01T00:00:00.000Z" })] }), at)).code, "SCRIPT_POLICY_DENIED");
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ publisher: "Other" })] }), at)).code, "SCRIPT_POLICY_DENIED");
});
test("M13-02F binds a signature to one script and does not accept reordered/swapped content", async () => {
const at = "2026-08-18T12:00:00.000Z";
assert.equal((await protocol.verifyScriptManifestSignature(manifest(), "clean", policy(), at)).status, "VERIFIED");
const swapped = manifest([script("other")]);
assert.equal((await protocol.verifyScriptManifestSignature(swapped, "other", policy(), at)).code, "SCRIPT_SIGNATURE_INVALID");
assert.equal((await protocol.verifyScriptManifestSignature(manifest([script("other"), script()]), "clean", policy(), at)).status, "VERIFIED");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,76 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { createPrivateKey, createPublicKey, sign } from "node:crypto";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "script-trust-policy-"));
const require = createRequire(import.meta.url);
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
}
const protocol = require(path.join(temporary, "scripting-platform.cjs"));
const digest = "a".repeat(64);
const signature = "b".repeat(128);
const privateKey = createPrivateKey({ key: Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), Buffer.from("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "hex")]), format: "der", type: "pkcs8" });
const publicKey = createPublicKey(privateKey).export({ format: "der", type: "spki" }).subarray(-32).toString("hex");
const script = (id = "clean", overrides = {}) => ({ id, name: id, entryPath: `scripts/${id}.py`, sourceByteLength: 128, sourceSha256: digest, publisher: "Team", signature, keyId: "key:new", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024 * 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false, ...overrides });
const key = (keyId, overrides = {}) => ({ keyId, publisher: "Team", algorithm: "ED25519", publicKey: "c".repeat(64), status: "ACTIVE", notBefore: "2026-01-01T00:00:00.000Z", notAfter: "2027-01-01T00:00:00.000Z", ...overrides });
const policy = (keys = [key("key:new")], overrides = {}) => ({ schemaVersion: 1, issuer: "web-trust", issuedAt: "2026-01-01T00:00:00.000Z", expiresAt: "2027-01-01T00:00:00.000Z", maxClockSkewMs: 300000, keys, ...overrides });
test("M13-02C parses signer identity, active rotation and timestamp windows", () => {
const parsed = protocol.parseScriptTrustPolicy(policy([key("key:new", { replaces: "key:old" }), key("key:old", { status: "REVOKED", revokedAt: "2026-06-01T00:00:00.000Z" })]));
assert.equal(parsed.keys.length, 2);
assert.equal(protocol.canonicalizeScriptTrustPolicy(parsed).keys[0].keyId, "key:new");
assert.match(protocol.serializeScriptTrustPolicy(parsed), /"algorithm":"ED25519"/);
assert.deepEqual(protocol.resolveScriptSigner({ schemaVersion: 1, scripts: [script()] }, "clean", parsed, "2026-08-18T12:00:00.000Z"), { status: "ELIGIBLE", keyId: "key:new", publisher: "Team", trust: "ACTIVE", cryptographicVerification: "REQUIRED" });
});
test("M13-02C rejects invalid rotation, revocation and timestamp policy declarations", () => {
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { replaces: "missing" })])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { replaces: "key:old" }), key("key:old", { publisher: "Other" })])), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:a", { replaces: "key:b" }), key("key:b", { replaces: "key:a" })])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { status: "REVOKED" })])), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { revokedAt: "2026-06-01T00:00:00.000Z" })])), /SCRIPT_POLICY_DENIED/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { notAfter: "2025-01-01T00:00:00.000Z" })])), /SCRIPT_MANIFEST_INVALID/);
assert.throws(() => protocol.parseScriptTrustPolicy(policy([key("key:new", { publicKey: "not-a-key" })])), /SCRIPT_SIGNATURE_INVALID/);
});
test("M13-02C resolves revoked, publisher-mismatched and expired signers fail-closed", () => {
const baseManifest = { schemaVersion: 1, scripts: [script()] };
const revoked = policy([key("key:new", { status: "REVOKED", revokedAt: "2026-06-01T00:00:00.000Z" })]);
assert.equal(protocol.resolveScriptSigner(baseManifest, "clean", revoked, "2026-08-18T12:00:00.000Z").trust, "REVOKED");
const mismatch = policy([key("key:new", { publisher: "Other" })]);
assert.equal(protocol.resolveScriptSigner(baseManifest, "clean", mismatch, "2026-08-18T12:00:00.000Z").trust, "PUBLISHER_MISMATCH");
const expired = policy([key("key:new", { notAfter: "2026-06-01T00:00:00.000Z" })]);
assert.equal(protocol.resolveScriptSigner(baseManifest, "clean", expired, "2026-08-18T12:00:00.000Z").trust, "KEY_EXPIRED");
const policyExpired = policy([key("key:new")], { expiresAt: "2026-06-01T00:00:00.000Z" });
assert.equal(protocol.resolveScriptSigner(baseManifest, "clean", policyExpired, "2026-08-18T12:00:00.000Z").trust, "POLICY_EXPIRED");
});
test("M13-02D verifies only the canonical declared content and source hash", async () => {
const baseScript = script("clean", { signature: "0".repeat(128) });
const unsigned = { schemaVersion: 1, scripts: [baseScript] };
const signedScript = { ...baseScript, signature: sign(null, Buffer.from(protocol.serializeScriptSignatureInput(unsigned, "clean")), privateKey).toString("hex") };
const signed = { schemaVersion: 1, scripts: [signedScript] };
const trust = policy([key("key:new", { publicKey })]);
const verified = await protocol.verifyScriptManifestSignature(signed, "clean", trust, "2026-08-18T12:00:00.000Z");
assert.equal(verified.status, "VERIFIED");
assert.equal(verified.code, "SCRIPT_SIGNATURE_VERIFIED");
const changedSource = { schemaVersion: 1, scripts: [{ ...signedScript, sourceSha256: "d".repeat(64) }] };
assert.deepEqual((await protocol.verifyScriptManifestSignature(changedSource, "clean", trust, "2026-08-18T12:00:00.000Z")).code, "SCRIPT_SIGNATURE_INVALID");
const changedSignature = { schemaVersion: 1, scripts: [{ ...signedScript, signature: `${signedScript.signature.slice(0, -1)}${signedScript.signature.endsWith("0") ? "1" : "0"}` }] };
assert.deepEqual((await protocol.verifyScriptManifestSignature(changedSignature, "clean", trust, "2026-08-18T12:00:00.000Z")).code, "SCRIPT_SIGNATURE_INVALID");
const revoked = policy([key("key:new", { publicKey, status: "REVOKED", revokedAt: "2026-06-01T00:00:00.000Z" })]);
assert.deepEqual((await protocol.verifyScriptManifestSignature(signed, "clean", revoked, "2026-08-18T12:00:00.000Z")).code, "SCRIPT_POLICY_DENIED");
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,24 @@
import assert from "node:assert/strict";
import test from "node:test";
import { classifyServerJobFault, createServerJobFaultReceipt, SERVER_JOB_FAULT_CODES } from "../../../tools/web/server-job-fault.mjs";
test("M13-04H classifies faults with stable precedence", () => {
assert.equal(classifyServerJobFault({ timedOut: true, oom: true, signal: "SIGKILL" }).code, SERVER_JOB_FAULT_CODES.TIMEOUT);
assert.equal(classifyServerJobFault({ oom: true, signal: "SIGKILL" }).code, SERVER_JOB_FAULT_CODES.OOM);
assert.equal(classifyServerJobFault({ signal: "SIGTERM" }).code, SERVER_JOB_FAULT_CODES.SIGNAL);
assert.equal(classifyServerJobFault({ code: 7 }).code, SERVER_JOB_FAULT_CODES.EXIT_FAILED);
assert.equal(classifyServerJobFault({ cancelRequested: true, timedOut: true }).code, SERVER_JOB_FAULT_CODES.CANCELLED);
});
test("M13-04H preserves the old revision and blocks failure publication", () => {
const receipt = createServerJobFaultReceipt({ baseRevision: 11, currentRevision: 11, timedOut: true });
assert.deepEqual(receipt, { schemaVersion: 1, state: "FAILED", code: "SERVER_JOB_TIMEOUT", stage: "PROCESS", exitCode: null, signal: null, timedOut: true, memoryExceeded: false, baseRevision: 11, currentRevision: 11, committedRevision: 11, publish: false, revisionPreserved: true, execution: "DISABLED" });
assert.throws(() => createServerJobFaultReceipt({ baseRevision: 11, currentRevision: 12, signal: "SIGTERM" }), /SERVER_JOB_REVISION_CONFLICT/);
assert.equal(createServerJobFaultReceipt({ baseRevision: 11, currentRevision: 11, code: 0 }).publish, true);
});
test("M13-04H derives OOM from bounded memory usage", () => {
const receipt = createServerJobFaultReceipt({ baseRevision: 3, currentRevision: 3, memoryBytes: 513, memoryLimitBytes: 512 });
assert.equal(receipt.code, SERVER_JOB_FAULT_CODES.OOM);
assert.equal(receipt.memoryExceeded, true);
});

View File

@@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { submitIdempotentServerJobResult } from "../../../tools/web/server-job-idempotency.mjs";
const h = (value) => crypto.createHash("sha256").update(value).digest("hex");
const identity = { requestId: "retry-1", projectId: "project-1", baseRevision: 4, sourceSha256: h("source"), settingsSha256: h("settings"), buildSha256: h("build") };
test("M13-04J reuses the exact verified result for the same request", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04j-reuse-"));
const options = { receiptDirectory: path.join(root, "receipts"), outputDirectory: path.join(root, "outputs") };
try {
const first = await submitIdempotentServerJobResult(identity, new Uint8Array([1, 2]), options);
const second = await submitIdempotentServerJobResult(identity, new Uint8Array([1, 2]), options);
assert.equal(first.reused, false);
assert.equal(second.reused, true);
assert.equal(first.outputSha256, second.outputSha256);
assert.equal((await fs.readdir(options.receiptDirectory)).length, 1);
} finally { await fs.rm(root, { recursive: true, force: true }); }
});
test("M13-04J rejects conflicting output/identity and isolates different requests", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04j-conflict-"));
const options = { receiptDirectory: path.join(root, "receipts"), outputDirectory: path.join(root, "outputs") };
try {
await submitIdempotentServerJobResult(identity, new Uint8Array([3]), options);
await assert.rejects(submitIdempotentServerJobResult(identity, new Uint8Array([4]), options), /SERVER_JOB_IDEMPOTENCY_CONFLICT/);
await assert.rejects(submitIdempotentServerJobResult({ ...identity, settingsSha256: h("changed") }, new Uint8Array([3]), options), /SERVER_JOB_IDEMPOTENCY_CONFLICT/);
const other = await submitIdempotentServerJobResult({ ...identity, requestId: "retry-2" }, new Uint8Array([4]), options);
assert.equal(other.reused, false);
assert.equal((await fs.readdir(options.receiptDirectory)).length, 2);
} finally { await fs.rm(root, { recursive: true, force: true }); }
});
test("M13-04J serializes concurrent duplicate requests", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04j-concurrent-"));
const options = { receiptDirectory: path.join(root, "receipts"), outputDirectory: path.join(root, "outputs") };
try {
const results = await Promise.all([
submitIdempotentServerJobResult({ ...identity, requestId: "retry-3" }, new Uint8Array([8]), options),
submitIdempotentServerJobResult({ ...identity, requestId: "retry-3" }, new Uint8Array([8]), options),
]);
assert.deepEqual(results.map((result) => result.reused).sort(), [false, true]);
} finally { await fs.rm(root, { recursive: true, force: true }); }
});

View File

@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { cleanupServerJobDirectory, createServerJobDirectory, prepareServerJobWorkspace } from "../../../tools/web/server-job-isolation.mjs";
test("M13-04A creates unpredictable one-shot directories and cleans them once", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04a-job-root-"));
try {
const first = await createServerJobDirectory(root, "server:job-one");
const second = await createServerJobDirectory(root, "server:job-two");
assert.notEqual(first.directoryName, second.directoryName);
assert.notEqual(first.directoryName, first.jobId);
assert.match(first.directoryName, /^\.blender-job-[0-9a-f-]+-[A-Za-z0-9]+$/);
assert.equal((await fs.stat(first.path)).mode & 0o777, 0o700);
assert.equal((await fs.stat(second.path)).mode & 0o777, 0o700);
const cleanedFirst = await cleanupServerJobDirectory(first);
const cleanedSecond = await cleanupServerJobDirectory(second);
assert.equal(cleanedFirst.state, "CLEANED");
assert.equal(cleanedFirst.cleanupCount, 1);
assert.equal(cleanedSecond.cleanupCount, 1);
await assert.rejects(fs.stat(first.path), { code: "ENOENT" });
await assert.rejects(fs.stat(second.path), { code: "ENOENT" });
assert.equal((await cleanupServerJobDirectory(cleanedFirst)).cleanupCount, 1);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("M13-04A rejects unsafe roots, IDs and cleanup escapes", async () => {
await assert.rejects(createServerJobDirectory("relative-root", "server:job"), /SERVER_JOB_DIRECTORY_INVALID/);
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04a-invalid-root-"));
try {
await assert.rejects(createServerJobDirectory(root, "../escape"), /SERVER_JOB_DIRECTORY_INVALID/);
await assert.rejects(cleanupServerJobDirectory({ schemaVersion: 1, root, path: path.join(root, "other"), state: "ALLOCATED" }), /SERVER_JOB_DIRECTORY_INVALID/);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("M13-04B isolates read-only source from writable output", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04b-workspace-"));
try {
const job = await createServerJobDirectory(root, "server:job-mount");
const workspace = await prepareServerJobWorkspace(job, new Uint8Array([1, 2, 3]));
assert.notEqual(path.dirname(workspace.sourcePath), workspace.outputDirectory);
assert.equal((await fs.stat(workspace.sourceDirectory)).mode & 0o777, 0o555);
assert.equal((await fs.stat(workspace.sourcePath)).mode & 0o777, 0o444);
assert.equal((await fs.stat(workspace.outputDirectory)).mode & 0o777, 0o700);
await assert.rejects(fs.writeFile(workspace.sourcePath, new Uint8Array([9])), { code: "EACCES" });
await fs.writeFile(path.join(workspace.outputDirectory, "result.bin"), new Uint8Array([4, 5]));
assert.deepEqual([...await fs.readFile(path.join(workspace.outputDirectory, "result.bin"))], [4, 5]);
await cleanupServerJobDirectory(job);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});

View File

@@ -0,0 +1,17 @@
import assert from "node:assert/strict";
import test from "node:test";
import { parseServerJobNetworkPolicy, resolveServerJobNetwork } from "../../../tools/web/server-job-network-policy.mjs";
test("M13-04D defaults to deny and admits only declared safe origins", () => {
const policy = parseServerJobNetworkPolicy({ schemaVersion: 1, allowedOrigins: ["https://example.com", "http://127.0.0.1:8787", "https://example.com"] });
assert.deepEqual(policy, { schemaVersion: 1, defaultNetwork: "DENY", allowedOrigins: ["http://127.0.0.1:8787", "https://example.com"] });
assert.deepEqual(resolveServerJobNetwork(policy), { status: "DENIED", code: "SERVER_NETWORK_DENIED", origin: null, network: "DISABLED" });
assert.equal(resolveServerJobNetwork(policy, "https://example.com").status, "ALLOWED");
assert.equal(resolveServerJobNetwork(policy, "https://other.example").code, "SERVER_NETWORK_DENIED");
});
test("M13-04D rejects unsafe or non-canonical origins", () => {
for (const origin of ["http://example.com", "file:///tmp/x", "https://example.com/path", "https://user:pass@example.com"]) {
assert.throws(() => parseServerJobNetworkPolicy({ schemaVersion: 1, allowedOrigins: [origin] }), /SERVER_NETWORK_/);
}
});

View File

@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createServerJobOutputReceipt, redactServerJobOutput, SERVER_JOB_OUTPUT_LIMITS } from "../../../tools/web/server-job-output.mjs";
test("M13-04F redacts credentials and filesystem paths before publishing output", () => {
const receipt = createServerJobOutputReceipt({
stdout: 'authorization: Bearer abc123 token="secret-value" source=/home/user/private.blend',
stderr: "failed at C:\\Users\\alice\\job\\source.blend file:///tmp/internal.log",
});
assert.equal(receipt.execution, "DISABLED");
assert.ok(receipt.totalRedactions >= 5);
assert.doesNotMatch(receipt.stdout.text, /abc123|secret-value|\/home\/user|C:\\Users|file:\/\//u);
assert.doesNotMatch(receipt.stderr.text, /alice|internal\.log/u);
assert.match(receipt.stdout.text, /<redacted>/u);
assert.match(receipt.stderr.text, /<internal-path>/u);
});
test("M13-04F truncates at UTF-8 byte boundaries and reports the source size", () => {
const value = "模型".repeat(100);
const receipt = createServerJobOutputReceipt({ stdout: value, stderr: "" }, { ...SERVER_JOB_OUTPUT_LIMITS, stdoutBytes: 32, stderrBytes: 32, totalBytes: 64 });
assert.equal(receipt.stdout.truncated, true);
assert.ok(receipt.stdout.emittedBytes <= 32);
assert.equal(Buffer.from(receipt.stdout.text, "utf8").toString("utf8"), receipt.stdout.text);
assert.equal(receipt.stdout.originalBytes, Buffer.byteLength(value, "utf8"));
assert.match(receipt.stdout.text, /<output-truncated>$/u);
});
test("M13-04F rejects invalid stream budgets and leaves empty streams explicit", () => {
assert.deepEqual(createServerJobOutputReceipt({ stdout: "", stderr: "" }).stdout, {
text: "", originalBytes: 0, emittedBytes: 0, redactionCount: 0, truncated: false,
});
assert.throws(() => createServerJobOutputReceipt({ stdout: "x", stderr: "" }, { stdoutBytes: 10, stderrBytes: 10, totalBytes: 10 }), /SERVER_JOB_OUTPUT_INVALID/);
assert.throws(() => redactServerJobOutput(42, 10), /SERVER_JOB_OUTPUT_INVALID/);
});

View File

@@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { cleanupServerJobDirectory, createServerJobDirectory } from "../../../tools/web/server-job-isolation.mjs";
import { cancelServerJobProcess, startServerJobProcess } from "../../../tools/web/server-job-process.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
test("M13-04G cancels the real process group and cleans the job directory", async () => {
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04g-process-"));
const job = await createServerJobDirectory(temporary, "server:cancel");
const childScript = "const {spawn}=require('node:child_process'); const c=spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'}); setInterval(()=>{},1000);";
const handle = startServerJobProcess(process.execPath, ["-e", childScript], { cwd: root });
let cleanupCount = 0;
try {
const receipt = await cancelServerJobProcess(handle, async () => { cleanupCount += 1; await cleanupServerJobDirectory(job); });
assert.equal(receipt.state, "CANCELLED");
assert.equal(receipt.cleanupCount, 1);
assert.equal(cleanupCount, 1);
assert.match(receipt.treeSignal, /GROUP|ALREADY_EXITED/);
await assert.rejects(fs.stat(job.path), { code: "ENOENT" });
await assert.doesNotReject(handle.completion);
} finally {
await fs.rm(temporary, { recursive: true, force: true });
}
});
test("M13-04G repeated cancellation is idempotent", async () => {
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04g-idempotent-"));
const job = await createServerJobDirectory(temporary, "server:repeat");
const handle = startServerJobProcess(process.execPath, ["-e", "setInterval(()=>{},1000)"], { cwd: root });
let cleanupCount = 0;
const cleanup = async () => { cleanupCount += 1; await cleanupServerJobDirectory(job); };
try {
const first = await cancelServerJobProcess(handle, cleanup);
const second = await cancelServerJobProcess(handle, cleanup);
assert.equal(first.state, "CANCELLED");
assert.equal(second.state, "CANCELLED");
assert.equal(first.cleanupCount, 1);
assert.equal(second.cleanupCount, 1);
assert.equal(cleanupCount, 1);
} finally {
await fs.rm(temporary, { recursive: true, force: true });
}
});

View File

@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createServerJobResourceReceipt, parseServerJobResourceBudget, SERVER_JOB_RESOURCE_LIMITS } from "../../../tools/web/server-job-resource-budget.mjs";
const budget = { schemaVersion: 1, ...SERVER_JOB_RESOURCE_LIMITS };
const usage = { cpuMs: 10, memoryBytes: 1024, processCount: 1, fileCount: 2, wallMs: 20, outputBytes: 512 };
test("M13-04C accepts bounded usage and marks enforcement", () => {
assert.deepEqual(parseServerJobResourceBudget(budget), budget);
assert.deepEqual(createServerJobResourceReceipt(budget, usage), { schemaVersion: 1, status: "SUCCEEDED", budget, usage, enforced: true, exceeded: [] });
});
test("M13-04C rejects each resource overage with a stable code", () => {
for (const field of Object.keys(SERVER_JOB_RESOURCE_LIMITS)) {
assert.throws(() => createServerJobResourceReceipt(budget, { ...usage, [field]: SERVER_JOB_RESOURCE_LIMITS[field] + 1 }), new RegExp(`SERVER_JOB_BUDGET_EXCEEDED.*${field}`));
}
assert.throws(() => parseServerJobResourceBudget({ ...budget, unknown: 1 }), /SERVER_JOB_BUDGET_INVALID/);
});

View File

@@ -0,0 +1,33 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { commitServerJobResult, verifyServerJobResultReceipt } from "../../../tools/web/server-job-result-binding.mjs";
const h = (value) => crypto.createHash("sha256").update(value).digest("hex");
const identity = { requestId: "request-1", projectId: "project-1", baseRevision: 7, sourceSha256: h("source"), settingsSha256: h("settings"), buildSha256: h("build") };
test("M13-04I commits only a readback-verified result and binds four identity hashes", async () => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04i-binding-"));
try {
const receipt = await commitServerJobResult(identity, new Uint8Array([1, 2, 3]), { outputDirectory: directory });
assert.equal(receipt.status, "COMMITTED");
assert.equal(receipt.publish, true);
assert.equal((await verifyServerJobResultReceipt(receipt, identity, directory)).verified, true);
assert.equal(receipt.outputByteLength, 3);
} finally { await fs.rm(directory, { recursive: true, force: true }); }
});
test("M13-04I blocks tamper, stale identity and partial/quota failures", async () => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "m13-04i-negative-"));
try {
await assert.rejects(commitServerJobResult({ ...identity, settingsSha256: h("changed") }, new Uint8Array([1]), { outputDirectory: directory, expectedIdentity: identity, faultAt: "AFTER_STAGE" }), /SERVER_JOB_RESULT_IDENTITY_MISMATCH/);
assert.deepEqual(await fs.readdir(directory), []);
const receipt = await commitServerJobResult(identity, new Uint8Array([4, 5]), { outputDirectory: directory });
await fs.writeFile(receipt.outputPath, new Uint8Array([9]));
await assert.rejects(verifyServerJobResultReceipt(receipt, identity, directory), /SERVER_JOB_RESULT_HASH_MISMATCH/);
await assert.rejects(commitServerJobResult({ ...identity, requestId: "request-2" }, new Uint8Array([8]), { outputDirectory: directory, faultAt: "QUOTA" }), /SERVER_JOB_RESULT_STORAGE_QUOTA/);
} finally { await fs.rm(directory, { recursive: true, force: true }); }
});

View File

@@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "stl-export-unit-"));
const sourcePath = path.join(root, "web/protocol/stl-export.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "stl-export.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const document = {
schemaVersion: 1,
variant: "STL_BINARY",
unitScale: 1,
declaredTriangleCount: 1,
triangleCount: 1,
removedDegenerateTriangles: 0,
normals: [[0, 1, 0]],
vertices: [[[-1, 0, 1], [1, 0, 1], [1, 0, -1]]],
bounds: { min: [-1, 0, -1], max: [1, 0, 1] },
};
test("M12-07F serializes binary STL and reports material loss", () => {
const output = protocol.exportBinarySTL(document);
assert.equal(output.byteLength, 134);
assert.equal(new DataView(output).getUint32(80, true), 1);
assert.deepEqual(protocol.createSTLLossReport(2), {
schemaVersion: 1,
operation: "STL_EXPORT_LOSS_REPORT",
canRoundTrip: true,
warningCount: 1,
warnings: [{ code: "STL_MATERIAL_UNSUPPORTED", severity: "warning", message: "STL has no material slots; 2 source material assignments are omitted" }],
});
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "stl-import-unit-"));
const sourcePath = path.join(root, "web/protocol/stl-import.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const modulePath = path.join(temporary, "stl-import.mjs");
fs.writeFileSync(modulePath, transpiled.outputText);
const protocol = await import(pathToFileURL(modulePath));
const fixtureRoot = path.join(root, "tests/files/web/m12_stl_edges_v1");
const bytes = (name) => {
const value = fs.readFileSync(path.join(fixtureRoot, name));
return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
};
test("M12-07E parses binary/ASCII normals and explicit unit scales", () => {
const binary = protocol.importSTL(bytes("capability-binary.stl"), { variant: "STL_BINARY", unitScale: 1 });
assert.equal(binary.triangleCount, 2);
assert.deepEqual(binary.normals, [[0, 1, 0], [0, 1, 0]]);
assert.deepEqual(binary.bounds, { min: [-1, 0, -1], max: [1, 0, 1] });
const scaled = protocol.importSTL(bytes("capability-binary.stl"), { variant: "STL_BINARY", unitScale: 0.001 });
assert.deepEqual(scaled.bounds, { min: [-0.001, 0, -0.001], max: [0.001, 0, 0.001] });
const ascii = protocol.importSTL(bytes("../m12_stl_capability_v1/capability-ascii.stl"), { variant: "STL_ASCII", unitScale: 1 });
assert.equal(ascii.triangleCount, 2);
assert.deepEqual(ascii.normals, binary.normals);
assert.deepEqual(ascii.bounds, binary.bounds);
});
test("M12-07E matches Blender's degenerate removal and blocks trailing bytes", () => {
const degenerate = protocol.importSTL(bytes("degenerate-binary.stl"), { variant: "STL_BINARY", unitScale: 1 });
assert.equal(degenerate.declaredTriangleCount, 2);
assert.equal(degenerate.removedDegenerateTriangles, 1);
assert.equal(degenerate.triangleCount, 1);
assert.throws(() => protocol.importSTL(bytes("trailing-binary.stl"), { variant: "STL_BINARY", unitScale: 1 }), /STL_TRAILING_BYTES/);
assert.throws(() => protocol.importSTL(bytes("capability-binary.stl"), { variant: "STL_BINARY", unitScale: 0 }), /STL_UNIT_SCALE_INVALID/);
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

View File

@@ -0,0 +1,24 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "../../node_modules/typescript/lib/typescript.js";
const root = path.resolve(import.meta.dirname, "../../..");
const sourcePath = path.join(root, "web/protocol/viewport-dpr.ts");
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true });
assert.deepEqual(output.diagnostics, []);
const dpr = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString("base64")}`);
test("M14-04B clamps DPR and computes stable backing dimensions", () => {
for (const [observed, expected] of [[1, 1], [1.5, 1.5], [2, 2], [3, 2], [0, 1], [Number.NaN, 1]]) {
const metrics = dpr.resolveViewportPixelMetrics(101, 57, observed);
assert.equal(metrics.pixelRatio, expected);
assert.deepEqual([metrics.backingWidth, metrics.backingHeight], [Math.floor(101 * expected), Math.floor(57 * expected)]);
}
});
test("M14-04B uses CSS bounds for DPR-independent NDC", () => {
assert.deepEqual(dpr.viewportNDC(150, 75, { left: 100, top: 25, width: 100, height: 100 }), { x: 0, y: 0 });
assert.throws(() => dpr.viewportNDC(0, 0, { left: 0, top: 0, width: 0, height: 1 }), /VIEWPORT_BOUNDS_INVALID/);
});