// Command paths holds the catalogue's modules to talking over the bus (novox/hq ADR 0260): no module // names a path that another module's manifest places under /run, /var/lib, /usr/local/lib or /etc. // Such a path is that module's state or configuration on its machine, and a second module reading or // writing it is two modules talking through a file, which the bus exists to replace. // // A module owns every `path` of its resources under those roots. Every other module's manifest, and // every file of its own but its documentation, its tests and its binaries, is searched for each as a // whole path: followed by nothing, a slash, or a character no path takes. A path a container sees // inside itself (the target of a `volumes` entry) is the container's, and is not searched. // // **A drop-in directory is no module's.** A directory whose name ends in `.d` (`/etc/sysctl.d`, // `/etc/systemd/system/.d`, `/etc/X11/xorg.conf.d`) is a place several modules each put their own // file, so naming it is not reaching into one module's state; the files in it are each their placer's. // // Two lists beside this file narrow it, each line a path or a finding and its reason after a `#`: // // - shared: paths every program on a machine reads as the machine's, not as a message from their // owner — /etc/hosts, which every resolver reads. // - known: the findings standing on the day the roots were widened (2026-10-08). The check fails on a // finding not listed, and on a listed one that no longer occurs, so the list only shrinks. // // **Neither list may grow unseen.** With `-base ` holding the base branch's copies of both, a line // the change adds is refused unless its reason says `allow:` and why — so an addition is a decision a // reviewer reads in the diff, not a way past the check. // // **What it cannot see:** a path built in code from pieces (`filepath.Join("/etc", name)`), one read // from the environment or a setting, and one reached through a shell the module runs. It reads text, // not what a program does; review and the runtime's grants cover the rest. // // go run . // // Exits 1 on a finding. package main import ( "bufio" "bytes" "encoding/json" "fmt" "io/fs" "os" "path/filepath" "regexp" "sort" "strings" ) // stateRoots are where a machine keeps a module's state and configuration. var stateRoots = []string{"/run/", "/var/lib/", "/usr/local/lib/", "/etc/"} // Finding is one module naming another's path. type Finding struct { Module, File, Path, Owner string } func (f Finding) String() string { return fmt.Sprintf("%s names %s in %s, and the path is %s's; modules talk over the bus (novox/hq ADR 0260)", f.Module, f.Path, f.File, f.Owner) } // Key is how a finding is listed in `known`: the module, the path and the file. func (f Finding) Key() string { return f.Module + " " + f.Path + " " + f.File } // containerTargets drops what a container sees inside itself from a manifest before it is searched: // the target half of every `volumes` entry of a container resource. func containerTargets(m map[string]any) { resources, _ := m["resources"].([]any) for _, raw := range resources { r, ok := raw.(map[string]any) if !ok || r["type"] != "container" { continue } volumes, _ := r["volumes"].([]any) for i, v := range volumes { if s, ok := v.(string); ok { if src, _, two := strings.Cut(s, ":"); two { volumes[i] = src } } if o, ok := v.(map[string]any); ok { delete(o, "target") delete(o, "container") } } } } // Check is every finding in the catalogue at root, before the lists narrow it. func Check(root string) ([]Finding, error) { files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } owner := map[string]string{} modules := map[string]string{} for _, f := range files { raw, err := os.ReadFile(f) if err != nil { return nil, err } var m struct { Module string `json:"module"` Resources []map[string]any `json:"resources"` } if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", f, err) } modules[m.Module] = filepath.Dir(f) for _, r := range m.Resources { p, _ := r["path"].(string) if r["type"] == "directory" && strings.HasSuffix(filepath.Base(p), ".d") { continue // a drop-in directory: several modules' place, no one module's } for _, root := range stateRoots { if strings.HasPrefix(p, root) && len(p) > len(root) { owner[strings.TrimRight(p, "/")] = m.Module } } } } paths := make([]string, 0, len(owner)) for p := range owner { paths = append(paths, p) } sort.Strings(paths) patterns := map[string]*regexp.Regexp{} for _, p := range paths { patterns[p] = regexp.MustCompile(regexp.QuoteMeta(p) + `(?:$|[^A-Za-z0-9._-])`) } names := make([]string, 0, len(modules)) for n := range modules { names = append(names, n) } sort.Strings(names) var out []Finding for _, module := range names { err := filepath.WalkDir(modules[module], func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } name := d.Name() if strings.HasSuffix(name, ".md") || strings.HasSuffix(name, "_test.go") || name == "go.sum" || name == "package-lock.json" { return nil } raw, err := os.ReadFile(path) if err != nil { return err } if bytes.IndexByte(raw, 0) >= 0 { return nil // a binary: what it says is in its source } if name == "module.json" { var m map[string]any if json.Unmarshal(raw, &m) == nil { containerTargets(m) raw, _ = json.Marshal(m) } } text := string(raw) for _, p := range paths { if owner[p] != module && patterns[p].MatchString(text) { rel, _ := filepath.Rel(root, path) out = append(out, Finding{Module: module, File: rel, Path: p, Owner: owner[p]}) } } return nil }) if err != nil { return nil, err } } return out, nil } // list reads one of the lists beside this check: each line's entry before a `#`, trimmed, with its // reason after it. func list(path string) (map[string]bool, map[string]string, error) { out, why := map[string]bool{}, map[string]string{} f, err := os.Open(path) if err != nil { return nil, nil, err } defer f.Close() s := bufio.NewScanner(f) for s.Scan() { line, reason, _ := strings.Cut(s.Text(), "#") if line = strings.TrimSpace(line); line != "" { out[line] = true why[line] = strings.TrimSpace(reason) } } return out, why, s.Err() } // Grown is every entry a list has that its base copy does not, and whose reason does not say `allow:`. func Grown(name string, now map[string]bool, why map[string]string, base map[string]bool) []string { var out []string for entry := range now { if !base[entry] && !strings.Contains(why[entry], "allow:") { out = append(out, fmt.Sprintf("checks/paths/%s gains %q, which the base branch's copy does not hold; "+ "say why after `# allow:` on its line, or keep it out", name, entry)) } } sort.Strings(out) return out } // Judge narrows the findings by the lists: what is left to refuse, and the known findings that no // longer occur, which must leave the list. func Judge(found []Finding, shared, known map[string]bool) (refused []Finding, gone []string) { seen := map[string]bool{} for _, f := range found { if shared[f.Path] { continue } seen[f.Key()] = true if !known[f.Key()] { refused = append(refused, f) } } for k := range known { if !seen[k] { gone = append(gone, k) } } sort.Strings(gone) return refused, gone } func main() { root, baseDir := ".", "" args := os.Args[1:] if len(args) >= 2 && args[0] == "-base" { baseDir, args = args[1], args[2:] } if len(args) > 0 { root = args[0] } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } here := filepath.Join(root, "checks", "paths") shared, sharedWhy, err1 := list(filepath.Join(here, "shared")) known, knownWhy, err2 := list(filepath.Join(here, "known")) if err1 != nil || err2 != nil { fmt.Fprintln(os.Stderr, "the lists beside the check cannot be read:", err1, err2) os.Exit(2) } var grown []string if baseDir == "" { fmt.Println("NOT COMPARED: no base branch's lists were given, so additions to them were not judged") } else { baseShared, _, err1 := list(filepath.Join(baseDir, "shared")) baseKnown, _, err2 := list(filepath.Join(baseDir, "known")) if err1 != nil || err2 != nil { fmt.Fprintln(os.Stderr, "the base branch's lists cannot be read:", err1, err2) os.Exit(2) } grown = append(Grown("shared", shared, sharedWhy, baseShared), Grown("known", known, knownWhy, baseKnown)...) } refused, gone := Judge(found, shared, known) for _, g := range grown { fmt.Println(g) } for _, f := range refused { fmt.Println(f) } for _, k := range gone { fmt.Printf("%q is in checks/paths/known and no longer occurs: take it out, so it cannot come back\n", k) } if len(refused) > 0 || len(gone) > 0 || len(grown) > 0 { os.Exit(1) } fmt.Printf("paths: no module names another's state on its machine (%d known, listed to go)\n", len(known)) }