diff --git a/00-META/glossary.md b/00-META/glossary.md index 8b749d22..b23fbc36 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -319,6 +319,11 @@ planner, the walk). **Downstream of** Module, Core, Health and repair and Data. `held`, and the final four). Not *change* (a diff). *Pipeline* is the predecessor's build-and-deploy mechanism, which the as-is designs describe; it is never a word for a delivery. *Not:* ~~deployment~~ (hq) +- **covered** — the end of a delivery that a later merge containing it delivered: every module it moves was + sent to every machine running it by the walk of a later merge into the same trunk, delivered on its own + passing check, whose merge commit the forge says contains this one. Not *superseded*, which is a delivery + whose walk a newer delivery took over before it ended; nor *delivered*, which its own walk or an empty plan + ends in ([ADR 0270](../02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md), proposed). - **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One level: a group holds deliveries, never groups. Its state is derived from its members, never set. diff --git a/02-DECISIONS/0270-a-held-delivery-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md b/02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md similarity index 56% rename from 02-DECISIONS/0270-a-held-delivery-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md rename to 02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md index 62f76eea..c2f37025 100644 --- a/02-DECISIONS/0270-a-held-delivery-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md +++ b/02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md @@ -7,7 +7,7 @@ reconstructed: false extends: 02-DECISIONS/0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md --- -# 270. A held delivery whose change already runs, or has nothing to run, ends by itself, and a hold whose reason is gone lifts +# 270. A held delivery that moves nothing, or that a later delivered merge contains, ends by itself, and a hold whose reason is gone lifts ## Context @@ -42,7 +42,7 @@ person could answer correctly: `release` would walk an old commit over a newer one, or walk nothing. `retire-history` refuses each one, because their heads were heard. `stop` is the only way out that works, and it is the wrong record: it sets -`mesh/delivery` to `error` on a pull request whose change runs on every machine. Four were stopped that way on +`mesh/delivery` to `error` on a pull request whose change a later delivered merge carried. Four were stopped that way on 2026-10-09 before the issue was found, and their pull requests now say "stopped: covered: …" as an error. A condition that only a wrong act can clear teaches people to ignore the list it is in. @@ -51,17 +51,21 @@ condition that only a wrong act can clear teaches people to ignore the list it i 1. **Stop them with why.** Rejected: it is what was done, and it records a delivered change as an error on its pull request. Issue 313 ruled it out for the same reason. 2. **A person's act, `supersede`, that ends a held delivery as superseded.** Rejected as the main path. Every - fact the person would check is already in mesh-delivery's own records: the plan, the forge's merge times - and the walks' records. A person asked to confirm a fact the mesh can read is a person asked for nothing. - A person keeps `stop` and `release` for what the facts do not settle. -3. **Ask the forge, or the controller, whether each running build's commit contains the merge** (git - ancestry through a new forge tool, or through the controller's records of what each machine runs). - Rejected for now. The forge module has no such tool, and adding one means changing a TypeScript module. - The trunks are protected: they take merges only through pull requests, and no one can push to them - (`gitea.gitea_branch_protection_get` on each trunk says `push: false`). So a commit the forge merged later - into the same trunk contains every commit merged into it before. The merge times say that order, and - mesh-delivery already holds them. Ancestry would answer the same question with more parts. -4. **Read the facts, and end the delivery by observation** — chosen, below. + fact the person would check can be read by the mesh: the plan, the walks' records and the forge's history. + A person asked to confirm a fact the mesh can read is a person asked for nothing. A person keeps `stop` and + `release` for what the facts do not settle. +3. **Trust merge order alone.** Rejected after review. On a trunk that takes no push, a commit the forge + merged later contains every commit merged before it, and mesh-delivery already holds the merge times. But + that rests on the trunk's protection, which is the forge's setting and which nothing in mesh-delivery + checks. A protection lifted once, or a merge time read wrongly, would end a delivery as covered when its + commit is not in the later one. Merge order stays, as the cheap first filter. +4. **Ask the forge whether the later merge commit contains this one** — chosen. The forge module gains a + read-only tool, `gitea_contains`, over the forge's compare of the two commits: none of this commit's history + missing from the later one means it is contained. mesh-delivery is not given the forge's general API tool, + which can write. The tool is TypeScript, as the rest of the forge module is; no Go route to the forge's API + exists for a module to call. Asking the controller what each machine runs was weighed and left: the + controller holds no clones, and what runs is already what the later walk's record says it sent. +5. **Read the facts, and end the delivery by observation**, with point 4 deciding containment — below. For issue 316 the alternative was to require `mesh/delivery-group` on the trunks, so that no member can merge before its composed check passed. That stays the operator's setting, as ADR 0239 left it. It does not settle @@ -74,26 +78,47 @@ a check that answers after the merge. that only adds a module the controller builds and sends nowhere. This holds whatever its check said: from `proposed`, `checked` and `rejected`, the row is tried before *merged unchecked → held*, and from `held` it is tried on every tick. `ready → published → delivered` already did this for a merge that passed. -2. **A new final state, `covered`.** A merged delivery is covered when every module its plan moves was sent - by a finished walk of another delivery that: - - is of the same repository and was merged into the same trunk; - - was merged after it, by the forge's merge times, so its merge commit contains this one; - - is `delivered`. + **A delivery that only adds a module is reported delivered whatever its build's outcome.** The build is + the controller's, registered when it lands and sent nowhere. A failed build of it is the build seat's + condition (S6) and is seen when the module is assigned, not this delivery's. +2. **A new final state, `covered`: a later merge that contains it was delivered.** A merged delivery is + covered when, for every module its plan moves, another delivery: + - is of the same repository and was merged into the same trunk, after it by the forge's merge times (the + cheap filter); + - is `delivered`, and its own check passed; + - was delivered by its own walk: the walk's commit is its merge commit, on that trunk; + - sent the module to every machine running it, as its machine steps say (sent to all at once, or to the + rest after its first machines passed), with no step of it failed or put back; + - has a merge commit that contains this one's, as the forge says through `gitea_contains`. - The modules a walk sent are read from the walk's own record, or from the machine steps kept for that - delivery. A delivery whose own walk is still open is never covered: its walk delivers it. The row is - observed. It is tried from `held`, and from `ready` once the ten minutes for a walk to open have passed, - before the no-walk hold. The transition's why names each covering delivery, the commit it merged as and its - walk. On the pull request, `mesh/delivery` says `covered: …` as a **success**, never an error. A group - counts a covered member as delivered, so the member after it gets its turn and is not stopped. -3. **A group member is not held for a composed check that has not answered.** "Did not pass for the heads that + The forge is asked only once every module has a candidate, once per pair, outside the holder's lock, and its + answer is kept on the delivery. A forge that cannot answer means "not yet covered": the delivery stays where + it is and is asked about again a minute later. A delivery whose own walk is still open is never covered: its + walk delivers it. + + The row is observed. It is tried from `held`, and from `ready` once the ten minutes for a walk to open have + passed, before the no-walk hold. From `held` it is tried before `go`. If a walk a person started fails + between two readings of a covered delivery, the delivery ends `covered`, not `delivering` then `failed`: the + later merge that contains it was delivered whatever its own walk did. The transition's why names each + covering delivery, the commit it merged as and its walk. On the pull request, `mesh/delivery` says + `covered: a later merge that contains it was delivered: …` as a **success**, never an error. A group counts + a covered member as delivered, so the member after it gets its turn and is not stopped. + + `covered` says what was delivered, not what runs now: a revert merged after the covering merge is possible, + and is that revert's own delivery. +3. **A merge held without a passing check is covered without a person** when a later delivery whose own check + passed carries it, and only then. A later delivery's walk runs only on a passing check, so its check has + passed in every case this matters for. What a person would have been asked, "does this unchecked change go + on?", was answered by the later change, which was checked with this one's change in its history and + delivered. +4. **A group member is not held for a composed check that has not answered.** "Did not pass for the heads that merged" means the check failed, ran for other heads, or was never asked. A member published while its group's check is asked and not answered waits in `published`, within that state's bound. -4. **A hold whose reason is gone lifts.** A member held because its group's composed check had not passed is +5. **A hold whose reason is gone lifts.** A member held because its group's composed check had not passed is taken back to `published` when that check passes or warns for the heads that merged, and its own walk is still open. The event is *lifted*, observed, and the member then takes its turn in the group's order. Every other hold stays a person's. -5. **Read again on every tick.** The holder reads the facts of every held delivery on each tick, not only when +6. **Read again on every tick.** The holder reads the facts of every held delivery on each tick, not only when an event arrives. A delivery that was held before these rows existed, or read back after a restart, ends by itself. Once it is final, nothing moves it again. @@ -102,7 +127,7 @@ The table of ADR 0239 decision 2 gains these rows: | From | Event | To | Guard | |---|---|---|---| | proposed, checked, rejected, held | done | delivered | merged, no walk, and its plan moves no module the mesh holds | -| ready, held | covered | covered | merged, no walk of its own open, and every module it moves sent by a finished walk of a later-merged, delivered delivery to the same trunk | +| ready, held | covered | covered | a later merge that contains it was delivered: merged, no walk of its own open, and every module it moves sent to every machine running it by the walk of a later merge into the same trunk, delivered on its own passing check, whose merge commit the forge says contains this one | | held | lifted | published | held only for its group's composed check, which has since passed or warned for the heads that merged, and its own walk still open | `delivered`, `failed`, `superseded`, `stopped`, `retired` and `covered` are final. @@ -112,11 +137,13 @@ The table of ADR 0239 decision 2 gains these rows: - **The held deliveries end without a person.** When mesh-delivery with these rows is delivered, its first ticks deliver the hq merges and the module-adding merges, and cover the controller and catalogue merges that a later walk carried. Their D14 conditions clear at the self-check's next reading of `stalled`. -- **A pull request no longer says an error for a change that runs.** The four stopped on 2026-10-09 keep their +- **A pull request no longer says an error for a change a later merge delivered.** The four stopped on 2026-10-09 keep their error: a final state is not undone. Their commit notes say why. -- **The trunk's protection is now a premise of a delivery's end.** If a trunk ever accepted a push, a later - merge might not contain an earlier one, and `covered` could be wrong. The protection is the forge's setting - on each trunk. mesh-delivery does not check it itself, and this is a known gap. +- **Containment is the forge's answer, not a premise.** The trunk's protection is no longer relied on: the + forge's compare decides. mesh-delivery gains one forge tool, which reads only. +- **The forge's tool must be delivered before `covered` can be reached.** Until `gitea_contains` answers, every + candidate is "not yet covered", and a held delivery stays held as before. Nothing ends wrongly while the forge + module is behind. - **`covered` is only as complete as the walks' records.** A module the later walk held back for a person is not sent, so it does not count. A delivery whose modules were only partly carried stays held for a person. - **A group member waits longer, rather than being held too soon.** A composed check that never answers leaves @@ -129,12 +156,20 @@ The table of ADR 0239 decision 2 gains these rows: before its check answered, on a failed check, or on a passing one. Each is delivered, and its status is a success. - `TestAMergeALaterDeliveredWalkCarriedIsCovered`: covered whether it was held first or not. It stays held - when the later walk carried only one of its modules, merged before it, went into another branch, or failed. - Its status is `success covered` and names the commit that covered it, never an error. + when the later walk carried only one of its modules, merged before it, went into another branch, or failed, + and when the forge says the later merge does not contain it. With the forge away it stays held, and is + covered once the forge answers. The forge is asked once per pair, and never for a pair merge order rules + out. Its status is `success covered: a later merge that contains it was delivered` and names the commit + that covered it, never an error. - `TestACoveredGroupMemberLetsTheNextOneGo`: the member after a covered one is let go, and the group ends delivered. - `TestHeldDeliveriesReadBackCloseByThemselvesOnce`: deliveries held before these rows existed are read back - from the store and end delivered or covered, or stay held when not covered. A second restart moves nothing. + from the store and end delivered or covered, or stay held when not covered. A merge held without a passing + check is covered by a later delivery whose own check passed, and stays held when that check did not pass. It + stays held when the later delivery's walk was of another commit or another branch, or sent its module to its + first machine only. A second restart moves nothing. +- The forge module's `test/checks.test.ts`: a compare with no commits ahead is contained, one with some is + not, an answer without a count is no answer, and only a sha reaches the compare's path. - `TestAGroupMemberWaitsForItsComposedCheckToAnswer` and `TestAMemberHeldForItsComposedCheckIsLiftedWhenItPasses`: a pass or a warning lifts the member; a failure, or a pass for other heads, keeps it held. @@ -154,4 +189,4 @@ stopped or failed. module opens no walk. - [Design 47](../03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md), *The state table* and *A delivery group*, which take these rows once this record is accepted. -- mesh-catalog #150, which builds it. +- mesh-catalog #150, which builds it, and the forge module's `gitea_contains` with it. diff --git a/02-DECISIONS/0271-a-send-held-for-the-buss-planned-step-is-told-to-the-operator-at-once-and-a-change-that-needs-it-says-so-before-it-merges.md b/02-DECISIONS/0271-a-send-held-for-the-buss-planned-step-is-told-to-the-operator-at-once-and-a-change-that-needs-it-says-so-before-it-merges.md index ad7e0ae6..389cc6fc 100644 --- a/02-DECISIONS/0271-a-send-held-for-the-buss-planned-step-is-told-to-the-operator-at-once-and-a-change-that-needs-it-says-so-before-it-merges.md +++ b/02-DECISIONS/0271-a-send-held-for-the-buss-planned-step-is-told-to-the-operator-at-once-and-a-change-that-needs-it-says-so-before-it-merges.md @@ -60,18 +60,22 @@ Three things were missing, read from the controller's code - each walk that waits, with its repository, commit and the modules of its tier; - the bus build, from and to; - since when: the first refusal this controller saw, or the save that kept the refusal after a restart; - - the verb `mesh-controller.bus upgrade --why … --reversible|--irreversible`. + - the call that ends it, as the operator makes it through the mesh MCP server: + `mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}`. Its plain words say that sends to the machine wait for a bus upgrade, what waits, for how long, and that the - operator starts the upgrade from the mesh MCP server. It clears once the bus's machine runs the build the - mesh holds, that is, once the step was taken, whatever the walks' notes still say. A walk whose note names + operator starts the upgrade from the mesh MCP server. It clears once the bus's machine has been sent the build the + mesh holds, that is, once the step was taken, whatever the walks' notes still say. From then on no send is + refused for the bus, so nothing waits for a person; whether the new bus came up healthy is the step's own + condition, `bus-maintenance` and then `bus-upgrade-failed` (probe DB), not this one. A walk whose note names an older bus build than the one held now does not count. 2. **S3 leaves out a walk held only by the bus's step**, as it leaves out a walk under a paused build seat. The wait is said once, as itself, never as lateness. 3. **The delivery plan says it before the merge.** When a change builds the module that provides the bus and that module runs somewhere, its plan has the step: "merging this needs a person's bus upgrade: nats is never - sent by an ordinary send, and until `mesh-controller.bus upgrade --why … --reversible|--irreversible` runs, - nothing else is sent to either". Its summary says "merging this needs a person's bus + sent by an ordinary send, and until the bus upgrade runs, nothing else is sent to either — + unless the new build turns out the same as the one running there (issue 280)". The plan cannot know that + before the build, so it says so rather than staying silent. Its summary says "merging this needs a person's bus upgrade". The forge module writes that summary into the `mesh/merge-gate` status and the plan into the pull request's comment. It is said, not enforced: the gate's verdict does not change. diff --git a/04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/01-diagnosis.md b/04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/01-diagnosis.md index 14276cb3..6b9fcb02 100644 --- a/04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/01-diagnosis.md +++ b/04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/01-diagnosis.md @@ -50,7 +50,7 @@ The group's order put it second. Open question 4 in the report asks whether that ## 2026-10-09 — the fix, proposed -[ADR 0270](../../02-DECISIONS/0270-a-held-delivery-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md) +[ADR 0270](../../02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md) (proposed) answers open questions 1 and 2, and mesh-catalog #150 builds it: - **Asked and not answered is a reason to wait, not to hold.** `groupTurns` leaves a member in `published` while diff --git a/04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/01-diagnosis.md b/04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/01-diagnosis.md index 16e5b25f..4e669d19 100644 --- a/04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/01-diagnosis.md +++ b/04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/01-diagnosis.md @@ -110,7 +110,7 @@ later walk. ## 2026-10-09 — the fix, proposed -[ADR 0270](../../02-DECISIONS/0270-a-held-delivery-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md) +[ADR 0270](../../02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md) (proposed) gives both ends this issue asks for, by observation, and mesh-catalog #150 builds them in mesh-delivery: @@ -118,15 +118,17 @@ mesh-delivery: the controller builds and sends nowhere opens no walk, so there is nothing to wait for. A merge whose plan moves nothing goes from `proposed`, `checked`, `rejected` or `held` to `delivered`, before the row *merged unchecked → held*. -- **`covered`, a new final state.** For every module its plan moves, a delivered delivery of the same repository - and trunk, merged after it, whose finished walk sent that module. The later merge contains this one because - the trunks take no push. The modules a walk sent are read from its record (`sent`, kept from now on) or from - the machine steps already kept, which every delivered controller delivery holds. `mesh/delivery` says - `covered: delivered with … (merged as …, walk …)` as a success. +- **`covered`, a new final state: a later merge that contains it was delivered.** For every module its plan + moves, a delivered delivery of the same repository and trunk, merged after it, delivered on its own passing + check by the walk of its own merge commit, whose machine steps say the module reached every machine running + it. The forge then says, through a new read-only tool `gitea_contains` (its compare of the two commits), + whether the later merge commit contains this one; merge order is only the first filter. A forge that cannot + answer leaves it held. `mesh/delivery` says `covered: a later merge that contains it was delivered: …` as a + success. - **Read on every tick**, so what is held now ends without an event. -This answers open question 1: mesh-delivery reads it from its own records, and asks neither the controller nor -the forge. Open question 3, why the controller's own merges opened no walk, is not answered by this fix; it no +This answers open question 1: mesh-delivery reads it from its own records and asks the forge for ancestry; the +controller is not asked. Open question 3, why the controller's own merges opened no walk, is not answered by this fix; it no longer leaves anything held. **How to verify after mesh-delivery is delivered with it.** Within a minute of its start: @@ -137,7 +139,9 @@ longer leaves anything held. 2. `mesh-delivery.show` of one of them, for example `novox/mesh-controller@b98fd0f396c8`, ends with a transition `held → covered (covered)` whose why names the later delivery and its walk. 3. `mesh-delivery.checks` for its pull request shows `mesh/delivery` as `success covered: …`. -4. At the self-check's next reading, `mesh-controller.conditions` no longer lists their +4. The forge module is delivered in the same pull request. Until its `gitea_contains` answers, nothing is + covered, and the held controller merges stay held. +5. At the self-check's next reading, `mesh-controller.conditions` no longer lists their `delivery..stalled`. -5. The two catalogue merges held for their group (issue 316) are covered only if a later delivered walk sent +6. The two catalogue merges held for their group (issue 316) are covered only if a later delivered walk sent their module. One that stays held is a real question: its change may not run anywhere. diff --git a/04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/01-diagnosis.md b/04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/01-diagnosis.md index 84873e05..2318eee2 100644 --- a/04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/01-diagnosis.md +++ b/04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/01-diagnosis.md @@ -67,8 +67,8 @@ person, and the person must be told. - **Row S17**, `bus..step-waiting`: raised on the first watchdog tick after a walk's send was refused for the bus's planned step, while the new bus build would still replace the bus on its machine. It is a warning, for the operator. It names the machines, each waiting walk and the modules of its tier, the bus build - from and to, since when, and `mesh-controller.bus upgrade --why … --reversible|--irreversible`. It clears once - the bus's machine runs the build the mesh holds. + from and to, since when, and the `mesh-controller.bus` call with `upgrade`. It clears once the bus's machine has + been sent the build the mesh holds. - **S3 leaves out a walk held only by it.** - **The delivery plan says it before the merge**: "merging this needs a person's bus upgrade: … nothing else is sent to either", and its summary, which `mesh/merge-gate` carries, says the same.