Why can two runs of the same declarative desired-state description do the work in different orders?
answer
- position in the file is not meaning
- edges come from references
- a partial order, not a sequence
- independent steps commute
- undeclared coupling is invisible
basics
~20 sOrder is not written in the description: the engine derives it from declared dependencies and may sequence anything they leave unconstrained. Two runs still agree because independent work commutes, provided every real dependency was declared.
solid answer
~40 sThe description states which items must exist, and which of them reference which; from that the engine builds a dependency graph and needs only an order consistent with it. Items with no path between them are unconstrained, so one run may do them left to right, another in parallel, and a third in the opposite order. The results agree because independent operations commute: reordering steps that do not affect each other cannot change where you end up. That guarantee is only as good as the graph. A dependency that is real but never declared — two items touching the same external thing without naming it — is invisible to the engine, and the run that happens to schedule them the wrong way round is the one that fails.
code
yaml · 9 linesitems:
- id: store
kind: datastore
- id: api
kind: service
needs: [store]
- id: worker
kind: service
needs: [store]go deeper
Remember that the file's layout is for readers, not for the machine. What controls order is which item mentions which; anything that mentions nothing may be done at any point the constraints allow.
Explain the derivation: references become edges, edges give a partial order, any topological order satisfies it, and independent nodes may overlap. Then name the catch — a dependency you never declared is one the engine is free to violate.
Show the diagnosis. A run that starts failing after an engine upgrade or a parallelism change is the classic signature of an undeclared coupling; be able to say how you would force different valid orders to expose it before it reaches production.
The org-level question is what the team owes the graph. Decide whether dependencies must be explicit even when redundant, how cycles are handled in review, and how much concurrency you are willing to run at given the descriptions your teams actually write.
## Order is absent from the description, not from the problem A desired-state description is a set of facts: these artifacts should exist, with these properties, and this one names that one. Nothing in it says "first", "then" or "afterwards". But the underlying problem still has order in it — a service that refers to a datastore cannot be usefully created before the datastore exists. The paradigm's move is to let the engine **derive** that order from what the items say about each other, instead of asking the author to encode it by position. The consequence catches people out: **where a line sits in the file is not meaning**. Move an item to the top and nothing about what you asked for has changed. ## How an engine recovers an order 1. **Read the items** and index them by identity. 2. **Turn references into edges.** Every place one item names another becomes a constraint: the named thing first. 3. **Reject cycles.** If the edges form a loop there is no consistent order, and a sound engine fails immediately rather than picking one arbitrarily. 4. **Choose any topological order.** Several usually exist; the engine picks one by whatever internal rule it likes, and that rule may change between versions. 5. **Overlap what is independent.** Items with no path between them can be worked on concurrently, which is where the same description shows visibly different orders across runs. Take three items: a datastore, a service that names the datastore, and a worker that also names the datastore. That is one edge from the service and one from the worker — two edges, and no edge between service and worker. So the datastore is always first, and the other two are genuinely unordered: either sequence, or both at once, satisfies the graph. ## Why two orders still agree The property being relied on is that **independent operations commute** — if two steps do not affect each other's inputs or outputs, performing them in either order leaves the same final state. A reduction system with that property is called **confluent** (the Church-Rosser property): different valid sequences of steps converge on the same result. A desired-state run is confluent exactly to the extent that every genuine interaction between items is represented as an edge the engine can see. ## When order independence is a lie | Coupling between two items | Visible to the engine? | What the engine may do | |---|---|---| | One item names the other | yes, an edge | always orders them | | Both touch the same external thing, unnamed | no | either order, or both at once | | One was simply written above the other | no — position is not meaning | may reverse them | | One needs a side effect the other causes | no, unless declared | may run them the wrong way round | The failures that follow are the characteristic bugs of this paradigm: - **The flaky run.** It has worked for months because the engine happened to pick a favourable order; a version upgrade or a parallelism bump reverses it, and now it fails half the time. - **The timing-shaped dependency.** Item B works only because item A was slow enough to finish something first. Nothing declares it, so nothing preserves it. - **The half-applied graph.** A failure part-way leaves some nodes applied and some not, and the next run must cope with the mixture — which is exactly what makes re-runnability a requirement rather than a nicety here. ## What this changes in practice - **Declare the edge instead of relying on position.** If B truly needs A, say so, even when today's engine happens to order them that way. - **Expect concurrency.** Anything that must not happen at the same time as something else needs a stated relationship, not a hopeful layout. - **Read failures as graph questions.** "Why did it do that first?" is usually answered by the edges the engine had, not by the file you were reading. - **Treat a cycle as a modelling error.** Two items that each need the other usually means one of them should be split. The pay-off is real: a correct graph gets you parallelism, safe retries and a file whose layout you can organise for human readers rather than for the machine. The obligation is equally real — you owe the engine every dependency that exists, because it can only honour the ones you wrote down.
- What happens if the dependencies in the description form a cycle?There is no order consistent with the constraints, so a sound engine refuses the whole run and names the loop rather than guessing a starting point. Treat it as a modelling error: usually one of the two items is doing two jobs and should be split, so that one half can be created before the other item and the second half after it.
- How do you catch a dependency that is real but never declared?Force the orders the engine is allowed to choose. Run with concurrency raised, and apply items in isolation and in different subsets, on a system built from nothing rather than one that already has the earlier artifacts. Anything that only succeeds in one particular sequence has a coupling the graph does not know about, and that coupling is what you then declare.
saying these in an interview costs you the question
- Items are applied top to bottom in file order
- Declaring extra dependencies is unnecessary if it currently works
- The engine can infer any dependency from the item contents
- Two independent items are guaranteed never to run at once
- A cycle just means the engine starts at the first item