Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
permissions:
contents: read
actions: read
pull-requests: read

concurrency:
group: deploy-${{ github.ref }}
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/testing-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down