| NEVER modify a committed migration file | Other environments have already run it; modifying breaks schema divergence detection |
| Forward-only — no down scripts | Down scripts are rarely correct in practice and encourage unsafe rollbacks |
| Sequential numbering (001, 002, 003…) | Prevents ordering ambiguity; gaps are forbidden |
| Each migration must be atomic and transactional | Partial failures leave schema in a known rollback state |
| No foreign key constraints | Referential integrity is maintained by the domain model and event handlers, not the DB |
| No CHECK constraints or other domain-invariant DDL | Business invariants belong in the write model (the aggregate), not in the read-model schema. Allowed: NULL/NOT NULL, length limits where they protect storage. Forbidden: CHECK (status IN (…)), CHECK (value > 0), anything that re-asserts a domain rule. |
| Read models are dumb projections | The read model exists to serve queries fast. It does not enforce business rules. Keep schemas, indices, and projector code as straightforward as possible. |
| To fix a committed migration, write a new one | Append a corrective migration; never edit history |