From 570b66230cfe3dcf46b4838a6f1c0d2d67c05939 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 21:29:12 +0100 Subject: [PATCH] =?UTF-8?q?Composer:=20find=20the=20store=20by=20the=20hol?= =?UTF-8?q?der=20of=20any=20of=20its=20names,=20the=20old=20seat's=20too?= =?UTF-8?q?=20(ADR=200632=20=C2=A72;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the indexer holds distribution's new manifest, every node reads main's, which claims only mesh-artifact-store: a new engine must find the store there. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- internal/compose/frombus/frombus.go | 8 ++++---- internal/compose/rendering.go | 14 +++++++++++--- internal/compose/rendering_test.go | 30 +++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/internal/compose/frombus/frombus.go b/internal/compose/frombus/frombus.go index f4c1d20..a7e887c 100644 --- a/internal/compose/frombus/frombus.go +++ b/internal/compose/frombus/frombus.go @@ -1142,7 +1142,7 @@ func storeSought(in compose.Input) string { for _, n := range in.Nodes { nodes = append(nodes, n.Name) for _, a := range n.Assigned { - if m, ok := in.Catalogue[a]; ok && claimsSeat(m, compose.StoreSeat) { + if m, ok := in.Catalogue[a]; ok && claimsStore(m) { where = append(where, a+" on "+n.Name) } } @@ -1157,10 +1157,10 @@ func storeSought(in compose.Input) string { ", none of it on the private network with a listen open to the mesh" } -// claimsSeat says whether m claims seat at the mesh's scope. -func claimsSeat(m catalogue.Manifest, seat string) bool { +// claimsStore says whether m claims a name of the store (resolution.StoreSeats) at the mesh's scope. +func claimsStore(m catalogue.Manifest) bool { for _, cl := range m.Claims { - if cs, ok := catalogue.SeatNamed(cl.Name); ok && cs.Name == seat && cl.At() == catalogue.ScopeMesh { + if cs, ok := catalogue.SeatNamed(cl.Name); ok && slices.Contains(resolution.StoreSeats, cs.Name) && cl.At() == catalogue.ScopeMesh { return true } } diff --git a/internal/compose/rendering.go b/internal/compose/rendering.go index f6c0bb4..7d06652 100644 --- a/internal/compose/rendering.go +++ b/internal/compose/rendering.go @@ -3,6 +3,7 @@ package compose import ( "errors" "fmt" + "slices" "sort" "strings" @@ -332,11 +333,18 @@ func (c *composer) namesInTheMesh() map[string]string { const StoreSeat = "container-image-registry" // storeHolders are the modules holding the store's seat: the holdings on record, and only where none is on record every -// module of the catalogue claiming it at the mesh's scope, as a seat holder's reach is read (replicatedHolders). +// module of the catalogue claiming it at the mesh's scope, as a seat holder's reach is read (replicatedHolders). Any name +// of the store counts (resolution.StoreSeats): until every counted engine states the new reading, the manifest every +// node reads is the holder's old one, which claims only mesh-artifact-store (ADR 0632 §2), and an engine that finds the +// store by the seat must find it there too. func (c *composer) storeHolders() map[string]catalogue.Manifest { + isStore := func(claim string) bool { + hs, ok := catalogue.SeatNamed(claim) + return ok && slices.Contains(resolution.StoreSeats, hs.Name) + } out := map[string]catalogue.Manifest{} for _, h := range c.in.Holdings { - if hs, ok := catalogue.SeatNamed(h.Claim); ok && hs.Name == StoreSeat && h.Scope == catalogue.ScopeMesh && h.Module != "" { + if isStore(h.Claim) && h.Scope == catalogue.ScopeMesh && h.Module != "" { if m, known := c.in.Catalogue[h.Module]; known { out[h.Module] = m } @@ -347,7 +355,7 @@ func (c *composer) storeHolders() map[string]catalogue.Manifest { } for name, m := range c.in.Catalogue { for _, cl := range m.Claims { - if cs, ok := catalogue.SeatNamed(cl.Name); ok && cs.Name == StoreSeat && cl.At() == catalogue.ScopeMesh { + if isStore(cl.Name) && cl.At() == catalogue.ScopeMesh { out[name] = m } } diff --git a/internal/compose/rendering_test.go b/internal/compose/rendering_test.go index 0489a5e..8c50b5b 100644 --- a/internal/compose/rendering_test.go +++ b/internal/compose/rendering_test.go @@ -318,6 +318,36 @@ func TestTheStoreIsFoundByTheImageSeatsHolderAndItsListen(t *testing.T) { t.Errorf("${seat:%s:reach} is %q", seat, r.SeatReach[seat]) } } + // main's distribution, the manifest every node reads while the indexer holds the new one (ADR 0632 §2): it claims + // only mesh-artifact-store and serves the retired provision; the engine finds the store by that claim and its listen. + old := in + old.Catalogue = map[string]catalogue.Manifest{} + for k, v := range in.Catalogue { + if k != "registry" { + old.Catalogue[k] = v + } + } + old.Catalogue["distribution"] = catalogue.Manifest{Module: "distribution", Version: "2", + Provides: []catalogue.Offer{{Name: catalogue.ArtifactStoreProvision, Scope: catalogue.ScopeMesh}}, + Claims: []catalogue.Claim{{Name: "mesh-artifact-store", Scope: catalogue.ScopeMesh}}, + Serves: map[string]map[string]any{catalogue.ArtifactStoreProvision: {"port": 5000}}, + Listens: []catalogue.Listening{{Name: "registry", Port: 5000, Protocol: "tcp", From: catalogue.FromMesh}}} + old.Nodes = append([]Node(nil), in.Nodes...) + for i := range old.Nodes { + if old.Nodes[i].Name == "anchor" { + var kept []string + for _, a := range old.Nodes[i].Assigned { + if a != "registry" && a != "old" { + kept = append(kept, a) + } + } + old.Nodes[i].Assigned = append(kept, "distribution") + old.Nodes[i].Ports = map[string]map[int]int{"distribution": {5000: 5400}} + } + } + if got := placed(t, old, "laptop").Rendering.ArtifactStore; got != "anchor.internal:5400" { + t.Errorf("main's distribution, claiming only mesh-artifact-store, is reached at %q, want anchor.internal:5400", got) + } m := in.Catalogue["registry"] m.Listens = append(m.Listens, catalogue.Listening{Name: "other", Port: 5300, From: catalogue.FromMesh}) in.Catalogue["registry"] = m