Keep the packages this node's compositions declare in the engine's own key, and answer them on the node seat (ADR 0565 clauses 6 and 15, issue 1953)

pacman still reads the controller's mirror because no engine wrote its key: the engine
now keeps each handed composition's package resources and, after a pass that handed
something new, writes mesh-host_packages.<node> only when the value differs, owed after
the growing bound when refused. The manifest declares the state so the grants part
creates it. The verb packages answers the key beside the mirror's key for the node and
their difference, and with write writes the key again from what was composed. H8's
integration test proves the write under the grant the grants part composes.

Needs mesh-sdk feat/node-packages (0ef7c0c) for DeclaredPackagesOf and the engine's read of the
mirror (go.mod pin left for the lead: the SDK commit is not pushed).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 16:09:13 +01:00
parent bc960c51db
commit e61141f596
13 changed files with 467 additions and 11 deletions
+50
View File
@@ -177,6 +177,56 @@ func TestTheEngineKeepsEachPlacedModulesDataItems(t *testing.T) {
}
})
}
// ADR 0565 §6 (1953): the node's key of the engine's state packages is written after a pass that handed something new
// when the packages its compositions declare differ, and only then: a composition adding a package writes it once,
// one that changes no package writes nothing; a write refused is owed after the growing bound; the verb's write reads
// the key again, so a key lost on the bus is written back. Another node's key is never touched.
pkgStore := &fakeData{kv: map[string][]byte{"n2": []byte(`{"node":"n2","packages":[]}`)}}
pc := New(Env{Node: "n1", Packages: pkgStore})
web := func(pkgs ...string) delivery.Composed {
comp := delivery.Composed{Node: "n1", Module: "web"}
for _, p := range pkgs {
comp.Resources = append(comp.Resources, []byte(`{"id":"`+p+`","type":"package","package":"`+p+`"}`))
}
comp.Resources = append(comp.Resources, []byte(`{"id":"conf","type":"file","path":"/etc/web.conf"}`))
return packagesOf(comp)
}
for _, step := range []struct {
name string
comp delivery.Composed
fail error
puts int
owed bool
holds string
}{
{name: "the run's first pass writes the key", comp: web("curl"), puts: 1,
holds: `{"node":"n1","packages":[{"package":"curl","modules":["web"],"absent":false}]}`},
{name: "a pass that changes no package writes nothing", comp: web("curl"), puts: 1},
{name: "a refused write is owed after the bound", comp: web("curl", "jq"), fail: errors.New("no grant yet"), puts: 1, owed: true},
{name: "a composition adding a package writes the key once", comp: web("curl", "jq"), puts: 2,
holds: `{"node":"n1","packages":[{"package":"curl","modules":["web"],"absent":false},{"package":"jq","modules":["web"],"absent":false}]}`},
} {
pkgStore.mu.Lock()
pkgStore.fail = step.fail
pkgStore.mu.Unlock()
pc.paid()
pc.mu.Lock()
pc.pkgs, pc.pkgsComposed, pc.pkgsDue = map[string]delivery.Composed{webKey: step.comp}, true, true
pc.mu.Unlock()
pc.publishPackages(context.Background())
if len(pkgStore.puts) != step.puts || pc.owed.Load() != step.owed ||
(step.holds != "" && string(pkgStore.kv["n1"]) != step.holds) || string(pkgStore.kv["n2"]) != `{"node":"n2","packages":[]}` {
t.Errorf("%s: %d writes, owed %v, n1 holds %s", step.name, len(pkgStore.puts), pc.owed.Load(), pkgStore.kv["n1"])
}
}
delete(pkgStore.kv, "n1")
if wrote, err := pc.WritePackages(context.Background()); !wrote || err != nil || pkgStore.kv["n1"] == nil {
t.Errorf("the verb's write of a lost key wrote %v (%v)", wrote, err)
}
if wrote, _ := pc.WritePackages(context.Background()); wrote {
t.Error("the verb's write of a key holding what was composed wrote it again")
}
// The items are worked out from what the pass read: a done build is read over the bus once, not again for the data
// items after the pass (a module in a running build is read again, since it may land).
b := newFakeBus()