Say a pull request nobody checks as a delivery held, owned by mesh-delivery (hq issue 347)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer delivery to the same trunk took over its walk

The confirmation review asked for the delivery domain's own vocabulary
instead of a provider standing. A gitea health check would need a `tools`
list (turning gitea's tools into seat verbs), a check of another kind
beside it, and would hold the forge's own deliveries at the first-node
gate; so the delivery's owner says it, with the word it already has.

- gitea_open_pulls (gitea): every open pull request from the forge itself,
  with what its base requires and the statuses its head has.
- mesh-delivery asks it every five minutes and keeps its own first-seen
  time per head (the forge's update time moves on every comment). A head
  on a base requiring mesh/merge-gate OR mesh/repo-check (hq requires only
  the second) with neither for ten minutes is a `stalled` line in state
  `unannounced`, which the controller raises as delivery.<id>.stalled (D14);
  H2 has nothing to close. Once announced it is a delivery of its own.
- The gitea-side watcher and its provider standing are gone.
This commit is contained in:
jochen
2026-10-09 13:14:36 +02:00
parent bcc6b3f52a
commit 32d55beb47
16 changed files with 323 additions and 110 deletions
@@ -239,6 +239,18 @@ type fakeForge struct {
contains map[string]bool
containsDown bool
containsAsks []string
// open is what OpenPulls answers; openDown a forge that cannot say.
open []OpenPull
openDown bool
}
func (f *fakeForge) OpenPulls() ([]OpenPull, error) {
f.mu.Lock()
defer f.mu.Unlock()
if f.openDown {
return nil, errors.New("the forge is away")
}
return append([]OpenPull(nil), f.open...), nil
}
func newFakeForge() *fakeForge {
@@ -41,6 +41,8 @@ type Holder struct {
// askTried is when a check this owner asks was last tried and could not be asked: tried again a minute on.
askTried map[string]time.Time
refusals []string
// unchecked are the open pull requests' heads seen without a merge check (unchecked.go).
unchecked map[string]unchecked
}
// unmatched is a walk no delivery is known for yet: its merge not heard, or merged before this owner held.
@@ -80,6 +82,7 @@ func (h *Holder) init() {
h.dirtyGroup = map[string]bool{}
h.unmatched = map[string]unmatched{}
h.askTried = map[string]time.Time{}
h.unchecked = map[string]unchecked{}
h.started = h.Now()
}
@@ -68,7 +68,7 @@ func run(h *Holder, l *listening) {
go func() {
tick := time.NewTicker(10 * time.Second)
defer tick.Stop()
last := time.Now()
last, looked := time.Now(), time.Time{}
for now := range tick.C {
if now.Sub(last) >= 30*time.Second {
last = now
@@ -76,6 +76,11 @@ func run(h *Holder, l *listening) {
logf("[mesh-delivery] the controller's walks cannot be read: %v", err)
}
}
// Every open pull request, from the forge itself, every five minutes (novox/hq issue 347).
if now.Sub(looked) >= 5*time.Minute {
looked = now
h.SeeOpenPulls()
}
h.Tick()
h.Flush()
}
@@ -65,7 +65,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
want = append(want, "seat:"+ControllerSeat+"."+v)
}
want = append(want, "gitea.gitea_note_append", "gitea.gitea_delivery_view", "gitea.gitea_commit_status",
"gitea.gitea_commit_statuses", "gitea.gitea_contains")
"gitea.gitea_commit_statuses", "gitea.gitea_contains", "gitea.gitea_open_pulls")
if !reflect.DeepEqual(m.Invokes, want) {
t.Fatalf("invokes %v, wanted %v", m.Invokes, want)
}
@@ -78,6 +78,9 @@ type Forge interface {
Statuses(owner, repo string, number int, commit, base string) (*ForgeChecks, error)
// Contains is whether a commit is contained in another — an ancestor of it, or it. Reads only.
Contains(owner, repo, commit, in string) (bool, error)
// OpenPulls is every open pull request, with what its base requires and what its head has (novox/hq issue
// 347). Reads only.
OpenPulls() ([]OpenPull, error)
}
// ForgeChecks is what the forge says of a commit's checks (the gitea module's gitea_commit_statuses).
@@ -339,6 +342,24 @@ func (f toolForge) Statuses(owner, repo string, number int, commit, base string)
return &c, nil
}
func (f toolForge) OpenPulls() ([]OpenPull, error) {
raw, err := f.ask("gitea.gitea_open_pulls", map[string]any{})
if err != nil {
return nil, err
}
answer, output, ok := answerOf(raw)
if !ok {
return nil, fmt.Errorf("the forge refused gitea_open_pulls: %s", lastLine(output))
}
var open struct {
Pulls []OpenPull `json:"pulls"`
}
if err := json.Unmarshal(answer, &open); err != nil || open.Pulls == nil {
return nil, fmt.Errorf("the forge's gitea_open_pulls is not readable: %s", clip(string(answer), 200))
}
return open.Pulls, nil
}
func (f toolForge) Contains(owner, repo, commit, in string) (bool, error) {
raw, err := f.ask("gitea.gitea_contains", map[string]any{"owner": owner, "repo": repo, "commit": commit, "in": in})
if err != nil {
@@ -0,0 +1,112 @@
package main
import (
"fmt"
"slices"
"time"
)
// A pull request the mesh was never asked to check (novox/hq issue 347).
//
// The forge's announcer is the only way the mesh hears of a pull request's head, and on 2026-10-09 it missed one: a
// pull request opened after its push moved nothing the announcer read, and waited unchecked, unable to merge, with
// nothing saying so. This owner asks the forge itself for every open pull request every few minutes, and one whose
// base requires the merge check — mesh/merge-gate or mesh/repo-check — with neither on its head for UncheckedAfter
// since this owner first saw it so is said as a delivery held: a `stalled` line in the state Unannounced, which the
// controller raises as `delivery.<id>.stalled` (D14) like any other delivery past its bound. The delivery domain's own
// word, so no second condition and no second judge: the id is the one its delivery will have once announced.
// Unannounced is a pull request's head the forge never announced: not a state of the table — no delivery exists —
// only what a stalled line says.
const Unannounced State = "unannounced"
// UncheckedAfter is how long a head may go without a merge check before it is said: the announcer passes every half
// minute and the check is asked at once, so ten minutes is a fault, never a queue.
const UncheckedAfter = 10 * time.Minute
// mergeChecks are the statuses that are the mesh's merge check (novox/hq ADR 0237): a base requiring either is
// one the mesh is meant to check.
var mergeChecks = []string{"mesh/merge-gate", "mesh/repo-check"}
// OpenPull is an open pull request as the forge says it (the gitea module's gitea_open_pulls): its head, its base,
// the contexts the base's protection requires, and the contexts its head has a status of, in any state.
type OpenPull struct {
Owner string `json:"owner"`
Repo string `json:"repo"`
Number int `json:"number"`
Base string `json:"base"`
HeadSHA string `json:"head_sha"`
HTMLURL string `json:"html_url,omitempty"`
Required []string `json:"required"`
Statuses []string `json:"statuses"`
}
// unchecked is a head seen without a merge check, and when this owner first saw it so — its own time, since the
// forge's update time moves on every comment.
type unchecked struct {
pull OpenPull
first time.Time
}
// wantsACheck is whether the base requires the mesh's merge check and the head has none of it.
func (p OpenPull) wantsACheck() bool {
required := slices.ContainsFunc(mergeChecks, func(c string) bool { return slices.Contains(p.Required, c) })
checked := slices.ContainsFunc(mergeChecks, func(c string) bool { return slices.Contains(p.Statuses, c) })
return required && !checked && p.HeadSHA != ""
}
// SeeOpenPulls asks the forge for every open pull request and keeps the heads that want a check and have none.
// A forge that cannot be asked changes nothing: what was seen stands, and nothing is guessed.
func (h *Holder) SeeOpenPulls() {
open, err := h.Forge.OpenPulls()
if err != nil {
h.Logf("[mesh-delivery] the forge's open pull requests cannot be read: %v", err)
return
}
h.mu.Lock()
defer h.mu.Unlock()
now := h.Now()
seen := map[string]unchecked{}
for _, p := range open {
if !p.wantsACheck() {
continue
}
id := IDOf(p.Owner+"/"+p.Repo, p.HeadSHA)
if h.deliveries[id] != nil {
continue // announced: a delivery with its own bound
}
first := now
if was, ok := h.unchecked[id]; ok {
first = was.first
} else {
h.Logf("[mesh-delivery] %s/%s#%d's head %s has no merge check; said if it has none in %s", p.Owner, p.Repo,
p.Number, short(p.HeadSHA), UncheckedAfter)
}
seen[id] = unchecked{pull: p, first: first}
}
h.unchecked = seen
}
// uncheckedLines are the heads unchecked past UncheckedAfter, as stalled lines; under the lock.
func (h *Holder) uncheckedLines(now time.Time) []StalledLine {
var out []StalledLine
for id, u := range h.unchecked {
if h.deliveries[id] != nil || now.Sub(u.first) < UncheckedAfter {
continue
}
out = append(out, StalledLine{ID: id, State: Unannounced, For: now.Sub(u.first).Round(time.Second).String(),
Bound: UncheckedAfter.String(),
H2: "none: the forge never announced it, so there is no delivery to close — the operator's",
Says: fmt.Sprintf("%s/%s#%d is open on %s, which requires the merge check, and its head has had no merge "+
"check: the mesh was never asked to check it. A new commit on its branch announces it",
u.pull.Owner, u.pull.Repo, u.pull.Number, u.pull.Base)})
}
return out
}
func short(sha string) string {
if len(sha) > 12 {
return sha[:12]
}
return sha
}
@@ -0,0 +1,77 @@
package main
import (
"strings"
"testing"
"time"
)
// **A pull request the mesh was never asked to check is a delivery held, and said** (novox/hq issue 347): one open
// on a base that requires the merge check — mesh/merge-gate or mesh/repo-check, as hq requires only the second —
// whose head has had neither for ten minutes since this owner first saw it so, is a stalled line: the controller
// raises it as `delivery.<id>.stalled` (D14), the delivery domain's own word for a delivery held past its bound.
func TestAPullRequestNobodyCheckedIsAStalledDelivery(t *testing.T) {
w := newWorld(t)
head := "bfe82315f42c8f4509d0598c32569a202d5eada6"
w.forge.open = []OpenPull{
{Owner: "novox", Repo: "hq", Number: 243, Base: "main", HeadSHA: head, Required: []string{"mesh/repo-check"}},
{Owner: "novox", Repo: "sdk", Number: 14, Base: "main", HeadSHA: "aaaa", Required: []string{"mesh/merge-gate", "mesh/repo-check"},
Statuses: []string{"mesh/merge-gate"}},
{Owner: "novox", Repo: "free", Number: 1, Base: "main", HeadSHA: "bbbb", Required: nil},
}
w.h.SeeOpenPulls()
if lines := w.h.Stalled(); len(lines) != 0 {
t.Fatalf("a head just seen is stalled already: %+v", lines)
}
w.later(6 * time.Minute)
w.h.SeeOpenPulls() // seen again: its own first-seen time stands, whatever the forge's update time says
w.later(5 * time.Minute)
lines := w.h.Stalled()
if len(lines) != 1 || lines[0].ID != IDOf("novox/hq", head) || lines[0].State != Unannounced ||
!strings.Contains(lines[0].Says, "no merge check") || !strings.HasPrefix(lines[0].H2, "none") {
t.Fatalf("an unchecked pull request on a base requiring only mesh/repo-check reads %+v", lines)
}
if lines[0].For != "11m0s" {
t.Fatalf("its time is not counted from when this owner first saw it: %s", lines[0].For)
}
// Announced at last: a delivery of its own, with its own bound — no longer this line.
w.h.PullUpdated(pr("novox/hq", 243, head, "decision/x"))
w.h.SeeOpenPulls()
for _, l := range w.h.Stalled() {
if l.State == Unannounced {
t.Fatalf("an announced pull request is still said unannounced: %+v", l)
}
}
}
// One that gets a check, or closes, is dropped; one the forge cannot be asked about is not guessed.
func TestAnUncheckedPullRequestThatGetsACheckOrClosesIsDropped(t *testing.T) {
w := newWorld(t)
w.forge.open = []OpenPull{{Owner: "novox", Repo: "hq", Number: 1, Base: "main", HeadSHA: "cccc",
Required: []string{"mesh/merge-gate"}}}
w.h.SeeOpenPulls()
w.later(11 * time.Minute)
if len(w.h.Stalled()) != 1 {
t.Fatal("not said")
}
w.forge.open[0].Statuses = []string{"mesh/merge-gate"}
w.h.SeeOpenPulls()
if len(w.h.Stalled()) != 0 {
t.Fatal("a pull request with a check is still said")
}
w.forge.open[0].Statuses = nil
w.h.SeeOpenPulls()
w.later(11 * time.Minute)
w.forge.openDown = true
w.h.SeeOpenPulls() // the forge away: what was seen stands, nothing new is guessed
if len(w.h.Stalled()) != 1 {
t.Fatal("a forge that could not be asked dropped or invented a line")
}
w.forge.openDown = false
w.forge.open = nil
w.h.SeeOpenPulls()
if len(w.h.Stalled()) != 0 {
t.Fatal("a closed pull request is still said")
}
}
@@ -189,6 +189,7 @@ func (h *Holder) Stalled() []StalledLine {
out = append(out, StalledLine{ID: d.ID, State: d.State, For: now.Sub(boundSince(d)).Round(time.Second).String(),
Bound: b.For.String(), H2: h2, Says: b.Says})
}
out = append(out, h.uncheckedLines(now)...)
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
return out
}