From d5e8458340f0f301456dcddd1ed1819b1b614422 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 20:27:12 +0100 Subject: [PATCH 1/3] Keep reading the kept declaration while a placed module runs at a build of the old form, so the node's own never drops it (issue 774) At 19:43:01 on 2026-10-30 shanks' new engine said "the kept declaration is no longer read: each of the 77 modules placed here has a have of its own", and from 19:43:57 every apply of the node's own was refused: it would remove systemd-resolved's resources (novox the same for nats and restic). systemd-resolved's have names only a controller build (build-1791404191702719760), no fingerprint, so the assigner wants it nowhere and it runs folded in the node's own, of which the kept declaration is the one record; retired, the folded module was composed as nothing. Quick fix: never retire the kept declaration. Thorough fix: allHave counts only a have naming a fingerprint of the new form (or removed); a module at a build of the old form keeps the read, named with why, until it has one. Chose thorough: the read retires by itself once every placed module is wanted. --- cmd/mesh-host/trust.go | 18 ++++++++++++------ cmd/mesh-host/whatruns_test.go | 13 +++++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cmd/mesh-host/trust.go b/cmd/mesh-host/trust.go index 2eadbc6..5e81454 100644 --- a/cmd/mesh-host/trust.go +++ b/cmd/mesh-host/trust.go @@ -251,25 +251,31 @@ func placedAllHave(ctx context.Context, session *link.Session, node string) (boo if err != nil { return false, err.Error() } - return allHave(p, func(module string) (bool, error) { - _, ok, err := session.ReadHaveErr(ctx, module) - return ok, err + return allHave(p, func(module string) (delivery.Have, bool, error) { + return session.ReadHaveErr(ctx, module) }) } -// allHave says whether has finds a have for every module p places, and why not. -func allHave(p delivery.Placement, has func(module string) (bool, error)) (bool, string) { +// allHave says whether has finds, for every module p places, a have that names a fingerprint of the new form, and why +// not. A module whose have names only a build of the old form runs inside the node's own composition, and the kept +// declaration is the one record of what runs of it: retired then, the node's own composed it as nothing and refused to +// remove what runs (issue 774, 2026-10-30 19:43: systemd-resolved on shanks, nats and restic on novox, each node's own +// apply refused from then on). +func allHave(p delivery.Placement, has func(module string) (delivery.Have, bool, error)) (bool, string) { if len(p.Modules) == 0 { return false, "the seat mesh-placement places no module here" } for _, m := range p.Modules { - ok, err := has(m.Module) + h, ok, err := has(m.Module) if err != nil { return false, "the have of " + m.Module + " is not read: " + err.Error() } if !ok { return false, m.Module + " has no have yet" } + if h.Phase != delivery.PhaseRemoved && !strings.HasPrefix(h.FP, delivery.SourcePrefix2) { + return false, m.Module + " runs at a build of the old form (" + h.Build + "), which only the kept declaration records" + } } return true, fmt.Sprintf("each of the %d modules placed here has a have of its own", len(p.Modules)) } diff --git a/cmd/mesh-host/whatruns_test.go b/cmd/mesh-host/whatruns_test.go index bdace6b..321180d 100644 --- a/cmd/mesh-host/whatruns_test.go +++ b/cmd/mesh-host/whatruns_test.go @@ -116,15 +116,20 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) { t.Fatalf("before the read is retired: %+v, %v", d, err) } placed := delivery.Placement{Node: "n1", Modules: []delivery.PlacedModule{{Module: "zsh"}, {Module: "lemurs"}}} + // Issue 774, 2026-10-30 19:43: a have naming only a build of the old form (systemd-resolved on shanks, folded in the + // node's own) keeps the kept declaration read, since it is the one record of what runs of it. + newForm := delivery.Have{FP: delivery.SourcePrefix2 + strings.Repeat("a", 64), Phase: delivery.PhaseRunning} + oldForm := delivery.Have{Build: "build-1791404191702719760", Phase: delivery.PhaseRunning} for _, row := range []struct { name string - has map[string]bool + has map[string]delivery.Have want bool }{ - {"every placed module has a have", map[string]bool{"zsh": true, "lemurs": true}, true}, - {"one has none yet", map[string]bool{"zsh": true}, false}, + {"every placed module has a have of the new form", map[string]delivery.Have{"zsh": newForm, "lemurs": newForm}, true}, + {"one has none yet", map[string]delivery.Have{"zsh": newForm}, false}, + {"one runs at a build of the old form", map[string]delivery.Have{"zsh": newForm, "lemurs": oldForm}, false}, } { - if got, why := allHave(placed, func(m string) (bool, error) { return row.has[m], nil }); got != row.want { + if got, why := allHave(placed, func(m string) (delivery.Have, bool, error) { h, ok := row.has[m]; return h, ok, nil }); got != row.want { t.Errorf("%s: %v (%s)", row.name, got, why) } } -- 2.54.0 From cc3f7e8e0154745f549c8764b9672d946f790ff5 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 20:30:37 +0100 Subject: [PATCH 2/3] Retire the kept read though mesh-wireguard has no fingerprint, since the node's own composes it from the manifest the mesh provides (issue 774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review of d5e8458 found that mesh-wireguard, provided by the mesh and never wanted at a fingerprint, kept the kept declaration read on every node for ever, against ADR 0550 ยง2 once the lease is written. A selfcompose row shows the node's own composes it from delivery.ProvidedManifest with nothing running, so the kept read holds nothing of it. Quick fix: name mesh-wireguard in allHave. Thorough fix: allHave counts every module delivery.ProvidedByMesh says the mesh provides as covered. Chose thorough: one list of provided modules, mesh-sdk's. --- cmd/mesh-host/trust.go | 4 +++- cmd/mesh-host/whatruns_test.go | 11 +++++++++++ internal/selfcompose/selfcompose_test.go | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/cmd/mesh-host/trust.go b/cmd/mesh-host/trust.go index 5e81454..caac1d7 100644 --- a/cmd/mesh-host/trust.go +++ b/cmd/mesh-host/trust.go @@ -273,7 +273,9 @@ func allHave(p delivery.Placement, has func(module string) (delivery.Have, bool, if !ok { return false, m.Module + " has no have yet" } - if h.Phase != delivery.PhaseRemoved && !strings.HasPrefix(h.FP, delivery.SourcePrefix2) { + // A module the mesh provides (mesh-wireguard) never has a fingerprint: the node's own composes it from the manifest + // mesh-sdk provides (delivery.ProvidedManifest), never from the kept declaration, so it keeps no read. + if h.Phase != delivery.PhaseRemoved && !strings.HasPrefix(h.FP, delivery.SourcePrefix2) && !delivery.ProvidedByMesh(m.Module) { return false, m.Module + " runs at a build of the old form (" + h.Build + "), which only the kept declaration records" } } diff --git a/cmd/mesh-host/whatruns_test.go b/cmd/mesh-host/whatruns_test.go index 321180d..05d809f 100644 --- a/cmd/mesh-host/whatruns_test.go +++ b/cmd/mesh-host/whatruns_test.go @@ -12,6 +12,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/declaration" "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/overlay" "github.com/novox/mesh-host/internal/agent" "github.com/novox/mesh-host/internal/liveness" @@ -133,6 +134,16 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) { t.Errorf("%s: %v (%s)", row.name, got, why) } } + // The module the mesh provides never has a fingerprint, and keeps no read (review of issue 774). + withWireguard := delivery.Placement{Node: "n1", Modules: []delivery.PlacedModule{{Module: "zsh"}, {Module: overlay.Name}}} + if got, why := allHave(withWireguard, func(m string) (delivery.Have, bool, error) { + if m == overlay.Name { + return oldForm, true, nil + } + return newForm, true, nil + }); !got { + t.Errorf("%s without a fingerprint beside zsh at the new form keeps the read: %s", overlay.Name, why) + } if got, _ := allHave(delivery.Placement{Node: "n1"}, nil); got { t.Error("a node with nothing placed retires the read") } diff --git a/internal/selfcompose/selfcompose_test.go b/internal/selfcompose/selfcompose_test.go index d8eb1f4..7139041 100644 --- a/internal/selfcompose/selfcompose_test.go +++ b/internal/selfcompose/selfcompose_test.go @@ -20,6 +20,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/compose/frombus" "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/overlay" "github.com/nats-io/nats.go" "github.com/novox/mesh-host/internal/agent" @@ -398,6 +399,27 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { if len(*told4) != 1 || !strings.HasPrefix((*told4)[0], "held power: ") || !strings.Contains((*told4)[0], `"shows" is none of them`) { t.Errorf("the agent was told %q, want power held alone, naming its item", *told4) } + // Review of issue 774 (the kept read): mesh-wireguard, which the mesh provides and which never has a fingerprint, is + // folded in the node's own and composed from the manifest mesh-sdk provides (delivery.ProvidedManifest), not from + // what runs nor the kept declaration: with nothing running (the kept read retired, Running empty) the node's own + // still carries its resources, so retiring the read removes nothing of it. + c5, told5 := fresh(env.Get) + p5 := c5.placements["n1"] + p5.Modules = append(append([]delivery.PlacedModule{}, p5.Modules...), delivery.PlacedModule{Module: overlay.Name}) + p5.Decided.Endpoint = "n1.example:51820" + c5.placements["n1"] = p5 + c5.inOwn[overlay.Name] = true + nodeKey5, _ := delivery.ComposedNodeKey("n1") + var own5 string + for _, u := range c5.compose(ctx) { + if u.Key == nodeKey5 { + own5 = string(u.Value) + } + } + if len(*told5) != 0 || !strings.Contains(own5, overlay.Name+".") { + t.Errorf("the node's own with %s folded and nothing running: told %q, carries %s: %.300s", overlay.Name, *told5, + overlay.Name, own5) + } // A read the bus did not answer, timed out or not, is tried again later; the run's own end and a refusal are not. if !BusFailed(fmt.Errorf("the revisions of it2.mailer.domain: %w", context.DeadlineExceeded)) || !BusFailed(fmt.Errorf("x: %w", nats.ErrNoResponders)) || -- 2.54.0 From 15361c661f51105bef1a57ab3ceda11edf71ce03 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 20:34:12 +0100 Subject: [PATCH 3/3] Pin mesh-sdk 4e9621ed, so a proposal foresees a bus account the Approve will mint and a held module's first reason leads a refusal (issue 774) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e6cd69a..2ded15d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-host go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030184532-9685990a15ed + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030193354-4e9621edd664 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 golang.org/x/crypto v0.57.0 diff --git a/go.sum b/go.sum index c91c159..f986418 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030184532-9685990a15ed h1:0h1S5YXrMdkjhB7gWzugqdLxb/HgDHWDmaVCNbEDAzc= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030184532-9685990a15ed/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030193354-4e9621edd664 h1:K23Dwp+MXwji+BIBUG5Rl6hvzI2FgggwPHWCVWxkSh4= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030193354-4e9621edd664/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= -- 2.54.0