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
This commit is contained in:
2026-11-05 19:36:21 +01:00
parent d630635229
commit 625d627794
21 changed files with 186 additions and 29 deletions
@@ -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"
@@ -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)
}
}
}
@@ -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,
@@ -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)}))
}
@@ -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) {}})
@@ -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"
@@ -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)}},
@@ -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)
}
}
}
@@ -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
@@ -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)
}
}
@@ -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"
}
+8 -1
View File
@@ -78,7 +78,14 @@ const ProvisionerPrefix = "provisioner."
// seat mesh-provisioner's state `plans`, `<node>.<module>` (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) {
+2 -3
View File
@@ -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{}{}:
@@ -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)
}
@@ -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)
@@ -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"
@@ -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)
}
}
}
@@ -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"
@@ -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{},
@@ -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)
@@ -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)
}
}
}