The node says why it composes nothing of a want, and hands again what its agent waits for (issue 1966)

Every pass names each wanted module it made no composition of, with why (Pass.Unmade, the verb compose, one journal line per
change); a want not taken is said; the agent says once per want that it waits for the composer and why, and tells it, so a
composition the composer believed handed is handed again.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-09 08:58:33 +01:00
parent 9b55bcb127
commit 5f37a43b91
7 changed files with 266 additions and 7 deletions
+56
View File
@@ -2090,3 +2090,59 @@ func (b *patternBus) putAssigned(vals map[string][]byte, rev uint64) {
b.put(buckets.Assignments, k, vals[k], rev)
}
}
// Issue 1966 (novox, 2026-11-09): a want the composer made nothing of was said nowhere, and a composition it believed
// handed that the agent never held stayed unhanded. Every pass names each wanted module it composed nothing of, with why,
// in Pass.Unmade and as a journal line; a want not taken is said; and the agent's word that it waits hands the
// composition again.
func TestAWantTheNodeMakesNothingOfSaysWhyAndAWaitingAgentIsHandedItAgain(t *testing.T) {
b := newFakeBus()
fp := meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64)
gfp := meshcatalogclient.SourcePrefix2 + strings.Repeat("b", 64)
digest := meshcatalogclient.ManifestDigest([]byte(lemurs))
man, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "lemurs", Manifest: lemurs})
key, _ := meshcatalogclient.ManifestsKey(digest)
b.kv[buckets.Manifests+"/"+key] = man
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := New(b.env())
out, err := c.Watch(ctx, "n1.>")
if err != nil {
t.Fatal(err)
}
place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"},
Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}})
have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}})
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"})
want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true})
ghost, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: gfp, Self: true})
nofp, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Self: true})
b.putAssigned(t, place)
b.put(t, buckets.Have, "n1.node", have)
b.put(t, buckets.Modules, "lemurs", mod)
b.put(t, buckets.Wants, "want.n1.lemurs", want)
b.put(t, buckets.Wants, "want.n1.ghost", ghost)
b.put(t, buckets.Wants, "want.n1.nofp", nofp)
for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} {
b.send(bucket, agent.Update{Caught: true})
}
for !next(t, out).Caught {
}
why := map[string]string{"ghost": c.WhyUnmade("ghost"), "nofp": c.WhyUnmade("nofp"), "lemurs": c.WhyUnmade("lemurs")}
if !strings.Contains(why["ghost"], "places no ghost") || !strings.Contains(why["nofp"], "names no fingerprint") || why["lemurs"] != "" {
t.Errorf("the pass says it composed nothing of %q", why)
}
b.mu.Lock()
said := strings.Join(b.said, "\n")
b.mu.Unlock()
if !strings.Contains(said, "the node composes nothing of ghost: ") {
t.Errorf("the journal does not say why ghost is not composed: %q", said)
}
// The agent waits for what the node believes it handed: handed again, and a wait for what was never handed does nothing.
c.Waiting("ghost", gfp)
quiet(t, out)
c.Waiting("lemurs", fp)
if u := next(t, out); !strings.Contains(u.Key, "lemurs") {
t.Errorf("handed %s, want lemurs's composition again", u.Key)
}
}