max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+26 insertions,
-0 deletions
| @@ -40,6 +40,32 @@ | |||
| 40 | 40 | echo "pre-commit: gitleaks not installed; skipping secret scan (astra gates on push)." | |
| 41 | 41 | fi | |
| 42 | 42 | ||
| 43 | + | # --- migration immutability ------------------------------------------------- | |
| 44 | + | # sqlx checksums a migration's whole file when it runs it and refuses one whose | |
| 45 | + | # bytes changed since. So editing a migration that has already been applied | |
| 46 | + | # anywhere breaks every deploy against that database with "previously applied | |
| 47 | + | # but modified" — for a comment edit, and for a line-ending change, exactly as | |
| 48 | + | # much as for a schema change. | |
| 49 | + | # | |
| 50 | + | # The 2026-07-27 exorcise sweep rewrote comments in 29 applied migrations and | |
| 51 | + | # converted one from CRLF to LF. Nothing in the test suite checksums a | |
| 52 | + | # migration, so it stayed invisible while it blocked every server deploy for | |
| 53 | + | # three days. Hence a gate at commit time: a migration that already exists in | |
| 54 | + | # HEAD may not be modified, renamed, or deleted. Adding a new one is always fine. | |
| 55 | + | touched="$(git diff --cached --name-only --diff-filter=MDR -- '*migrations/*.sql')" | |
| 56 | + | if [ -n "$touched" ]; then | |
| 57 | + | echo "pre-commit: these already-committed migrations were modified, renamed, or deleted:" | |
| 58 | + | while IFS= read -r m; do | |
| 59 | + | [ -n "$m" ] && echo " $m" | |
| 60 | + | done <<< "$touched" | |
| 61 | + | echo " A migration is immutable once applied; sqlx checksums the whole" | |
| 62 | + | echo " file, comments included. Write a new migration instead." | |
| 63 | + | echo " Bypass ONLY if it has never been applied anywhere, including" | |
| 64 | + | echo " prod, staging, and your dev database: git commit --no-verify." | |
| 65 | + | exit 1 | |
| 66 | + | fi | |
| 67 | + | echo "pre-commit: migrations unmodified." | |
| 68 | + | ||
| 43 | 69 | # Paths the gate ignores (extended regex, matched against repo-relative paths). | |
| 44 | 70 | # Empty means check everything. | |
| 45 | 71 | SKIP_PATHS="${SKIP_PATHS:-}" |