From e60734f3e71b052d51181fdaede72e10864f544a Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 12 Feb 2026 20:04:38 +0000 Subject: [PATCH] ci: add migration integrity gate for migration-relevant changes --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++++ Makefile | 22 ++++++++++++++++++++++ docs/03-development.md | 22 +++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f17053e0..4dcf4890 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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. diff --git a/Makefile b/Makefile index b64228ff..4ecffdc3 100644 --- a/Makefile +++ b/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 diff --git a/docs/03-development.md b/docs/03-development.md index 4949c0c3..b3e1417c 100644 --- a/docs/03-development.md +++ b/docs/03-development.md @@ -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.