From 4ee06bd99f3a30d829bbf61bd4aa81345c211594 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 6 Oct 2026 19:12:51 +0200 Subject: [PATCH] ADR 0236: no build reaches a machine without a gate, and a release plan walks what waits The coordinator's review: at the switch to roll, every send would carry the old default's backlog unjudged. Measured: 88 of the 103 rebuilt modules were byte-identical, and no build waited on any machine. --- ...n-itself-and-so-it-rolls-out-unattended.md | 39 ++++++++++++++++++- .../45-a-core-that-cannot-fail-silently.md | 8 +++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md b/02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md index f290050..1e392ce 100644 --- a/02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md +++ b/02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md @@ -126,6 +126,39 @@ resulting policy of every module the mesh held on 2026-10-06: The private network itself is provided by the controller and moves only with it; the modules that run on no machine move nothing. +**4a. No build reaches a machine without a gate — the backlog included.** A send carries a machine's +whole declaration, so a plan sending one module, a cascade, a healer's resend or a whole-mesh push would +carry every other build waiting there. Under the old default builds were registered and sent nowhere; +on the day the default becomes `roll`, the next send of anything would restart them all at once, on every +machine. So: + +- **a gated send carries everything waiting on its machine, and its gate judges all of it** — a plan's + first machine, a release plan's machine; a pass is each build's verdict, a failure puts back what was + found wanting, on the machines that were sent it; +- a module a send exists for may move where it goes — a policy of *together*, a rollback; a build that + passed a gate on one machine may go to the others; +- a person's send naming a machine (`push `, the bus step) carries what it carries; +- **every other send is refused, or leaves the machine, while a build no gate has seen waits there** — + a plan's "rest", a cascade, a healer's, a whole-mesh push, the bus's user list carried — said with what + waits and the remedy; +- **a rebuild that made the same artifacts from the same manifest is no move.** + +**The release plan walks what waits.** Whenever builds no gate has seen wait on machines and no plan that +has started walks them, the mesh opens a release plan: every such machine heard from, **one at a time, +the control node last**, each sent everything waiting there and judged by the gate before the next is +sent. A machine not heard from when its turn comes is left. A build asked outside a plan (a `rebuild`) +waits for it too, instead of being sent one machine after another unjudged. **A release plan that fails +puts back what failed and stops; the next one opens only when a person says `upgrade release-backlog +--why`**, and until then `release-held` says what waits. `upgrade backlog` lists it, read-only. + +Chosen over holding the whole backlog for a person's release (safer by one human glance, but every +merge after the switch would then stall behind it) and over waves of a few modules (a send cannot carry +part of a declaration — ADR 0221's second option — so the bound that can be kept is one machine at a +time, which is the one kept). Measured on 2026-10-06 at the switch: the catalogue merge that rebuilt +103 modules for a change to the build agent made **88 of them byte-identical** to the builds before +(no move) and 15 different; every machine had already been sent all of them by hand that evening, so +**no build waited on any of the four machines** when this was decided. + **5. The controller's rollback is the node-engine's on its machine; the contract is written once on each side.** mesh-controller `internal/lease/witness.go` and mesh-host `internal/witness/contract.go`, held field for field: @@ -200,8 +233,9 @@ not say which files went, marks the module deleted in its plan, which goes on. node-engine reports container state, which is mesh-host's to add. A build whose migration cannot be undone is put back all the same; marking such a build `not-reversible` for the witness is not composed yet. -- **A build asked by hand that rolls out** (a `rebuild` outside a plan) is still sent one machine at a - time by the upgrade handler, without the gate; only plans are gated. +- **A merge after a release plan failed may stall** on a machine where a build waits for the person's + release: its first send carries and judges what waits there, but its "rest" waits. Said in the plan + and by `release-held`. - **Rollout order**: mesh-host first (its genesis user list, and the witness, which reads nothing the controller does not yet grant until then); the controller second, whose migration turns the store's default `record` rows into no choice; the catalogue third — its manifests carry `upgrade`, which a @@ -217,6 +251,7 @@ not say which files went, marks the module deleted in its plan, which goes on. | the health definitions | the controller's test per component: tools not served, a condition since the send, a witness's verdict, node tools not answering, a lease held by an older controller or one not ready | | the policy | the catalogue's test: default roll, a module's word, irreplaceable data, the bus whatever it says; a record without why refused; the store's test: the current builds read the derived policy | | the bus is never rolled out | the controller's test: the bus records whatever it says, a person's roll-out is refused, a plan sends nothing, no send may reach its machine while a new bus build waits — a rebuild with the same artifacts and manifest excepted — the step refuses without its word on reversibility and without its snapshot, and starts with both | +| no build without a gate | the controller's test: the backlog released one machine at a time, the first judged before the second is sent, each pass kept, a rebuild with the same bytes no move, a send that judges nothing refused; a release that fails puts back what it carried on its first machine, goes no further, holds the next until a person releases it with why; a cascade does not carry a build no gate has seen, and does once one passed | | a deleted module is not built | the controller's test: a merge deleting a module's manifest asks no build and forgets it; a build finding no manifest leaves the plan going | | the witness's contract | the lease package's test of the host's rule; the broker's test of the grants (the ping on every machine, the lease's key where the controller runs, no write); the controller's test that a witness's verdict is its condition while reports carry it and cleared after | | live | the next merge to the catalogue sends one machine first and the rest after its gate, with no push; `plans ` shows the gate's record; the next week's hand-act log has no push for a build that rolled out | diff --git a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md index 908d59b..d5a2ee0 100644 --- a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md +++ b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md @@ -445,6 +445,11 @@ the other machines follow. "Reported applied" is not enough. the gate sending the previous build. A witness says its verdict in `rollbacks` on every report while it stands; the controller raises `core...` from it — urgent for rolled-back, not-reversible, restore-failed and halted — and clears it with the first report without it. +- **No build reaches a machine without a gate**: a gated send carries and judges everything waiting on + its machine; every other send — a plan's rest, a cascade, a healer's, a whole-mesh push — is refused + or leaves the machine while a build no gate has seen waits there; a rebuild with the same artifacts and + manifest is no move. What waits is walked by a **release plan**, one machine at a time, the control + node last, each judged; one that fails holds the next until `upgrade release-backlog --why`. - **A new controller that passes its gate sends the bus's machine the user list it composes**, when that changed and nothing held back would go with it. - **The default policy is to roll** (ADR 0236 §4); `record` stays where a module says why, keeps @@ -623,8 +628,7 @@ installer's grants. In mesh-catalog, the modules that keep `record` say why, and says which files a merge deleted. On branches, not yet merged. **Not yet:** R3, R7, R8 on a lab mesh, and so the *done when*; container state in the node-engine's report, without which a container that crash-loops after its compose applied is seen only through what it breaks; composing `not-reversible` for -a build whose migration cannot be undone; the gate for a build asked by hand outside a plan; the next -three core rollouts' verdicts. +a build whose migration cannot be undone; the next three core rollouts' verdicts. ### Phase 5 — Checks before merge, and the replays