ADR-0012: Alembic for Schema Migrations¶
Status: Accepted Date: 2026-03-23
Context¶
Dazzle generates SQLAlchemy models from DSL entity definitions. As apps evolve, entity fields change — types widen, columns are added or removed, foreign keys are introduced. The database schema must keep pace with these DSL changes.
The previous hand-rolled MigrationPlanner compared old and new AppSpec IR trees and emitted raw DDL strings. This approach had critical gaps:
- No history — migrations were applied but not recorded; re-running was destructive
- No rollback —
ALTER TABLEstatements had no corresponding undo operations - Type change gaps — widening
str(50)tostr(200)required manual handling - No diffing standard — each migration path was bespoke code, not a proven algorithm
- Audit trail absent — production deployments had no migration log
Decision¶
Replace MigrationPlanner with Alembic, SQLAlchemy's official migration tool.
Pipeline: DSL → SQLAlchemy MetaData → Alembic autogenerate → migration files
Safe type casts (e.g. VARCHAR(50) → VARCHAR(200)) are applied automatically. Three operating modes:
| Mode | Trigger | Behaviour |
|---|---|---|
dev |
dazzle serve --local |
Auto-migrate on startup, no confirmation |
explicit |
dazzle db migrate |
Generate and apply migration files |
production |
DAZZLE_ENV=production |
Guard mode — refuse to start if schema is out of date |
Why Alembic?¶
| Criterion | Alembic | Custom MigrationPlanner | Manual DDL |
|---|---|---|---|
| Autogenerate diffing | Yes | Partial | No |
| Rollback support | Yes | No | No |
| Audit trail | Yes (versions table) | No | No |
| Battle-tested | Yes (SQLAlchemy project) | No | No |
| LLM edge case hook | Yes (env.py) | N/A | N/A |
LLM Cognition for Edge Cases¶
Alembic autogenerate is deterministic for unambiguous changes. For edge cases — column renames, data migrations, split columns — Alembic emits a stub and Dazzle's LLM integration fills in the migration body. This is the only place LLM cognition is used; all other migrations are fully automated.
Consequences¶
Positive¶
- Proven diffing algorithm replaces custom code
- Full rollback support via
alembic downgrade alembic_versiontable provides deployment audit trail- Production guard prevents silent schema drift
- Type widening handled automatically without custom cases
Negative¶
alembic.iniandenv.pymust be maintained alongside the project- Autogenerate does not detect column renames — requires manual annotation or LLM assist
- Migration files are generated artefacts; they must be committed to version control
Neutral¶
MigrationPlannerclass removed entirely — no compatibility shim- Existing databases receive a baseline migration on first Alembic run
dazzle db resetbypasses Alembic (dev-only destructive reset remains unchanged)
Alternatives Considered¶
1. Custom MigrationPlanner (Status Quo)¶
Continue extending the hand-rolled planner.
Rejected: No rollback, no history, bespoke code growing unbounded. Correctness not guaranteed.
2. Manual DDL Scripts¶
Developers write SQL migration files by hand.
Rejected: Error-prone, no autogenerate, inconsistent across contributors. Breaks DSL-first principle.
3. Flyway / Liquibase¶
JVM-based migration tools.
Rejected: Not Python-native, requires JVM dependency, no SQLAlchemy integration.
Implementation¶
See src/dazzle/back/migrations/ for Alembic environment. env.py imports the DSL-derived SQLAlchemy MetaData to enable autogenerate. Safe type casts are registered in alembic/cast_rules.py.