Skip to content

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:

Sabotagedrizzle-kit check
Migration SQL hand-edited to diverge from the schemapasses
src/schema.ts changed with no migration generatedpasses
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:

  1. No schema drift — runs drizzle-kit generate as a probe; anything it produces is drift. The probe’s side effects on meta/ are backed up and restored unconditionally.
  2. 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 to git 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 check is 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.