GATE A-23, §431: the gate serves recovery, marking the delivery record's field recovery, and no longer serves checks

Builds: design 47 G §81, §132, §431, A-23 (G-20), A-18 as corrected

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 21:55:57 +01:00
parent ab5423f87f
commit a587125047
6 changed files with 107 additions and 19 deletions
@@ -10,6 +10,7 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
@@ -345,24 +346,20 @@ func tools(h *Holder, l *listening) []stdio.Tool {
Input: map[string]any{"type": "object", "properties": map[string]any{
"branch": str("the watched branch, escaped as a key name is; absent: the list of the branches the gate keeps a source revision of")}},
Run: func(a map[string]any) (any, error) { return h.SourceRevision(strArg(a, "branch")), nil }},
{Name: seat + "checks",
Description: "What the mesh's checks said of a pull request's head or of one commit: each of the commit's " +
"statuses (mesh/check, mesh/delivery, …) with its state, description and when it " +
"was set; the merge check's verdict as the checker's mesh/check line says it, and when it was heard; and whether the branch's protection would let it " +
"merge, every required status being success.",
{Name: seat + "recovery",
Description: "Name a merge a recovery (design 47 G §81): the delivery record that holds the commit is marked with who named it " +
"(the caller the runtime proved), why and the commit, and recovery-merged is said for every channel.",
Input: map[string]any{"type": "object", "properties": map[string]any{"repository": str("owner/repository"),
"number": str("a pull request's number: its head is read"),
"commit": str("a commit's sha, or the start of one, instead of a pull request")},
"required": []string{"repository"}},
"commit": str("the merge commit"), "why": str("why the merge is a recovery")},
"required": []string{"repository", "commit", "why"}},
Run: func(a map[string]any) (any, error) {
if err := need(a, "repository"); err != nil {
if err := need(a, "repository", "commit", "why"); err != nil {
return nil, err
}
number, err := intArg(a, "number")
if err != nil {
return nil, err
if h.Records == nil {
return nil, errors.New("this gate keeps no delivery records")
}
return h.Checks(strArg(a, "repository"), number, strArg(a, "commit"))
return h.Records.Recovery(strArg(a, "repository"), strArg(a, "commit"), strArg(a, "why"), stdio.CallerOf(a))
}},
{Name: seat + "stalled",
Description: "Every delivery held past its state's bound, with the transition the table lets healer H2 take. " +
@@ -76,8 +76,8 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
t.Fatal("the manifest declares a seat; mesh-delivery is the mesh's own")
}
// The verbs the seat keeps: times, what-if, release, stop and retire-history went with the plans (ADR 0539 §17), and
// settle with the merge states (design 47 G §431).
if want := []string{"deliveries", "show", "groups", "source", "checks", "stalled"}; !reflect.DeepEqual(m.Claims[0].Serves, want) {
// settle and checks with the merge states (design 47 G §431); recovery is G's (§81, A-23).
if want := []string{"deliveries", "show", "groups", "source", "recovery", "stalled"}; !reflect.DeepEqual(m.Claims[0].Serves, want) {
t.Fatalf("serves %v, wanted %v", m.Claims[0].Serves, want)
}
// The forge messages' layout is the bridge's published package forgemsg, taken by its series (ADR 0634 §3, design 32).
@@ -285,7 +285,7 @@ func TestTheGateHoldsTheSeatAsTheSDKsTableOfRelease6HasIt(t *testing.T) {
served[verb] = true
}
}
for _, removed := range []string{"settle"} { // design 47 G §431
for _, removed := range []string{"settle", "checks"} { // design 47 G §431
if served[removed] {
t.Errorf("the gate serves %s, which G removed with the merge states", removed)
}
@@ -293,7 +293,7 @@ func TestTheGateHoldsTheSeatAsTheSDKsTableOfRelease6HasIt(t *testing.T) {
t.Errorf("the manifest serves %s, which G removed with the merge states", removed)
}
}
for _, verb := range []string{"deliveries", "show", "groups", "checks", "stalled"} {
for _, verb := range []string{"deliveries", "show", "groups", "stalled", "recovery"} {
if !served[verb] {
t.Errorf("the gate does not serve %s, which the seat keeps", verb)
}
@@ -98,6 +98,16 @@ type Record struct {
// Acts are the acts owed the forge for the record's pull requests, by id, with their outcomes (owedended.go).
Acts map[string]RecordAct `json:"acts,omitempty"`
Ended *RecordEnded `json:"ended,omitempty"`
// Recovery marks the merge its author named a recovery by the seat's verb recovery (design 47 G §81, A-23).
Recovery *RecordRecovery `json:"recovery,omitempty"`
}
// RecordRecovery is the field recovery of a record: who named the merge a recovery (the caller the runtime proved), why,
// and the merge commit.
type RecordRecovery struct {
By string `json:"by"`
Why string `json:"why"`
Commit string `json:"commit"`
}
// RecordGroup is the group's state as the record keeps it: what `merges` held for the group, and its members.
@@ -789,6 +789,37 @@ func TestTheRecordsToolsReadThemAndEndOneByHand(t *testing.T) {
t.Fatalf("a second repair did something: %+v", lines)
}
})
t.Run("recovery marks the record that holds the commit through its ordinary update and says recovery-merged once (design 47 G A-23)", func(t *testing.T) {
rs, r, said := setup(t)
commit := strings.Repeat("1", 40)
got, err := rs.Recovery("novox/app", commit, "the live delivery was broken", "jochen")
if err != nil || !got.Recovery || got.By != "jochen" || got.Commit != commit || got.Repository != "novox/app" {
t.Fatalf("%+v %v", got, err)
}
held, err := rs.Read(r.Instance)
if err != nil || held.Recovery == nil || *held.Recovery != (RecordRecovery{By: "jochen", Why: "the live delivery was broken", Commit: commit}) {
t.Fatalf("the record is not marked: %+v %v", held, err)
}
subject := meshdelivery.SubjectRecoveryMerged
if _, err := rs.Recovery("novox/app", commit[:12], "the live delivery was broken", "jochen"); err != nil {
t.Fatal(err)
}
n := 0
for _, s := range *said {
if strings.HasPrefix(s, subject+" ") {
n++
}
}
if n != 1 {
t.Fatalf("recovery-merged said %d times: %v", n, *said)
}
if _, err := rs.Recovery("novox/other", commit, "why", "jochen"); err == nil {
t.Fatal("a commit no record holds was marked")
}
if _, err := rs.Recovery("novox/app", commit, " ", "jochen"); err == nil {
t.Fatal("a recovery with no why was taken")
}
})
t.Run("a removal asks the runtime's ledger removal of the instance, and one said twice changes nothing", func(t *testing.T) {
was := ledgerCall
t.Cleanup(func() { ledgerCall = was })
@@ -0,0 +1,46 @@
package main
import (
"errors"
"fmt"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
)
// Recovery names a merge a recovery (design 47 G §81, A-23): the delivery record that holds the commit is marked through its
// ordinary update, with one field recovery holding who named it (the caller the runtime proved), why and the commit, and
// recovery-merged is said. Said twice for the same mark, it changes nothing and says nothing more.
func (rs *Records) Recovery(repository, commit, why, by string) (meshdelivery.RecoveryAnswer, error) {
repository, commit, why = strings.ToLower(strings.TrimSpace(repository)), strings.TrimSpace(commit), strings.TrimSpace(why)
if repository == "" || commit == "" || why == "" {
return meshdelivery.RecoveryAnswer{}, errors.New("a recovery names its repository, its merge commit and why")
}
kept, err := rs.Kept()
if err != nil {
return meshdelivery.RecoveryAnswer{}, fmt.Errorf("the delivery records cannot be read: %w", err)
}
for _, r := range kept {
for id, head := range r.Heads {
if !strings.HasPrefix(strings.ToLower(id), repository+"@") || !strings.HasPrefix(head, commit) {
continue
}
want := RecordRecovery{By: by, Why: why, Commit: head}
if r.Recovery != nil && *r.Recovery == want {
return meshdelivery.RecoveryAnswer{By: by, Commit: head, Recovery: true, Repository: repository}, nil
}
seq, err := rs.Ledger.Apply(r.Instance, OpTrace, map[string]any{"recovery": want})
if err != nil {
return meshdelivery.RecoveryAnswer{}, err
}
r.Recovery, r.Sequence = &want, seq
now := rs.now()
if err := rs.say(meshdelivery.SubjectRecoveryMerged, meshdelivery.RecoveryMergedEvent{At: &now, By: by, Commit: head,
Repository: repository, V: 1, Why: why}, "recovery-merged."+r.Instance+"."+head); err != nil {
return meshdelivery.RecoveryAnswer{}, fmt.Errorf("the record %s is marked but recovery-merged is not said: %w", r.Instance, err)
}
return meshdelivery.RecoveryAnswer{By: by, Commit: head, Recovery: true, Repository: repository}, nil
}
}
return meshdelivery.RecoveryAnswer{}, fmt.Errorf("no delivery record holds %s at %s: the merge is taken in first", repository, commit)
}
+6 -2
View File
@@ -11,7 +11,7 @@
"show",
"groups",
"source",
"checks",
"recovery",
"stalled"
]
}
@@ -127,6 +127,9 @@
},
"words": {
"type": "string"
},
"recovery": {
"type": "object"
}
},
"operations": [
@@ -154,7 +157,8 @@
"name": "trace",
"effect": "set-fields",
"fields": [
"trace"
"trace",
"recovery"
],
"sequence": true
},