Grants read the have's build for memberships and holdings, and a failed read of it holds the pass back (issue 1966, review round 1)

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-08 17:00:15 +01:00
parent 77862abb12
commit 563a624a6d
4 changed files with 63 additions and 2 deletions
+49
View File
@@ -4042,3 +4042,52 @@ func TestMembershipKeepsWhatTheRunningBuildReads(t *testing.T) {
})
}
}
// A pass reads each machine's have for the build it runs (issue 1966): its membership keeps what that older build reads, a
// have or build that cannot be read holds the pass back, and the seats held are the ones that build claims (ADR 0660 §2).
func TestPassComposesMembershipFromTheHaveBuild(t *testing.T) {
const old = `{"module":"mod-y","version":"1","own-secrets":{"broker":"/var/lib/y/broker"},"reads":["mod-x.kept","mod-x.old"]}`
runs := func(b *fakeBus) {
b.manifestOf("mod-y", yManifest)
digest := meshcatalogclient.ManifestDigest([]byte(old))
b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: "mod-y", Manifest: old})
fp := "src2:" + strings.Repeat("cd", 32)
b.put(grants.ReadHave, "b.mod-y", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, Fp: fp})
key, _ := builds.BuilderBuildKey("mod-y", fp)
b.put(grants.ReadBuilds, key, map[string]any{"v": keys.Version, "state": "done", "manifest": digest})
}
t.Run("the published membership keeps a read of the build the have names", func(t *testing.T) {
b := fixture()
runs(b)
partOn(b).Pass()
if got := string(b.own[grants.StateMemberships]["b.mod-y"]); !strings.Contains(got, "mod-x.old") || !strings.Contains(got, "mod-x.kept") {
t.Errorf("b.mod-y's membership lacks the running build's read: %s", got)
}
})
t.Run("a build that cannot be read is a failed read", func(t *testing.T) {
b := fixture()
runs(b)
b.refused[grants.ReadBuilds] = true
if f := grants.ReadInputs(b, &grants.Manifests{}).ReadFailed(); !strings.Contains(strings.Join(f, "\n"), grants.ReadBuilds) {
t.Errorf("a build read refused is not a failed read: %v", f)
}
})
t.Run("the seats held are the have build's claims, then the want's, then the trunk's", func(t *testing.T) {
claims := catalogue.Manifest{Module: "m", Version: "1", Claims: []catalogue.Claim{{Name: "mesh-catalog", Scope: catalogue.ScopeMesh}}}
bare := catalogue.Manifest{Module: "m", Version: "1"}
for name, row := range map[string]struct {
had, running map[string]catalogue.Manifest
held bool
}{
"trunk alone": {nil, nil, false},
"have claims": {map[string]catalogue.Manifest{"a.m": claims}, map[string]catalogue.Manifest{"a.m": bare}, true},
"want claims": {nil, map[string]catalogue.Manifest{"a.m": claims}, true},
"have is bare": {map[string]catalogue.Manifest{"a.m": bare}, map[string]catalogue.Manifest{"a.m": claims}, false},
} {
in := grants.Inputs{Nodes: []string{"a"}, Assigned: map[string][]string{"a": {"m"}}, Manifests: map[string]catalogue.Manifest{"m": bare}, Had: row.had, Running: row.running}
if got := len(grants.HoldingsOf(in).Claimed) > 0; got != row.held {
t.Errorf("%s: claimed %v, want %v", name, got, row.held)
}
}
})
}
@@ -754,6 +754,9 @@ func runningByHaves(r Reader, in *Inputs, cache *Manifests, into map[string]cata
for node, modules := range in.Assigned {
for _, module := range modules {
raw, found, err := r.Get(ReadHave, node+"."+module)
if err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadHave + " " + node + "." + module, Why: "cannot be read, so the build it runs is unknown: " + err.Error(), Failed: true})
}
if err != nil || !found {
fromTrunk = append(fromTrunk, node+"."+module)
continue
@@ -769,6 +772,9 @@ func runningByHaves(r Reader, in *Inputs, cache *Manifests, into map[string]cata
continue
}
braw, found, err := r.Get(ReadBuilds, bk)
if err != nil {
in.Gaps = append(in.Gaps, Gap{Input: ReadBuilds + " " + bk, Why: "cannot be read, so the manifest of the build " + node + "." + module + " runs is unknown: " + err.Error(), Failed: true})
}
if err != nil || !found {
fromTrunk = append(fromTrunk, node+"."+module)
continue
@@ -206,7 +206,10 @@ func HoldingsOf(in Inputs) Holdings {
}
for node, modules := range in.Assigned {
for _, module := range modules {
m, ok := in.Running[node+"."+module]
m, ok := in.Had[node+"."+module]
if !ok {
m, ok = in.Running[node+"."+module]
}
if !ok {
m, ok = in.Manifests[module]
}
@@ -448,7 +448,10 @@ func holdersAfterPass(in Inputs) ([]catalogue.Held, []Gap) {
var assignments []catalogue.Assignment
for _, node := range slices.Sorted(maps.Keys(in.Assigned)) {
for _, module := range in.Assigned[node] {
m, ok := in.Running[node+"."+module]
m, ok := in.Had[node+"."+module]
if !ok {
m, ok = in.Running[node+"."+module]
}
if !ok {
m, ok = in.Manifests[module]
}