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:
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user