From 95ce92c62f613b93ce56756902edb3155bd50ec7 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 5 Oct 2026 22:23:29 +0200 Subject: [PATCH] ADR 0221: a push sends no build a policy or a plan holds back, except to the machine it names A named push's cascade sent every machine a build held back by `record` or by a plan waiting on its first machine, so a change meant to be walked through the mesh one machine at a time reached all of them at once (issue 259). Records the decision, narrows ADR 0083's flush with a dated pointer, amends to-be 30, and locates issue 259 in the controller. --- ...083-one-push-leaves-the-mesh-consistent.md | 7 + ...lds-back-except-to-the-machine-it-names.md | 122 ++++++++++++++++++ 02-DECISIONS/README.md | 1 + .../30-the-mesh-updates-itself-on-a-push.md | 16 +++ .../00-report.md | 4 +- .../01-diagnosis.md | 27 ++++ 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md create mode 100644 04-ISSUES/259-a-named-push-sent-every-machine/01-diagnosis.md diff --git a/02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md b/02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md index d2fbd27..88c5d77 100644 --- a/02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md +++ b/02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md @@ -45,6 +45,13 @@ an operator obligation, and an obligation enforced by nothing is issue 057 resta declaration is computed from the whole mesh; delivering a mesh that is knowingly inconsistent and merely saying so would make "push succeeded" mean less than it says. +> **The mechanism changed — 2026-10-05, by [ADR 0221](0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md).** +> A named push still flushes every other machine that is behind, compared against what each was last +> sent. A machine whose modules would move to a build their upgrade policy records, or that an open plan +> has not sent it yet, is no longer flushed: the push names it and leaves it for `push `. "Behind +> for an unrelated reason" no longer covers a held upgrade +> ([issue 259](../04-ISSUES/259-a-named-push-sent-every-machine/00-report.md)). + ## Consequences - One push is sufficient for a cross-node consumer: the provider's grants arrive from the same diff --git a/02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md b/02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md new file mode 100644 index 0000000..8795192 --- /dev/null +++ b/02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md @@ -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 ` sent all four machines the +new build, and so did a later `push `. 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 ` 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 `, check, `push `. +- **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 ` 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 diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 4072782..ce13ae7 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -196,6 +196,7 @@ python3 00-META/checks/index.py fail if stale - **0212** — [A seat says what it receives, and the machine's hotkeys are a seat](0212-a-seat-says-what-it-receives-and-the-machines-hotkeys-are-a-seat.md) - **0218** — [A plan sends grants before code, rolls a module out one machine first, and a newer merge takes over an older plan](0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md) - **0219** — [The build queue is controlled through the controller and the build seat](0219-the-build-queue-is-controlled-through-the-controller-and-the-build-seat.md) +- **0221** — [A push sends no build a policy or a plan holds back, except to the machine it names](0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md) ### Its tiers, from the bottom up diff --git a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md index ad88df6..7c92650 100644 --- a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md +++ b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md @@ -4,6 +4,7 @@ status: proposed code: [] updated: 2026-10-05 decisions: + - 02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md - 02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md - 02-DECISIONS/0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md - 02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md @@ -149,6 +150,21 @@ What a merge changed is read from the forge whole, page by page module directory the mesh does not hold yet is that module's own, not shared code, when its definition is among the changed paths. +## What a push does not send (2026-10-05) + +Revision, [ADR 0221](../../02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md). +A named push still sends every other machine its work left behind, but no longer a build that an upgrade +policy or a plan is holding back +([issue 259](../../04-ISSUES/259-a-named-push-sent-every-machine/00-report.md)). + +- **A send records the builds it carried**: which build of each module the machine was sent. +- **A machine a push did not name is left** when a module it runs would move to a build its policy + records rather than rolls out, or that an open plan has not sent it yet. A machine whose last send's + builds are not known is left too. The push names the machine, the module, the builds and the reason, + and says `push ` sends it. +- The machine a push names, a push of every machine, and `push --behind` send held builds as before. + Walking a change through the mesh under `record` is one `push ` per machine. + ## Why now, and why not yet **Why it matters:** self-update is the difference between a mesh a person maintains by typing diff --git a/04-ISSUES/259-a-named-push-sent-every-machine/00-report.md b/04-ISSUES/259-a-named-push-sent-every-machine/00-report.md index 3bbe9d9..cd49e5c 100644 --- a/04-ISSUES/259-a-named-push-sent-every-machine/00-report.md +++ b/04-ISSUES/259-a-named-push-sent-every-machine/00-report.md @@ -1,9 +1,9 @@ --- -status: open +status: located opened: 2026-10-05 located-in: [mesh-controller] fixed-by: -amended-design: +amended-design: 03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md --- # 259. A push to one machine sent every machine diff --git a/04-ISSUES/259-a-named-push-sent-every-machine/01-diagnosis.md b/04-ISSUES/259-a-named-push-sent-every-machine/01-diagnosis.md new file mode 100644 index 0000000..3e16192 --- /dev/null +++ b/04-ISSUES/259-a-named-push-sent-every-machine/01-diagnosis.md @@ -0,0 +1,27 @@ +# 259 — Diagnosis + +## 2026-10-05 + +**Located in the controller's push.** A named push ends with the cascade of +[ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md): it sends every other machine +whose declaration's digest differs from the digest it was last sent. The comparison is the only one the +mesh can make from what it keeps. It keeps a digest of each send and nothing about what the send +carried, so a machine behind because a module moved under `record` and a machine behind because of a +grant read the same. + +**Not only `record`.** A plan rolling a module out one machine first +([ADR 0218](../../02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md)) +leaves the other machines differing while it waits on the first. Any named push elsewhere in that +window sends them the build the plan is holding. + +**And the machine holding the bus.** [Issue 249](../249-a-modules-new-state-is-refused-until-a-push-the-merge-did-not-make/00-report.md) +narrowed *whether* that machine is added to a push to its user list alone. Once added, it is sent its +whole declaration, held builds included. + +**Ruled out:** composing a held machine with its held modules kept at their last build, so it gets the +grant and not the upgrade. Reasons in the decision. + +**Decided** in [ADR 0221](../../02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md): +each send records which build of each module it carried, and a push does not send a machine it did not +name any build a policy or an open plan holds back. It names the machine and the remedy instead. The +fix is a pull request on mesh-controller; `fixed-by:` is filled when it merges.