// Command noplacement refuses what the repository path of placement left behind (novox/hq ADR 0567 §10, §12). // // Placement, which module runs on which node, is bus state held by the seat mesh-placement; no file of the // catalogue says it, and no manifest reads it from the delivery indexer, which no longer keeps it. It refuses: // // 1. a `placement` directory (or file) at the root of the catalogue; // // 2. a `particulars` file at the root (the mesh's node names come from the checker's run, §16); // // 3. a manifest naming a state, stream, event or bucket of the indexer's placement (`mesh-delivery-indexer.placement…` // or `mesh-delivery-indexer_placement…`) in any string value, such as `reads`; // // 4. a manifest, other than mesh-controller's, that reads the controller's assignments (`mesh-controller.assignments`): // every reader takes placement from the seat mesh-placement (novox/hq ADR 0549 §3, ADR 0567), and the lease write is // refused while one still reads them (ADR 0549 §6; issue 774). The modules that still read them for what the // placement does not carry are named in checks/noplacement/assignments-readers, each with why; that list only // shrinks: an entry whose module no longer reads them is refused too. // // go run . // // Exits 1 on a finding. package main import ( "encoding/json" "fmt" "os" "path/filepath" "sort" "strings" ) // Prefixes are the names of the indexer's placement parts; a manifest string starting with one names them. var Prefixes = []string{"mesh-delivery-indexer.placement", "mesh-delivery-indexer_placement"} // Findings is every refusal in the catalogue at root, sorted. func Findings(root string) ([]string, error) { var out []string if _, err := os.Lstat(filepath.Join(root, "placement")); err == nil { out = append(out, "placement: the repository holds no placement; it is bus state of the seat mesh-placement (novox/hq ADR 0567 §10)") } if _, err := os.Lstat(filepath.Join(root, "particulars")); err == nil { out = append(out, "particulars: the repository lists no node names; the checker hands them to the check (novox/hq ADR 0567 §16)") } manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(manifests) for _, file := range manifests { raw, err := os.ReadFile(file) if err != nil { return nil, err } var doc any if err := json.Unmarshal(raw, &doc); err != nil { return nil, fmt.Errorf("%s: %w", file, err) } module := filepath.Base(filepath.Dir(file)) for _, s := range named(doc, nil) { out = append(out, fmt.Sprintf("%s: names %q, which the indexer no longer keeps; placement is the seat mesh-placement's (novox/hq ADR 0567 §12)", module, s)) } } readers, err := assignmentsReaders(root) if err != nil { return nil, err } out = append(out, readers...) sort.Strings(out) return out, nil } // AssignmentsRead is the controller's assignments as a manifest reads them, and Allowed the file, relative to the // catalogue's root, that names the modules still reading them: ` ` per line, `#` a comment. const ( AssignmentsRead = "mesh-controller.assignments" Allowed = "checks/noplacement/assignments-readers" ) // assignmentsReaders is every finding on the reads of the controller's assignments: a module other than the controller // reading them that Allowed does not name, and a module Allowed names that no longer reads them (issue 774). func assignmentsReaders(root string) ([]string, error) { allowed := map[string]bool{} raw, err := os.ReadFile(filepath.Join(root, Allowed)) if err != nil && !os.IsNotExist(err) { return nil, err } for _, line := range strings.Split(string(raw), "\n") { if f := strings.Fields(line); len(f) > 0 && !strings.HasPrefix(f[0], "#") { allowed[f[0]] = true } } manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } var found []string reads := map[string]bool{} for _, path := range manifests { raw, err := os.ReadFile(path) if err != nil { return nil, err } var m struct { Module string `json:"module"` Reads []string `json:"reads"` } if json.Unmarshal(raw, &m) != nil || m.Module == "mesh-controller" { continue // a manifest that does not read is the manifest checks' to refuse } for _, r := range m.Reads { if strings.TrimSpace(r) != AssignmentsRead { continue } reads[m.Module] = true if !allowed[m.Module] { found = append(found, fmt.Sprintf("%s reads %s: read where modules run from mesh-delivery-assigner.placement, "+ "the seat mesh-placement's (novox/hq ADR 0549 §3, ADR 0567); the lease write is refused while a module reads "+ "the assignments (ADR 0549 §6)", m.Module, AssignmentsRead)) } } } for module := range allowed { if !reads[module] { found = append(found, fmt.Sprintf("%s names %s, which no longer reads %s: take it off the list, which only shrinks", Allowed, module, AssignmentsRead)) } } return found, nil } // named collects the strings of a decoded manifest that start with one of the Prefixes. func named(v any, into []string) []string { switch v := v.(type) { case string: for _, p := range Prefixes { if strings.HasPrefix(v, p) { return append(into, v) } } case []any: for _, e := range v { into = named(e, into) } case map[string]any: for _, e := range v { into = named(e, into) } } return into } func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "usage: noplacement ") os.Exit(2) } found, err := Findings(os.Args[1]) if err != nil { fmt.Println(err) os.Exit(1) } for _, f := range found { fmt.Println(f) } if len(found) > 0 { os.Exit(1) } fmt.Println("no placement directory, no manifest reads the indexer's placement, and only the modules listed read the controller's assignments") }