Skip to content

fix(ci): guard the Alembic (Postgres) track in schema-parity (#1342)#1345

Open
dolho wants to merge 2 commits into
devfrom
feature/1342-alembic-parity-ci-guard
Open

fix(ci): guard the Alembic (Postgres) track in schema-parity (#1342)#1345
dolho wants to merge 2 commits into
devfrom
feature/1342-alembic-parity-ci-guard

Conversation

@dolho

@dolho dolho commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Related to #1342

Problem

The schema-parity required check validated only the SQLite track (db/migrations.pydb/schema.py). A schema change that ships the SQLite migration + updates schema.py/tables.py but omits the Alembic revision under src/backend/migrations/versions/ passed every required check green — yet broke PostgreSQL, where init_database() runs alembic_runner.upgrade_to_head() (applies revision files only; does not autogenerate from tables.py). Two PRs reached "green CI but PG-broken" in one review pass.

Fix

A cross-track guard folded into the existing required schema-parity job (no new required-check to manage — AC #2):

  • scripts/ci/check_alembic_parity.py — fails a PR that ADDS schema DDL to db/{migrations,schema,tables}.py without a net-new file under src/backend/migrations/versions/. Pure stdlib, PR-only (diffs base...head).
  • Heuristic / false-positive guard (AC fix: add missing logging_config.py to backend Dockerfile #4) — the signal is a DDL keyword on an added, non-comment line:
    • SQL (migrations.py/schema.py): CREATE TABLE/INDEX/TRIGGER, ALTER TABLE, ADD/DROP COLUMN, RENAME …
    • SQLAlchemy (tables.py): Column(/Table(/Index(/UniqueConstraint(/ForeignKey(
    • Comment edits, data-only and down migrations carry no DDL keyword → don't trip. Documented in the script docstring + workflow header.
  • tests/unit/test_alembic_parity_guard.py — 20 unit tests incl. the acceptance fixtures (SQLite-only change fails; dual-tracked passes; comment/data-only pass). Wired into the parity pytest run.
  • Notes the enforcement in architecture.md Invariant Feature/vector log retention #3.

Acceptance criteria

  • CI fails when a PR adds SQLite-track schema DDL without a new Alembic revision
  • Wired as part of the existing required schema-parity job
  • Fixture: SQLite-only schema change fails; dual-tracked passes (unit tests + e2e smoke)
  • False-positive guard: comments / data-only / down migrations don't trip it; heuristic documented

Verification

24 passed   # 4 existing schema-parity + 20 new guard tests

End-to-end smoke against real git refs:

  • clean (no DB DDL) → pass (exit 0)
  • synthetic SQLite-only ADD COLUMN, no revision → FAIL (exit 1, prints offending line)
    • paired revision file → pass (exit 0)

🤖 Generated with Claude Code

The schema-parity required check validated only the SQLite track
(migrations.py ↔ schema.py). A schema change that ships the SQLite
migration but omits the Alembic revision under
src/backend/migrations/versions/ passed every required check green yet
broke PostgreSQL — init_database() runs alembic_runner.upgrade_to_head(),
which applies revision files only and does not autogenerate from
tables.py. Two PRs reached "green CI but PG-broken" and had to be held by
hand.

Add a cross-track guard, folded into the existing required schema-parity
job (no new required-check to manage):

- scripts/ci/check_alembic_parity.py — fails a PR that ADDS schema DDL to
  db/{migrations,schema,tables}.py without a net-new revision file under
  src/backend/migrations/versions/. Pure stdlib, PR-only (diffs base...head).
- Heuristic / false-positive guard: the signal is a DDL keyword on an
  *added, non-comment* line (SQL: CREATE/ALTER/ADD COLUMN/…; SQLAlchemy:
  Column(/Table(/Index(/…). Comment edits, data-only and down migrations
  carry no DDL keyword, so they don't trip it. Documented in the script
  docstring and the workflow header.
- tests/unit/test_alembic_parity_guard.py — 20 tests incl. the acceptance
  fixtures (SQLite-only change fails; dual-tracked passes; comment/data-only
  pass). Wired into the parity pytest run.

Also notes the enforcement in architecture.md Invariant #3.

Verified locally: 24 tests pass; end-to-end smoke across clean / SQLite-only
(exit 1) / paired-revision (exit 0) scenarios behaves correctly.

Related to #1342

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dolho dolho requested a review from AndriiPasternak31 as a code owner June 25, 2026 07:49
Verifying against real repo history surfaced a false positive: the
migration-runner refactor #1263 (_atomic_rebuild table rebuilds) re-emits
CREATE TABLE / CREATE INDEX for *existing* tables in a rename-swap but adds
no actual schema and no new MIGRATIONS entry — yet the original "any added
DDL keyword" heuristic flagged it, violating AC #4 (non-schema edits must
not trip).

Tighten the signal to two conjuncts: a schema change must (1) register a
net-new ("name", _migrate_fn) entry in the MIGRATIONS list AND (2) carry a
DDL keyword. Runner refactors / table rebuilds add no entry → exempt;
data-only new migrations carry no DDL → exempt; real column/table adds do
both → caught.

Validated against real commits:
  • #740 agent_loops, #526 agent_ownership column → FAIL (correctly blocked)
  • #668 compat, voice_name (both shipped an Alembic revision) → PASS
  • #1263 runner refactor → PASS (false positive fixed)
A full post-Alembic history scan finds 0 outstanding missing revisions, so
no backfill is owed; the pre-Alembic columns are already in 0001_baseline.

28 tests pass (added MIGRATIONS-entry detection + the #1263 rebuild case).

Related to #1342

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dolho dolho requested a review from vybe June 25, 2026 08:40
@github-actions

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant