From dd9b6a04858eb5e6b87d116ee2c83d361259bb45 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 29 Oct 2026 22:50:06 +0100 Subject: [PATCH] checks/credentialform: a module that provides and keeps a bound state must be read by mesh-provisioner (issue 1952) --- checks/credentialform/main.go | 34 ++++++++++++++++++++++++++++++ checks/credentialform/main_test.go | 11 ++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/checks/credentialform/main.go b/checks/credentialform/main.go index 836dfda4..e5a15729 100644 --- a/checks/credentialform/main.go +++ b/checks/credentialform/main.go @@ -21,6 +21,8 @@ func Problems(root string) ([]string, error) { } sort.Strings(files) var out []string + reads := map[string]bool{} + var binders []string for _, f := range files { raw, err := os.ReadFile(f) if err != nil { @@ -31,6 +33,38 @@ func Problems(root string) ([]string, error) { return nil, fmt.Errorf("%s: %w", f, err) } out = append(out, catalogue.CredentialFormProblems(m)...) + var kept struct { + Reads []string `json:"reads"` + State []json.RawMessage `json:"state"` + } + _ = json.Unmarshal(raw, &kept) // reads may hold objects in another module; only the provisioner's are read + if m.Module == "mesh-provisioner" { + for _, r := range kept.Reads { + reads[r] = true + } + } + if len(m.Provides) > 0 && keepsBound(kept.State) { + binders = append(binders, m.Module) + } + } + for _, module := range binders { + if !reads[module+".bound"] { + out = append(out, fmt.Sprintf("%s provides and keeps a `bound` state, and mesh-provisioner's manifest does not read %s.bound", module, module)) + } } return out, nil } + +// keepsBound says whether a manifest's `state` names `bound`, as a plain name or an object with a name. +func keepsBound(state []json.RawMessage) bool { + for _, s := range state { + var name string + var obj struct { + Name string `json:"name"` + } + if json.Unmarshal(s, &name) == nil && name == "bound" || json.Unmarshal(s, &obj) == nil && obj.Name == "bound" { + return true + } + } + return false +} diff --git a/checks/credentialform/main_test.go b/checks/credentialform/main_test.go index 0038a018..33c1c4f4 100644 --- a/checks/credentialform/main_test.go +++ b/checks/credentialform/main_test.go @@ -26,15 +26,18 @@ func aCatalogue(t *testing.T, manifests map[string]string) string { // catalogue is clean. func TestEveryOfferSaysTheFormOfItsCredential(t *testing.T) { root := aCatalogue(t, map[string]string{ - "silent": `{"module":"silent","provides":[{"name":"thing","scope":"mesh"}]}`, - "says": `{"module":"says","provides":[{"name":"a","credential":{"form":"pair"}},{"name":"b","credential":{"form":"none"}},{"name":"c","credential":{"own":"jwt"}}]}`, + "silent": `{"module":"silent","provides":[{"name":"thing","scope":"mesh"}]}`, + "binder": `{"module":"binder","provides":[{"name":"d","credential":{"form":"pair"}}],"state":["bound"]}`, + "mesh-provisioner": `{"module":"mesh-provisioner","reads":["other.bound"]}`, + "says": `{"module":"says","provides":[{"name":"a","credential":{"form":"pair"}},{"name":"b","credential":{"form":"none"}},{"name":"c","credential":{"own":"jwt"}}]}`, }) got, err := Problems(root) if err != nil { t.Fatal(err) } - if len(got) != 1 || !strings.Contains(got[0], `silent provides "thing"`) { - t.Fatalf("refused %v, wanted the one silent offer", got) + if len(got) != 2 || !strings.Contains(got[0], `silent provides "thing"`) || + !strings.Contains(got[1], "binder") || !strings.Contains(got[1], "mesh-provisioner") { + t.Fatalf("refused %v, wanted the silent offer and the binder the provisioner does not read", got) } real, err := Problems(filepath.Join("..", "..")) if err != nil {