diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 01e212089..8eb81cf67 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -85,7 +85,12 @@ const ( // not read here leaves its keys asked, saying its entries are not read. route-proxy (mesh-control) and node-tools // (mesh-tools) answer route and mcp-endpoint by the provider helper from outside this catalogue (issue 774, sweep of // 2026-10-30 20:10: their keys stood asked on every node). -var BoundReads = []string{"gitea", "keycloak", "postgres", "route-adapter", "route-proxy", "node-tools", "mesh-vault"} +var BoundReads = []string{"gitea", "keycloak", "postgres", "route-adapter", "route-proxy", "node-tools", vaultModule} + +// vaultModule is the holder of the seat mesh-vault, the provider of every credential: named here from this module's own +// manifest (its `reads` has mesh-vault.bound; TestTheManifestDeclaresWhatTheCodeKeeps), since no client names a holder +// (novox/hq ADR 0625 §4). +const vaultModule = "mesh-vault" // boundRead is a provider's entries as this module reads them. func boundRead(module string) string { return module + ".bound" } @@ -299,7 +304,7 @@ func (h *Holder) ComposeNodeWith(ctx context.Context, node string, retireMany bo continue } cred := meshprovisionerclient.Provision{Provision: cp, Local: cl, Consumer: meshprovisionerclient.End{Module: cm, Node: cn}, - Provider: meshprovisionerclient.End{Module: meshprovisionerclient.VaultModule, Node: vaultNode}} + Provider: meshprovisionerclient.End{Module: vaultModule, Node: vaultNode}} if cn == node { if _, seen := needed[ck]; !seen { needed[ck] = cred @@ -404,7 +409,7 @@ func (h *Holder) providerPlan(p catalogue.PlanProvision, node string, here map[s // vaultNode is the node mesh-vault is placed on: the provider of every credential. func (h *Holder) vaultNode() string { - nodes := h.placedOn(meshprovisionerclient.VaultModule) + nodes := h.placedOn(vaultModule) if len(nodes) == 0 { return "" } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go index 4938b8ea6..e3efd7caa 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go @@ -134,9 +134,9 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) { published(t, mem, c) vaultManifest := `{"module":"mesh-vault","version":"1","state":["bound"]}` // the vault keeps its outcomes as entries vaultDigest := catalogclient.ManifestDigest([]byte(vaultManifest)) - vaultValue, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: meshprovisionerclient.VaultModule, Manifest: vaultManifest}) + vaultValue, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: vaultModule, Manifest: vaultManifest}) mem.write(t, ManifestsRead, mustKey(catalogclient.ManifestsKey(vaultDigest)), string(vaultValue)) - mem.write(t, ModulesRead, meshprovisionerclient.VaultModule, `{"v":1,"none2":"the test's","manifest":"`+vaultDigest+`"}`) + mem.write(t, ModulesRead, vaultModule, `{"v":1,"none2":"the test's","manifest":"`+vaultDigest+`"}`) for state, keys := range mem.states { for key, raw := range keys { if err := s.Put(state, key, json.RawMessage(raw)); err != nil { @@ -217,7 +217,7 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) { } // The providers' outcomes: the vault provides the credential where restic has one, postgres runs healthy. for _, dep := range state(key).DependsOn { - if err := s.Put(boundRead(meshprovisionerclient.VaultModule), dep, map[string]any{"state": "provided"}); err != nil { + if err := s.Put(boundRead(vaultModule), dep, map[string]any{"state": "provided"}); err != nil { t.Fatal(err) } } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go index 4df58189f..bc28baf93 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go @@ -334,6 +334,10 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) { t.Errorf("the manifest lists %s, which is not served", tool) } } + // The vault the holder places credentials on is the holder its manifest reads the bound outcomes of. + if !slices.Contains(m.Reads, vaultModule+".bound") || !slices.Contains(BoundReads, vaultModule) { + t.Errorf("the vault is %q: the manifest reads %v and the holder reads the bound states of %v", vaultModule, m.Reads, BoundReads) + } // The builds read serves only haveOutcome's offer check (ADR 0603 clause 5: which manifest the build a provider runs // was made from) and makes no plan, so ADR 0593 §8 ("it composes nothing") holds. // The assigner's placement and wants and the indexer's modules and manifests are asked once of their owners' read diff --git a/modules/mesh-self-check/cmd/mesh-self-check/main.go b/modules/mesh-self-check/cmd/mesh-self-check/main.go index 119da72cd..bbe266a99 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/main.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/main.go @@ -12,7 +12,6 @@ import ( "os" "time" - assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" @@ -78,8 +77,8 @@ func NewChecker() *Checker { // self-check's own (first-view-unanswered), written into its state findings. func newViews() *views.Reader { r := &views.Reader{Module: Module, Logf: logf, Findings: meshconditionsclient.FindingWriter{State: stdio.State("findings")}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}} - r.Follow(assignerclient.Module, placing.StatePlacement, PlacementTool) - r.Follow(assignerclient.Module, placing.StateSeatHolders, SeatHoldersTool) + r.Follow(assignerModule, placing.StatePlacement, PlacementTool) + r.Follow(assignerModule, placing.StateSeatHolders, SeatHoldersTool) r.Follow(indexerModule, "modules", ModulesTool) r.Follow(indexerModule, "manifests", ManifestsTool) return r diff --git a/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go b/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go index effb10828..ac0c5b8ac 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "slices" "testing" + + placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" ) type manifest struct { @@ -69,6 +71,14 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { if !slices.Equal(m.Consume, Consumes) { t.Errorf("consumes %v, want the views families the probes follow, %v", m.Consume, Consumes) } + for _, view := range []string{assignerModule + ".views." + placing.StatePlacement, assignerModule + ".views." + placing.StateSeatHolders} { + if !slices.Contains(m.Consume, view) { + t.Errorf("the probes follow %s, the manifest consumes %v", view, m.Consume) + } + } + if PlacementRead != "mesh-delivery-assigner.placement" || SeatHoldersRead != "mesh-delivery-assigner.seat-holders" { + t.Errorf("the states are read as %s and %s", PlacementRead, SeatHoldersRead) + } states := map[string]int{} for _, s := range m.State { var d struct { diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go index b0fd1d722..53607686e 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go @@ -26,8 +26,8 @@ import ( // The states the machine probes read (declared under `reads`), and the verb D8 asks each node. const ( - PlacementRead = assignerclient.Module + "." + "placement" - SeatHoldersRead = assignerclient.SeatHoldersRead + PlacementRead = assignerModule + "." + placing.StatePlacement + SeatHoldersRead = assignerModule + "." + placing.StateSeatHolders PresenceRead = watchmanModule + ".presence" BannedSeat = "node-intrusion-prevention" BannedVerb = "seat:" + BannedSeat + ".banned" @@ -226,7 +226,9 @@ func probeBans(ctx context.Context, c *Checker) ([]meshconditionsclient.Observat // The holders whose states the probes read, one constant each (a reader names its holder once; ADR 0634 §4). const ( - watchmanModule = "mesh-delivery-watchman" + watchmanModule = "mesh-delivery-watchman" + // assignerModule is the holder of the seat mesh-module-assigner, named from this module's manifest (consumes: its views). + assignerModule = "mesh-delivery-assigner" indexerModule = "mesh-delivery-indexer" nodeEngineModule = "mesh-host" ) diff --git a/modules/mesh-vault/cmd/mesh-vault/outcomes.go b/modules/mesh-vault/cmd/mesh-vault/outcomes.go index 95659f173..1baf86db1 100644 --- a/modules/mesh-vault/cmd/mesh-vault/outcomes.go +++ b/modules/mesh-vault/cmd/mesh-vault/outcomes.go @@ -122,12 +122,12 @@ func (v *Vault) outcomes() (map[string]BoundOutcome, error) { consumer := meshprovisionerclient.End{Module: a.Module, Node: a.Node} for _, p := range a.Provisioning.Provisions { provider := meshprovisionerclient.End{Module: p.ProviderModule, Node: p.Provider} - if p.ProviderModule == meshprovisionerclient.VaultModule && p.Provision == "secret" { + if p.ProviderModule == vaultModule && p.Provision == "secret" { // The vault's own offer: a value it hands the consumer's client on request (`get`), which it serves as // soon as it runs. key, err := meshprovisionerclient.Key(a.Node, a.Module, p.Provision, p.Local) if err == nil { - out[key] = BoundOutcome{Provider: p.Provider, ProviderModule: meshprovisionerclient.VaultModule, Provision: p.Provision, + out[key] = BoundOutcome{Provider: p.Provider, ProviderModule: vaultModule, Provision: p.Provision, Consumer: a.Node, Module: a.Module, Local: p.Local, State: meshprovisionerclient.Provided, Why: "the vault hands it to the consumer's client on request"} } @@ -143,7 +143,7 @@ func (v *Vault) outcomes() (map[string]BoundOutcome, error) { if err != nil || name == "" { continue } - o := BoundOutcome{Provider: p.Provider, ProviderModule: meshprovisionerclient.VaultModule, Provision: cp, Consumer: cn, + o := BoundOutcome{Provider: p.Provider, ProviderModule: vaultModule, Provision: cp, Consumer: cn, Module: cm, Local: cl, VaultName: name} rec, held, err := v.Store.Read(name) switch { diff --git a/modules/mesh-vault/cmd/mesh-vault/pair.go b/modules/mesh-vault/cmd/mesh-vault/pair.go index 51710b66e..06a62d16b 100644 --- a/modules/mesh-vault/cmd/mesh-vault/pair.go +++ b/modules/mesh-vault/cmd/mesh-vault/pair.go @@ -866,6 +866,6 @@ func (e *Env) credentialsState() []PairOutcome { // §1, §6): a provision `secret-` whose provider is the vault, in state asked. func credentialAsked(raw []byte) bool { p, err := meshprovisionerclient.Decode(raw) - return err == nil && p.State == meshprovisionerclient.Asked && p.Provider.Module == meshprovisionerclient.VaultModule && + return err == nil && p.State == meshprovisionerclient.Asked && p.Provider.Module == vaultModule && strings.HasPrefix(p.Provision, catalogue.CredentialProvisionPrefix) } diff --git a/modules/mesh-vault/cmd/mesh-vault/placed.go b/modules/mesh-vault/cmd/mesh-vault/placed.go index febd082e1..8d22ea678 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed.go @@ -16,9 +16,6 @@ import ( "strings" ) -// PlacementRead is the seat mesh-module-assigner's placement, as the vault's manifest reads it (mesh-sdk's contract). -const PlacementRead = assignerclient.PlacementRead - // PlacementReader reads the seat's placement: every key, and one key's value (nil when there is none). type PlacementReader struct { Keys func() ([]string, error) diff --git a/modules/mesh-vault/cmd/mesh-vault/placed_test.go b/modules/mesh-vault/cmd/mesh-vault/placed_test.go index 36c0aeef9..c4dd40374 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed_test.go @@ -1,11 +1,15 @@ package main import ( + "encoding/json" + "os" + "slices" + "strings" + "testing" + vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "strings" - "testing" ) // placementsOf is the seat mesh-module-assigner's placement of each node, as the vault reads it. @@ -67,3 +71,22 @@ func TestTheVaultReadsWhereModulesStandFromPlacement(t *testing.T) { t.Errorf("two bus nodes: %v", err) } } + +// The assigner's tool and views family the vault names are the ones its manifest invokes and consumes; no client names a +// holder (novox/hq ADR 0625 §4). +func TestThePlacementNamesAreTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Invokes []string `json:"invokes"` + Consumes []string `json:"consumes"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + 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/placementview.go b/modules/mesh-vault/cmd/mesh-vault/placementview.go index d4d224d08..068dbfb1f 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placementview.go +++ b/modules/mesh-vault/cmd/mesh-vault/placementview.go @@ -10,7 +10,6 @@ import ( "context" "encoding/json" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" "sort" "strings" "time" @@ -22,8 +21,11 @@ import ( // PlacementTool is the assigner's ordinary read tool of its placement; PlacementViewsEvent the event of its views family. const ( - PlacementTool = assignerclient.Module + ".assigner_read_placement" - PlacementViewsEvent = assignerclient.Module + ".views.placement" + // assignerModule is the holder of the seat mesh-module-assigner, named from this module's manifest (invokes its read tool, + // consumes its views; TestThePlacementNamesAreTheManifests). + assignerModule = "mesh-delivery-assigner" + PlacementTool = assignerModule + ".assigner_read_placement" + PlacementViewsEvent = assignerModule + ".views.placement" FirstViewsTool = "vault_first_views" FirstViewAgainTool = "vault_first_view_again" ) @@ -38,7 +40,7 @@ var placementView *views.Follower // it is held (a first view's keys too). func newPlacementView(ask func(key string, body any) (json.RawMessage, error), gone func(key string), changed func(), findings views.FindingWriter) *views.Follower { - return &views.Follower{Owner: assignerclient.Module, Tool: PlacementTool, State: "placement", Logf: say, + return &views.Follower{Owner: assignerModule, Tool: PlacementTool, State: "placement", Logf: say, Ask: func(_ context.Context, after string) (views.Answer, error) { body := map[string]any{"values": true} if after != "" {