Issue 347: a pull request opened after its push is never checked, and nothing says so
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

The forge's announcer reads only repositories whose update time moved; a
pull request opened after its push does not move it. Located in the forge
module, with the fix (mesh-catalog) and how it is checked.
This commit is contained in:
jochen
2026-10-09 12:55:26 +02:00
parent d81ae38970
commit 2ba0d68306
@@ -0,0 +1,65 @@
---
status: located
opened: 2026-10-09
located-in: [mesh-catalog (modules/gitea, the announcer in index.ts and the client)]
fixed-by:
amended-design:
---
# 347. A pull request opened after its push is never checked, and nothing says so
## Symptom
On 2026-10-09 a pull request on the SDK's repository (opened at 10:11 UTC, its branch pushed at 10:03) had no
`mesh/merge-gate` and no `mesh/repo-check` status twenty minutes later, while four pull requests opened in the
same minutes on other repositories were checked within three. `mesh-delivery.checks` answered "no status" for it,
and no condition, log line or status said why. The pull request could not merge, because its base requires both
statuses, and nothing in the mesh knew it existed.
The same day a call of the forge module's `gitea_get_pull_request` with a misnamed argument asked the forge for
`pulls/NaN`.
## Why it is an issue
- **Work is silently never checked.** The forge's announcer is the only way the mesh learns of a pull request's
head (to-be 45 §9). A pull request it misses waits for ever, with no status and no condition: failure is not
loud, the worst kind.
- **The cause, measured by the review of ADR 0272.** Each pass of the announcer (`pollPulls`, modules/gitea
index.ts) reads only the repositories whose update time moved since its last pass, less a minute's margin
(`movedSince`, issue 250). A push moves a repository's update time; opening, reopening or retargeting a pull
request does not. So a pull request opened more than about a minute after its branch was pushed is in a
repository the announcer skips, on every pass, until something else is pushed there.
- **A number that is not one reaches the forge.** A tool's numeric argument read under the wrong name is `NaN`,
and the client asked the forge about it rather than refusing.
## Acceptance test (the operator's view)
A pull request I open on a protected branch, whenever I open it after pushing its branch, has a merge check within a
minute. If one ever has none for ten minutes, the mesh tells me which pull request, as a condition. A tool asked
with a pull request number that is not one refuses and says why.
## The fix
In the forge module (mesh-catalog, `modules/gitea`):
1. Each pass also looks at every repository holding a pull request updated since the floor, found by the forge's
own search of open pull requests by their own update time; and every ten minutes, or when the search fails, it
looks at every repository (a sweep).
2. Every five minutes the forge itself is asked for every open pull request. One whose base requires
`mesh/merge-gate` and whose head has had no such status for ten minutes is said as the forge failing *the
merge check of that pull request*, a provider standing ([ADR 0224](../../02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md))
the controller keeps as a condition and says to the operator. It is said again every pass while it lasts and
recovered when a check arrives or the pull request closes. The watcher asks the forge, not the announcer's
record, so a fault in the announcer cannot hide itself.
3. The client refuses a pull request or issue number that is not a whole number above zero before it asks.
## How it is checked
- `test/announcing.test.ts` (modules/gitea): a pull request opened two minutes after its push, with the last pass
thirty seconds after that, is in a repository the pass looks at; without its own update time nothing is looked at
(the fault, held as a case); a sweep and the first pass look at every repository. An open pull request on a
protected base with no merge check for ten minutes is said; one with a check, on a base that requires none, or
five minutes old is not. `pulls/NaN`, `0` and `1.5` are refused and the forge is never asked. All three fail on
the commit before the fix.
- Live, after delivery: the next pull request opened later than a minute after its push has its statuses within a
minute; `mesh-controller.conditions` shows none for it.