Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
contents: read
# Caller выбирает revision, который чекаутит worker, включая код PR. Сам код
# считается недоверенным и исполняется только внутри одноразовой ячейки.
uses: Labpics-Team/lab-colors/.github/workflows/ci-worker.yml@1461bc2ed60142aed3a8723e618b883be6418156
uses: Labpics-Team/lab-colors/.github/workflows/ci-worker.yml@beecd257371a7a6421079b0d8207a109969aa332
33 changes: 33 additions & 0 deletions packages/colors/bench/private-program-wasm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"schemaVersion": 1,
"role": "private-program-consumer",
"artifact": "packages/colors/private-program/labcolors_private_program.wasm",
"toolchain": {
"rust": "1.96.0",
"rustcCommit": "ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96",
"cargo": "1.96.0",
"cargoCommit": "30a34c6821b57de0aaec83a901aca39f88f6778c",
"target": "wasm32-unknown-unknown",
"profile": "release",
"feature": "private-fixture",
"node": "24.14.0",
"binaryenRelease": "version_117",
"binaryenNodeArchiveSha256": "2d5a42f2d167a7cc2b4b6664c44c5ace1690d13db4f527324f052afbad461a07",
"binaryenComponentSha256": {
"wasm-opt.js": "c0b4bc26f1a588dc686ae36b32c4fea3d7b99f4fb8a1778d0ba4129f326f8449",
"wasm-opt.wasm": "d823328d8fcad3a59aa605c61d1620d30b9156f086d30ff3246b43c32526856b",
"wasm-opt.worker.js": "5b7952731f6ea1d5954db968e45b13f862853e6ef14a03b3f35d036f6136b624"
},
"wasmOptFlags": "-Oz --enable-bulk-memory --enable-nontrapping-float-to-int"
},
"measurement": {
"source": "github-actions-run-31473003387",
"platform": "linux-x64",
"rawBytes": 339336
},
"policy": {
"maxRawBytes": 339336,
"basis": "exact optimized private Program artifact from GitHub Actions Linux",
"gzip": "diagnostic-only"
}
}
89 changes: 89 additions & 0 deletions packages/colors/test/javascript-source-contract.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
function tokens(source) {
const result = [];
for (let index = 0; index < source.length; ) {
const character = source[index];
const next = source[index + 1];
if (/\s/u.test(character)) {
index += 1;
} else if (character === "/" && next === "/") {
index = source.indexOf("\n", index + 2);
if (index === -1) break;
} else if (character === "/" && next === "*") {
index = source.indexOf("*/", index + 2);
if (index === -1) throw new Error("unterminated JavaScript block comment");
index += 2;
} else if (character === '"' || character === "'") {
const quote = character;
const start = index;
index += 1;
while (index < source.length && source[index] !== quote) {
index += source[index] === "\\" ? 2 : 1;
}
if (index >= source.length) throw new Error("unterminated JavaScript string literal");
const raw = source.slice(start, index + 1);
result.push({ type: "string", value: quote === '"' ? JSON.parse(raw) : raw.slice(1, -1) });
index += 1;
} else if (character === "`") {
index += 1;
while (index < source.length && source[index] !== "`") {
index += source[index] === "\\" ? 2 : 1;
}
if (index >= source.length) throw new Error("unterminated JavaScript template literal");
index += 1;
} else if (/[A-Za-z_$]/u.test(character)) {
const start = index;
index += 1;
while (/[A-Za-z0-9_$]/u.test(source[index] ?? "")) index += 1;
result.push({ type: "identifier", value: source.slice(start, index) });
} else {
result.push({ type: "punctuator", value: character });
index += 1;
}
}
return result;
}

export function chromeArguments(source) {
const sourceTokens = tokens(source);
for (let index = 0; index < sourceTokens.length - 4; index += 1) {
if (
sourceTokens[index].type !== "string" ||
sourceTokens[index].value !== "goog:chromeOptions" ||
sourceTokens[index + 1].value !== ":" ||
sourceTokens[index + 2].value !== "{"
) {
continue;
}
let objectDepth = 1;
for (let cursor = index + 3; cursor < sourceTokens.length && objectDepth > 0; cursor += 1) {
const token = sourceTokens[cursor];
if (token.value === "{") objectDepth += 1;
if (token.value === "}") objectDepth -= 1;
if (
objectDepth === 1 &&
token.type === "identifier" &&
token.value === "args" &&
sourceTokens[cursor + 1]?.value === ":" &&
sourceTokens[cursor + 2]?.value === "["
) {
const args = [];
let nestedDepth = 0;
let element = [];
for (let argument = cursor + 3; argument < sourceTokens.length; argument += 1) {
const argumentToken = sourceTokens[argument];
if (nestedDepth === 0 && (argumentToken.value === "," || argumentToken.value === "]")) {
if (element.length === 1 && element[0].type === "string") args.push(element[0].value);
element = [];
if (argumentToken.value === "]") return args;
continue;
}
if (["[", "{", "("].includes(argumentToken.value)) nestedDepth += 1;
if (["]", "}", ")"].includes(argumentToken.value)) nestedDepth -= 1;
element.push(argumentToken);
}
throw new Error("unterminated goog:chromeOptions args array");
}
}
}
throw new Error("goog:chromeOptions args array is absent");
}
23 changes: 21 additions & 2 deletions packages/colors/test/private-program-ci-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import { dirname, join, resolve } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";

import { chromeArguments } from "./javascript-source-contract.mjs";

const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, "../../..");
const read = (...parts) => readFileSync(join(root, ...parts), "utf8");
const normalizeNewlines = (value) => value.replaceAll("\r\n", "\n");

const CALLER_WORKER_SHA = "1461bc2ed60142aed3a8723e618b883be6418156";
const CALLER_WORKER_SHA = "beecd257371a7a6421079b0d8207a109969aa332";
const CALLER_WORKER_REFERENCE =
` uses: Labpics-Team/lab-colors/.github/workflows/ci-worker.yml@${CALLER_WORKER_SHA}`;
const RUNTIME_BUDGET_COMMAND = " run: node scripts/check-wasm-size-budget.mjs";
Expand Down Expand Up @@ -182,12 +184,15 @@ function assertPrivateMutationDeadline(workflow) {
assert.ok(70 > 40 + 20 + 5, "outer timeout must exceed declared budgets and teardown headroom");
}

test("Stage A keeps the public caller pinned to the pre-Stage-B immutable worker", () => {
test("Stage B activates the public caller at the merged Stage A worker commit", () => {
const caller = read(".github", "workflows", "ci.yml");
assertImmutableCaller(caller);

for (const mutation of [
caller.replace(CALLER_WORKER_SHA, "0".repeat(40)),
caller.replace(CALLER_WORKER_SHA, "main"),
caller.replace(CALLER_WORKER_SHA, CALLER_WORKER_SHA.slice(0, 12)),
caller.replace(CALLER_WORKER_REFERENCE, ""),
caller.replace(CALLER_WORKER_REFERENCE, `${CALLER_WORKER_REFERENCE}\n${CALLER_WORKER_REFERENCE}`),
caller.replace(
CALLER_WORKER_REFERENCE,
Expand Down Expand Up @@ -261,6 +266,20 @@ test("worker binds the browser proof to the exact verified tarball bytes", () =>
assert.throws(() => assertWorkerOrderAndRoles(reordered));
});

test("private Program browser proof owns the CI Chrome launch invariant", () => {
const browserProof = read("scripts", "test-private-program-browser.mjs");
assert.ok(
chromeArguments(browserProof).includes("--no-sandbox"),
"the userspace CfT proof must opt out of an unavailable host sandbox",
);
const flagInCommentOnly = browserProof.replace(
' "--no-sandbox",',
' // "--no-sandbox",',
);
assert.notEqual(flagInCommentOnly, browserProof);
assert.equal(chromeArguments(flagInCommentOnly).includes("--no-sandbox"), false);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test("private mutation keeps its own deadline reachable inside the wasm job", () => {
const worker = read(".github", "workflows", "ci-worker.yml");
assertPrivateMutationDeadline(worker);
Expand Down
Loading
Loading