test(runner): stage the build-once binary remotely

This commit is contained in:
Dotta 2026-09-03 17:45:10 -05:00
parent 73a146e076
commit 7fe94196ba
5 changed files with 62 additions and 17 deletions

View File

@ -890,9 +890,6 @@ jobs:
OPENROUTER_API_KEY: ${{ matrix.credentialName == 'OPENROUTER_API_KEY' && secrets.OPENROUTER_API_KEY || '' }}
DAYTONA_API_KEY: ${{ matrix.environmentId == 'daytona' && secrets.DAYTONA_API_KEY || '' }}
PAPERCLIP_E2E_DAYTONA_IMAGE: ${{ needs.daytona_image.outputs.image }}
# Bind PRP identity to the exact build-once bytes that the server
# stages into Daytona instead of a separately built image binary.
PAPERCLIP_RUNNER_REMOTE_BINARY_PATH: ${{ github.workspace }}/packages/paperclip-runner/runner/target/debug/paperclip-runnerd
PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH: ${{ github.workspace }}/packages/paperclip-runner/provider-pack
PAPERCLIP_E2E_CAMPAIGN_ID: gha-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.executionId }}
run: pnpm test:e2e:runner -- --id "${{ matrix.executionId }}"

View File

@ -42,7 +42,7 @@ export function runnerE2EServerControlPaths(temporaryRoot: string) {
}
/**
* Local native cells use the debug binary produced by build:runner-binaries.
* Native cells use the debug binary produced once by build:runner-binaries.
* Preserve an explicit override for release builds and developer workflows.
*/
export function resolvePaperclipRunnerBinaryForHarness(
@ -53,11 +53,7 @@ export function resolvePaperclipRunnerBinaryForHarness(
): string | undefined {
if (configuredPath?.trim()) return configuredPath;
if (
!executions.some(
(execution) =>
execution.environment.id === "local" &&
execution.profile.generation === "native",
)
!executions.some((execution) => execution.profile.generation === "native")
) {
return undefined;
}
@ -73,6 +69,24 @@ export function resolvePaperclipRunnerBinaryForHarness(
);
}
/**
* Remote native cells stage the same controller-owned binary whose digest is
* authorized by the PRP control plane. Local cells launch it directly.
*/
export function resolvePaperclipRemoteRunnerBinaryForHarness(
executions: readonly MatrixExecution[],
runnerBinary: string | undefined,
): string | undefined {
if (!runnerBinary) return undefined;
return executions.some(
(execution) =>
execution.profile.generation === "native" &&
execution.environment.expectedExecutionTarget.kind === "remote",
)
? runnerBinary
: undefined;
}
/**
* Build the environment inherited by the Paperclip server. Paid credentials
* deliberately stay in the launcher/Playwright process and cross the server

View File

@ -21,7 +21,10 @@ import { renderRunnerE2EDashboard } from "./dashboard.js";
import { packageEvidence } from "./evidence.js";
import { classifyFailure, shouldRetryFailure } from "./failure-classifier.js";
import { buildRunnerCampaign } from "./history.js";
import { resolvePaperclipRunnerBinaryForHarness } from "./harness-env.js";
import {
resolvePaperclipRemoteRunnerBinaryForHarness,
resolvePaperclipRunnerBinaryForHarness,
} from "./harness-env.js";
import { assertEmbeddedDatabaseIsolation } from "./instance-isolation.js";
import {
assertSecretFree,
@ -395,6 +398,10 @@ async function runAttempt(input: {
betterAuthSecret,
]);
attemptSecrets = credentials;
const runnerBinary = resolvePaperclipRunnerBinaryForHarness(
executions,
repositoryRoot,
);
const childEnv: NodeJS.ProcessEnv = {
...process.env,
PATH: providerPath,
@ -407,10 +414,9 @@ async function runAttempt(input: {
PAPERCLIP_RUNNER_E2E_PRIVATE_DIR: privateDir,
PAPERCLIP_RUNNER_E2E_WORKSPACE: workspace,
PAPERCLIP_RUNNER_E2E_SERVER_LOG: path.join(privateDir, "server.log"),
PAPERCLIP_RUNNER_BINARY: resolvePaperclipRunnerBinaryForHarness(
executions,
repositoryRoot,
),
PAPERCLIP_RUNNER_BINARY: runnerBinary,
PAPERCLIP_RUNNER_REMOTE_BINARY_PATH:
resolvePaperclipRemoteRunnerBinaryForHarness(executions, runnerBinary),
// Vite's optimized dependency cache embeds revision query strings. A
// private per-attempt cache prevents an earlier cell or local rebuild
// from producing `504 Outdated Optimize Dep` during browser bootstrap.

View File

@ -10,6 +10,7 @@ import { classifyFailure, shouldRetryFailure } from "./failure-classifier.js";
import {
assertIsolatedServerEnvironment,
buildPaperclipServerEnvironment,
resolvePaperclipRemoteRunnerBinaryForHarness,
resolvePaperclipRunnerBinaryForHarness,
runnerE2EServerControlPaths,
} from "./harness-env.js";
@ -55,6 +56,9 @@ describe("runner E2E local binary resolution", () => {
const localNativeExecution = runnerExecutionById(
"core-compatibility.runner-codex.local.message-marker",
);
const remoteNativeExecution = runnerExecutionById(
"core-compatibility.runner-acpx-claude.daytona.message-marker",
);
it("uses the debug runner binary built by the E2E workflow", () => {
expect(
@ -82,6 +86,33 @@ describe("runner E2E local binary resolution", () => {
),
).toBe("/custom/paperclip-runnerd");
});
it("uses and stages the same build-once binary for remote native cells", () => {
const runnerBinary = resolvePaperclipRunnerBinaryForHarness(
[remoteNativeExecution],
"/repository",
undefined,
"linux",
);
expect(runnerBinary).toBe(
path.join(
"/repository",
"packages/paperclip-runner/runner/target/debug/paperclip-runnerd",
),
);
expect(
resolvePaperclipRemoteRunnerBinaryForHarness(
[remoteNativeExecution],
runnerBinary,
),
).toBe(runnerBinary);
expect(
resolvePaperclipRemoteRunnerBinaryForHarness(
[localNativeExecution],
runnerBinary,
),
).toBeUndefined();
});
});
describe("runner E2E server port allocation", () => {

View File

@ -376,9 +376,6 @@ describe("public repository paid workflow security", () => {
expect(testJob).toContain(
"test -x packages/paperclip-runner/runner/target/debug/paperclip-runnerd",
);
expect(testJob).toContain(
"PAPERCLIP_RUNNER_REMOTE_BINARY_PATH: ${{ github.workspace }}/packages/paperclip-runner/runner/target/debug/paperclip-runnerd",
);
expect(testJob).toContain(".payload.runnerSourceRevision == $revision");
expect(workflow).toContain("Qualify local provider Node interpreter");
expect(testJob).toContain(