Files
PaperClipAI/packages/paperclip-runner/scripts/runner-protocol-eval-workflow-security.test.mjs
T
DottaandClaude Opus 5.5 b1d00eeca0 fix(evals): derive the controlled Runner eval policy from committed defaults (#19253)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Runner protocol qualification runs real providers against 41 cases
and reports them in TestOps.
> - Controlled mode (#17889) adds a spend cap, private evidence storage,
and cost and trace accounting.
> - Operators must hand-maintain one large JSON variable for each
campaign size, price review, and lockfile change.
> - A one-cell policy blocked the first 41-case run, and its prices
expired after 24 hours.
> - This pull request derives that policy from committed defaults and
the selected cases.
> - The benefit is that a full run needs no policy edit, and all three
safety properties stay.

## Linked Issues or Issue Description

Refs #17889, #19011, #19070.

**What existing behavior does this improve?**

Controlled Runner protocol campaigns read
`RUNNER_PROTOCOL_EVAL_CONTROLLER_POLICY_JSON`. That variable holds the
cell limit, a price table with a review expiry, a pinned lockfile
digest, and self-hashed bound proofs.

**Current behavior**

A campaign with more cells than `maxCells` fails before provider work.
Expired prices also fail before provider work. Any change to the
lockfile needs a new pin. A one-cell policy made all 41 TestOps cases
fail as infrastructure, so an operator had to edit the variable by hand
before the full run.

**Proposed behavior**

The workflow derives the policy at preparation time. The cell count
comes from the selected catalog. Prices come from a committed table with
a version and a source URL. The controller installs the committed frozen
lockfile. One optional variable, `RUNNER_PROTOCOL_EVAL_MAX_USD`, can
override the committed $400 spend cap.

**Reason and benefit**

Each piece that was hand-maintained duplicated a check that already
exists. The manifest digest binds every allocation. The frozen install
binds dependencies to the reviewed commit. Under model-request
readiness, the self-hashed bounds never changed admission or accounting.

**Breaking changes**

The old variable is no longer read. It can stay set or be deleted.
Retained manifests that include the old proofs still replay.

## What Changed

- Add
`packages/paperclip-runner/scripts/controlled-evals/hosted-defaults.json`.
It holds the spend cap, duration, route, Claude Sonnet 5 prices,
allocation, overhead reservation and trace limits.
- Add `hostedPolicy()` in `hosted.mjs`. It derives `maxCells` from the
selection. It gives each campaign a validity window from preparation to
one hour after its deadline. It applies the optional spend cap override.
- Remove `capacityBound` and the policy self-hash from hosted
preparation. `manifest.mjs` needs model `provisionalBound` only when
readiness is not `model_requests`.
- Workflow: stop reading the policy variable. The controller build
installs the committed `pnpm-lock.yaml` with `--frozen-lockfile` and
fails on any lockfile drift. Preparation checks that the runtime receipt
lockfile digest equals the committed lockfile. The catalog limit is now
1 to 512 selected cells.
- Keep everything else the same: the spend-cap check before provider
work, private S3 retention of the manifest and derived
`hosted-policy.json`, receipts, tracing, first-attempt and zero-retry
gates, and paid-environment approvals.
- Update `hosted.test.mjs`, `portable.test.mjs`,
`runner-protocol-eval-workflow-security.test.mjs`, and
`docs/runner-protocol-live-evals.md`.

## Verification

- `node --test scripts/controlled-evals/hosted.test.mjs`: 8 pass. The
new tests show that `maxCells` equals the selection and that the
validity window covers the deadline with no committed expiry. They also
show that the spend cap override applies. With the committed defaults,
41 cells validate with $339 reserved, and 49 cells fail on the $400
budget.
- Negative controls still fail: drift in selection, source or attempt; a
bad lock digest; missing tracing; a duration over 4 hours; a malformed
or too-low spend cap; and zero cells.
- `node --test scripts/runner-protocol-eval-workflow-security.test.mjs`:
20 pass. Controlled admission now passes whether the old variable is
absent, still set, or malformed. A real git checkout with a modified
`pnpm-lock.yaml` fails the controller guard before install.
- `node --test scripts/controlled-evals/controller.test.mjs
scripts/controlled-evals/portable.test.mjs`: all pass.
- `actionlint .github/workflows/runner-protocol-live-evals.yml` and `git
diff --check` pass.
- `tests/runner-e2e/workflow-security.test.ts` has 5 failures that this
change does not cause. They concern `pr-trusted.yml`,
`chat-catalog-testops-case.yml` and `runner-full-stack-e2e.yml`. This PR
does not touch those files.
- I have not run a live controlled campaign on this branch. Controlled
mode runs only from the default branch. The current 41-case run uses
master and the existing variable.

## Risks

- The committed price table has no expiry. A provider price change needs
a reviewed edit to the table. The spend cap still limits the maximum
spend.
- If the committed lockfile does not match the package manifests, the
controller build fails before provider work. CI already requires a
frozen lockfile.
- The spend cap is now $400 by default. It was $339 in the last manual
policy. The cap is a reservation limit, not expected spend. The expected
spend for 41 cases is about $5–20.
- Only ACPX Claude Sonnet 5 has a committed allocation, as in the old
variable. A new controlled model needs a committed price entry and
allocation.

## Model Used

Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool
use and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 10:08:10 -06:00

535 lines
30 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import test from "node:test";
const repositoryRoot = resolve(import.meta.dirname, "../../..");
const workflowPath = resolve(
repositoryRoot,
".github/workflows/runner-protocol-live-evals.yml",
);
const trustedPrWorkflowPath = resolve(
repositoryRoot,
".github/workflows/pr-trusted.yml",
);
test("Grok subscription credentials require explicit catalog selection and observed auth evidence", async () => {
const workflow = await readFile(workflowPath, "utf8");
const paid = workflow.slice(workflow.indexOf(" steps: &direct_eval_steps"), workflow.indexOf(" eval_shard_1:"));
assert.match(workflow, /grok_authentication:\n[\s\S]*?type: choice\n[\s\S]*?default: api_key/u);
assert.match(workflow, /--grok-authentication "\$GROK_AUTHENTICATION"/u);
assert.ok(paid.includes("PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET: ${{ matrix.credentialName == 'PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET' && secrets.GROK_AUTH_JSON || '' }}"));
assert.ok(paid.includes("XAI_API_KEY: ${{ matrix.credentialName == 'XAI_API_KEY' && secrets.XAI_API_KEY || '' }}"));
assert.match(paid, /--summary-path cell-output\/roster-summary\.json/u);
assert.match(paid, /JSON\.parse\(readFileSync\("cell-output\/roster-summary\.json", "utf8"\)\)\.authenticationMode/u);
assert.match(paid, /authenticationMode !== expected/u);
for (const job of [workflow.slice(0, workflow.indexOf(" eval_shard_0:")), workflow.slice(workflow.indexOf(" report:"))]) {
assert.doesNotMatch(job, /secrets\.GROK_AUTH_JSON/u);
}
});
test("direct live eval workflow keeps paid execution behind stable actor authorization", async () => {
const workflow = await readFile(workflowPath, "utf8");
assert.match(workflow, /^\s{2}authorize:/mu);
assert.match(workflow, /RUNNER_E2E_ALLOWED_ACTOR_IDS/u);
assert.match(workflow, /github\.actor_id/u);
assert.match(workflow, /github\.triggering_actor/u);
assert.match(workflow, /refs\/heads\/\$DEFAULT_BRANCH/u);
assert.match(workflow, /Reauthorize paid execution before provider access/u);
const copilotCredential = "COPILOT_GITHUB_TOKEN: ${{ matrix.credentialName == 'COPILOT_GITHUB_TOKEN' && secrets.COPILOT_GITHUB_TOKEN || '' }}";
assert.equal(workflow.split(copilotCredential).length - 1, 1);
const paid = workflow.slice(workflow.indexOf(" eval_shard_0:"), workflow.indexOf(" eval_shard_1:"));
assert.ok(paid.includes("'runner-e2e-private-paid' || 'runner-e2e-paid'"));
assert.ok(paid.includes(copilotCredential));
const reauthorizationIndex = paid.indexOf("Reauthorize paid execution before provider access");
assert.ok(reauthorizationIndex >= 0);
assert.ok(reauthorizationIndex < paid.indexOf(copilotCredential));
assert.match(
workflow,
/Reauthorize paid execution before provider access[\s\S]*actions\/checkout@[0-9a-f]{40}[\s\S]*Run one immutable direct protocol cell/u,
);
assert.doesNotMatch(
workflow,
/^\s{2}(?:pull_request|pull_request_target|push|workflow_call|workflow_run):/mu,
);
const actions = [
...workflow.matchAll(/^\s*(?:-\s*)?uses:\s*([^\s#]+)/gmu),
].map((match) => match[1]);
assert.ok(actions.length > 0);
for (const action of actions) assert.match(action, /^[^@]+@[0-9a-f]{40}$/u);
});
test("pull request CI builds the canonical Evalbook viewer", async () => {
const workflow = await readFile(trustedPrWorkflowPath, "utf8");
const buildJob = workflow.slice(
workflow.indexOf(" build:"),
workflow.indexOf(" verify_serialized_server:"),
);
assert.match(
buildJob,
/name: Build Runner Evalbook viewer[\s\S]*pnpm --filter @paperclipai\/paperclip-runner build:issue-thread/u,
);
});
test("resolves both repositories immutably and bounds total matrix concurrency", async () => {
const workflow = await readFile(workflowPath, "utf8");
const targetLock = workflow.slice(
workflow.indexOf(" target_lock:"),
workflow.indexOf(" catalog:"),
);
assert.match(targetLock, /ref: \$\{\{ needs\.authorize\.outputs\.target_sha \}\}/u);
assert.match(targetLock, /pnpm install --ignore-scripts --no-frozen-lockfile --lockfile-only/u);
assert.match(targetLock, /Upload resolved target lockfile/u);
const build = workflow.slice(
workflow.indexOf(" build_runner:"),
workflow.indexOf(" eval_shard_0:"),
);
assert.match(build, /Download resolved target lockfile/u);
assert.match(build, /Restore resolved target lockfile/u);
const authorize = workflow.slice(
workflow.indexOf(" authorize:"),
workflow.indexOf(" catalog:"),
);
assert.match(authorize, /repos\/\$REPOSITORY\/branches\/\$encoded_branch/u);
assert.match(authorize, /\^\[0-9a-f\]\{40\}\$/u);
assert.match(
authorize,
/repos\/paperclipai\/paperclip-evals\/commits\/\$EVALS_SHA/u,
);
assert.match(authorize, /COMMITPERCLIP_KEY/u);
assert.match(authorize, /GH_REPO: paperclipai\/paperclip-evals/u);
assert.match(
authorize,
/GH_TOKEN: \$\{\{ steps\.evals_token\.outputs\.value \}\}/u,
);
assert.match(authorize, /test "\$resolved" = "\$EVALS_SHA"/u);
const catalog = workflow.slice(
workflow.indexOf(" catalog:"),
workflow.indexOf(" build_runner:"),
);
assert.match(
catalog,
/ref: \$\{\{ needs\.authorize\.outputs\.evals_sha \}\}/u,
);
assert.match(catalog, /RUNNER_E2E_MAX_PARALLEL/u);
assert.match(catalog, /max_parallel_per_shard/u);
const privateCheckouts = [
...workflow.matchAll(
/repository: paperclipai\/paperclip-evals[\s\S]*?persist-credentials: false/gmu,
),
];
assert.equal(privateCheckouts.length, 4);
const privateTokenSteps = [
...workflow.matchAll(
/^ - name: Generate private eval-repository token\n(?<body>(?:^ {8,}.*\n?)*)/gmu,
),
];
assert.equal(privateTokenSteps.length, 5);
for (const tokenStep of privateTokenSteps) {
assert.match(
tokenStep.groups.body,
/^ {10}GH_REPO: paperclipai\/paperclip-evals$/mu,
"every private-eval token must be minted from the eval repository installation",
);
}
for (const checkout of privateCheckouts) {
assert.match(
checkout[0],
/token: \$\{\{ steps\.evals_token\.outputs\.value \}\}/u,
);
}
assert.match(workflow, /matrix_0/u);
assert.match(workflow, /matrix_1/u);
assert.match(
workflow,
/pnpm --filter @paperclipai\/paperclip-runner deploy --prod/u,
);
assert.match(
workflow,
/runner_cli=runner-protocol-build\/extracted\/portable\/dist\/cli\/eval-session\.js/u,
);
assert.doesNotMatch(workflow, /npm install --prefix/u);
});
test("publishes only the separately sanitized Evalbook through trusted OIDC code", async () => {
const workflow = await readFile(workflowPath, "utf8");
const report = workflow.slice(
workflow.indexOf(" report:"),
workflow.indexOf(" publish_history:"),
);
assert.match(
report,
/Render the access-controlled canonical Evalbook report/u,
);
assert.match(
report,
/--viewer-root runner-protocol-build\/extracted\/dist-issue-thread/u,
);
assert.match(report, /runner-protocol-eval-campaign\.mjs sanitize/u);
assert.match(
report,
/Upload legacy canonical Evalbook and raw attempts/u,
);
assert.match(report, /Upload publisher-only sanitized Evalbook/u);
assert.match(
report,
/verify-runner-evalbook-viewer\.mjs --report-root runner-protocol-merged\/public-report/u,
);
assert.match(
report,
/verify-runner-evalbook-viewer\.mjs --report-root runner-protocol-merged\/report/u,
);
assert.match(
report,
/--viewer-root runner-protocol-build\/extracted\/dist-issue-thread\s*\\\n\s*--public-viewer/u,
);
assert.equal([...report.matchAll(/--viewer-root /gu)].length, 2);
const publisher = workflow.slice(workflow.indexOf(" publish_history:"));
assert.match(publisher, /ref: \$\{\{ github\.sha \}\}/u);
assert.match(publisher, /id-token: write/u);
assert.match(publisher, /runner-protocol-eval-public-/u);
assert.match(publisher, /publish-runner-protocol-eval-history\.mjs/u);
assert.match(publisher, /runner-protocol-evals/u);
assert.match(publisher, /runner-protocol-viewer-/u);
assert.match(publisher, /PAPERCLIP_RUNNER_PROTOCOL_EVAL_VIEWER_DIR/u);
assert.match(publisher, /url: \$\{\{ steps\.publish\.outputs\.report_url \}\}/u);
assert.match(publisher, /Publish versioned report and refresh the root index\n\s+id: publish/u);
assert.doesNotMatch(publisher, /(?:OPENAI|ANTHROPIC|OPENROUTER)_API_KEY/u);
assert.doesNotMatch(publisher, /paperclipai\/paperclip-evals/u);
assert.doesNotMatch(publisher, /downloaded-runner-protocol-evals/u);
});
test("provisions the Codex userns profile before any provider credentials", async () => {
const workflow = await readFile(workflowPath, "utf8");
const direct = workflow.slice(
workflow.indexOf(" steps: &direct_eval_steps"),
workflow.indexOf(" eval_shard_1:"),
);
const profile = direct.indexOf("Provision Codex sandbox on the disposable trusted runner");
const credentials = direct.indexOf("Prepare short-lived AgentCore web identity");
assert.ok(profile >= 0 && credentials >= 0 && profile < credentials);
assert.match(direct, /apparmor_restrict_unprivileged_userns/u);
assert.match(direct, /apparmor_parser/u);
assert.match(direct, /userns,/u);
assert.match(direct, /runner-protocol-build\/extracted\/portable/u);
assert.match(direct, /matrix\.provider == 'codex'/u);
assert.match(direct, /matrix\.rosterId == 'protocol-live-acpx-codex-control'/u);
assert.doesNotMatch(direct, /matrix\.profileId/u);
const build = workflow.slice(
workflow.indexOf(" build_runner:"),
workflow.indexOf(" eval_shard_0:"),
);
assert.match(build, /Materialize the pinned OpenCode executable before packaging/u);
assert.match(build, /materialize-opencode-binary\.mjs/u);
});
test("report preparation stays on the trusted lock and install mode", async () => {
const workflow = await readFile(workflowPath, "utf8");
const report = workflow.slice(
workflow.indexOf(" report:"),
workflow.indexOf(" publish_history:"),
);
assert.match(report, /ref: \$\{\{ github\.sha \}\}/u);
assert.doesNotMatch(report, /Download resolved target lockfile/u);
assert.doesNotMatch(report, /Restore resolved target lockfile/u);
assert.doesNotMatch(report, /--no-frozen-lockfile|cache: pnpm/u);
assert.match(report, /pnpm install --frozen-lockfile --ignore-scripts --store-dir "\$RUNNER_TEMP\/runner-report-pnpm-store"/u);
});
test("trusted catalog, direct eval, and report orchestration stay on workflow revision", async () => {
const workflow = await readFile(workflowPath, "utf8");
for (const [job, next] of [
[" catalog:", " build_runner:"],
[" eval_shard_0:", " eval_shard_1:"],
[" report:", " publish_history:"],
]) {
const section = workflow.slice(workflow.indexOf(job), workflow.indexOf(next));
assert.match(section, /ref: \$\{\{ github\.sha \}\}/u, `${job} must use the trusted workflow revision`);
assert.doesNotMatch(section, /ref: \$\{\{ needs\.authorize\.outputs\.target_sha \}\}/u, `${job} must not execute target orchestration code`);
}
});
test("authentication failures retain cell metadata without leaking malformed summary content", async () => {
const workflow = await readFile(workflowPath, "utf8");
const script = workflow.match(/CELL_EXIT_CODE="\$status" node --input-type=module <<'NODE'\n([\s\S]*?) NODE/u)?.[1];
assert.ok(script);
const root = await mkdtemp(resolve(tmpdir(), "grok-cell-evidence-"));
try {
await mkdir(resolve(root, "cell-output"));
const summary = resolve(root, "cell-output/roster-summary.json");
for (const [content, expectedFailure, expectedMode] of [
[undefined, "grok_authentication_evidence_unreadable", undefined],
["{SENSITIVE_SENTINEL", "grok_authentication_evidence_unreadable", undefined],
["null", "grok_authentication_evidence_unreadable", undefined],
['{"authenticationMode":"SENSITIVE_SENTINEL"}', "grok_authentication_evidence_mismatch", undefined],
['{"authenticationMode":"api_key"}', "grok_authentication_evidence_mismatch", "api_key"],
['{"authenticationMode":"subscription"}', undefined, "subscription"],
]) {
if (content === undefined) await rm(summary, { force: true });
else await writeFile(summary, content);
const result = spawnSync(process.execPath, ["--input-type=module", "-e", script], {
cwd: root, encoding: "utf8",
env: { CREDENTIAL_NAME: "PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET", CELL_ID: "cell-1", CASE_ID: "context", ROSTER_FILE: "grok.json", CELL_EXIT_CODE: "7" },
});
assert.equal(result.status, expectedFailure ? 1 : 0);
const retained = await readFile(resolve(root, "cell-output/cell.json"), "utf8");
const metadata = JSON.parse(retained);
assert.equal(metadata.cellId, "cell-1");
assert.equal(metadata.caseId, "context");
assert.equal(metadata.exitCode, 7);
assert.equal(metadata.authenticationEvidenceFailure, expectedFailure);
assert.equal(metadata.authenticationMode, expectedMode);
assert.doesNotMatch(retained + result.stdout + result.stderr, /SENSITIVE_SENTINEL/u);
}
} finally {
await rm(root, { recursive: true, force: true });
}
});
function workflowShell(workflow, name) {
const step = workflow.slice(workflow.indexOf(` - name: ${name}\n`)).split(/\n - /u)[0];
assert.ok(step.startsWith(` - name: ${name}\n`));
const script = step.split(' run: |\n')[1];
assert.ok(script);
return script.split('\n').map(line => line.slice(10)).join('\n');
}
test('controlled admission rejects automatic retries and job reruns without a hand-maintained policy', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow, 'Validate accounting mode and retry envelope');
const root = await mkdtemp(resolve(tmpdir(), 'runner-workflow-admission-'));
try {
// The retired policy variable is ignored whether it is absent or still set.
for (const [mode, event, attempt, retries, policy, status] of [
['controlled', 'workflow_dispatch', '1', '0', '', 0],
['controlled', 'workflow_dispatch', '2', '0', JSON.stringify({schema:'paperclip.runner-eval.hosted-policy/v1',controllerLockSha256:'e'.repeat(64)}), 1],
['controlled', 'workflow_dispatch', '1', '1', '', 1],
// Schedules are forced to controlled mode with zero retries, so requested legacy retries cannot apply.
['legacy', 'schedule', '1', '1', '', 0],
['controlled', 'schedule', '2', '0', '', 1],
['controlled', 'workflow_dispatch', '1', '0', JSON.stringify({schema:'paperclip.runner-eval.hosted-policy/v1',controllerLockSha256:'e'.repeat(64)}), 0],
['controlled', 'workflow_dispatch', '1', '0', 'not json', 0],
['legacy', 'workflow_dispatch', '1', '1', '', 0],
]) {
const result = spawnSync('bash', ['-c', script], { encoding: 'utf8', env: { PATH: process.env.PATH,
ACCOUNTING_MODE: mode, EVENT_NAME: event, GITHUB_RUN_ATTEMPT: attempt, RETRIES: retries,
CONTROLLER_POLICY_JSON: policy, TARGET_SHA: 'a'.repeat(40), GITHUB_SHA: 'a'.repeat(40), GITHUB_OUTPUT: resolve(root, 'output') } });
assert.equal(result.status, status, `${mode}/${event}/${attempt}/${retries}: ${result.stderr}`);
}
} finally { await rm(root, { recursive: true, force: true }); }
});
test('controlled workers keep the required GitHub-hosted origin even when the legacy AWS fleet is enabled', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow.slice(0, workflow.indexOf('\n target_lock:')), 'Select paid test runner');
const root = await mkdtemp(resolve(tmpdir(), 'runner-controlled-origin-'));
try {
for (const [mode, aws, runner] of [['controlled', 'true', 'ubuntu-latest'], ['controlled', 'false', 'ubuntu-latest'],
['legacy', 'true', 'runs-on/fleet=paperclip-public-pr-x64/env=public-ci'], ['legacy', 'false', 'ubuntu-latest']]) {
const output = resolve(root, `${mode}-${aws}`);
const result = spawnSync('bash', ['-euc', script], { encoding: 'utf8', env: { PATH: process.env.PATH,
ACCOUNTING_MODE: mode, AWS_PAID_RUNNER_ENABLED: aws, GITHUB_OUTPUT: output } });
assert.equal(result.status, 0, result.stderr);
assert.match(await readFile(output, 'utf8'), new RegExp(`^runner=${runner}$`, 'm'));
}
} finally { await rm(root, { recursive: true, force: true }); }
});
test('requested parallelism respects both configured capacity and the selected runner cap', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow, 'Build the two bounded roster-plus-case matrices');
const prefix = script.slice(0, script.indexOf('node packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs'));
assert.ok(prefix.includes('REQUESTED_MAX_PARALLEL'));
for (const [configured, limit, requested, expected] of [['100', '57', '2', '2'], ['100', '57', '', '57'],
['100', '100', '', '100'], ['8', '57', '16', null], ['100', '57', '58', null], ['100', '57', '1', null]]) {
const result = spawnSync('bash', ['-euc', `${prefix}\nprintf '%s' "$MAX_PARALLEL"`], { encoding: 'utf8', env: {
PATH: process.env.PATH, MAX_PARALLEL: configured, MAX_PARALLEL_LIMIT: limit, REQUESTED_MAX_PARALLEL: requested } });
assert.equal(result.status === 0, expected !== null, `${configured}/${limit}/${requested}: ${result.stderr}`);
if (expected !== null) assert.equal(result.stdout, expected);
}
});
test('the provider uses an owned same-byte Node before credentials without changing shared host permissions', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const paid = workflow.slice(workflow.indexOf(' steps: &direct_eval_steps'), workflow.indexOf(' eval_shard_1:'));
const staging = paid.indexOf('Stage an owned provider Node before credentials');
assert.ok(staging > paid.indexOf('Verify and extract portable runner'));
assert.ok(staging < paid.indexOf('Obtain private archive credentials for transfer steps only'));
assert.ok(staging < paid.indexOf('Prepare short-lived AgentCore web identity'));
assert.ok(staging < paid.indexOf('Run one immutable direct protocol cell'));
const command = workflowShell(paid, 'Stage an owned provider Node before credentials');
assert.match(command, /node packages\/paperclip-runner\/scripts\/stage-runner-provider-node\.mjs/u);
assert.match(command, /"\$RUNNER_TEMP\/runner-protocol-provider-node"/u);
assert.match(command, /portable\/dist\/cli\/acpx-runtime-sidecar\.cjs/u);
assert.match(command, /cell-output\/provider-node-receipt\.json/u);
assert.doesNotMatch(command, /secrets\.|\bsudo\b|\bchmod\b|hostedtoolcache/u);
});
test('TestOps admission preserves the selected mode gates, current source and dedicated roster', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow, 'Validate accounting mode and retry envelope');
const root = await mkdtemp(resolve(tmpdir(), 'runner-testops-admission-'));
try {
for (const [mode, source, roster, policy, retries, expected] of [
['legacy', 'a', 'protocol-live-testops-claude', true, '0', 0],
['controlled', 'a', 'protocol-live-testops-claude', true, '0', 0],
['controlled', 'a', 'protocol-live-testops-claude', false, '0', 0],
['controlled', 'a', 'protocol-live-testops-claude', true, '1', 1],
['controlled', 'b', 'protocol-live-testops-claude', true, '0', 1],
['legacy', 'b', 'protocol-live-testops-claude', true, '0', 1],
['legacy', 'a', 'all', true, '0', 1],
]) {
const result = spawnSync('bash', ['-c', script], { encoding: 'utf8', env: { PATH: process.env.PATH,
ACCOUNTING_MODE: mode, EVENT_NAME: 'workflow_dispatch', GITHUB_RUN_ATTEMPT: '1', RETRIES: retries,
TESTOPS_REQUESTED: 'true', REQUESTED_ROSTERS: roster,
CONTROLLER_POLICY_JSON: policy ? JSON.stringify({schema:'paperclip.runner-eval.hosted-policy/v1',controllerLockSha256:'e'.repeat(64)}) : '',
TARGET_SHA: source.repeat(40), GITHUB_SHA: 'a'.repeat(40), GITHUB_OUTPUT: resolve(root, 'output') } });
assert.equal(result.status, expected, `${mode}/${source}/${roster}: ${result.stderr}`);
}
} finally { await rm(root, { recursive: true, force: true }); }
});
test('TestOps receives only a closed projection created inside the report boundary', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const report = workflow.slice(workflow.indexOf(' report:'), workflow.indexOf(' testops_report:'));
const publisher = workflow.slice(workflow.indexOf(' testops_report:'), workflow.indexOf(' publish_history:'));
const publicationCondition = publisher.match(/^ if: (.+)$/mu)?.[1];
assert.ok(publicationCondition);
const admits = (cancelled, enabled = true, selected = true) => Function('always', 'cancelled', 'needs',
`return ${publicationCondition}`)(() => true, () => cancelled,
{ authorize: { outputs: { testops_enabled: String(enabled) } }, testops_selection: { result: selected ? 'success' : 'failure' } });
assert.equal(admits(false), true);
assert.equal(admits(true), false);
assert.equal(admits(false, false), false);
assert.equal(admits(false, true, false), false);
assert.match(report, /environment:.*runner-e2e-private-history/u);
assert.match(report, /Create the closed TestOps projection inside the report boundary[\s\S]*runner-protocol-testops\.mjs export/u);
const publicGrid = report.slice(report.indexOf(' - name: Render the same canonical grid'), report.indexOf(' - name: Create the closed TestOps projection'));
assert.match(publicGrid, /if: needs\.authorize\.outputs\.accounting_mode == 'legacy'/u);
assert.doesNotMatch(publicGrid, /testops_enabled|env -u|unset /u);
const sanitize = report.slice(report.indexOf(' - name: Project safe run records'), report.indexOf(' - name: Render the same canonical grid'));
assert.match(sanitize, /accounting_mode == 'legacy' \|\| needs\.authorize\.outputs\.testops_enabled == 'true'/u);
assert.match(sanitize, /runner-protocol-eval-campaign\.mjs sanitize/u);
const projection = workflowShell(report, 'Create the closed TestOps projection inside the report boundary');
assert.match(projection, /controlled\)[\s\S]*export-runs[\s\S]*campaign\.json runner-protocol-merged\/public-runs/u);
assert.match(projection, /legacy\)[\s\S]*export \\[\s\S]*runner-protocol-merged\/public-report/u);
assert.match(report, /Transfer only the closed TestOps results and receipt[\s\S]*path: runner-protocol-testops-projection\//u);
assert.match(publisher, /name: runner-protocol-testops-projection-/u);
assert.doesNotMatch(publisher, /runner-protocol-eval-public-|runner-protocol-merged|private-history|AWS_ACCESS_KEY_ID|BRAINTRUST_API_KEY/u);
assert.match(publisher, /steps\.projection\.outcome != 'success'/u);
});
test('controlled capacity counts the filtered selection before any provider work', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const filter = workflow.indexOf(' - name: Apply the exact selected protocol cases before fan-out');
const limit = workflow.indexOf(' - name: Enforce controlled limits against the selected catalog');
const upload = workflow.indexOf(' - name: Upload immutable campaign catalog');
assert.ok(filter > 0 && filter < limit && limit < upload);
const script = workflowShell(workflow, 'Enforce controlled limits against the selected catalog');
const cell = { provider: 'acpx', acpxAgent: 'claude', driver: 'acpx_runtime', model: 'claude-sonnet-5' };
const codex = { provider: 'codex', driver: 'codex_app_server', model: 'gpt-6' };
const root = await mkdtemp(resolve(tmpdir(), 'runner-selected-capacity-'));
try {
const defaults = resolve(root, 'packages/paperclip-runner/scripts/controlled-evals/hosted-defaults.json');
await mkdir(resolve(defaults, '..'), { recursive: true });
await writeFile(defaults, await readFile(resolve(repositoryRoot, 'packages/paperclip-runner/scripts/controlled-evals/hosted-defaults.json')));
// Campaign size is the selection itself; the spend cap is enforced during manifest preparation.
for (const [cells, expected] of [[[cell], 0], [Array(41).fill(cell), 0], [Array(512).fill(cell), 0],
[Array(513).fill(cell), 1], [[{ ...cell, provider: 'unsupported' }], 1], [[], 1],
// A supported driver without a committed allocation fails here, not after the builds.
[[cell, codex], 1], [[{ ...cell, model: 'claude-other' }], 1]]) {
await writeFile(resolve(root, 'runner-protocol-eval-catalog.json'), JSON.stringify({ cells }));
const result = spawnSync('bash', ['-euc', script], { cwd: root, encoding: 'utf8', env: { PATH: process.env.PATH,
CONTROLLER_POLICY_JSON: JSON.stringify({ maxCells: 1 }) } });
assert.equal(result.status === 0, expected === 0, result.stderr);
}
} finally { await rm(root, { recursive: true, force: true }); }
});
test('controlled evidence uses private storage and cloud credentials stay out of provider execution', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const paid = workflow.slice(workflow.indexOf(' steps: &direct_eval_steps'), workflow.indexOf(' eval_shard_1:'));
const model = paid.slice(paid.indexOf(' - name: Run one immutable direct protocol cell'), paid.indexOf(' - name: Retain controlled cell evidence'));
assert.ok(paid.indexOf('Verify current worker admission before provider credentials') < paid.indexOf('Run one immutable direct protocol cell'));
assert.ok(paid.indexOf('Provision Codex sandbox') < paid.indexOf('Obtain private archive credentials'));
assert.doesNotMatch(model, /AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN/u);
assert.match(model, /runner_cli="\$root\/runtime\/controlled-evals\/run.mjs"/u);
for (const step of workflow.split(/\n - /u).filter(step => /uses: actions\/upload-artifact/u.test(step))) {
if (/path: (?:cell-output|runner-protocol-merged)\//u.test(step)) assert.match(step, /accounting_mode == 'legacy'/u);
assert.doesNotMatch(step, /path: .*runner-protocol-control\//u);
}
for (const step of workflow.split(/\n - /u).filter(step => /uses: aws-actions\/configure-aws-credentials/u.test(step) && step.includes('id: private_history_credentials'))) {
assert.match(step, /output-env-credentials: false/u);
assert.match(step, /disable-retry: true/u);
}
assert.match(workflow, /upload protocol-control/u);
assert.match(workflow, /upload "protocol-cell-\$CELL_ID"/u);
assert.match(workflow, /upload protocol-report/u);
assert.match(workflow, /--controller-manifest "\$PAPERCLIP_RUNNER_EVAL_CONTROL_MANIFEST"/u);
});
test('Braintrust-controlled workers do not require an unused direct provider credential', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow, 'Run one immutable direct protocol cell');
const credentialCheck = script.slice(script.indexOf('if [ "$ACCOUNTING_MODE"'), script.indexOf('if [ "$PROVIDER"'));
assert.ok(credentialCheck.includes('test -n "${!CREDENTIAL_NAME:-}"'));
for (const [mode, credential, status] of [['controlled', '', 0], ['legacy', '', 1], ['legacy', 'synthetic-only', 0]]) {
const result = spawnSync('bash', ['-euc', credentialCheck], { encoding: 'utf8', env: {
ACCOUNTING_MODE: mode, CREDENTIAL_NAME: 'OPENAI_API_KEY', OPENAI_API_KEY: credential,
} });
assert.equal(result.status, status, result.stderr);
}
});
test('private archive jobs consume verified controller code without resolving dependencies', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const builder = workflow.slice(workflow.indexOf(' build_controller:'), workflow.indexOf(' prepare_controller:'));
const preparer = workflow.slice(workflow.indexOf(' prepare_controller:'), workflow.indexOf(' eval_shard_0:'));
assert.doesNotMatch(builder, /id-token: write|environment:|secrets\./u);
assert.match(builder, /build-runtime.mjs/u);
assert.doesNotMatch(builder, /--no-frozen-lockfile/u);
assert.doesNotMatch(builder, /target_lock|RESOLVED_LOCK_SHA256|runner-controller-lock/u);
assert.match(builder, /git diff --quiet HEAD -- pnpm-lock\.yaml/u);
assert.match(preparer, /committed="\$\(sha256sum pnpm-lock\.yaml/u);
assert.match(preparer, /PAPERCLIP_RUNNER_EVAL_CONTROLLER_LOCK_SHA256: \$\{\{ steps\.controller_lock\.outputs\.sha256 \}\}/u);
// Defaults come from the verified controller runtime, not the workspace that evals code can modify.
assert.doesNotMatch(preparer, /hosted-defaults\.json/u);
assert.doesNotMatch(workflow, /RUNNER_PROTOCOL_EVAL_CONTROLLER_POLICY_JSON/u);
assert.match(builder, /pnpm install --frozen-lockfile --ignore-scripts --store-dir/u);
assert.doesNotMatch(preparer, /pnpm install|build-runtime.mjs|cache: pnpm/u);
assert.ok(preparer.indexOf('verify-runtime.mjs') < preparer.indexOf('hosted.mjs" prepare'));
assert.match(preparer, /needs\.build_controller\.outputs\.runtime_receipt_sha256/u);
for (const job of workflow.split(/\n(?= [A-Za-z0-9_]+:)/u).filter(value => value.includes('id-token: write'))) {
assert.doesNotMatch(job, /--no-frozen-lockfile|cache: pnpm|actions\/cache/u);
for (const install of job.matchAll(/pnpm install[^\n]*/gu)) {
assert.match(install[0], /--frozen-lockfile/u);
assert.match(install[0], /--ignore-scripts/u);
assert.match(install[0], /--store-dir/u);
}
}
});
test('controller dependency lock rejects a modified committed lockfile before installation', async () => {
const workflow = await readFile(workflowPath, 'utf8');
const script = workflowShell(workflow, 'Build portable controller in an unprivileged job');
const guard = script.slice(0, script.indexOf('pnpm install'));
const root = await mkdtemp(resolve(tmpdir(), 'runner-committed-lock-'));
const git = (...args) => spawnSync('git', args, { cwd: root, encoding: 'utf8' });
try {
git('init', '-q'); git('config', 'user.email', 'test@example.com'); git('config', 'user.name', 'test');
await writeFile(resolve(root, 'pnpm-lock.yaml'), 'committed dependencies');
git('add', 'pnpm-lock.yaml'); git('commit', '-qm', 'lock');
const head = git('rev-parse', 'HEAD').stdout.trim();
const run = () => spawnSync('bash', ['-euc', guard], { cwd: root, encoding: 'utf8', env: { PATH: process.env.PATH, GITHUB_SHA: head } });
assert.equal(run().status, 0, run().stderr);
await writeFile(resolve(root, 'pnpm-lock.yaml'), 'changed dependencies');
assert.notEqual(run().status, 0);
} finally { await rm(root, { recursive: true, force: true }); }
});