GATE8: gate_source and gate_source_again, the gate's source revisions read and said again

Builds: design 47 G §83, §84, §93
This commit is contained in:
2026-11-12 04:55:19 +01:00
parent 500c8c8cec
commit 75514e05e2
4 changed files with 147 additions and 1 deletions
@@ -267,6 +267,7 @@ func tools(h *Holder, l *listening) []stdio.Tool {
}},
}
list = append(list, recordTools(h, meshhandactsclient.Recorder{Ask: stdio.Ask})...)
list = append(list, sourceTools(h, meshhandactsclient.Recorder{Ask: stdio.Ask})...)
// A verb that changes a delivery asks for a look at what it makes possible (react.go).
for i := range list {
run := list[i].Run
@@ -0,0 +1,83 @@
package main
// The gate's tools over its source revisions (design 47 G §83, §84, §93): gate_source reads every branch's source revision
// whole, and gate_source_again says source-moved once more for one branch, recorded first in the hand-act log of the seat
// mesh-hand-acts, so a part that missed it reads the branch's source again.
import (
"errors"
"fmt"
"strconv"
"strings"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// SourceAll is the seat verb source's answer for every branch the gate keeps a source revision of, each as the verb answers
// it for that branch, or why the branches cannot be read.
func (h *Holder) SourceAll() map[string]any {
list := h.SourceRevision("")
if list.Error != "" {
return map[string]any{"error": list.Error}
}
out := make([]seat.SourceAnswer, 0, len(list.Branches))
for _, b := range list.Branches {
out = append(out, h.SourceRevision(b))
}
return map[string]any{"branches": out}
}
// SourceAgain says source-moved again for one branch with the revision its key holds now, after the hand-act log took it:
// nothing is said when the log did not take it, or the branch has no source key.
func (h *Holder) SourceAgain(branch, why, by string, acts handActs) (map[string]any, error) {
if strings.TrimSpace(branch) == "" || strings.TrimSpace(why) == "" {
return nil, errors.New("say the branch and why its source-moved is said again")
}
if h.Source == nil || h.SaySeat == nil {
return nil, errors.New("this gate keeps no source or says no events")
}
held, rev, err := h.Source.Read(branch)
if err != nil {
return nil, err
}
if held == nil {
return nil, fmt.Errorf("the gate keeps no source revision of the branch %s", branch)
}
if acts == nil {
return nil, errors.New("the hand-act log cannot be reached here, so nothing was said")
}
if _, err := acts.Record(meshhandactsclient.Request{What: "said source-moved again for the branch " + branch,
Why: why, Cause: "gate_source_again", Verb: "source-again"}); err != nil {
return nil, fmt.Errorf("the hand act was not recorded through mesh-hand-acts, so nothing was said: %w", err)
}
at := h.Now().UTC()
escaped := keys.EscapeName(branch)
id := "source-moved." + escaped + "." + strconv.FormatUint(rev, 10) + ".again." + strconv.FormatInt(at.UnixNano(), 10)
if err := h.SaySeat(seat.SubjectSourceMoved(escaped),
seat.SourceMovedEvent{Branch: branch, Group: held.Group, Revision: int64(rev), At: &at}, id); err != nil {
return nil, err
}
return map[string]any{"said": "source-moved", "branch": branch, "revision": rev, "by": by}, nil
}
// sourceTools are the source's tools; acts is the hand-act log.
func sourceTools(h *Holder, acts handActs) []stdio.Tool {
return []stdio.Tool{
{Name: "gate_source",
Description: "Every branch's source revision the gate keeps, each whole: its repositories with their trunk branch and head, " +
"the group that moved it last and the key's revision. Read, never written.",
Run: func(map[string]any) (any, error) { return h.SourceAll(), nil }},
{Name: "gate_source_again",
Description: "Say source-moved again for one branch, with the revision its source key holds now, for a part that missed it; " +
"recorded in the hand-act log of the seat mesh-hand-acts first.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"branch": str("the watched branch"), "why": str("why it is said again, in plain words")},
"required": []string{"branch", "why"}},
Run: func(a map[string]any) (any, error) {
return h.SourceAgain(strArg(a, "branch"), strArg(a, "why"), stdio.CallerOf(a), acts)
}},
}
}
@@ -0,0 +1,60 @@
package main
import (
"errors"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"strings"
"testing"
"time"
)
type branchSource struct{ memSource }
func (b *branchSource) Branches() ([]string, error) {
var out []string
for k := range b.vals {
out = append(out, k)
}
return out, nil
}
// Design 47 G §83, §84: gate_source answers every branch's source revision whole; gate_source_again says source-moved once
// more for one branch with a message id of its own, only after the hand-act log took it.
func TestTheSourceToolsReadEveryBranchAndSayItAgainAfterTheLog(t *testing.T) {
src := &branchSource{}
for _, b := range []string{"main", "feat"} {
_, _ = src.Set(b, func(*Source) (Source, bool) {
return Source{Group: "g-" + b, Heads: map[string]string{"novox/app": "c1"}}, true
})
}
type said struct{ subject, id string }
var events []said
now := time.Date(2026, 11, 12, 4, 0, 0, 0, time.UTC)
h := &Holder{Now: func() time.Time { return now }, Source: src,
SaySeat: func(subject string, body any, id string) error {
events = append(events, said{subject, id})
return nil
}}
all := h.SourceAll()["branches"].([]seat.SourceAnswer)
if len(all) != 2 {
t.Fatalf("gate_source answered %+v", all)
}
acts := &handActsFake{refuse: errors.New("log down")}
if _, err := h.SourceAgain("main", "a part missed it", "me", acts); err == nil || len(events) != 0 {
t.Fatalf("said without the log: %v %v", err, events)
}
acts.refuse = nil
if _, err := h.SourceAgain("main", "", "me", acts); err == nil {
t.Fatal("said with no reason")
}
if _, err := h.SourceAgain("nothing", "x", "me", acts); err == nil {
t.Fatal("said for a branch with no source")
}
if _, err := h.SourceAgain("main", "a part missed it", "me", acts); err != nil {
t.Fatal(err)
}
if len(acts.took) != 1 || acts.took[0].Cause != "gate_source_again" || len(events) != 1 ||
!strings.HasSuffix(events[0].subject, ".event.source-moved.main") || !strings.Contains(events[0].id, ".again.") {
t.Fatalf("log %+v events %+v", acts.took, events)
}
}
+3 -1
View File
@@ -349,7 +349,9 @@
"gate_record",
"gate_record_end",
"gate_record_repair",
"gate_records"
"gate_records",
"gate_source",
"gate_source_again"
],
"config": {
"record-retention": {