field note № 7 · why a fleet migration needs decoupled producers

Migrate the contract, not the fleet

A fleet-wide migration flips one shared contract and lets every script relocate at once. But a flip only reaches the scripts that ask the contract where to write. The ones that hard-wire a local path — especially the generators an orchestrator calls in-process — never appear on the list the migration runs from. You can't migrate what won't tell you it's there.

The weakness

The plan was clean. Field note № 5 had collapsed sixty hard-wired copies of one local path into a single contract module — a module that names the directory taxonomy and resolves it from the environment. Migrating the whole fleet off the protected folder was now supposed to be a single move: inject the new layout, and every script that imports the contract relocates together, behind back-compat, with a one-variable rollback.

So the deployment was modelled as a list. Enumerate the fleet, confirm each unit imports the contract, flip the environment, verify. The list was built the obvious way: read every scheduled job's command line, follow the shell wrappers they invoke, and check those files. By that list, the fleet was migrated. Then the real run went red.

The real failure mode

One scheduled job is not one script. The daily hygiene job is a Python orchestrator: it imports two dozen audit scripts in-process and calls each as an adapter. The migration's list had named the orchestrator — and zero of the generators it imports. Those children still hard-wired the old local path. Under the real unattended runtime, twelve of them tried to write to a folder the contract had moved out from under them, and were denied. The list said migrated. The call graph said otherwise.

The wrong fix (the one the list invites)

The tempting response is to patch the twelve that turned red and re-run until it's green. It would have worked — and it would have been the same trap that built the problem:

Chasing the red checks accepts the framing that the list was right and a few scripts were stragglers. The list was the problem.

A migration is only as deployable as the fleet is decoupled. A script that hard-wires its output doesn't just resist the move — it hides from it. The coupling and the blind spot are the same fact, seen twice.

The reframe

The unit of migration was never "a scheduled job." It was "a producer of output." And producers don't live on the scheduler's list; they live on the call graph. A generator reached three imports deep inside an orchestrator is every bit as much a producer as the one named in a plist — it just declared its dependency privately, by hard-wiring a path, instead of publicly, by asking the contract. So the real requirement isn't "migrate the fleet." It's "make every producer declare its output through the contract, and enumerate by following the calls, not the commands." Decoupling isn't a tidiness goal here. It's the precondition that makes a one-flip migration possible at all.

The removal

Three moves, each taking out a hard-wire and the blind spot it created:

The proof — and what the call graph surfaced

The migration only counts if the list that failed is replaced by one that can't hide a producer — and proven on the real runtime, not in a console.

12 red
hygiene checks that failed the flip — from 10 in-process producers the command-line list never named.
+8 found
more producers surfaced by walking imports + subprocess calls — latent, invisible to the original list.
18 → 1
producers (10 + 8) repointed at one shared contract; each now asks where to write instead of asserting it.

The failed run wasn't a setback; it was the cheapest possible proof that a list built from command lines is structurally blind to in-process dependencies. The same trace that explained the twelve red checks found eight more producers that would have failed the next time they happened to run — caught before they ever stamped a false green. That is the difference between a migration that patches what broke and one that closes the class: you don't ask "which scripts failed," you ask "what kind of producer can this list not see," and you change the list until the answer is none.

This is the same shape as field note № 1 read from the other side. There, the move was to remove a coupling rather than harden the coupled component. Here, an unremoved coupling — a producer hard-wired to a local path — is precisely what made it invisible to the migration. Coupling doesn't only make a thing fragile. It makes the thing unreachable by the very operation meant to strengthen it.

The principle, generalised

This was one orchestrator on one Mac, but the moves aren't about Python imports:

You can't deploy a migration across scripts that hard-wire their own world. The flip reaches the ones that ask a shared contract where to live, and skips — silently — the ones that decided for themselves. Migrate the contract, and a decoupled fleet follows. Migrate a list, and the couplings you couldn't see stay exactly where they were.

antifragile.gf.cx · field note № 7 · published 2026-06-27 · drawn from a live gf.cx filesystem-contract migration · imports assets.gf.cx favicon + card primitives