From c1334f3f85a8b281da8127358fdcb20e9d741133 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 30 Sep 2026 21:14:40 +0200 Subject: [PATCH] The artifact store's seat is named for its scope: mesh-artifact-store ADR 0121 decided it and deferred it as a delivering-seat migration; ADR 0122's aliases made it one update and one alias (migration 0048). The former name resolves to it forever (novox/hq ADR 0156, issue 123). --- internal/broker/agreement_catalogue_test.go | 2 +- .../catalogue/artifact_store_alias_test.go | 18 ++++++++++++++++++ internal/catalogue/artifact_store_seat_test.go | 4 ++-- internal/catalogue/broker_seat_test.go | 2 +- internal/catalogue/seats.go | 11 ++++++++--- ...ifact-store-seat-is-named-for-its-scope.sql | 12 ++++++++++++ 6 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 internal/catalogue/artifact_store_alias_test.go create mode 100644 internal/inventory/migrations/0048-the-artifact-store-seat-is-named-for-its-scope.sql diff --git a/internal/broker/agreement_catalogue_test.go b/internal/broker/agreement_catalogue_test.go index 218904e..9a61b3d 100644 --- a/internal/broker/agreement_catalogue_test.go +++ b/internal/broker/agreement_catalogue_test.go @@ -67,7 +67,7 @@ func TestTheAgreementCheckCatchesASubscriptionThatMatchesNothing(t *testing.T) { // An event published under a seat's name is real even though no module declares it as its own. if bad := Disagreements(nil, []AConsumer{{Module: "watcher", Consumes: []string{"mesh-artifact-store.image.pushed"}}}, - []DeclaredSeat{{Name: "the-artifact-store", Emits: []string{"image.pushed"}}}); len(bad) != 0 { + []DeclaredSeat{{Name: "mesh-artifact-store", Emits: []string{"image.pushed"}}}); len(bad) != 0 { t.Fatalf("an event a seat emits was reported as matching nothing: %v", bad) } } diff --git a/internal/catalogue/artifact_store_alias_test.go b/internal/catalogue/artifact_store_alias_test.go new file mode 100644 index 0000000..761d476 --- /dev/null +++ b/internal/catalogue/artifact_store_alias_test.go @@ -0,0 +1,18 @@ +package catalogue + +import "testing" + +// A claim written before the rename still holds (novox/hq ADR 0122, ADR 0156): with the store's +// aliases loaded, the former name resolves to the seat. +func TestTheArtifactStoresFormerNameResolvesToIt(t *testing.T) { + was := aliases + t.Cleanup(func() { aliases = was }) + UseAliases(map[string]string{"the-artifact-store": "mesh-artifact-store"}) + seat, known := SeatNamed("the-artifact-store") + if !known || seat.Name != "mesh-artifact-store" || seat.Delivers != "artifact-store" { + t.Fatalf("the former name did not resolve: %+v %v", seat, known) + } + if _, known := SeatNamed("mesh-artifact-store"); !known { + t.Fatal("the seat is not in the set under its name") + } +} diff --git a/internal/catalogue/artifact_store_seat_test.go b/internal/catalogue/artifact_store_seat_test.go index 4b658e4..182dcac 100644 --- a/internal/catalogue/artifact_store_seat_test.go +++ b/internal/catalogue/artifact_store_seat_test.go @@ -23,7 +23,7 @@ func TestASecondArtifactStoreAnywhereIsRefusedByName(t *testing.T) { } // A second one, on any other machine, is refused — and the refusal names the seat. - elsewhere := World{Held: []Held{{Claim: "the-artifact-store", Scope: ScopeMesh, + elsewhere := World{Held: []Held{{Claim: "mesh-artifact-store", Scope: ScopeMesh, Node: "anchor", Module: "distribution"}}} other := workstation() other.Name = "laptop" @@ -32,7 +32,7 @@ func TestASecondArtifactStoreAnywhereIsRefusedByName(t *testing.T) { t.Fatal("a second store was accepted on another machine; it would offer artifact-store a " + "second time and every consumer elsewhere would refuse to choose") } - if !strings.Contains(err.Error(), "the-artifact-store") || !strings.Contains(err.Error(), "one per mesh") { + if !strings.Contains(err.Error(), "mesh-artifact-store") || !strings.Contains(err.Error(), "one per mesh") { t.Fatalf("refused without naming the seat: %v", err) } } diff --git a/internal/catalogue/broker_seat_test.go b/internal/catalogue/broker_seat_test.go index f9ca20a..c04389a 100644 --- a/internal/catalogue/broker_seat_test.go +++ b/internal/catalogue/broker_seat_test.go @@ -59,7 +59,7 @@ func TestRenamingASeatDidNotRenameTheInterfaceItDelivers(t *testing.T) { for _, pair := range []struct{ seat, delivers string }{ {"git", "git"}, {"npm-package-registry", "npm-package-registry"}, - {"the-artifact-store", "artifact-store"}, + {"mesh-artifact-store", "artifact-store"}, {"mesh-store", "postgres-database"}, {"mesh-broker", "mesh-bus"}, } { diff --git a/internal/catalogue/seats.go b/internal/catalogue/seats.go index f521daa..72ee46d 100644 --- a/internal/catalogue/seats.go +++ b/internal/catalogue/seats.go @@ -66,7 +66,11 @@ var defaultSeats = []Seat{ // rather than receives ambiently — 23 of the catalogue's modules never speak, and an ambient // connection would mint a credential for each. {Name: "mesh-broker", Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "novox/hq ADR 0079"}, - {Name: "the-artifact-store", Scope: ScopeMesh, Delivers: "artifact-store", Decision: "novox/hq ADR 0075"}, + // Named for its scope since 2026-09-30 (novox/hq ADR 0156); `the-artifact-store` resolves to it as + // an alias on a mesh that predates the rename. It serves artifacts of every kind a build makes — + // images and archives, by digest — which is why the provision is the artifact store and not an + // image registry. + {Name: "mesh-artifact-store", Scope: ScopeMesh, Delivers: "artifact-store", Decision: "novox/hq ADR 0075"}, {Name: "mesh-catalog", Scope: ScopeMesh, Decision: "novox/hq ADR 0121"}, // Deferred renames (novox/hq ADR 0121): these deliver a provision, so renaming them is a // delivering-seat migration with a mesh-wide cascade if a holder stops resolving mid-flight. @@ -97,8 +101,9 @@ var defaultSeats = []Seat{ // A system seat name is the control plane's namespace: `mesh-*` for a mesh-wide role, `node-*` for // a per-node one (novox/hq ADR 0121). A claim to a system name the mesh does not define is refused; // any other name is a module's own to define and claim. Some of the mesh's own seats predate this -// convention and are not yet renamed (git, npm-package-registry, the-artifact-store, -// the-private-network) — those are in the set, so they resolve by name, not by prefix. +// convention and are not yet renamed (git, npm-package-registry, the-private-network) — those are +// in the set, so they resolve by name, not by prefix. the-artifact-store was renamed on 2026-09-30 +// (novox/hq ADR 0156) and resolves through the alias table on a mesh that knew it. func isSystemSeatName(name string) bool { return strings.HasPrefix(name, "mesh-") || strings.HasPrefix(name, "node-") } diff --git a/internal/inventory/migrations/0048-the-artifact-store-seat-is-named-for-its-scope.sql b/internal/inventory/migrations/0048-the-artifact-store-seat-is-named-for-its-scope.sql new file mode 100644 index 0000000..26f8a72 --- /dev/null +++ b/internal/inventory/migrations/0048-the-artifact-store-seat-is-named-for-its-scope.sql @@ -0,0 +1,12 @@ +-- The artifact store's seat is named for its scope, like the mesh's other seats (novox/hq ADR 0121, +-- ADR 0156, issue 123). +-- +-- `the-artifact-store` was the last of the mesh's own seats named for the job it happened to do rather +-- than for the mesh; ADR 0121 decided the rename and deferred it because a delivering seat that stops +-- resolving mid-flight takes a provision away from every consumer. ADR 0122 removed that risk: a seat's +-- former name is an alias that resolves to it forever, a held record follows the rename by cascade, and +-- a claim written with the old name still holds. So the rename is one update and one alias. +update seat set name = 'mesh-artifact-store' where name = 'the-artifact-store'; +insert into seat_alias (alias, seat) values ('the-artifact-store', 'mesh-artifact-store') + on conflict (alias) do update set seat = excluded.seat; +update seat_alias set seat = 'mesh-artifact-store' where seat = 'the-artifact-store';