Two PRs targeting staging can each add a migration with the same numeric prefix (e.g. 044_*.up.sql). Each passes CI independently. They collide at merge time. Worst case: second migration silently doesn't apply and prod schema drifts from what the code expects. Caught manually 2026-04-30 during PR #2276 rebase: 044_runtime_image_pins collided with 044_platform_inbound_secret from RFC #2312. This workflow makes that detection automatic at PR-open time. How it works: scripts/ops/check_migration_collisions.py runs on every PR that touches workspace-server/migrations/**. For each new/modified migration filename, extracts the numeric prefix and checks: 1. Does the base branch already have a DIFFERENT migration file with the same prefix? (PR branched off an old base, base advanced and another PR landed the same number — needs rebase.) 2. Is another OPEN PR (not this one) also adding a migration with the same prefix? (Race-window collision — both pass CI separately, would collide at merge time.) Either case → exit 1 with a clear ::error:: message naming the conflicting PR(s) so the author knows what to renumber. Implementation notes: - Uses git ls-tree (not working-tree walk) so it works against any base ref without checkout. - Uses gh pr diff --name-only per open PR, bounded by `gh pr list --limit 100`. ~30s worst case for a busy repo, <5s normally. - --diff-filter=AM picks up Added or Modified — renaming a migration in place is also flagged (intentional; renaming migrations isn't safe). - Same filename in both PR and base = no collision (PR is editing in-place, fine). Tests: scripts/ops/test_check_migration_collisions.py — 9 cases on the regex classifier (the load-bearing piece). End-to-end git/gh path is exercised by running the workflow against real PRs. Hard-gates Tier 1 item 1 (#2341). Cheapest, cleanest gate. Catches one specific class of merge-time foot-gun automatically. Refs hard-gates discussion 2026-04-30. Tier 1 of 4 (others tracked in #2342, #2343, #2344).
52 lines
1.9 KiB
YAML
52 lines
1.9 KiB
YAML
name: Check migration collisions
|
|
|
|
# Hard gate (#2341): fails a PR that adds a migration prefix already
|
|
# claimed by the base branch or another open PR. Caught manually 2026-04-30
|
|
# during PR #2276 rebase: 044_runtime_image_pins collided with
|
|
# 044_platform_inbound_secret from RFC #2312. This workflow makes that
|
|
# check automatic.
|
|
#
|
|
# Trigger model: pull_request only — there's no value running this on
|
|
# pushes to staging or main (those are post-merge; the gate must fire
|
|
# pre-merge to be useful). Path filter scopes to PRs that actually touch
|
|
# migrations.
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
paths:
|
|
- 'workspace-server/migrations/**'
|
|
- 'scripts/ops/check_migration_collisions.py'
|
|
- '.github/workflows/check-migration-collisions.yml'
|
|
|
|
permissions:
|
|
contents: read
|
|
# gh pr list/diff need read access to other PRs
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
check:
|
|
name: Migration version collision check
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
# Need history to diff against base ref
|
|
fetch-depth: 0
|
|
|
|
- name: Detect collisions
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
BASE_REF: origin/${{ github.event.pull_request.base.ref }}
|
|
HEAD_REF: ${{ github.event.pull_request.head.sha }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
# gh CLI uses GH_TOKEN from env
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# Make sure base ref is fetched (checkout@v4 with fetch-depth=0
|
|
# fetches history but not necessarily named refs in the form we
|
|
# expect; explicit fetch is cheap insurance).
|
|
git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1 || true
|
|
python3 scripts/ops/check_migration_collisions.py
|