Steer the roster from your config
Module 1 let the fleet find itself — the catalog answers, every tenant gets forged. Clean default. But sometimes the catalog shouldn’t be the one deciding. You want to hit a named set of tenants from config. You want to roll out one region at a time. You want to rehearse against a single database before you strike the whole fleet.
Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith.
Today we take the roster off the catalog and put it in your hands. Two tools, one distinction to keep straight: one replaces the roster, the other narrows it. Let’s fire it up.
Hand the roster to config
Section titled “Hand the roster to config”Discovery reads the catalog. But your deployment config can override it — name the databases yourself, and SchemaSmith deploys to exactly those. That’s TemplateTargets. Here it is in quench.settings.dev.json:
"TemplateTargets": { "Main": { "Databases": [ "fleet_tenant_001", "fleet_tenant_002" ] }}Preview it before you touch anything:
schemaquench --ConfigFile:quench.settings.dev.json --PreviewTargets[localhost,11433] Template 'Main' Databases sourced from TemplateTargets (2 entries); DatabaseIdentificationScript bypassed.Template: Main [required] db: fleet_tenant_001 db: fleet_tenant_002Two tenants — and the catalog holds five. DatabaseIdentificationScript bypassed says it flat out: your config replaced discovery. The catalog didn’t get a vote. Quench it:
schemaquench --ConfigFile:quench.settings.dev.json[localhost,11433].[fleet_tenant_001] Dispatching work unit (source: db=TemplateTargets:Main:Databases, schema=(regular template))[localhost,11433].[fleet_tenant_002] Dispatching work unit (source: db=TemplateTargets:Main:Databases, schema=(regular template))source: db=TemplateTargets:Main:Databases — every work unit came from your settings file, not the server. Tenants 003, 004, 005 are still sitting right there in the catalog, and they got nothing this run. The config is the roster now.
One package, many environments
Section titled “One package, many environments”Here’s why that matters. The package never changed — same Shop, same tables, same catalog script still sitting in the template. All that moved was which settings file you pointed --ConfigFile at. So you ship one canonical package everywhere, and each environment’s config declares its own fleet. Point it at prod:
schemaquench --ConfigFile:quench.settings.prod.json[localhost,11433] Template 'Main' Databases sourced from TemplateTargets (5 entries); DatabaseIdentificationScript bypassed.Five entries this time. Same package, different roster. Dev hits two, prod hits five, and the thing you build and test is byte-for-byte identical in both. The config is the only moving part.
One gotcha before you strip discovery
Section titled “One gotcha before you strip discovery”There’s a catch worth knowing. A Databases override still needs the template to declare some DatabaseIdentificationScript — that’s how SchemaSmith knows the template fans out on the database axis at all. Pull that line out of Template.json entirely and quench, and the run refuses before it touches a thing:
SchemaQuench.TemplateTargetValidationException: TemplateTargets.Main.Databases requires Template 'Main' to declare a DatabaseIdentificationScript. Add a placeholder script (e.g., SELECT 'CONFIG-DRIVEN' AS DatabaseName WHERE 1=0) to mark this template as database-fan-out.The hammer stays holstered, and the error tells you the fix. When your deployment system is the only authority — no naming convention to discover — you give it a placeholder that returns nothing:
"DatabaseIdentificationScript": "SELECT 'CONFIG-DRIVEN' AS DatabaseName WHERE 1=0"Quench again and the override drives the whole fan-out exactly as before. The placeholder query never even runs — the config bypasses it. The lab ships the real catalog script so you can watch the override win over a live discovery result; the placeholder is the form you reach for when there’s genuinely nothing to discover.
A canary — narrow one run without touching the roster
Section titled “A canary — narrow one run without touching the roster”TemplateTargets replaces the roster. A scope filter does something different — it narrows whatever was discovered or overridden, for this one run only. That’s your canary. Open quench.settings.canary.json — plain discovery, plus a single filter line:
"Databases": [ "fleet_tenant_001" ]Preview it first — same habit as always:
schemaquench --ConfigFile:quench.settings.canary.json --PreviewTargetsLocate Databases To Quench (localhost,11433)[Target] Resolved 1 work unit(s) after filtering 5 discovered unit(s) for template 'Main'.Template: Main [required] db: fleet_tenant_001filtering 5 discovered unit(s) down to one — discovery still read the whole catalog; the filter just narrowed it to your canary. Quench it:
schemaquench --ConfigFile:quench.settings.canary.jsonOne work unit — fleet_tenant_001. Deploy the canary, confirm it came off the fire clean, then drop the filter and run the whole fleet with confidence.
And it fails fast on a fat finger. Name a tenant that isn’t there and the run stops before any work — and tells you what was on the roster:
Target filter rejection for template 'Main': One or more Target.* filter values do not match the discovered work-unit set. Target.Databases value(s) not discovered: [fleet_tenant_999]. Available: [fleet_tenant_001,fleet_tenant_002,fleet_tenant_003,fleet_tenant_004,fleet_tenant_005].No silent no-op. No “zero tenants matched, exiting clean” that leaves you thinking it worked. A typo is an error, and the error hands you the real names.
Replace, or narrow?
Section titled “Replace, or narrow?”Two mechanisms, and the whole module turns on telling them apart:
| Config | What it does | Reach for it when |
|---|---|---|
Target.TemplateTargets.Main.Databases | Replaces discovery — the config list is the roster | your deployment system owns the fleet list |
Target.Databases | Narrows the discovered or overridden set for this run | a canary, one region, one tenant — right now |
They stack. TemplateTargets declares the roster; Target.Databases filters it down to today’s canary. Config owns the who, the filter owns the how much of it, this time.
Check yourself: You set Target.TemplateTargets.Main.Databases to three tenants, and the catalog holds five. Separately, you add Target.Databases with one of those three. How many databases does the run touch, and why?
One. TemplateTargets replaces discovery first — the roster becomes those three, and the other two catalog databases are out entirely. Then Target.Databases narrows that three-tenant set down to the one you filtered to. Replace sets the universe; the filter intersects it. The catalog’s five never mattered once the override took over.
A smith with a full order doesn’t always run the whole batch at once. Sometimes the work order names the pieces — these three blades, not those. Sometimes you strike one first to check the temper before you commit the heat to fifty. Same mold, same forge — you’re just choosing what goes under the hammer, and when. That’s steering a fleet: the package stays canonical, and you decide exactly which tenants a run reaches.
Deploying the same schema to more than one environment and wishing the package didn’t have to know the difference? Email me at forgebarrett@schemasmith.com — tell me how your dev, staging, and prod rosters differ today, and I’ll show you where the config seam belongs.
Next up: Course 7 · Module 3 — Provisioning at onboarding, where the roster names a tenant database that doesn’t exist yet — and SchemaSmith stands it up as part of the run instead of skipping it.
Until then, may your config name true, your canary come back clean, and every run reach exactly the tenants you meant.
— Forge