THIS EXPLANATION
THE ROOM
CDA·65 Computing, Data & AI 6 MIN · 8 STATIONS

Declaring the destination, not the route

A Socratic walk-through of declaring the destination, not the route — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

Why is writing down what a system should look like more reliable than the commands to get it there?

Two files describe the same server. One is a script: install this package, edit that line, create this user, restart the service. The other is a description: this package is present, that setting has this value, this user exists, this service is running.

They look like the same information in two grammars. And on a fresh machine they do produce the same result. So why does one of them get called a step forward in how we manage systems, while the other is the thing everybody is trying to get away from? The difference cannot be visible on the fresh machine, because there they agree. It must show up somewhere else — and the useful question is where.

b

Reasoning it through

REASONING #

Run each of them twice.

The script's second run does something odd. It tries to create a user that exists, appends the same line to a file that already has it, installs a package already installed. Some of those steps fail; some succeed harmlessly; at least one usually leaves the machine slightly worse than before. The description's second run does nothing at all, because everything it asks for is already true.

That asymmetry is the first real clue, and it has a name — idempotence, the property that applying something twice leaves the same result as applying it once. But notice why the description has it. Not because its author was more careful: because a statement about a desired condition can always be checked before it is acted on, and a command cannot. "This user exists" implies a test. "Create this user" does not.

Now push further. Run each on a machine that has been running for a year and drifted. The script's steps were written as a route from a particular origin, and the machine is no longer at that origin. The description does not care where the machine started: it states an endpoint, and the gap between here and there is computed at the time, from the actual current state.

This is the substantive difference, and it is worth stating plainly: a script encodes a path, which is only valid from one starting point. A declaration encodes a destination, which is valid from every starting point. The number of possible starting points for a long-lived machine is enormous, which is exactly why route-encoding fails at scale and destination-encoding does not.

But something has to close the gap. If the file merely says what should be true, who makes it true? Some component must read the declaration, inspect the world, compute the difference, and act — and then, crucially, do it again. That repeated cycle is the reconciliation loop, and it is what makes this a control system rather than a wish. A single reconciliation is a very good installer; a continuous one pulls a drifting machine back, because it measures reality afresh on every pass rather than trusting what it did last time.

Consider what that gives you for free. The same file that creates the system also repairs it and documents it, and — since it is a statement rather than a sequence — can be diffed. Ask what a change will do and the answer is computable in advance: compare declared state to observed state and show the difference. That is why the plan-then-apply pattern exists here and has no natural equivalent in the scripted world, where the only way to learn what a script does is to run it. It is also why a commit history means more: a history of scripts records what was done, while a history of declarations records what the system was supposed to be at each point in time, which is the question you actually ask during an incident.

One honest caveat: the boundary is fuzzier than the marketing suggests. Every declarative tool has an imperative engine underneath, and most have escape hatches through which people smuggle routes back in. Some things genuinely resist declaration too: a database migration is an ordered sequence with side effects, and pretending it is a state is how people lose data.

c

The analogy

THE ANALOGY #
THE FIGURE

Think of the difference between giving someone driving directions and giving them an address. Directions are precise and efficient, and they work perfectly from the corner you had in mind — but they are useless to a person who is somewhere else, and worse than useless if a road has closed since you wrote them. An address is less specific about the journey and yet more robust: it can be reached from anywhere, re-planned when a road shuts, and checked continuously against where you actually are.

WHERE IT BREAKS DOWN

the driver holds the map and does the re-planning, whereas a declarative system has no general map at all — it can only compute a route through the transitions its own model already knows about, which is why an unmodelled change (something edited by hand, or a resource type the tool has no concept of) is simply invisible to it rather than something it routes around.

d

Clarifying the model

THE MODEL #

Three clarifications worth holding onto.

First, declarative is not the same as continuously reconciled. Some tools compute a plan and apply it once, on demand; others loop forever. Both are declarative in grammar, but only the looping kind resists drift, because drift is by definition what happens between runs — and a guarantee refreshed only when a person triggers it lasts exactly as long as nobody touches the machine.

Second, the file is not the truth — it is a claim about the truth, and the loop is what makes the claim good. Systems where the declaration exists but the loop is disabled are the worst of both worlds: a document everyone trusts and a reality that has quietly moved on.

Third, ordering does not vanish; it moves. Someone still has to know that the service cannot start before the config file exists. In a script that knowledge is line order; in a declarative system it is a dependency between resources, from which the engine derives an order. That is a real gain — stated once, and everything unrelated can run in parallel — but it is not the absence of sequencing, and treating it as such produces the classic "it works on the second run" bug, where a missing dependency edge is masked by a resource that happened to exist by then.

e

A picture of it

THE PICTURE #
Declaring the destination, not the route
Declaring the destination, not the route Follow the numbered messages in order and notice that the controller reads reality every pass rather than remembering what it did last time -- that is the whole reason the second arrow exists. The alt block is the idempotence: the same cycle either acts or does nothing, depending on what it just observed, and nothing in the declaration changes between the two cases. The note at the bottom is the difference between an installer and a control loop. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/declaring-the-destination-not-the-route.md","sourceIndex":1,"sourceLine":4,"sourceHash":"e570625c71162c69a4e60c28c8cab0b3fb1ee721719def6f30bd43a56f41d7c6","diagramType":"sequence","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":751,"height":866},"qa":{"passed":true,"findings":[]}} Actual system 01 Controller 02 Declared state 03 alt [difference found] [already matching] loop repeats, so later drift is pulled back read what should be true 1 observe what is true 2 current facts 3 compute the difference 4 apply only the difference 5 do nothing this pass 6
KINDSlifelineparticipantalternativemessage

How to readFollow the numbered messages in order and notice that the controller reads reality every pass rather than remembering what it did last time — that is the whole reason the second arrow exists. The alt block is the idempotence: the same cycle either acts or does nothing, depending on what it just observed, and nothing in the declaration changes between the two cases. The note at the bottom is the difference between an installer and a control loop.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

A declaration is more reliable than a script not because it is tidier but because it is valid from any starting point, and because stating a condition makes it checkable in a way that stating a command does not. The power sits in the pairing: a description of the destination, plus a loop that keeps measuring the distance to it. Take away the loop and you are left with a document; take away the description and you are back to a route that expires the moment the world moves.

g

Where to go next

ONWARD #
  • Configuration drift, which is the failure the reconciliation loop exists to absorb.
  • GitOps, where the declared state lives in a repository and the loop watches the repository.
  • Where declaration genuinely fails — schema migrations, one-way data transformations, stateful cutovers.
h

Key terms

TERMS #
TermWhat it means
Idempotencethe property that applying an operation many times has the same effect as applying it once.
Reconciliation loopa repeating cycle that observes actual state, compares it to declared state, and applies the difference.
Planthe computed difference between declared and observed state, shown before it is applied.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4