GATE A-39, A-29, A-37, A-30, A-40, §592: the gate's findings go through one held writer over its own findings state, approval-carried-open is written for each approval carried and cleared by the seat git's pull-merged and pull-closed

Builds: design 47 G A-39, A-29, A-37, A-30, A-17, A-40, §542, §592

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 22:19:53 +01:00
parent 14699713b9
commit f06dd01bfe
9 changed files with 279 additions and 4 deletions
@@ -0,0 +1,97 @@
package main
import (
"context"
"encoding/json"
"fmt"
"strconv"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
gitseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/keys"
"git.novox.be/novox/mesh-sdk/go/views"
)
// The gate's own findings (design 47 G A-39, A-29, A-37, A-30, §592): they go through one held writer over the gate's own state
// `findings`, whose three events the manifest declares. The first is `approval-carried-open`: for each `approval-carried` the
// gate says, a carried Approve nobody uses is seen, and cleared when the seat `git` says that pull request merged or closed.
// Module is the gate's module name, the observer of its findings.
const Module = "mesh-delivery-gate"
// The scope and the kind of the finding a carried approval nobody uses is (design 47 §592).
const (
FindingScope = "gate"
ApprovalCarriedOpen = "approval-carried-open"
)
// pullMergedEvent and pullClosedEvent are the seat git's events that end a pull request, as the gate's manifest consumes them.
var (
pullMergedEvent = declared(gitseat.EventPullMerged)
pullClosedEvent = declared(gitseat.EventPullClosed)
)
// FindingWriter is what the gate holds of the one held writer (views.NewStdioHeldWriter over its state `findings`).
type FindingWriter interface {
Write(ctx context.Context, f meshconditions.Finding) error
Clear(ctx context.Context, scope, id, kind, why string) error
Held() []meshconditions.Finding
}
// NewFindingWriter makes the gate's held writer over the state findings, hearing findings-asked on the subject of the
// generated contract of the seat that defines it (A-17). It is started once the runtime serves (A-40).
func NewFindingWriter() (*views.HeldWriter[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent], error) {
return views.NewStdioHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: Module, State: stdio.State("findings"), Kinds: []string{ApprovalCarriedOpen}})
}
// approvalID is the id of a pull request's finding: its repository and number, each escaped as a key's name is.
func approvalID(repository string, number int64) string {
return keys.EscapeName(repository) + "." + keys.EscapeName(strconv.FormatInt(number, 10))
}
// approvalOpen writes the finding for an `approval-carried` the gate says, naming the head the Approve named, unless it holds it.
func (h *Holder) approvalOpen(repository string, number int, head string) error {
if h.Findings == nil {
return nil
}
id := approvalID(repository, int64(number))
for _, f := range h.Findings.Held() {
if f.Scope == FindingScope && f.ID == id && f.Kind == ApprovalCarriedOpen {
return nil
}
}
at := h.Now().UTC()
pull := fmt.Sprintf("%s#%d", repository, number)
return h.Findings.Write(context.Background(), meshconditions.Finding{Scope: FindingScope, ID: id, Kind: ApprovalCarriedOpen,
Subject: meshconditions.Subject{Scope: FindingScope, ID: id},
Severity: meshconditions.FindingSeverityWarning, Resolver: "operator",
Summary: fmt.Sprintf("the Approve of %s at %s, given before G, is carried and no merge or close of that pull request followed", pull, shortOf(head, 12)),
Headline: "An Approve carried over waits for its pull request",
Explanation: "The operator's Approve of this pull request, given before the delivery's switch, counts as its approval step. Nobody has merged or closed the pull request since.",
Needs: "merge the pull request through the seat git's merge-pull, or close it; the finding clears when the seat git says either.",
Source: "approval-carried", Observer: Module, At: at, Since: at})
}
// pullEnded clears the finding of a pull request the seat git said merged or closed; a pull request with none changes nothing.
func (h *Holder) pullEnded(body []byte, why string) error {
if h.Findings == nil {
return nil
}
var p struct {
Repository string `json:"repository"`
Number int64 `json:"number"`
}
if err := json.Unmarshal(body, &p); err != nil || p.Repository == "" || p.Number <= 0 {
h.Refuse(fmt.Errorf("a %s of the seat git is not readable: %v", why, err))
return nil
}
id := approvalID(p.Repository, p.Number)
for _, f := range h.Findings.Held() {
if f.Scope == FindingScope && f.ID == id && f.Kind == ApprovalCarriedOpen {
return h.Findings.Clear(context.Background(), FindingScope, id, ApprovalCarriedOpen, "the pull request was "+why)
}
}
return nil
}
@@ -0,0 +1,123 @@
package main
import (
"context"
"encoding/json"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/views"
)
// memState is the gate's state `findings` in memory.
type memState struct {
mu sync.Mutex
m map[string][]byte
}
func (s *memState) Get(key string) (*stdio.StateEntry, error) {
s.mu.Lock()
defer s.mu.Unlock()
if v, ok := s.m[key]; ok {
return &stdio.StateEntry{Key: key, Value: v}, nil
}
return nil, nil
}
func (s *memState) Put(key string, value any) (uint64, error) {
raw, _ := json.Marshal(value)
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = raw
return 1, nil
}
func (s *memState) Delete(key string) error { s.mu.Lock(); delete(s.m, key); s.mu.Unlock(); return nil }
func (s *memState) Keys() (k []string, _ error) {
s.mu.Lock()
defer s.mu.Unlock()
for key := range s.m {
k = append(k, key)
}
return
}
type oldFake map[string][]byte
func (o oldFake) Keys(string) (k []string, _ error) {
for key := range o {
k = append(k, key)
}
return
}
func (o oldFake) Get(_, key string) ([]byte, uint64, error) { return o[key], 1, nil }
// The gate's own finding (A-39, A-29, A-37, §592): written through the held writer for each approval carried, once, and cleared
// when the seat git says that pull request merged or closed; the writer's events are the manifest's.
func TestTheGatesApprovalCarriedOpenIsWrittenThroughItsHeldWriterAndClearedByTheSeatGit(t *testing.T) {
at := time.Date(2026, 11, 11, 21, 0, 0, 0, time.UTC)
head := strings.Repeat("1", 40)
state := &memState{m: map[string][]byte{}}
var mu sync.Mutex
var said []string
w, err := views.NewHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: Module, State: state, Kinds: []string{ApprovalCarriedOpen}, Node: "ace",
Now: func() time.Time { return at },
Publish: func(_ context.Context, subject string, _ []byte, _ string) error {
mu.Lock()
said = append(said, subject)
mu.Unlock()
return nil
},
Listen: func(context.Context, string, func([]byte)) (func(), error) { return func() {}, nil }})
if err != nil {
t.Fatal(err)
}
if err := w.Start(context.Background()); err != nil {
t.Fatal(err)
}
merge, merr := mdc.EncodeMerge(Merge{V: 1, Group: "feat/x", Ask: "merge.feat_x.0123456789ab", Approved: map[string]string{"novox/app@7": head, "novox/web@8": head, "novox/db@9": head},
Pulls: []MergePull{{Head: mdc.Head{Repository: "novox/db", Number: 9, Head: head}, State: mdc.MergeMerged, Commit: "c0ffee"}}}.stored())
h := &Holder{Logf: t.Logf, Now: func() time.Time { return at }, Old: oldFake{"feat_x": merge}, Findings: w, SaySeat: func(string, any, string) error { return nil }}
if merr != nil {
t.Fatal(merr)
}
if err := h.carryApprovals(); err != nil {
t.Fatal(err)
}
held := w.Held()
if len(held) != 2 {
t.Fatalf("held %+v", held)
}
for _, f := range held {
if f.Kind != ApprovalCarriedOpen || f.Scope != FindingScope || f.Observer != Module || !strings.Contains(f.Summary, head[:12]) {
t.Fatalf("finding %+v", f)
}
}
if id := approvalID("novox/app", 7); held[0].ID != id && held[1].ID != id {
t.Fatalf("no finding with id %s: %+v", id, held)
}
n := len(said)
if err := h.carryApprovals(); err != nil || len(said) != n {
t.Fatalf("said again: %v, %d events (was %d)", err, len(said), n)
}
// the seat git says one merged and the other closed; one it does not know changes nothing
if err := h.pullEnded([]byte(`{"repository":"novox/app","number":7}`), "merged"); err != nil {
t.Fatal(err)
}
if err := h.pullEnded([]byte(`{"repository":"novox/other","number":1}`), "merged"); err != nil || len(w.Held()) != 1 {
t.Fatalf("held %+v: %v", w.Held(), err)
}
if err := h.pullEnded([]byte(`{"repository":"novox/web","number":8}`), "closed"); err != nil || len(w.Held()) != 0 {
t.Fatalf("held %+v: %v", w.Held(), err)
}
got := strings.Join(said, " ")
for _, ev := range []string{"finding-written", "finding-cleared"} {
if !strings.Contains(got, ev) {
t.Fatalf("no %s said: %v", ev, said)
}
}
}
@@ -32,6 +32,8 @@ type Holder struct {
Consumer *StepConsumer
// SaySeat says an event of the seat mesh-delivery (recordevents.go); nil says none.
SaySeat SeatSay
// Findings is the gate's one held writer of its own findings (findings.go); nil writes none.
Findings FindingWriter
// Say says a condition to the operator, and Conditions keeps which the gate raised (conditions.go); nil says none.
Say func(c oc.Condition) error
Conditions ConditionStore
@@ -6,7 +6,7 @@ import "git.novox.be/novox/mesh-sdk/go/announce"
// issue 1790): the module says it itself, from its code, before it serves, and a reader is granted what it said
// (clause 3). A state is readable when another module's manifest reads it (keeps_test.go names the readers). keeps_test.go holds the list to the
// manifest's `state`, and keeps_integration_test.go that the module announces it on a bus.
var Kept = []announce.State{{Name: "conditions"}, {Name: "deliveries"}, {Name: "groups"}, {Name: "merges", Readable: true}, {Name: "owed", Readable: true}, {Name: "source", Readable: true}}
var Kept = []announce.State{{Name: "conditions"}, {Name: "deliveries"}, {Name: "findings"}, {Name: "groups"}, {Name: "merges", Readable: true}, {Name: "owed", Readable: true}, {Name: "source", Readable: true}}
// announceKept says Kept through keeps, which is stdio.Keeps.
func announceKept(keeps func(...announce.State) error) error { return keeps(Kept...) }
@@ -6,6 +6,7 @@
package main
import (
"context"
"errors"
"fmt"
"os"
@@ -31,7 +32,7 @@ func (l *listening) get() string { l.mu.Lock(); defer l.mu.Unlock(); return l.n
// The events this owner follows: the forge bridge's word on an act owed, and the step events the delivery records are built
// from (consumer.go).
var followed = append([]string{owedEndedEvent}, stepPatterns()...)
var followed = append(append([]string{owedEndedEvent}, stepPatterns()...), pullMergedEvent, pullClosedEvent)
func main() {
settingsFile := os.Getenv("MESH_DELIVERY_GATE_SETTINGS")
@@ -66,6 +67,20 @@ func main() {
h.Records.Changed = h.recordChanged
h.Old = kvOld{}
h.Consumer = &StepConsumer{Records: h.Records, Families: stepFamilies, Now: h.Now, Logf: logf, Taken: h.TakenIn}
// The gate's one held writer of its own findings (design 47 G A-39, A-37, A-40): made at once, started once the runtime serves.
fw, err := NewFindingWriter()
if err != nil {
logf("%v", err)
os.Exit(1)
}
h.Findings = fw
go func() {
<-stdio.Served()
if err := fw.Start(context.Background()); err != nil {
logf("%v", err)
os.Exit(1)
}
}()
l := &listening{now: "not yet: starting"}
go run(h, l)
if err := announceKept(stdio.Keeps); err != nil {
@@ -130,6 +145,12 @@ func take(h *Holder, e stdio.Envelope) error {
if e.Key == owedEndedEvent {
return h.OwedEnded(e.Body)
}
if e.Key == pullMergedEvent {
return h.pullEnded(e.Body, "merged")
}
if e.Key == pullClosedEvent {
return h.pullEnded(e.Body, "closed")
}
return nil
}
@@ -10,6 +10,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatlist/seatlisttest"
@@ -136,7 +137,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
// From G's delivery the gate hears neither the bridge's merged and pushed messages nor the teller's answers nor the view's
// reach (design 47 G §592, §593): it consumes the indexer's take-in.taken and the bridge's owed.ended, and the git events that clear
// an approval carried.
if !reflect.DeepEqual(m.Consumes, followed) {
if want := append(slices.Clone(followed), meshconditions.Seat+"."+meshconditions.EventFindingsAsked); !reflect.DeepEqual(m.Consumes, want) {
t.Fatalf("consumes %v, follows %v", m.Consumes, followed)
}
for _, c := range m.Consumes {
@@ -146,6 +147,11 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
}
}
}
// A-30: the events its held writer says are declared in the manifest, and the two events that clear its finding are consumed.
if !strings.Contains(raw, `"finding-written.*"`) || !strings.Contains(raw, `"finding-cleared.*"`) || !strings.Contains(raw, `"findings-stated.*"`) ||
pullMergedEvent == "" || pullClosedEvent == "" {
t.Fatalf("the finding events are not declared, or the pull events not consumed: %q %q", pullMergedEvent, pullClosedEvent)
}
if !reflect.DeepEqual(m.Emits, []string{"transition"}) { // `group` and `merged` go with the merge states (§431)
t.Fatalf("emits %v", m.Emits)
}
@@ -263,6 +263,9 @@ func (h *Holder) carryApprovals() error {
if err := h.SaySeat(subject, body, fmt.Sprintf("approval-carried.%s.%d", repository, number)); err != nil {
return fmt.Errorf("the approval of %s#%d cannot be said carried: %w", repository, number, err)
}
if err := h.approvalOpen(repository, number, head); err != nil {
return fmt.Errorf("the finding of the approval of %s#%d cannot be written: %w", repository, number, err)
}
}
}
return nil
+1
View File
@@ -4,6 +4,7 @@ go 1.26.0
require (
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
+23 -1
View File
@@ -18,7 +18,10 @@
],
"consumes": [
"mesh-delivery-forge-bridge.owed.ended",
"mesh-delivery-indexer.take-in.taken"
"mesh-delivery-indexer.take-in.taken",
"git.pull-merged",
"git.pull-closed",
"mesh-conditions.findings-asked"
],
"emits": [
"transition"
@@ -297,6 +300,25 @@
"name": "conditions",
"storage": "key-value",
"template": "state"
},
{
"name": "findings",
"storage": "key-value",
"template": "state"
},
{
"name": "finding-events",
"storage": "stream",
"template": "events",
"options": {
"events": [
"finding-written.*",
"finding-cleared.*",
"findings-stated.*"
],
"max-age-days": 7,
"max-mib": 16
}
}
],
"packages": {