// Command environment refuses a module's code reading a MESH_* word of its environment that nothing gives it: not its // manifest, and not the node-engine or the tool runner for every process (the lead's ruling on A3n, issue 1966). // // A MESH_* word is the mesh's contract with a module's process: the manifest sets it (an `env` of a build artifact or a // resource, an `environment`'s `variables`, a `NAME=` line of a file the module ships for an env-file), or the mesh gives // it to every process (Given). A word read and set by nothing is a contract nobody keeps: the program runs on its // default or on nothing, and says so to nobody (MESH_REGISTRY_REACH answered every published reference without its // registry; MESH_CONFIG_CALLERS held every window shut). Found here, it is refused: the code reads what the mesh gives // (a manifest word, a config value through its tools, a seat's name), or the manifest sets it. // // A read is `os.Getenv` or `os.LookupEnv` of a literal or of a constant of the same directory (Go), and `env.MESH_*` or // `env["MESH_*"]` (TypeScript, `process.env` included); tests, test directories, node_modules and dist are not read. The // modules still reading such a word are listed in the file `unset` beside this check, ` ` a line, until // each is fixed: a line no longer found is refused, so the list only shrinks. // // go run . // // Exits 1 on a finding. package main import ( "bufio" "encoding/json" "fmt" "io/fs" "os" "path/filepath" "regexp" "sort" "strings" ) // Given is every MESH_* word the mesh gives a module's processes whatever their manifest says, and who gives it. var Given = map[string]string{ // The tool runner, to every bundle and to a module's own runtime (mesh-tools node-tools runtime.BundleEnv). "MESH_MODULE": "the tool runner", "MESH_SERVED_MODULE": "the tool runner", "MESH_NODE": "the tool runner", // The node-engine, to the tool runner it runs as the operator's account; its bundles inherit them (mesh-host // compose/resolution runtime.go). "MESH_OPERATOR_ACCOUNT": "the node-engine", "MESH_OPERATOR_HOME": "the node-engine", // The node-engine, to every module that yields to the node's builds (ADR 0553 ยง3; catalogue.BuildCPUShareWord). "MESH_BUILD_CPU_SHARE": "the node-engine", } const listFile = "unset" var ( goRead = regexp.MustCompile(`os\.(?:Getenv|LookupEnv)\(\s*(?:"(MESH_[A-Z0-9_]+)"|([A-Za-z_][A-Za-z0-9_]*))\s*\)`) goConst = regexp.MustCompile(`\b([A-Za-z_][A-Za-z0-9_]*)\s*(?:string\s*)?=\s*"(MESH_[A-Z0-9_]+)"`) tsRead = regexp.MustCompile(`\benv(?:\.|\[\s*["'])(MESH_[A-Z0-9_]+)`) envLine = regexp.MustCompile(`(?m)^\s*(?:export\s+)?(MESH_[A-Z0-9_]+)=`) ) // skipped are the directories whose code no process of the module runs. var skipped = map[string]bool{"node_modules": true, "dist": true, "test": true, "testdata": true, ".git": true} // Check answers, per module, each MESH_* word its code reads that its manifest does not set and the mesh does not // give, with the files reading it. func Check(root string) (map[string]map[string][]string, error) { manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(manifests) out := map[string]map[string][]string{} for _, path := range manifests { dir := filepath.Dir(path) raw, err := os.ReadFile(path) if err != nil { return nil, err } var manifest any if err := json.Unmarshal(raw, &manifest); err != nil { return nil, fmt.Errorf("%s: %w", path, err) } set := map[string]bool{} setBy(manifest, set) reads := map[string]map[string]bool{} consts := map[string]map[string]string{} // directory -> constant -> word var goFiles []string err = filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if p != dir && skipped[d.Name()] { return filepath.SkipDir } return nil } name := d.Name() text, err := os.ReadFile(p) if err != nil { return err } rel, _ := filepath.Rel(dir, p) rel = filepath.ToSlash(rel) switch { case strings.HasSuffix(name, "_test.go"), strings.HasSuffix(name, ".test.ts"), strings.HasSuffix(name, ".d.ts"): case strings.HasSuffix(name, ".go"): goFiles = append(goFiles, p) c := consts[filepath.Dir(p)] if c == nil { c = map[string]string{} consts[filepath.Dir(p)] = c } for _, m := range goConst.FindAllStringSubmatch(string(text), -1) { c[m[1]] = m[2] } case strings.HasSuffix(name, ".ts"): for _, m := range tsRead.FindAllStringSubmatch(string(text), -1) { read(reads, m[1], rel) } case name != "module.json": // A file the module ships: a NAME= line is a word an env-file gives. for _, m := range envLine.FindAllStringSubmatch(string(text), -1) { set[m[1]] = true } } return nil }) if err != nil { return nil, err } for _, p := range goFiles { text, _ := os.ReadFile(p) rel, _ := filepath.Rel(dir, p) for _, m := range goRead.FindAllStringSubmatch(string(text), -1) { word := m[1] if word == "" { word = consts[filepath.Dir(p)][m[2]] } if word != "" { read(reads, word, filepath.ToSlash(rel)) } } } module := filepath.Base(dir) for word, files := range reads { if set[word] || Given[word] != "" { continue } if out[module] == nil { out[module] = map[string][]string{} } for f := range files { out[module][word] = append(out[module][word], f) } sort.Strings(out[module][word]) } } return out, nil } func read(reads map[string]map[string]bool, word, file string) { if reads[word] == nil { reads[word] = map[string]bool{} } reads[word][file] = true } // setBy collects the words a manifest sets: every key of an `env` object and of an `environment`'s `variables`. func setBy(v any, set map[string]bool) { switch v := v.(type) { case map[string]any: for k, e := range v { if m, ok := e.(map[string]any); ok && (k == "env" || k == "variables") { for word := range m { set[word] = true } } setBy(e, set) } case []any: for _, e := range v { setBy(e, set) } } } // Listed is the list's ` ` lines; a line starting with # and a blank line are not entries. func Listed(path string) (map[string]bool, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() out := map[string]bool{} s := bufio.NewScanner(f) for s.Scan() { line := strings.Join(strings.Fields(s.Text()), " ") if line == "" || strings.HasPrefix(line, "#") { continue } out[line] = true } return out, s.Err() } // Judge answers the reads not on the list (refused) and the list's lines no read stands for (stale). func Judge(found map[string]map[string][]string, listed map[string]bool) (refused, stale []string) { seen := map[string]bool{} for module, words := range found { for word := range words { line := module + " " + word seen[line] = true if !listed[line] { refused = append(refused, line) } } } for line := range listed { if !seen[line] { stale = append(stale, line) } } sort.Strings(refused) sort.Strings(stale) return refused, stale } func main() { root := "." if len(os.Args) > 1 { root = os.Args[1] } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } listed, err := Listed(listFile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } refused, stale := Judge(found, listed) for _, line := range refused { module, word, _ := strings.Cut(line, " ") fmt.Printf("%s reads %s (%s), which its manifest does not set and the mesh gives no process: set it in the "+ "manifest, or read what the mesh gives\n", module, word, strings.Join(found[module][word], ", ")) } for _, line := range stale { fmt.Printf("checks/environment/%s names %q, which no code reads unset any more: take it off the list\n", listFile, line) } if len(refused) > 0 || len(stale) > 0 { os.Exit(1) } fmt.Printf("environment: every MESH_* word a module reads is set by its manifest or given by the mesh (%d still listed)\n", len(listed)) }