Answer the node seat's packages from the engine's own key alone

The controller's declared-packages mirror ended with the controller (issue 1126) and is deleted after group B's
delivery, so the verb no longer reads or compares it.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 11:22:54 +01:00
parent ff4dc71ce4
commit 0d1bbe967d
3 changed files with 24 additions and 54 deletions
+5 -10
View File
@@ -333,8 +333,8 @@ func composeFrom(current *agentNow, composer *atomic.Pointer[selfcompose.Compose
// packagesReadFor bounds the verb packages' reads and its write.
const packagesReadFor = 20 * time.Second
// packagesFrom answers the verb packages (novox/hq ADR 0565 §6, §15): this node's key of the engine's state packages
// beside the controller's mirror's key for it and their difference; with write, the key written again from what the
// packagesFrom answers the verb packages (novox/hq ADR 0565 §6, §15): this node's key of the engine's state packages;
// with write, the key written again from what the
// composer that runs now handed, refused in words while the node composes none of its own.
func packagesFrom(node string, session *link.Session, composer *atomic.Pointer[selfcompose.Composer]) func(write bool) (any, error) {
return func(write bool) (any, error) {
@@ -344,8 +344,8 @@ func packagesFrom(node string, session *link.Session, composer *atomic.Pointer[s
if write {
c := composer.Load()
if c == nil {
return nil, errors.New("this node composes none of its own modules now (it applies the controller's " +
"compositions, or its node agent is starting), so it has no packages of its own to write")
return nil, errors.New("this node composes none of its own modules now (its node agent is starting), " +
"so it has no packages of its own to write")
}
w, err := c.WritePackages(ctx)
if err != nil {
@@ -354,12 +354,7 @@ func packagesFrom(node string, session *link.Session, composer *atomic.Pointer[s
wrote = &w
}
key, keyErr := link.EnginePackages{Session: session}.Get(ctx, node)
var mirror []byte
h, mirrorErr := session.KVHistory(ctx, link.MirrorPackagesBucket, node)
if mirrorErr == nil && len(h) > 0 && !h[len(h)-1].Deleted {
mirror = h[len(h)-1].Value
}
v := link.PackagesAnswer(node, key, mirror, keyErr, mirrorErr)
v := link.PackagesAnswer(node, key, keyErr)
v.Wrote = wrote
return v, nil
}
+13 -35
View File
@@ -17,10 +17,6 @@ import (
// mesh-host.packages (delivery.EnginePackagesRead).
const PackagesBucket = "mesh-host_" + delivery.EnginePackagesState
// MirrorPackagesBucket is the controller's declared-packages mirror (ADR 0507 §5), which the engine reads its own node's
// key of until the lease write, to compare (the node seat's verb packages).
const MirrorPackagesBucket = "mesh-controller_declared-packages"
// packagesStore binds PackagesBucket over the link open now: bound, never created.
func (l *natsLink) packagesStore() (nats.KeyValue, error) {
kv, err := l.js.KeyValue(PackagesBucket)
@@ -83,51 +79,33 @@ func (p EnginePackages) Put(_ context.Context, key string, value []byte) error {
return err
}
// PackagesView is the node seat's verb packages' answer (ADR 0565 §6): this node's key of the engine's state packages,
// and until the lease write the controller's mirror's key for the node and every package they hold differently.
// PackagesView is the node seat's verb packages' answer (ADR 0565 §6): this node's key of the engine's state packages.
// The controller's mirror it was once compared with ended with the controller (issue 1126).
type PackagesView struct {
Node string `json:"node"`
// Key is the node's key as the bus holds it (Source); null when the engine has written none.
Source string `json:"source"`
Key *delivery.DeclaredPackages `json:"key"`
KeyError string `json:"key_error,omitempty"`
// Mirror is the controller's mirror's key for the node (MirrorSource); null when it holds none or cannot be read.
MirrorSource string `json:"mirror_source"`
Mirror *delivery.DeclaredPackages `json:"mirror"`
MirrorError string `json:"mirror_error,omitempty"`
// Differences is every package the two hold differently, read only when both are; Agree when there is none.
Differences []delivery.PackageDifference `json:"differences"`
Agree bool `json:"agree"`
// Wrote says, for write, whether the key was written again (false: it already held what the engine composed).
Wrote *bool `json:"wrote,omitempty"`
Note string `json:"note,omitempty"`
}
// PackagesAnswer is the verb's answer from the two keys as read (nil: no key), each with its read's error.
func PackagesAnswer(node string, key, mirror []byte, keyErr, mirrorErr error) PackagesView {
v := PackagesView{Node: node, Source: delivery.EnginePackagesRead, MirrorSource: "mesh-controller.declared-packages",
Differences: []delivery.PackageDifference{}}
read := func(raw []byte, err error, into **delivery.DeclaredPackages, said *string) {
if err == nil && raw != nil {
var d delivery.DeclaredPackages
if err = json.Unmarshal(raw, &d); err == nil {
*into = &d
}
}
if err != nil {
*said = err.Error()
// PackagesAnswer is the verb's answer from the key as read (nil: no key), with its read's error.
func PackagesAnswer(node string, key []byte, keyErr error) PackagesView {
v := PackagesView{Node: node, Source: delivery.EnginePackagesRead}
if keyErr == nil && key != nil {
var d delivery.DeclaredPackages
if keyErr = json.Unmarshal(key, &d); keyErr == nil {
v.Key = &d
}
}
read(key, keyErr, &v.Key, &v.KeyError)
read(mirror, mirrorErr, &v.Mirror, &v.MirrorError)
switch {
case v.Key != nil && v.Mirror != nil:
v.Differences = delivery.DiffDeclaredPackages(*v.Key, *v.Mirror)
v.Agree = len(v.Differences) == 0
case v.Key == nil && v.KeyError == "":
v.Note = "the engine has written no key for this node yet: pacman reads the controller's mirror for it"
case v.Mirror == nil && v.MirrorError == "":
v.Note = "the controller's mirror holds no key for this node: nothing to compare"
case keyErr != nil:
v.KeyError = keyErr.Error()
case v.Key == nil:
v.Note = "the engine has written no key for this node yet"
}
return v
}
+6 -9
View File
@@ -241,15 +241,13 @@ func TestALinkThatHearsHandOversIsGivenTheQueues(t *testing.T) {
return map[string]any{"module": module, "digest": digest, "diff": diff}, nil
},
Compose: func(module string) (any, error) { return map[string]any{"composed": true, "retried": module}, nil },
// ADR 0565 §6, §15: the node's key beside the controller's mirror's, the mirror's from its writer's test.
// ADR 0565 §6, §15: the node's key of the engine's state packages.
Packages: func(write bool) (any, error) {
key := []byte(`{"node":"n1","packages":[{"package":"curl","modules":["backup","web"],"absent":false}]}`)
mirror := []byte(`{"node":"n1","packages":[{"package":"curl","modules":["backup","web"],"absent":false},` +
`{"package":"nano","modules":["web"],"absent":true}]}`)
if !write {
return PackagesAnswer("n1", key, mirror, nil, nil), nil
return PackagesAnswer("n1", key, nil), nil
}
v := PackagesAnswer("n1", nil, nil, nil, errors.New("not granted"))
v := PackagesAnswer("n1", nil, nil)
wrote := true
v.Wrote = &wrote
return v, nil
@@ -265,11 +263,10 @@ func TestALinkThatHearsHandOversIsGivenTheQueues(t *testing.T) {
// The recovery verb compose (issue 774) makes a pass now, with a module's apply tried again when named.
{"compose", EngineToolSubject("compose", "n1"), `"composed":true,"retried":""`},
{"compose", EngineToolSubject("compose", "n1") + " " + `{"module":"route-proxy"}`, `"retried":"route-proxy"`},
// The read verb packages (ADR 0565 §6) names both sources and their difference; with write, what it wrote, and a
// key the engine has not written, or a mirror it may not read, is said and not an error.
{"packages", EngineToolSubject("packages", "n1"), `"differences":[{"package":"nano","absent":true,"engine":[],"mirror":["web"]}],"agree":false`},
// The read verb packages (ADR 0565 §6) names its source; with write, what it wrote, and a key the engine has not
// written is said and not an error.
{"packages", EngineToolSubject("packages", "n1"), `"source":"mesh-host.packages","key":{"node":"n1"`},
{"packages", EngineToolSubject("packages", "n1") + " " + `{"write":true}`, `"mirror":null,"mirror_error":"not granted","differences":[],"agree":false,"wrote":true`},
{"packages", EngineToolSubject("packages", "n1") + " " + `{"write":true}`, `"key":null,"wrote":true`},
{"packages", EngineToolSubject("packages", "n1") + " " + `{"write":"true"}`, `"note":"the engine has written no key for this node yet`},
{"unserved", "mesh.seat.node-engine.call.unserved.n1.x", `"error":"the node seat node-engine serves no verb \"unserved\""`},
} {