Infrastructure facts don't belong in service code
A fact about infrastructure stored in service code is a second source of truth, and two copies drift. Generate the view from the source instead.
Two copies of one fact drift.
Keeping two copies in sync is an obligation, and every obligation that rests on a human remembering is a failure waiting for a busy week. The copies start identical. One changes, the other doesn't, and now the system holds two answers to the same question.
A view stored as state becomes a second copy
A fact about infrastructure that lives in service code is one the service doesn't own.
Take a monitoring component that needs to know what should be running in each region. That expected picture came from the service's own code, which kept its own list of what should run. The same facts already lived in the definition the deploy system used to create the infrastructure. Two copies of one fact. A new component in that definition did nothing to the expected picture, because the picture came from the service code. The new component stayed invisible to the monitor until someone updated that code by hand.
The expected picture is a view of the infrastructure: the same facts the definition already holds, shaped for one consumer. Stored in service code, that view takes on its own life, and a view with its own life is just a copy. The fact ended up owned by the service that reads it, not the layer that manages the infrastructure it describes.
Every infrastructure change becomes a code change
When the description of infrastructure lives in service code, that code becomes a gate on infrastructure. A new region, a new component, a new declared address: none of it should touch service code, yet all of it now waits on a code change, a review, a deploy. Work that should be a one-line edit to the definition takes on the full ceremony of a software release.
The friction is the visible cost. The drift is the expensive one. The definition changes because deploys force it to. The expected picture lags, because updating it is a separate step, and separate steps get skipped under load. So the monitor checks reality against a map that no longer matches it. It goes quiet on components that were added, because its map never learned they exist. It alarms on components that moved, because its map still expects them where they were. The map and the territory diverge in whichever direction is cheaper to leave alone.
Generate the view; don't maintain it
The fix removes the copy no one owns. Make the layer that owns the fact the only place it's defined, and have every consumer derive its view from there.
Generate the expected picture from that definition instead of producing it separately. Add a component to the definition and the next run rebuilds the file with it included: no service code change, no code review, no software deploy, as long as the consumer regenerates from the definition at runtime rather than baking the file into a build. The file still exists, but it's rebuilt from the definition every time instead of kept in step by hand, so it can't hold an answer the definition doesn't.
The definition is the source of truth for what *should* run; the live infrastructure is still a separate thing, and the monitor still checks the generated expectation against it. That check sharpens, because the expected side now comes from the definition instead of a stale hand-kept map. It also leads reality during a rollout: the expectation tracks desired state, so it will flag components that are declared but not yet running everywhere until convergence catches up. Gate the generator on rollout completion, or let the alarm tolerate that window. What vanished was one of two hand-maintained copies of desired state. The desired-versus-actual comparison stayed.
Matching the old format is the hard part: the generated file has to line up, field for field, with what consumers already parse. A projection still needs maintenance. The generator has one contract: read the definition, emit the format consumers expect. That contract changes far less often than infrastructure does.
The same trap shows up far from enterprise scale. Run a few services from a git repository and deploy them by automation: the repository is the one place the desired state is defined, and the deploy converges the running services toward it. Keep a separate list of what should be running, and you've rebuilt a second copy. The precondition for drift is small: two copies of one fact, and enough time for one to change.
A copy without an owner is just drift
A derived copy is sometimes worth keeping. A cache is a deliberate second copy: you trade staleness for speed or cost. The difference is ownership. A cache has an owner: a mechanism that bounds how stale it can get, whether by refreshing when the source changes or by expiring on a staleness bound someone chose on purpose. It can't diverge far, because someone built the mechanism that caps it.
The file in service code had none of that. No refresh trigger, no staleness bound, no owner: nothing kept it current but someone's memory. The test is ownership. Does the copy have someone who controls how far it can diverge? A copy with that owner is a cache. A copy without one is drift that hasn't surfaced yet.
Generation has a cost of its own. It spends work every time the view is derived. A stored copy pays per change. When the source rarely changes and the view is read constantly, a stored copy with a real refresh mechanism can be the right call. But that's a decision to own a cache, made on purpose, with the invalidation built. The old file was never that. It became a source of truth because no one set out to make it one.
Here's the test you can run against any system on your desk. Pick something the system stores, and ask: if I change the source it came from, does anything force me to remember to change this too? If a human has to remember, it's a second copy.
Then check whether the source can rebuild it exactly. Sometimes it can't: the thing holds history, a human override, or a field the source never tracked. That's a record, not a view, and when you can't tell which, treat it as a record. Records get an owner. Views get generated from the source instead of stored as their own truth.
If it is a view, the default is to generate it from the source rather than store it as its own copy. Keep the stored copy only when the source rarely changes, the view is read constantly, and you build the refresh that bounds its drift. A schema redeclared inside the service that consumes it, a constant pasted into two configs: same question, same answer.
The copies you keep aligned by remembering are the ones already drifting. You just haven't deployed the change that reveals it.