From bf97a513ac8cdf50e1f56d243a5039827e567009 Mon Sep 17 00:00:00 2001 From: Richard Orchard Date: Thu, 13 Aug 2026 15:17:07 +0800 Subject: [PATCH] Fix deploy CI artifact lookup for squash merges on main. Deploy assumed merge commits and fell back to github.sha for single-parent squash merges, so it never found the PR's successful ci.yml web-publish artifact. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 26 +++++++++++++++++++------- docs/architecture/testing-policy.md | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f03fe762..07cb7f9f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,6 +10,7 @@ on: permissions: contents: read actions: read + pull-requests: read concurrency: group: deploy-${{ github.ref }} @@ -57,12 +58,11 @@ jobs: fi # Finds the ci.yml run that built+tested this exact code, so `deploy` can - # download its publish artifact instead of rebuilding. main is updated via - # merge commits (see branch protection / PR merge method), so github.sha - # here is the merge commit, not the PR head that ci.yml actually ran - # against — walk to the merge commit's second parent to get that head SHA. - # (A non-merge push, e.g. an admin push straight to main, has no second - # parent; fall back to github.sha itself in that case.) + # download its publish artifact instead of rebuilding. github.sha on main is + # the merge result, not the PR head that ci.yml ran against: + # - merge commits (2 parents): second parent is the PR head + # - squash/rebase merges (1 parent): look up the associated PR head via API + # - direct pushes with no PR: fall back to github.sha itself resolve-ci-run: runs-on: ubuntu-latest needs: changes @@ -77,12 +77,24 @@ jobs: - name: Determine PR head SHA id: sha + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PARENTS=($(git rev-list --parents -n 1 HEAD)) if [ "${#PARENTS[@]}" -ge 3 ]; then HEAD_SHA="${PARENTS[2]}" + echo "Merge commit — using second parent as PR head SHA." else - HEAD_SHA="${PARENTS[0]}" + # Squash/rebase merge: the commit on main is not the PR head that + # ci.yml tested. Resolve via the commit→PR association. + HEAD_SHA=$(gh api "repos/${{ github.repository }}/commits/${PARENTS[0]}/pulls" \ + --jq '.[0].head.sha // empty') + if [ -n "$HEAD_SHA" ]; then + echo "Single-parent commit — resolved associated PR head SHA." + else + HEAD_SHA="${PARENTS[0]}" + echo "No associated PR — falling back to commit SHA itself." + fi fi echo "Resolved PR head SHA: $HEAD_SHA" echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" diff --git a/docs/architecture/testing-policy.md b/docs/architecture/testing-policy.md index 738c0461..48ae5dd8 100644 --- a/docs/architecture/testing-policy.md +++ b/docs/architecture/testing-policy.md @@ -329,7 +329,7 @@ These gates are guardrails, not a replacement for useful assertions. New or chan | `smoke-test` | Published app, curl `/health`, `/`, `/news` (after `coverage`) | Yes | | `e2e-test` | Playwright suite on a self-hosted `e2e` runner (Windows or macOS, after `coverage`) | Yes (required PR merge gate) | -CI/CD uses two workflows. `.github/workflows/ci.yml` runs the pull-request build, deterministic tests, coverage gates, conditional `ef-migrations`, smoke test, and required e2e merge gate. After merge, `.github/workflows/deploy.yml` resolves the `ci.yml` run that built and tested the merged PR's head commit and reuses its `web-publish` artifact (no rebuild), then runs `migrate` → `deploy` → `post-deploy-smoke`. The PR `ef-migrations` job uses the same migration connection string as deploy so SQL Server failures are caught before merge. +CI/CD uses two workflows. `.github/workflows/ci.yml` runs the pull-request build, deterministic tests, coverage gates, conditional `ef-migrations`, smoke test, and required e2e merge gate. After merge, `.github/workflows/deploy.yml` resolves the `ci.yml` run that built and tested the merged PR's head commit (via merge-commit second parent, or the commit→PR association for squash/rebase merges) and reuses its `web-publish` artifact (no rebuild), then runs `migrate` → `deploy` → `post-deploy-smoke`. The PR `ef-migrations` job uses the same migration connection string as deploy so SQL Server failures are caught before merge. Two further workflows run on a schedule only and never gate a PR merge or a deploy: `.github/workflows/nightly-legacy-checks.yml` (legacy read/write probes, then the real-data Playwright UI suite, then a residue check — see "Data Integration Tests" and "Nightly UI Regression (Real Data)" above) and `.github/workflows/livesite-readonly-sweep.yml` (the live-site read-only sweep). Both are continuous signal for catching drift, not merge gates; a failure there does not block or revert anything automatically.