From 496452fb54172ebedf3881f85c6209b96ad4b1c7 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 01:28:33 +0200 Subject: [PATCH] Issue 362: two merges seconds apart left their delivery waiting for a person, because a walk at a later commit matched no live delivery; issue 349 points at it --- .../00-report.md | 8 +++ .../00-report.md | 57 ++++++++++++++++++ .../01-diagnosis.md | 59 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/00-report.md create mode 100644 04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md diff --git a/04-ISSUES/349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md b/04-ISSUES/349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md index 3f2ff2ff..914f0da7 100644 --- a/04-ISSUES/349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md +++ b/04-ISSUES/349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md @@ -62,3 +62,11 @@ first, the earlier one second; one plan is left open, at the later commit, build done. `TestTheBranchOrderIsTheMerges` holds the order and the lookup, to the nanosecond. The first and the last fail on the commit before the fix, and every condition of the change was mutated and caught. A replay in mesh-lab's register is still owed before this core issue resolves (ADR 0237). + +## A sequel (2026-10-10) + +Planning an earlier merge at the later merge's commit left that earlier merge's delivery without a walk: +mesh-delivery matches a walk to the delivery merged as its commit, so two catalogue merges 18 seconds apart +left the earlier one held for a person and the walk carrying it waiting for a word nothing gave, for 58 +minutes, with no condition raised. See +[issue 362](../362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/00-report.md). diff --git a/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/00-report.md b/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/00-report.md new file mode 100644 index 00000000..584b0a73 --- /dev/null +++ b/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/00-report.md @@ -0,0 +1,57 @@ +--- +status: located +opened: 2026-10-10 +located-in: [mesh-catalog modules/mesh-delivery (holder.go deliveryWalking), mesh-controller cmd/mesh-controller (upgrades.go SourceMoved, release_plan.go laterOnTheBranch)] +fixed-by: +amended-design: +--- + +# 362. Two catalogue merges seconds apart left their delivery waiting for a person + +## Symptom + +Shortly after midnight on 2026-10-10 (local time) two pull requests of the catalogue were merged +18 seconds apart: the claude-code change at 00:20:52, then the dunst change at 00:21:09. Both had passed +their checks. + +`mesh-delivery.deliveries` then showed every delivery of the dunst pull request `superseded`, and the +claude-code delivery `held`, waiting for "a person: merged, and the controller opened no walk for it within +10m0s — nothing it holds follows that branch, or the merge was not heard: a person decides". The +controller's `plans` said of the walk for the catalogue's head, which carried both merges: "published; its +walk waits for mesh-delivery's word — `plans go … --why …` starts it by hand". + +Both reasons were wrong. A walk had been opened, and it contained the held merge. Nothing gave it the word. +The operator's change sat undelivered for 58 minutes, until the operator's hand-act `mesh-controller.plans +go` started the walk. **No condition was raised to anyone in that time**: the hold is bounded at 24 hours +before `mesh-delivery.stalled` lists it, and the walk's own wait raised nothing either. + +## Why it is an issue + +A merge that passed its checks must reach its nodes without a person, and a delivery that cannot must say +so loudly and truly. Here both failed: two ordinary merges close together, which happens whenever the +operator merges a batch, stopped the mesh's delivery silently, and the reason it gave pointed away from the +cause. + +## Where it lives + +The diagnosis ([01-diagnosis.md](01-diagnosis.md)) traces it to two parts that each behave as written and +disagree on what a walk's commit means: + +- The controller plans a merge it hears after a later merge of the same branch at the later merge's commit, + and supersedes the earlier walk (the fix for + [issue 349](../349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md)). + So the claude-code merge got a walk at the dunst merge's commit. +- mesh-delivery finds the delivery a walk walks by its walk id or by the commit it was merged as. The new + walk matched the dunst delivery, which its first walk's supersession had already ended, so no live + delivery held the walk and nothing gave it its turn. The claude-code delivery, merged as a different + commit, never got a walk and was held after ten minutes. + +## Open + +- How mesh-delivery learns which merges a walk carries: the controller's walk record naming every merge it + answers (its commit and the commits planned into it), or mesh-delivery reading containment itself, as it + already does to decide *covered*. +- A superseded walk whose successor matches only final deliveries: whether the successor should be adopted + as a delivery of its own, so that something always holds a waiting walk. +- A walk waiting for mesh-delivery's word past a bound, and a delivery held because no walk was opened, + raise a condition to the operator instead of waiting silently (failure must be loud). diff --git a/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md b/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md new file mode 100644 index 00000000..52ace325 --- /dev/null +++ b/04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md @@ -0,0 +1,59 @@ +# 362. Diagnosis + +## 2026-10-10 — the trail + +Times are local (UTC+2). Commits are the catalogue's. + +**What the forge did.** The claude-code pull request was merged as 553b7191 at 00:20:52, and the dunst pull +request as 48bda475 at 00:21:09. 48bda475 is the branch's head and contains 553b7191. + +**What the controller did** (its journal, 00:21:14 to 00:21:15): + +1. It heard the dunst merge first and opened a walk at 48bda475 for dunst, waiting for mesh-delivery's word. +2. It then heard the claude-code merge, and printed "553b7191 was merged before 48bda475 (…, building): what + it moved is built from 48bda475, which contains it". This is the rule that answered + [issue 349](../349-a-merge-acted-on-late-superseded-the-merge-after-it-and-built-the-older-commit/00-report.md): + `SourceMoved` replaces the merge's commit and merge time with the newest planned merge's, when that merge + is later on the branch (`laterOnTheBranch`). +3. The second walk, also at 48bda475 and with the same merge time, superseded the first by when each was made + (`supersededBy`, `earlierOnTheBranch`), and carried claude-code and dunst. It waited for mesh-delivery's + word. + +So every walk the controller opened was at 48bda475. None was at 553b7191. + +**What mesh-delivery did** (`mesh-delivery.show` for both deliveries): + +1. The dunst delivery (merged as 48bda475) took the first walk, matched by its commit, and became + `published` at 00:21:14. +2. The first walk's record came back superseded, and the table's `superseded` transition ended the dunst + delivery at 00:21:15 ("a newer delivery to the same trunk took over its walk"). +3. The second walk was matched to a delivery by `deliveryWalking`, which looks first for a delivery holding + that walk id and then for any delivery whose `merged_as` is the walk's commit, final or not. That is the + dunst delivery again, already final. The walk was linked there, so it was never kept as unmatched and + never adopted as a delivery of its own. A turn (`turn`) is given only to a `published` or `delivering` + delivery, so nothing ever gave the second walk its word. +4. The claude-code delivery, merged as 553b7191, matched no walk. Ten minutes after its merge + (`unwalkedPast`) it was held for a person at 00:30:58, with a reason that names two causes, neither of + which happened. + +After the operator's `plans go`, the second walk's machine steps (claude-code and dunst, each on a node) were recorded on the superseded dunst delivery, which confirms that mesh-delivery reads that walk as +the dunst delivery's. + +**Why nothing was raised.** A delivery held for a person is bounded at 24 hours before +`mesh-delivery.stalled` lists it (`Bounds` in its table). The walk waiting for mesh-delivery's word is +counted against the controller's plan wait bound of 30 minutes for `status`, but no condition reached the +operator in the 58 minutes it waited. I did not trace why the controller's side stayed quiet; that belongs +to the fix. + +## Ruled out + +- **The merge was not heard.** The controller's journal shows it was heard and planned within 23 seconds. +- **Nothing follows that branch.** claude-code follows the catalogue's trunk; the second walk built it. +- **A failed check.** Both heads passed the merge gate and the repository's check. + +## Located + +Two parts, each correct by its own rule, disagree on what a walk's commit means. The controller (since issue +349) plans an earlier merge at a later commit that contains it; mesh-delivery assumes a walk's commit is the +merge commit of exactly one delivery. `located-in` names both. The fix is to make the walk say which merges +it answers, or to make mesh-delivery match by containment, and to let no waiting walk be held by nothing.