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
253 lines
8.1 KiB
Go
253 lines
8.1 KiB
Go
// 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))
|
|
}
|