mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-10 03:08:10 +02:00
1139 lines
58 KiB
YAML
1139 lines
58 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 the EC2 fleet (no provider credentials)"
|
|
type: boolean
|
|
default: false
|
|
verify_source:
|
|
description: "Run broad source checks on GitHub-hosted Ubuntu without building an image"
|
|
type: boolean
|
|
default: false
|
|
source_root_tests_only:
|
|
description: "With verify_source, run only the complete pnpm test:run suite (180-minute bound)"
|
|
type: boolean
|
|
default: false
|
|
source_e2e_support_only:
|
|
description: "Run only E2E support typecheck, complete unit suite and catalog on the exact reviewed coverage source"
|
|
type: boolean
|
|
default: false
|
|
verify_runner:
|
|
description: "Run the complete offline Runner verify command on GitHub-hosted Ubuntu without building an image"
|
|
type: boolean
|
|
default: false
|
|
verify_pi_intel_retained:
|
|
description: "Run original startup and SDK contracts on exact retained bba Intel artifacts, without rebuilding"
|
|
type: boolean
|
|
default: false
|
|
verify_pi_intel:
|
|
description: "Verify frozen Pi 1.0 closed startup and SDK contracts on native Intel macOS (no provider prompts)"
|
|
type: boolean
|
|
default: false
|
|
diagnose_pi_snapshot_streaming:
|
|
description: "Compare retained B9 baseline and unpublished streaming snapshots in four ABBA children (diagnostic only)"
|
|
type: boolean
|
|
default: false
|
|
diagnose_pi_snapshot_pool:
|
|
description: "Measure exact retained B9 snapshots in four process-start pool4/16 ABBA children (diagnostic only)"
|
|
type: boolean
|
|
default: false
|
|
pi_startup_overlap:
|
|
description: "With diagnose_pi_startup, evaluate one unpublished two-module admission-overlap derivative (not qualification)"
|
|
type: boolean
|
|
default: false
|
|
diagnose_pi_startup:
|
|
description: "Instrument one retained B9 profile12 native Intel startup with unchanged deadlines (diagnostic only, no provider prompts)"
|
|
type: boolean
|
|
default: false
|
|
diagnose_ajv_pack:
|
|
description: "Diagnose only pinned AJV npm directory packing on Linux (no Runner build/tests)"
|
|
type: boolean
|
|
default: false
|
|
expected_source_sha:
|
|
description: "Require this immutable target commit (required for verify_runner, verify_source or source_e2e_support_only)"
|
|
type: string
|
|
required: false
|
|
expected_resolved_lock_sha256:
|
|
description: "Require this reviewed resolved dependency lock (required for verify_runner, verify_source or source_e2e_support_only)"
|
|
type: string
|
|
required: false
|
|
verify_public_install:
|
|
description: "Build and verify clean public npm installation on EC2 (no provider credentials)"
|
|
type: boolean
|
|
default: false
|
|
build_eval_viewer:
|
|
description: "Build the canonical eval report viewer on EC2"
|
|
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:
|
|
# Different immutable manual sources retain independent evidence; the same
|
|
# source/mode still supersedes itself. Ordinary PR grouping is unchanged.
|
|
group: docker-runner-check-${{ github.event.pull_request.number || github.ref }}-${{ inputs.verify_pi_intel_retained && 'pi-retained-qualification' || inputs.diagnose_pi_snapshot_streaming && 'pi-snapshot-streaming' || inputs.diagnose_pi_snapshot_pool && 'pi-snapshot-pool' || inputs.pi_startup_overlap && 'pi-startup-overlap' || inputs.diagnose_pi_startup && 'pi-startup-diagnostic' || inputs.verify_pi_intel && 'pi-native-intel' || inputs.diagnose_ajv_pack && 'ajv-diagnostic' || inputs.verify_runner && 'runner-verification' || inputs.source_e2e_support_only && 'source-e2e-support' || inputs.source_root_tests_only && 'source-root-tests' || inputs.verify_source && 'verification' || inputs.verify_public_install && 'public-install' || inputs.build_eval_viewer && 'viewer' || 'image' }}${{ github.event_name == 'workflow_dispatch' && inputs.expected_source_sha && format('-{0}', inputs.expected_source_sha) || '' }}
|
|
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 }}
|
|
EXPECTED_SOURCE_SHA: ${{ inputs.expected_source_sha }}
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
VERIFY_RUNNER: ${{ inputs.verify_runner }}
|
|
VERIFY_SOURCE: ${{ inputs.verify_source }}
|
|
SOURCE_ROOT_TESTS_ONLY: ${{ inputs.source_root_tests_only }}
|
|
SOURCE_E2E_SUPPORT_ONLY: ${{ inputs.source_e2e_support_only }}
|
|
IMAGE_MODE: ${{ inputs.publish_eval_image || inputs.verify_public_install || inputs.build_eval_viewer }}
|
|
VERIFY_PI_INTEL: ${{ inputs.verify_pi_intel }}
|
|
VERIFY_PI_INTEL_RETAINED: ${{ inputs.verify_pi_intel_retained }}
|
|
DIAGNOSE_PI_SNAPSHOT_STREAMING: ${{ inputs.diagnose_pi_snapshot_streaming }}
|
|
DIAGNOSE_PI_SNAPSHOT_POOL: ${{ inputs.diagnose_pi_snapshot_pool }}
|
|
DIAGNOSE_PI_STARTUP: ${{ inputs.diagnose_pi_startup }}
|
|
PI_STARTUP_OVERLAP: ${{ inputs.pi_startup_overlap }}
|
|
DIAGNOSE_AJV: ${{ inputs.diagnose_ajv_pack }}
|
|
OTHER_MODE: ${{ inputs.verify_pi_intel_retained || inputs.publish_eval_image || inputs.verify_source || inputs.verify_public_install || inputs.build_eval_viewer || inputs.verify_pi_intel || inputs.diagnose_pi_startup || inputs.diagnose_pi_snapshot_pool || inputs.diagnose_pi_snapshot_streaming || inputs.source_e2e_support_only }}
|
|
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}$ ]]
|
|
if [ "$SOURCE_ROOT_TESTS_ONLY" = true ]; then
|
|
test "$VERIFY_SOURCE" = true
|
|
fi
|
|
if [ "$VERIFY_RUNNER" = true ] || [ "$VERIFY_SOURCE" = true ] || [ "$SOURCE_E2E_SUPPORT_ONLY" = true ]; then
|
|
[[ "$EXPECTED_SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]]
|
|
[[ "$EXPECTED_LOCK_SHA256" =~ ^[0-9a-f]{64}$ ]]
|
|
fi
|
|
if [ "$SOURCE_E2E_SUPPORT_ONLY" = true ]; then
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_PI_INTEL" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = 5a6a531575b194fde1d353bc19242c23ba887227
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ "$VERIFY_SOURCE" = true ]; then
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$IMAGE_MODE" != true
|
|
fi
|
|
if [ "$VERIFY_RUNNER" = true ]; then
|
|
test "$OTHER_MODE" != true
|
|
fi
|
|
if [ "$DIAGNOSE_AJV" = true ]; then
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$OTHER_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = 34f49a201a804564cfae81e425266b3f9f987e30
|
|
test "$EXPECTED_LOCK_SHA256" = 5ae57d1ddd475691dac1f1cfbd4836e54f7da1956b47e6e705b218ae3070ae2e
|
|
fi
|
|
if [ "$VERIFY_PI_INTEL_RETAINED" = true ]; then
|
|
test "$VERIFY_PI_INTEL" != true
|
|
test "$DIAGNOSE_PI_SNAPSHOT_STREAMING" != true
|
|
test "$DIAGNOSE_PI_SNAPSHOT_POOL" != true
|
|
test "$DIAGNOSE_PI_STARTUP" != true
|
|
test "$PI_STARTUP_OVERLAP" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$SOURCE_E2E_SUPPORT_ONLY" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = bba63f51207de8513ad2d9593694f718fc60ef17
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ "$VERIFY_PI_INTEL" = true ]; then
|
|
test "$DIAGNOSE_PI_SNAPSHOT_STREAMING" != true
|
|
test "$DIAGNOSE_PI_SNAPSHOT_POOL" != true
|
|
test "$DIAGNOSE_PI_STARTUP" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = 33f4a2137d071c65e5583decf70b0c39ac28ec56
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
# Standard hosted runner minutes are free for this public repository.
|
|
# Artifact storage and unrelated resource costs are not inferred here.
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ "$PI_STARTUP_OVERLAP" = true ]; then
|
|
test "$DIAGNOSE_PI_STARTUP" = true
|
|
fi
|
|
if [ "$DIAGNOSE_PI_STARTUP" = true ]; then
|
|
test "$DIAGNOSE_PI_SNAPSHOT_STREAMING" != true
|
|
test "$DIAGNOSE_PI_SNAPSHOT_POOL" != true
|
|
test "$VERIFY_PI_INTEL" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$SOURCE_E2E_SUPPORT_ONLY" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
if [ "$PI_STARTUP_OVERLAP" = true ]; then
|
|
test "$EXPECTED_SOURCE_SHA" = f5c5fde380f60937ef26cc5a92e1d6a043e9cddc
|
|
else
|
|
test "$EXPECTED_SOURCE_SHA" = bba63f51207de8513ad2d9593694f718fc60ef17
|
|
fi
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ "$DIAGNOSE_PI_SNAPSHOT_POOL" = true ]; then
|
|
test "$DIAGNOSE_PI_SNAPSHOT_STREAMING" != true
|
|
test "$DIAGNOSE_PI_STARTUP" != true
|
|
test "$VERIFY_PI_INTEL" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$SOURCE_E2E_SUPPORT_ONLY" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = b9e5d6ecdb05ab7244c90976e07c950f8d09b15b
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ "$DIAGNOSE_PI_SNAPSHOT_STREAMING" = true ]; then
|
|
test "$DIAGNOSE_PI_SNAPSHOT_POOL" != true
|
|
test "$DIAGNOSE_PI_STARTUP" != true
|
|
test "$VERIFY_PI_INTEL" != true
|
|
test "$VERIFY_RUNNER" != true
|
|
test "$VERIFY_SOURCE" != true
|
|
test "$SOURCE_ROOT_TESTS_ONLY" != true
|
|
test "$SOURCE_E2E_SUPPORT_ONLY" != true
|
|
test "$DIAGNOSE_AJV" != true
|
|
test "$IMAGE_MODE" != true
|
|
test "$EXPECTED_SOURCE_SHA" = b9e5d6ecdb05ab7244c90976e07c950f8d09b15b
|
|
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
|
|
test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public
|
|
fi
|
|
if [ -n "$EXPECTED_SOURCE_SHA" ]; then
|
|
test "$target_sha" = "$EXPECTED_SOURCE_SHA"
|
|
fi
|
|
echo "target_sha=$target_sha" >> "$GITHUB_OUTPUT"
|
|
|
|
manual_pi_intel:
|
|
name: Frozen Pi 1.0 native Intel startup and SDK contracts
|
|
if: github.event_name == 'workflow_dispatch' && inputs.verify_pi_intel
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted CI helpers independently of candidate source
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Checkout exact frozen production source
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
path: candidate
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
with:
|
|
node-version: 24.21.0
|
|
architecture: x64
|
|
package-manager-cache: false
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
with:
|
|
version: 9.15.4
|
|
package_json_file: candidate/package.json
|
|
- name: Build frozen pack and run unchanged no-provider contracts once
|
|
timeout-minutes: 55
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/verify.py \
|
|
--source "$GITHUB_WORKSPACE/candidate" \
|
|
--fresh-profile13 \
|
|
--output "$GITHUB_WORKSPACE/pi-intel-evidence"
|
|
- name: Admit complete evidence within the reserved storage bound
|
|
if: always()
|
|
id: pi_intel_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-intel-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain exact input, independent output, test, and cleanup evidence
|
|
if: always() && steps.pi_intel_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-native-intel-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-intel-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_pi_retained_qualification:
|
|
name: Canonical Pi13 checks on retained bba native Intel build
|
|
if: github.event_name == 'workflow_dispatch' && inputs.verify_pi_intel_retained
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted qualification helpers only
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Download exact retained build artifacts
|
|
timeout-minutes: 5
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api repos/paperclipai/paperclip/actions/artifacts/11226760000/zip \
|
|
> "$RUNNER_TEMP/pi-retained-37004499595.zip"
|
|
printf '%s %s\n' 9bc17434eb15b1f38b29b42b8ef95a477520a097a060ac1af8dc916d10d14d92 \
|
|
"$RUNNER_TEMP/pi-retained-37004499595.zip" | shasum -a 256 --check
|
|
- name: Run original startup and SDK contracts with full final integrity and cleanup
|
|
timeout-minutes: 22
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/retained_qualification.py \
|
|
--archive "$RUNNER_TEMP/pi-retained-37004499595.zip" \
|
|
--output "$GITHUB_WORKSPACE/pi-retained-qualification-evidence"
|
|
- name: Admit complete results and provenance within the storage bound
|
|
if: always()
|
|
id: retained_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_diagnostic_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-retained-qualification-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain complete canonical assertions, provenance and cleanup
|
|
if: always() && steps.retained_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-retained-qualification-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-retained-qualification-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_pi_startup_diagnostic:
|
|
name: Retained bba profile13 native Intel startup timing diagnostic (not qualification)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.diagnose_pi_startup && !inputs.pi_startup_overlap
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 25
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted diagnostic helpers only
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Download exact failed qualification artifacts
|
|
timeout-minutes: 5
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api repos/paperclipai/paperclip/actions/artifacts/11226760000/zip \
|
|
> "$RUNNER_TEMP/pi-startup-37004499595.zip"
|
|
printf '%s %s\n' 9bc17434eb15b1f38b29b42b8ef95a477520a097a060ac1af8dc916d10d14d92 \
|
|
"$RUNNER_TEMP/pi-startup-37004499595.zip" | shasum -a 256 --check
|
|
- name: Run one instrumented startup with original assertions and deadlines
|
|
timeout-minutes: 18
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/startup_diagnostic.py \
|
|
--inputs bba --archive "$RUNNER_TEMP/pi-startup-37004499595.zip" \
|
|
--output "$GITHUB_WORKSPACE/pi-startup-diagnostic-evidence"
|
|
- name: Admit complete diagnostic evidence within the storage bound
|
|
if: always()
|
|
id: diagnostic_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_diagnostic_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-startup-diagnostic-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain original and diagnostic identities, timings, assertions and cleanup
|
|
if: always() && steps.diagnostic_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-startup-diagnostic-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-startup-diagnostic-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_pi_startup_overlap:
|
|
name: Retained f5 native Intel admission overlap prototype (not qualification)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.diagnose_pi_startup && inputs.pi_startup_overlap
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 25
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted diagnostic helpers only
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Download exact failed qualification artifacts
|
|
timeout-minutes: 5
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api repos/paperclipai/paperclip/actions/artifacts/11218057612/zip \
|
|
> "$RUNNER_TEMP/pi-startup-36984851998.zip"
|
|
printf '%s %s\n' 5159dcb57b380648679d441a73b87d50334dcca969adc95150e8429a71cbdfeb \
|
|
"$RUNNER_TEMP/pi-startup-36984851998.zip" | shasum -a 256 --check
|
|
- name: Run one unpublished overlap startup with original assertions and deadlines
|
|
timeout-minutes: 18
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/startup_overlap.py \
|
|
--archive "$RUNNER_TEMP/pi-startup-36984851998.zip" \
|
|
--output "$GITHUB_WORKSPACE/pi-startup-overlap-evidence"
|
|
- name: Admit complete diagnostic evidence within the storage bound
|
|
if: always()
|
|
id: diagnostic_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_diagnostic_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-startup-overlap-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain original and prototype identities, assertions and cleanup
|
|
if: always() && steps.diagnostic_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-startup-overlap-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-startup-overlap-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_pi_snapshot_pool:
|
|
name: Retained B9 native Intel snapshot pool ABBA (not qualification)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.diagnose_pi_snapshot_pool
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 25
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted diagnostic helpers only
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Download exact failed qualification artifacts
|
|
timeout-minutes: 5
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api repos/paperclipai/paperclip/actions/artifacts/11208570718/zip \
|
|
> "$RUNNER_TEMP/pi-startup-36959948724.zip"
|
|
printf '%s %s\n' b82cf6c843006781e2e6d5f0a06e0ff39497ffc457b94a511f8bdda66628aa43 \
|
|
"$RUNNER_TEMP/pi-startup-36959948724.zip" | shasum -a 256 --check
|
|
- name: Measure four exact snapshots with pool size fixed before each process starts
|
|
timeout-minutes: 18
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/snapshot_pool.py \
|
|
--archive "$RUNNER_TEMP/pi-startup-36959948724.zip" \
|
|
--output "$GITHUB_WORKSPACE/pi-snapshot-pool-evidence"
|
|
- name: Admit complete diagnostic evidence within the storage bound
|
|
if: always()
|
|
id: diagnostic_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_diagnostic_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-snapshot-pool-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain original identities, four snapshot measurements and cleanup
|
|
if: always() && steps.diagnostic_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-snapshot-pool-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-snapshot-pool-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_pi_snapshot_streaming:
|
|
name: Retained B9 native Intel baseline/streaming ABBA (not qualification)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.diagnose_pi_snapshot_streaming
|
|
needs: authorize_manual
|
|
runs-on: macos-15-intel
|
|
timeout-minutes: 25
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
steps:
|
|
- name: Checkout trusted diagnostic helpers only
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: trusted-ci
|
|
persist-credentials: false
|
|
- name: Download exact failed qualification artifacts
|
|
timeout-minutes: 5
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api repos/paperclipai/paperclip/actions/artifacts/11208570718/zip \
|
|
> "$RUNNER_TEMP/pi-startup-36959948724.zip"
|
|
printf '%s %s\n' b82cf6c843006781e2e6d5f0a06e0ff39497ffc457b94a511f8bdda66628aa43 \
|
|
"$RUNNER_TEMP/pi-startup-36959948724.zip" | shasum -a 256 --check
|
|
- name: Measure four baseline/candidate snapshots with identical process-start pool4
|
|
timeout-minutes: 18
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/snapshot_streaming.py \
|
|
--archive "$RUNNER_TEMP/pi-startup-36959948724.zip" \
|
|
--output "$GITHUB_WORKSPACE/pi-snapshot-streaming-evidence"
|
|
- name: Admit complete diagnostic evidence within the storage bound
|
|
if: always()
|
|
id: diagnostic_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 -B trusted-ci/scripts/ci/pi-intel/admit_diagnostic_evidence.py \
|
|
--evidence "$GITHUB_WORKSPACE/pi-snapshot-streaming-evidence" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
- name: Retain original identities, four snapshot measurements and cleanup
|
|
if: always() && steps.diagnostic_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: pi-snapshot-streaming-${{ github.run_id }}
|
|
include-hidden-files: true
|
|
path: pi-snapshot-streaming-evidence/
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
manual_runner_verify:
|
|
name: Full offline Runner verification on GitHub-hosted Ubuntu
|
|
if: github.event_name == 'workflow_dispatch' && inputs.verify_runner
|
|
needs: authorize_manual
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 55
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
CI: "true"
|
|
PAPERCLIP_TELEMETRY_ENABLED: "false"
|
|
NPM_CONFIG_AUDIT: "false"
|
|
NPM_CONFIG_FUND: "false"
|
|
NPM_CONFIG_UPDATE_NOTIFIER: "false"
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
with:
|
|
node-version: 24.21.0
|
|
package-manager-cache: false
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
with:
|
|
version: 9.15.4
|
|
- name: Record exact source and install dependencies
|
|
timeout-minutes: 8
|
|
env:
|
|
SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
test "$(git rev-parse HEAD)" = "$SOURCE_SHA"
|
|
mkdir -p remote-runner-verification
|
|
git rev-parse HEAD > remote-runner-verification/source.txt
|
|
cp pnpm-lock.yaml remote-runner-verification/original-pnpm-lock.yaml
|
|
sha256sum pnpm-lock.yaml > remote-runner-verification/original-lock.sha256
|
|
# A stale stacked-branch lock may be resolved only to the reviewed overlay.
|
|
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile
|
|
cp pnpm-lock.yaml remote-runner-verification/resolved-pnpm-lock.yaml
|
|
sha256sum pnpm-lock.yaml > remote-runner-verification/resolved-lock.sha256
|
|
git diff -- pnpm-lock.yaml > remote-runner-verification/lock.diff
|
|
printf '%s pnpm-lock.yaml\n' "$EXPECTED_LOCK_SHA256" | sha256sum --check --strict
|
|
pnpm install --frozen-lockfile --ignore-scripts
|
|
- name: Select pinned Rust and install browser dependencies
|
|
timeout-minutes: 15
|
|
run: |
|
|
set -euo pipefail
|
|
cd packages/paperclip-runner
|
|
rustup show
|
|
rustc --version --verbose > ../../remote-runner-verification/rust.txt
|
|
pnpm exec playwright install --with-deps chromium
|
|
- name: Run the complete offline Runner verification
|
|
timeout-minutes: 35
|
|
run: |
|
|
set -uo pipefail
|
|
status=0
|
|
timeout --signal=TERM --kill-after=20s 34m \
|
|
pnpm --filter @paperclipai/paperclip-runner verify \
|
|
> remote-runner-verification/verify.log 2>&1 || status=$?
|
|
printf '%s\n' "$status" > remote-runner-verification/exit-code.txt
|
|
tail -n 100 remote-runner-verification/verify.log
|
|
exit "$status"
|
|
- name: Admit complete Runner evidence within the storage bound
|
|
if: always()
|
|
id: runner_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 - <<'PYTHON'
|
|
import hashlib, json, os, pathlib, shutil
|
|
root = pathlib.Path('remote-runner-verification')
|
|
stage = pathlib.Path('remote-runner-evidence-upload')
|
|
if not root.is_dir() or root.is_symlink(): raise RuntimeError('Runner evidence root is absent or unsafe')
|
|
if stage.exists() or stage.is_symlink(): raise RuntimeError('Evidence upload stage already exists')
|
|
runner_root = pathlib.Path('packages/paperclip-runner')
|
|
if any(p.is_symlink() for p in (runner_root, *runner_root.parents)): raise RuntimeError('Runner output ancestry is a symlink')
|
|
roots = [root]
|
|
def walk_error(error): raise error
|
|
# Owned output directories only: never traverse dependency/workspace symlinks.
|
|
for parent, directories, _files in os.walk('packages/paperclip-runner', followlinks=False, onerror=walk_error):
|
|
if any(name in ('playwright-report', 'test-results') for name in _files): raise RuntimeError('Browser evidence root is not a regular directory')
|
|
for name in list(directories):
|
|
path = pathlib.Path(parent) / name
|
|
if path.is_symlink():
|
|
if name in ('playwright-report', 'test-results'): raise RuntimeError('Browser evidence root is a symlink')
|
|
directories.remove(name)
|
|
elif name in ('playwright-report', 'test-results'):
|
|
roots.append(path)
|
|
directories.remove(name)
|
|
files, size, limit, seen = [], 0, 512 * 1024 * 1024, set()
|
|
for base in sorted(set(roots)):
|
|
for parent in (base, *base.parents):
|
|
if parent.is_symlink(): raise RuntimeError('Evidence symlink ancestry is not allowed')
|
|
if not base.is_dir(): raise RuntimeError('Evidence output is not a directory')
|
|
for path in sorted(base.rglob('*')):
|
|
if path.is_symlink(): raise RuntimeError('Evidence symlink is not allowed')
|
|
if path.is_dir(): continue
|
|
if not path.is_file(): raise RuntimeError('Nonregular evidence is not allowed')
|
|
if path in seen: continue
|
|
seen.add(path)
|
|
size += path.stat().st_size
|
|
if size > limit: raise RuntimeError('Complete Runner evidence exceeds 512 MiB bound')
|
|
digest = hashlib.sha256()
|
|
with path.open('rb') as source:
|
|
for chunk in iter(lambda: source.read(1024 * 1024), b''): digest.update(chunk)
|
|
files.append({'path': str(path), 'bytes': path.stat().st_size, 'sha256': digest.hexdigest()})
|
|
inventory = json.dumps({'bytes': size, 'roots': [str(p) for p in roots], 'discovery': 'owned directories; no symlink traversal', 'files': files}, indent=2) + '\n'
|
|
if not files or size + len(inventory.encode()) > limit: raise RuntimeError('Complete Runner evidence exceeds bound or is absent')
|
|
stage.mkdir()
|
|
for item in files:
|
|
source, destination = pathlib.Path(item['path']), stage / item['path']
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(source, destination, follow_symlinks=False)
|
|
if destination.is_symlink() or destination.stat().st_size != item['bytes']: raise RuntimeError('Evidence changed during staging')
|
|
digest = hashlib.sha256()
|
|
with destination.open('rb') as copied:
|
|
for chunk in iter(lambda: copied.read(1024 * 1024), b''): digest.update(chunk)
|
|
if digest.hexdigest() != item['sha256']: raise RuntimeError('Evidence digest changed during staging')
|
|
(stage / 'evidence-inventory.json').write_text(inventory)
|
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as output: output.write('admitted=true\n')
|
|
PYTHON
|
|
- name: Retain Runner verification evidence
|
|
if: always() && steps.runner_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: runner-full-verification-${{ github.run_id }}
|
|
path: remote-runner-evidence-upload/
|
|
include-hidden-files: true
|
|
retention-days: 14
|
|
|
|
manual_source_verify:
|
|
name: ${{ inputs.source_e2e_support_only && 'Focused E2E support verification on GitHub-hosted Ubuntu' || 'Full source verification on GitHub-hosted Ubuntu' }}
|
|
if: github.event_name == 'workflow_dispatch' && (inputs.verify_source || inputs.source_e2e_support_only)
|
|
needs: authorize_manual
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: ${{ inputs.source_e2e_support_only && 45 || inputs.source_root_tests_only && 200 || 295 }}
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
CI: "true"
|
|
PAPERCLIP_TELEMETRY_ENABLED: "false"
|
|
NPM_CONFIG_AUDIT: "false"
|
|
NPM_CONFIG_FUND: "false"
|
|
NPM_CONFIG_UPDATE_NOTIFIER: "false"
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
persist-credentials: false
|
|
- name: Checkout trusted read-only executable observer
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: .trusted-source-observer
|
|
sparse-checkout: scripts/ci/source-observer/executable_holders.py
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
- name: Record immutable source and planned checks
|
|
env:
|
|
SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
SOURCE_ROOT_TESTS_ONLY: ${{ inputs.source_root_tests_only }}
|
|
SOURCE_E2E_SUPPORT_ONLY: ${{ inputs.source_e2e_support_only }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p remote-source-verification
|
|
git -C .trusted-source-observer rev-parse HEAD > remote-source-verification/observer-source.txt
|
|
test "$(cat remote-source-verification/observer-source.txt)" = "${{ github.workflow_sha }}"
|
|
sha256sum .trusted-source-observer/scripts/ci/source-observer/executable_holders.py > remote-source-verification/observer-helper.sha256
|
|
git rev-parse HEAD > remote-source-verification/source.txt
|
|
test "$(cat remote-source-verification/source.txt)" = "$SOURCE_SHA"
|
|
printf '%s\n' "$EXPECTED_LOCK_SHA256" > remote-source-verification/expected-resolved-lock.sha256
|
|
cp pnpm-lock.yaml remote-source-verification/original-pnpm-lock.yaml
|
|
sha256sum pnpm-lock.yaml > remote-source-verification/original-lock.sha256
|
|
printf '%s\n' \
|
|
'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' \
|
|
> remote-source-verification/commands.txt
|
|
if [ "$SOURCE_ROOT_TESTS_ONLY" = true ]; then
|
|
printf '%s\n' 'pnpm test:run' > remote-source-verification/commands.txt
|
|
fi
|
|
git ls-tree -r -z HEAD > remote-source-verification/source-tree.zlist
|
|
git archive --format=tar HEAD | sha256sum > remote-source-verification/source-archive.sha256
|
|
if [ "$SOURCE_E2E_SUPPORT_ONLY" = true ]; then
|
|
printf '%s pnpm-lock.yaml\n' e11d69fa8702a906c293c1cb307c71df90d3b1f1617b3c23ebf17fa9a87fec79 | sha256sum --check --strict
|
|
printf '%s\n' \
|
|
'pnpm test:e2e:runner:typecheck' \
|
|
'pnpm test:e2e:runner:unit' \
|
|
'node cli/node_modules/tsx/dist/cli.mjs tests/runner-e2e/launch.ts --list' \
|
|
> remote-source-verification/commands.txt
|
|
fi
|
|
index=0
|
|
while IFS= read -r _check; do
|
|
index=$((index + 1))
|
|
printf 'not-run\n' > "remote-source-verification/check-$index.status"
|
|
: > "remote-source-verification/check-$index.log"
|
|
done < remote-source-verification/commands.txt
|
|
for phase in resolution lock-check install test-build-deps; do
|
|
printf 'not-run\n' > "remote-source-verification/$phase.status"
|
|
: > "remote-source-verification/$phase.log"
|
|
done
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
with:
|
|
node-version: 24.21.0
|
|
package-manager-cache: false
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
with:
|
|
version: 9.15.4
|
|
- name: Install the exact reviewed dependency graph without lifecycle scripts
|
|
timeout-minutes: 10
|
|
env:
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
node --version > remote-source-verification/node.txt
|
|
pnpm --version > remote-source-verification/pnpm.txt
|
|
test "$(cat remote-source-verification/node.txt)" = v24.21.0
|
|
test "$(cat remote-source-verification/pnpm.txt)" = 9.15.4
|
|
phase=resolution
|
|
trap 'printf "%s\n" "$?" > "remote-source-verification/$phase.status"' EXIT
|
|
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile > remote-source-verification/resolution.log 2>&1
|
|
printf '0\n' > remote-source-verification/resolution.status
|
|
cp pnpm-lock.yaml remote-source-verification/resolved-pnpm-lock.yaml
|
|
sha256sum pnpm-lock.yaml > remote-source-verification/resolved-lock.sha256
|
|
git diff -- pnpm-lock.yaml > remote-source-verification/lock.diff
|
|
phase=lock-check
|
|
printf '%s pnpm-lock.yaml\n' "$EXPECTED_LOCK_SHA256" | sha256sum --check --strict > remote-source-verification/lock-check.log 2>&1
|
|
printf '0\n' > remote-source-verification/lock-check.status
|
|
phase=install
|
|
pnpm install --frozen-lockfile --ignore-scripts > remote-source-verification/install.log 2>&1
|
|
- name: Build workspace prerequisites used by the root tests
|
|
timeout-minutes: 5
|
|
run: |
|
|
set -euo pipefail
|
|
# Match test:run:general and test:run:serialized on a clean checkout.
|
|
# The SDK exports dist files, which an ignore-scripts install cannot provide.
|
|
trap 'printf "%s\n" "$?" > remote-source-verification/test-build-deps.status' EXIT
|
|
timeout --verbose --signal=TERM --kill-after=15s 4m \
|
|
pnpm --filter @paperclipai/plugin-sdk ensure-build-deps \
|
|
> remote-source-verification/test-build-deps.log 2>&1
|
|
- name: Build Runner declarations for focused E2E support
|
|
if: inputs.source_e2e_support_only
|
|
timeout-minutes: 8
|
|
run: |
|
|
set -euo pipefail
|
|
# Server imports resolve the Runner package's generated dist declarations.
|
|
# The full source mode creates these during its earlier workspace checks.
|
|
trap 'printf "%s\n" "$?" > remote-source-verification/runner-typescript.status' EXIT
|
|
timeout --verbose --signal=TERM --kill-after=15s 7m \
|
|
pnpm --filter @paperclipai/paperclip-runner build:typescript \
|
|
> remote-source-verification/runner-typescript.log 2>&1
|
|
- name: Run every source check and retain each result
|
|
timeout-minutes: ${{ inputs.source_e2e_support_only && 28 || inputs.source_root_tests_only && 182 || 273 }}
|
|
env:
|
|
SOURCE_E2E_SUPPORT_ONLY: ${{ inputs.source_e2e_support_only }}
|
|
run: |
|
|
set -uo pipefail
|
|
status=0
|
|
index=0
|
|
progress_pid=
|
|
checks_started_at=$(date +%s)
|
|
trap 'if [ -n "$progress_pid" ]; then kill "$progress_pid" 2>/dev/null || true; fi' EXIT
|
|
observe_holders() {
|
|
# Exact executable identity only: no argv/environment reads or signals.
|
|
timeout --signal=TERM --kill-after=2s 10s python3 -B \
|
|
.trusted-source-observer/scripts/ci/source-observer/executable_holders.py \
|
|
--target "$GITHUB_WORKSPACE/packages/paperclip-runner/dist/bin/paperclip-runnerd" \
|
|
> "remote-source-verification/check-$index-holders-$1.json" \
|
|
2> "remote-source-verification/check-$index-holders-$1.stderr"
|
|
observer_status=$?
|
|
printf '%s\n' "$observer_status" > "remote-source-verification/check-$index-holders-$1.status"
|
|
if [ "$observer_status" -ne 0 ]; then status=1; fi
|
|
}
|
|
while IFS= read -r check; do
|
|
index=$((index + 1))
|
|
echo "Starting $check"
|
|
observe_holders before
|
|
printf 'running\n' > "remote-source-verification/check-$index.status"
|
|
# test:run executes all groups serially. Recorded server durations
|
|
# alone exceed 85 minutes; ordinary CI distributes them across shards.
|
|
check_timeout=15m
|
|
if [ "$check" = 'pnpm test:run' ]; then check_timeout=180m; fi
|
|
remaining=900
|
|
if [ "$SOURCE_E2E_SUPPORT_ONLY" = true ]; then
|
|
# Leave job time for final auditing and complete evidence upload.
|
|
remaining=$((1380 - $(date +%s) + checks_started_at))
|
|
if [ "$remaining" -lt 900 ] && [ "$remaining" -gt 0 ]; then check_timeout="${remaining}s"; fi
|
|
fi
|
|
date -u +%FT%TZ > "remote-source-verification/check-$index.started-at"
|
|
started_at=$(date +%s)
|
|
(
|
|
while sleep 60; do
|
|
echo "Still running $check ($(( $(date +%s) - started_at ))s elapsed)"
|
|
tail -n 2 "remote-source-verification/check-$index.log"
|
|
done
|
|
) &
|
|
progress_pid=$!
|
|
check_status=0
|
|
if [ "$remaining" -gt 0 ]; then
|
|
timeout --verbose --signal=TERM --kill-after=15s "$check_timeout" bash -c "$check" > "remote-source-verification/check-$index.log" 2>&1 || check_status=$?
|
|
else
|
|
check_status=124
|
|
printf 'Not launched: focused command budget exhausted.\n' > "remote-source-verification/check-$index.log"
|
|
fi
|
|
kill "$progress_pid" 2>/dev/null || true
|
|
wait "$progress_pid" 2>/dev/null || true
|
|
progress_pid=
|
|
printf '%s\n' "$(( $(date +%s) - started_at ))" > "remote-source-verification/check-$index.elapsed-seconds"
|
|
date -u +%FT%TZ > "remote-source-verification/check-$index.finished-at"
|
|
printf '%s\n' "$check_status" > "remote-source-verification/check-$index.status"
|
|
printf '%s\t%s\n' "$check_status" "$check" >> remote-source-verification/check-status.tsv
|
|
if [ "$check_status" -ne 0 ]; then status=1; fi
|
|
echo "Finished $check (exit $check_status)"
|
|
observe_holders after
|
|
done < remote-source-verification/commands.txt
|
|
exit "$status"
|
|
- name: Audit source and lock closure even on check failure
|
|
if: always()
|
|
timeout-minutes: 2
|
|
env:
|
|
SOURCE_SHA: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
run: |
|
|
set -uo pipefail
|
|
mkdir -p remote-source-verification
|
|
status=0
|
|
date -u +%FT%TZ > remote-source-verification/final-audit.started-at
|
|
git rev-parse HEAD > remote-source-verification/final-source.txt
|
|
test "$(cat remote-source-verification/final-source.txt)" = "$SOURCE_SHA" || status=1
|
|
git status --porcelain=v1 --untracked-files=no > remote-source-verification/final-source-status.txt
|
|
git diff HEAD -- . ':(exclude)pnpm-lock.yaml' > remote-source-verification/final-source.diff
|
|
git diff HEAD --quiet -- . ':(exclude)pnpm-lock.yaml' || status=1
|
|
git ls-tree -r -z HEAD > remote-source-verification/final-source-tree.zlist
|
|
cmp remote-source-verification/source-tree.zlist remote-source-verification/final-source-tree.zlist || status=1
|
|
git archive --format=tar HEAD | sha256sum > remote-source-verification/final-source-archive.sha256 || status=1
|
|
cmp remote-source-verification/source-archive.sha256 remote-source-verification/final-source-archive.sha256 || status=1
|
|
git show HEAD:pnpm-lock.yaml | sha256sum > remote-source-verification/final-tracked-lock.sha256 || status=1
|
|
test "$(cut -d ' ' -f 1 remote-source-verification/original-lock.sha256)" = "$(cut -d ' ' -f 1 remote-source-verification/final-tracked-lock.sha256)" || status=1
|
|
printf '%s pnpm-lock.yaml\n' "$EXPECTED_LOCK_SHA256" | sha256sum --check --strict > remote-source-verification/final-overlay-check.log 2>&1 || status=1
|
|
date -u +%FT%TZ > remote-source-verification/final-audit.finished-at
|
|
printf '%s\n' "$status" > remote-source-verification/final-audit.status
|
|
exit "$status"
|
|
- name: Capture the final lock state even on resolution failure
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -d remote-source-verification ] && [ -f pnpm-lock.yaml ]; then
|
|
cp pnpm-lock.yaml remote-source-verification/final-pnpm-lock.yaml
|
|
sha256sum pnpm-lock.yaml > remote-source-verification/final-lock.sha256
|
|
git diff -- pnpm-lock.yaml > remote-source-verification/final-lock.diff
|
|
fi
|
|
- name: Admit complete source evidence within the storage bound
|
|
if: always()
|
|
id: source_e2e_evidence
|
|
timeout-minutes: 2
|
|
run: |
|
|
python3 - <<'PYTHON'
|
|
import hashlib, json, os, pathlib
|
|
root = pathlib.Path('remote-source-verification')
|
|
if not root.is_dir() or root.is_symlink(): raise RuntimeError('Source evidence root is absent or unsafe')
|
|
files, size, limit = [], 0, 256 * 1024 * 1024
|
|
for path in sorted(root.rglob('*')):
|
|
if path.is_symlink(): raise RuntimeError('Evidence symlink is not allowed')
|
|
if path.is_dir(): continue
|
|
if not path.is_file(): raise RuntimeError('Nonregular evidence is not allowed')
|
|
size += path.stat().st_size
|
|
if size > limit: raise RuntimeError('Complete evidence exceeds 256 MiB bound')
|
|
digest = hashlib.sha256()
|
|
with path.open('rb') as source:
|
|
for chunk in iter(lambda: source.read(1024 * 1024), b''): digest.update(chunk)
|
|
files.append({'path': str(path.relative_to(root)), 'bytes': path.stat().st_size, 'sha256': digest.hexdigest()})
|
|
inventory = json.dumps({'bytes': size, 'files': files}, indent=2) + '\n'
|
|
if not files or size + len(inventory.encode()) > limit: raise RuntimeError('Complete evidence exceeds bound or is absent')
|
|
(root / 'evidence-inventory.json').write_text(inventory)
|
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as output: output.write('admitted=true\n')
|
|
PYTHON
|
|
- name: Retain source verification evidence even on failure
|
|
if: always() && steps.source_e2e_evidence.outputs.admitted == 'true'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: ${{ inputs.source_e2e_support_only && 'source-e2e-support' || inputs.source_root_tests_only && 'source-root-tests' || 'source-full-verification' }}-${{ github.run_id }}
|
|
path: remote-source-verification/
|
|
include-hidden-files: true
|
|
retention-days: ${{ inputs.source_e2e_support_only && 7 || 14 }}
|
|
|
|
manual_ajv_pack_diagnostic:
|
|
name: Pinned AJV directory-pack diagnostic on Linux
|
|
if: github.event_name == 'workflow_dispatch' && inputs.diagnose_ajv_pack
|
|
needs: authorize_manual
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ needs.authorize_manual.outputs.target_sha }}
|
|
persist-credentials: false
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
path: .ajv-diagnostic-driver
|
|
sparse-checkout: .github/scripts/diagnose-ajv-pack.py
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
with:
|
|
node-version: 24.21.0
|
|
package-manager-cache: false
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
|
with:
|
|
version: 9.15.4
|
|
- name: Install only exact reviewed dependency graph without scripts
|
|
timeout-minutes: 6
|
|
env:
|
|
EXPECTED_LOCK_SHA256: ${{ inputs.expected_resolved_lock_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
mkdir -p ajv-pack-diagnostic "$RUNNER_TEMP/ajv-install-home"
|
|
export HOME="$RUNNER_TEMP/ajv-install-home"
|
|
export NPM_CONFIG_USERCONFIG="$HOME/user.npmrc"
|
|
export NPM_CONFIG_GLOBALCONFIG="$HOME/global.npmrc"
|
|
touch "$NPM_CONFIG_USERCONFIG" "$NPM_CONFIG_GLOBALCONFIG"
|
|
test "$(git rev-parse HEAD)" = 34f49a201a804564cfae81e425266b3f9f987e30
|
|
test "$(node --version)" = v24.21.0
|
|
test "$(npm --version)" = 11.19.0
|
|
test "$(pnpm --version)" = 9.15.4
|
|
git rev-parse HEAD > ajv-pack-diagnostic/source.txt
|
|
git -C .ajv-diagnostic-driver rev-parse HEAD > ajv-pack-diagnostic/driver-source.txt
|
|
cp pnpm-lock.yaml ajv-pack-diagnostic/original-pnpm-lock.yaml
|
|
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile > ajv-pack-diagnostic/resolution.log 2>&1
|
|
cp pnpm-lock.yaml ajv-pack-diagnostic/resolved-pnpm-lock.yaml
|
|
printf '%s pnpm-lock.yaml\n' "$EXPECTED_LOCK_SHA256" | sha256sum --check --strict
|
|
pnpm install --frozen-lockfile --ignore-scripts > ajv-pack-diagnostic/install.log 2>&1
|
|
- name: Compare installed-layout and identical plain-package packing
|
|
timeout-minutes: 3
|
|
run: python3 .ajv-diagnostic-driver/.github/scripts/diagnose-ajv-pack.py
|
|
- name: Retain diagnostic evidence even on npm failure
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: ajv-pack-diagnostic-${{ github.run_id }}
|
|
path: ajv-pack-diagnostic/
|
|
retention-days: 14
|
|
|
|
manual_image:
|
|
name: Build and verify on EC2
|
|
if: github.event_name == 'workflow_dispatch' && !inputs.verify_runner && !inputs.verify_source && !inputs.diagnose_ajv_pack && !inputs.verify_pi_intel && !inputs.diagnose_pi_startup && !inputs.diagnose_pi_snapshot_pool && !inputs.diagnose_pi_snapshot_streaming && !inputs.source_e2e_support_only && !inputs.verify_pi_intel_retained
|
|
needs: authorize_manual
|
|
runs-on: runs-on/fleet=paperclip-public-pr-x64/env=public-ci
|
|
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 clean public npm installation
|
|
if: inputs.verify_public_install
|
|
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.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
|