From f25481342f22769f412b6744994b6d08f06910a0 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 13:12:58 +0000 Subject: [PATCH] Find the teller's warrant by the heads its Approve is bound to, and judge a gap as hearMerge does (ADR 0457, issue 1336, review round 1) --- cmd/mesh-controller/catch_up.go | 144 ++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 42 deletions(-) diff --git a/cmd/mesh-controller/catch_up.go b/cmd/mesh-controller/catch_up.go index 01e827dc..e5e17b0c 100644 --- a/cmd/mesh-controller/catch_up.go +++ b/cmd/mesh-controller/catch_up.go @@ -205,8 +205,8 @@ var readMergeEvent = func(ctx context.Context, owner, repo, branch string, tm tr return m, nil } -// mergeAskPrefix is what the id of a merge ask of the delivery begins with when it names a pull request and its head: the -// number and the head's first twelve digits (mesh-delivery's `merge---`). +// mergeAskPrefix is what the id of a merge ask made by the retired module mesh-delivery begins with: the pull request's number +// and the head's first twelve digits (`merge---`). The teller's ids name neither (see findMergeWarrant). func mergeAskPrefix(number int, head string) string { if len(head) > 12 { head = head[:12] @@ -221,8 +221,8 @@ func askersOfMerges() []string { return []string{sdkdelivery.TellerModule, theDe // caughtUpWarrant judges a chosen merge ask of the router as the warrant of a merge: the ask is the operator's, the warrant // answers that very ask as it was published, and it is the operator's Approve proved by the channel it came through (ADR 0437 -// §6, as movedOnApprove judges an announced one). What it cannot re-prove is the heads the Approve was bound to: the -// router's record does not carry them, and the ask's id names the pull request and the head merged. +// §6, as movedOnApprove judges an announced one). The heads the Approve was bound to are checked by the caller, where it can +// name them. func caughtUpWarrant(asker string, ask *asks.Ask, w *asks.Warrant) (string, error) { if err := askerAccepted(asker); err != nil { return "", err @@ -281,33 +281,107 @@ var routerAskIDs = func(ctx context.Context, inv catalogueReader, asker string) return ids, nil } -// findMergeWarrant finds the warrant of a merge: the router's chosen merge ask whose id names the pull request and its head, -// under an asker the controller takes a warrant from. Why it holds; or found false with why not, for a person to read; or an -// error when the router could not be read, which is no answer. A variable so a test needs no bus. -var findMergeWarrant = func(ctx context.Context, inv catalogueReader, number int, head string) (why string, found bool, reason string, err error) { - if number <= 0 { - return "", false, "the merge commit names no pull request, so no ask of the router can name it", nil +// closedRecord is a router record of an ask that ended: it never changes, so it is read once. +type closedRecord struct { + ask *asks.Ask + w *asks.Warrant +} + +var closedRecords sync.Map // asker + "." + id -> closedRecord + +// closedRecordOf is the router's record of an ask, kept once it is closed. A nil ask is an ask still open, ended without a +// choice, or not there. +func closedRecordOf(ctx context.Context, inv catalogueReader, asker, id string) (*asks.Ask, *asks.Warrant, error) { + if v, ok := closedRecords.Load(asker + "." + id); ok { + r := v.(closedRecord) + return r.ask, r.w, nil } - prefix := mergeAskPrefix(number, head) - reason = fmt.Sprintf("the router holds no merge ask %s* of %s", prefix, strings.Join(askersOfMerges(), " or ")) + ask, w, err := routerAskOf(ctx, inv, asker, id) + if err != nil { + return nil, nil, err + } + if ask != nil && w != nil { + closedRecords.Store(asker+"."+id, closedRecord{ask, w}) + } + return ask, w, nil +} + +// deliveryIDOf is the id the gate gives a delivery, and so the key of its head in the heads an Approve binds: the repository as +// owner/name, lower-cased, `@` and the first twelve characters of the head. +func deliveryIDOf(owner, repo, head string) string { + if len(head) > 12 { + head = head[:12] + } + return strings.ToLower(owner+"/"+repo) + "@" + head +} + +// coversHead says an ask the operator approved is for this merge: its Approve is bound to the heads of the pull request alone +// (mergeAct over {delivery id: head}, which is how a pull request not in a group is asked), or, for a group's ask, whose other +// members' heads the router's record does not carry in full, the ask as published names this pull request at this head in its +// details ("# ... at :"), and the digest the warrant answers covers those words. +func coversHead(ask *asks.Ask, owner, repo string, number int, head string) (how string, ok bool) { + option, found := ask.Option(optionApprove) + if !found { + return "", false + } + heads := map[string]string{deliveryIDOf(owner, repo, head): head} + if option.Performs(mergeAct(ask.ID, optionApprove, heads)) == nil { + return "bound to the head of this pull request", true + } + short := head + if len(short) > 12 { + short = short[:12] + } + line := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(repo) + `#` + strconv.Itoa(number) + ` .* at ` + regexp.QuoteMeta(short) + `:`) + if line.MatchString(ask.Details) { + return "a group's ask naming this pull request at this head", true + } + return "", false +} + +// findMergeWarrant finds the warrant of a merge, with no read the controller does not make today: the router's chosen merge +// ask for it. Under the teller (releases 3e on) the id is `merge---`, which names neither the pull +// request nor the head, so the teller's closed asks are read and the one the operator approved whose heads cover the merge's +// second parent is the warrant (coversHead). Under mesh-delivery, which made the earlier merges, the id names the pull request +// and the head and is found by them. Why it holds; or found false with why not, for a person to read; or an error when the +// router could not be read, which is no answer. A variable so a test needs no bus. +var findMergeWarrant = func(ctx context.Context, inv catalogueReader, owner, repo string, number int, head string) (why string, found bool, reason string, err error) { + reason = "the router holds no chosen merge ask of " + strings.Join(askersOfMerges(), " or ") + " that covers the head " + head[:min(12, len(head))] for _, asker := range askersOfMerges() { ids, err := routerAskIDs(ctx, inv, asker) if err != nil { return "", false, "", err } for _, id := range ids { - if !strings.HasPrefix(id, prefix) { + named := number > 0 && strings.HasPrefix(id, mergeAskPrefix(number, head)) + if asker == sdkdelivery.TellerModule && !strings.HasPrefix(id, sdkdelivery.AskKindMerge+"-") { continue } - ask, w, err := routerAskOf(ctx, inv, asker, id) + if asker == theDelivery && !named { + continue + } + ask, w, err := closedRecordOf(ctx, inv, asker, id) if err != nil { return "", false, "", err } - if why, err := caughtUpWarrant(asker, ask, w); err == nil { - return why, true, "", nil - } else { - reason = fmt.Sprintf("%s's ask %s holds no operator's Approve: %v", asker, id, err) + if ask == nil || w == nil { + continue } + warrant, err := caughtUpWarrant(asker, ask, w) + if err != nil { + if named { + reason = fmt.Sprintf("%s's ask %s holds no operator's Approve: %v", asker, id, err) + } + continue + } + how, covers := coversHead(ask, owner, repo, number, head) + if !covers && !named { + continue + } + if !covers { + how = "the ask's id names this pull request and head" + } + return warrant + ", " + how, true, "", nil } } return "", false, reason, nil @@ -413,11 +487,19 @@ func (f following) catchUpTrunk(ctx context.Context, t caughtTrunk) (later bool) if err != nil { return true } - if !wouldBePlanned(ctx, inv, m, entries, read) { + facts, err := factsOfMerge(ctx, inv, m, time.Now().UTC(), entries, read) + if err != nil { + fmt.Printf("catch-up: %s %.8s cannot be judged now: %v\n", repository, tm.Commit, err) + later = true + continue + } + // The very test hearMerge makes: a merge it would plan, or take as a first build, is a gap; any other it would say + // nothing reads, and is no gap for a person to decide. + if !facts.plans() && !facts.addsModuleToBuilds { settledMerges.Store(repository+"@"+tm.Commit, true) continue } - why, found, reason, err := findMergeWarrant(ctx, inv, m.Number, tm.Head) + why, found, reason, err := findMergeWarrant(ctx, inv, t.Owner, t.Repo, m.Number, tm.Head) if err != nil { fmt.Printf("catch-up: the router cannot be read, so %s %.8s is not caught up now: %v\n", repository, tm.Commit, err) later = true @@ -448,28 +530,6 @@ func (f following) catchUpTrunk(ctx context.Context, t caughtTrunk) (later bool) return later } -// wouldBePlanned says hearing the merge would plan something: it touches a module the mesh holds and is not history for every -// one of them (or is owed, as hearMerge judges). A merge that moves nothing is no gap for a person to decide. -func wouldBePlanned(ctx context.Context, inv *inventory.Inventory, m link.SourceMoved, entries []inventory.Entry, - read map[string][]inventory.ReadRepository) bool { - raw := m - raw.MergedAt = "" - touches := wouldMove(raw, entries, read) - if len(touches) == 0 { - return false - } - from, packaging, _ := mergeCandidates(m, entries, read) - if len(from) > 0 || len(packaging) > 0 { - return true - } - merged, err := time.Parse(time.RFC3339, m.MergedAt) - if err != nil { - return true - } - owed, err := owedLate(ctx, inv, m, merged, touches) - return err != nil || owed -} - // raiseMergeNotHeard keeps open, and observes again, the condition of a merge with no warrant to plan it. One for each // repository and commit, however many catch-ups find it. func (f following) raiseMergeNotHeard(ctx context.Context, repository, branch string, tm trunkMerge, m link.SourceMoved, reason string) {