"""Alembic chain integrity guard. Hard invariants enforced in CI to prevent the kind of accidental ``revision = "087"`` collision and dangling ``down_revision`` we hit in PRs #109/#111/#114: 1. Exactly one head — ``len(alembic heads) == 1``. 2. Every ``down_revision`` reference resolves to an actual revision in ``alembic/versions/`` (no dangling parents). 3. No two migration files declare the same ``revision`` id. Runs without a database connection: parses the version files directly with the same ScriptDirectory loader Alembic uses. Exits 0 on success, 1 with a human-readable diagnostic on failure. """ from __future__ import annotations import sys from collections import Counter from pathlib import Path from alembic.config import Config from alembic.script import ScriptDirectory def _alembic_root() -> Path: here = Path(__file__).resolve() return here.parent.parent # server/ def main() -> int: root = _alembic_root() cfg = Config(str(root / "alembic.ini")) cfg.set_main_option("script_location", str(root / "alembic")) script = ScriptDirectory.from_config(cfg) revisions = list(script.walk_revisions()) revision_ids = {rev.revision for rev in revisions} errors: list[str] = [] # 1. Exactly one head heads = list(script.get_heads()) if len(heads) != 1: errors.append( f"expected exactly 1 alembic head, found {len(heads)}: {heads!r}" ) # 2. No dangling down_revision references for rev in revisions: parents = rev.down_revision if parents is None: continue if isinstance(parents, str): parents_iter: tuple[str, ...] = (parents,) else: parents_iter = tuple(parents) for parent in parents_iter: if parent not in revision_ids: errors.append( f"revision {rev.revision} ({rev.path}) " f"references missing down_revision {parent!r}" ) # 3. No duplicate revision ids rev_counter = Counter(rev.revision for rev in revisions) for rev_id, count in rev_counter.items(): if count > 1: errors.append(f"duplicate revision id {rev_id!r} declared {count} times") if errors: print("Alembic chain integrity FAILED:", file=sys.stderr) for err in errors: print(f" - {err}", file=sys.stderr) return 1 print( f"Alembic chain OK: {len(revisions)} revisions, " f"single head = {heads[0]}" ) return 0 if __name__ == "__main__": sys.exit(main())