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 }}-
|
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
|
- name: Run backend checks
|
||||||
env:
|
env:
|
||||||
# Keep CI builds deterministic.
|
# 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)
|
backend-migrate: ## Apply backend DB migrations (uses backend/migrations)
|
||||||
cd $(BACKEND_DIR) && uv run alembic upgrade head
|
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
|
.PHONY: build
|
||||||
build: frontend-build ## Build artifacts
|
build: frontend-build ## Build artifacts
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,23 @@
|
|||||||
# Development workflow
|
# 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