From 625d62779415b012536765c9becc8f3edad83ecf Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 19:36:21 +0100 Subject: [PATCH] No client names the holder of the seat mesh-provisioner: each reader keeps its own constant, held to its manifest (ADR 0625 section 4, issue 1966) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../gitea/cmd/forge-account-provider/main.go | 6 +++- .../provisioner_names_test.go | 28 +++++++++++++++++++ .../cmd/forge-account-provider/service.go | 2 +- .../forge-account-provider/service_test.go | 8 +++--- .../cmd/keycloak-provider/grant_moved_test.go | 6 ++-- .../keycloak/cmd/keycloak-provider/moved.go | 6 +++- .../cmd/keycloak-provider/moved_test.go | 2 +- .../provisioner_names_test.go | 28 +++++++++++++++++++ .../cmd/mesh-provisioner-client/client.go | 5 +++- .../route_names_test.go | 16 ++++++++--- .../cmd/mesh-provisioner/holder.go | 7 +++-- modules/mesh-vault/cmd/mesh-vault/askers.go | 9 +++++- modules/mesh-vault/cmd/mesh-vault/main.go | 5 ++-- .../mesh-vault/cmd/mesh-vault/placed_test.go | 9 ++++++ .../mesh-vault/cmd/mesh-vault/vault_test.go | 4 +-- .../postgres/cmd/postgres-provider/moved.go | 6 +++- .../provisioner_names_test.go | 28 +++++++++++++++++++ .../route-adapter/cmd/route-provider/main.go | 6 +++- .../cmd/route-provider/provider.go | 2 +- .../cmd/route-provider/provider_test.go | 4 +-- .../route-provider/provisioner_names_test.go | 28 +++++++++++++++++++ 21 files changed, 186 insertions(+), 29 deletions(-) create mode 100644 modules/gitea/cmd/forge-account-provider/provisioner_names_test.go create mode 100644 modules/keycloak/cmd/keycloak-provider/provisioner_names_test.go create mode 100644 modules/postgres/cmd/postgres-provider/provisioner_names_test.go create mode 100644 modules/route-adapter/cmd/route-provider/provisioner_names_test.go diff --git a/modules/gitea/cmd/forge-account-provider/main.go b/modules/gitea/cmd/forge-account-provider/main.go index 298a6ed75..a9ae148ab 100644 --- a/modules/gitea/cmd/forge-account-provider/main.go +++ b/modules/gitea/cmd/forge-account-provider/main.go @@ -100,7 +100,7 @@ func main() { } cfg.Log = say svc := NewService(cfg) - p := svc.Provider(provider.StateBus(Module)) + p := svc.Provider(provider.StateBus(Module, provisionerModule)) ctx := context.Background() // A pass answers every consumer; one at a time, and one per change of a machine's key. var passing sync.Mutex @@ -132,3 +132,7 @@ func main() { os.Exit(1) } } + +// provisionerModule is the holder of the seat mesh-provisioner, named from this module's manifest (its `reads` of plans and +// provisions; TestTheProvisionerIsTheManifests). +const provisionerModule = "mesh-provisioner" diff --git a/modules/gitea/cmd/forge-account-provider/provisioner_names_test.go b/modules/gitea/cmd/forge-account-provider/provisioner_names_test.go new file mode 100644 index 000000000..9a3325a0d --- /dev/null +++ b/modules/gitea/cmd/forge-account-provider/provisioner_names_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "os" + "slices" + "testing" +) + +// The module holding the seat mesh-provisioner, which this provider reads the plans and provisions of, is the one its manifest's +// `reads` name: no client names a holder (novox/hq ADR 0625 §4). +func TestTheProvisionerIsTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Reads []string `json:"reads"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + for _, state := range []string{"plans", "provisions"} { + if !slices.Contains(m.Reads, provisionerModule+"."+state) { + t.Errorf("the provider reads %s.%s, its manifest's reads are %v", provisionerModule, state, m.Reads) + } + } +} diff --git a/modules/gitea/cmd/forge-account-provider/service.go b/modules/gitea/cmd/forge-account-provider/service.go index 3ab458d53..cbc7f1813 100644 --- a/modules/gitea/cmd/forge-account-provider/service.go +++ b/modules/gitea/cmd/forge-account-provider/service.go @@ -125,7 +125,7 @@ func (s *Service) Provider(bus provider.Bus) *provider.Provider { vb := &valuesBus{Bus: bus, s: s, written: map[string][]byte{}} s.bound = vb return provider.New(provider.Options{ - Node: s.cfg.Node, Module: Module, + Node: s.cfg.Node, Module: Module, Provisioner: provisionerModule, Offers: map[string]provider.Offer{ProvisionName: {Credential: provider.NoCredential}}, Bus: vb, Make: s.Make, Gone: s.Gone, diff --git a/modules/gitea/cmd/forge-account-provider/service_test.go b/modules/gitea/cmd/forge-account-provider/service_test.go index 4fa5f7070..38c32729b 100644 --- a/modules/gitea/cmd/forge-account-provider/service_test.go +++ b/modules/gitea/cmd/forge-account-provider/service_test.go @@ -110,19 +110,19 @@ func (b *fakeBus) bindClient(node string) { // bindPlan makes module on node bind gitea's forge-account as the seat mesh-provisioner keeps it: its plan, and the // provision key asked of gitea on novox (novox/hq ADR 0570 clauses 1 to 3). func (b *fakeBus) bindPlan(node, module, as string) { - b.set(meshprovisionerclient.PlansBucket, node+"."+module, []byte(fmt.Sprintf( + b.set(meshprovisionerclient.PlansBucketOf(provisionerModule), node+"."+module, []byte(fmt.Sprintf( `{"v":1,"node":%q,"module":%q,"plan":{"provisions":[{"provision":"forge-account","provider":"novox","provider-module":"gitea","as":%q}]}}`, node, module, as))) - b.set(meshprovisionerclient.ProvisionsBucket, node+"."+module+".forge-account", provisionKey(node, module, meshprovisionerclient.Asked, nil)) + b.set(meshprovisionerclient.ProvisionsBucketOf(provisionerModule), node+"."+module+".forge-account", provisionKey(node, module, meshprovisionerclient.Asked, nil)) } // retire is the operator's yes to the retiring of module's forge-account on node: its plan gone, its key retiring // with the warrant (ADR 0570 clause 5), the only way the provider lets an entry go. func (b *fakeBus) retire(node, module string) { b.mu.Lock() - delete(b.buckets[meshprovisionerclient.PlansBucket], node+"."+module) + delete(b.buckets[meshprovisionerclient.PlansBucketOf(provisionerModule)], node+"."+module) b.mu.Unlock() - b.set(meshprovisionerclient.ProvisionsBucket, node+"."+module+".forge-account", provisionKey(node, module, meshprovisionerclient.Retiring, + b.set(meshprovisionerclient.ProvisionsBucketOf(provisionerModule), node+"."+module+".forge-account", provisionKey(node, module, meshprovisionerclient.Retiring, &meshprovisionerclient.Warrant{Ask: "ask-1", Option: "yes", By: "operator", At: time.Date(2026, 10, 29, 12, 0, 0, 0, time.UTC)})) } diff --git a/modules/keycloak/cmd/keycloak-provider/grant_moved_test.go b/modules/keycloak/cmd/keycloak-provider/grant_moved_test.go index 59cb8f086..74b4212c8 100644 --- a/modules/keycloak/cmd/keycloak-provider/grant_moved_test.go +++ b/modules/keycloak/cmd/keycloak-provider/grant_moved_test.go @@ -49,7 +49,7 @@ func noOpPlan(bus *bucketBus, node, module string) { // keycloak on novox the key the seat composes, asked (novox/hq ADR 0570 clauses 1 to 3). func seatPlan(bus *bucketBus, node, module string, plan map[string]any) { raw, _ := json.Marshal(map[string]any{"v": 1, "node": node, "module": module, "plan": plan}) - _ = bus.Put(ctx, meshprovisionerclient.PlansBucket, node+"."+module, raw) + _ = bus.Put(ctx, meshprovisionerclient.PlansBucketOf(provisionerModule), node+"."+module, raw) list, _ := plan["provisions"].([]any) for _, e := range list { entry := e.(map[string]any) @@ -64,7 +64,7 @@ func seatPlan(bus *bucketBus, node, module string, plan map[string]any) { if err != nil { panic(err) } - _ = bus.Put(ctx, meshprovisionerclient.ProvisionsBucket, key, asked) + _ = bus.Put(ctx, meshprovisionerclient.ProvisionsBucketOf(provisionerModule), key, asked) } } @@ -103,7 +103,7 @@ func movedWorld(t *testing.T, h *harness.Harness) (f *fakeKeycloak, start func() } bus := &bucketBus{buckets: map[string]map[string][]byte{}} noOpPlan(bus, "elsewhere", "unrelated") - p := provider.New(provider.Options{Node: h.Node, Module: "keycloak", Bus: bus, + p := provider.New(provider.Options{Provisioner: provisionerModule, Node: h.Node, Module: "keycloak", Bus: bus, Vault: provider.ConsumersFile{Paths: []string{consumersFile}}, Offers: map[string]provider.Offer{ProvisionName: {Credential: provider.PerConsumer, Serves: servedValues(testIssuer)}}, Make: run.Make, Gone: run.Gone, Every: h.Every, Logf: func(string, ...any) {}}) diff --git a/modules/keycloak/cmd/keycloak-provider/moved.go b/modules/keycloak/cmd/keycloak-provider/moved.go index 84d13647a..b0fa8234a 100644 --- a/modules/keycloak/cmd/keycloak-provider/moved.go +++ b/modules/keycloak/cmd/keycloak-provider/moved.go @@ -50,9 +50,13 @@ func valuesOf(e provider.Entry) map[string]any { // them. Each client it gives is made and checked by the harness's grant pass exactly as on the grant path; a client // a plan names is the helper's alone. func moved(consumersFile string, h *harness.Harness, issuer string) (*harness.Moved, *provider.Provider, provider.Bus, error) { - return harness.NewMoved(consumersFile, h, harness.MovedSpec{Module: "keycloak", Provision: ProvisionName, + return harness.NewMoved(consumersFile, h, harness.MovedSpec{Module: "keycloak", Provisioner: provisionerModule, Provision: ProvisionName, Offer: provider.Offer{Credential: provider.PerConsumer, Serves: servedValues(issuer)}, Login: func(e provider.Entry) string { return e.As }, Values: valuesOf, Noun: "client id", Made: "client", Backend: "Keycloak", WithGrants: true}) } + +// provisionerModule is the holder of the seat mesh-provisioner, named from this module's manifest (its `reads` of plans and +// provisions; TestTheProvisionerIsTheManifests). +const provisionerModule = "mesh-provisioner" diff --git a/modules/keycloak/cmd/keycloak-provider/moved_test.go b/modules/keycloak/cmd/keycloak-provider/moved_test.go index b9e4a0aac..9025ea1b2 100644 --- a/modules/keycloak/cmd/keycloak-provider/moved_test.go +++ b/modules/keycloak/cmd/keycloak-provider/moved_test.go @@ -158,7 +158,7 @@ func TestBothPathsLeaveTheSameClientInKeycloak(t *testing.T) { seatPlan(bus, "home", "grafana", map[string]any{ "provisions": []any{map[string]any{"provision": ProvisionName, "provider": "novox", "provider-module": "keycloak", "contributes": grafanaContributes, "names": grafanaNames}}}) - pr := provider.New(provider.Options{Node: "novox", Module: "keycloak", Bus: bus, + pr := provider.New(provider.Options{Provisioner: provisionerModule, Node: "novox", Module: "keycloak", Bus: bus, Vault: provider.ConsumersFile{Paths: []string{file}}, Offers: map[string]provider.Offer{ProvisionName: {Credential: provider.PerConsumer, Serves: servedValues(testIssuer)}}, diff --git a/modules/keycloak/cmd/keycloak-provider/provisioner_names_test.go b/modules/keycloak/cmd/keycloak-provider/provisioner_names_test.go new file mode 100644 index 000000000..9a3325a0d --- /dev/null +++ b/modules/keycloak/cmd/keycloak-provider/provisioner_names_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "os" + "slices" + "testing" +) + +// The module holding the seat mesh-provisioner, which this provider reads the plans and provisions of, is the one its manifest's +// `reads` name: no client names a holder (novox/hq ADR 0625 §4). +func TestTheProvisionerIsTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Reads []string `json:"reads"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + for _, state := range []string{"plans", "provisions"} { + if !slices.Contains(m.Reads, provisionerModule+"."+state) { + t.Errorf("the provider reads %s.%s, its manifest's reads are %v", provisionerModule, state, m.Reads) + } + } +} diff --git a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go index 2b2660784..0d176ed65 100644 --- a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go +++ b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go @@ -49,11 +49,14 @@ const ( haveBucket = engineHolder + "_" + nodeengine.StateHave ) +// provisionerModule is the module holding the seat mesh-provisioner, whose plans the client reads. +const provisionerModule = "mesh-provisioner" + // The buckets the client reads and writes, and the subject it reports on. const ( // PlansBucket is where each module's provisioning plan on each node is read: the seat mesh-provisioner's // `plans` (novox/hq ADR 0570 clause 2), read as an assignment's value carried it (asAssignment). - PlansBucket = meshprovisionerclient.PlansBucket + PlansBucket = provisionerModule + "_" + meshprovisionerclient.PlansState // PlacementBucket is the seat mesh-module-assigner's placement (novox/hq ADR 0567): a module it names on this node stays // here while the seat mesh-provisioner holds no plan of it (nats on novox runs without a want), filled from its // last plan, and leaves only when neither names it (issue 1126 B12; the controller's assignments mirror, read for diff --git a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/route_names_test.go b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/route_names_test.go index 2f4482717..cb6e8e1a1 100644 --- a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/route_names_test.go +++ b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/route_names_test.go @@ -55,9 +55,9 @@ func (m *memBus) Delete(_ context.Context, bucket, key string) error { // as the seat mesh-provisioner keeps it (ADR 0570 clause 2). func routeEntryFor(t *testing.T, module, planEntry string) string { t.Helper() - bus := &memBus{buckets: map[string]map[string][]byte{meshprovisionerclient.PlansBucket: { + bus := &memBus{buckets: map[string]map[string][]byte{meshprovisionerclient.PlansBucketOf(provisionerModule): { node + "." + module: []byte(`{"v":1,"node":"` + node + `","module":"` + module + `","plan":{"provisions":[` + - planEntry + `]}}`)}, meshprovisionerclient.ProvisionsBucket: {}}} + planEntry + `]}}`)}, meshprovisionerclient.ProvisionsBucketOf(provisionerModule): {}}} // The key the seat composed for that entry, provided-for: asked of route-proxy (ADR 0570 clause 3). var b struct { Provision string `json:"provision"` @@ -76,8 +76,8 @@ func routeEntryFor(t *testing.T, module, planEntry string) string { if err != nil { t.Fatal(err) } - bus.buckets[meshprovisionerclient.ProvisionsBucket][key] = raw - p := provider.New(provider.Options{Node: "novox", Module: "route-proxy", Bus: bus, Logf: t.Logf, + bus.buckets[meshprovisionerclient.ProvisionsBucketOf(provisionerModule)][key] = raw + p := provider.New(provider.Options{Provisioner: provisionerModule, Node: "novox", Module: "route-proxy", Bus: bus, Logf: t.Logf, Offers: map[string]provider.Offer{"route": {Credential: provider.NoCredential, Serves: map[string]any{}}}}) out, err := p.Pass(context.Background()) if err != nil { @@ -157,3 +157,11 @@ func TestNatsAModuleThatFailedOnAMissingNameIsFilledWhenTheNameArrives(t *testin return e.read(t, "/etc/gitea/origins") == "https://git.example.org\n" }) } + +// The module holding the seat mesh-provisioner is this client's own word (the grants part grants the bucket whole; no client +// names a holder, novox/hq ADR 0625 §4), and the plans bucket is its state plans. +func TestThePlansBucketIsTheSeatsStatePlans(t *testing.T) { + if PlansBucket != "mesh-provisioner_plans" || provisionerModule != "mesh-provisioner" { + t.Errorf("the plans bucket is %q of %q", PlansBucket, provisionerModule) + } +} diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 8eb81cf67..11be6b1d3 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -92,6 +92,9 @@ var BoundReads = []string{"gitea", "keycloak", "postgres", "route-adapter", "rou // (novox/hq ADR 0625 §4). const vaultModule = "mesh-vault" +// plansRead is the address a provider's manifest's `reads` names this seat's state plans by: the holder is this module. +const plansRead = Module + "." + meshprovisionerclient.PlansState + // boundRead is a provider's entries as this module reads them. func boundRead(module string) string { return module + ".bound" } @@ -783,10 +786,10 @@ func (h *Holder) unread(module string) string { var decl struct { Reads []string `json:"reads"` } - if json.Unmarshal([]byte(manifest), &decl) != nil || slices.Contains(decl.Reads, meshprovisionerclient.PlansRead) { + if json.Unmarshal([]byte(manifest), &decl) != nil || slices.Contains(decl.Reads, plansRead) { return "" } - return module + " reads no plans of the seat (its manifest's reads do not name " + meshprovisionerclient.PlansRead + + return module + " reads no plans of the seat (its manifest's reads do not name " + plansRead + "), so its helper never answers this key" } diff --git a/modules/mesh-vault/cmd/mesh-vault/askers.go b/modules/mesh-vault/cmd/mesh-vault/askers.go index cfc4d1c0b..0fc6f5c26 100644 --- a/modules/mesh-vault/cmd/mesh-vault/askers.go +++ b/modules/mesh-vault/cmd/mesh-vault/askers.go @@ -78,7 +78,14 @@ const ProvisionerPrefix = "provisioner." // seat mesh-provisioner's state `plans`, `.` (novox/hq ADR 0570 clause 2), no longer the controller's // assignments. A plan is read as the assignment's value carried it (asAssignment), so the rules that read a plan are // unchanged. -const AssignmentsRead = meshprovisionerclient.PlansRead +const AssignmentsRead = provisionerModule + "." + meshprovisionerclient.PlansState + +// ProvisionsRead is the seat's state provisions as the manifest's `reads` names it; provisionerModule the module holding the seat +// mesh-provisioner (TestThePlacementNamesAreTheManifests). +const ( + ProvisionsRead = provisionerModule + "." + meshprovisionerclient.ProvisionsState + provisionerModule = "mesh-provisioner" +) // asAssignment is a plan's key in the form the vault's rules read: `{node, module, provisioning: plan}`, nil for none. func asAssignment(key string, raw []byte, err error) ([]byte, error) { diff --git a/modules/mesh-vault/cmd/mesh-vault/main.go b/modules/mesh-vault/cmd/mesh-vault/main.go index 6a5ac44d6..8f74f566b 100644 --- a/modules/mesh-vault/cmd/mesh-vault/main.go +++ b/modules/mesh-vault/cmd/mesh-vault/main.go @@ -9,7 +9,6 @@ package main import ( "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" - "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" "encoding/json" @@ -80,7 +79,7 @@ func build() *Env { Delete: func(key string) error { return stdio.State(BoundState).Delete(key) }, Keys: func() ([]string, error) { return stdio.State(BoundState).Keys() }, Provision: func(key string) ([]byte, error) { - e, err := stdio.State(meshprovisionerclient.ProvisionsRead).Get(key) + e, err := stdio.State(ProvisionsRead).Get(key) if err != nil || e == nil { return nil, err } @@ -220,7 +219,7 @@ func watchCredentialAsks(env *Env, kick chan<- struct{}) bool { if env.Vault == nil { return true } - err := stdio.State(meshprovisionerclient.ProvisionsRead).Watch("", func(c stdio.StateChange) error { + err := stdio.State(ProvisionsRead).Watch("", func(c stdio.StateChange) error { if !c.Current && credentialAsked(c.Value) { select { case kick <- struct{}{}: diff --git a/modules/mesh-vault/cmd/mesh-vault/placed_test.go b/modules/mesh-vault/cmd/mesh-vault/placed_test.go index c4dd40374..85fc5bbf6 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed_test.go @@ -86,6 +86,15 @@ func TestThePlacementNamesAreTheManifests(t *testing.T) { if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } + var reads struct { + Reads []string `json:"reads"` + } + _ = json.Unmarshal(raw, &reads) + for _, r := range []string{AssignmentsRead, ProvisionsRead} { + if !slices.Contains(reads.Reads, r) { + t.Errorf("the vault reads %s, its manifest's reads are %v", r, reads.Reads) + } + } if !slices.Contains(m.Invokes, PlacementTool) || !slices.Contains(m.Consumes, PlacementViewsEvent) { t.Errorf("the vault asks %s and follows %s, its manifest invokes %v and consumes %v", PlacementTool, PlacementViewsEvent, m.Invokes, m.Consumes) } diff --git a/modules/mesh-vault/cmd/mesh-vault/vault_test.go b/modules/mesh-vault/cmd/mesh-vault/vault_test.go index bb086b76f..7cc8522de 100644 --- a/modules/mesh-vault/cmd/mesh-vault/vault_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/vault_test.go @@ -204,9 +204,9 @@ func TestNoBucketHoldsASecret(t *testing.T) { for _, call := range []string{"Get(", "Put(", "Delete(", "Keys("} { code = []byte(strings.Replace(string(code), "stdio.State(BoundState)."+call, "", 1)) } - code = []byte(strings.Replace(string(code), "stdio.State(meshprovisionerclient.ProvisionsRead).Get(", "", 1)) + code = []byte(strings.Replace(string(code), "stdio.State(ProvisionsRead).Get(", "", 1)) // And one watch of them, for a credential asked of the vault (issue 1126). - code = []byte(strings.Replace(string(code), "stdio.State(meshprovisionerclient.ProvisionsRead).Watch(", "", 1)) + code = []byte(strings.Replace(string(code), "stdio.State(ProvisionsRead).Watch(", "", 1)) } if m := bucket.Find(code); m != nil { t.Errorf("%s opens a bucket (%s): the vault keeps every secret in its own store only", f, m) diff --git a/modules/postgres/cmd/postgres-provider/moved.go b/modules/postgres/cmd/postgres-provider/moved.go index 3efa2c1cb..a37286d77 100644 --- a/modules/postgres/cmd/postgres-provider/moved.go +++ b/modules/postgres/cmd/postgres-provider/moved.go @@ -30,8 +30,12 @@ const ProvisionName = "postgres-database" // It serves its consumers the port its server is published on, on its machine (servedPort), never the container's // own 5432: on a machine that publishes it elsewhere (6852:5432 on novox) nothing answers 5432 (issue 957). func moved(consumersFile string, h *harness.Harness, servedPort int) (*harness.Moved, *provider.Provider, provider.Bus, error) { - return harness.NewMoved(consumersFile, h, harness.MovedSpec{Module: "postgres", Provision: ProvisionName, + return harness.NewMoved(consumersFile, h, harness.MovedSpec{Module: "postgres", Provisioner: provisionerModule, Provision: ProvisionName, Offer: provider.Offer{Credential: provider.PerConsumer, Serves: map[string]any{"port": servedPort}}, Login: func(e provider.Entry) string { return e.User }, Noun: "login", Made: "login", Backend: "the backend", WithGrants: true}) } + +// provisionerModule is the holder of the seat mesh-provisioner, named from this module's manifest (its `reads` of plans and +// provisions; TestTheProvisionerIsTheManifests). +const provisionerModule = "mesh-provisioner" diff --git a/modules/postgres/cmd/postgres-provider/provisioner_names_test.go b/modules/postgres/cmd/postgres-provider/provisioner_names_test.go new file mode 100644 index 000000000..9a3325a0d --- /dev/null +++ b/modules/postgres/cmd/postgres-provider/provisioner_names_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "os" + "slices" + "testing" +) + +// The module holding the seat mesh-provisioner, which this provider reads the plans and provisions of, is the one its manifest's +// `reads` name: no client names a holder (novox/hq ADR 0625 §4). +func TestTheProvisionerIsTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Reads []string `json:"reads"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + for _, state := range []string{"plans", "provisions"} { + if !slices.Contains(m.Reads, provisionerModule+"."+state) { + t.Errorf("the provider reads %s.%s, its manifest's reads are %v", provisionerModule, state, m.Reads) + } + } +} diff --git a/modules/route-adapter/cmd/route-provider/main.go b/modules/route-adapter/cmd/route-provider/main.go index 42673701c..455e5bfdb 100644 --- a/modules/route-adapter/cmd/route-provider/main.go +++ b/modules/route-adapter/cmd/route-provider/main.go @@ -23,7 +23,7 @@ func main() { if node == "" { say("MESH_NODE is not set; answering no consumers") } else { - p := newProvider(node, provider.StateBus(ModuleName), say) + p := newProvider(node, provider.StateBus(ModuleName, provisionerModule), say) go func() { if err := p.Run(context.Background()); err != nil { say("the provider helper stopped: %v", err) @@ -35,3 +35,7 @@ func main() { os.Exit(1) } } + +// provisionerModule is the holder of the seat mesh-provisioner, named from this module's manifest (its `reads` of plans and +// provisions; TestTheProvisionerIsTheManifests). +const provisionerModule = "mesh-provisioner" diff --git a/modules/route-adapter/cmd/route-provider/provider.go b/modules/route-adapter/cmd/route-provider/provider.go index 3d5832b67..90d875966 100644 --- a/modules/route-adapter/cmd/route-provider/provider.go +++ b/modules/route-adapter/cmd/route-provider/provider.go @@ -25,7 +25,7 @@ const ModuleName = "route-adapter" func newProvider(node string, bus provider.Bus, logf func(string, ...any)) *provider.Provider { return provider.New(provider.Options{ Node: node, - Module: ModuleName, + Module: ModuleName, Provisioner: provisionerModule, Offers: map[string]provider.Offer{ProvisionName: { Credential: provider.NoCredential, Serves: map[string]any{}, diff --git a/modules/route-adapter/cmd/route-provider/provider_test.go b/modules/route-adapter/cmd/route-provider/provider_test.go index dada87368..016027c8f 100644 --- a/modules/route-adapter/cmd/route-provider/provider_test.go +++ b/modules/route-adapter/cmd/route-provider/provider_test.go @@ -51,7 +51,7 @@ func TestAConsumerOfARouteIsToldItsNamesFromItsPlanEntry(t *testing.T) { "provider-module": ModuleName, "at": "10.7.0.1", "from": "novox", "as": "mesh_g14_gitea", "names": map[string]any{"name": "git.example.org", "internal-name": "git.novox.internal", "name-web": "git.example.org"}}}}}) - if err := bus.Put(context.Background(), meshprovisionerclient.PlansBucket, "g14.gitea", plan); err != nil { + if err := bus.Put(context.Background(), meshprovisionerclient.PlansBucketOf(provisionerModule), "g14.gitea", plan); err != nil { t.Fatal(err) } // The key the seat composed for the entry, asked of route-adapter (novox/hq ADR 0570 clauses 1, 3). @@ -61,7 +61,7 @@ func TestAConsumerOfARouteIsToldItsNamesFromItsPlanEntry(t *testing.T) { if err != nil { t.Fatal(err) } - if err := bus.Put(context.Background(), meshprovisionerclient.ProvisionsBucket, key, asked); err != nil { + if err := bus.Put(context.Background(), meshprovisionerclient.ProvisionsBucketOf(provisionerModule), key, asked); err != nil { t.Fatal(err) } p := newProvider("novox", bus, t.Logf) diff --git a/modules/route-adapter/cmd/route-provider/provisioner_names_test.go b/modules/route-adapter/cmd/route-provider/provisioner_names_test.go new file mode 100644 index 000000000..9a3325a0d --- /dev/null +++ b/modules/route-adapter/cmd/route-provider/provisioner_names_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "os" + "slices" + "testing" +) + +// The module holding the seat mesh-provisioner, which this provider reads the plans and provisions of, is the one its manifest's +// `reads` name: no client names a holder (novox/hq ADR 0625 §4). +func TestTheProvisionerIsTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Reads []string `json:"reads"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + for _, state := range []string{"plans", "provisions"} { + if !slices.Contains(m.Reads, provisionerModule+"."+state) { + t.Errorf("the provider reads %s.%s, its manifest's reads are %v", provisionerModule, state, m.Reads) + } + } +}