refactor(ci): plumb regenerated PR lockfile via artifact (#7629)

## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - The CI pipeline (`.github/workflows/pr.yml`) gates every PR with a
`policy` job and a fleet of downstream test/build/release jobs
> - `policy` blocks committing `pnpm-lock.yaml` from non-dependabot,
non-`chore/refresh-lockfile` branches and (when manifests change)
already regenerates the lockfile in memory — but throws it away
> - Every downstream job runs `pnpm install --frozen-lockfile`, so
manifest-only PRs from human/agent branches deadlock: they can't commit
the lockfile and can't pass `--frozen-lockfile` either, and
`refresh-lockfile.yml` only runs `on: push: master`
> - This pull request closes the loop by uploading the regenerated
lockfile as a workflow artifact (`pr-lockfile`) from `policy` and
restoring it in each downstream job before `pnpm install
--frozen-lockfile` runs
> - The benefit is that any manifest-only PR (e.g. a routine dep bump)
goes green end-to-end without a separate refresh-lockfile round-trip,
and unrelated PRs are unaffected because the download step is
best-effort (`continue-on-error: true`)

## What Changed

- `.github/workflows/pr.yml::policy`: add `id: regen_lockfile` to the
existing manifest-detection step, emit a `regenerated` output, and
conditionally `actions/upload-artifact@v4` `pnpm-lock.yaml` as
`pr-lockfile` (retention 1 day) when a manifest changed
- `.github/workflows/pr.yml` (every downstream job —
`typecheck_release_registry`, `general_tests` [3 matrix], `build`,
`verify_serialized_server` [4 matrix], `canary_dry_run`, `e2e`): add a
`Restore regenerated PR lockfile (if policy uploaded one)` step using
`actions/download-artifact@v4` with `continue-on-error: true`, placed
before the existing `pnpm install --frozen-lockfile`
- `.github/workflows/pr.yml::canary_dry_run`: guard the pre-existing
`git checkout -- pnpm-lock.yaml` so it no longer clobbers an
artifact-restored lockfile when one is present

## Verification

- `node -e "require('js-yaml').load(...)"` parses the file; all 6
downstream jobs have the restore step, `policy` has the upload step
- This branch is itself a manifest-untouched PR, so `policy` will not
upload an artifact and every downstream job will skip the download
silently — the existing `--frozen-lockfile` path is exercised end-to-end
with no behavioral change for non-manifest PRs
- Once merged, the next manifest-touching PR (PAPA-530's #7581) will
exercise the artifact path and is expected to go green

## Risks

- **Behavior on non-manifest PRs:** the download step uses
`continue-on-error: true` so a missing artifact is silently ignored; net
behavior on PRs that don't change manifests is identical to today
- **Concurrency / cross-PR artifact bleed:** artifacts are scoped to a
single workflow run, so two concurrent PRs can't see each other's
`pr-lockfile`
- **`canary_dry_run`:** the prior unconditional `git checkout --
pnpm-lock.yaml` was a defensive no-op when `--frozen-lockfile` is
honored; the new conditional preserves that for non-manifest PRs and
additionally preserves the artifact-restored lockfile for manifest PRs

## Model Used

- Claude (Anthropic) — `claude-opus-4-7`, extended thinking, tool use
enabled

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
Devin Foley
2026-06-05 20:26:33 -07:00
committed by GitHub
parent 4693d770aa
commit f48905398c
+72 -2
View File
@@ -13,6 +13,8 @@ jobs:
policy:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
lockfile_regenerated: ${{ steps.regen_lockfile.outputs.regenerated }}
steps:
- name: Checkout repository
@@ -63,13 +65,30 @@ jobs:
node ./scripts/check-release-package-bootstrap.mjs "${changed_paths[@]}"
- name: Validate dependency resolution when manifests change
id: regen_lockfile
run: |
changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")"
manifest_pattern='(^|/)package\.json$|^pnpm-workspace\.yaml$|^\.npmrc$|^pnpmfile\.(cjs|js|mjs)$'
if printf '%s\n' "$changed" | grep -Eq "$manifest_pattern"; then
pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile
echo "regenerated=1" >> "$GITHUB_OUTPUT"
else
echo "regenerated=0" >> "$GITHUB_OUTPUT"
fi
# Manifest-only PRs (where pnpm-lock.yaml stays at base because the policy
# job above blocks committing it) need the regenerated lockfile for the
# downstream `pnpm install --frozen-lockfile` steps. Upload it here so
# every job consumes the same hash without recomputing.
- name: Upload regenerated lockfile for downstream jobs
if: steps.regen_lockfile.outputs.regenerated == '1'
uses: actions/upload-artifact@v4
with:
name: pr-lockfile
path: pnpm-lock.yaml
retention-days: 1
if-no-files-found: error
typecheck_release_registry:
name: Typecheck + Release Registry
needs: [policy]
@@ -85,6 +104,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -125,6 +151,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -171,6 +204,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -214,6 +254,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -241,6 +288,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -251,11 +305,20 @@ jobs:
run: pnpm install --frozen-lockfile
# `release.sh` always executes its Step 2/7 workspace build, even when
# `--skip-verify` bypasses the initial verification gate.
# `--skip-verify` bypasses the initial verification gate. release.sh
# also requires a clean working tree, so any in-place lockfile churn
# from `pnpm install --frozen-lockfile` must be reverted first — unless
# the policy job uploaded a regenerated lockfile (manifest-only PRs),
# in which case we keep the artifact-restored copy so the workspace
# build sees a lockfile that matches the manifest.
- name: Release canary dry run via release.sh internal build
env:
USED_ARTIFACT_LOCKFILE: ${{ needs.policy.outputs.lockfile_regenerated || '0' }}
run: |
git checkout -B master HEAD
git checkout -- pnpm-lock.yaml
if [ "$USED_ARTIFACT_LOCKFILE" != "1" ]; then
git checkout -- pnpm-lock.yaml
fi
./scripts/release.sh canary --skip-verify --dry-run
e2e:
@@ -272,6 +335,13 @@ jobs:
with:
version: 9.15.4
- name: Restore regenerated PR lockfile (if policy uploaded one)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: pr-lockfile
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with: