|
| 1 | +--- |
| 2 | +title: "Declarative Does Not Mean Destructive" |
| 3 | +linkTitle: "Convergence without surprise" |
| 4 | +description: "How Farrow uses per-node hashes and explicit operations so missing configuration can never authorize deletion." |
| 5 | +date: 2026-08-27T21:00:00+08:00 |
| 6 | +weight: 30 |
| 7 | +categories: [Design] |
| 8 | +tags: [Lifecycle, Drift, Safety] |
| 9 | +icon: fa-solid fa-arrows-rotate |
| 10 | +--- |
| 11 | + |
| 12 | +“Declarative” is often shortened to “make reality equal the file.” That is a |
| 13 | +useful slogan until the file is incomplete, the wrong branch is checked out, |
| 14 | +or one YAML group is temporarily removed. If absence is treated as deletion, |
| 15 | +an ordinary editing mistake becomes a destructive operation. |
| 16 | + |
| 17 | +Farrow uses a narrower rule: |
| 18 | + |
| 19 | +> Desired state may authorize creation. It can describe drift. It never |
| 20 | +> authorizes destruction by omission. |
| 21 | +
|
| 22 | +The distinction is central to a VM runtime because roots, data disks, SSH keys, |
| 23 | +and local evidence are not stateless replicas. Recreating them may be correct, |
| 24 | +but it must be a decision the operator can see. |
| 25 | + |
| 26 | +## From Inventory to node identity |
| 27 | + |
| 28 | +Farrow does not hash the whole Pigsty Inventory. It first extracts the fields |
| 29 | +it owns, fills defaults, resolves image and runtime choices, and builds a |
| 30 | +canonical resolved spec. Each node then receives a hash of: |
| 31 | + |
| 32 | +- the deployment envelope shared by every node, such as subnet, login user, |
| 33 | + and architecture policy; and |
| 34 | +- exactly that node's resolved definition. |
| 35 | + |
| 36 | +Adding a peer therefore does not change an existing node's hash. Editing an |
| 37 | +unconsumed Pigsty field—PostgreSQL version, packages, or service policy—does |
| 38 | +not produce VM drift. The VM layer reacts only to the contract it actually |
| 39 | +understands. |
| 40 | + |
| 41 | +This also avoids a dangerous half-promise: Farrow does not pretend to implement |
| 42 | +Ansible's entire variable system. Unknown `vm_*` keys and conflicting values |
| 43 | +inside the owned namespace fail. Everything outside the documented boundary |
| 44 | +is opaque rather than partially interpreted. |
| 45 | + |
| 46 | +> [!NOTE] |
| 47 | +> **Decision status: current.** Farrow converges additions automatically, but |
| 48 | +> definition changes and removal require explicit commands. See |
| 49 | +> [Daily Operations](/docs/start/operations/) for the command workflow. |
| 50 | +
|
| 51 | +## The five plan outcomes |
| 52 | + |
| 53 | +`farrow plan` compares desired state, applied deployment state, and committed |
| 54 | +node state. The result is intentionally small: |
| 55 | + |
| 56 | +| Outcome | Meaning | Apply path | |
| 57 | +| --- | --- | --- | |
| 58 | +| create | desired node has no committed state | `farrow up` creates it | |
| 59 | +| unchanged | definition and runtime still match | running peer stays untouched; stopped peer may start | |
| 60 | +| recreate | node definition changed | explicit `farrow recreate --force <node>` | |
| 61 | +| missing | applied node is absent or skipped in the Inventory | explicit `farrow destroy <node> --force`, or restore it to the file | |
| 62 | +| envelope drift | subnet, login identity, architecture, or runtime policy changed | whole-deployment recreate | |
| 63 | + |
| 64 | +Plan is read-only. It reports the exact node sets and, in text mode, the command |
| 65 | +that applies the required explicit transition. |
| 66 | + |
| 67 | +## Why `up` stops at drift |
| 68 | + |
| 69 | +Farrow could decide that changing CPU or memory is harmless enough to apply, |
| 70 | +or that a new image should silently rebuild a root disk. Pre-1.0 intentionally |
| 71 | +does neither. A changed VM definition is classified as recreate and `up` |
| 72 | +returns a typed conflict. |
| 73 | + |
| 74 | +That conservative boundary has two advantages: |
| 75 | + |
| 76 | +1. all changes that can invalidate Guest state share one visible operation; |
| 77 | +2. Farrow can finish every prerequisite check before touching the current |
| 78 | + node. |
| 79 | + |
| 80 | +The recreate path resolves the selected emulator, acceleration policy, |
| 81 | +firmware, image bytes, network backend, shares, and persistent-disk contract |
| 82 | +before destruction. If a foreign emulator is missing or a share is unsafe, |
| 83 | +the existing VM remains intact. |
| 84 | + |
| 85 | +## Why missing nodes block convergence |
| 86 | + |
| 87 | +A node can disappear from desired state for many reasons that do not express |
| 88 | +deletion intent: |
| 89 | + |
| 90 | +- the operator opened a reduced Inventory while debugging; |
| 91 | +- a group was renamed or filtered; |
| 92 | +- `vm_skip` temporarily marks a real or external host; |
| 93 | +- a merge conflict dropped a YAML branch; |
| 94 | +- the configuration file itself is unavailable. |
| 95 | + |
| 96 | +When applied state contains such a node, `up` stops and names it. The operator |
| 97 | +must either restore the definition or run the explicit destroy command. This |
| 98 | +is deliberately more friction than automatic garbage collection—and far less |
| 99 | +friction than recovering an unintended disk deletion. |
| 100 | + |
| 101 | +Persistent data disks add another boundary. Normal destroy preserves them; |
| 102 | +purging disks and deployment keys requires the separate whole-deployment purge |
| 103 | +contract. One confirmation cannot silently grow into broader authority. |
| 104 | + |
| 105 | +## Convergence is still incremental |
| 106 | + |
| 107 | +Safety does not mean rebuilding everything. New nodes are created without |
| 108 | +stopping existing peers. Selected stopped nodes start without recreating |
| 109 | +running ones. A per-node recreate preserves peers and, when requested by the |
| 110 | +disk contract, persistent data. |
| 111 | + |
| 112 | +The result is declarative where desired state is strong evidence—creation and |
| 113 | +comparison—and explicit where the cost is irreversible. Farrow does not make |
| 114 | +the operator manually calculate drift, but it also does not confuse a diff |
| 115 | +with permission. |
| 116 | + |
| 117 | +Read next: [A PID is not a virtual machine](/blog/design/identity-before-pid/). |
0 commit comments