From 062c29c3a22f75bd7dad5c450b529ab9de255a2e Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 27 Oct 2026 02:27:04 +0100 Subject: [PATCH] Judge a node's own composition in its candidate answer only where it runs one, as its agent applies it only on a want (issue 1126) --- go/compose/frombus/answer.go | 15 ++++++++++++++- go/compose/frombus/frombus_test.go | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/go/compose/frombus/answer.go b/go/compose/frombus/answer.go index 2650c18..1cadb44 100644 --- a/go/compose/frombus/answer.go +++ b/go/compose/frombus/answer.go @@ -16,7 +16,7 @@ import ( // for its own composition), compared with what it runs (delivery.CompareComposed). It applies nothing and writes // nothing; head and revision are the candidate's, said back in the answer. // -// fps gives the build each module is composed at: what the node runs, and for a module the candidate places that the +// The node's own composition is compared only where the node runs one. fps gives the build each module is composed at: what the node runs, and for a module the candidate places that the // node does not run, the build the trunk holds; a placed module with none is added, with no build of it known. What the // composition stands in for is judged: a value of a module the group adds (on this node or another: its credential, own // secrets and boxes are made only once it is placed, ADR 0549 ยง5) is not yet issued, which is no refusal; any other @@ -49,6 +49,19 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st if err != nil { return refuse(err) } + // The node's own composition is judged only where the node runs one: one that runs none applies it only once a want + // names it (the node agent's own composer hands it on that want alone), so a candidate does not add it. + runsOwn := false + for _, c := range running { + runsOwn = runsOwn || c.Module == "" + } + if !runsOwn { + for key, c := range composed { + if c.Module == "" { + delete(composed, key) + } + } + } cmp := delivery.CompareComposed(composed, running) a.Same, a.Differs, a.Removed = cmp.Same, cmp.Differs, cmp.Removed diff --git a/go/compose/frombus/frombus_test.go b/go/compose/frombus/frombus_test.go index 7b6fef7..0914062 100644 --- a/go/compose/frombus/frombus_test.go +++ b/go/compose/frombus/frombus_test.go @@ -232,6 +232,15 @@ func TestInput(t *testing.T) { for _, c := range runs { runsNow = append(runsNow, c) } + withoutOwn := func(cs []delivery.Composed) []delivery.Composed { + var out []delivery.Composed + for _, c := range cs { + if c.Module != "" { + out = append(out, c) + } + } + return out + } placedAs := func(modules ...string) map[string]*delivery.Placement { p := one(modules...).placements["n1"] return map[string]*delivery.Placement{"n1": &p} @@ -245,6 +254,8 @@ func TestInput(t *testing.T) { }{ "the same": {one("keeper", "lemurs"), placedAs("keeper", "lemurs"), runsNow, delivery.CandidateSame, func(a delivery.CandidateAnswer) bool { return len(a.Same) == 3 && a.Head == "h1" && a.Revision == 7 }}, + "the node runs no own composition": {one("keeper", "lemurs"), placedAs("keeper", "lemurs"), withoutOwn(runsNow), delivery.CandidateSame, + func(a delivery.CandidateAnswer) bool { return len(a.Same) == 2 && len(a.Added) == 0 }}, "one removed": {one("keeper", "lemurs"), placedAs("keeper"), runsNow, delivery.CandidateDiffers, func(a delivery.CandidateAnswer) bool { return reflect.DeepEqual(a.Removed, []string{"lemurs"}) }}, "one added, its secret not yet issued": {func() *bus { b := one("lemurs"); b.own = nil; return b }(), placedAs("keeper", "lemurs"),