Why AI-Generated Migrations Need a Different Gate Than Code Patches MonkeyCode proposes a separate pre-merge gate for AI-generated database migrations, distinct from code patch reviews, because migrations can make changes irreversible. The gate crash-tests up.sql and down.sql pairs against a throwaway Postgres schema to ensure the down migration restores the original schema, forcing explicit human approval for one-way doors. Most patch reviews ask: does this change behave the way we intended? A migration review has to answer a harder question: what does this change make impossible to undo? A code change can usually be reverted by applying the old diff again. A migration that drops a column, truncates a table, or rewrites data may make the previous state unrecoverable even if the commit is reverted. That asymmetry is why schema changes deserve a separate pre-merge gate from ordinary AI-generated code. One practical way to use MonkeyCode's free model access and free server option is not to generate final schema changes and push them, but to generate up.sql and down.sql pairs and then crash-test them against a throwaway Postgres schema before a human reviewer spends time on them. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I won't quote quota or hardware specifics because those change; check the current plan before building a pipeline around them. A typical AI patch gate checks that generated tests pass, that a diff is small, and that fixtures still match. That is useful for application logic, but it can pass a migration that has all three of these problems: down.sql . down.sql that recreates the column but cannot restore the data.The result is often discovered too late: a developer approves a reasonable-looking ALTER TABLE , CI is green, and then production data disappears or the deploy stalls while the DDL waits on a lock. The gate below checks three things before a human sees the migration: DROP TABLE , TRUNCATE , or unguarded UPDATE . up.sql against a throwaway database. down.sql and comparing the schema snapshot with the baseline.The third check is the important one. If the down migration cannot restore the original schema, then the up migration is proposing a one-way door, and a human should be forced to approve that explicitly. The script below assumes you have local psql and pg dump installed and can create databases on the server. Treat it as a runnable starting point, not a guarantee. bash /usr/bin/env python3 """ migration probe.py: run a migration pair against a throwaway Postgres database. Usage: python migration probe.py \ --uri postgresql://user:pass@localhost:5432/postgres \ --dir migrations/2026 08 14 add flags """ import argparse import os import subprocess import sys import uuid def sh args, label="" : result = subprocess.run args, capture output=True, text=True if result.returncode = 0: print f" fail {label}\n{result.stderr}" sys.exit result.returncode return result.stdout def main : parser = argparse.ArgumentParser parser.add argument "--uri", required=True parser.add argument "--dir", required=True args = parser.parse args base = args.uri db = f"shadow {uuid.uuid4 .hex :10 }" def schema uri : return sh "pg dump", "--schema-only", "--no-owner", uri , "schema snapshot", before = schema base sh "psql", base, "-c", f'CREATE DATABASE {db}' , "create shadow db" db uri = base.replace "/postgres", f"/{db}" try: sh "psql", db uri, "-f", os.path.join args.dir, "up.sql" , "apply up" after = schema db uri sh "psql", db uri, "-f", os.path.join args.dir, "down.sql" , "apply down" rolled back = schema db uri finally: sh "psql", base, "-c", f'DROP DATABASE IF EXISTS {db}' , "drop shadow db" if after == before: print " warn up migration produced no schema change" if rolled back = before: print " fail down migration did not restore original schema" diffs = subprocess.run "diff", "-", "-" , input=f"{before}\n{rolled back}", capture output=True, text=True, print diffs.stdout sys.exit 1 print " ok migration pair is schema-roundtrip clean" if name == " main ": main Pair that with a simple destructive-keyword check that runs before the shadow apply: DESTRUCTIVE = "DROP TABLE", "DROP COLUMN", "TRUNCATE", "DELETE FROM" def blockers path : text = open path, encoding="utf-8" .read .upper return kw for kw in DESTRUCTIVE if kw in text If the blocker list is not empty, the workflow should not reject automatically. It should switch the migration from normal review to destructive-review mode , requiring an explicit note about what data is lost and whether the down migration can recover it. The distinction from a patch gate is the success condition. For code, the useful question is often: does the new behavior match specification? For a migration, the useful question is: can we return to the previous state? A schema round-trip test catches the common failure where down.sql recreates a column but leaves out a default, loses a constraint, or reverses operations in the wrong order. It cannot prove the migration is safe, but it moves the obvious failures from production to a disposable server. pg dump --schema-only compares structure, not data. A down migration may restore a table but erase rows. Use a data fixture or a subset dump for critical changes.This is not a replacement for a human reviewer who understands the production schema, the retention policy, and the business reason for deleting data. Skip the automatic path entirely for migrations that touch high-risk tables, regulated data, or anything where deletion is unrecoverable by design. A sensible loop is to let a model generate the up-and-down pair, let the probe filter out the reversible failures, and then take the remaining destructive cases to a human with the full context. That makes AI-generated migrations useful without making them authoritative.