Review round 2: a held module's object reads come from its running manifest; one with none keeps the list in force, said; indexer_judge asks the engine for the running build again (issue 774)
mesh/delivery-group group feat/l3a-waits rejected: composed together: the checker's verdict at the members' heads (de2cdacb9cde): shanks
mesh/check failed (3): shanks: novox/mesh-sdk#163 does not merge into the trunk: files in conflict: go/deli…; and 2 more | 83f7a7612d19 on c4470f42dba9
mesh/delivery closed: the pull request closed unmerged

This commit is contained in:
2026-11-03 13:07:11 +01:00
parent 1fe1beee58
commit 83f7a7612d
5 changed files with 60 additions and 1 deletions
@@ -362,6 +362,8 @@ func (ix *Indexer) JudgeAgain(ctx context.Context, module string) TakenIn {
out.Answer = "the indexer holds no manifest of " + module
return out
}
// The build this program runs is asked again, so a start pass that missed the engine's answer is recovered without a restart.
ix.learnOwnBuild(ctx)
if _, err := ix.JudgeAll(ctx, nil); err != nil {
out.Answer = "the manifests could not be judged: " + err.Error()
return out
@@ -175,7 +175,15 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
if rows, _ := ListVerdicts(keys, get, "", true); len(rows) <= len(keys)-3 || rows[0].Module > rows[len(rows)-1].Module {
t.Fatalf("indexer_verdicts, all: %d rows of %d verdicts", len(rows), len(keys))
}
// indexer_judge asks the engine for the running build again: a start pass that missed it is recovered (the trunk's value of
// the indexer is the newer one, so the field waits; asked and answered with that same build it is refused).
ix.ownFP = ""
ix.OwnBuild = func(context.Context) (string, error) { return "src2:" + strings.Repeat("a", 64), nil }
if got := ix.JudgeAgain(ctx, "later"); !got.Stored || !strings.Contains(got.Answer, "refused") || ix.ownFP == "" {
t.Fatalf("indexer_judge did not learn the running build: %+v", got)
}
ix.ownFP = ownFP
ix.OwnBuild = nil
if got := ix.JudgeAgain(ctx, "later"); !got.Stored || !strings.Contains(got.Answer, "waits") || !strings.Contains(got.Answer, "a-field-from-later") {
t.Fatalf("indexer_judge: %+v", got)
}
+33
View File
@@ -680,6 +680,22 @@ func TestGrants(t *testing.T) {
if slices.Contains(web.Publish, "$JS.API.DIRECT.GET.KV_mesh-knowledge_sections.>") {
t.Error("the object bucket's read is granted as a state's")
}
// ADR 0601 clause 3: the owner's trunk manifest now waits for a field; its running build's manifest still declares the
// bucket, so web keeps its object grant.
fp := "src2:" + strings.Repeat("cd", 32)
b.put(grants.ReadModules, "mesh-knowledge", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32),
Manifest: delivery.ManifestDigest([]byte(`{"module":"mesh-knowledge","version":"2"}`)), Repository: "novox/x", Commit: "c",
Verdict: delivery.VerdictRefused, Problems: []string{"it waits"}, Unknown: []string{"core-credentials"}})
b.put(grants.ReadHave, "a.mesh-knowledge", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, FP: fp})
heldKey, _ := delivery.BuilderBuildKey("mesh-knowledge", fp)
b.put(grants.ReadBuilds, heldKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": delivery.ManifestDigest([]byte(knowledge))})
if heldWeb := (func() grants.User {
us := shadowOf(t, b)
return us[slices.IndexFunc(us, func(u grants.User) bool { return u.User == "a.web" })]
})(); !slices.Contains(heldWeb.Publish, read) {
t.Errorf("a reader of a held module's object store lost its grant: %v", heldWeb.Publish)
}
b.manifestOf("mesh-knowledge", knowledge) // accepted again for the rest of the row
// ADR 0592 §6, §25: the bucket past 75 percent of its cap is said, in one warning, and cleared once below; the history
// stream at 10 percent is not.
var said []asks.Condition
@@ -2736,6 +2752,11 @@ func TestGrants(t *testing.T) {
}
b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}})
// mod-r runs a build whose manifest is the accepted one: the held module's seats and streams are composed from it.
runFP := "src2:" + strings.Repeat("cd", 32)
b.put(grants.ReadHave, "b.mod-r", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, FP: runFP})
runKey, _ := delivery.BuilderBuildKey("mod-r", runFP)
b.put(grants.ReadBuilds, runKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": delivery.ManifestDigest([]byte(ctl))})
p := partOn(b)
p.Objects = &fakeObjects{}
var said []asks.Condition
@@ -2777,6 +2798,18 @@ func TestGrants(t *testing.T) {
t.Errorf("%s: the kept user was not said: %+v", held.name, last)
}
}
// A held module that runs no build the part can read declares nothing to anyone: the list in force is kept, said.
delete(b.states[grants.ReadHave], "b.mod-r")
b.put(grants.ReadModules, "mod-r", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32),
Manifest: delivery.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c", Verdict: delivery.VerdictRefused,
Problems: []string{"it is held"}, Unknown: []string{"core-credentials"}})
b.put(grants.ReadAccounts, "a.mod-x", map[string]any{"user": "a.mod-x", "hash": "$2a$10$gen9", "generation": 9, "fingerprint": "sha256:9"})
wasList, _, _ := p.Sealed()
res := p.Compose()
nowList, _, _ := p.Sealed()
if nowList.Digest != wasList.Digest || res.Sealed == nil || !strings.Contains(res.Sealed.Why, "running manifest of mod-r") {
t.Errorf("a held module with no running manifest did not keep the list in force: %+v", res.Sealed)
}
// Accepted again: the users compose from the manifest and the condition clears.
b.manifestOf("mod-r", ctl)
if p.Compose(); said[len(said)-1].State != asks.StateCleared {
@@ -445,6 +445,22 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
in.Gaps = append(in.Gaps, Gap{Input: "wanted manifests", Why: "no build record names the manifest at the wanted fingerprint, so the trunk's is used: " + strings.Join(fromTrunk, ", ")})
}
in.Held = heldManifests(r, in, cache)
// A held module placed on a node whose running manifest is not found declares no seat, stream or object bucket to
// anyone: composing past it would drop the grants of those who use them, so the list in force is kept (ADR 0601
// clause 3), and the gap says which module and why.
for _, g := range in.Refused {
module, ok := strings.CutPrefix(g.Input, "manifest of ")
if _, found := in.Held[module]; !ok || found {
continue
}
for _, assigned := range in.Assigned {
if slices.Contains(assigned, module) {
in.Gaps = append(in.Gaps, Gap{Input: "running manifest of " + module, Failed: true,
Why: "its manifest is held and no running build's manifest is found, so the seats and streams it declares cannot be composed: the list in force is kept"})
break
}
}
}
in.Hashes = readHashes(r, &in.Gaps)
// The seat mesh-enrolment's keys are every token since the controller's end (issue 1126).
in.Enrolling, _ = readEnrolling(r, &in.Gaps)
@@ -81,7 +81,7 @@ func records(in Inputs, union bool) (buscompose.Records, []Gap) {
d.KeyedReads = append(d.KeyedReads, buscompose.KeyedRead{Read: sr.Read, Key: sr.Key})
}
d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, declared, node, values, d.Consumes)
d.Reads, d.ReadsObjects, d.ReadsRefused = readsOfObjects(m.Module, node, d.Reads, d.ReadsRefused, in.Manifests, values)
d.Reads, d.ReadsObjects, d.ReadsRefused = readsOfObjects(m.Module, node, d.Reads, d.ReadsRefused, declared, values)
return d
}
d := declare(m)