ci(migrations): guard against editing already-committed migrations #415

Merged
nrupard merged 2 commits from feat/DEV-395-migration-immutability-guard into main 2026-06-30 22:53:17 +02:00
Owner

Implements DEV-395. Prevents the migration-checksum break that blocked the v0.4.0 deploy on nc-01.

Problem

sqlx stores a SHA-384 checksum of each migration in _sqlx_migrations on apply and re-verifies on every startup. Editing a migration already applied to any DB makes it refuse to boot: migration N was previously applied but has been modified. That is exactly what happened: 023_seed_data.sql was edited (seed cleanup, commit 398c0ba) after a build had applied the earlier version, so v0.4.0 could not start on prod.

Guard

scripts/check-migration-immutability.nu (nushell, mirrors the existing check-migration-prefixes.nu):

  • Diffs migrations/ against the merge-base with origin/main (git diff --diff-filter=MRD --name-only origin/main...HEAD -- migrations/).
  • Fails (exit 1) if any migration file already present on main was Modified, Renamed, or Deleted, listing the file(s) and telling the author to add a new migration instead.
  • New migration files (Added) always pass.
  • Fails loud (exit 2) if the diff cannot run, so a broken base can never silently read as "nothing changed".

Wired into .forgejo/workflows/check.yml next to the prefix check, with fetch-depth: 0 on the checkout so the merge-base is available.

Verified locally

  • No migration change -> pass (exit 0).
  • Committed edit to 023_seed_data.sql -> fail (exit 1), names the file.
  • New migration added -> pass (exit 0).

Notes

  • The check gates PRs (the merge path). Direct pushes to main that edit a migration are not caught by origin/main...HEAD (merge-base equals HEAD post-push); main should stay PR-gated.
  • Documented in CLAUDE.md (migrations are immutable; add a new one).
  • A sibling DEV issue / follow-up: extend bunyip's check-migration-versions.sh with the same content-immutability check (it currently only enforces version-number uniqueness).
Implements DEV-395. Prevents the migration-checksum break that blocked the v0.4.0 deploy on nc-01. ## Problem sqlx stores a SHA-384 checksum of each migration in `_sqlx_migrations` on apply and re-verifies on every startup. Editing a migration already applied to any DB makes it refuse to boot: `migration N was previously applied but has been modified`. That is exactly what happened: `023_seed_data.sql` was edited (seed cleanup, commit 398c0ba) after a build had applied the earlier version, so v0.4.0 could not start on prod. ## Guard `scripts/check-migration-immutability.nu` (nushell, mirrors the existing `check-migration-prefixes.nu`): - Diffs `migrations/` against the merge-base with `origin/main` (`git diff --diff-filter=MRD --name-only origin/main...HEAD -- migrations/`). - Fails (exit 1) if any migration file already present on `main` was Modified, Renamed, or Deleted, listing the file(s) and telling the author to add a new migration instead. - New migration files (Added) always pass. - Fails loud (exit 2) if the diff cannot run, so a broken base can never silently read as "nothing changed". Wired into `.forgejo/workflows/check.yml` next to the prefix check, with `fetch-depth: 0` on the checkout so the merge-base is available. ## Verified locally - No migration change -> pass (exit 0). - Committed edit to `023_seed_data.sql` -> fail (exit 1), names the file. - New migration added -> pass (exit 0). ## Notes - The check gates PRs (the merge path). Direct pushes to `main` that edit a migration are not caught by `origin/main...HEAD` (merge-base equals HEAD post-push); main should stay PR-gated. - Documented in CLAUDE.md (migrations are immutable; add a new one). - A sibling DEV issue / follow-up: extend bunyip's `check-migration-versions.sh` with the same content-immutability check (it currently only enforces version-number uniqueness).
ci(migrations): guard against editing already-committed migrations
Some checks failed
E2E / Playwright against staging (pull_request) Failing after 1m36s
Check / fmt + clippy + build + tests (pull_request) Successful in 3m7s
Integration / integration tests (pull_request) Successful in 10m36s
21126e86fb
sqlx records a SHA-384 checksum of each migration in `_sqlx_migrations` when it applies it and re-verifies on every startup, so editing a migration that has already been applied to any database makes that database refuse to boot ("migration N was previously applied but has been modified"). This is exactly how the v0.4.0 deploy broke nc-01: `023_seed_data.sql` was edited (a routine seed cleanup) after a build had already applied the earlier version.

Add `scripts/check-migration-immutability.nu`: it diffs `migrations/` against the merge-base with `origin/main` (`origin/main...HEAD`, `--diff-filter=MRD`) and fails if any migration file already present on `main` was modified, renamed, or deleted. Adding a new migration is always allowed. It fails loud (exit 2) if the diff cannot run, so a broken base can never read as "nothing changed". Wired into `.forgejo/workflows/check.yml` next to the existing prefix-uniqueness check, with `fetch-depth: 0` on the checkout so the merge-base is available.

Documents the immutability rule in CLAUDE.md.

#DEV-395

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: nrupard <natrsmith11@gmail.com>
Merge branch 'main' into feat/DEV-395-migration-immutability-guard
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 43s
Check / fmt + clippy + build + tests (pull_request) Successful in 1m54s
Create release / Gate (release-branch merges only) (pull_request) Successful in 18s
Create release / Create release from merged PR (pull_request) Has been skipped
Integration / integration tests (pull_request) Successful in 9m5s
d69e006d49
nrupard scheduled this pull request to auto merge when all checks succeed 2026-06-30 22:52:21 +02:00
nrupard deleted branch feat/DEV-395-migration-immutability-guard 2026-06-30 22:53:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!415
No description provided.