ADR-0020: Own the schema-integrity check rather than trust drizzle-kit check
ADR-0020: Own the schema-integrity check rather than trust drizzle-kit check
- Status: Accepted
- Date: 2026-07-30
- Supersedes: —
- Superseded by: —
Context
ADR-0008 makes the Drizzle schema the single source of truth, and docs/standards/ci-and-gates.md step 2 promised that CI would fail on “schema drift, and conflicting migration ordering”. drizzle-kit check was the obvious implementation.
WI-006’s Definition of Done required proving that step could actually fail. It could not. Verified against drizzle-kit 0.31.10, all three returned Everything's fine:
| Sabotage | drizzle-kit check |
|---|---|
| Migration SQL hand-edited to diverge from the schema | passes |
src/schema.ts changed with no migration generated | passes |
| Two migrations sharing a journal index (parallel branches) | passes |
The name implies a guarantee it does not provide.
Decision
Implement the check ourselves in packages/db/scripts/check-schema.mjs, exposed as pnpm --filter db check and run as part of that package’s test task so the gate cannot skip it.
It verifies:
- No schema drift — runs
drizzle-kit generateas a probe; anything it produces is drift. The probe’s side effects onmeta/are backed up and restored unconditionally. - Journal integrity — indices unique and contiguous, every entry has a file, every file has an entry.
Rationale
A gate step that cannot fail is worse than a missing one: it reads as coverage while providing none. That is the failure this project’s entire delivery system is built to prevent, so shipping it in the gate itself would have been especially bad.
generate is the reliable drift probe because it is the same code path that produces migrations — if it emits nothing, the schema and migrations genuinely agree.
Consequences
- ~90 lines of our own code to maintain, and a dependency on
drizzle-kit generate’s output shape. Tolerable: the alternative is a gate that lies. - The check mutates
meta/while probing, so it backs up and restores. An earlier version delegated restoration togit checkout, which silently failed before the package was first committed — a check that corrupts the tree when it fails is worse than no check. - Re-evaluate if drizzle-kit gains real drift detection. The trigger is a changelog entry, not a version bump.
drizzle-kit checkis still run by nothing; it is not wrong, just far weaker than its name suggests.
Alternatives considered
Trust drizzle-kit check. Rejected on evidence — it passes all three sabotages.
Compare committed snapshots by hand. Rejected: it reimplements what generate already does correctly, and would drift from drizzle’s own semantics.