Compose what runs from the haves while the wants view is unanswered (issue 1966, recovery)

Since 16:39 the running assigner cannot answer its wants page within a call's
bound, and the failed wants read held the whole user list, so no new build got
its credential. As the assignments view already does, an unanswered wants view
now composes what runs from each have's fingerprint, is said as a gap that is no
failed read, and keeps the pass to one that only adds (keepsLoaded), so the list
is held only where it would take from a loaded user.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-08 23:29:26 +01:00
parent e7779d2afd
commit e79a546eb7
3 changed files with 28 additions and 2 deletions
+8
View File
@@ -2640,6 +2640,14 @@ func TestGrants(t *testing.T) {
b.put(grants.ReadHave, "b.mod-y", runs)
b.put(grants.ReadHave, "a.mesh-delivery-indexer", runs)
}, ""},
// The wants view unanswered, the assignments answered (issue 1966, the list held from 2026-11-08 16:39): what runs
// comes from the haves, the wants are no failed read, and the pass only adds.
{"the wants view unanswered", func(b *fakeBus, p *grants.Part) {
b.refused[grants.ReadWants] = true
if in := grants.ReadInputs(b, &grants.Manifests{}); in.WantsUnread == "" || len(in.ReadFailed()) != 0 {
t.Errorf("an unanswered wants view is a failed read, or composes no fallback: %q, %v", in.WantsUnread, in.ReadFailed())
}
}, ""},
// The ledger of a held module stays with its module's users, whatever the seat list (held.go).
{"the ledger of a module whose manifest is refused", func(b *fakeBus, p *grants.Part) {
b.refuse("mod-x", ledgerX, "it is held")
+18 -2
View File
@@ -180,6 +180,11 @@ type Inputs struct {
// answering (bootstrap only, issue 1966); empty when the assignments placed them. Such a pass retires nothing
// (ReadFailures) and seals no list dropping a user of the list in force (holdsBack).
ByHaves string
// WantsUnread says why this pass composed what runs from each have's fingerprint, the wants view not answering while
// the assignments view did (bootstrap only, issue 1966: a wants page the running assigner cannot answer in a call's
// bound); empty when the wants were read. As ByHaves, such a pass retires nothing (ReadFailures) and only adds
// (keepsLoaded): the list is held only where it would take a permission from a loaded user (holdsBack).
WantsUnread string
// Held is, by module, the manifest a held module's running build was judged on (its have's fingerprint, the builder's
// build of it): what the seats it defines, the streams others hear from it and its reads are composed from while its
// trunk manifest is refused or waits, so no other principal loses a grant to it (ADR 0601 clause 3).
@@ -519,12 +524,20 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
// What each machine is wanted to run: the want's fingerprint, the builder's build of it, the manifest it built.
var fromTrunk []string
wants := map[string][]byte{}
if in.ByHaves == "" {
_, wantsErr := r.Keys(ReadWants)
switch {
case in.ByHaves == "" && wantsErr == nil:
wants = each(r, ReadWants, &in.Gaps)
// What each machine runs now is its have's build, which the want may already have left (issue 1966): the
// memberships keep what either build reads, calls and consumes.
runningByHaves(r, &in, cache, in.Had)
} else {
case in.ByHaves == "":
// **Bootstrap only** (issue 1966), as the assignments' placement by the haves: while the wants view does not answer,
// what runs is each placed module's have's fingerprint, said as a gap that is no failed read, and the pass only adds.
in.WantsUnread = "the wants view did not answer (" + wantsErr.Error() + "), so what runs is composed from each have's fingerprint"
in.Gaps = append(in.Gaps, Gap{Input: ReadWants, Why: "refused or absent: what runs is composed from each have's fingerprint instead, until it answers (" + wantsErr.Error() + ")"})
fromTrunk = runningByHaves(r, &in, cache, in.Running)
default:
// Placed by the haves, what runs is what each have's fingerprint names, not a want: the assigner's wants are its
// view too, and not read in such a pass.
in.Gaps = append(in.Gaps, Gap{Input: ReadWants, Why: "not read: the modules are placed by the node-engines' haves this pass, so what runs is composed from each have's fingerprint"})
@@ -716,6 +729,9 @@ func (in Inputs) ReadFailures() []string {
if in.ByHaves != "" {
out = append(out, ReadAssignments+": "+in.ByHaves)
}
if in.WantsUnread != "" {
out = append(out, ReadWants+": "+in.WantsUnread)
}
for _, g := range in.Refused {
out = append(out, g.Input+": "+g.Why)
}
@@ -21,6 +21,8 @@ func (in Inputs) keepsLoaded() string {
switch {
case in.ByHaves != "":
return in.ByHaves
case in.WantsUnread != "":
return in.WantsUnread
case in.SeatListUnread:
return "the seat list is unread, so only the bootstrapped seats compose"
}