From f65d10d5739f77ce2fe34d59fd3833514867543b Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 18:22:32 +0100 Subject: [PATCH] =?UTF-8?q?The=20catalogue=20checks=20its=20placement=20fi?= =?UTF-8?q?les:=20each=20reads=20as=20its=20node's,=20and=20no=20port,=20a?= =?UTF-8?q?ddress,=20hub=20or=20seat=20is=20placed=20twice=20(issue=201126?= =?UTF-8?q?,=20ADR=200549=20=C2=A71,=20ADR=200555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- checks/placement/go.mod | 5 +++ checks/placement/go.sum | 14 ++++++ checks/placement/main.go | 80 ++++++++++++++++++++++++++++++++++ checks/placement/main_test.go | 55 +++++++++++++++++++++++ checks/placement/reads-outside | 1 + 5 files changed, 155 insertions(+) create mode 100644 checks/placement/go.mod create mode 100644 checks/placement/go.sum create mode 100644 checks/placement/main.go create mode 100644 checks/placement/main_test.go create mode 100644 checks/placement/reads-outside diff --git a/checks/placement/go.mod b/checks/placement/go.mod new file mode 100644 index 00000000..862c6310 --- /dev/null +++ b/checks/placement/go.mod @@ -0,0 +1,5 @@ +module checks/placement + +go 1.26.0 + +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026171759-a431746c0e8c diff --git a/checks/placement/go.sum b/checks/placement/go.sum new file mode 100644 index 00000000..21148b59 --- /dev/null +++ b/checks/placement/go.sum @@ -0,0 +1,14 @@ +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026171759-a431746c0e8c h1:zaSfYTJd03b0pF29QTZ6G5ajfjOXRXjHIpOiGu/niHA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026171759-a431746c0e8c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= +github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/checks/placement/main.go b/checks/placement/main.go new file mode 100644 index 00000000..659edcc6 --- /dev/null +++ b/checks/placement/main.go @@ -0,0 +1,80 @@ +// Command placement is the catalogue's check of its placement files (novox/hq ADR 0549 §1, ADR 0555; issue 1126): every +// `placement/.json` reads as a placement of the node its name says, and the files together are refused, every +// finding named at once, for two modules of one node on one host port, a pin, binding or holding naming a provider not +// placed where it says, a seat held once for the mesh held twice, two nodes with one address on the private network, or +// more than one hub (mesh-sdk delivery.CheckPlacements). +// +// A module the catalogue does not hold and a node not enrolled are the composition check's to refuse: placement names +// modules of every repository the indexer reads and the nodes the mesh enrolled, neither of which this tree holds. +// +// go run . +// +// Exits 1 on a finding. +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +// Check reads every placement file under root and answers each finding; none is a catalogue whose placement holds. A +// catalogue with no placement directory has none to check. +func Check(root string) ([]string, error) { + dir := filepath.Join(root, delivery.PlacementDir) + entries, err := os.ReadDir(dir) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + var found []string + placements := map[string]delivery.Placement{} + for _, e := range entries { + rel := delivery.PlacementDir + "/" + e.Name() + node, ok := delivery.PlacementFileNode(rel) + if e.IsDir() || !ok { + found = append(found, fmt.Sprintf("%s is not a placement file: the directory holds only .json, one per node", rel)) + continue + } + raw, err := os.ReadFile(filepath.Join(dir, e.Name())) + if err != nil { + return nil, err + } + p, err := delivery.DecodePlacement(raw) + if err != nil { + found = append(found, fmt.Sprintf("%s does not read as a placement: %v", rel, err)) + continue + } + if p.Revision != "" { + found = append(found, fmt.Sprintf("%s names a revision, which only the indexer writes into the key", rel)) + } + placements[node] = p + } + all := func(string) bool { return true } + found = append(found, delivery.CheckPlacements(placements, all, all)...) + sort.Strings(found) + return found, nil +} + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "usage: placement ") + os.Exit(2) + } + found, err := Check(os.Args[1]) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, f := range found { + fmt.Println(f) + } + if len(found) > 0 { + os.Exit(1) + } +} diff --git a/checks/placement/main_test.go b/checks/placement/main_test.go new file mode 100644 index 00000000..88e45aca --- /dev/null +++ b/checks/placement/main_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// The catalogue's placement files as the merge check reads them (ADR 0549 §1, ADR 0555): the real tree holds every +// rule, and a tree that breaks them is refused with every finding named at once. +func TestPlacementFiles(t *testing.T) { + found, err := Check("../..") + if err != nil { + t.Fatal(err) + } + for _, f := range found { + t.Errorf("%s (novox/hq ADR 0549 §1)", f) + } + + root := t.TempDir() + write := func(name, body string) { + t.Helper() + if err := os.MkdirAll(filepath.Join(root, "placement"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "placement", name), []byte(body), 0o644); err != nil { + t.Fatal(err) + } + } + if found, err := Check(root); err != nil || len(found) != 0 { + t.Fatalf("a catalogue with no placement directory: %v, %v", found, err) + } + write("ace.json", `{"v":1,"node":"ace","decided":{"hub":true,"address":"10.77.0.1"},"modules":[{"module":"a","ports":{"80":8080}},{"module":"b","ports":{"81":8080}}]}`) + write("g14.json", `{"v":1,"node":"g14","decided":{"hub":true,"address":"10.77.0.1"},"modules":[{"module":"c","bindings":{"pg":{"node":"ace","module":"pg"}}}]}`) + write("novox.json", `{"v":1,"node":"shanks","modules":[]}`) + write("bad.json", `{"v":1,"node":"bad","modules":{}}`) + write("dated.json", `{"v":1,"node":"dated","revision":"abc","modules":[]}`) + write("notes.md", "x") + found, err = Check(root) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{"a and b are both published on host port 8080", "bound to ace/pg", "both have the address 10.77.0.1", + "ace, g14 are each placed as it", "placement file of novox names the node", "bad.json does not read", "dated.json names a revision", + "notes.md is not a placement file"} { + hit := false + for _, f := range found { + hit = hit || strings.Contains(f, want) + } + if !hit { + t.Errorf("no finding says %q; found %v", want, found) + } + } +} diff --git a/checks/placement/reads-outside b/checks/placement/reads-outside new file mode 100644 index 00000000..f6b2d871 --- /dev/null +++ b/checks/placement/reads-outside @@ -0,0 +1 @@ +These tests read files outside this directory (the whole catalogue, the repository, or its root files): mesh-build-go never keeps this package's verdict (issue 1935).