Constraint added NOT VALID and never validated in a later migration
Warning — this works, but blocks traffic or rewrites data at scale.
What it catches
NOT VALID is half of a two-step pattern: add the constraint instantly (protecting new writes), then VALIDATE CONSTRAINT in a later migration to check existing rows without an exclusive lock. Skip the second step and the rows that were already there are never checked — the constraint looks enforced, the planner may even trust it, and the data underneath is not. Fires only when the rest of the migration set is visible and no later file validates it.
Fires on
ALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users (id) NOT VALID;
-- ...and no later migration runs VALIDATE CONSTRAINT orders_user_fkDo this instead
Add the VALIDATE CONSTRAINT step as its own later migration (it takes only SHARE UPDATE EXCLUSIVE, so it does not block writes). Validating in the same file as exclusive-lock DDL trips BV036 — keep it separate.
-- Next migration, on its own:
SET lock_timeout = '5s';
ALTER TABLE orders VALIDATE CONSTRAINT orders_user_fk; This rule runs in the hosted service on Startup and above — add --remote with a team token, or use the GitHub Action: npx bolvrk check migration.sql