fix(runner-e2e): align Daytona image and provider pack provenance (#13814)

## Thinking Path

> - Paperclip is an open source app people use to manage AI agents for
work.
> - The Daytona runner image provides the native runner and its provider
package.
> - The controller also sends a provider package to native Daytona cells
when the image package does not match.
> - A stale image and a package from another source revision caused a
1.8 GB upload before Claude could run.
> - This pull request refreshes the reviewed lock checksum and documents
how to reuse the exact package from an immutable image.
> - The benefit is a reproducible setup path and clear evidence when
image and package provenance do not match.

## Linked Issues or Issue Description

**What happened?**

A Daytona native Claude run used image source revision
`45c99a0d06cbd5b04982b06b79de321149930ac5` with a controller provider
package from revision `294853dc...`. Runtime verification rejected the
image package and staged a large package upload before model execution.
A hosted campaign also failed during image setup because the Dockerfile
expected lock checksum `d7d96cf0...` while the resolved lockfile
checksum was
`4b796c312833ebf2be4c38228babc0292c76774fb40d43bd18b54bd6b205753d`.

Related public work reviewed:
[#12795](https://github.com/paperclipai/paperclip/pull/12795),
[#12862](https://github.com/paperclipai/paperclip/pull/12862), and
[#12887](https://github.com/paperclipai/paperclip/pull/12887).

**Expected behavior**

The Daytona image and controller provider package must come from the
same verified build. A package extracted from the immutable image must
pass the existing manifest, source revision, lockfile, binary, bridge,
and artifact checks before a native Claude run starts.

**Steps to reproduce**

1. Set `PAPERCLIP_E2E_DAYTONA_IMAGE` to the old immutable image digest.
2. Set `PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` to a package built
from a different source revision.
3. Run a native Claude Daytona cell.
4. Observe provider package verification failure followed by the large
staging upload.
5. Build the image with the stale Dockerfile lock checksum and observe
the checksum failure.

**Paperclip version or commit**

`3b8df3dcd6f99e99277faa45f3351989edb5c239`.

**Deployment mode**

Daytona native runner E2E.

**Install method**

Built from source.

**Agent adapter(s) involved**

Claude Code through the native ACPX runner.

**Database mode**

Not database-related.

**Access context**

Not applicable to the setup failure.

## What Changed

- Refreshed `PAPERCLIP_RUNNER_LOCK_SHA256` in
`docker/daytona-runner/Dockerfile` to the resolved lockfile checksum.
- Added a local guide for extracting the provider package from an
immutable verified image with Docker.
- Documented the required provenance checks and the expected
manifest-matched runtime log.
- Documented that cold package upload coverage must remain separate from
recovery coverage.
- Pinned the preview-service test guest to the test runner’s Node
executable and logged guest startup and bound ports for readiness
diagnostics.

## Verification

- 442 focused E2E tests passed.
- Typecheck passed.
- `pnpm build` passed.
- Five provider-pack reuse tests passed.
- Six Daytona image contract tests passed.
- Fixed Daytona campaign
[35740613581](https://github.com/paperclipai/paperclip/actions/runs/35740613581)
passed.
- The overall hiring campaign
[35739993219](https://github.com/paperclipai/paperclip/actions/runs/35739993219)
failed because of an unrelated Mini metadata failure; its Claude cell
passed.
- Full local `pnpm test:run` was attempted but did not complete. The
isolated Postgres install was repaired and its 15-test probe passed.
- After the fixture change, all seven preview reservation tests passed
locally and CI server shard 6/12 passed on `20234f75f`. This removes
login-shell Node resolution variance; the exact cause of the earlier
CI-only timeout is not established.
- CI run
[35766034635](https://github.com/paperclipai/paperclip/actions/runs/35766034635)
passed on `20234f75f`. All server, runner, browser, build, and typecheck
gates passed.
- The signoff browser case initially failed waiting for an approver run.
All five signoff tests passed locally without changes; the one allowed
CI retry passed all 19 shard tests. This is recorded as an intermittent
failure, not a demonstrated product fix.
- Greptile reviewed `20234f75f`: 5/5, no actionable findings.
- [Follow-up
report](https://pages.paperclip.ing/runner-daytona-hiring-20260922/)
includes timings, evidence links, and the remaining hiring configuration
failure.
- Review the immutable image source revision and extracted
`provider-pack.json` before another paid recovery run.

## Risks

- The Dockerfile checksum gate intentionally fails when the resolved
lockfile changes. A future dependency change must refresh the reviewed
checksum with the image change.
- The local extraction guide requires Docker and a pullable immutable
image.
- An image built from an older source revision can still fail runtime
manifest verification. The guide does not bypass that check.
- The change does not alter runner prompts, approval policy, or recovery
behavior.

## Model Used

OpenAI Codex using the primary GPT-6 backend; the exact backend
deployment ID is not exposed. Repository analysis and code execution
used tool access. Assistance also came from OpenAI gpt-5.6-luna.

## 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-09-22 13:49:00 -05:00
1 parent 2788f20fc0
commit a68f3d8e35
3 files changed
+50 -3

No files matched your search

+1 -1
View File
@@ -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 \
@@ -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)(
+42
View File
@@ -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