The gate serves the seat mesh-delivery's verb source: each repository's trunk branch and head and the revision, read from source.main (milestone 47/G, source verb)

A node-engine reads the repositories here to call a reverted build's catch-up tool once per repository, never the
gate's state. Rows in issue 888's test and the bus test, which call the verb over the runtime.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ

(Joined from b45fd0ab.)
Builds: design 47 G §15 in part (GATE7)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 14:37:33 +01:00
parent 32528aa58f
commit c6cf8b64f1
7 changed files with 68 additions and 1 deletions
@@ -137,6 +137,17 @@ func (s *fakeSource) Move(update func(prev *Source) (Source, bool)) error {
return nil
}
// Read is source.main as held, and its revision: the number of writes.
func (s *fakeSource) Read() (*Source, uint64, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.main == nil {
return nil, 0, nil
}
held := *s.main
return &held, uint64(len(s.moves)), nil
}
func sortStrings(s []string) { sort.Strings(s) }
func itoa(n int) string { return strconv.Itoa(n) }
@@ -631,6 +631,11 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB
src, err := mdc.DecodeSource(e.Value())
return err == nil && src.Group == "fix/x" && src.Heads["novox/app"] == strings.Repeat("b", 40)
})
// The seat's verb source answers it from the bucket, with its revision on the bus.
said := strings.ReplaceAll(rt.call(t, Seat+".source", map[string]any{}), `\"`, `"`)
if !strings.Contains(said, `{"head":"`+strings.Repeat("b", 40)+`","repository":"novox/app","trunk":"main"}`) || !strings.Contains(said, `"revision":`) {
t.Fatalf("source answers %s", said)
}
// The delivery ends merged (ADR 0539 §17), and the gate asked the controller nothing (R6-C3).
rt.until("the delivery to be merged", eventWithin, func() bool {
e, err := b.kvs["deliveries"].Get(ctx, kvKey(IDOf("novox/app", head)))
@@ -5,6 +5,7 @@ import (
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
)
// Issue 888: the checker judges on the trunk source.main names (mesh-delivery modules/mesh-delivery-checker main.go), and the
@@ -38,6 +39,12 @@ func TestAMergeHeardOnlyFromTheForgeMovesSourceMain(t *testing.T) {
if w.source.main == nil || w.source.main.Heads[i888App] != i888Merge1 || w.source.main.Heads[i888Other] != i888OtherH {
t.Fatalf("source.main is %+v; wanted %s at %s and %s kept", w.source.main, i888App, i888Merge1, i888Other)
}
// The seat's verb source answers each repository's trunk branch and head, and the revision, from the same key.
if a := w.h.SourceRevision(); a.Error != "" || a.Revision != 1 || len(a.Repositories) != 2 ||
a.Repositories[0] != (seat.SourceHead{Repository: i888App, Trunk: MergeBranch, Head: i888Merge1}) ||
a.Repositories[1].Repository != i888Other || a.Repositories[1].Head != i888OtherH {
t.Fatalf("source answers %+v", a)
}
n := len(w.source.moves)
w.settleAll()
w.h.PullMerged(i888MergedAt(i888App, 3, i888Head1, i888Merge1, w.now.Add(-time.Minute))) // heard again
@@ -354,6 +354,12 @@ func tools(h *Holder, l *listening) []stdio.Tool {
Input: map[string]any{"type": "object", "properties": map[string]any{
"all": map[string]any{"type": "string", "enum": []string{"true", "false"}}}},
Run: func(a map[string]any) (any, error) { return h.Groups(boolArg(a, "all")), nil }},
{Name: seat + "source",
Description: "The source revision the gate holds: every repository it names, its trunk branch and that trunk's head, the " +
"group that moved it last and its revision. Read, never written: a node-engine reads the repositories here to call a " +
"reverted build's catch-up tool once per repository, never the gate's state.",
Input: map[string]any{"type": "object", "properties": map[string]any{}},
Run: func(map[string]any) (any, error) { return h.SourceRevision(), 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 " +
@@ -75,7 +75,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
}
// The eight verbs the seat keeps (ADR 0539 §17): times, what-if, release, stop and retire-history went with the plans; and
// settle, a person's ask to settle merges held without a passing check (ADR 0556 clause 5).
if want := []string{"deliveries", "show", "groups", "checks", "table", "stalled", "recheck", "close", "settle"}; !reflect.DeepEqual(m.Claims[0].Serves, want) {
if want := []string{"deliveries", "show", "groups", "source", "checks", "table", "stalled", "recheck", "close", "settle"}; !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).
@@ -11,6 +11,7 @@ import (
"time"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
stdio "git.novox.be/novox/mesh-sdk/go"
)
@@ -32,6 +33,8 @@ type SourceStore interface {
// Move reads source.main and writes what update makes of it, unless update says nothing is to be written; a write
// another writer won is read again and tried again.
Move(update func(prev *Source) (Source, bool)) error
// Read is source.main as held and its revision on the bus; nil and 0 before it is first written.
Read() (*Source, uint64, error)
}
// kvMerges is merges over the runtime.
@@ -107,6 +110,40 @@ func (kvSource) Move(update func(prev *Source) (Source, bool)) error {
return fmt.Errorf("source.main was written by another writer first %d times: %w", changeAttempts, stdio.ErrChanged)
}
func (kvSource) Read() (*Source, uint64, error) {
e, err := stdio.State("source").Get(mdc.SourceMainKey)
if err != nil || e == nil {
return nil, 0, err
}
held, err := mdc.DecodeSource(e.Value)
if err != nil {
return nil, 0, fmt.Errorf("source.main is not readable: %w", err)
}
return &held, e.Revision, nil
}
// SourceRevision is the seat's verb source: every repository source.main names, its trunk branch and head, the group that
// moved it last and its revision, or why it cannot be read. source.main names each repository's main only (MergeBranch).
// Outside the lock.
func (h *Holder) SourceRevision() seat.SourceAnswer {
if h.Source == nil {
return seat.SourceAnswer{Error: "no source bucket"}
}
held, revision, err := h.Source.Read()
if err != nil {
return seat.SourceAnswer{Error: err.Error()}
}
if held == nil {
return seat.SourceAnswer{}
}
out := seat.SourceAnswer{Group: held.Group, Revision: int64(revision)}
for repository, head := range held.Heads {
out.Repositories = append(out.Repositories, seat.SourceHead{Repository: repository, Trunk: MergeBranch, Head: head})
}
sort.Slice(out.Repositories, func(i, j int) bool { return out.Repositories[i].Repository < out.Repositories[j].Repository })
return out
}
// SourceMain is source.main as the bucket source holds it, read and never written: its group and each repository's trunk
// head, or why it cannot be read. Outside the lock.
func (h *Holder) SourceMain() map[string]any {
+1
View File
@@ -10,6 +10,7 @@
"deliveries",
"show",
"groups",
"source",
"checks",
"table",
"stalled",