feat: bind gate verdicts to hermetic candidates

This commit is contained in:
v-openrig-build committed 2026-08-09 09:55:31 +00:00
1 parent df05193e4f
commit 4f930dedb8
6 files changed
+684 -2

No files matched your search

@@ -0,0 +1,194 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawn, spawnSync } from "node:child_process";
import {
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import net from "node:net";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const GATE_SCRIPT = fileURLToPath(new URL("./gate-lane.mjs", import.meta.url));
function write(path, text) {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, text);
}
function git(root, ...args) {
return execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
}
function makeRepo() {
const root = mkdtempSync(join(tmpdir(), "gate-candidate-"));
write(join(root, ".gitignore"), "node_modules/\npackages/cli/daemon/\n");
write(join(root, "tracked.txt"), "one\n");
write(join(root, "packages/cli/index.js"), "export {};\n");
const packages = {
"": { workspaces: ["packages/cli"] },
"node_modules/@openrig/cli": { resolved: "packages/cli", link: true },
"packages/cli": { name: "@openrig/cli" },
};
write(join(root, "package-lock.json"), `${JSON.stringify({ name: "fixture", lockfileVersion: 3, packages }, null, 2)}\n`);
mkdirSync(join(root, "node_modules/@openrig"), { recursive: true });
symlinkSync("../../packages/cli", join(root, "node_modules/@openrig/cli"));
git(root, "init", "-q");
git(root, "config", "user.email", "[email protected]");
git(root, "config", "user.name", "Gate Test");
git(root, "add", ".");
git(root, "commit", "-qm", "fixture");
return root;
}
async function freePort() {
const server = net.createServer();
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const { port } = server.address();
await new Promise((resolve) => server.close(resolve));
return port;
}
async function runGate(root, extraEnv = {}) {
const port = await freePort();
return spawnSync(process.execPath, [GATE_SCRIPT], {
cwd: root,
encoding: "utf8",
env: {
...process.env,
OPENRIG_GATE_LANE_PORT: String(port),
OPENRIG_GATE_LANE_SMOKE: "1",
OPENRIG_GATE_VERDICT: join(root, "gate-lane-verdict.json"),
...extraEnv,
},
});
}
function assertCandidateRefusal(result, pattern) {
assert.notEqual(result.status, 0, `gate must refuse\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
assert.match(`${result.stdout}\n${result.stderr}`, pattern);
}
test("candidate binding: tracked dirt refuses before the first cleanup mutation", async () => {
const root = makeRepo();
const sentinel = join(root, "packages/cli/daemon/sentinel.txt");
write(sentinel, "must survive refusal\n");
write(join(root, "tracked.txt"), "dirty\n");
const result = await runGate(root);
assertCandidateRefusal(result, /candidate.*dirty|tracked.*change/i);
assert.equal(existsSync(sentinel), true, "pre-mutation refusal preserves the stale-bundle sentinel");
});
test("candidate binding: a Git-visible untracked file refuses", async () => {
const root = makeRepo();
write(join(root, "untracked.test.mjs"), "// visible\n");
assertCandidateRefusal(await runGate(root), /candidate.*dirty|untracked/i);
});
test("candidate binding: same verdict basename at a different path is not excluded", async () => {
const root = makeRepo();
write(join(root, "sub/dir/gate-lane-verdict.json"), "{}\n");
assertCandidateRefusal(await runGate(root), /sub\/dir\/gate-lane-verdict\.json|candidate.*dirty/i);
});
test("candidate binding: only the canonical verdict path may be present and the verdict binds HEAD", async () => {
const root = makeRepo();
const verdictPath = join(root, "receipts/current-verdict.json");
write(verdictPath, "{}\n");
const head = git(root, "rev-parse", "HEAD");
const result = await runGate(root, { OPENRIG_GATE_VERDICT: verdictPath });
assert.equal(result.status, 0, result.stderr);
const verdict = JSON.parse(readFileSync(verdictPath, "utf8"));
assert.equal(verdict.candidateSha, head);
assert.equal(verdict.gate, "pass");
});
test("candidate binding: HEAD drift during the gate refuses before writing a verdict", async () => {
const root = makeRepo();
const verdictPath = join(root, "gate-lane-verdict.json");
const fakeBin = mkdtempSync(join(tmpdir(), "gate-fake-npm-"));
const marker = join(tmpdir(), `gate-drift-marker-${process.pid}-${Date.now()}`);
const fakeNpm = join(fakeBin, "npm");
write(fakeNpm, `#!/bin/sh\n: > "$OPENRIG_TEST_MARKER"\nsleep 1\nexit 0\n`);
chmodSync(fakeNpm, 0o755);
const port = await freePort();
const child = spawn(process.execPath, [GATE_SCRIPT], {
cwd: root,
env: {
...process.env,
PATH: `${fakeBin}:${process.env.PATH}`,
OPENRIG_TEST_MARKER: marker,
OPENRIG_GATE_LANE_PORT: String(port),
OPENRIG_GATE_VERDICT: verdictPath,
},
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => { stdout += chunk; });
child.stderr.on("data", (chunk) => { stderr += chunk; });
const closed = new Promise((resolve) => child.on("close", resolve));
const deadline = Date.now() + 5000;
while (!existsSync(marker) && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 20));
}
assert.equal(existsSync(marker), true, "the real gate reached its first injected leg");
write(join(root, "tracked.txt"), "two\n");
git(root, "add", "tracked.txt");
git(root, "commit", "-qm", "move head during gate");
const status = await closed;
assertCandidateRefusal({ status, stdout, stderr }, /HEAD.*(drift|changed)|candidateSha/i);
assert.equal(existsSync(verdictPath), false, "a drifted run writes no verdict");
});
test("candidate binding: Git-visible input added during the gate refuses before writing a verdict", async () => {
const root = makeRepo();
const verdictPath = join(root, "gate-lane-verdict.json");
const fakeBin = mkdtempSync(join(tmpdir(), "gate-fake-npm-"));
const marker = join(tmpdir(), `gate-dirty-marker-${process.pid}-${Date.now()}`);
const release = join(tmpdir(), `gate-dirty-release-${process.pid}-${Date.now()}`);
const fakeNpm = join(fakeBin, "npm");
write(fakeNpm, `#!/bin/sh\n: > "$OPENRIG_TEST_MARKER"\nwhile [ ! -e "$OPENRIG_TEST_RELEASE" ]; do sleep 0.02; done\nexit 0\n`);
chmodSync(fakeNpm, 0o755);
const port = await freePort();
const child = spawn(process.execPath, [GATE_SCRIPT], {
cwd: root,
env: {
...process.env,
PATH: `${fakeBin}:${process.env.PATH}`,
OPENRIG_TEST_MARKER: marker,
OPENRIG_TEST_RELEASE: release,
OPENRIG_GATE_LANE_PORT: String(port),
OPENRIG_GATE_VERDICT: verdictPath,
},
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => { stdout += chunk; });
child.stderr.on("data", (chunk) => { stderr += chunk; });
const closed = new Promise((resolve) => child.on("close", resolve));
const deadline = Date.now() + 5000;
while (!existsSync(marker) && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 20));
}
if (!existsSync(marker)) write(release, "abort\n");
assert.equal(existsSync(marker), true, "the real gate reached its first injected leg");
write(join(root, "late-input.test.mjs"), "// introduced while the gate was running\n");
write(release, "continue\n");
const status = await closed;
assertCandidateRefusal({ status, stdout, stderr }, /candidate.*dirty|late-input\.test\.mjs/i);
assert.equal(existsSync(verdictPath), false, "a late-dirty run writes no verdict");
});
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env node
import { existsSync, readFileSync, unlinkSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
function refuse(message) {
throw new Error(`[gate-consume] REFUSED: ${message}`);
}
/** Honor one exact real-gate verdict, then remove it so it cannot be reused. */
export function consumeGateVerdict({ verdictPath, headSha, log = console.log }) {
if (!existsSync(verdictPath)) refuse(`verdict is missing: ${verdictPath}`);
let verdict;
try {
verdict = JSON.parse(readFileSync(verdictPath, "utf8"));
} catch (error) {
refuse(`verdict is not valid JSON: ${error?.message ?? error}`);
}
if (typeof verdict?.candidateSha !== "string" || verdict.candidateSha.length === 0) {
refuse("candidateSha is required");
}
if (verdict.candidateSha !== headSha) {
refuse(`candidateSha does not match current HEAD (verdict=${verdict.candidateSha}, HEAD=${headSha})`);
}
if (verdict.gate !== "pass") refuse(`gate must be "pass" (received ${JSON.stringify(verdict.gate)})`);
if (verdict.smoke !== false) refuse("smoke must be strictly false; only a real gate may be consumed");
unlinkSync(verdictPath);
if (existsSync(verdictPath)) refuse(`consumed verdict still exists after unlink: ${verdictPath}`);
log(`[gate-consume] honored and consumed candidate ${headSha}; removed ${verdictPath}`);
return verdict;
}
function readHead(root) {
const result = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" });
if (result.status !== 0) refuse(`cannot derive current HEAD: ${result.stderr.trim()}`);
return result.stdout.trim();
}
function main() {
const root = process.cwd();
const verdictPath = resolve(process.argv[2] ?? process.env.OPENRIG_GATE_VERDICT ?? join(root, "gate-lane-verdict.json"));
consumeGateVerdict({ verdictPath, headSha: readHead(root) });
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
main();
} catch (error) {
console.error(error?.message ?? error);
process.exitCode = 1;
}
}
+93
View File
@@ -0,0 +1,93 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const CONSUMER = fileURLToPath(new URL("./gate-lane-consume.mjs", import.meta.url));
function write(path, text) {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, text);
}
function git(root, ...args) {
return execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
}
function makeRepo() {
const root = mkdtempSync(join(tmpdir(), "gate-consume-"));
write(join(root, "tracked.txt"), "one\n");
git(root, "init", "-q");
git(root, "config", "user.email", "[email protected]");
git(root, "config", "user.name", "Gate Test");
git(root, "add", ".");
git(root, "commit", "-qm", "fixture");
return { root, head: git(root, "rev-parse", "HEAD"), verdictPath: join(root, "gate-lane-verdict.json") };
}
function runConsumer(root, verdictPath) {
return spawnSync(process.execPath, [CONSUMER, verdictPath], { cwd: root, encoding: "utf8" });
}
function assertRefused(result, verdictPath, pattern) {
assert.notEqual(result.status, 0, `consumer must refuse\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
assert.match(`${result.stdout}\n${result.stderr}`, pattern);
if (verdictPath) assert.equal(existsSync(verdictPath), true, "a refused verdict is retained");
}
function put(path, value) {
write(path, `${JSON.stringify(value)}\n`);
}
test("consumer refuses loudly when no verdict exists", () => {
const { root, verdictPath } = makeRepo();
const result = runConsumer(root, verdictPath);
assert.notEqual(result.status, 0);
assert.match(`${result.stdout}\n${result.stderr}`, /verdict.*(missing|does not exist)/i);
});
test("consumer refuses and retains a verdict with no candidateSha", () => {
const { root, verdictPath } = makeRepo();
put(verdictPath, { gate: "pass", smoke: false });
assertRefused(runConsumer(root, verdictPath), verdictPath, /candidateSha.*required|missing.*candidateSha/i);
});
test("consumer derives current HEAD and refuses a stale candidateSha", () => {
const { root, head, verdictPath } = makeRepo();
put(verdictPath, { candidateSha: head, gate: "pass", smoke: false });
write(join(root, "tracked.txt"), "two\n");
git(root, "add", "tracked.txt");
git(root, "commit", "-qm", "move head");
assertRefused(runConsumer(root, verdictPath), verdictPath, /candidateSha.*(mismatch|does not match)|current HEAD/i);
});
test("consumer refuses and retains a matching-SHA failed verdict", () => {
const { root, head, verdictPath } = makeRepo();
put(verdictPath, { candidateSha: head, gate: "fail", smoke: false });
assertRefused(runConsumer(root, verdictPath), verdictPath, /gate.*pass/i);
});
test("consumer refuses and retains a smoke:true verdict", () => {
const { root, head, verdictPath } = makeRepo();
put(verdictPath, { candidateSha: head, gate: "pass", smoke: true });
assertRefused(runConsumer(root, verdictPath), verdictPath, /smoke.*false|real gate/i);
});
test("consumer refuses and retains a verdict whose smoke field is absent", () => {
const { root, head, verdictPath } = makeRepo();
put(verdictPath, { candidateSha: head, gate: "pass" });
assertRefused(runConsumer(root, verdictPath), verdictPath, /smoke.*false|missing.*smoke/i);
});
test("consumer honors only a matching real PASS and removes the consumed artifact", () => {
const { root, head, verdictPath } = makeRepo();
put(verdictPath, { candidateSha: head, gate: "pass", smoke: false });
const result = runConsumer(root, verdictPath);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /consum|honor/i);
assert.match(result.stdout, new RegExp(head));
assert.equal(existsSync(verdictPath), false, "successful consume removes the verdict");
});
+88
View File
@@ -0,0 +1,88 @@
import { lstatSync, readFileSync, realpathSync } from "node:fs";
import { isAbsolute, join, relative } from "node:path";
function fail(message) {
throw new Error(`[gate-hermeticity] REFUSED: ${message}`);
}
function inside(parent, child) {
const rel = relative(parent, child);
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
}
/**
* Verify that this worktree owns the dependency root and every lock-derived
* @openrig workspace link. The check reports only; it never repairs.
*/
export function checkDependencyRoot(repoRoot, { log = console.log } = {}) {
const root = realpathSync(repoRoot);
const nodeModules = join(root, "node_modules");
let nodeModulesStat;
try {
nodeModulesStat = lstatSync(nodeModules);
} catch {
fail(`node_modules is absent at ${nodeModules}`);
}
if (nodeModulesStat.isSymbolicLink()) {
let actual = "<unresolved>";
try { actual = realpathSync(nodeModules); } catch { /* reported below */ }
log(`[gate-hermeticity] node_modules actual=${actual} expected=${nodeModules}`);
fail(`node_modules is a symlink: ${nodeModules}`);
}
if (!nodeModulesStat.isDirectory()) fail(`node_modules is not a directory: ${nodeModules}`);
let lock;
try {
lock = JSON.parse(readFileSync(join(root, "package-lock.json"), "utf8"));
} catch (error) {
fail(`cannot read package-lock.json: ${error?.message ?? error}`);
}
const entries = Object.entries(lock?.packages ?? {})
.filter(([path]) => path.startsWith("node_modules/@openrig/"))
.sort(([a], [b]) => a.localeCompare(b));
if (entries.length === 0) fail("zero scoped @openrig links were derived from package-lock.json");
const packagesRoot = realpathSync(join(root, "packages"));
const pairs = [];
const failures = [];
for (const [linkPath, metadata] of entries) {
const name = linkPath.slice("node_modules/".length);
if (metadata?.link !== true || typeof metadata.resolved !== "string" || metadata.resolved.trim() === "") {
log(`[gate-hermeticity] ${name} actual=<unchecked> expected=<missing lock-derived target>`);
failures.push(`${name} has no lock-derived workspace target`);
continue;
}
const expectedPath = join(root, metadata.resolved);
let expected;
try {
expected = realpathSync(expectedPath);
} catch (error) {
log(`[gate-hermeticity] ${name} actual=<unchecked> expected=<unresolved:${expectedPath}>`);
failures.push(`${name} expected target cannot be resolved: ${error?.message ?? error}`);
continue;
}
if (!inside(packagesRoot, expected)) {
log(`[gate-hermeticity] ${name} actual=<unchecked> expected=${expected}`);
failures.push(`${name} lock-derived target escapes ${packagesRoot}`);
continue;
}
const actualPath = join(root, linkPath);
let actual;
try {
actual = realpathSync(actualPath);
} catch (error) {
log(`[gate-hermeticity] ${name} actual=<unresolved:${actualPath}> expected=${expected}`);
failures.push(`${name} actual target cannot be resolved: ${error?.message ?? error}`);
continue;
}
const line = `[gate-hermeticity] ${name} actual=${actual} expected=${expected}`;
log(line);
pairs.push({ name, actual, expected });
if (actual !== expected) failures.push(`${name} actual target does not equal its lock-derived expected target`);
}
if (failures.length > 0) fail(failures.join("; "));
return { checked: pairs.length, pairs };
}
+191
View File
@@ -0,0 +1,191 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawn, spawnSync } from "node:child_process";
import {
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import net from "node:net";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const GATE_SCRIPT = fileURLToPath(new URL("./gate-lane.mjs", import.meta.url));
const CONSUMER_SCRIPT = fileURLToPath(new URL("./gate-lane-consume.mjs", import.meta.url));
function write(path, text) {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, text);
}
function git(root, ...args) {
return execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
}
function makeRepo({ lockEntries = true, dependencyRoot = "exact" } = {}) {
const root = mkdtempSync(join(tmpdir(), "gate-hermeticity-"));
write(join(root, ".gitignore"), "node_modules/\npackages/cli/daemon/\n");
write(join(root, "package.json"), '{"private":true}\n');
write(join(root, "packages/cli/index.js"), "export {};\n");
write(join(root, "packages/other/index.js"), "export {};\n");
const packages = lockEntries
? {
"": { workspaces: ["packages/cli"] },
"node_modules/@openrig/cli": { resolved: "packages/cli", link: true },
"packages/cli": { name: "@openrig/cli" },
}
: { "": { workspaces: [] } };
write(join(root, "package-lock.json"), `${JSON.stringify({ name: "fixture", lockfileVersion: 3, packages }, null, 2)}\n`);
if (dependencyRoot === "symlink") {
const foreign = mkdtempSync(join(tmpdir(), "gate-hermeticity-root-"));
mkdirSync(join(foreign, "node_modules"), { recursive: true });
symlinkSync(join(foreign, "node_modules"), join(root, "node_modules"));
} else if (dependencyRoot !== "absent") {
mkdirSync(join(root, "node_modules/@openrig"), { recursive: true });
const target = dependencyRoot === "swapped"
? "../../packages/other"
: dependencyRoot === "cross-tree"
? join(mkdtempSync(join(tmpdir(), "gate-hermeticity-cross-")), "packages/cli")
: "../../packages/cli";
if (dependencyRoot === "cross-tree") mkdirSync(target, { recursive: true });
symlinkSync(target, join(root, "node_modules/@openrig/cli"));
}
git(root, "init", "-q");
git(root, "config", "user.email", "[email protected]");
git(root, "config", "user.name", "Gate Test");
git(root, "add", ".");
git(root, "commit", "-qm", "fixture");
return root;
}
async function freePort() {
const server = net.createServer();
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const { port } = server.address();
await new Promise((resolve) => server.close(resolve));
return port;
}
async function runGate(root) {
const port = await freePort();
return spawnSync(process.execPath, [GATE_SCRIPT], {
cwd: root,
encoding: "utf8",
env: {
...process.env,
OPENRIG_GATE_LANE_PORT: String(port),
OPENRIG_GATE_LANE_SMOKE: "1",
OPENRIG_GATE_VERDICT: join(root, "gate-lane-verdict.json"),
},
});
}
function assertHermeticRefusal(result, pattern) {
assert.notEqual(result.status, 0, `gate must refuse\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
assert.match(`${result.stdout}\n${result.stderr}`, pattern);
}
test("dependency root: absent node_modules refuses before the gate mutates", async () => {
const result = await runGate(makeRepo({ dependencyRoot: "absent" }));
assertHermeticRefusal(result, /node_modules.*absent|missing dependency root/i);
});
test("dependency root: a symlinked node_modules root refuses", async () => {
const root = makeRepo({ dependencyRoot: "symlink" });
const sentinel = join(root, "packages/cli/daemon/sentinel.txt");
write(sentinel, "must survive refusal\n");
const result = await runGate(root);
assertHermeticRefusal(result, /node_modules.*symlink/i);
assert.equal(existsSync(sentinel), true, "the hermeticity refusal runs before cleanup mutation");
});
test("dependency root: a scoped link escaping to another tree refuses and prints both operands", async () => {
const result = await runGate(makeRepo({ dependencyRoot: "cross-tree" }));
assertHermeticRefusal(result, /@openrig\/cli[\s\S]*actual[\s\S]*expected/i);
});
test("dependency root: a scoped link swapped to another in-tree package refuses", async () => {
const result = await runGate(makeRepo({ dependencyRoot: "swapped" }));
assertHermeticRefusal(result, /@openrig\/cli[\s\S]*packages\/other[\s\S]*packages\/cli/i);
});
test("dependency root: a lock deriving zero scoped links refuses instead of vacuously passing", async () => {
const result = await runGate(makeRepo({ lockEntries: false }));
assertHermeticRefusal(result, /zero|no scoped @openrig/i);
});
test("dependency root: exact lock-derived target passes and prints the actual/expected pair", async () => {
const result = await runGate(makeRepo());
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /@openrig\/cli[\s\S]*actual[\s\S]*expected/i);
assert.match(result.stdout, /packages\/cli/i);
});
test("dependency root: a later refusal invalidates a same-HEAD prior PASS", async () => {
const root = makeRepo();
const verdictPath = join(root, "gate-lane-verdict.json");
const first = await runGate(root);
assert.equal(first.status, 0, first.stderr);
assert.equal(existsSync(verdictPath), true, "the first gate writes a PASS receipt");
rmSync(join(root, "node_modules/@openrig/cli"), { force: true });
symlinkSync("../../packages/other", join(root, "node_modules/@openrig/cli"));
const second = await runGate(root);
assertHermeticRefusal(second, /@openrig\/cli[\s\S]*packages\/other[\s\S]*packages\/cli/i);
assert.equal(existsSync(verdictPath), false, "the refused retry invalidates the prior PASS");
const consume = spawnSync(process.execPath, [CONSUMER_SCRIPT, verdictPath], { cwd: root, encoding: "utf8" });
assert.notEqual(consume.status, 0, "the prior PASS is no longer consumable");
assert.match(`${consume.stdout}\n${consume.stderr}`, /verdict.*missing/i);
});
test("dependency root: drift during the gate refuses before writing a verdict", async () => {
const root = makeRepo();
const verdictPath = join(root, "gate-lane-verdict.json");
const fakeBin = mkdtempSync(join(tmpdir(), "gate-hermeticity-fake-npm-"));
const marker = join(tmpdir(), `gate-root-marker-${process.pid}-${Date.now()}`);
const release = join(tmpdir(), `gate-root-release-${process.pid}-${Date.now()}`);
const fakeNpm = join(fakeBin, "npm");
write(fakeNpm, `#!/bin/sh\n: > "$OPENRIG_TEST_MARKER"\nwhile [ ! -e "$OPENRIG_TEST_RELEASE" ]; do sleep 0.02; done\nexit 0\n`);
chmodSync(fakeNpm, 0o755);
const port = await freePort();
const child = spawn(process.execPath, [GATE_SCRIPT], {
cwd: root,
env: {
...process.env,
PATH: `${fakeBin}:${process.env.PATH}`,
OPENRIG_TEST_MARKER: marker,
OPENRIG_TEST_RELEASE: release,
OPENRIG_GATE_LANE_PORT: String(port),
OPENRIG_GATE_VERDICT: verdictPath,
},
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => { stdout += chunk; });
child.stderr.on("data", (chunk) => { stderr += chunk; });
const closed = new Promise((resolve) => child.on("close", resolve));
const deadline = Date.now() + 5000;
while (!existsSync(marker) && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 20));
}
if (!existsSync(marker)) write(release, "abort\n");
assert.equal(existsSync(marker), true, "the real gate reached its first injected leg");
rmSync(join(root, "node_modules/@openrig/cli"), { force: true });
symlinkSync("../../packages/other", join(root, "node_modules/@openrig/cli"));
write(release, "continue\n");
const status = await closed;
assertHermeticRefusal({ status, stdout, stderr }, /@openrig\/cli[\s\S]*packages\/other[\s\S]*packages\/cli|gate-hermeticity/i);
assert.equal(existsSync(verdictPath), false, "a late dependency-root drift writes no verdict");
});
+64 -2
View File
@@ -5,12 +5,13 @@
// (advisory, recorded) → run BOTH legs (typecheck AND vitest AND vitest:ui — the two honesty gaps) →
// write the C2 verdict (green carries the load it ran under) → release (kernel also frees on death).
import { spawnSync, execSync } from "node:child_process";
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
import { writeFileSync, readFileSync, mkdirSync, realpathSync, unlinkSync } from "node:fs";
import { tmpdir, loadavg } from "node:os";
import { join, dirname } from "node:path";
import { join, dirname, basename, isAbsolute, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { acquireGateLane, GATE_LANE_PORT } from "./gate-lane-lock.mjs";
import { renderRefusal, runGate, observeForeignLoad, cleanStaleVendoredBundle } from "./gate-lane-run.mjs";
import { checkDependencyRoot } from "./gate-lane-hermeticity.mjs";
const RUNTIME_DIR = (() => { const d = join(tmpdir(), "openrig-gate"); mkdirSync(d, { recursive: true }); return d; })();
const HOLDER_INFO = join(RUNTIME_DIR, "gate-lane.holder.json");
@@ -37,6 +38,53 @@ function snapshotProcesses() {
} catch { return []; }
}
function runGit(args, cwd) {
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
if (result.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim()}`);
return result.stdout;
}
function readCandidateSha(repoRoot) {
const sha = runGit(["rev-parse", "HEAD"], repoRoot).trim();
if (!sha) throw new Error("candidate HEAD is empty");
return sha;
}
function canonicalVerdictRelativePath(repoRoot) {
const verdictPath = resolve(VERDICT_PATH);
let canonicalPath = verdictPath;
try {
canonicalPath = realpathSync(verdictPath);
} catch {
try { canonicalPath = join(realpathSync(dirname(verdictPath)), basename(verdictPath)); } catch { /* absent parent: no status entry can match */ }
}
const rel = relative(realpathSync(repoRoot), canonicalPath);
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null;
return rel.split(sep).join("/");
}
function assertCandidateClean(repoRoot) {
const canonicalVerdict = canonicalVerdictRelativePath(repoRoot);
const records = runGit(["status", "--porcelain=v1", "-z", "--untracked-files=all"], repoRoot)
.split("\0")
.filter(Boolean);
const unexpected = records.filter((record) => {
if (record.length < 4 || record[2] !== " ") return true;
return record.slice(3).split(sep).join("/") !== canonicalVerdict;
});
if (unexpected.length > 0) {
throw new Error(`candidate worktree is dirty: ${unexpected.join(" | ")}`);
}
}
function invalidatePriorVerdict() {
try {
unlinkSync(VERDICT_PATH);
} catch (error) {
if (error?.code !== "ENOENT") throw error;
}
}
async function main() {
const startedAt = new Date().toISOString();
const lane = await acquireGateLane({ port: GATE_LANE_PORT, holderInfoPath: HOLDER_INFO });
@@ -46,8 +94,15 @@ async function main() {
}
let exitCode = 3;
try {
// This acquired run supersedes every earlier receipt, including for the same HEAD.
invalidatePriorVerdict();
const foreignLoad = observeForeignLoad({ loadavg: loadavg(), processes: snapshotProcesses() });
if (foreignLoad.advisory.length) console.warn(`⚠ advisory (exit UNCHANGED, recorded in verdict): ${foreignLoad.advisory.join("; ")}`);
// Bind the run to one clean, worktree-local candidate BEFORE the first repository mutation.
const repoRoot = process.cwd();
const candidateSha = readCandidateSha(repoRoot);
assertCandidateClean(repoRoot);
checkDependencyRoot(repoRoot);
// SOURCE-TRUTH: drop any stale vendored daemon bundle (a gitignored build:package leftover) before
// the legs run, so test:repo's freshness guard is never poisoned by a desk leftover. Real package-time
// assembly is still guarded; a fresh checkout is a no-op.
@@ -61,6 +116,13 @@ async function main() {
// picks the executor AND flows into the verdict, so the two can never disagree. The gate's own
// negative-control test calls THIS SAME runGate — it guards this shipped path, not a lookalike.
const verdict = await runGate({ smoke: SMOKE, realExec, foreignLoad, startedAt, ledger, cutCeiling });
const endingSha = readCandidateSha(repoRoot);
if (endingSha !== candidateSha) {
throw new Error(`candidate HEAD changed during gate (started ${candidateSha}, ended ${endingSha})`);
}
assertCandidateClean(repoRoot);
checkDependencyRoot(repoRoot);
verdict.candidateSha = candidateSha;
mkdirSync(dirname(VERDICT_PATH), { recursive: true }); // ensure the verdict's home exists
writeFileSync(VERDICT_PATH, JSON.stringify(verdict, null, 2));
console.log(verdict.ledgerState); // in-band LOUD: name every exclusion (or "0 exclusions")