Files
PaperClipAI/.github/workflows/docker-runner-check.yml
T
DottaandPaperclip a8bd012635 fix(evals): apply reviewed lock corrections to reports and qualification images (#17937)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/98/changes/paperclip/98
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip manages agents and records their work.
> - Product E2E campaigns measure real agent behavior and model spend.
> - A paid hosted run completed both cases and all ten model receipts.
> - Its report job could not install dependencies from the committed
lock.
> - The trusted checkout needs a reviewed dependency correction.
> - This change applies a reviewed lock correction before the frozen
report install.

## Linked Issues or Issue Description

Refs: #17928

**Description**
The report in [run
40576181788](https://github.com/paperclipai/paperclip/actions/runs/40576181788)
failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`. Both paid cases had
passed. The worker and build jobs already restored the resolved source
lock, but the report did not.

**Steps to reproduce**
Dispatch the bounded daily campaign on the default branch with a stale
committed lock configuration. Workers use the resolved lock. The report
uses the stale lock and fails before report generation.

**Expected behavior**
The report installs frozen dependencies from a verified lock for the
exact trusted source. Evidence can be reported without repeating paid
work.

## What Changed

- Apply a reviewed lock correction from the trusted report checkout
before its frozen install. Use the same correction for the qualification
image build, which also requires a compatible frozen lock.
- Pin the original lock, correction and output SHA-256. The trusted
authorization job rejects unknown inputs before any paid work.
- Apply the same correction to trusted manual history publication.
Qualification image builds apply it only when their target equals the
workflow revision; other targets keep their own lock.
- Update existing Claude ACP and postgres patch identities and link the
declared yaml dependency. Existing package tarball integrity entries
stay unchanged; the declared yaml dependency adds exactly yaml@2.9.1
with pinned integrity.
- Keep runtime registry resolution and worker target locks out of the
credential-bearing report.
- Test valid correction, idempotence and changed-lock, changed-patch and
wrong-output rejection.

## Verification

- Focused workflow and lock-correction tests: 36 passed.
- `actionlint .github/workflows/runner-full-stack-e2e.yml` passed.
- Original worker evidence: 2/2 passed, ten final model receipts, zero
unresolved model requests.
- The original report was regenerated from retained evidence with zero
provider calls and saved privately with replay provenance.
- Broader repository checks run in CI. Prior full Product checks passed
for #17928.

## Risks

The correction is tied to the reviewed committed lock. Dependency input
changes need a reviewed correction update. No new registry version or
lifecycle script can enter the privileged report through this fix. The
original failed workflow stays failed. No root lockfile is committed.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, repository inspection and
code execution. The exact serving model ID and context window are not
exposed to this session.

## 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
- [ ] All Paperclip CI gates are green
- [ ] 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>
2026-10-27 20:55:13 -05:00

322 lines
16 KiB
YAML

name: Docker Runner check
on:
workflow_dispatch:
inputs:
target_branch:
description: "Branch or full commit SHA in this repository to build"
type: string
required: false
build_eval_image:
description: "Build an immutable full-source qualification OCI artifact without publication"
type: boolean
default: false
publish_eval_image:
description: "Publish an immutable Daytona qualification image on hosted Linux (no provider credentials)"
type: boolean
default: false
release_run_id:
description: "Consume a successful exact-source Release Smoke pack, daemon and public-package artifact set"
type: string
required: false
release_base_image:
description: "Immutable Paperclip Daytona base image for the release-artifact consumer"
type: string
required: false
verify_source:
description: "Run broad source checks on hosted Linux"
type: boolean
default: false
verify_public_install:
description: "Build and verify clean public npm installation on hosted Linux (no provider credentials)"
type: boolean
default: false
build_eval_viewer:
description: "Build the canonical eval report viewer on hosted Linux"
type: boolean
default: false
pull_request:
paths:
- .github/workflows/docker-runner-check.yml
- Dockerfile
- .dockerignore
- scripts/check-docker-runner-cache.sh
- packages/paperclip-runner/rust-toolchain.toml
- packages/paperclip-runner/runner/**
- packages/paperclip-runner/protocol/**
permissions: {}
concurrency:
# Publishing a newer image must not discard an earlier verification's evidence.
group: docker-runner-check-${{ github.event.pull_request.number || github.ref }}-${{ inputs.verify_source && 'verification' || inputs.verify_public_install && 'public-install' || inputs.build_eval_viewer && 'viewer' || 'image' }}
cancel-in-progress: true
jobs:
runner:
if: github.event_name == 'pull_request'
name: Compile isolated native Runner
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
# Compile the real target, then change source in a disposable context.
# A fresh builder must import dependencies and produce changed binary metadata.
# The baseline build anonymously seeds from the public BuildKit cache at
# ghcr.io/paperclipai/paperclip:buildcache-{amd64,arm64}; the verification
# build imports only this run's locally exported cache on a fresh builder.
# No registry credentials or image publication.
- name: Verify native build and dependency cache reuse
run: bash scripts/check-docker-runner-cache.sh
authorize_manual:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 5
outputs:
target_sha: ${{ steps.authorize.outputs.target_sha }}
steps:
- name: Authorize an explicit maintainer image build
id: authorize
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
REPOSITORY_ID: ${{ github.repository_id }}
ACTOR_ID: ${{ github.actor_id }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
ALLOWED_IDS: ${{ vars.AWS_CI_TRUSTED_USER_IDS }}
TARGET_BRANCH: ${{ inputs.target_branch || github.ref_name }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
PUBLISH_EVAL_IMAGE: ${{ inputs.publish_eval_image }}
VERIFY_SOURCE: ${{ inputs.verify_source }}
VERIFY_PUBLIC_INSTALL: ${{ inputs.verify_public_install }}
BUILD_EVAL_VIEWER: ${{ inputs.build_eval_viewer }}
run: |
set -euo pipefail
test "$REPOSITORY" = paperclipai/paperclip
test "$REPOSITORY_ID" = 1170821064
jq -e 'type == "array" and length > 0 and all(.[]; type == "number")' <<< "$ALLOWED_IDS" >/dev/null
triggering_id="$(gh api "users/$TRIGGERING_ACTOR" --jq .id)"
for id in "$ACTOR_ID" "$triggering_id"; do
jq -e --argjson id "$id" 'index($id) != null' <<< "$ALLOWED_IDS" >/dev/null
done
if [ -n "$RELEASE_RUN_ID" ]; then
[[ "$RELEASE_RUN_ID" =~ ^[1-9][0-9]*$ ]]
test "$PUBLISH_EVAL_IMAGE" = true
test "$VERIFY_SOURCE" != true
test "$VERIFY_PUBLIC_INSTALL" != true
test "$BUILD_EVAL_VIEWER" != true
fi
if [[ "$TARGET_BRANCH" =~ ^[a-f0-9]{40}$ ]]; then
target_sha="$(gh api "repos/$REPOSITORY/commits/$TARGET_BRANCH" --jq .sha)"
else
target_sha="$(gh api "repos/$REPOSITORY/git/ref/heads/$TARGET_BRANCH" --jq '.object.sha')"
fi
[[ "$target_sha" =~ ^[0-9a-f]{40}$ ]]
echo "target_sha=$target_sha" >> "$GITHUB_OUTPUT"
manual_image:
name: Build and verify on Linux
needs: authorize_manual
# Keep maintainer authorization below; use a native hosted Linux builder.
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
actions: read
packages: write
steps:
- name: Authorize an explicit maintainer image build
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
REPOSITORY_ID: ${{ github.repository_id }}
ACTOR_ID: ${{ github.actor_id }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
ALLOWED_IDS: ${{ vars.AWS_CI_TRUSTED_USER_IDS }}
run: |
set -euo pipefail
test "$REPOSITORY" = paperclipai/paperclip
test "$REPOSITORY_ID" = 1170821064
jq -e 'type == "array" and length > 0 and all(.[]; type == "number")' <<< "$ALLOWED_IDS" >/dev/null
triggering_id="$(gh api "users/$TRIGGERING_ACTOR" --jq .id)"
for id in "$ACTOR_ID" "$triggering_id"; do
jq -e --argjson id "$id" 'index($id) != null' <<< "$ALLOWED_IDS" >/dev/null
done
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.release_run_id != '' && github.sha || needs.authorize_manual.outputs.target_sha }}
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 9.15.4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
package-manager-cache: false
- name: Apply reviewed qualification lock corrections
if: inputs.release_run_id == '' && (inputs.build_eval_image || inputs.publish_eval_image)
env:
TARGET_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
run: |
set -euo pipefail
if [ "$TARGET_SHA" = "$GITHUB_SHA" ]; then
node tests/runner-e2e/prepare-report-lock.mjs
fi
- name: Resolve source dependencies without lifecycle scripts
if: inputs.release_run_id == ''
run: |
if [[ "${{ inputs.build_eval_image || inputs.publish_eval_image }}" == true ]]; then
pnpm install --frozen-lockfile --ignore-scripts
else
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile
fi
mkdir -p remote-verification
sha256sum pnpm-lock.yaml > remote-verification/lock.sha256
git rev-parse HEAD > remote-verification/source.txt
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- if: inputs.publish_eval_image
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare hosted image build capacity
if: (inputs.build_eval_image || inputs.publish_eval_image) && inputs.release_run_id == ''
run: |
# These preinstalled SDKs are absent from the image build context.
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
df -h .
test "$(df --output=avail -BG . | tail -1 | tr -dc '0-9')" -ge 35
- name: Build checksum-verified qualification image
if: (inputs.build_eval_image || inputs.publish_eval_image) && inputs.release_run_id == ''
env:
SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
run: |
set -euo pipefail
image="ghcr.io/paperclipai/paperclip-daytona-runner:qualification-${SOURCE_SHA}-${GITHUB_RUN_ID}"
lock_sha="$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)"
content_id="$(node cli/node_modules/tsx/dist/cli.mjs tests/runner-e2e/daytona-image-content.ts)"
[[ "$content_id" =~ ^[0-9a-f]{64}$ ]]
printf '%s\n' "$content_id" > remote-verification/content-id.txt
output_args=(--output "type=oci,dest=remote-verification/image.oci.tar")
if [[ "${{ inputs.publish_eval_image }}" == true ]]; then
output_args=(--push)
fi
docker buildx build --platform linux/amd64 \
--file docker/daytona-runner/Dockerfile \
--build-arg "PAPERCLIP_RUNNER_SOURCE_REVISION=$SOURCE_SHA" \
--build-arg "PAPERCLIP_RUNNER_CONTENT_ID=$content_id" \
--build-arg "PAPERCLIP_RUNNER_LOCK_SHA256=$lock_sha" \
--cache-from type=registry,ref=ghcr.io/paperclipai/paperclip-daytona-runner:e2e-buildcache-amd64 \
--tag "$image" --metadata-file remote-verification/image.json "${output_args[@]}" .
digest="$(jq -r '."containerimage.digest"' remote-verification/image.json)"
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]
immutable="${image%:*}@$digest"
if [[ "${{ inputs.publish_eval_image }}" == true ]]; then
docker logout ghcr.io
docker buildx imagetools inspect "$immutable" >/dev/null
else
sha256sum remote-verification/image.oci.tar > remote-verification/image-archive.sha256
fi
echo "$immutable" > remote-verification/image.txt
echo "Image: $immutable; published: ${{ inputs.publish_eval_image }}" >> "$GITHUB_STEP_SUMMARY"
- name: Consume exact release artifacts into the qualification image
if: inputs.publish_eval_image && inputs.release_run_id != ''
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_BASE_IMAGE: ${{ inputs.release_base_image }}
run: |
set -euo pipefail
test '${{ inputs.verify_source }}' != true
test '${{ inputs.verify_public_install }}' != true
test '${{ inputs.build_eval_viewer }}' != true
mkdir -p remote-verification
git rev-parse HEAD > remote-verification/tooling-source.txt
printf '%s\n' "$SOURCE_SHA" > remote-verification/source.txt
node scripts/prepare-runner-release-image.mjs remote-verification/release-context
image="ghcr.io/paperclipai/paperclip-daytona-runner:qualification-${SOURCE_SHA}-${GITHUB_RUN_ID}"
docker buildx build --platform linux/amd64 \
--build-arg "BASE_IMAGE=$RELEASE_BASE_IMAGE" \
--build-arg "SOURCE_REVISION=$SOURCE_SHA" \
--tag "$image" --metadata-file remote-verification/image.json --push \
remote-verification/release-context
digest="$(jq -r '."containerimage.digest"' remote-verification/image.json)"
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]
immutable="${image%:*}@$digest"
docker run --rm --entrypoint cat "$immutable" /opt/paperclip-runner/provider-pack/provider-pack.json \
> remote-verification/image-provider-pack.json
cmp remote-verification/release-context/provider-pack.json remote-verification/image-provider-pack.json
test "$(docker run --rm --entrypoint id "$immutable" -un)" = daytona
docker run --rm --entrypoint sha256sum "$immutable" /usr/local/bin/paperclip-runnerd \
> remote-verification/image-daemon.sha256
expected="$(jq -r .daemonSha256 remote-verification/release-image-inputs.json)"
test "$(cut -d ' ' -f 1 remote-verification/image-daemon.sha256)" = "$expected"
printf '%s\n' "$immutable" > remote-verification/image.txt
rm -rf remote-verification/release-context
docker logout ghcr.io
echo "Image: $immutable" >> "$GITHUB_STEP_SUMMARY"
- name: Verify repository on hosted Linux
if: inputs.verify_source
# Leave time for artifact retention within the job deadline.
timeout-minutes: 80
run: |
set -uo pipefail
pnpm install --frozen-lockfile --ignore-scripts
git fetch --no-tags --depth=1 origin 2a8a99e4a5f69aa803b3f10b982f583e75a87042 16ef1a5744b23bb043a8b286e6288921da1de98f
status=0
for check in 'pnpm -r typecheck' 'pnpm test:run' 'pnpm check:token-gates' 'pnpm test:e2e:runner:typecheck' 'pnpm test:e2e:runner:unit' 'pnpm build' 'if [ -f scripts/verify-grok-npm-install.mjs ]; then node scripts/verify-grok-npm-install.mjs; fi'; do
label="$(echo "$check" | tr -cs 'a-zA-Z0-9' '-')"
echo "Starting $check"
check_status=0
limit=15m
if [ "$check" = 'pnpm test:run' ]; then limit=60m; fi
timeout --signal=TERM --kill-after=15s "$limit" bash -c "$check" > "remote-verification/$label.log" 2>&1 || check_status=$?
printf '%s\t%s\n' "$check_status" "$check" >> remote-verification/check-status.tsv
if [ "$check_status" -ne 0 ]; then status=1; fi
echo "Finished $check (exit $check_status)"
done
exit "$status"
- name: Verify clean public npm installation
if: inputs.verify_public_install && !inputs.verify_source
timeout-minutes: 35
run: |
set -euo pipefail
test -f scripts/verify-grok-npm-install.mjs || { echo "Selected source does not provide the public-install verifier"; exit 1; }
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile > remote-verification/npm-setup.log 2>&1
pnpm install --frozen-lockfile --ignore-scripts >> remote-verification/npm-setup.log 2>&1
pnpm build > remote-verification/npm-build.log 2>&1
PAPERCLIP_RUNNER_QUALIFICATION_PACKAGES_DIR="$PWD/remote-verification/qualification-packages" \
node scripts/verify-grok-npm-install.mjs > remote-verification/public-npm-install.log 2>&1
- name: Build canonical eval report viewer
if: always() && (inputs.verify_source || inputs.build_eval_viewer)
run: |
set -euo pipefail
pnpm install --frozen-lockfile --ignore-scripts --filter '@paperclipai/paperclip-runner...'
pnpm --filter @paperclipai/paperclip-runner build:issue-thread > remote-verification/viewer-build.log 2>&1
tar -czf remote-verification/eval-viewer.tar.gz -C packages/paperclip-runner dist-issue-thread
sha256sum remote-verification/eval-viewer.tar.gz > remote-verification/eval-viewer.sha256
- name: Retain source, image and verification evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: grok-remote-verification-${{ github.run_id }}
path: |
remote-verification/
!remote-verification/release-context/**
compression-level: 0
retention-days: ${{ inputs.build_eval_image && 1 || 7 }}