Join 9: the builder reads the build seats' steps in seat form, at the seat's holder (A-116, A-128)

module.json reads seat:node-build-<language>.steps as the checker and the watchman do; the
tools bundle reads <holder>.steps, the holder found by seatlist.HolderOf where it is read.

Builds: design 47 G A-116, A-126, A-128

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 02:06:19 +01:00
parent bce3c96acb
commit ac153497f6
5 changed files with 49 additions and 8 deletions
@@ -2,9 +2,12 @@ package main
import (
"encoding/json"
"errors"
"strings"
"testing"
"mesh-delivery-builder/internal/build"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
)
@@ -30,3 +33,24 @@ func TestNewestIsReadFromTheCatalogHalfOfEachWatchedBranch(t *testing.T) {
t.Fatalf("newest = %v, %v, want each branch's entry fingerprint whole", got, err)
}
}
// A build seat's steps are read in its holder's state, the holder found where it is read (design 47 G A-128); a seat with no
// holder found reads as an error.
func TestABuildSeatsStepsAreReadInItsHoldersState(t *testing.T) {
var named []string
steps := stepsOfHolder(func(seat string) (string, error) {
if seat == "node-build-go" {
return "mesh-build-go", nil
}
return "", errors.New("no holder")
}, func(name string) build.StateRead {
named = append(named, name)
return build.StateRead{Keys: func() ([]string, error) { return []string{"k"}, nil }}
})
if ks, err := steps("node-build-go").Keys(); err != nil || len(ks) != 1 || len(named) != 1 || named[0] != "mesh-build-go.steps" {
t.Errorf("read %v (keys %v, %v); want the holder's mesh-build-go.steps", named, ks, err)
}
if _, err := steps("node-build-node").Keys(); err == nil || !strings.Contains(err.Error(), "node-build-node") {
t.Errorf("a seat with no holder reads %v; want an error naming it", err)
}
}
@@ -293,6 +293,22 @@ func askKept(s stdio.KeptState, state, bucket, what string, bound time.Duration,
}
}
// stepsOfHolder reads a build seat's steps in the state of the seat's holder (`<holder>.steps`), the holder found where it is
// read by mesh-sdk's seatlist helper (design 47 G A-128, A-126), as the manifest's `seat:<seat>.steps` reads name it; a seat
// whose holder cannot be found reads as an error naming it.
func stepsOfHolder(holder func(seat string) (string, error), read func(name string) build.StateRead) func(seat string) build.StateRead {
return func(seat string) build.StateRead {
module, err := holder(seat)
if err != nil {
fail := fmt.Errorf("the holder of %s is not found: %w", seat, err)
return build.StateRead{Keys: func() ([]string, error) { return nil, fail },
Get: func(string) (json.RawMessage, time.Time, error) { return nil, time.Time{}, fail },
Written: func(string) time.Time { return time.Time{} }}
}
return read(module + ".steps")
}
}
func read(s stdio.KeptState) build.StateRead {
return build.StateRead{Keys: s.Keys, Get: func(key string) (json.RawMessage, time.Time, error) {
e, err := s.Get(key)
@@ -331,7 +347,7 @@ const (
func main() {
reasks := stdio.State(build.ReAsksState)
r := build.StuckReader{Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[mesh-delivery-builder-tools] "+f+"\n", a...) }, Builds: read(stdio.State("builds")), ReAsks: read(reasks), StepsOf: func(seat string) build.StateRead { return read(stdio.State(seat + ".steps")) },
r := build.StuckReader{Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[mesh-delivery-builder-tools] "+f+"\n", a...) }, Builds: read(stdio.State("builds")), ReAsks: read(reasks), StepsOf: stepsOfHolder(func(seat string) (string, error) { return seatlist.HolderOf(stdio.Ask, seat) }, func(name string) build.StateRead { return read(stdio.State(name)) }),
Hearing: read(stdio.State(buildrun.HearingState))}
put := func(key string, value []byte) error {
_, err := reasks.Put(key, json.RawMessage(value))
@@ -26,7 +26,7 @@ func TestTheNamesAreTheManifests(t *testing.T) {
t.Fatal(err)
}
for _, read := range []string{
"node-build-go.steps", "node-build-image.steps", "node-build-node.steps",
"seat:node-build-go.steps", "seat:node-build-image.steps", "seat:node-build-node.steps",
} {
if !slices.Contains(m.Reads, read) {
t.Errorf("the builder reads %s, its manifest's reads are %v", read, m.Reads)
@@ -119,8 +119,9 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
// 1887/A4c: the builder chooses no node, so it reads neither presence nor have, invokes no build module's tool and
// holds no capacity; it puts asks on the three build seats and reads their steps (ADR 0525 §1, §4, §5). Whether a
// node's machine is heard it asks the watchdogs seat's presence (issue 1966, ADR 0596 §2, ADR 0668 §3).
if want := []string{"node-build-go.steps", "node-build-image.steps", "node-build-node.steps",
"seat:mesh-module-assigner.assignments"}; !reflect.DeepEqual(sorted(m.Reads), want) {
// Each build seat's steps are read in seat form, its holder found where it is read (A-116, A-128).
if want := []string{"seat:mesh-module-assigner.assignments", "seat:node-build-go.steps", "seat:node-build-image.steps",
"seat:node-build-node.steps"}; !reflect.DeepEqual(sorted(m.Reads), want) {
t.Errorf("reads = %v, want %v", m.Reads, want)
}
if want := []string{"node-build-go", "node-build-image", "node-build-node", "operator-channel"}; !reflect.DeepEqual(sorted(m.Uses), want) {
+4 -4
View File
@@ -155,10 +155,10 @@
{ "name": "build-log", "max-age-days": 30, "max-mib": 3072 }
],
"reads": [
"node-build-go.steps",
"node-build-image.steps",
"node-build-node.steps",
"seat:mesh-module-assigner.assignments"
"seat:mesh-module-assigner.assignments",
"seat:node-build-go.steps",
"seat:node-build-image.steps",
"seat:node-build-node.steps"
],
"uses": [
"node-build-go",