Keep a left-out module's provisions and backups, and refuse more identity keys (hq ADR 0262)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

A module left out for an unknown key inside an entry lost its whole manifest, so every consumer of what
it provides was refused and its data stopped being copied. Read past only the unknown key, keep its
backup lines, and say in the condition what stops.
This commit is contained in:
jochen
2026-10-08 18:03:46 +02:00
parent e3ec15f707
commit 5d47e0bfd6
6 changed files with 247 additions and 16 deletions
+72 -2
View File
@@ -223,7 +223,8 @@ func TestANodeCalledDefaultIsANodesLayer(t *testing.T) {
func TestAKeyIsTheOperatorsByWhatItIsAbout(t *testing.T) {
for _, key := range []string{"max-tokens", "show-hostname", "ghost-opacity", "users-per-page", "mailbox-size",
"font-size", "width", "keyboard-delay", "ipv6-preferred", "client-width", "user-agent", "url-timeout",
"site-title", "cert-renewal-days", "name", "font-name"} {
"site-title", "cert-renewal-days", "name", "font-name", "allow-resize", "use-gpu", "disable-sender-check",
"max-recipients", "gateway-timeout", "sender-delay"} {
if w := operatorsWord(key); w != "" {
t.Errorf("%s read as the operator's (%s)", key, w)
}
@@ -235,7 +236,12 @@ func TestAKeyIsTheOperatorsByWhatItIsAbout(t *testing.T) {
"allowed-hosts": "host", "admin-emails": "email", "tokens": "token", "hostname": "hostname",
"apikey": "apikey", "servername": "servername", "tls-cert": "cert", "site": "site", "timezone": "timezone",
"bearer": "bearer", "bind-ipv4": "ipv4", "listen-ipv6": "ipv6", "site-name": "site-name",
"server-name": "server-name", "public-name": "public-name", "smtp-relay": "smtp-relay"} {
"server-name": "server-name", "public-name": "public-name", "smtp-relay": "smtp-relay",
"host-name": "host-name", "user-name": "user-name", "domain-name": "domain-name",
"nameserver": "nameserver", "upstream-nameservers": "nameserver", "dns-server": "dns-server",
"dns-servers": "dns-server", "default-gateway": "gateway", "lan-subnet": "subnet", "sender": "sender",
"notify-recipients": "recipient", "contact": "contact", "tls-certificate": "certificate",
"allow-from": "allow-from", "deny-from": "deny-from", "allow-hosts": "host", "use-host": "host"} {
if w := operatorsWord(key); w != word {
t.Errorf("%s: read %q, want %q", key, w, word)
}
@@ -332,3 +338,67 @@ func TestAStoredManifestWithAnUnknownKeyIsLeftOutAndRegistrationRefusesIt(t *tes
t.Fatal("a malformed stored manifest was read")
}
}
// A module left out for a key this controller does not know, inside an entry, is read past that key
// alone: it still provides what it provides, its other entries are whole, and a key of the same name
// that another entry knows is kept (novox/hq ADR 0262).
func TestALeftOutModuleStillProvidesWhatItProvides(t *testing.T) {
var m Manifest
raw := `{"module": "later", "version": "2",
"provides": [{"name": "db", "scope": "mesh"}, {"name": "cache", "a-field-from-later": 1}],
"state": [{"name": "s", "history": 3}],
"data": {"own": [{"id": "d", "path": "${dir:d}", "class": "valuable", "backup": {"dump": "x", "into": "d", "class": "later"}}]},
"resources": [{"id": "d", "type": "directory", "mode": "0700"}]}`
if err := json.Unmarshal([]byte(raw), &m); err != nil {
t.Fatal(err)
}
if len(m.Provides) != 2 || m.Provides[0].Name != "db" || m.Provides[1].Name != "cache" {
t.Fatalf("provides: %+v", m.Provides)
}
if len(m.State) != 1 || m.State[0].History != 3 {
t.Fatalf("state: %+v", m.State)
}
if m.Data == nil || len(m.Data.Own) != 1 || m.Data.Own[0].Class != "valuable" {
t.Fatalf("data: the item's own class was taken for the backup's unknown one: %+v", m.Data)
}
for _, want := range []string{"provides[1].a-field-from-later", "data.own[0].backup.class"} {
if !strings.Contains(m.UnknownField(), want) {
t.Errorf("unknown %q does not say it read without %s", m.UnknownField(), want)
}
}
if !strings.Contains(UnknownFieldReason(m), "left out") {
t.Fatal(UnknownFieldReason(m))
}
}
// A left-out module's data is still copied: the backup holder on its machine keeps its lines while every
// other thing of it is left out.
func TestALeftOutModulesDataIsStillBackedUp(t *testing.T) {
var later Manifest
if err := json.Unmarshal([]byte(`{"module": "later", "version": "2", "a-field-from-later": 1,
"resources": [{"id": "d", "type": "directory", "mode": "0700"}, {"id": "rc", "type": "file", "path": "/etc/later.conf", "content": "x\n"}],
"data": {"own": [{"id": "d", "path": "${dir:d}", "class": "valuable"}]}}`), &later); err != nil {
t.Fatal(err)
}
holder := Manifest{Module: "backups", Version: "1",
Claims: []Claim{{Name: BackupSeat, Scope: ScopeNode}},
Resources: []map[string]any{{"id": "list", "type": "file", "path": "/etc/backups.list", "mode": "0644",
"content": "${contribution:" + BackupSeat + ":backup}"}}}
r := anAdoptedAnchor()
r.Modules = append(r.Modules, holder, later)
composed, err := r.Compose(anchorRendering(false))
if err != nil {
t.Fatal(err)
}
if _, left := composed.LeftOut["later"]; !left {
t.Fatalf("not left out: %v", composed.LeftOut)
}
got := byID(composed.Resources)
if _, declared := got["later.rc"]; declared {
t.Fatal("the left-out module's file is still declared")
}
list, _ := got["backups.list"]["content"].(string)
if !strings.Contains(list, "# later") || !strings.Contains(list, "path /var/lib/later/d") {
t.Fatalf("the left-out module's data is no longer backed up:\n%s", list)
}
}