Convergence Gate¶
The convergence gate is the strongest correctness check in DBWarden. It proves that the complete migration history can reproduce the schema declared by the current models.
The gate answers one question:
If a new environment starts with an empty database and applies every migration, does the resulting schema exactly match the models in the repository?
If the answer is no, CI should fail.
Definition¶
Convergence means:
Empty database
|
v
Apply every migration
|
v
Extract live schema
|
v
Compare live schema with model spec
|
v
Pass only if there is zero drift
This is an end-to-end, model-history-consistency check. It does not only test the newest migration. It tests the entire path from an empty database to the current model state.
Pipeline¶
The typical CI pipeline has four phases.
1. Spin Up an Ephemeral Database¶
CI starts a disposable database service. For PostgreSQL this is usually a container. For ClickHouse this can be a ClickHouse service container. The database should be empty at the start of the job.
The important property is isolation. The gate should never run against a shared developer database, because manual changes in that database would hide or create drift that is unrelated to the repository.
2. Apply the Full Migration History¶
Run the same command used in deployment:
This applies versioned migrations and any repeatable migration behavior supported by the project configuration. The gate tests the migration files that will run in production, not a simplified test fixture.
3. Check the Resulting Schema¶
Run DBWarden's check command:
The check compares the database state with the model state. If the command reports differences, CI fails. Some teams also run dbwarden diff in table or JSON mode for diagnostic output:
Use the diff output to see which object drifted. The gate should not ignore drift. If the models are correct, generate or write a migration. If the migration is correct, update the model.
4. Fail Fast on Drift¶
The final rule is simple:
The migration history and the models must agree.
GitHub Actions Example¶
This example uses PostgreSQL. The same pattern applies to any backend that can run in CI.
name: dbwarden-convergence
on:
pull_request:
push:
branches: [main]
jobs:
convergence:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: dbwarden
POSTGRES_PASSWORD: dbwarden
POSTGRES_DB: app
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql://dbwarden:dbwarden@localhost:5432/app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install project
run: pip install -e .
- name: Apply migrations
run: dbwarden migrate --database primary
- name: Check convergence
run: dbwarden check --database primary
- name: Print drift diagnostics on failure
if: failure()
run: dbwarden diff --database primary --out table
The --health-cmd option belongs to Docker service configuration. The DBWarden commands are the two important checks: migrate and check.
Example Error Caught by the Gate¶
Assume a model declares an index:
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), nullable=False)
class Meta:
indexes = [IndexSpec(name="idx_users_email", columns=["email"])]
But the migration only creates the table:
-- upgrade
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- rollback
DROP TABLE IF EXISTS users CASCADE;
The migration applies successfully. A unit test that only checks syntax might pass. Production would have a table without the declared index.
The convergence gate catches this:
Model state:
users has idx_users_email
Extracted database state after migrations:
users has no idx_users_email
Result:
drift detected
The fix is to generate or add a migration that creates the missing index.
Other Errors It Catches¶
Leftover Column¶
A column is removed from the model, but no migration drops it. The gate applies all migrations, extracts the schema, and sees that the database still contains the old column.
Reversed Migration¶
A migration accidentally renames customer_id to client_id while the model still declares customer_id. The migration applies, but the final schema disagrees with the model.
Missing Backend Metadata¶
PostgreSQL table storage parameters, identity options, RLS policies, or ClickHouse engine settings are easy to miss in hand-written SQL. The gate compares the extracted backend-specific shape, not only the visible column list.
Why This Is the Gold Standard¶
The convergence gate tests exactly the behavior that matters in production:
- It uses the real migration files.
- It uses the real backend SQL emitters.
- It uses a real database engine.
- It verifies the final schema against the models.
- It catches drift introduced anywhere in the history.
This is stronger than checking that a single generated migration looks plausible. A migration can be syntactically valid and still fail to converge.
Relationship to Offline Integrity¶
Offline workflows are useful when CI cannot reach a database service. They let DBWarden generate migrations from a checked-in model state instead of a live database. That is deterministic, but it does not prove the SQL applies to a real engine.
A strong pipeline uses both:
Offline integrity check
|
v
Generate or verify migration files deterministically
|
v
Convergence gate on a live database
|
v
Prove the SQL reproduces the model state
See Offline Integrity for the first half of that workflow.