pacman reads a node's declared packages from the node-engine alone (ADR 0565 clause 6)

The controller's declared-packages mirror ended with its lease: the view no longer falls back to it, the manifest no
longer reads it, and a node whose engine wrote no key is named in the answer (declared_none) instead. The fallback
rows of the view test became the no-key and unreadable-engine cases; no test added.

(issue 774, 774/S7)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 00:12:53 +01:00
parent fc5b849d85
commit 4497f26257
5 changed files with 63 additions and 88 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ that seat (ADR 0207).
`package.changed`. A list over the bus's cap on a value is not written: the key keeps its last value and
`<node>.not-current` says the size and since when, which the health says too. `pacman_packages`,
`pacman_updates`, `pacman_installed` and `pacman_foreign` answer every node from these states, and what no
module declares from the node-engine's `packages` (read as `mesh-host.packages`, the engine's manifest's name), else the controller's `declared-packages` until its lease is written, without asking a machine.
module declares from the node-engine's `packages` (read as `mesh-host.packages`, the engine's manifest's name), without asking a machine; a node whose engine has written no key is named in the answer (`declared_none`) and the controller is not read (ADR 0565 clause 6).
- **Upgrades as runs, one node at a time (ADR 0508).** Every full upgrade and every rollback is a run in the
state `runs` at `<node>.<run>`, written at each step and emitted as it: the package record taken and read back
(`records`, kept `record-days`), the DKMS gate before the transaction (a kernel an installed DKMS module's own
@@ -135,7 +135,7 @@ func TestTheKeeperDeclaresTheModulesStatesAndEvents(t *testing.T) {
}
// The verbs read which packages each node's modules declare; the process reads no other module's state
// (ADR 0507 §5, ADR 0436 §1).
if got := strs(raw["reads"]); !reflect.DeepEqual(got, []string{delivery.EnginePackagesRead, "mesh-controller.declared-packages"}) {
if got := strs(raw["reads"]); !reflect.DeepEqual(got, []string{delivery.EnginePackagesRead}) {
t.Errorf("reads: %v", got)
}
}
+44 -59
View File
@@ -20,18 +20,13 @@ import (
// through the manifest's `reads`.
const DeclaredPackagesState = SourceEngine
// ControllerDeclaredState is the controller's mirror of the same packages (ADR 0507 §5), the per-node fallback for a node
// whose engine has written no key yet, until the controller's lease is written away (ADR 0565 clause 6).
const ControllerDeclaredState = "mesh-controller.declared-packages"
// SourceEngine is the one source a node's declaration is read from, said in the answer: the node-engine's state, read by
// its manifest's name (mesh-host.packages, its bucket mesh-host_packages; mesh-sdk delivery.EnginePackagesRead). The
// controller's mirror of the same packages ended with its lease (ADR 0565 clause 6, issue 774): a node whose engine has
// written no key has no declaration here, and the answer says so.
const SourceEngine = delivery.EnginePackagesRead
// The sources a node's declaration is read from, said in the answer: the node-engine's state, read by its manifest's
// name (mesh-host.packages, its bucket mesh-host_packages; mesh-sdk delivery.EnginePackagesRead), and the mirror.
const (
SourceEngine = delivery.EnginePackagesRead
SourceController = "mesh-controller.declared-packages"
)
// declaredValue is a node's key of `packages`, in mesh-sdk's layout (the controller's mirror unchanged).
// declaredValue is a node's key of `packages`, in mesh-sdk's layout.
type declaredValue = delivery.DeclaredPackages
// nodeInstalled is one node's key of `installed` with its not-current mark.
@@ -118,53 +113,42 @@ func (m *Machine) installedOn(node string) ([]nodeInstalled, error) {
return out, nil
}
// declaredOn is what each node's modules declare present, by node and package, with the declaring modules, and the source
// that answered for each node: its engine's key, else the controller's mirror; a node neither holds is missing. When the
// engine's state cannot be read at all (its bucket is not on the bus yet, or the read is refused) the mirror answers for
// every node and the engine's error is returned as a word to say beside the sources; it is an error only when the mirror
// cannot be read either (ADR 0565 clause 6).
func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, map[string]string, string, error) {
// declaredOn is what each node's modules declare present, by node and package, with the source that answered for each
// node (its engine's key), the nodes whose engine has written none, and a word when the engine's state cannot be read at
// all (its bucket is not on the bus yet, or the read is refused): then no node has a declaration and the word says why.
// Nothing else is read: the controller's mirror ended with its lease (ADR 0565 clause 6).
func (m *Machine) declaredOn(nodes []string) (map[string]map[string][]string, map[string]string, []string, string) {
out := map[string]map[string][]string{}
from := map[string]string{}
engineErr := ""
for _, source := range []string{SourceEngine, SourceController} {
r := m.States(source)
read := map[string]map[string][]string{}
readFrom := map[string]string{}
var failed error
for _, n := range nodes {
if from[n] != "" {
continue
}
var v declaredValue
found, err := readValue(r, n, &v)
if err != nil {
failed = err
break
}
if !found {
continue
}
readFrom[n] = source
read[n] = map[string][]string{}
for _, p := range v.Packages {
if !p.Absent {
read[n][p.Package] = p.Modules
}
}
var none []string
r := m.States(SourceEngine)
for _, n := range nodes {
var v declaredValue
found, err := readValue(r, n, &v)
if err != nil {
return map[string]map[string][]string{}, map[string]string{}, nil, SourceEngine + " cannot be read: " + err.Error()
}
if failed != nil {
if source == SourceEngine {
engineErr = SourceEngine + " cannot be read: " + failed.Error()
continue
}
return nil, nil, "", failed
if !found {
none = append(none, n)
continue
}
for n, d := range read {
out[n], from[n] = d, readFrom[n]
from[n] = SourceEngine
out[n] = map[string][]string{}
for _, p := range v.Packages {
if !p.Absent {
out[n][p.Package] = p.Modules
}
}
}
return out, from, engineErr, nil
return out, from, none, ""
}
// declaredWord says which nodes' engines have written no packages key.
func declaredWord(none []string) string {
if len(none) == 0 {
return ""
}
return "the node-engine has written no packages key for " + strings.Join(none, ", ") + ": no declaration is shown for them"
}
// NodeView is one node in the view.
@@ -178,10 +162,10 @@ type NodeView struct {
Current bool `json:"current"`
NotCurrent *NotCurrent `json:"not_current,omitempty"`
// Undeclared is the packages installed explicitly from a repository that no module assigned to the node
// declares; null when neither the node-engine nor the controller's mirror holds a declaration of the node.
// declares; null when the node-engine holds no declaration of the node.
Undeclared []PackageVersion `json:"undeclared"`
Foreign []PackageVersion `json:"foreign"`
// DeclaredFrom is the source that answered for the node's declaration; empty when neither holds one.
// DeclaredFrom is the source that answered for the node's declaration; empty when the engine holds none.
DeclaredFrom string `json:"declared_from,omitempty"`
}
@@ -201,7 +185,7 @@ type PackageOnNode struct {
Foreign bool `json:"foreign,omitempty"`
DeclaredBy []string `json:"declared_by"`
Current bool `json:"current"`
// DeclaredFrom is the source that answered for the node's declaration; empty when neither holds one.
// DeclaredFrom is the source that answered for the node's declaration; empty when the engine holds none.
DeclaredFrom string `json:"declared_from,omitempty"`
}
@@ -217,10 +201,7 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) {
for _, ni := range all {
nodes = append(nodes, ni.Node)
}
declared, sources, engineErr, err := m.declaredOn(nodes)
if err != nil {
return nil, err
}
declared, sources, none, engineErr := m.declaredOn(nodes)
if name != "" {
on := []PackageOnNode{}
for _, ni := range all {
@@ -242,6 +223,8 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) {
answer := map[string]any{"package": name, "nodes": on}
if engineErr != "" {
answer["declared_engine_error"] = engineErr
} else if w := declaredWord(none); w != "" {
answer["declared_none"] = w
}
return answer, nil
}
@@ -294,6 +277,8 @@ func (m *Machine) Packages(name string, limit int) (map[string]any, error) {
out := map[string]any{"nodes": views}
if engineErr != "" {
out["declared_engine_error"] = engineErr
} else if w := declaredWord(none); w != "" {
out["declared_none"] = w
}
for key, list := range map[string][]Spread{"differing": differing, "some_nodes": some} {
b := bound(key, list, limit)
+16 -25
View File
@@ -43,7 +43,7 @@ func viewStates(t *testing.T) (*fakeStates, *Machine) {
}
func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) {
states, m := viewStates(t)
_, m := viewStates(t)
r, err := m.Packages("zsh", 500)
if err != nil {
t.Fatal(err)
@@ -56,22 +56,22 @@ func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) {
if a.DeclaredFrom != SourceEngine || b.DeclaredFrom != "" {
t.Errorf("sources: %q, %q", a.DeclaredFrom, b.DeclaredFrom)
}
// A node whose engine has written no key reads the controller's mirror, said so; a node with both reads its engine's.
var legacy, stale declaredValue
json.Unmarshal([]byte(`{"node":"node-b","packages":[{"package":"zsh","modules":["legacy"],"absent":false}]}`), &legacy)
json.Unmarshal([]byte(`{"node":"node-a","packages":[{"package":"zsh","modules":["stale"],"absent":false}]}`), &stale)
mirror := states.store(ControllerDeclaredState)
mirror.Put("node-b", legacy)
mirror.Put("node-a", stale)
// A node whose engine has written no key has no declaration, said so, and no controller state is read (774/S7/AC2);
// an engine whose state cannot be read at all is said once beside the sources, for every node (774/S7/AC1 reads the
// engine's key above).
read := m.States
m.States = func(name string) StateReader {
if name != SourceEngine && name != StateInstalled {
t.Errorf("state %s was read: only the engine's packages are", name)
}
return read(name)
}
r2, _ := m.Packages("zsh", 500)
on2 := r2["nodes"].([]PackageOnNode)
if on2[0].DeclaredFrom != SourceEngine || strings.Join(on2[0].DeclaredBy, ",") != "login,shell" ||
on2[1].DeclaredFrom != SourceController || strings.Join(on2[1].DeclaredBy, ",") != "legacy" {
t.Errorf("fallback: %+v", on2)
if on2[0].DeclaredFrom != SourceEngine || on2[1].DeclaredFrom != "" || len(on2[1].DeclaredBy) != 0 ||
!strings.Contains(fmt.Sprint(r2["declared_none"]), "no packages key for node-b") {
t.Errorf("a node with no key: %+v %v", on2, r2["declared_none"])
}
// The engine's state cannot be read (its bucket is not on the bus yet): the mirror answers for every node, and the
// answer says why beside declared_from; a mirror that cannot be read either is an error.
read := m.States
m.States = func(name string) StateReader {
if name == SourceEngine {
return failingReader{errors.New("is issued and its bucket is not on the bus yet")}
@@ -80,19 +80,10 @@ func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) {
}
r3, err3 := m.Packages("zsh", 500)
on3, _ := r3["nodes"].([]PackageOnNode)
if err3 != nil || len(on3) != 2 || on3[0].DeclaredFrom != SourceController || strings.Join(on3[0].DeclaredBy, ",") != "stale" ||
on3[1].DeclaredFrom != SourceController || !strings.Contains(fmt.Sprint(r3["declared_engine_error"]), "not on the bus yet") {
if err3 != nil || len(on3) != 2 || on3[0].DeclaredFrom != "" || on3[1].DeclaredFrom != "" ||
!strings.Contains(fmt.Sprint(r3["declared_engine_error"]), "not on the bus yet") {
t.Errorf("engine unreadable: %+v %v", r3, err3)
}
m.States = func(name string) StateReader {
if name == SourceEngine || name == SourceController {
return failingReader{errors.New("refused")}
}
return read(name)
}
if _, err := m.Packages("zsh", 500); err == nil {
t.Error("neither state could be read and no error was said")
}
m.States = read
if a.Node != "node-a" || !a.Installed || a.Version != "5.9-2" || !a.Explicit || a.Repository != "extra" ||
strings.Join(a.DeclaredBy, ",") != "login,shell" || !a.Current {
+1 -2
View File
@@ -12,8 +12,7 @@
}
],
"reads": [
"mesh-host.packages",
"mesh-controller.declared-packages"
"mesh-host.packages"
],
"uses": [
"operator-channel"