Why ForgeSchema Refuses a Migration Plan Made Stale by an Overnight Hotfix
Monday afternoon, a developer builds a migration plan. It adds a notes column to customers and an index on it. Two colleagues review it, approve it, and it’s scheduled for Wednesday’s maintenance window.
Tuesday night there’s an incident. Someone on call adds a column to customers by hand to unblock a hotfix. It works, everyone goes to bed, and the plan sitting in the deploy queue still describes Monday’s database.
This is how teams end up with migrations nobody ever saw. The reviewed SQL might still run fine. Or it might collide with the new column, or quietly produce a schema that matches neither Monday’s design nor Tuesday’s reality.
The check that happens under the lock
ForgeSchema’s plan file carries a fingerprint of the source schema it was built from. When Wednesday comes and someone runs
./bin/forgeschema apply reviewed-plan.json
apply first takes its locks on the tables the plan changes. Only then does it read the catalog again and compare the live schema with the reviewed source. The order matters. A check done before waiting for a lock could miss a change committed while it waited.
The hotfix column shows up as a difference. Apply stops with exit code 5, the database no longer matches the plan, and nothing is committed. No partial DDL, no history row.
What you do next
You inspect again, build a new plan from the schema as it really is, and review that. If the hotfix column should stay, put it in the desired YAML. If it shouldn’t, that’s a conversation, and in this alpha a drop is blocked anyway.
The same check covers the other direction too. After running the operations, apply inspects the result and compares it with the reviewed target before it commits. If concurrent DDL changed something in between, that’s drift as well, exit 5, rolled back.
It also stops a plan being replayed by accident. Once applied, the source no longer matches, so running the same file twice fails the source check rather than being reported as a success.
One honest limit. The checks catch changes made before them. They can’t stop someone running DDL on an unrelated table at the same moment, so the docs ask you to pause external DDL while apply runs. Tuesday’s hotfix is exactly the case this was built for, though. Read more about the apply and verify workflow.