diff --git a/tests/runner-e2e/harness-env.ts b/tests/runner-e2e/harness-env.ts index 3264ba7bfa..b8ac040e08 100644 --- a/tests/runner-e2e/harness-env.ts +++ b/tests/runner-e2e/harness-env.ts @@ -26,6 +26,21 @@ const AMBIENT_EXTERNAL_STATE_KEYS = [ ] as const; const PROVIDER_SECRET_KEY = /^(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA)(?:_|$)/; +export function runnerE2EServerControlPaths(temporaryRoot: string) { + const controlDirectory = path.join(temporaryRoot, "control"); + return { + controlDirectory, + restartRequestPath: path.join( + controlDirectory, + "server-restart.request.json", + ), + restartAcknowledgementPath: path.join( + controlDirectory, + "server-restart.ack.json", + ), + }; +} + /** * Local native cells use the debug binary produced by build:runner-binaries. * Preserve an explicit override for release builds and developer workflows. diff --git a/tests/runner-e2e/runner.spec.ts b/tests/runner-e2e/runner.spec.ts index 5a0ad01cbe..b1fee74603 100644 --- a/tests/runner-e2e/runner.spec.ts +++ b/tests/runner-e2e/runner.spec.ts @@ -6,6 +6,7 @@ import { RunnerApi, pollUntil } from "./api.js"; import { buildRuntimeUsage, summarizeExecutionBilling } from "./billing.js"; import { runnerExecutionById } from "./catalog.js"; import { classifyFailure } from "./failure-classifier.js"; +import { runnerE2EServerControlPaths } from "./harness-env.js"; import { setupLiveFixtures, type LiveFixtureValues } from "./live-fixtures.js"; import { evaluateMatcher, type MatcherResult } from "./matchers.js"; import { @@ -166,15 +167,11 @@ async function restartIsolatedPaperclipServer(input: { requestId: string; deadlineAt: number; }): Promise { - const controlDirectory = path.join(privateRoot!, "control"); - const requestPath = path.join( + const { controlDirectory, - "server-restart.request.json", - ); - const acknowledgementPath = path.join( - controlDirectory, - "server-restart.ack.json", - ); + restartRequestPath: requestPath, + restartAcknowledgementPath: acknowledgementPath, + } = runnerE2EServerControlPaths(temporaryRoot!); await mkdir(controlDirectory, { recursive: true }); const temporaryRequestPath = `${requestPath}.${process.pid}.${input.requestId}.tmp`; await writeFile( @@ -237,10 +234,11 @@ const executionIds = (() => { })(); const executions = executionIds.map(runnerExecutionById); const attempt = Number(process.env.PAPERCLIP_RUNNER_E2E_ATTEMPT ?? "1"); +const temporaryRoot = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT; const privateRoot = process.env.PAPERCLIP_RUNNER_E2E_PRIVATE_DIR; const workspacePath = process.env.PAPERCLIP_RUNNER_E2E_WORKSPACE; -if (!privateRoot || !workspacePath) - throw new Error("Runner E2E private/workspace paths are required"); +if (!temporaryRoot || !privateRoot || !workspacePath) + throw new Error("Runner E2E temporary/private/workspace paths are required"); function record(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) @@ -1424,8 +1422,7 @@ for (const execution of executions) { )?.[1] ?? /Using fallback workspace "([^"]+)"/.exec(runLogContent)?.[1]; const cwd = String(workspaceContext.cwd ?? fallbackWorkspace ?? ""); - const isolatedRoot = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT ?? ""; - if (!isolatedRoot || !cwd.startsWith(`${isolatedRoot}/`)) { + if (!cwd.startsWith(`${temporaryRoot}/`)) { invariantFailures.push( `local run workspace escaped the isolated root: ${cwd}`, ); diff --git a/tests/runner-e2e/server.ts b/tests/runner-e2e/server.ts index bdf286b771..7de94a4874 100644 --- a/tests/runner-e2e/server.ts +++ b/tests/runner-e2e/server.ts @@ -5,6 +5,7 @@ import path from "node:path"; import { assertIsolatedServerEnvironment, buildPaperclipServerEnvironment, + runnerE2EServerControlPaths, } from "./harness-env.js"; function required(name: string) { @@ -21,12 +22,11 @@ const port = required("PAPERCLIP_RUNNER_E2E_PORT"); const repositoryRoot = path.resolve(import.meta.dirname, "../.."); const tsxCli = path.join(repositoryRoot, "cli/node_modules/tsx/dist/cli.mjs"); const paperclipCli = path.join(repositoryRoot, "cli/src/index.ts"); -const controlDirectory = path.join(temporaryRoot, "control"); -const restartRequestPath = path.join( +const { controlDirectory, - "server-restart.request.json", -); -const restartAckPath = path.join(controlDirectory, "server-restart.ack.json"); + restartRequestPath, + restartAcknowledgementPath: restartAckPath, +} = runnerE2EServerControlPaths(temporaryRoot); const restartTimeoutMs = 180_000; const gracefulStopTimeoutMs = 30_000; const serverEnvironment = buildPaperclipServerEnvironment(process.env, { diff --git a/tests/runner-e2e/support.test.ts b/tests/runner-e2e/support.test.ts index 015a7c83c7..9bbcb7d643 100644 --- a/tests/runner-e2e/support.test.ts +++ b/tests/runner-e2e/support.test.ts @@ -11,6 +11,7 @@ import { assertIsolatedServerEnvironment, buildPaperclipServerEnvironment, resolvePaperclipRunnerBinaryForHarness, + runnerE2EServerControlPaths, } from "./harness-env.js"; import { runnerExecutionById } from "./catalog.js"; import { assertEmbeddedDatabaseIsolation } from "./instance-isolation.js"; @@ -354,6 +355,22 @@ describe("runner E2E failure policy", () => { }); describe("runner E2E server isolation", () => { + it("shares restart control files beneath the isolated temporary root", () => { + expect(runnerE2EServerControlPaths("/tmp/cell")).toEqual({ + controlDirectory: path.join("/tmp/cell", "control"), + restartRequestPath: path.join( + "/tmp/cell", + "control", + "server-restart.request.json", + ), + restartAcknowledgementPath: path.join( + "/tmp/cell", + "control", + "server-restart.ack.json", + ), + }); + }); + it("strips database and paid-provider credentials from the Paperclip process", () => { const env = buildPaperclipServerEnvironment( {