diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index 88cf863aa6..cff873d317 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -28,7 +28,7 @@ COPY cli/package.json ./cli/package.json # The complete resolved lock (including transitive integrity hashes) is reviewed. # Reject registry-time drift BEFORE installing packages or running lifecycle code. # Refresh this digest together with source/provider dependency changes. -ARG PAPERCLIP_RUNNER_LOCK_SHA256=d7d96cf0d98cf0946f6195e29ba173b03711a947a1c38f312b67cda56c254c22 +ARG PAPERCLIP_RUNNER_LOCK_SHA256=4b796c312833ebf2be4c38228babc0292c76774fb40d43bd18b54bd6b205753d RUN pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile \ && printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \ && sha256sum -c /tmp/provider-lock.sha256 \ diff --git a/server/src/__tests__/workspace-runtime-exposure-reservation.test.ts b/server/src/__tests__/workspace-runtime-exposure-reservation.test.ts index 09fcf23ed7..08615ec352 100644 --- a/server/src/__tests__/workspace-runtime-exposure-reservation.test.ts +++ b/server/src/__tests__/workspace-runtime-exposure-reservation.test.ts @@ -269,9 +269,14 @@ const DECLARED_EXPOSE = { * loopback, matching the real managed lane. Readiness then has a real listener * to probe, so the lifecycle runs to `ready` exactly as in production. */ +// Use the test runner's executable. A login shell can resolve bare `node` to +// another installation. Keep startup facts in the service log so a readiness +// failure identifies which executable started and which ports it bound. +const guestNode = `'${process.execPath.replace(/'/g, "'\\''")}'`; const GUEST_COMMAND = - "node -e \"const http=require('node:http');const p=Number(process.env.PORT);" - + "for(const q of [p,p+10000])http.createServer((_,r)=>{r.statusCode=200;r.end('ok')}).listen(q,'127.0.0.1');" + `${guestNode} -e "const http=require('node:http');const p=Number(process.env.PORT);` + + "console.log('guest-start',process.execPath,p);" + + "for(const q of [p,p+10000])http.createServer((_,r)=>{r.statusCode=200;r.end('ok')}).listen(q,'127.0.0.1',()=>console.log('guest-listening',q));" + "setInterval(()=>{},1000)\""; (embeddedPostgresSupport.supported ? describe : describe.skip)( diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index 15b642759e..cbe7695cc7 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -325,6 +325,48 @@ pullable, includes the provider pack, and advertises `dial_ws_loopback`, the image job deliberately fails its anonymous-pull check otherwise. Existing content tags are never rebuilt or overwritten by the workflow. +### Match the local controller package to the Daytona image + +Native ACPX (including Claude) and OpenCode Daytona cells also require +`PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` on the controller. The package and +the image must come from the same verified build. Equal provider version numbers +are insufficient: verification compares the complete manifest, source revision, +Node executable, lockfile, and built bridge hashes. An independently rebuilt +package can fail that comparison and trigger a large upload before any model +work begins. + +Prefer the hosted workflow: it builds the image and controller package together, +and uses the image's recorded source revision when reusing an image. For a local +run, use the immutable image from the campaign for the code under test and copy +its exact package. Do not copy credentials or change manifest fields to force a +match. Docker must be running; the temporary container below is never started. + +```sh +( + set -eu + : "${PAPERCLIP_E2E_DAYTONA_IMAGE:?Set the verified immutable image digest}" + case "$PAPERCLIP_E2E_DAYTONA_IMAGE" in + *@sha256:*) ;; + *) echo "Use an immutable image digest" >&2; exit 1 ;; + esac + docker pull --platform linux/amd64 "$PAPERCLIP_E2E_DAYTONA_IMAGE" + pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/paperclip-e2e-provider-pack.XXXXXX")" + container_id="$(docker create --platform linux/amd64 --network none \ + --entrypoint /bin/true "$PAPERCLIP_E2E_DAYTONA_IMAGE")" + trap 'docker rm "$container_id" >/dev/null' EXIT + docker cp "$container_id:/opt/paperclip-runner/provider-pack/." "$pack_dir/" + test -f "$pack_dir/provider-pack.json" + printf 'Set PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH to: %s\n' "$pack_dir" +) +``` + +Export the printed path in the shell that launches the eval. Runtime verification +still checks all package artifacts. The run log must show +`using manifest-matched provider pack from the sandbox image`; after reuse it can +instead show `reusing manifest-matched provider pack from the workspace`. A setup failure before provider +execution does not measure Claude recovery. Keep cold-upload coverage separate +from the recovery test, and retain mismatched or failed attempts as evidence. + ## Evidence and cleanup Packaged, access-controlled evidence is written beneath