name: Docker Runner check on: workflow_dispatch: inputs: target_branch: description: "Branch in this repository to build; resolved to one immutable commit before checkout" type: string required: false publish_eval_image: description: "Publish an immutable Daytona qualification image on hosted Linux (no provider credentials)" type: boolean default: 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 verify_pi_macos_install: description: "Verify frozen Pi shipping source on native hosted ARM and Intel Macs; no provider calls" type: boolean default: false verify_pi_intel_continuation: description: "Continue native Intel admission from immutable retained package artifact 11517098137; no provider calls" 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_pi_macos_install || inputs.verify_pi_intel_continuation) && 'mac-install' || inputs.verify_source && 'verification' || inputs.verify_public_install && 'public-install' || inputs.build_eval_viewer && 'viewer' || 'image' }} cancel-in-progress: ${{ !inputs.verify_pi_macos_install && !inputs.verify_pi_intel_continuation }} 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 }} 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 target_sha="$(gh api "repos/$REPOSITORY/git/ref/heads/$TARGET_BRANCH" --jq '.object.sha')" [[ "$target_sha" =~ ^[0-9a-f]{40}$ ]] echo "target_sha=$target_sha" >> "$GITHUB_OUTPUT" manual_image: if: ${{ !inputs.verify_pi_macos_install && !inputs.verify_pi_intel_continuation }} 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 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: ${{ 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: Resolve source dependencies without lifecycle scripts run: | pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile 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: Build checksum-verified Grok provider image if: inputs.publish_eval_image 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)" 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=qualification-$SOURCE_SHA" \ --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 --push . digest="$(jq -r '."containerimage.digest"' remote-verification/image.json)" [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] immutable="${image%:*}@$digest" docker logout ghcr.io docker buildx imagetools inspect "$immutable" >/dev/null echo "$immutable" > remote-verification/image.txt 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: 38 run: | set -uo pipefail pnpm install --frozen-lockfile --ignore-scripts 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 timeout --signal=TERM --kill-after=15s 15m 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 --frozen-lockfile --ignore-scripts > remote-verification/npm-setup.log 2>&1 pnpm build > remote-verification/npm-build.log 2>&1 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/ retention-days: 7 pi_native_macos_install: if: github.event_name == 'workflow_dispatch' && inputs.verify_pi_macos_install name: Pi normal install (${{ matrix.arch }}) needs: authorize_manual runs-on: ${{ matrix.host }} timeout-minutes: 90 permissions: contents: read strategy: fail-fast: false matrix: include: - host: macos-15 arch: arm64 - host: macos-15-intel arch: x64 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 - name: Checkout verification orchestration uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.sha }} path: orchestration persist-credentials: false - name: Checkout frozen shipping source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: 06a3d9739f402baa7f8be7aaa29dda6db7354bfb path: source fetch-depth: 0 persist-credentials: false - name: Verify immutable source ancestry env: TARGET_SHA: ${{ needs.authorize_manual.outputs.target_sha }} ORCHESTRATION_SHA: ${{ github.sha }} run: | set -euo pipefail test "$(git -C source rev-parse HEAD)" = 06a3d9739f402baa7f8be7aaa29dda6db7354bfb test "$(git -C orchestration rev-parse HEAD)" = "$ORCHESTRATION_SHA" git -C source merge-base --is-ancestor 06a3d9739f402baa7f8be7aaa29dda6db7354bfb "$TARGET_SHA" - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 with: version: 9.15.4 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 24.21.0 package-manager-cache: false - name: Normal native public npm installation and Pi admission env: PI_QUALIFICATION_ORCHESTRATION_SHA: ${{ github.sha }} EXPECTED_ARCH: ${{ matrix.arch }} run: | set -euo pipefail node orchestration/scripts/pi-qualification-macos.mjs "$PWD/source" "$RUNNER_TEMP/pi-native-install-evidence" "$EXPECTED_ARCH" - name: Retain native installation evidence and package archives if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: pi-native-install-${{ matrix.arch }}-${{ github.run_id }} path: | ${{ runner.temp }}/pi-native-install-evidence/*.json ${{ runner.temp }}/pi-native-install-evidence/*.log ${{ runner.temp }}/pi-native-install-evidence/archives/*.tgz retention-days: 14 if-no-files-found: error pi_native_intel_continuation: if: github.event_name == 'workflow_dispatch' && inputs.verify_pi_intel_continuation name: Pi native Intel retained-package admission needs: authorize_manual runs-on: macos-15-intel timeout-minutes: 35 permissions: contents: read actions: read 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 - name: Checkout verification orchestration uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.sha }} path: orchestration persist-credentials: false - name: Checkout frozen shipping source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: 06a3d9739f402baa7f8be7aaa29dda6db7354bfb path: source fetch-depth: 0 persist-credentials: false - name: Verify immutable source ancestry env: TARGET_SHA: ${{ needs.authorize_manual.outputs.target_sha }} ORCHESTRATION_SHA: ${{ github.sha }} run: | set -euo pipefail test "$(git -C source rev-parse HEAD)" = 06a3d9739f402baa7f8be7aaa29dda6db7354bfb test "$(git -C orchestration rev-parse HEAD)" = "$ORCHESTRATION_SHA" git -C source merge-base --is-ancestor 06a3d9739f402baa7f8be7aaa29dda6db7354bfb "$TARGET_SHA" - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 with: version: 9.15.4 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 24.21.0 package-manager-cache: false - name: Download immutable retained native Intel packages env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail gh api repos/paperclipai/paperclip/actions/artifacts/11517098137 > "$RUNNER_TEMP/pi-original-artifact.json" jq -e '.name == "pi-native-install-x64-37694650556" and .expired == false and .digest == "sha256:b6923562c643ce7c970885692c2ce6d939c644ba66212870a7becfe9be454cac"' "$RUNNER_TEMP/pi-original-artifact.json" >/dev/null gh run download 37694650556 --repo paperclipai/paperclip --name pi-native-install-x64-37694650556 --dir "$RUNNER_TEMP/pi-original-intel" - name: Complete normal Intel package installation and Pi admission timeout-minutes: 30 env: PI_QUALIFICATION_ORCHESTRATION_SHA: ${{ github.sha }} run: | set -euo pipefail node orchestration/scripts/pi-qualification-intel-continuation.mjs "$PWD/source" "$RUNNER_TEMP/pi-original-intel" "$RUNNER_TEMP/pi-native-install-evidence" - name: Retain native installation evidence and package archives if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: pi-native-install-x64-continuation-${{ github.run_id }} path: | ${{ runner.temp }}/pi-native-install-evidence/*.json ${{ runner.temp }}/pi-native-install-evidence/*.log ${{ runner.temp }}/pi-native-install-evidence/archives/*.tgz ${{ runner.temp }}/pi-native-install-evidence/original-build/ ${{ runner.temp }}/pi-original-artifact.json retention-days: 14 if-no-files-found: error