checks/environment: refuse a module reading a MESH_* word nothing sets
A word its manifest does not set and the mesh gives no process is a contract nobody keeps (MESH_REGISTRY_REACH, MESH_CONFIG_CALLERS). The 63 reads outside this milestone's modules are listed in unset, which only shrinks (lead's ruling on A3n). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
// 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, `<module> <WORD>` a line, until
|
||||
// each is fixed: a line no longer found is refused, so the list only shrinks.
|
||||
//
|
||||
// go run . <catalogue root>
|
||||
//
|
||||
// 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 `<module> <WORD>` 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))
|
||||
}
|
||||
Reference in New Issue
Block a user