ci: prove fresh installs and upgraded deployments converge on one schema
The follow-up promised in PR #256's review thread: a real drift check,
designed the way willchen96 described - a pinned baseline from an older
point in history, only the migrations added since, and a schema comparison
against a fresh install. It also fixes the first drift it caught.
WHY THIS MATTERS
This repo maintains the database shape twice, on purpose: schema.sql is
what a FRESH install runs; backend/migrations/ is what an EXISTING
deployment applies to move forward. They are edited by hand, in parallel,
and nothing forces them to agree. When they disagree, the two classes of
real deployment silently diverge - and no ordinary test notices, because
test databases are always built fresh.
This is not hypothetical. Commit b2dbb39 ("narrow service role schema
grants") tightened service_role from GRANT ALL to select/insert/update/
delete - in schema.sql only, with no migration. Every deployment created
before 2026-07-23 and upgraded by the book still lets service_role
TRUNCATE any table, create TRIGGERs, and reset sequences. Fresh installs
do not. Same codebase, two different security postures.
HOW THE CHECK WORKS
The new "Schema drift" workflow builds both REAL installation paths in one
disposable Supabase stack and demands they converge:
upgraded: schema.sql as of a pinned baseline commit (9a1277b, the
commit that introduced dated migrations) + only the
migrations git says were ADDED since (git diff
--diff-filter=A) - the documented upgrade path, exactly
fresh: today's schema.sql
Each build is reduced to a canonical fingerprint
(backend/scripts/schema-fingerprint.sql): tables, columns, constraints,
indexes, RLS policies, function definitions, triggers, views, enums, and
exploded per-privilege ACLs, every section totally ordered.
WHY A FINGERPRINT INSTEAD OF DIFFING pg_dump
Two reasons, both lessons from PR #256:
1. Column order. Migrations append columns; schema.sql may declare them
anywhere. Upgraded and fresh databases therefore differ in physical
column order forever - a benign difference a raw pg_dump diff would
flag on every table. The fingerprint sorts columns by name: benign
difference ignored, every real difference kept.
2. Grant order. An ACL array is ordered by GRANT execution order, which
legitimately differs between the two paths. Exploding to one row per
(object, grantee, privilege) and sorting compares the meaning, not the
history.
And unlike the replay-history approach reverted in PR #256, this never
runs an old migration against a schema from its future, and it can never
be "fixed" by editing shipped migrations - the failure message explicitly
forbids that.
THE ACCOMPANYING MIGRATION
20260805_01_narrow_service_role_grants.sql is the forward fix for the
b2dbb39 drift: it revokes service_role's excess table/sequence privileges
so upgraded deployments land on the same least-privilege grants a fresh
install gets. Note the difference from what PR #256 reverted: adding a NEW
dated migration to move deployments forward is exactly what migrations are
for; editing already-shipped ones is what they must never suffer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| Repository | open-legal-products/mike |
|---|---|
| Author | Amal <mamalanand3@gmail.com> |
| Authored | |
| Committed | |
| Parents | ab716822 |
| Stats | 3 files changed , +320 |
| Part of | Add schema drift checks for database upgrades |
Capture this commit into my fork
Download a Markdown prompt that tells Claude how to port this
exact commit into your working tree. Run it via
claude -p < capture-commit-4e136af6.md
from inside the repo you want the change in.