Merge pull request 'Issue 347: a pull request opened after its push is never checked, and nothing says so' (#247) from issues/347-a-pull-request-opened-after-its-push-is-never-checked into main

This commit was merged in pull request #247.
This commit is contained in:
2026-10-09 11:38:40 +00:00
@@ -0,0 +1,73 @@
---
status: located
opened: 2026-10-09
located-in: [mesh-catalog (modules/gitea, the announcer in index.ts and the client; modules/mesh-delivery, saying it), mesh-controller (its words)]
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 — on a branch that requires `mesh/merge-gate` or only `mesh/repo-check` —
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 delivery's owner (`mesh-delivery`) asks the forge itself for every open pull request
(`gitea_open_pulls`), never what the announcer remembers. A head on a base that requires the merge check —
`mesh/merge-gate` **or `mesh/repo-check`**, since this repository requires only the second — with neither on it
for ten minutes since the owner first saw it so (its own time: the forge's update time moves on every comment)
is a delivery held past its bound: a `stalled` line in state `unannounced`, which the controller raises as
`delivery.<id>.stalled` like any other, with words that say to push a new commit. The delivery domain's own
word, chosen over a provider standing and over a health check of the forge: that check would need a `tools`
list (turning the forge's tools into seat verbs) and a check of another kind beside it, and would hold the
forge's own deliveries at the first-node gate.
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. Every open pull request is read
with its base's required statuses and its head's. `pulls/NaN`, `0` and `1.5` are refused and the forge is never
asked. Each fails on the commit before the fix.
- `unchecked_test.go` (modules/mesh-delivery): a head on a base requiring only `mesh/repo-check`, with no check,
is a stalled `unannounced` line after ten minutes, counted from when the owner first saw it; one with a check,
on a base requiring none, announced, closed, or behind a forge that cannot be asked is not said or not changed.
- `TestAnUnannouncedPullRequestSaysToPushANewCommit` (mesh-controller): the condition's words say to push a new
commit, never to stop or release a delivery that does not exist.
- 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.