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-whose-change-already-runs-or-has-nothing-to-run-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md new file mode 100644 index 00000000..fcb8657a --- /dev/null +++ b/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 @@ -0,0 +1,157 @@ +--- +topic: the mesh +status: proposed +date: 2026-10-09 +deciders: jochen +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 + +## Context + +[ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md) +decision 2 gives a delivery one table of states. A delivery is `held` when only a person can decide whether +it goes on. A person then has four ways out: `release` walks the delivery's own commit, `stop` ends it as +stopped, `retire-history` ends history +([ADR 0250](0250-a-merge-heard-only-from-the-buss-history-is-retired-and-owed-work-the-forge-cannot-do-ends.md)), +and the walk can start on another word (`go`). + +On 2026-10-09, fifteen deliveries were held, most for more than a day, each raising probe D14's +`delivery..stalled` (read with `mesh-delivery.deliveries` and `show`). None of them asked a question a +person could answer correctly: + +- **Four merges of hq build nothing.** Their plan says "builds nothing: the change touches no module of the + mesh's graph". They were merged before their check answered, and the row *merged unchecked → held* is tried + before anything reads the plan. +- **Four catalogue merges only add a module.** The controller builds and registers a new module and sends it + nowhere, so it opens no walk ([issue 300](../04-ISSUES/300-a-new-module-is-not-built-at-its-merge/00-report.md)). + mesh-delivery counted the new module as something a walk moves, so the no-walk rule held each one ten + minutes after its merge. +- **Five controller merges had no walk of their own.** A later merge into the same trunk was delivered, and + its walk sent every module these move. The diagnosis of [issue 319](../04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/01-diagnosis.md) + checked each merge commit against the later delivered commit: each was contained in it, and the + controller's `status` listed none of their modules as behind. +- **Two catalogue merges were held for their group's composed check** + ([issue 316](../04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/00-report.md)). + One was published while the check was asked and not yet answered. The code read "not answered" as "did + not pass" and held it. Two minutes later the check passed for exactly the heads that merged, and nothing + read that answer for the held member. The own walk of each of the two was since taken over by a later walk + of the same trunk that sent its module again. + +`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 +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. + +## Considered Options + +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. + +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 +a check that answers after the merge. + +## Decision + +1. **A merge that moves nothing is delivered at once.** A delivery whose plan moves no module the mesh holds + is delivered as soon as it is merged and has no walk. That covers a plan that builds nothing and a plan + 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`. + + 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 + 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 + 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 + 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. + +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 | +| 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. + +## Consequences + +- **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 + 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. +- **`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 + the member in `published`. Its bound of 30 minutes lists it as stalled. That is the right owner for a check + that does not answer. + +**How it is checked.** mesh-delivery's tests, in `covered_test.go`: + +- `TestAMergeThatMovesNothingIsDeliveredAtOnce`: builds nothing, or only a new module sent nowhere; merged + 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. +- `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. +- `TestAGroupMemberWaitsForItsComposedCheckToAnswer` and `TestAMemberHeldForItsComposedCheckIsLiftedWhenItPasses`: + a pass or a warning lifts the member; a failure, or a pass for other heads, keeps it held. + +The table's own tests take and refuse every new row. They also check that the only way to `published` other +than a ready delivery's merge is *lifted* from `held`, and that nothing observed ends a held delivery as +stopped or failed. + +## References + +- [Issue 319](../04-ISSUES/319-a-held-delivery-a-later-commit-superseded-has-no-end-but-release/00-report.md) and + [issue 316](../04-ISSUES/316-a-group-member-held-before-its-composed-check-answered-stayed-held-after-it-passed/00-report.md), + their reports and diagnoses. +- [ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md), + decisions 2, 4 and 9; [ADR 0250](0250-a-merge-heard-only-from-the-buss-history-is-retired-and-owed-work-the-forge-cannot-do-ends.md), + the last state added to the table. +- [Issue 300](../04-ISSUES/300-a-new-module-is-not-built-at-its-merge/00-report.md), why a new + 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, the branch `fix/319-316-covered-and-lifted`. 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 new file mode 100644 index 00000000..0450472d --- /dev/null +++ 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 @@ -0,0 +1,117 @@ +--- +topic: the mesh +status: proposed +date: 2026-10-09 +deciders: jochen +reconstructed: false +extends: 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 +--- + +# 271. A send held for the bus's planned step is told to the operator at once, and a change that needs it says so before it merges + +## Context + +A new build of the bus is a **planned step** +([ADR 0236](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md), +[to-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §8). Only a person's +`mesh-controller.bus upgrade` moves it. Until then, every send to the bus's machine is refused, because such a +send would carry the new bus with it. The controller's `sendToEach` refuses the send whole, and a walk keeps +the refusal as its note and tries again on the next tick. + +On 2026-10-08 a catalogue merge built a new bus for the control-node +([issue 336](../04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/00-report.md)). For 28 minutes every +send to that machine was held: the controller's own walk, the node-engine's, and builds kept back for a gate. +`mesh-controller.conditions` listed nothing about it. The operator found the hold only because a walk looked +stuck, and asked where the approve button was. + +Three things were missing, read from the controller's code +([diagnosis](../04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/01-diagnosis.md)): + +- the refusal reaches only the walk's note, which nobody reads unless they already ask `plans`; +- the only watchdog that could fire, S3, fires no sooner than 30 minutes in. Its words say the delivery is late + and that nothing is lost. They name neither the bus nor the verb, and its resolver is not the operator; +- the merge gate and the delivery plan were green. The plan said "a bus step", but not that merging would + stop every later send to the bus's machine until a person acts. + +## Considered Options + +1. **Lower S3's bound for a walk refused for the bus.** Rejected: it would still say lateness, about one walk, + in words that name neither the cause nor the verb. A wait for a person is not a walk running late. +2. **Raise it from the self-check's probe DB, which already says a running bus step.** Rejected: the probe reads + only a step that has started, and it runs on the self-check's interval. The refusal happens in the walk, + which the watchdogs read every half minute. +3. **Let the walk send everything but the bus, and keep the bus at the build its machine runs.** Not decided + here. The diagnosis thinks it may now be possible, because every send keeps a recorded module at the build + its machine was last sent. That has to be checked against the bus's user list, which issue 249 sends first in + the same declaration. It would not remove the need to tell the operator: the step still waits for a person. +4. **Hold the merge for the operator's word when it builds a new bus.** Rejected for now. It would block a + change on a decision the operator can take after the merge as well. Asking the operator from the merge gate + belongs with the asks of ADR 0259 (an ask is a message on the bus that any channel answers), which is still + a pull request and not built. +5. **A condition of its own, raised at the first refusal, and a line in the delivery plan** — chosen. + +## Decision + +1. **Row S17 of the signals table: a send held for the bus's planned step is told to a person.** On the first + watchdog tick after a walk's send was refused for the bus, the controller raises + `bus..step-waiting`, kind `bus-step-waiting`. It is a warning, and its resolver is the operator. + Its summary names: + - the machines whose bus the new build would replace; + - 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`. + + 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 + 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 + 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. + +## Consequences + +- **The operator is told within half a minute of the first refusal**, not when a walk looks stuck. The + condition says what to run. +- **Merging a change that builds the bus is a known promise.** Its pull request says, before the merge, that a + person's step follows and what waits until then. +- **One more row in the signals table.** The generated test walks it like every other row. +- **The condition is about the bus, not about one walk.** Several walks waiting for the same step are one + condition, and the last one to be refused does not reopen it. +- Turning the condition into an ask, whose yes starts the step with the operator's word recorded, waits for + ADR 0259's asks to be built. + +**How it is checked.** In the controller: + +- the signals table's generated test for S17: with a new bus build waiting and no send refused, nothing is + raised; with a refused walk, the condition is raised; when the signal returns, it clears; +- `TestASendHeldForTheBusStepIsFoundFromTheWalksItHolds`: a walk refused for the bus is found, and since when. + A refusal for another reason, a refusal for an older bus build, a bus that already runs the build held, and a + walk waiting for its delivery's word are not; +- `TestASendHeldForTheBusStepIsSaidAtOnceAndNotAsLateness`: the condition's key, kind and operator resolver; + its summary names the machine, the module, the builds from and to, the verb and since when; its words are + plain. S3 says nothing of the walk past its bound, and still raises for a walk held for something else. Through + the keeper, the condition opens at the first tick and clears when the step was taken; +- the replay `TestReplay336`: a walk of the node-engine refused for a new bus build on the control-node raises + the condition on the next watchdog tick, through the store, the plans and the watchdogs' own gathering. It + clears once the machine is sent the new bus. It fails on the controller before the fix and passes on it; +- `TestAChangePlanSaysWhatEachMachineReceives`: a change that builds the bus says "merging this needs a + person's bus upgrade", the machine, and the verb. + +## References + +- [Issue 336](../04-ISSUES/336-a-send-held-for-the-bus-step-raises-nothing/00-report.md), its report and + diagnosis. +- [ADR 0236](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md), + the bus as a planned step; [ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md) + rule 4, no failure is silent; + [ADR 0254](0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md), + the precedent of a wait for a person said as its own condition. +- [To-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §3, the signals table, and §8, the + bus as a planned step, which take this row once this record is accepted. 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 b0540d6d..3eec6ec4 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 @@ -47,3 +47,19 @@ The group's order put it second. Open question 4 in the report asks whether that **Located** in mesh-delivery: `groupTurns` holds on a composed check that has not answered, and neither `Checked` nor the table brings a held member back when the check later passes. + +## 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) +(proposed) answers open questions 1 and 2, and the catalogue's branch `fix/319-316-covered-and-lifted` builds it: + +- **Asked and not answered is a reason to wait, not to hold.** `groupTurns` leaves a member in `published` while + its group's composed check is asked for the current heads and has no verdict. +- **A hold whose reason is gone lifts, by observation.** A member held because its group's composed check had not + passed goes back to `published` (the event *lifted*) once the check passes or warns for the heads that merged, + while its own walk is still open. It then takes its turn in order. +- A held member whose own walk was since taken over by a later walk is not lifted, since its walk can no longer + deliver it. It is `covered` when that later walk was delivered and sent its module (issue 319). + +Open question 3, whether `mesh/delivery-group` is required on the trunks, stays the operator's setting. Open +question 4 is not touched. 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 89a11b1e..5ca5696e 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 @@ -107,3 +107,37 @@ remaining seven were left `held` for this issue's fix. The seven show the two ends this issue asks for: four hold nothing to deliver, and three are covered by a 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) +(proposed) gives both ends this issue asks for, by observation, and the catalogue's branch +`fix/319-316-covered-and-lifted` builds them in mesh-delivery: + +- **A merge that moves nothing is delivered at once.** `MovesNothing()` no longer counts `Plan.New`: a module + 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. +- **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 +longer leaves anything held. + +**How to verify after mesh-delivery is delivered with it.** Within a minute of its start: + +1. `mesh-delivery.deliveries` with `state: held` lists none of the four hq merges, none of the module-adding + merges (`builds new: …, sent nowhere`), and none of the five controller merges held for "no walk opened". + The hq and module-adding ones are `delivered`; the controller ones are `covered`. +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 + `delivery..stalled`. +5. 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 70d78006..9bc38487 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 @@ -58,3 +58,24 @@ running build and carry the rest, so a pending bus step would stop holding every control-node. That needs checking against the bus's user list, which [issue 249](../249-a-modules-new-state-is-refused-until-a-push-the-merge-did-not-make/00-report.md) sends first in the same declaration, before it is proposed as a fix. It does not replace the condition: the step still waits for a person, and the person must be told. + +## 2026-10-09 — the fix, proposed + +[ADR 0271](../../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) +(proposed) takes suggested fixes 1 and 3. The controller's branch `fix/336-bus-step-waiting` builds them: + +- **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. +- **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. + +The replay `TestReplay336` in the controller fails on the commit before the fix ("the first tick after the refusal +told the operator nothing about the bus's step: 0 condition(s)") and passes on it. It still has to be registered +in mesh-lab's replays register as R336 before this issue resolves with `replay: R336`. + +Suggested fix 2 (an ask) waits for ADR 0259's asks. The guess about letting a send keep the bus at its running +build and carry the rest is not checked here.