S1 grants-unread-catalog-no-hold: an entry not answered while the catalog half is unread is no failed read, so the list composes from the trunk and the want's entry

Builds: design 47 G A-363 (G-22-lead-answers-2.md), start-waits.md S1

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-13 20:41:44 +01:00
parent bac26c8a28
commit a118867b43
3 changed files with 69 additions and 2 deletions
+64
View File
@@ -5207,3 +5207,67 @@ func (h heldHalf) Revision() (meshwants.AssignmentRevision, bool) {
}
return *h.rev, true
}
// TestPassBeforeTheCatalogHalfComposesFromTheTrunkToo is design 47 G A-363: while the catalog half is unread, each account is composed
// from the manifest the indexer's `modules` first view names for its module (the trunk's) in union with what its want's entry gives, never
// from the list in force alone, so the first parts of a new build get their subjects before any of them serves.
func TestPassBeforeTheCatalogHalfComposesFromTheTrunkToo(t *testing.T) {
older := strings.Replace(xManifest, `{"name":"kept","history":3}`, `{"name":"older"}`, 1)
rows := []struct {
name string
entry bool // the want's entry is answered (the older build); else it is not answered
wantsOld bool
}{
{"the want's entry is not answered", false, false},
{"the want names an entry that is asked", false, false},
{"the want's entry is an older build", true, true},
}
for _, row := range rows {
t.Run(row.name, func(t *testing.T) {
b := fixture()
if strings.Contains(row.name, "asked") {
b.put(grants.ReadWants, "want.a.mod-x", meshwantsclient.Want{V: keys.Version, Fp: "src2:" + strings.Repeat("cd", 32), Composed: "sha256:" + strings.Repeat("ef", 32)})
b.put(grants.ReadHave, "a.mod-x", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK,
Fp: "src2:" + strings.Repeat("cd", 32)})
b.entriesAsked = true
}
if row.entry {
digest := meshcatalogclient.ManifestDigest([]byte(older))
b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: "mod-x", Manifest: older})
fp := "src2:" + strings.Repeat("cd", 32)
b.put(grants.ReadWants, "want.a.mod-x", meshwantsclient.Want{V: keys.Version, Fp: fp, Composed: "sha256:" + strings.Repeat("ef", 32)})
b.entry(fp, digest)
}
b.mu.Lock()
b.catalogWaited = []string{"the catalog half of the branch main"}
b.mu.Unlock()
in := grants.ReadInputs(b, &grants.Manifests{})
if in.CatalogUnread == "" {
t.Fatal("the catalog half is not unread")
}
// An entry asked and not answered is no failed read while the catalog half is unread: the trunk's manifest composes the
// account and the list is not held for it.
if f := in.ReadFailed(); len(f) > 0 {
t.Errorf("the unread catalog half's asked entry is a failed read: %v", f)
}
recs, _ := grants.ListRecords(in)
users, err := grants.ComposeUsers(recs)
if err != nil {
t.Fatal(err)
}
var got []string
for _, u := range users {
if u.User == "a.mod-x" {
got = append(got, u.Publish...)
got = append(got, u.Subscribe...)
}
}
if !slices.ContainsFunc(got, func(s string) bool { return strings.Contains(s, "kept") }) {
t.Errorf("a.mod-x is not granted the trunk manifest's state kept: %v", got)
}
if row.wantsOld && !slices.ContainsFunc(got, func(s string) bool { return strings.Contains(s, "older") }) {
t.Errorf("a.mod-x is not granted its want's entry's state older: %v", got)
}
})
}
}
@@ -204,7 +204,7 @@ func catalogWaiting(r Reader) string {
return ""
}
slices.Sort(w)
return fmt.Sprintf("the first views of the catalog half are not in (%v), so each account is composed from its want's entry by fingerprint and the list in force is kept", w)
return fmt.Sprintf("the first views of the catalog half are not in (%v), so each account is composed from the trunk's manifest of its module in union with its want's entry by fingerprint, and the list in force is kept in union", w)
}
// **The grants part reads the assignment half** (design 46 approval mark §6, design 47 G §195, lead answer A-203): the view of
@@ -836,7 +836,10 @@ func runningByHaves(r Reader, in *Inputs, cache *Manifests, into map[string]cata
if err != nil {
if state == meshcatalogclient.EntryAsked {
in.HadFailed[node+"."+module] = "the catalog half's entry of " + h.Fp + " is asked"
in.Gaps = append(in.Gaps, Gap{Input: "catalog entry " + h.Fp, Why: "asked and not answered yet, so the manifest of the build " + node + "." + module + " runs is unknown: " + err.Error(), Failed: true})
// While the catalog half is unread an entry cannot be answered yet: that is no failed read, and the account is composed
// from the trunk's manifest of its module (the indexer's `modules` view) in union with what its entry gives, so the list is
// not held to the list in force alone (design 47 G, A-363).
in.Gaps = append(in.Gaps, Gap{Input: "catalog entry " + h.Fp, Why: "asked and not answered yet, so the manifest of the build " + node + "." + module + " runs is unknown: " + err.Error(), Failed: in.CatalogUnread == ""})
}
fromTrunk = append(fromTrunk, node+"."+module)
continue