Emergency patches: carve and deploy safely
Production needs one column added to one table, right now. You can’t wait for the next full release — but you also can’t hand-carve a package with just that table, because the deploy will read every table you left out as deleted and drop it. So you’re stuck: deploy the whole product, or risk nuking three tables to ship one column.
Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith. Last time we hardened the deploy with runtime gates. Today we close out Course 6 with the emergency patch — packaging a change down to only the objects that changed, and carrying it to a target safely. This is the schema-side twin of the datafix from Module 1: that fixed bad data with a scoped account; this ships a schema fix without touching anything it shouldn’t.
First, the disaster — why you can’t just carve by hand
Section titled “First, the disaster — why you can’t just carve by hand”Here’s the incident fix: a nullable PriceReviewBatch column on OrderItem. The tempting move is to copy just OrderItem into a little package and deploy that. Let’s watch what that costs. The lab’s naive-subset/ is exactly that — OrderItem and nothing else — deployed to a throwaway tenant:
schemaquench --ConfigFile:quench.settings.scratch.json Drop tables removed from the product Dropping table [dbo].Customer Dropping table [dbo].Product Dropping table [dbo].SalesOrderFAILED to quench:Could not create constraint or index. See previous errors.Three tables gone. SchemaSmith did exactly what a full deploy is supposed to do — reconcile the database to the product — and your product only mentioned OrderItem, so the rest are “removed.” Then the run fails outright, because OrderItem’s foreign keys now point at tables that don’t exist. That’s the trap: a subset that’s honest about being partial reads as a product that’s had everything else deleted. (PostgreSQL and MySQL drop the same three tables — only the wording differs.)
The fix: SchemaShears carves and stamps
Section titled “The fix: SchemaShears carves and stamps”SchemaShears builds the same object-level patch, but it does the one thing the hand-carve can’t: it tells the deploy “this is a subset — don’t treat the missing objects as deleted.” Point it at the full product and a manifest of what changed:
schemashears --Source:Package --Manifest:patch-manifest.txt --Output:patchSchemaShears: 3 files into 'patch' (1 from manifest).The manifest is just the changed files — the natural way to produce it is git diff --name-only <before> <after> -- Package/. SchemaShears pulls in that file plus the scaffolding it needs (Product.json, the touched Template.json), and writes a report telling you why each file is in:
SchemaShears patch build report================================Scaffolding Product.jsonManifest Templates\Main\Tables\dbo.OrderItem.jsonScaffolding Templates\Main\Template.jsonAnd here’s the safety — it stamps the emitted Product.json so every drop-by-absence category is turned off:
"DropTablesRemovedFromProduct": false,"DropColumnsRemovedFromProduct": false,"DropUnknownIndexes": false,"DropForeignKeysRemovedFromProduct": false,"DropCheckConstraintsRemovedFromProduct": false,"DropStatisticsRemovedFromProduct": falseThat’s the SQL Server patch. It stamps only the flags your engine accepts, so PostgreSQL’s also carries "DropExcludeConstraintsRemovedFromProduct": false, and MySQL and MariaDB have no DropStatisticsRemovedFromProduct to stamp.
Lint it before it goes anywhere:
schemaquench --Validate --SchemaPackagePath:./patchWARN [SS-FK-002] Template 'Main' / Table '[OrderItem]' / FK '[FK_OrderItem_Product]': RelatedTable '[Product]' does not resolve to any table in the package (resolved schema 'dbo'). The deploy succeeds only if it already exists on the target.WARN [SS-FK-002] Template 'Main' / Table '[OrderItem]' / FK '[FK_OrderItem_SalesOrder]': RelatedTable '[SalesOrder]' does not resolve to any table in the package (resolved schema 'dbo'). The deploy succeeds only if it already exists on the target.0 error(s), 2 warning(s)Exit 0. The patch leaves Product and SalesOrder out on purpose — they’re already on the target — so these two warnings are exactly what you expect. Read them anyway: the same warning in a package you meant to be complete is a misspelled RelatedTable.
That stamp is the whole game. The deploy honors it with sticky semantics — once a flag is false, nothing downstream can turn it back on — so this patch can only add and alter. It physically cannot drop the objects it didn’t carry.
Deploy it — one column, no collateral
Section titled “Deploy it — one column, no collateral”schemaquench --ConfigFile:quench.settings.canary.jsonPriceReviewBatch lands on OrderItem. Customer, Product, and SalesOrder are untouched — still there, still full of data. Same carve, opposite outcome: where the hand-carved subset dropped three tables and died, the stamped patch adds one column and leaves everything else exactly where it was. Re-run it and nothing changes — the patch is idempotent. That’s a schema fix delivered to one tenant fast, with nothing else disturbed.
Who runs this? The patch is DDL, so it deploys as a schema-capable account — not the least-privilege
datafix_userfrom Module 1. Data hotfix usesdatafix_user; schema hotfix uses the schema-deploy principal. Two halves of an incident response, two different grants.
The override — because sometimes you do mean it
Section titled “The override — because sometimes you do mean it”The suppression is a default, not a cage. When you genuinely want a subset to drop what it omits, --AllowDrops names the categories to leave enabled:
schemashears --Source:Package --Manifest:patch-manifest.txt --Output:patch-allowdrops --AllowDrops:TablesNow patch-allowdrops/Product.json leaves DropTablesRemovedFromProduct alone — the other six stay false — and deploying it (to a freshly seeded scratch; Step 1 wrecked the old one) drops the three omitted tables, because you said so. Then it fails exactly as the naive subset did, exit 2: the patch still carries OrderItem, whose foreign keys point at the tables it just dropped. The override takes the safety net away; it doesn’t make the drop a good idea. Allow a category only when nothing left in the patch still depends on what it lets go — here that’s never, which is why the stamp is the default.
Check yourself: You carve a patch with SchemaShears containing only a changed OrderItem table (default AllowDrops — empty). You deploy it to a database that also has Customer, Product, and SalesOrder. What happens to those three tables?
Nothing — they’re untouched. With an empty --AllowDrops, SchemaShears stamps every drop-by-absence flag (including DropTablesRemovedFromProduct) to false in the emitted Product.json, and the deploy honors that with sticky semantics. So even though the patch’s product only defines OrderItem, the deploy can’t read Customer/Product/SalesOrder as “removed” and drop them — the way a naive hand-carved subset would. The patch can only add and alter what it carries. (Deploy the same subset without the stamp — or with --AllowDrops:Tables — and those three tables drop.)
A good smith doesn’t reforge the whole blade to fix one nick. He works the spot that needs it and leaves the rest of the temper alone — and he never grinds away steel he didn’t mean to touch. That’s an object-level patch: carry only the object that changed, stamp it so it can’t bite the objects it left behind, and strike it into production in one pass. Fast, minimal, and safe by construction.
That’s every gate in the operator’s kit now — least privilege, datafix, pre-flight, CI validation, runtime gates, and the emergency patch. Got an incident-patch story, or a subset that dropped something it shouldn’t have? Email me at forgebarrett@schemasmith.com — I read every one.
That’s a wrap on Course 6. You can run SchemaSmith like an operator, end to end.
Until then, may your patches carry only what changed, and drop nothing you didn’t name.
— Forge