ci(runner): materialize pinned OpenCode binary

This commit is contained in:
Dotta 2026-09-03 13:57:53 -05:00
parent d238d6c01e
commit a673fca0a3
6 changed files with 242 additions and 3 deletions

View File

@ -619,6 +619,10 @@ jobs:
- if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Materialize verified pinned OpenCode executable
if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
run: node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs
- name: Download immutable shared campaign outputs
if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@ -762,6 +766,10 @@ jobs:
# the protected environment during setup.
- run: pnpm install --frozen-lockfile --ignore-scripts
- name: Materialize verified pinned OpenCode executable
if: matrix.environmentId == 'local' && (matrix.profileId == 'legacy-opencode' || matrix.profileId == 'runner-opencode' || matrix.suiteId == 'openrouter-model-breadth')
run: node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs
- name: Download immutable campaign outputs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:

View File

@ -73,7 +73,7 @@
"typecheck:rust": "cargo fmt --manifest-path runner/Cargo.toml --all -- --check && cargo check --manifest-path runner/Cargo.toml --locked --workspace",
"typecheck:browser": "tsc -p tsconfig.browser.json --noEmit",
"test": "pnpm run test:typescript && pnpm run test:rust",
"test:typescript": "pnpm run ensure:eval-build-deps && pnpm run build:rust && node --test test/protocol-contract.test.mjs test/acpx-sidecar-contract.test.mjs test/acpx-codex-package-contract.test.mjs scripts/aws-agentcore-provisioning.test.mjs scripts/build-verified-provider-entrypoints.test.mjs && vitest run",
"test:typescript": "pnpm run ensure:eval-build-deps && pnpm run build:rust && node --test test/protocol-contract.test.mjs test/acpx-sidecar-contract.test.mjs test/acpx-codex-package-contract.test.mjs scripts/aws-agentcore-provisioning.test.mjs scripts/build-verified-provider-entrypoints.test.mjs scripts/materialize-opencode-binary.test.mjs && vitest run",
"test:rust": "cargo test --release --manifest-path runner/Cargo.toml --locked --workspace",
"test:codex": "cargo test --manifest-path runner/Cargo.toml --locked -p paperclip-runner-core --test codex_provider",
"test:durable": "cargo test --manifest-path runner/Cargo.toml --locked -p paperclip-runner-core durable::",

View File

@ -0,0 +1,109 @@
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
chmodSync,
copyFileSync,
existsSync,
linkSync,
lstatSync,
readFileSync,
realpathSync,
unlinkSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
const OPENCODE_VERSION = "1.18.17";
const BASELINE_PACKAGE = "opencode-linux-x64-baseline";
function readPackage(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
function sha256(path) {
return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function assertPackage(packageRoot, expectedName) {
const packageJson = readPackage(join(packageRoot, "package.json"));
if (
packageJson.name !== expectedName ||
packageJson.version !== OPENCODE_VERSION
) {
throw new Error(
`Expected ${expectedName}@${OPENCODE_VERSION}, received ${String(packageJson.name)}@${String(packageJson.version)}`,
);
}
}
export function materializePinnedOpenCodeBinary(options = {}) {
const platform = options.platform ?? process.platform;
const architecture = options.architecture ?? process.arch;
if (platform !== "linux" || architecture !== "x64") {
throw new Error(
`Pinned OpenCode materialization requires linux/x64, received ${platform}/${architecture}`,
);
}
const packageRoot = realpathSync(
options.packageRoot ??
resolve(import.meta.dirname, "../node_modules/opencode-ai"),
);
const dependencyRoot = dirname(packageRoot);
const baselineRoot = realpathSync(join(dependencyRoot, BASELINE_PACKAGE));
assertPackage(packageRoot, "opencode-ai");
assertPackage(baselineRoot, BASELINE_PACKAGE);
const source = join(baselineRoot, "bin", "opencode");
const target = join(packageRoot, "bin", "opencode.exe");
if (!lstatSync(source).isFile()) {
throw new Error("Pinned OpenCode source executable is not a regular file");
}
if (existsSync(target)) {
if (!lstatSync(target).isFile()) {
throw new Error("OpenCode target executable is not a regular file");
}
unlinkSync(target);
}
try {
linkSync(source, target);
} catch (error) {
const code = error?.code;
if (!new Set(["EACCES", "EMLINK", "EPERM", "EXDEV"]).has(code)) {
throw error;
}
copyFileSync(source, target);
}
chmodSync(target, 0o755);
const sourceDigest = sha256(source);
const targetDigest = sha256(target);
if (sourceDigest !== targetDigest) {
throw new Error("Materialized OpenCode executable digest mismatch");
}
const targetStat = lstatSync(target);
const mode = targetStat.mode & 0o777;
if (!targetStat.isFile() || (mode & 0o111) === 0 || mode & 0o022) {
throw new Error("Materialized OpenCode executable has unsafe permissions");
}
const version = spawnSync(target, ["--version"], {
encoding: "utf8",
timeout: 30_000,
windowsHide: true,
});
if (version.status !== 0 || version.stdout.trim() !== OPENCODE_VERSION) {
throw new Error(
`Materialized OpenCode executable did not report ${OPENCODE_VERSION}`,
);
}
return { sourceDigest, target, version: OPENCODE_VERSION };
}
const invokedPath = process.argv[1]
? pathToFileURL(realpathSync(process.argv[1])).href
: null;
if (invokedPath === import.meta.url) {
const result = materializePinnedOpenCodeBinary();
process.stdout.write(`${JSON.stringify(result)}\n`);
}

View File

@ -0,0 +1,107 @@
import assert from "node:assert/strict";
import {
chmod,
mkdir,
mkdtemp,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "node:test";
import { materializePinnedOpenCodeBinary } from "./materialize-opencode-binary.mjs";
const temporaryDirectories = [];
afterEach(async () => {
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true })),
);
});
async function fixture(options = {}) {
const root = await mkdtemp(join(tmpdir(), "paperclip-opencode-binary-"));
temporaryDirectories.push(root);
const packageRoot = join(root, "opencode-ai");
const baselineRoot = join(root, "opencode-linux-x64-baseline");
await Promise.all([
mkdir(join(packageRoot, "bin"), { recursive: true }),
mkdir(join(baselineRoot, "bin"), { recursive: true }),
]);
await Promise.all([
writeFile(
join(packageRoot, "package.json"),
JSON.stringify({
name: "opencode-ai",
version: options.packageVersion ?? "1.18.17",
}),
),
writeFile(
join(baselineRoot, "package.json"),
JSON.stringify({
name: "opencode-linux-x64-baseline",
version: options.baselineVersion ?? "1.18.17",
}),
),
writeFile(join(packageRoot, "bin", "opencode.exe"), "sentinel\n"),
]);
const source = join(baselineRoot, "bin", "opencode");
if (options.symlinkSource) {
const realSource = join(root, "real-opencode");
await writeFile(realSource, "#!/bin/sh\necho 1.18.17\n");
await chmod(realSource, 0o755);
await symlink(realSource, source);
} else {
await writeFile(source, "#!/bin/sh\necho 1.18.17\n");
await chmod(source, 0o755);
}
return packageRoot;
}
test("materializes the pinned baseline executable with a verified version", async () => {
const packageRoot = await fixture();
const result = materializePinnedOpenCodeBinary({
packageRoot,
platform: "linux",
architecture: "x64",
});
assert.equal(result.version, "1.18.17");
assert.match(result.sourceDigest, /^[0-9a-f]{64}$/);
});
test("refuses version, file-type, and platform drift", async () => {
const wrongVersion = await fixture({ baselineVersion: "1.18.18" });
assert.throws(
() =>
materializePinnedOpenCodeBinary({
packageRoot: wrongVersion,
platform: "linux",
architecture: "x64",
}),
/Expected opencode-linux-x64-baseline@1\.18\.17/,
);
const symlinkSource = await fixture({ symlinkSource: true });
assert.throws(
() =>
materializePinnedOpenCodeBinary({
packageRoot: symlinkSource,
platform: "linux",
architecture: "x64",
}),
/source executable is not a regular file/,
);
const unsupported = await fixture();
assert.throws(
() =>
materializePinnedOpenCodeBinary({
packageRoot: unsupported,
platform: "darwin",
architecture: "arm64",
}),
/requires linux\/x64/,
);
});

View File

@ -31,8 +31,9 @@ must never run repository lifecycle scripts. The shared-build and provider-pack
jobs also receive no provider credentials and disable dependency lifecycle
scripts; they package outputs with SHA-256 sidecars that consumers verify
before extraction. The paid test job installs with lifecycle scripts disabled,
and provider secrets are scoped only to its final test step rather than
dependency setup. Report sanitization and AWS
and materializes the exact pinned OpenCode executable from its lockfile-verified
optional package without invoking package lifecycle code. Provider secrets are
scoped only to the final test step rather than dependency setup. Report sanitization and AWS
history publication explicitly use the trusted workflow commit and do not
consume the target lockfile. Never run the workflow definition from the target
branch.

View File

@ -309,6 +309,9 @@ describe("public repository paid workflow security", () => {
expect(buildJob).toContain(
"node packages/paperclip-runner/scripts/build-provider-pack.mjs",
);
expect(buildJob).toContain(
"node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs",
);
expect(buildJob).toContain("runner-e2e-build-bundle.tar.gz.sha256");
expect(buildJob).toContain("runner-e2e-provider-pack.tar.gz.sha256");
expect(buildJob).toContain(
@ -342,6 +345,17 @@ describe("public repository paid workflow security", () => {
);
expect(testJob).toContain(".payload.runnerSourceRevision == $revision");
expect(workflow).toContain("Qualify local provider Node interpreter");
expect(testJob).toContain(
"Materialize verified pinned OpenCode executable",
);
expect(testJob).toContain(
"matrix.profileId == 'legacy-opencode' || matrix.profileId == 'runner-opencode' || matrix.suiteId == 'openrouter-model-breadth'",
);
expect(testJob).toContain(
"node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs",
);
expect(testJob).not.toContain("postinstall.mjs");
expect(testJob).not.toContain("pnpm rebuild");
expect(testJob).not.toContain("build:typescript");
expect(testJob).not.toContain("build:runner-binaries");
expect(testJob).not.toContain("build-provider-pack.mjs");