|
|
|
@@ -0,0 +1,122 @@
|
|
|
|
|
---
|
|
|
|
|
topic: the mesh
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-10-05
|
|
|
|
|
deciders: jochen
|
|
|
|
|
reconstructed: false
|
|
|
|
|
extends: 02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 221. A push sends no build a policy or a plan holds back, except to the machine it names
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
[ADR 0083](0083-one-push-leaves-the-mesh-consistent.md) makes a named push finish what it starts. After
|
|
|
|
|
the named machine is sent, every other machine whose declaration differs from what it was last sent is
|
|
|
|
|
sent too. The case it was written for is a grant: assigning a consumer changes the provider's
|
|
|
|
|
declaration on another machine. ADR 0083 accepted that a machine behind *for an unrelated reason* is
|
|
|
|
|
flushed as well, and called that correct rather than a cost.
|
|
|
|
|
|
|
|
|
|
On 2026-10-05 that reasoning met an upgrade policy
|
|
|
|
|
([issue 259](../04-ISSUES/259-a-named-push-sent-every-machine/00-report.md)). A change to the resolver
|
|
|
|
|
modules was merged with the policy `record`, so that each machine would take it only when pushed, one
|
|
|
|
|
at a time: the anchor first, each checked before the next. `push <anchor>` sent all four machines the
|
|
|
|
|
new build, and so did a later `push <laptop>`. A fault in the change
|
|
|
|
|
([issue 260](../04-ISSUES/260-the-resolver-started-before-its-zones-file-existed/00-report.md)) was met
|
|
|
|
|
on every machine at once.
|
|
|
|
|
|
|
|
|
|
The cascade compares one digest per machine, and a digest cannot say why a machine differs. Under
|
|
|
|
|
`record`, every machine running the module differs from the merge on, so every machine is flushed. The
|
|
|
|
|
same holds for a plan's rollout under [ADR 0218](0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md):
|
|
|
|
|
while the plan waits on its first machine, the rest differ, and any named push elsewhere sends them the
|
|
|
|
|
build the plan is holding. [Issue 249](../04-ISSUES/249-a-modules-new-state-is-refused-until-a-push-the-merge-did-not-make/00-report.md)
|
|
|
|
|
met the same confusion for the machine holding the bus, which a send adds when its user list changed.
|
|
|
|
|
It narrowed that check to the user list, but the machine, once added, is still sent its whole
|
|
|
|
|
declaration.
|
|
|
|
|
|
|
|
|
|
So `record` held a change back from nothing but the merge, and "one machine first" held it back only
|
|
|
|
|
from the plan's own sends.
|
|
|
|
|
|
|
|
|
|
## Considered Options
|
|
|
|
|
|
|
|
|
|
1. **Report instead of cascade:** list every machine that is behind and send none. Rejected: it is the
|
|
|
|
|
option ADR 0083 rejected, and for the same reason. [Issue 057](../04-ISSUES/057-a-cross-node-consumer-is-provisioned-only-when-the-provider-is-pushed-again/00-report.md)'s provider would wait for a second push
|
|
|
|
|
nothing tells anyone to make.
|
|
|
|
|
2. **Send a held machine its consequences and keep its held modules at their last build.** The cascade
|
|
|
|
|
would compose the machine with each held module pinned at the build it was last sent, so a grant
|
|
|
|
|
still reaches it and the upgrade does not. Rejected: the catalogue resolves every machine against
|
|
|
|
|
one manifest per module, the current one. Pinning means resolving a machine's set against a mix of
|
|
|
|
|
current and older manifests, read back from the build records, beside the current settings, seats
|
|
|
|
|
and grants. The result matches neither what the machine runs nor what the mesh would send it, so
|
|
|
|
|
neither `plan` nor `status` could show it. A grant composed for the new build may not fit the old
|
|
|
|
|
one. It is a second composition path to get one edge case right, and the edge case has a one-word
|
|
|
|
|
remedy: name the machine.
|
|
|
|
|
3. **Keep, with every send, which build of each module the machine was sent, and leave a machine any
|
|
|
|
|
of whose modules a policy or a plan holds back.** Chosen.
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
**1. A send records the builds it carried.** With the digest of every declaration it sends, the mesh
|
|
|
|
|
keeps which build of each module the declaration carried: the commit the module's current build was
|
|
|
|
|
made from. A module left out of the declaration keeps the build it was last sent. A declaration sent by
|
|
|
|
|
hand records that what it carried is not known.
|
|
|
|
|
|
|
|
|
|
**2. A push does not send a machine it did not name a held build.** A machine reached by a named push's
|
|
|
|
|
cascade, or added because it holds the bus, is not sent when any module it runs would move to a build
|
|
|
|
|
that:
|
|
|
|
|
|
|
|
|
|
- its upgrade policy records rather than rolls out, or
|
|
|
|
|
- an open plan has not yet sent it: the plan is still building the module, or has sent it to its first
|
|
|
|
|
machine and this is not that machine.
|
|
|
|
|
|
|
|
|
|
A module the machine was never sent counts as a move. A machine whose last send's builds are not known
|
|
|
|
|
counts as held. It was sent before this was kept, or by hand, so a held upgrade cannot be told apart
|
|
|
|
|
from anything else.
|
|
|
|
|
|
|
|
|
|
**3. It is named, not hidden.** The push says which machine it left, which module and which builds, why,
|
|
|
|
|
and that `push <node>` sends it. For the machine holding the bus it also says that the bus may refuse
|
|
|
|
|
what this push's machines were newly granted until that machine is sent.
|
|
|
|
|
|
|
|
|
|
**4. Everything else stays as it is.** A machine with nothing held is flushed exactly as ADR 0083
|
|
|
|
|
decides: a grant, a peer, a setting. The machine a push names is sent everything, held builds included.
|
|
|
|
|
A push that names no machine sends every machine. `push --behind` still sends every machine that is
|
|
|
|
|
behind, held or not. It is the remedy `record` names when an upgrade is announced ("`push --behind`
|
|
|
|
|
when you want them"), and the one command for taking a recorded upgrade everywhere. Narrowing it would
|
|
|
|
|
leave `record` with no way to say "now".
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
- `record` and "one machine first" hold a change back from every push that does not name the machine.
|
|
|
|
|
Walking a change through the mesh is `push <anchor>`, check, `push <next>`.
|
|
|
|
|
- **The cost:** a held machine that is also owed a grant from this push waits for its own push. The
|
|
|
|
|
provider in issue 057's case, if a held upgrade is pending on it, is not sent its new grant, and its
|
|
|
|
|
consumer is refused until the provider is pushed. The push names that machine and the remedy, so the
|
|
|
|
|
wait is announced, not silent. When the holder of the bus is held, a new grant may be refused by the
|
|
|
|
|
bus until it is sent, and the push says so.
|
|
|
|
|
- On the first push after this ships, no machine's last send has its builds recorded yet. Each is held
|
|
|
|
|
from cascades until it is pushed once: by name, in a whole-mesh push, or by `push --behind`.
|
|
|
|
|
- A manifest handed over by hand does not change the commit a module records, so a cascade still sends
|
|
|
|
|
its change. Only builds have a commit to compare.
|
|
|
|
|
- ADR 0083's consequence that a machine behind for an unrelated reason is flushed is narrowed. It still
|
|
|
|
|
holds for every reason except a build held back.
|
|
|
|
|
|
|
|
|
|
## How it is checked
|
|
|
|
|
|
|
|
|
|
| Rule | Checked by |
|
|
|
|
|
|---|---|
|
|
|
|
|
| a send records the builds it carried, and "not known" | the controller's test: the builds kept with a send round-trip, an empty send is known and empty, a send by hand reads as not known |
|
|
|
|
|
| a left-out module keeps its last build | the controller's test: a module left out of a declaration records the build it was last sent |
|
|
|
|
|
| a recorded upgrade holds a machine a push did not name | the controller's test: with a module under `record` moved, the cascade of a push naming the anchor leaves the laptop's last send unchanged and names it, the module, both builds and `push laptop` |
|
|
|
|
|
| held and owed something else: not sent, both said | the same test: a newly placed machine changes the laptop's peers while the upgrade is held; the laptop is not sent and the output says why and that what else it is owed waits |
|
|
|
|
|
| a roll-out policy is not held | the same test: with the policy set to roll out, the laptop is sent and its new build recorded |
|
|
|
|
|
| a consequence nothing holds is still sent (issue 057) | the controller's test: a placed machine's peers reach the others by cascade; a machine whose last send is not known is held |
|
|
|
|
|
| a plan waiting on its first machine holds the rest | the controller's test: a machine the plan has not reached is held, the first machine is not; a module sent everywhere, failed, or in a closed plan holds nothing |
|
|
|
|
|
| live | the next change merged under `record`: `push <anchor>` sends the anchor alone and names every other machine running the module |
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [Issue 259](../04-ISSUES/259-a-named-push-sent-every-machine/00-report.md), [issue 260](../04-ISSUES/260-the-resolver-started-before-its-zones-file-existed/00-report.md), [issue 249](../04-ISSUES/249-a-modules-new-state-is-refused-until-a-push-the-merge-did-not-make/00-report.md)
|
|
|
|
|
- [ADR 0083](0083-one-push-leaves-the-mesh-consistent.md): the cascade, narrowed here
|
|
|
|
|
- [ADR 0218](0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md): one machine first, now held from a push as well
|
|
|
|
|
- [ADR 0010](0010-delivery.md): delivery, and what `push --behind` answers
|
|
|
|
|
- [to-be 30](../03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md): the design this amends
|