graph.Served takes the node-engine contract's haves (running, healthy), so a caller needs no adapter; the checker reads them through nodeengineclient (B7j; ADR 0634 §1, §2; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 16:01:48 +01:00
parent f938a53e09
commit 5fe482edec
3 changed files with 33 additions and 8 deletions
@@ -14,6 +14,8 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/process"
@@ -154,7 +156,7 @@ func (n nodeNames) Names(ctx context.Context) ([]string, error) {
}
var out []string
for key := range lister.Keys() {
if node, _, isNode, err := delivery.ParseHaveKey(key); err == nil && isNode {
if node, _, isNode, err := nodeengineclient.ParseHaveKey(key); err == nil && isNode {
out = append(out, node)
}
}
@@ -180,9 +182,9 @@ func (r runningServes) Served(ctx context.Context) (func(string) bool, error) {
if err != nil {
return nil, err
}
haves := map[string]map[string]delivery.Have{}
haves := map[string]map[string]nodeengine.HaveByNodeModuleValue{}
for key := range lister.Keys() {
node, module, isNode, err := delivery.ParseHaveKey(key)
node, module, isNode, err := nodeengineclient.ParseHaveKey(key)
if err != nil || isNode {
continue
}
@@ -190,9 +192,9 @@ func (r runningServes) Served(ctx context.Context) (func(string) bool, error) {
if err != nil {
continue
}
if h, err := delivery.DecodeHave(e.Value()); err == nil {
if h, err := nodeengineclient.DecodeHave(e.Value()); err == nil {
if haves[node] == nil {
haves[node] = map[string]delivery.Have{}
haves[node] = map[string]nodeengine.HaveByNodeModuleValue{}
}
haves[node][module] = h
}
+5 -3
View File
@@ -11,6 +11,8 @@ import (
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
)
// Version is the version of a node's value this package writes and the only one it reads.
@@ -595,16 +597,16 @@ func OfManifests(manifests map[string]catalogue.Manifest) Graph {
// (servesOf answers the verbs of a module's build at a fingerprint, ServesOf of its manifest, and whether it was read).
// A build whose manifest cannot be read counts what fallback answers of its module (the graph's Serves, so a build read
// late never holds its callers for good, or nothing), and is named in unread, sorted, so its reader says so.
func Served(haves map[string]map[string]delivery.Have, servesOf func(module, fp string) ([]string, bool),
func Served(haves map[string]map[string]nodeengine.HaveByNodeModuleValue, servesOf func(module, fp string) ([]string, bool),
fallback func(module string) []string) (served func(invoke string) bool, unread []string) {
set := map[string]bool{}
for _, node := range slices.Sorted(maps.Keys(haves)) {
for _, module := range slices.Sorted(maps.Keys(haves[node])) {
h := haves[node][module]
if h.Phase != delivery.PhaseRunning || h.Health != delivery.HealthOK {
if h.Phase != nodeengine.HaveByNodeModuleValuePhaseRunning || h.Health != nodeengine.HaveByNodeModuleValueHealthOk {
continue
}
serves, ok := servesOf(module, h.FP)
serves, ok := servesOf(module, h.Fp)
if !ok {
unread = append(unread, module)
if fallback != nil {
+21
View File
@@ -8,6 +8,7 @@ import (
"strings"
"testing"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
@@ -16,6 +17,7 @@ import (
func TestGraphEdges(t *testing.T) {
t.Run("a node's value", nodeValues)
t.Run("a reader of the seat list waits for its holder", readersOfTheSeatList)
t.Run("a verb is served by a running healthy have of the node-engine contract", servedByHaves)
fp := "src2:" + strings.Repeat("b", 64)
node := func(repository, path string, edit func(*Node)) Node {
n := Node{FP: fp, Repository: repository, Path: path}
@@ -291,3 +293,22 @@ func readersOfTheSeatList(t *testing.T) {
t.Errorf("the node-agent waits for %v, want nothing: the bootstrap carries it", got)
}
}
// servedByHaves: Served counts the node-engine contract's haves, running and healthy only; an unread build is named.
func servedByHaves(t *testing.T) {
running := nodeengine.HaveByNodeModuleValue{Fp: "a", Phase: nodeengine.HaveByNodeModuleValuePhaseRunning, Health: nodeengine.HaveByNodeModuleValueHealthOk}
failed := nodeengine.HaveByNodeModuleValue{Fp: "b", Phase: nodeengine.HaveByNodeModuleValuePhaseRunning, Health: nodeengine.HaveByNodeModuleValueHealthFailed}
haves := map[string]map[string]nodeengine.HaveByNodeModuleValue{"n1": {"m1": running, "m2": failed, "m3": running}}
served, unread := Served(haves, func(module, fp string) ([]string, bool) {
if module == "m3" {
return nil, false
}
return []string{module + ".verb"}, true
}, nil)
if !served("m1.verb") || served("m2.verb") {
t.Fatalf("a running healthy have serves, a failed one does not: m1 %v, m2 %v", served("m1.verb"), served("m2.verb"))
}
if !reflect.DeepEqual(unread, []string{"m3"}) {
t.Fatalf("unread = %v, want [m3]", unread)
}
}