From 75514e05e21f768f18f5d6bef4a5337321e7f216 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 04:55:19 +0100 Subject: [PATCH] GATE8: gate_source and gate_source_again, the gate's source revisions read and said again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §83, §84, §93 --- .../cmd/mesh-delivery-gate/main.go | 1 + .../cmd/mesh-delivery-gate/sourcetools.go | 83 +++++++++++++++++++ .../mesh-delivery-gate/sourcetools_test.go | 60 ++++++++++++++ modules/mesh-delivery-gate/module.json | 4 +- 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools.go create mode 100644 modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools_test.go diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/main.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/main.go index 86fb32541..e836b4e57 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/main.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/main.go @@ -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 diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools.go new file mode 100644 index 000000000..2cebafb6c --- /dev/null +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools.go @@ -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) + }}, + } +} diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools_test.go new file mode 100644 index 000000000..a842f05ea --- /dev/null +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/sourcetools_test.go @@ -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) + } +} diff --git a/modules/mesh-delivery-gate/module.json b/modules/mesh-delivery-gate/module.json index e7d471e6e..0760ff7c9 100644 --- a/modules/mesh-delivery-gate/module.json +++ b/modules/mesh-delivery-gate/module.json @@ -349,7 +349,9 @@ "gate_record", "gate_record_end", "gate_record_repair", - "gate_records" + "gate_records", + "gate_source", + "gate_source_again" ], "config": { "record-retention": {