From 3b00f3890d25bc7ab614a4fc0af0af28a021794c Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 24 Oct 2026 18:17:25 +0200 Subject: [PATCH] Review round 1: a user-list-only revision of the bus is written at once at the fingerprint it runs, before the record hold; pin mesh-sdk a523a8c (issue 1906) --- .../integration_test.go | 27 +++++++++++--- modules/mesh-delivery-assigner/go.mod | 2 +- modules/mesh-delivery-assigner/go.sum | 4 +-- .../internal/assign/wants.go | 31 ++++++++++++++++ .../internal/assign/wants_test.go | 36 +++++++++++++++++++ modules/mesh-delivery-view/go.mod | 2 +- modules/mesh-delivery-view/go.sum | 4 +-- modules/mesh-delivery-watchman/go.mod | 2 +- modules/mesh-delivery-watchman/go.sum | 4 +-- 9 files changed, 98 insertions(+), 14 deletions(-) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index 8212bb4..9db3745 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -726,12 +726,15 @@ func TestARecordMoveWaitsForTheYesAndANeitherMoveDoesNot(t *testing.T) { b.wantNow(t, "ace", "web", "a move judged neither written at once", func(w delivery.Want) bool { return w.Composed == free }) } -// 1906/D4 (ADR 0539 §7): a revision of the bus module's composition at the same fingerprint that is judged neither (its user -// list only) is written at once, and no ask is made; the bus's new fingerprint is not written. +// 1906/D4 (ADR 0539 §7, §15), whole on the bus: the bus's new fingerprint is not written, and a revision of its composition at the +// fingerprint it runs that differs only in its user list is written at once, with the shape the controller gives it: judged +// recreates (the file is mounted), policy record from the bus, user-list-only; no record ask is made. func TestAUserListRevisionOfTheBusIsWrittenAtOnce(t *testing.T) { b := startBed(t, "") fNew, fOld := delivery.SourcePrefix2+strings.Repeat("b", 64), delivery.SourcePrefix2+strings.Repeat("a", 64) b.fixture(t, "bus", fNew, `{"module":"bus","version":"1","provides":[{"name":"mesh-bus"}]}`) + running, _ := delivery.EncodeHave(delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}) + b.putJSON(t, delivery.HaveBucket, "ace.bus", running) b.putWant(t, "ace", "bus", delivery.Want{FP: fOld, Composed: "sha256:" + strings.Repeat("a", 64)}) b.compose(t, "ace", "bus", fNew, 'b', delivery.JudgedNeither) b.start(t) @@ -739,7 +742,21 @@ func TestAUserListRevisionOfTheBusIsWrittenAtOnce(t *testing.T) { if w := b.wantNow(t, "ace", "bus", "the want", func(delivery.Want) bool { return true }); w.FP != fOld { t.Fatalf("the assigner moved the bus to a new fingerprint: %+v", w) } - b.putWant(t, "ace", "bus", delivery.Want{FP: fNew, Composed: "sha256:" + strings.Repeat("b", 64)}) - free := b.compose(t, "ace", "bus", fNew, 'e', delivery.JudgedNeither) - b.wantNow(t, "ace", "bus", "the user-list revision written at once", func(w delivery.Want) bool { return w.Composed == free }) + // the controller composes the bus at the fingerprint it runs with a new user list + key, _ := delivery.ComposedKey("ace", "bus", fOld) + free := "sha256:" + strings.Repeat("e", 64) + v, err := delivery.EncodeComposedDigest(delivery.ComposedDigest{Digest: free, Judgement: delivery.JudgedRecreates, Against: "sha256:" + strings.Repeat("a", 64), + Words: "the user list file is mounted", UserListOnly: true, Policy: delivery.PolicyRecord, PolicyFrom: "the bus"}) + if err != nil { + t.Fatal(err) + } + b.putJSON(t, delivery.ControllerComposedBucket, key, v) + w := b.wantNow(t, "ace", "bus", "the user-list revision written at once", func(w delivery.Want) bool { return w.Composed == free }) + if w.FP != fOld { + t.Errorf("the want = %+v, want it at the fingerprint the bus runs", w) + } + rkey, _ := delivery.RecordAskKey("ace", "bus", free) + if b.hasKey(delivery.AssignerAsksBucket, rkey) { + t.Errorf("the user-list revision was asked") + } } diff --git a/modules/mesh-delivery-assigner/go.mod b/modules/mesh-delivery-assigner/go.mod index 23f5928..cd5a97f 100644 --- a/modules/mesh-delivery-assigner/go.mod +++ b/modules/mesh-delivery-assigner/go.mod @@ -3,7 +3,7 @@ module mesh-delivery-assigner go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-delivery-assigner/go.sum b/modules/mesh-delivery-assigner/go.sum index 90c5e6a..7962f20 100644 --- a/modules/mesh-delivery-assigner/go.sum +++ b/modules/mesh-delivery-assigner/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 h1:9FENYqhynDUf7ByotHEdDjmvyFEPCYmteHjN8gRE3ao= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 h1:aFfcEXmd24GpY5na6Xaic0A9xmAAa/jbrrX5wErPDdU= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8/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= diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index e5ffbe1..732bb2d 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -187,6 +187,15 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st } before, had := wants[node] var next delivery.Want + // A revision of the bus module's composition that differs only in its user list is written at once, at the fingerprint the + // node runs, outside the rules, the record hold and the bar on a new fingerprint: a new module's credential must reach the + // bus before the module can connect (ADR 0539 §7). The controller marks it; whatever it judges of the file's mount stands. + if w, ok := m.userListRevision(node, before, had, n); ok { + if !had || !delivery.SameWant(normal(before), normal(w)) { + out.Wants[node] = w + } + continue + } switch ok, rule, words := Holds(mm, node, n); { case ok: d, _ := m.composition(node, m.FP) @@ -291,3 +300,25 @@ func (m Module) recorded(d delivery.ComposedDigest) bool { policy, _, _ := catalogue.DerivedUpgrade(m.Manifest) return policy == catalogue.PolicyRecord } + +// userListRevision is the want that names the bus module's composition at the fingerprint the node runs when that composition +// differs from the one the node runs only in the bus's user list (ComposedDigest.UserListOnly), and whether there is one. A node +// whose want names another fingerprint than the one it runs, or a module that does not provide the bus, has none. +func (m Module) userListRevision(node string, before delivery.Want, had bool, n Nodes) (delivery.Want, bool) { + if !catalogue.ProvidesBus(m.Manifest) { + return delivery.Want{}, false + } + running, ran := n.Haves[node][m.Name] + if !ran || running.FP == "" || (had && (before.Removed || before.FP != running.FP)) { + return delivery.Want{}, false + } + d, ok := m.composition(node, running.FP) + if !ok || !d.UserListOnly { + return delivery.Want{}, false + } + w := delivery.Want{FP: running.FP, Composed: d.Digest} + if had && before.Composed == d.Digest { + w.Backup, w.Words = before.Backup, before.Words + } + return w, true +} diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index 3678fff..901d917 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -417,3 +417,39 @@ func TestTheBusWithNoWantIsComparedWithWhatItRuns(t *testing.T) { t.Errorf("the bus was moved to a new fingerprint with no want: %+v", w) } } + +// Review round 1, item 1 (ADR 0539 §7): a revision of the bus module's composition that differs only in its user list is written +// at once, at the fingerprint the node runs, before the record hold, whatever the controller judges of the file's mount (it +// says recreates) and whatever policy the bus has (record, from the bus). The shape is the controller's: judged recreates against +// the composition the node runs, policy record, UserListOnly set. +func TestAUserListOnlyRevisionOfTheBusIsWrittenAtOnceBeforeTheRecordHold(t *testing.T) { + m, n, _ := movingTo(delivery.JudgedRecreates) + m.Manifest.Provides = []catalogue.Offer{{Name: "mesh-bus"}} + userList := "sha256:" + strings.Repeat("7", 64) + m.Composed = func(node, fp string) (delivery.ComposedDigest, bool) { + d := delivery.ComposedDigest{Digest: dig(fp), Judgement: delivery.JudgedRecreates, Against: dig(fOld), Words: "the user list file is mounted", + Policy: delivery.PolicyRecord, PolicyFrom: "the bus"} + if fp == fOld { + d.Digest, d.UserListOnly = userList, true // the composition at the fingerprint the node runs, with the new user list + } + return d, true + } + n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + for name, wanted := range map[string]map[string]delivery.Want{ + "a want at the fingerprint it runs": {"ace": {FP: fOld, Composed: dig(fOld)}}, + "no readable want": nil, + } { + out := Wants(m, n, wanted, nil) + if w := out.Wants["ace"]; w.FP != fOld || w.Composed != userList { + t.Errorf("%s: ace's want = %+v, want the running fingerprint naming the user-list revision", name, w) + } + if len(out.Record) != 0 { + t.Errorf("%s: the user-list revision was asked: %+v", name, out.Record) + } + } + // the same flag on a module that is not the bus changes nothing + m.Manifest.Provides = nil + if out := Wants(m, n, map[string]delivery.Want{"ace": {FP: fOld, Composed: dig(fOld)}}, nil); out.Wants["ace"].Composed == userList { + t.Errorf("a module that is not the bus was written at once: %+v", out.Wants["ace"]) + } +} diff --git a/modules/mesh-delivery-view/go.mod b/modules/mesh-delivery-view/go.mod index 1058fc1..99eecea 100644 --- a/modules/mesh-delivery-view/go.mod +++ b/modules/mesh-delivery-view/go.mod @@ -3,7 +3,7 @@ module mesh-delivery-view go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-delivery-view/go.sum b/modules/mesh-delivery-view/go.sum index 90c5e6a..7962f20 100644 --- a/modules/mesh-delivery-view/go.sum +++ b/modules/mesh-delivery-view/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 h1:9FENYqhynDUf7ByotHEdDjmvyFEPCYmteHjN8gRE3ao= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 h1:aFfcEXmd24GpY5na6Xaic0A9xmAAa/jbrrX5wErPDdU= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8/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= diff --git a/modules/mesh-delivery-watchman/go.mod b/modules/mesh-delivery-watchman/go.mod index 2437fae..9bdab56 100644 --- a/modules/mesh-delivery-watchman/go.mod +++ b/modules/mesh-delivery-watchman/go.mod @@ -3,7 +3,7 @@ module mesh-delivery-watchman go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-delivery-watchman/go.sum b/modules/mesh-delivery-watchman/go.sum index f9d8c41..4fbca7b 100644 --- a/modules/mesh-delivery-watchman/go.sum +++ b/modules/mesh-delivery-watchman/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89 h1:9FENYqhynDUf7ByotHEdDjmvyFEPCYmteHjN8gRE3ao= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024150354-5da87a0dda89/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8 h1:aFfcEXmd24GpY5na6Xaic0A9xmAAa/jbrrX5wErPDdU= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024161532-a523a8cbb0c8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op h1:1BOWQJweNyvZMlpAHXGLiZQn9S+QXGcz3xh94lC0w6E= github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=