ci: add migration integrity gate for migration-relevant changes
This commit is contained in:
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -62,6 +62,36 @@ jobs:
|
||||
nextjs-${{ runner.os }}-node-${{ steps.setup-node.outputs.node-version }}-
|
||||
|
||||
|
||||
|
||||
- name: Run migration integrity gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD_SHA="${{ github.sha }}"
|
||||
git fetch --no-tags --depth=1 origin "$BASE_SHA"
|
||||
else
|
||||
BASE_SHA="${{ github.event.before }}"
|
||||
HEAD_SHA="${{ github.sha }}"
|
||||
fi
|
||||
|
||||
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED_FILES"
|
||||
|
||||
if ! echo "$CHANGED_FILES" | grep -Eq '^backend/(app/models|db|migrations|alembic\.ini)'; then
|
||||
echo "No migration-relevant backend changes detected; skipping migration gate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$CHANGED_FILES" | grep -Eq '^backend/app/models/' && ! echo "$CHANGED_FILES" | grep -Eq '^backend/migrations/versions/'; then
|
||||
echo "Model changes detected without a migration under backend/migrations/versions/."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make backend-migration-check
|
||||
|
||||
- name: Run backend checks
|
||||
env:
|
||||
# Keep CI builds deterministic.
|
||||
|
||||
22
Makefile
22
Makefile
@@ -104,6 +104,28 @@ frontend-test: frontend-tooling ## Frontend tests (vitest)
|
||||
backend-migrate: ## Apply backend DB migrations (uses backend/migrations)
|
||||
cd $(BACKEND_DIR) && uv run alembic upgrade head
|
||||
|
||||
.PHONY: backend-migration-check
|
||||
backend-migration-check: ## Validate Alembic migrations on clean Postgres (upgrade head + single-head sanity)
|
||||
@set -euo pipefail; \
|
||||
CONTAINER_NAME="mc-migration-check-$$RANDOM"; \
|
||||
docker run -d --rm --name $$CONTAINER_NAME -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=migration_ci -p 55432:5432 postgres:16 >/dev/null; \
|
||||
cleanup() { docker rm -f $$CONTAINER_NAME >/dev/null 2>&1 || true; }; \
|
||||
trap cleanup EXIT; \
|
||||
for i in $$(seq 1 30); do \
|
||||
if docker exec $$CONTAINER_NAME pg_isready -U postgres -d migration_ci >/dev/null 2>&1; then break; fi; \
|
||||
sleep 1; \
|
||||
if [ $$i -eq 30 ]; then echo "Postgres did not become ready"; exit 1; fi; \
|
||||
done; \
|
||||
cd $(BACKEND_DIR) && \
|
||||
AUTH_MODE=local \
|
||||
LOCAL_AUTH_TOKEN=ci-local-token-ci-local-token-ci-local-token-ci-local-token \
|
||||
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:55432/migration_ci \
|
||||
uv run alembic upgrade head && \
|
||||
AUTH_MODE=local \
|
||||
LOCAL_AUTH_TOKEN=ci-local-token-ci-local-token-ci-local-token-ci-local-token \
|
||||
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:55432/migration_ci \
|
||||
uv run alembic heads | grep -q "(head)"
|
||||
|
||||
.PHONY: build
|
||||
build: frontend-build ## Build artifacts
|
||||
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
# Development workflow
|
||||
|
||||
Placeholder: see root `README.md` for current setup steps.
|
||||
## Migration integrity gate (CI)
|
||||
|
||||
CI enforces a migration integrity gate to prevent merge-time schema breakages.
|
||||
|
||||
### What it validates
|
||||
|
||||
- Alembic migrations can apply from a clean Postgres database (`upgrade head`)
|
||||
- Alembic revision graph resolves to a head revision after migration apply
|
||||
- On migration-relevant PRs, CI also checks that model changes are accompanied by migration updates
|
||||
|
||||
If any of these checks fails, CI fails and the PR is blocked.
|
||||
|
||||
### Local reproduction
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
make backend-migration-check
|
||||
```
|
||||
|
||||
This command starts a temporary Postgres container, runs migration checks, and cleans up the container.
|
||||
|
||||
Reference in New Issue
Block a user