diff --git a/.github/workflows/runner-full-stack-e2e.yml b/.github/workflows/runner-full-stack-e2e.yml index 8ea7b4a56c..577928ae7d 100644 --- a/.github/workflows/runner-full-stack-e2e.yml +++ b/.github/workflows/runner-full-stack-e2e.yml @@ -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 }}" diff --git a/tests/runner-e2e/harness-env.ts b/tests/runner-e2e/harness-env.ts index b8ac040e08..d591e859f6 100644 --- a/tests/runner-e2e/harness-env.ts +++ b/tests/runner-e2e/harness-env.ts @@ -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 diff --git a/tests/runner-e2e/launch.ts b/tests/runner-e2e/launch.ts index 9d4a739f20..d2575b43a7 100644 --- a/tests/runner-e2e/launch.ts +++ b/tests/runner-e2e/launch.ts @@ -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. diff --git a/tests/runner-e2e/support.test.ts b/tests/runner-e2e/support.test.ts index f950318687..c1e1f8a8c5 100644 --- a/tests/runner-e2e/support.test.ts +++ b/tests/runner-e2e/support.test.ts @@ -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", () => { diff --git a/tests/runner-e2e/workflow-security.test.ts b/tests/runner-e2e/workflow-security.test.ts index 3ac05e0093..cf554d4c34 100644 --- a/tests/runner-e2e/workflow-security.test.ts +++ b/tests/runner-e2e/workflow-security.test.ts @@ -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(