mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-07 07:23:08 +02:00
ci(runner): prepare target lockfile once for paid validation (#12774)
## Thinking Path > - The trusted target-branch runner workflow checks out PR code before paid tests. > - PR policy intentionally forbids manual lockfile commits. > - Some runner changes legitimately alter pnpm patch hashes. > - Frozen installs therefore fail before test selection. > - Resolve one script-disabled lockfile from the authorized immutable target SHA and distribute it by exact artifact ID and digest. > - Keep provider credentials and trusted reporting outside this resolution job. ## Linked Issues or Issue Description Target-branch paid runner campaigns currently fail frozen install when a PR changes pnpm patch content, even though ordinary PR CI regenerates the lockfile. ## What Changed - Added one credential-free target-lock job that resolves the authorized immutable target SHA with lifecycle scripts disabled. - Uploaded the resolved lockfile with its SHA-256 and restored it by exact artifact ID before every target-code frozen install. - Left trusted reporting and history jobs on the workflow SHA. - Changed the disabled-AWS fallback from unavailable ubuntu-latest-m to ubuntu-latest. ## Risks The workflow evaluates pnpm lockfile resolution from authorized target code. That job receives no provider credentials, disables lifecycle scripts, rejects unrelated workspace mutations, and exposes only a digest-verified lockfile artifact. Paid-secret jobs consume only that lockfile after exact artifact-ID and SHA-256 validation. ## Verification - Runner workflow-security focused tests pass. - actionlint passes. - Prettier and git diff checks pass. ## Model Used OpenAI Codex, GPT-5. ## Checklist - [x] Change is narrowly scoped to paid runner orchestration. - [x] Target lock resolution has no provider credentials and disables lifecycle scripts. - [x] Downloaded artifacts are selected by exact artifact ID and verified by SHA-256. - [x] Trusted reporting and history jobs remain on the workflow SHA.
This commit is contained in:
1 parent
39898ab22f
commit
6e50ca9d0a
4 files changed
+236
-30
No files matched your search
@@ -121,7 +121,7 @@ jobs:
|
||||
AWS_PAID_RUNNER_ENABLED: ${{ vars.RUNNER_E2E_AWS_ENABLED }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
github_runner='ubuntu-latest-m'
|
||||
github_runner='ubuntu-latest'
|
||||
aws_runner='runs-on/fleet=paperclip-public-pr-x64/env=public-ci'
|
||||
|
||||
if [ "$AWS_PAID_RUNNER_ENABLED" = true ]; then
|
||||
@@ -140,9 +140,56 @@ jobs:
|
||||
echo '::notice title=Paid runner routing::RUNNER_E2E_AWS_ENABLED is not true; using the existing paid runner'
|
||||
fi
|
||||
|
||||
target_lock:
|
||||
name: Resolve target pnpm lockfile
|
||||
needs: authorize
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
artifact_id: ${{ steps.upload.outputs.artifact-id }}
|
||||
lock_sha256: ${{ steps.lock.outputs.sha256 }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
ref: ${{ needs.authorize.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
|
||||
|
||||
- name: Resolve target lockfile without lifecycle scripts
|
||||
id: lock
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pnpm install --ignore-scripts --no-frozen-lockfile --lockfile-only
|
||||
test -s pnpm-lock.yaml
|
||||
unexpected="$(git status --short | awk '$2 != "pnpm-lock.yaml" { print }')"
|
||||
if [ -n "$unexpected" ]; then
|
||||
echo "Lockfile resolution changed files other than pnpm-lock.yaml:" >&2
|
||||
echo "$unexpected" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "sha256=$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload resolved target lockfile
|
||||
id: upload
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: runner-e2e-target-pnpm-lock-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: pnpm-lock.yaml
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
catalog:
|
||||
name: Validate catalog and select cells
|
||||
needs: authorize
|
||||
needs: [authorize, target_lock]
|
||||
if: github.event_name != 'schedule' || vars.RUNNER_FULL_STACK_E2E_NIGHTLY_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
@@ -160,6 +207,26 @@ jobs:
|
||||
ref: ${{ needs.authorize.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download resolved target lockfile
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
artifact-ids: ${{ needs.target_lock.outputs.artifact_id }}
|
||||
path: ${{ runner.temp }}/runner-e2e-target-lock
|
||||
|
||||
- name: Restore resolved target lockfile
|
||||
env:
|
||||
TARGET_SHA: ${{ needs.authorize.outputs.target_sha }}
|
||||
EXPECTED_LOCK_SHA256: ${{ needs.target_lock.outputs.lock_sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test "$(git rev-parse HEAD)" = "$TARGET_SHA"
|
||||
lock="$RUNNER_TEMP/runner-e2e-target-lock/pnpm-lock.yaml"
|
||||
test -f "$lock"
|
||||
test "$(find "$(dirname "$lock")" -type f | wc -l | tr -d ' ')" = 1
|
||||
test "$(sha256sum "$lock" | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
with:
|
||||
version: 9.15.4
|
||||
@@ -249,7 +316,7 @@ jobs:
|
||||
|
||||
daytona_image:
|
||||
name: Publish verified Daytona image
|
||||
needs: [authorize, catalog]
|
||||
needs: [authorize, target_lock, catalog]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
@@ -266,6 +333,26 @@ jobs:
|
||||
ref: ${{ needs.authorize.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download resolved target lockfile
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
artifact-ids: ${{ needs.target_lock.outputs.artifact_id }}
|
||||
path: ${{ runner.temp }}/runner-e2e-target-lock
|
||||
|
||||
- name: Restore resolved target lockfile
|
||||
env:
|
||||
TARGET_SHA: ${{ needs.authorize.outputs.target_sha }}
|
||||
EXPECTED_LOCK_SHA256: ${{ needs.target_lock.outputs.lock_sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test "$(git rev-parse HEAD)" = "$TARGET_SHA"
|
||||
lock="$RUNNER_TEMP/runner-e2e-target-lock/pnpm-lock.yaml"
|
||||
test -f "$lock"
|
||||
test "$(find "$(dirname "$lock")" -type f | wc -l | tr -d ' ')" = 1
|
||||
test "$(sha256sum "$lock" | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- name: No Daytona image needed
|
||||
id: local_only
|
||||
if: needs.catalog.outputs.needs_daytona != 'true'
|
||||
@@ -355,7 +442,7 @@ jobs:
|
||||
|
||||
test:
|
||||
name: ${{ matrix.executionId }}
|
||||
needs: [authorize, catalog, daytona_image]
|
||||
needs: [authorize, target_lock, catalog, daytona_image]
|
||||
# The authorize job selects only one of two literal, reviewed runner labels;
|
||||
# no dispatch input or repository variable can inject an arbitrary label.
|
||||
runs-on: ${{ needs.authorize.outputs.test_runner }}
|
||||
@@ -390,6 +477,26 @@ jobs:
|
||||
ref: ${{ needs.authorize.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download resolved target lockfile
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
artifact-ids: ${{ needs.target_lock.outputs.artifact_id }}
|
||||
path: ${{ runner.temp }}/runner-e2e-target-lock
|
||||
|
||||
- name: Restore resolved target lockfile
|
||||
env:
|
||||
TARGET_SHA: ${{ needs.authorize.outputs.target_sha }}
|
||||
EXPECTED_LOCK_SHA256: ${{ needs.target_lock.outputs.lock_sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test "$(git rev-parse HEAD)" = "$TARGET_SHA"
|
||||
lock="$RUNNER_TEMP/runner-e2e-target-lock/pnpm-lock.yaml"
|
||||
test -f "$lock"
|
||||
test "$(find "$(dirname "$lock")" -type f | wc -l | tr -d ' ')" = 1
|
||||
test "$(sha256sum "$lock" | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
with:
|
||||
version: 9.15.4
|
||||
@@ -398,7 +505,10 @@ jobs:
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
# This job receives provider credentials only in the final paid-test
|
||||
# step. Keep target-selected dependency lifecycle code from running in
|
||||
# the protected environment during setup.
|
||||
- run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Build runner TypeScript prerequisites
|
||||
run: pnpm --filter @paperclipai/paperclip-eval-kernel build
|
||||
|
||||
Reference in new issue
Block a user