diff --git a/checks/manifests/go.mod b/checks/manifests/go.mod new file mode 100644 index 00000000..6fbb4f39 --- /dev/null +++ b/checks/manifests/go.mod @@ -0,0 +1,5 @@ +module checks/manifests + +go 1.26.0 + +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030181247-4c1384faefa0 diff --git a/checks/manifests/go.sum b/checks/manifests/go.sum new file mode 100644 index 00000000..fc5109a2 --- /dev/null +++ b/checks/manifests/go.sum @@ -0,0 +1,2 @@ +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030181247-4c1384faefa0 h1:t04baH2Xz/NUsOEV3ctKY59XjiY5WmEuststvH+XeOs= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030181247-4c1384faefa0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= diff --git a/checks/manifests/main.go b/checks/manifests/main.go new file mode 100644 index 00000000..9658b260 --- /dev/null +++ b/checks/manifests/main.go @@ -0,0 +1,61 @@ +// Package manifests judges every module's manifest as the node-engine reads it, and the catalogue as one shelf: each +// modules/*/module.json through mesh-sdk's ParseManifestWith against the predefined.json at the root, with its templates +// read from the tree beside it (WithTemplatesFrom), then the rules between manifests (CatalogueProblems, +// IdentityProblems, DataProblems). +// +// Why it is here (issue 774): on 2026-10-30 mesh-catalog ca8d6f33 gave power's bar item two attributes its bar places +// by none of. ParseManifest refuses that, and every node-engine refused to compose at 19:13; no check of the pull +// request had parsed the manifest. That judgement was mesh-controller's `module check`, which the merge gate ran over +// every manifest before the checker moved to mesh-delivery; the checker runs this repository's Go tests and the +// composition, and no test parsed a manifest whole. This one does, in the same functions the nodes use. +package manifests + +import ( + "fmt" + "os" + "path/filepath" + "sort" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// Problems is what is wrong with the manifests under root/modules, one line each: a manifest the mesh refuses, named by +// its path, then the rules between the manifests that parse. +func Problems(root string) ([]string, error) { + files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) + if err != nil { + return nil, err + } + sort.Strings(files) + list, err := catalogue.ReadPredefined(root) + if err != nil { + return nil, err + } + var out []string + shelf := catalogue.Shelf{} + for _, f := range files { + raw, err := os.ReadFile(f) + if err != nil { + return nil, err + } + rel, _ := filepath.Rel(root, f) + m, err := catalogue.ParseManifestWith(raw, list) + if err == nil { + m, err = catalogue.WithTemplatesFrom(m, filepath.Dir(f)) + } + if err != nil { + out = append(out, fmt.Sprintf("%s: %v", rel, err)) + continue + } + if _, twice := shelf[m.Module]; twice { + out = append(out, fmt.Sprintf("%s: %s was already given; two manifests name one module", rel, m.Module)) + continue + } + shelf[m.Module] = m + } + between := catalogue.CatalogueProblems(shelf) + between = append(between, catalogue.IdentityProblems(shelf, catalogue.DefaultLongestMachine)...) + between = append(between, catalogue.DataProblems(shelf)...) + sort.Strings(between) + return append(out, between...), nil +} diff --git a/checks/manifests/main_test.go b/checks/manifests/main_test.go new file mode 100644 index 00000000..49ba2ff6 --- /dev/null +++ b/checks/manifests/main_test.go @@ -0,0 +1,51 @@ +package manifests + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// Every manifest of the catalogue is one the nodes can use, and the catalogue holds together as one shelf. A row is a +// manifest written into a catalogue of its own: power's bar item as mesh-catalog ca8d6f33 gave it (issue 774) is +// refused naming the attribute, and the same item without it passes. +func TestEveryManifestIsOneTheNodesCanUse(t *testing.T) { + for _, row := range []struct { + name, attributes, refused string + }{ + {name: "the bar places by bar and place", attributes: `"bar":"bottom","place":"status","order":"45"`}, + {name: "an attribute the bar places by none of (ca8d6f33)", + attributes: `"bar":"bottom","place":"status","order":"45","shows":"state","state":"power.draw"`, + refused: `"shows" is none of them`}, + } { + root := t.TempDir() + dir := filepath.Join(root, "modules", "bar-user") + if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil { + t.Fatal(err) + } + manifest := `{"module":"bar-user","version":"1","moved":true,` + + `"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},` + + `"items":[{"collection":"contribution:node-bar:block","attributes":{` + row.attributes + `},"template":"files/bar"}]}` + for path, body := range map[string]string{"module.json": manifest, "files/bar": "{\"shows\":\"command\"}\n"} { + if err := os.WriteFile(filepath.Join(dir, path), []byte(body), 0o644); err != nil { + t.Fatal(err) + } + } + got, err := Problems(root) + if err != nil { + t.Fatal(err) + } + joined := strings.Join(got, "\n") + if (row.refused == "") != (len(got) == 0) || !strings.Contains(joined, row.refused) { + t.Errorf("%s: refused %q, want %q", row.name, joined, row.refused) + } + } + real, err := Problems(filepath.Join("..", "..")) + if err != nil { + t.Fatal(err) + } + if len(real) != 0 { + t.Fatalf("the catalogue holds manifests the nodes cannot use:\n%s", strings.Join(real, "\n")) + } +} diff --git a/checks/manifests/reads-outside b/checks/manifests/reads-outside new file mode 100644 index 00000000..e0af3e80 --- /dev/null +++ b/checks/manifests/reads-outside @@ -0,0 +1,4 @@ +# What these tests read outside their package, as paths from the repository's root (issue 1936). +# every module's directory (its manifest and the templates beside it), and the list of predefined keys. +modules +predefined.json