Merge pull request 'Issue 362: two merges seconds apart left their delivery waiting for a person' (#263) from issues/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person into main

This commit was merged in pull request #263.
This commit is contained in:
2026-10-09 23:32:52 +00:00
3 changed files with 124 additions and 0 deletions
@@ -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).
@@ -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).
@@ -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.