mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Bind the original archive and receipt identities, preserve the cancelled attempt, and emit synchronous stage progress. No shipping source or provider input changes. Co-Authored-By: Paperclip <noreply@paperclip.ing>
391 lines
18 KiB
YAML
391 lines
18 KiB
YAML
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
|