Compare commits

..
Author SHA1 Message Date
jochen 1fe6ea4ee4 claude-code: start only the tool servers set at the controller's terminal
mesh/merge-gate out of date: judged against trunk 7f77ce663a9e, the trunk is now e47d0694c752; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 7f77ce663a9e, the trunk is now e47d0694c752; judged again on a recheck or when it is merged
mesh/delivery closed: the pull request closed unmerged
mesh/check failed: g14: novox/mesh-catalog#152 does not merge into the trunk: files in conflict: modules/claude-code/README.md, modules/claude-code/cm…
A server left in the servers state could still be put there by a holder of
the runner's credential; render mcp_servers alone and keep the state for
listing and unregistering (hq issue 340). Waits for the laptop's server to
be set at the terminal first.
2026-10-09 09:51:41 +02:00
2664 changed files with 18070 additions and 320880 deletions
-34
View File
@@ -2,45 +2,11 @@ node_modules/
dist/
# Go tool bundles built in place (go build in a module's cmd/<name>-tools) are build output.
modules/pacman/pacman-tools
modules/slack/cmd/slack-tools/slack-tools
modules/jetbrains-toolbox/cmd/toolbox-tools/toolbox-tools
modules/messenger/cmd/messenger/messenger
modules/mesh-watcher/cmd/mesh-watcher/mesh-watcher
modules/claude-code/cmd/claude-code/claude-code
modules/dnsmasq/cmd/dnsmasq-tools/dnsmasq-tools
# ...and the same built from the module's root (go build ./cmd/<name>), which names it after the module.
modules/messenger/messenger
modules/claude-code/claude-code
modules/mesh-watcher/mesh-watcher
modules/dnsmasq/dnsmasq
modules/mesh-issues/cmd/mesh-issues/mesh-issues
modules/mesh-issues/mesh-issues
modules/mounts/cmd/mounts/mounts
modules/mesh-knowledge/cmd/mesh-knowledge/mesh-knowledge
modules/mesh-knowledge/mesh-knowledge
modules/mesh-reads/mesh-reads
modules/mesh-reads/cmd/mesh-reads/mesh-reads
modules/mesh-files/mesh-files
modules/mesh-files/cmd/mesh-files/mesh-files
# The provisioner client built in place (issue 671).
modules/mesh-provisioner-client/cmd/mesh-provisioner-client/mesh-provisioner-client
modules/brother/cmd/brother/brother
modules/mesh-build-go/cmd/mesh-build-go/mesh-build-go
modules/mesh-config/mesh-config
modules/mesh-build-image/cmd/mesh-build-image/mesh-build-image
*.test
# binaries a local go build leaves in a module root
/checks/paths/paths
/modules/mesh-build-go/mesh-build-go
/modules/mesh-delivery-gate/mesh-delivery-gate
modules/mesh-config/cmd/mesh-config/mesh-config
# Built by their modules' build step (issue 1577), never committed.
modules/forticlient/cmd/forticlient-tools/forticlient-tools
modules/networkmanager/uplink-tools
checks/words/words
modules/mesh-cli/mesh-cli
modules/mesh-cli/cmd/mesh-cli/mesh-cli
-6
View File
@@ -1,6 +0,0 @@
# Release 57: config and provisioning
This branch carries the work of hq design 57 (ADR 0348, ADR 0349) in this repository. Each package
merges here through its own reviewed pull request; the branch reaches main as one pull request, in one
delivery group with the branch of the same name in mesh-controller, mesh-host, mesh-sdk, mesh-catalog
and hq, after a rehearsal in the lab. This file is deleted by the last commit before that pull request.
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/agenthome
go 1.22
-128
View File
@@ -1,128 +0,0 @@
// Command agenthome refuses, in every module's manifest, a resource placed under the agent account's home
// that the node-engine cannot write without a gap (novox/hq ADR 0266).
//
// The agent account owns its home and everything in it, so it can put a symbolic or hard link anywhere
// there. The node-engine, as root, writes a whole file or a directory below a home through descriptors it
// opened itself and refuses a link it finds; it unpacks an archive, and writes into an existing file (`into`:
// json or block), only after checking the path, so a link swapped in between the check and the write would
// point root's write at any file. No module does either today. This check keeps it so: a resource of type
// `archive`, or a `file` with `into`, whose path is under ${machine:agent-home} — written so, reached through
// one of the module's own directories (${dir:<id>}), or written as a literal /home path — is refused. The
// node-engine refuses the same below the home of any account declared never to become root, whatever the path.
//
// go run . <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// AgentHome is the machine fact naming the agent account's home.
const AgentHome = "${machine:agent-home}"
// ofDir is where a manifest names one of its own directories: ${dir:<id>}.
var ofDir = regexp.MustCompile(`\$\{dir:([a-z0-9][a-z0-9_-]*)\}`)
// resolve replaces each ${dir:<id>} with that resource's own path, as far as the manifest says it, so a path
// reaching the agent's home through one of the module's directories is seen for what it is.
func resolve(p string, dirs map[string]string) string {
for i := 0; i < 8 && ofDir.MatchString(p); i++ {
p = ofDir.ReplaceAllStringFunc(p, func(word string) string {
if to, ok := dirs[ofDir.FindStringSubmatch(word)[1]]; ok {
return to
}
return word
})
}
return p
}
// underAHome says whether a resolved path lies under the agent's home: by its machine fact, or written as a
// literal home, which no definition may carry anyway (ADR 0112) and which may well be the agent's.
func underAHome(p string) bool {
// Cleaned first, so //home/x or /./home/x is seen as the /home/x it is.
if p != "" {
p = filepath.Clean(p)
}
return strings.HasPrefix(p, AgentHome) || strings.HasPrefix(p, "/home/") || p == "/home"
}
// Finding is one resource refused.
type Finding struct {
Module, ID, Why string
}
// Check reads every manifest under root/modules and answers what it refuses.
func Check(root string) ([]Finding, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Finding
for _, path := range manifests {
raw, err := os.ReadFile(path)
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", path, err)
}
dirs := map[string]string{}
for _, r := range m.Resources {
id, _ := r["id"].(string)
if p, _ := r["path"].(string); id != "" && p != "" {
dirs[id] = p
}
}
for _, r := range m.Resources {
p, _ := r["path"].(string)
if !underAHome(resolve(p, dirs)) {
continue
}
id, _ := r["id"].(string)
kind, _ := r["type"].(string)
into, _ := r["into"].(string)
switch {
case kind == "archive":
out = append(out, Finding{m.Module, id, "an archive unpacked under the agent's home"})
case kind == "file" && into != "":
out = append(out, Finding{m.Module, id, "a file written into (" + into + ") under the agent's home"})
}
}
}
return out, nil
}
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)
}
for _, f := range found {
fmt.Printf("%s: %s is %s: the node-engine writes it as root after checking the path, and the agent "+
"account could swap a link in between (novox/hq ADR 0266); place a whole file or a directory instead\n",
f.Module, f.ID, f.Why)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("agent-home: no archive and no file written into under the agent's home")
}
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func manifest(t *testing.T, root, module, resources string) {
t.Helper()
dir := filepath.Join(root, "modules", module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
body := `{"module":"` + module + `","resources":[` + resources + `]}`
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
func TestAnArchiveOrAFileWrittenIntoUnderTheAgentsHomeIsRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "a", `{"id":"theme","type":"archive","path":"${machine:agent-home}/.local/share/x"}`)
manifest(t, root, "b", `{"id":"cfg","type":"file","path":"${machine:agent-home}/.claude.json","into":"json","content":"{}"}`)
manifest(t, root, "c", `{"id":"dir","type":"directory","path":"${machine:agent-home}/.claude"},`+
`{"id":"whole","type":"file","path":"${machine:agent-home}/x","content":"y"},`+
`{"id":"op","type":"archive","path":"${machine:account-home}/.local/share/x"}`)
manifest(t, root, "d", `{"id":"home","type":"directory","path":"${machine:agent-home}/.claude"},`+
`{"id":"inner","type":"directory","path":"${dir:home}/inner"},`+
`{"id":"via-dir","type":"archive","path":"${dir:inner}/theme"}`)
manifest(t, root, "e", `{"id":"literal","type":"file","path":"/home/agent/.bashrc","into":"block","content":"x"}`)
manifest(t, root, "f", `{"id":"unclean","type":"archive","path":"//./home/agent/x"}`)
found, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(found) != 5 || found[0].Module != "a" || found[1].Module != "b" || found[2].ID != "via-dir" ||
found[3].ID != "literal" || found[4].ID != "unclean" {
t.Fatalf("refused %+v", found)
}
}
func TestTheCatalogueHasNone(t *testing.T) {
found, err := Check("../..")
if err != nil {
t.Fatal(err)
}
if len(found) != 0 {
t.Fatalf("%+v", found)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest.
modules/*/module.json
-7
View File
@@ -1,7 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/config
go 1.22
require git.novox.be/novox/mesh-catalog/checks/trunk v0.0.0
replace git.novox.be/novox/mesh-catalog/checks/trunk => ../trunk
-201
View File
@@ -1,201 +0,0 @@
// Command config refuses the settings form's names in a module that has moved to config (novox/hq ADR 0348
// clauses 1 and 11, design 57 package K6, issue 665).
//
// "Config" replaces "settings": a manifest declares its values under `config`, a mergeable file names the keys it
// takes under `takes-config`, and a file asks for a value with `${config:<key>}`. A mergeable file naming no keys at
// all is the settings form's last shape: it takes every key of its module's layer and declares none, so mesh-config
// could set none of them (ADR 0348 §10, ADR 0326 §3); it is found here like an old name (issue 735). The old names — `settings`,
// `takes-settings` and `${setting:<key>}` — stay accepted by the controller as aliases until every module has
// moved, and are then refused (ADR 0348 §1). The modules not yet moved are listed in the file `unmoved` beside this
// check, one per line: the catalogue's list ADR 0348 §11 names. Every other module says no old name, in its
// manifest or in a template file its manifest names; a module on the list that says none is taken off it; and a
// line the trunk's copy (the merge base's, given with -base) does not hold is refused, so the list only shrinks;
// while the trunk holds no copy, the modules in seed are the trunk's.
// When it is empty, the controller refuses the old names itself (design 57 package R1).
//
// Prose is not judged: a comment in a file's text that says "settings" composes into the machine's file, and
// rewording it would change what a node is sent.
//
// go run . [-base <the trunk's unmoved>] <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// The settings form's names (ADR 0348 §1). A key of a JSON object is a quoted string followed by a colon; a
// string value is never followed by one.
var (
settingRef = regexp.MustCompile(`\$\{setting:[a-z0-9][a-z0-9_.-]*\}`)
takesSettingsKey = regexp.MustCompile(`"takes-settings"\s*:`)
oldPlaceholder = "${setting:…}"
oldTakes = "takes-settings"
oldDeclaration = "settings"
wholeLayer = "taking its whole layer"
listFile = "unmoved"
listPathInCatalog = "checks/config/" + listFile
)
// Check answers, per module that says any, the settings form's names its manifest and its template files say.
func Check(root 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][]string{}
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Settings json.RawMessage `json:"settings"`
Resources []map[string]any `json:"resources"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
var said []string
if len(m.Settings) > 0 && string(m.Settings) != "null" {
said = append(said, oldDeclaration)
}
if takesSettingsKey.Match(raw) {
said = append(said, oldTakes)
}
if settingRef.Match(raw) {
said = append(said, oldPlaceholder)
}
for _, r := range m.Resources {
// A mergeable file naming no keys takes every key of the layer (ADR 0326 §3), declaring none of them.
if how, _ := r["merge"].(string); how != "" {
_, oldList := r["takes-settings"]
_, newList := r["takes-config"]
if !oldList && !newList {
said = append(said, fmt.Sprintf("%v %s", r["id"], wholeLayer))
}
}
name, _ := r["template"].(string)
if name == "" {
continue
}
text, err := os.ReadFile(filepath.Join(filepath.Dir(path), filepath.FromSlash(name)))
if err != nil {
return nil, fmt.Errorf("%s names the template %s: %w", m.Module, name, err)
}
if settingRef.Match(text) {
said = append(said, oldPlaceholder+" in "+name)
}
}
if len(said) > 0 {
sort.Strings(said)
out[m.Module] = said
}
}
return out, nil
}
// seed is the list as it was first written, standing for the trunk's copy until the trunk holds one.
// The modules taking a whole layer were found after the list was first written and before the trunk held a copy
// (issue 735), so they stand in it too.
var seed = map[string]bool{
"fail2ban": true, "influxdb": true, "keycloak": true, "mailu": true, "searxng": true,
"baserow": true, "confluence": true, "gitea": true, "gitlab": true, "grafana": true, "home-assistant": true,
"icecast": true, "jira": true, "letta": true, "matrix": true, "nodered": true, "supabase": true, "unifi": true,
}
// Unmoved is the modules a list names, one per line; a line starting with # and a blank line are not modules.
func Unmoved(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.TrimSpace(s.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
out[line] = true
}
return out, s.Err()
}
// Judge answers the modules that say an old name and are not on the list (refused), the modules on the list that
// say none (stale), and the modules on the list that the trunk's list does not hold (added).
func Judge(found map[string][]string, unmoved, base map[string]bool) (refused, stale, added []string) {
for m := range found {
if !unmoved[m] {
refused = append(refused, m)
}
}
for m := range unmoved {
if _, says := found[m]; !says {
stale = append(stale, m)
}
if !base[m] {
added = append(added, m)
}
}
sort.Strings(refused)
sort.Strings(stale)
sort.Strings(added)
return refused, stale, added
}
func main() {
basePath := flag.String("base", "", "the trunk's copy of unmoved (the merge base's); seed when absent")
flag.Parse()
root := "."
if flag.NArg() > 0 {
root = flag.Arg(0)
}
found, err := Check(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
unmoved, err := Unmoved(listFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
base := seed
if *basePath != "" {
if base, err = Unmoved(*basePath); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
refused, stale, added := Judge(found, unmoved, base)
for _, m := range refused {
fmt.Printf("%s says %s, the settings form's names, and is not on %s: write config, takes-config and "+
"${config:<key>} throughout, and name the keys each mergeable file takes (novox/hq ADR 0348 §1, §11)\n", m, strings.Join(found[m], ", "), listPathInCatalog)
}
for _, m := range stale {
fmt.Printf("%s names %s, which says no old name: take it off the list\n", listPathInCatalog, m)
}
for _, m := range added {
fmt.Printf("%s gains %s, which the trunk's copy does not hold: the list only shrinks — move the module to "+
"config instead (novox/hq ADR 0348 §11)\n", listPathInCatalog, m)
}
if len(refused) > 0 || len(stale) > 0 || len(added) > 0 {
os.Exit(1)
}
fmt.Printf("config: no moved module says settings, takes-settings or ${setting:<key>}, or takes a whole layer "+
"(%d not yet moved)\n",
len(unmoved))
}
-131
View File
@@ -1,131 +0,0 @@
package main
import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
func module(t *testing.T, root, name, body string, files map[string]string) {
t.Helper()
dir := filepath.Join(root, "modules", name)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
for path, text := range files {
full := filepath.Join(dir, filepath.FromSlash(path))
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
}
// Each of the settings form's names is found where it stands: the declaration, a mergeable file's keys, a
// placeholder in the manifest, and a placeholder in a template file the manifest names (novox/hq ADR 0348 §1).
func TestEveryOldNameIsFound(t *testing.T) {
root := t.TempDir()
module(t, root, "declares", `{"module":"declares","settings":{"size":{"kind":"preference","default":1,"why":"w"}},`+
`"resources":[{"id":"f","type":"file","content":"${setting:size}"}]}`, nil)
module(t, root, "takes", `{"module":"takes","resources":[{"id":"f","type":"file","merge":"json",`+
`"takes-settings":["a"],"content":"{}"}]}`, nil)
module(t, root, "asks", `{"module":"asks","serves":{"x":{"d":"${setting:domain}"}}}`, nil)
module(t, root, "templated", `{"module":"templated","config":{"size":{"type":"number","description":"d",`+
`"default":1}},"resources":[{"id":"f","type":"file","template":"files/conf"}]}`,
map[string]string{"files/conf": "size = ${setting:size}\n"})
module(t, root, "moved", `{"module":"moved","config":{"size":{"type":"number","description":"d","default":1}},`+
`"resources":[{"id":"f","type":"file","template":"files/conf"},{"id":"g","type":"file","merge":"json",`+
`"takes-config":[],"content":"{\"settings\": 1}"}]}`,
map[string]string{"files/conf": "size = ${config:size}\n# change the settings, never this file\n"})
module(t, root, "none", `{"module":"none","resources":[{"id":"f","type":"file","content":"no settings here"}]}`, nil)
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
want := map[string][]string{
"asks": {"${setting:…}"},
"declares": {"${setting:…}", "settings"},
"takes": {"takes-settings"},
"templated": {"${setting:…} in files/conf"},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("found %v, want %v", got, want)
}
}
// A module not on the list says no old name; one on the list that says none is taken off it; a line the trunk's
// list does not hold is refused, so the list only shrinks until it is empty (novox/hq ADR 0348 §11).
func TestTheListOfModulesNotYetMovedOnlyShrinks(t *testing.T) {
found := map[string][]string{"old": {"settings"}, "forgotten": {"takes-settings"}}
unmoved := map[string]bool{"old": true, "done": true, "new": true}
base := map[string]bool{"old": true, "done": true, "forgotten": true}
refused, stale, added := Judge(found, unmoved, base)
if !reflect.DeepEqual(refused, []string{"forgotten"}) || !reflect.DeepEqual(stale, []string{"done", "new"}) ||
!reflect.DeepEqual(added, []string{"new"}) {
t.Fatalf("refused %v, stale %v, added %v", refused, stale, added)
}
}
func TestTheListIsReadWithoutItsComments(t *testing.T) {
path := filepath.Join(t.TempDir(), "unmoved")
if err := os.WriteFile(path, []byte("# why\n\nmailu\n searxng \n"), 0o644); err != nil {
t.Fatal(err)
}
got, err := Unmoved(path)
if err != nil || !reflect.DeepEqual(got, map[string]bool{"mailu": true, "searxng": true}) {
t.Fatalf("%v %v", got, err)
}
}
// The catalogue itself: every moved module says config alone, and the list names exactly the modules that
// still say settings.
func TestTheCatalogueSaysConfigInEveryMovedModule(t *testing.T) {
found, err := Check(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
unmoved, err := Unmoved("unmoved")
if err != nil {
t.Fatal(err)
}
refused, stale, added := Judge(found, unmoved, seed)
for _, m := range refused {
t.Errorf("%s is not on checks/config/unmoved and says %s", m, strings.Join(found[m], ", "))
}
for _, m := range stale {
t.Errorf("checks/config/unmoved names %s, which says no old name", m)
}
for _, m := range added {
t.Errorf("checks/config/unmoved gains %s, which its first copy did not hold", m)
}
}
// A mergeable file that names no keys takes every key of its module's layer (novox/hq ADR 0326 §3): the settings
// form's last shape, which declares none of what it takes, so mesh-config could set none of it (ADR 0348 §10). It
// is found like an old name, in a module that says no other (issue 735); one naming its keys, an empty list
// included, is not.
func TestAMergeableFileTakingTheWholeLayerIsFound(t *testing.T) {
root := t.TempDir()
module(t, root, "whole", `{"module":"whole","resources":[{"id":"runtime-config","type":"file","merge":"json",`+
`"template":"files/config.json"}]}`, map[string]string{"files/config.json": "{}\n"})
module(t, root, "names", `{"module":"names","config":{"url":{"type":"string","description":"d","default":""}},`+
`"resources":[{"id":"runtime-config","type":"file","merge":"json","takes-config":["url"],"content":"{}"}]}`, nil)
module(t, root, "none", `{"module":"none","resources":[{"id":"c","type":"file","merge":"json",`+
`"takes-config":[],"content":"{}"}]}`, nil)
module(t, root, "plain", `{"module":"plain","resources":[{"id":"c","type":"file","content":"{}"}]}`, nil)
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
want := map[string][]string{"whole": {"runtime-config taking its whole layer"}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("found %v, want %v", got, want)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest and the files it names.
modules
-33
View File
@@ -1,33 +0,0 @@
package main
import (
"path/filepath"
"testing"
"git.novox.be/novox/mesh-catalog/checks/trunk"
)
// The script's comparison of this list with the trunk's (novox/hq ADR 0529 clause 2): the list of modules not yet moved gains none
// the trunk's copy at the merge base does not hold. The catalogue itself is judged against the list by
// TestTheCatalogueSaysConfigInEveryMovedModule. Skipped, and said, where no trunk is beside.
func TestTheUnmovedListHoldsNoModuleTheTrunksDoesNot(t *testing.T) {
path, ok := trunk.Copy(t, "checks/config/unmoved")
found, err := Check(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
unmoved, err := Unmoved("unmoved")
if err != nil {
t.Fatal(err)
}
base := seed
if ok {
if base, err = Unmoved(path); err != nil {
t.Fatal(err)
}
}
_, _, added := Judge(found, unmoved, base)
for _, m := range added {
t.Errorf("checks/config/unmoved gains %s, which the trunk's copy does not hold: the list only shrinks", m)
}
}
-5
View File
@@ -1,5 +0,0 @@
# The modules still written in the settings form (`settings`, `takes-settings`, `${setting:<key>}`, or a mergeable
# file naming no keys, which takes its whole layer), one per line: the catalogue's list of modules not yet moved to
# config (novox/hq ADR 0348 §11). Every other module says config alone. This list only shrinks: a module that says no
# old name is taken off, and a line the trunk's copy does not hold is refused. When it is empty, the controller
# refuses the old names (design 57 package R1).
-164
View File
@@ -1,164 +0,0 @@
// Package controllerdeps is the catalogue's dependence check on the controller (novox/hq ADR 0590 §5, issue 1126): the
// controller is not placed, so no module of this tree reads, requires, invokes, consumes, waits on, uses or otherwise
// names it. Nothing is kept: not the lease, not the entries once kept to its write (ADR 0571 §13, superseded with ADR
// 0582). A later decision that places a controller again must first lift this check.
//
// It refuses, over the tree at root:
//
// - a directory modules/mesh-controller: the controller is no module of the catalogue;
// - a manifest string naming the controller's seat, a state, verb or event of it, or one of its buckets
// (`mesh-controller.<name>`, `seat:mesh-controller.<verb>`, `mesh-controller_<bucket>`), anywhere in the manifest;
// - a manifest string that is the bare module name `mesh-controller` outside the module's own `config`, where a value
// is the module's data (an issue owner naming the controller's repository is no dependency), not an address;
// - a go.mod of a module or of a check requiring the controller's repository (its module path, github.com/novox/mesh-controller,
// or the forge's git.novox.be/novox/mesh-controller): the repository is archived once its last module is delivered
// elsewhere (novox/hq ADR 0593 §11), so nothing here may be built from it;
// - a line of any module's file naming one of the controller's buckets (`mesh-controller_`): its buckets are removed
// after the controller's end (ADR 0590 §6), so a reader of one reads nothing. A test (`_test.go`, `.test.ts`) reads
// no live bucket, its names are its own data, and a `testdata` directory is a copy of text from elsewhere (a design,
// a backlog): both are skipped.
//
// The rule is said once, here, for the catalogue: decoding stays the manifests check's (a manifest that is no JSON is
// an error here, never judged).
package controllerdeps
import (
"bufio"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// Module is the controller's module name, and Bucket the prefix every bucket of its carries.
const (
Module = "mesh-controller"
Bucket = Module + "_"
)
// address is a string naming the controller's seat, one of its states, verbs or events, or one of its buckets.
var address = regexp.MustCompile(`(^|[^A-Za-z0-9_-])(seat:)?mesh-controller([._][A-Za-z0-9])`)
// repository is a go.mod line naming the controller's repository by either of its module paths.
var repository = regexp.MustCompile(`(github\.com|git\.novox\.be)/novox/mesh-controller(/|\s|$)`)
// Refusals is each reason the check gives over the tree at root, sorted; none when it passes.
func Refusals(root string) ([]string, error) {
var out []string
if _, err := os.Lstat(filepath.Join(root, "modules", Module)); err == nil {
out = append(out, Module+": the controller is no module of the catalogue: it is not placed and nothing may depend on it (novox/hq ADR 0590 §5)")
}
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
for _, f := range files {
raw, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var doc any
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, fmt.Errorf("%s: %w", f, err)
}
module := filepath.Base(filepath.Dir(f))
for _, s := range names(doc, false, nil) {
out = append(out, fmt.Sprintf("%s: names %q: the controller is not placed, so no manifest reads, requires, invokes, "+
"consumes, waits on or uses it; name the seat that holds the job now (novox/hq ADR 0590 §5)", module, s))
}
}
mods, err := filepath.Glob(filepath.Join(root, "*", "*", "go.mod"))
if err != nil {
return nil, err
}
sort.Strings(mods)
for _, f := range mods {
if rel, _ := filepath.Rel(root, f); !strings.HasPrefix(rel, "modules"+string(filepath.Separator)) && !strings.HasPrefix(rel, "checks"+string(filepath.Separator)) {
continue
}
raw, err := os.ReadFile(f)
if err != nil {
return nil, err
}
for n, l := range strings.Split(string(raw), "\n") {
if repository.MatchString(l) {
rel, _ := filepath.Rel(root, f)
out = append(out, fmt.Sprintf("%s:%d: requires the controller's repository: it is archived once its last module is built elsewhere, so nothing here is built from it (novox/hq ADR 0593 §11)", filepath.ToSlash(rel), n+1))
}
}
}
buckets, err := bucketLines(root)
if err != nil {
return nil, err
}
out = append(out, buckets...)
sort.Strings(out)
return out, nil
}
// names collects the strings of a decoded manifest that name the controller; inConfig is under the module's `config`.
func names(v any, inConfig bool, into []string) []string {
switch v := v.(type) {
case string:
if address.MatchString(v) || (!inConfig && strings.TrimSpace(v) == Module) {
into = append(into, v)
}
case []any:
for _, e := range v {
into = names(e, inConfig, into)
}
case map[string]any:
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
into = names(v[k], inConfig || k == "config", into)
}
}
return into
}
// bucketLines is every line of a module's file, outside tests and testdata, that names one of the controller's buckets.
func bucketLines(root string) ([]string, error) {
var out []string
modules := filepath.Join(root, "modules")
err := filepath.WalkDir(modules, func(path string, d fs.DirEntry, err error) error {
if err != nil {
if os.IsNotExist(err) && path == modules {
return nil
}
return err
}
if d.IsDir() {
if n := d.Name(); n == "testdata" || n == "node_modules" || n == ".git" {
return filepath.SkipDir
}
return nil
}
if !d.Type().IsRegular() || strings.HasSuffix(d.Name(), "_test.go") || strings.HasSuffix(d.Name(), ".test.ts") {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 64*1024), 16*1024*1024)
rel, _ := filepath.Rel(root, path)
for n := 1; sc.Scan(); n++ {
if strings.Contains(sc.Text(), Bucket) {
out = append(out, fmt.Sprintf("%s:%d: names the controller's bucket (%s…): its buckets are removed after its "+
"end (novox/hq ADR 0590 §6); read the seat that holds the job now", filepath.ToSlash(rel), n, Bucket))
}
}
return sc.Err()
})
return out, err
}
@@ -1,64 +0,0 @@
package controllerdeps
import (
"os"
"path/filepath"
"strings"
"testing"
)
// ADR 0590 §5 (issue 1126 B10): no manifest of the catalogue at this head reads, invokes, consumes, waits on, uses or
// otherwise names the controller's seat, states or buckets, and no module's code names a bucket of the controller's.
func TestNothingInTheCatalogueNamesTheController(t *testing.T) {
got, err := Refusals("../..")
if err != nil {
t.Fatal(err)
}
for _, r := range got {
t.Error(r)
}
}
// The check refuses every name of the controller, the lease and the entries once kept to its write among them, on a
// tree of its own; a repository named mesh-controller (an issue's owner) and a word in prose are no dependency.
func TestRefusalsJudgeATree(t *testing.T) {
for _, row := range []struct {
name string
files map[string]string
want string // empty: passes
}{
{"the lease is refused like any read", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-controller.lease"]}`}, `a: names "mesh-controller.lease"`},
{"a verb once kept to the write", map[string]string{"modules/b/module.json": `{"module":"b","invokes":["seat:mesh-controller.collect"]}`}, `b: names "seat:mesh-controller.collect"`},
{"an event consumed", map[string]string{"modules/c/module.json": `{"module":"c","consumes":[{"event":"mesh-controller.artifacts-gone"}]}`}, "mesh-controller.artifacts-gone"},
{"a requirement", map[string]string{"modules/d/module.json": `{"module":"d","requires":[{"name":"x","from":"mesh-controller"}]}`}, `d: names "mesh-controller"`},
{"the controller's own module", map[string]string{"modules/mesh-controller/module.json": `{"module":"mesh-controller"}`}, "mesh-controller: the controller is no module of the catalogue"},
{"a bucket in code", map[string]string{"modules/e/cmd/e/main.go": "package main\nconst b = \"mesh-controller_assignments\"\n"}, "modules/e/cmd/e/main.go:2: names the controller's bucket"},
{"a requirement of the controller's repository", map[string]string{"modules/h/go.mod": "module h\n\nrequire github.com/novox/mesh-controller v0.0.0\n"}, "modules/h/go.mod:3: requires the controller's repository"},
{"its forge path in a check", map[string]string{"checks/i/go.mod": "module i\n\nrequire (\n\tgit.novox.be/novox/mesh-controller/modules/route-proxy v0.1.0\n)\n"}, "checks/i/go.mod:4"},
{"another repository of the forge passes", map[string]string{"modules/j/go.mod": "module j\n\nrequire git.novox.be/novox/mesh-sdk/go v0.1.0\n"}, ""},
{"a bucket in a test is its data", map[string]string{"modules/e/cmd/e/main_test.go": "package main\n// mesh-controller_lease\n"}, ""},
{"a fixture copied from elsewhere", map[string]string{"modules/e/cmd/e/testdata/x.md": "mesh-controller_rollouts\n"}, ""},
{"an owner and prose pass", map[string]string{"modules/f/module.json": `{"module":"f","config":{"owners":{"default":["mesh-controller","mesh-controller modules/route-proxy"]}},"why":"the mesh-controller module is gone"}`}, ""},
{"the seat's successors pass", map[string]string{"modules/g/module.json": `{"module":"g","reads":["mesh-placement.placement"],"invokes":["seat:mesh-hand-acts.hand-act"]}`}, ""},
} {
t.Run(row.name, func(t *testing.T) {
root := t.TempDir()
for path, text := range row.files {
p := filepath.Join(root, path)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Refusals(root)
if err != nil {
t.Fatal(err)
}
if joined := strings.Join(got, "\n"); (row.want == "") != (joined == "") || !strings.Contains(joined, row.want) {
t.Errorf("refusals %q, want %q", joined, row.want)
}
})
}
}
-3
View File
@@ -1,3 +0,0 @@
module checks/controllerdeps
go 1.22
-3
View File
@@ -1,3 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (issue 1936).
# every module's directory: its manifest, for a name of the controller, and its files, for one of the controller's buckets.
modules
-5
View File
@@ -1,5 +0,0 @@
module checks/credentialform
go 1.26.0
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
-2
View File
@@ -1,2 +0,0 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
-98
View File
@@ -1,98 +0,0 @@
// Package credentialform refuses what the catalogue's tree holds about credentials and routes that no manifest judge
// sees: a module that provides and keeps a `bound` state the provisioner does not read, and a route received as a file.
// It judges no offer's credential form: that rule (an own secret, an issuer, or `form` pair or none; the lead,
// 2026-10-29) is part of the catalogue's rules, which the checker's judge runs on every change (CatalogueProblems,
// novox/hq ADR 0578 §2); this package called it itself until 2026-11.
//
// It refuses a route received as a file too (issue 774, research 072 row 34, the operator on 2026-10-31: "there is only
// 1 path, the other is simply deprecated/invalid"): a route is a provision, served from the seat mesh-provisioner's
// plans alone, and a manifest's `receives.route` is the deprecated path. A module still moving off it is listed in
// stillReceivingRoutes with why; every refusal names them all, and a listed module that no longer receives one is
// refused too, so the list only shrinks.
package credentialform
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Problems is every module of the manifests under root/modules that keeps a `bound` state the provisioner does not read,
// and every route received as a file, one line each.
func Problems(root string) ([]string, error) {
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(files)
var out []string
var receiving []string
reads := map[string]bool{}
var binders []string
for _, f := range files {
raw, err := os.ReadFile(f)
if err != nil {
return nil, err
}
m, err := catalogue.Decode(raw)
if err != nil {
return nil, fmt.Errorf("%s: %w", f, err)
}
if _, received := m.Receives["route"]; received {
receiving = append(receiving, m.Module)
} else if _, listed := stillReceivingRoutes[m.Module]; listed {
out = append(out, fmt.Sprintf("%s no longer receives route; take it off stillReceivingRoutes", m.Module))
}
var kept struct {
Reads []string `json:"reads"`
State []json.RawMessage `json:"state"`
}
_ = json.Unmarshal(raw, &kept) // reads may hold objects in another module; only the provisioner's are read
if m.Module == "mesh-provisioner" {
for _, r := range kept.Reads {
reads[r] = true
}
}
if len(m.Provides) > 0 && keepsBound(kept.State) {
binders = append(binders, m.Module)
}
}
for _, module := range binders {
if !reads[module+".bound"] {
out = append(out, fmt.Sprintf("%s provides and keeps a `bound` state, and mesh-provisioner's manifest does not read %s.bound", module, module))
}
}
for _, module := range receiving {
if _, listed := stillReceivingRoutes[module]; !listed {
out = append(out, fmt.Sprintf("%s receives route as a file: a route is a provision, served from the seat "+
"mesh-provisioner's plans alone (issue 774); still on the deprecated path: %s", module,
strings.Join(receiving, ", ")))
}
}
return out, nil
}
// stillReceivingRoutes is every module still receiving its routes as a file, and why it has not moved yet.
var stillReceivingRoutes = map[string]string{
"route-adapter": "migration scaffolding writing into a predecessor's proxy (novox/hq ADR 0104), deleted when that " +
"migration ends; it reads the received file its TypeScript adapter turns into the predecessor's route files",
}
// keepsBound says whether a manifest's `state` names `bound`, as a plain name or an object with a name.
func keepsBound(state []json.RawMessage) bool {
for _, s := range state {
var name string
var obj struct {
Name string `json:"name"`
}
if json.Unmarshal(s, &name) == nil && name == "bound" || json.Unmarshal(s, &obj) == nil && obj.Name == "bound" {
return true
}
}
return false
}
-54
View File
@@ -1,54 +0,0 @@
package credentialform
import (
"os"
"path/filepath"
"strings"
"testing"
)
func aCatalogue(t *testing.T, manifests map[string]string) string {
t.Helper()
root := t.TempDir()
for module, body := range manifests {
dir := filepath.Join(root, "modules", module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
return root
}
// A binder the provisioner does not read is refused and named; an offer that says no form is the judge's to refuse, not
// this check's; a route received as a file is refused but for the module still moving off it; and the real catalogue is
// clean.
func TestNoBinderIsUnreadAndNoRouteIsReceivedAsAFile(t *testing.T) {
root := aCatalogue(t, map[string]string{
"silent": `{"module":"silent","provides":[{"name":"thing","scope":"mesh"}]}`,
"binder": `{"module":"binder","provides":[{"name":"d","credential":{"form":"pair"}}],"state":["bound"]}`,
"mesh-provisioner": `{"module":"mesh-provisioner","reads":["other.bound"]}`,
"says": `{"module":"says","provides":[{"name":"a","credential":{"form":"pair"}},{"name":"b","credential":{"form":"none"}},{"name":"c","credential":{"own":"jwt"}}]}`,
// A route received as a file (issue 774, research 072 row 34): refused, naming every module still on it; the one
// listed as still moving passes.
"old-proxy": `{"module":"old-proxy","receives":{"route":"/var/lib/old-proxy/mesh.json"}}`,
"route-adapter": `{"module":"route-adapter","receives":{"route":"/var/lib/route-adapter/mesh.json"}}`,
})
got, err := Problems(root)
if err != nil {
t.Fatal(err)
}
if len(got) != 2 || !strings.Contains(got[0], "binder") || !strings.Contains(got[0], "mesh-provisioner") ||
!strings.HasPrefix(got[1], "old-proxy receives route") || !strings.Contains(got[1], "old-proxy, route-adapter") {
t.Fatalf("refused %v, wanted the binder the provisioner does not read and the received route", got)
}
real, err := Problems(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if len(real) != 0 {
t.Fatalf("the catalogue has binders or routes it must not:\n%s", strings.Join(real, "\n"))
}
}
-3
View File
@@ -1,3 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (issue 1936).
# every manifest, for its bound state and its received routes.
modules/*/module.json
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/deps
go 1.22
-266
View File
@@ -1,266 +0,0 @@
// Command deps makes a module's dependencies available to the catalogue's check, or says why it cannot
// (novox/hq issue 385).
//
// A module's tests run only when its dependencies can be fetched. The build seat clones what a check
// needs with the forge credential and forgets that credential before the check runs, so a module pinning
// an SDK commit the seat's module cache does not hold cannot fetch it from the forge. The SDK is cloned
// beside the check all the same (MESH_CHECK_BESIDE, mesh-controller's besideRefs): every requirement of the
// mesh's own forge is taken from the clone of its repository there, at the commit or the tag its version
// names, through a go.mod of its own (-modfile) — the module's own files are not changed, and no forge
// credential is added.
//
// Usage: deps -beside <dir> -work <dir> <module dir>. It prints the go.mod to build the module with, or
// nothing when the module fetches what it needs; when it cannot, it prints why and exits 1, and the check
// fails: a module whose tests could not run is a failure, never a note.
package main
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
// forge is the prefix of the Go modules of the mesh's own forge: <forge><repository>[/<directory>].
const forge = "git.novox.be/novox/"
func main() {
beside := flag.String("beside", "", "where the repositories beside the check are cloned (MESH_CHECK_BESIDE)")
work := flag.String("work", os.TempDir(), "where the go.mod and the sources taken from beside are written")
flag.Parse()
if flag.NArg() != 1 {
fmt.Println("usage: deps -beside <dir> -work <dir> <module dir>")
os.Exit(2)
}
modfile, err := resolve(flag.Arg(0), *beside, *work)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if modfile != "" {
fmt.Println(modfile)
}
}
// resolve is the go.mod the module at dir builds with: "" when its own fetches everything, else one
// beside it in work that takes every requirement of the mesh's forge from its clone beside the check.
func resolve(dir, beside, work string) (string, error) {
out, err := goIn(dir, "mod", "download")
if err == nil {
return "", nil
}
fetching := lastLine(out)
required, err := forgeRequirements(dir)
if err != nil {
return "", err
}
if len(required) == 0 {
return "", fmt.Errorf("its dependencies cannot be fetched: %s", fetching)
}
if beside == "" {
return "", fmt.Errorf("its dependencies cannot be fetched (%s), and nothing was cloned beside the check to "+
"take %s from", fetching, repositories(required))
}
into, err := os.MkdirTemp(work, filepath.Base(dir)+"-")
if err != nil {
return "", err
}
modfile := filepath.Join(into, "go.mod")
if err := copyFile(filepath.Join(dir, "go.mod"), modfile); err != nil {
return "", err
}
if err := copyFile(filepath.Join(dir, "go.sum"), filepath.Join(into, "go.sum")); err != nil &&
!errors.Is(err, os.ErrNotExist) {
return "", err
}
for _, r := range required {
source, err := export(beside, r, into)
if err != nil {
return "", fmt.Errorf("its dependencies cannot be fetched (%s), and %s@%s cannot be taken from the "+
"clone beside the check: %w", fetching, r.Path, r.Version, err)
}
if out, err := goIn(dir, "mod", "edit", "-replace", r.Path+"@"+r.Version+"="+source, modfile); err != nil {
return "", fmt.Errorf("%s@%s cannot be taken from the clone beside the check: %s", r.Path, r.Version,
lastLine(out))
}
}
if out, err := goIn(dir, "mod", "download", "-modfile="+modfile); err != nil {
return "", fmt.Errorf("its dependencies cannot be fetched even with %s taken from the clone beside the "+
"check: %s", repositories(required), lastLine(out))
}
return modfile, nil
}
// requirement is one module the module requires.
type requirement struct {
Path string
Version string
}
// forgeRequirements is every requirement of the module at dir that comes from the mesh's own forge.
func forgeRequirements(dir string) ([]requirement, error) {
out, err := goIn(dir, "mod", "edit", "-json")
if err != nil {
return nil, fmt.Errorf("its go.mod cannot be read: %s", lastLine(out))
}
var mod struct {
Require []requirement
Replace []struct{ Old, New requirement }
}
if err := json.Unmarshal(out, &mod); err != nil {
return nil, fmt.Errorf("its go.mod cannot be read: %w", err)
}
// A requirement the go.mod replaces to a directory is a package of the module's own repository (novox/hq ADR 0593 §1): it
// is in the tree under check, so it is fetched from nowhere.
local := map[string]bool{}
for _, r := range mod.Replace {
if r.New.Version == "" {
local[r.Old.Path] = true
}
}
var on []requirement
for _, r := range mod.Require {
if strings.HasPrefix(r.Path, forge) && !local[r.Path] {
on = append(on, r)
}
}
return on, nil
}
// repositoryOf splits a module path of the forge into its repository and the directory in it.
func repositoryOf(path string) (string, string) {
repo, sub, _ := strings.Cut(strings.TrimPrefix(path, forge), "/")
return repo, sub
}
func repositories(rs []requirement) string {
var names []string
for _, r := range rs {
repo, _ := repositoryOf(r.Path)
names = append(names, repo)
}
return strings.Join(names, ", ")
}
// pseudo is a pseudo-version's commit: v0.1.14-0.20261012082113-9747f3e1b1ac → 9747f3e1b1ac.
var pseudo = regexp.MustCompile(`[-.]\d{14}-([0-9a-f]{12})$`)
// refOf is the ref a version names in its repository: a pseudo-version's commit, else the tag a module in
// a directory is released under (<directory>/<version>).
func refOf(sub, version string) string {
version = strings.TrimSuffix(version, "+incompatible")
if m := pseudo.FindStringSubmatch(version); m != nil {
return m[1]
}
if sub != "" {
return sub + "/" + version
}
return version
}
// export writes the module r names, at its version, from its repository's clone beside the check into
// into, and answers its directory.
func export(beside string, r requirement, into string) (string, error) {
repo, sub := repositoryOf(r.Path)
clone := filepath.Join(beside, repo)
if _, err := os.Stat(filepath.Join(clone, ".git")); err != nil {
return "", fmt.Errorf("no clone of %s is beside the check", repo)
}
ref := refOf(sub, r.Version)
commit, err := exec.Command("git", "-C", clone, "rev-parse", "--verify", "--quiet", ref+"^{commit}").Output()
if err != nil {
return "", fmt.Errorf("the clone of %s holds no %s (a commit or tag not pushed to the forge?)", repo, ref)
}
at := strings.TrimSpace(string(commit))
args := []string{"-C", clone, "archive", "--format=tar", at}
if sub != "" {
args = append(args, "--", sub)
}
var stderr bytes.Buffer
archive := exec.Command("git", args...)
archive.Stderr = &stderr
raw, err := archive.Output()
if err != nil {
return "", fmt.Errorf("git archive of %s at %s: %v %s", repo, ref, err, strings.TrimSpace(stderr.String()))
}
root := filepath.Join(into, repo+"@"+at[:12])
if err := untar(raw, root); err != nil {
return "", err
}
source := filepath.Join(root, filepath.FromSlash(sub))
if _, err := os.Stat(filepath.Join(source, "go.mod")); err != nil {
return "", fmt.Errorf("%s at %s has no go.mod in %q", repo, ref, sub)
}
return source, nil
}
// untar writes a tar's directories and regular files under root.
func untar(raw []byte, root string) error {
r := tar.NewReader(bytes.NewReader(raw))
for {
h, err := r.Next()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
name := filepath.Clean(filepath.FromSlash(h.Name))
if name == "." || strings.HasPrefix(name, "..") || filepath.IsAbs(name) {
continue
}
path := filepath.Join(root, name)
switch h.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(path, 0o755); err != nil {
return err
}
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
body, err := io.ReadAll(r)
if err != nil {
return err
}
if err := os.WriteFile(path, body, 0o644); err != nil {
return err
}
}
}
}
func copyFile(from, to string) error {
raw, err := os.ReadFile(from)
if err != nil {
return err
}
return os.WriteFile(to, raw, 0o644)
}
// goIn runs the go command in dir.
func goIn(dir string, args ...string) ([]byte, error) {
cmd := exec.Command("go", args...)
cmd.Dir = dir
var stderr bytes.Buffer
cmd.Stderr = &stderr
out, err := cmd.Output()
if err != nil {
return append(out, stderr.Bytes()...), err
}
return out, nil
}
// lastLine is the last line a command said, which is where go says what failed.
func lastLine(out []byte) string {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
return strings.TrimSpace(lines[len(lines)-1])
}
-202
View File
@@ -1,202 +0,0 @@
package main
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// offline is a Go that fetches nothing: what the build seat is once the forge credential is gone, for a pin
// its module cache does not hold (novox/hq issue 385).
func offline(t *testing.T) {
t.Helper()
t.Setenv("GOPROXY", "off")
t.Setenv("GOFLAGS", "")
t.Setenv("GOWORK", "off")
// Not GOPRIVATE: a private module is fetched directly by git, around GOPROXY, with whatever credential
// the machine running the test holds.
t.Setenv("GOPRIVATE", "")
t.Setenv("GONOPROXY", "")
t.Setenv("GOSUMDB", "off")
t.Setenv("GOMODCACHE", t.TempDir())
}
func git(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t",
"GIT_COMMITTER_EMAIL=t@t", "GIT_AUTHOR_DATE=2026-10-12T08:21:13Z", "GIT_COMMITTER_DATE=2026-10-12T08:21:13Z")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
}
return strings.TrimSpace(string(out))
}
func write(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
// sdkBeside is the SDK's repository cloned beside a check, its Go module under go/, one commit tagged
// go/v0.1.7. It answers the beside root and the commit.
func sdkBeside(t *testing.T) (string, string) {
t.Helper()
beside := t.TempDir()
repo := filepath.Join(beside, "mesh-sdk")
write(t, filepath.Join(repo, "go", "go.mod"), "module git.novox.be/novox/mesh-sdk/go\n\ngo 1.22\n")
write(t, filepath.Join(repo, "go", "sdk", "sdk.go"), "package sdk\n\nfunc Hello() string { return \"hello\" }\n")
write(t, filepath.Join(repo, "README.md"), "the SDK\n")
git(t, repo, "init", "--quiet")
git(t, repo, "add", ".")
git(t, repo, "commit", "--quiet", "-m", "sdk")
git(t, repo, "tag", "go/v0.1.7")
return beside, git(t, repo, "rev-parse", "HEAD")
}
// moduleRequiring is a catalogue module whose code and test use the SDK at the version given.
func moduleRequiring(t *testing.T, version string) string {
t.Helper()
dir := filepath.Join(t.TempDir(), "messenger")
write(t, filepath.Join(dir, "go.mod"), "module git.novox.be/novox/mesh-catalog/modules/messenger\n\ngo 1.22\n\n"+
"require git.novox.be/novox/mesh-sdk/go "+version+"\n")
write(t, filepath.Join(dir, "main.go"), "package main\n\nimport \"git.novox.be/novox/mesh-sdk/go/sdk\"\n\n"+
"func main() { println(sdk.Hello()) }\n")
return dir
}
func builds(t *testing.T, dir, modfile string) {
t.Helper()
args := []string{"vet"}
if modfile != "" {
args = append(args, "-modfile="+modfile)
}
cmd := exec.Command("go", append(args, "./...")...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("the module does not build with %q: %v\n%s", modfile, err, out)
}
}
// A module that needs nothing it cannot fetch is left as it is: no other go.mod.
func TestAModuleThatFetchesItsDependenciesNeedsNothingBeside(t *testing.T) {
offline(t)
dir := filepath.Join(t.TempDir(), "plain")
write(t, filepath.Join(dir, "go.mod"), "module example.com/plain\n\ngo 1.22\n")
write(t, filepath.Join(dir, "main.go"), "package main\n\nfunc main() {}\n")
modfile, err := resolve(dir, "", t.TempDir())
if err != nil || modfile != "" {
t.Fatalf("resolve = %q, %v; want nothing", modfile, err)
}
}
// The case of issue 385: the module pins an SDK commit the build seat cannot fetch. It is taken from the
// SDK's clone beside the check, and the module builds against it.
func TestAPinnedSDKCommitIsTakenFromTheCloneBeside(t *testing.T) {
offline(t)
beside, commit := sdkBeside(t)
dir := moduleRequiring(t, "v0.1.14-0.20261012082113-"+commit[:12])
modfile, err := resolve(dir, beside, t.TempDir())
if err != nil {
t.Fatalf("resolve: %v", err)
}
if modfile == "" {
t.Fatal("resolve said the module fetches its dependencies, with nothing to fetch them from")
}
builds(t, dir, modfile)
if raw, _ := os.ReadFile(filepath.Join(dir, "go.mod")); strings.Contains(string(raw), "replace") {
t.Fatalf("the module's own go.mod was changed:\n%s", raw)
}
}
// A released SDK is found by the tag it is released under: go/<version>.
func TestATaggedSDKIsTakenFromTheCloneBeside(t *testing.T) {
offline(t)
beside, _ := sdkBeside(t)
dir := moduleRequiring(t, "v0.1.7")
modfile, err := resolve(dir, beside, t.TempDir())
if err != nil {
t.Fatalf("resolve: %v", err)
}
builds(t, dir, modfile)
}
// A commit the clone beside does not hold (a branch never pushed) cannot be tested, and says which.
func TestACommitTheCloneBesideDoesNotHoldIsSaid(t *testing.T) {
offline(t)
beside, _ := sdkBeside(t)
dir := moduleRequiring(t, "v0.1.14-0.20261010132341-4f3efc98e0c0")
_, err := resolve(dir, beside, t.TempDir())
if err == nil {
t.Fatal("resolve found a commit nothing holds")
}
for _, want := range []string{"git.novox.be/novox/mesh-sdk/go@v0.1.14-0.20261010132341-4f3efc98e0c0", "4f3efc98e0c0"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("the reason %q does not name %s", err, want)
}
}
}
// With nothing cloned beside (a check by hand without the forge), the reason says that.
func TestNoCloneBesideIsSaid(t *testing.T) {
offline(t)
_, commit := sdkBeside(t)
dir := moduleRequiring(t, "v0.1.14-0.20261012082113-"+commit[:12])
for _, beside := range []string{"", t.TempDir()} {
_, err := resolve(dir, beside, t.TempDir())
if err == nil {
t.Fatalf("resolve with %q beside found what is not there", beside)
}
if !strings.Contains(err.Error(), "mesh-sdk") {
t.Errorf("the reason %q does not name the repository it needed", err)
}
}
}
func TestTheRefOfAVersion(t *testing.T) {
cases := []struct{ sub, version, want string }{
{"go", "v0.1.14-0.20261012082113-9747f3e1b1ac", "9747f3e1b1ac"},
{"go", "v0.0.0-20261012082113-9747f3e1b1ac", "9747f3e1b1ac"},
{"go", "v0.1.7", "go/v0.1.7"},
{"", "v1.2.0", "v1.2.0"},
{"", "v2.0.0+incompatible", "v2.0.0"},
}
for _, c := range cases {
if got := refOf(c.sub, c.version); got != c.want {
t.Errorf("refOf(%q, %q) = %q, want %q", c.sub, c.version, got, c.want)
}
}
}
// ADR 0593 §1: a module requires a sibling package of its own repository and replaces it to the directory beside it; that
// package is no fetch from the forge (it is in the tree under check), so it is never looked for in a clone beside the check,
// and the module builds with the SDK taken from there.
func TestASiblingPackageReplacedToItsDirectoryIsNotFetched(t *testing.T) {
offline(t)
beside, commit := sdkBeside(t)
version := "v0.1.14-0.20261012082113-" + commit[:12]
root := t.TempDir()
sibling := "git.novox.be/novox/mesh-catalog/modules/builder/runner"
write(t, filepath.Join(root, "builder", "runner", "go.mod"), "module "+sibling+"\n\ngo 1.22\n\nrequire git.novox.be/novox/mesh-sdk/go "+version+"\n")
write(t, filepath.Join(root, "builder", "runner", "run.go"), "package runner\n\nimport \"git.novox.be/novox/mesh-sdk/go/sdk\"\n\nfunc Run() string { return sdk.Hello() }\n")
dir := filepath.Join(root, "user")
write(t, filepath.Join(dir, "go.mod"), "module git.novox.be/novox/mesh-catalog/modules/user\n\ngo 1.22\n\nrequire (\n\t"+sibling+" v0.0.0\n"+
"\tgit.novox.be/novox/mesh-sdk/go "+version+"\n)\n\nreplace "+sibling+" => ../builder/runner\n")
write(t, filepath.Join(dir, "main.go"), "package main\n\nimport \""+sibling+"\"\n\nfunc main() { println(runner.Run()) }\n")
modfile, err := resolve(dir, beside, t.TempDir())
if err != nil {
t.Fatalf("resolve: %v", err)
}
if modfile == "" {
t.Fatal("resolve said the module fetches its dependencies, with nothing to fetch the SDK from")
}
builds(t, dir, modfile)
}
-128
View File
@@ -1,128 +0,0 @@
// Package failurekit refuses a build module whose tests do not run the build runner's conformance kit (issue 1901, its thorough
// fix): a module that claims a node-build-* seat and serves a test step must hold a test that calls conformance.Run, so
// its parse of the test runner's output is held to the failure contract (a failing test named with its cause, never a
// passing line in its place). A build module with no test step (mesh-build-image builds images and runs no tests) is
// exempt from that, and said so. Every build module, test step or not, must also hold a test that calls
// conformance.LeftRunning with its own start sweep, so a step a previous process left running is ended at its start
// (issue 1890).
package failurekit
import (
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
)
// Judged is what the check says of one build module: Refused says why it fails the check, "" when it passes; Exempt
// says why the kit does not apply.
type Judged struct {
Module, Refused, Exempt string
}
type manifest struct {
Claims []struct {
Name string `json:"name"`
} `json:"claims"`
Tools []json.RawMessage `json:"tools"`
}
// Judge reads every modules/<m>/module.json of the catalogue at root and judges each module that claims a node-build-*
// seat.
func Judge(root string) ([]Judged, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Judged
for _, p := range manifests {
raw, err := os.ReadFile(p)
if err != nil {
return nil, err
}
var m manifest
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", p, err)
}
seat := ""
for _, c := range m.Claims {
if strings.HasPrefix(c.Name, "node-build-") {
seat = c.Name
}
}
if seat == "" {
continue
}
dir := filepath.Dir(p)
j := Judged{Module: filepath.Base(dir)}
var refused []string
test := testTool(m.Tools)
switch {
case test == "":
j.Exempt = fmt.Sprintf("it claims %s and serves no test step, so the failure contract's cases do not apply", seat)
default:
runs, err := runsTheKit(dir, "conformance.Run(")
if err != nil {
return nil, err
}
if !runs {
refused = append(refused, fmt.Sprintf("it claims %s and serves the test step %s, and no test of it calls conformance.Run "+
"(mesh-build-go buildstep/conformance): run the kit with the module's own parse path (issue 1901)", seat, test))
}
}
// Every build module keeps steps, so every one runs the left-running case, test step or not (issue 1890).
sweeps, err := runsTheKit(dir, "conformance.LeftRunning(")
if err != nil {
return nil, err
}
if !sweeps {
refused = append(refused, fmt.Sprintf("it claims %s, and no test of it calls conformance.LeftRunning "+
"(mesh-build-go buildstep/conformance): run the kit's left-running case with the module's own start sweep (issue 1890)", seat))
}
j.Refused = strings.Join(refused, "; ")
out = append(out, j)
}
return out, nil
}
// testTool is the name of the module's test step tool (<prefix>_test), or "".
func testTool(tools []json.RawMessage) string {
for _, raw := range tools {
var name string
if json.Unmarshal(raw, &name) != nil {
var t struct {
Name string `json:"name"`
}
if json.Unmarshal(raw, &t) != nil {
continue
}
name = t.Name
}
if strings.HasSuffix(name, "_test") {
return name
}
}
return ""
}
// runsTheKit says a _test.go file under dir calls the kit's function call (such as "conformance.Run(").
func runsTheKit(dir, call string) (bool, error) {
found := false
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
if err != nil || found || d.IsDir() || !strings.HasSuffix(p, "_test.go") {
return err
}
raw, err := os.ReadFile(p)
if err != nil {
return err
}
found = strings.Contains(string(raw), call) &&
strings.Contains(string(raw), `"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance"`)
return nil
})
return found, err
}
-77
View File
@@ -1,77 +0,0 @@
package failurekit
import (
"os"
"path/filepath"
"testing"
)
// 1901/AC7 and 1890/AC8: a build module with a test step and no kit is refused, one that runs the kit passes, one with no
// test step is exempt from the failure cases but not from the left-running case, a module that runs only the failure
// cases is refused, and a module that claims no node-build-* seat is not judged.
func TestABuildModuleWithATestStepMustRunTheKit(t *testing.T) {
root := t.TempDir()
const imp = "package main\n\nimport \"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance\"\n\n"
const sweep = "func TestSweep(t *testing.T) { conformance.LeftRunning(t, nil) }\n"
kit := imp + "func TestKit(t *testing.T) { conformance.Run(t, conformance.GoTest, nil) }\n" + sweep
for path, body := range map[string]string{
"modules/a/module.json": `{"claims":[{"name":"node-build-a"}],"tools":["build_a_fetch","build_a_test"]}`,
"modules/a/cmd/a/check_test.go": "package main\n",
"modules/b/module.json": `{"claims":[{"name":"node-build-b"}],"tools":[{"name":"build_b_test"}]}`,
"modules/b/cmd/b/kit_test.go": kit,
"modules/c/module.json": `{"claims":[{"name":"node-build-c"}],"tools":["build_c_build"]}`,
"modules/c/cmd/c/kit_test.go": imp + sweep,
"modules/f/module.json": `{"claims":[{"name":"node-build-f"}],"tools":["build_f_build"]}`,
"modules/g/module.json": `{"claims":[{"name":"node-build-g"}],"tools":["build_g_test"]}`,
"modules/g/cmd/g/kit_test.go": imp + "func TestKit(t *testing.T) { conformance.Run(t, conformance.GoTest, nil) }\n",
"modules/d/module.json": `{"claims":[{"name":"other"}],"tools":["d_test"]}`,
"modules/e/module.json": `{"claims":[{"name":"node-build-e"}],"tools":["build_e_test"]}`,
"modules/e/cmd/e/kit.go": kit, // not a test file
} {
p := filepath.Join(root, path)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Judge(root)
if err != nil {
t.Fatal(err)
}
want := map[string]string{"a": "refused", "b": "passes", "c": "exempt", "e": "refused", "f": "refused", "g": "refused"}
if len(got) != len(want) {
t.Fatalf("judged %+v, want a, b, c, e, f and g", got)
}
for _, j := range got {
is := "passes"
if j.Refused != "" {
is = "refused"
} else if j.Exempt != "" {
is = "exempt"
}
if is != want[j.Module] {
t.Errorf("%s %s (%+v), want it %s", j.Module, is, j, want[j.Module])
}
}
}
// Every build module of this catalogue with a test step runs the kit.
func TestTheCataloguesBuildModulesRunTheKit(t *testing.T) {
got, err := Judge(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if len(got) == 0 {
t.Fatal("no module claims a node-build-* seat: the check reads the wrong tree")
}
for _, j := range got {
switch {
case j.Refused != "":
t.Errorf("%s: %s", j.Module, j.Refused)
case j.Exempt != "":
t.Logf("%s is exempt: %s", j.Module, j.Exempt)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/failurekit
go 1.22
-7
View File
@@ -1,7 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Judge("../.."): every manifest, and the directory of each module claiming a node-build-* seat, walked for a test that
# calls the kit. The build modules are the mesh-build-* directories; a build module named otherwise needs its line here.
modules/*/module.json
modules/mesh-build-*
-5
View File
@@ -1,5 +0,0 @@
module checks/health
go 1.26.0
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
-2
View File
@@ -1,2 +0,0 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
-75
View File
@@ -1,75 +0,0 @@
// Package health is the catalogue's count of long-running resources that do not say how they are ready (novox/hq ADR 0240 rule
// 8), kept in health-undeclared and judged by a test over the real tree (ADR 0529 clauses 2 and 4): it replaces the step of
// merge-check.sh that read it out of the controller's `module check`. Every manifest is decoded with mesh-sdk's catalogue
// package, the one the controller counts with, and judged by nothing here (novox/hq ADR 0578 §3): a resource's `health` is
// in the manifest itself, not in a template's text.
package health
import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Count is the long-running resources without `health` of every module under root/modules.
func Count(root string) (int, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return 0, err
}
sort.Strings(manifests)
if len(manifests) == 0 {
return 0, fmt.Errorf("no manifest under %s: the count would be zero for any catalogue", filepath.Join(root, "modules"))
}
n := 0
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return 0, err
}
m, err := catalogue.Decode(raw)
if err != nil {
return 0, fmt.Errorf("%s: %w", path, err)
}
n += len(catalogue.Undeclared(m))
}
return n, nil
}
// Kept is the number the file keeps: the first number of its first line.
func Kept(path string) (int, error) {
raw, err := os.ReadFile(path)
if err != nil {
return 0, err
}
first, _, _ := strings.Cut(string(raw), "\n")
digits := ""
for _, r := range strings.TrimSpace(first) {
if r < '0' || r > '9' {
break
}
digits += string(r)
}
n, err := strconv.Atoi(digits)
if err != nil {
return 0, fmt.Errorf("%s does not begin with the number it keeps: %q", path, first)
}
return n, nil
}
// Judge says what is wrong with a count against the kept number; empty when they agree. It only goes down: a rise is refused,
// and a fall asks the new number written in health-undeclared, so the count cannot rise again.
func Judge(kept, counted int) string {
switch {
case counted > kept:
return fmt.Sprintf("the long-running resources without health rose from %d to %d: declare how each new one is ready (ADR 0240 rule 8)", kept, counted)
case counted < kept:
return fmt.Sprintf("the long-running resources without health fell from %d to %d: write %d in health-undeclared, so the count cannot rise again", kept, counted, counted)
}
return ""
}
-88
View File
@@ -1,88 +0,0 @@
package health
import (
"os"
"path/filepath"
"strings"
"testing"
)
func aCatalogue(t *testing.T, manifests map[string]string) string {
t.Helper()
root := t.TempDir()
for module, body := range manifests {
dir := filepath.Join(root, "modules", module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
return root
}
// A service that stays up and does not say how it is ready is counted; one that says, and a file, are not.
func TestOnlyALongRunningResourceWithoutHealthIsCounted(t *testing.T) {
root := aCatalogue(t, map[string]string{
"quiet": `{"module":"quiet","resources":[{"id":"svc","type":"service","unit":"quiet.service","state":"running"}]}`,
"says": `{"module":"says","resources":[{"id":"svc","type":"service","unit":"says.service","state":"running",` +
`"health":{"kind":"unit"}}]}`,
"file": `{"module":"file","resources":[{"id":"cfg","type":"file","path":"/etc/file.conf","content":"x"}]}`,
})
counted, err := Count(root)
if err != nil {
t.Fatal(err)
}
if counted != 1 {
t.Fatalf("counted %d, wanted the one quiet service", counted)
}
}
func TestTheCountMayOnlyGoDown(t *testing.T) {
for _, c := range []struct {
kept, counted int
says string
}{
{58, 58, ""},
{58, 59, "rose from 58 to 59"},
{58, 57, "fell from 58 to 57"},
} {
got := Judge(c.kept, c.counted)
if (c.says == "") != (got == "") || !strings.Contains(got, c.says) {
t.Errorf("kept %d counted %d: %q, wanted %q", c.kept, c.counted, got, c.says)
}
}
}
func TestTheKeptCountIsTheFirstNumberOfTheFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "health-undeclared")
if err := os.WriteFile(path, []byte("58\nThe long-running resources ...\n"), 0o644); err != nil {
t.Fatal(err)
}
if n, err := Kept(path); err != nil || n != 58 {
t.Fatalf("%d %v", n, err)
}
if err := os.WriteFile(path, []byte("many\n"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := Kept(path); err == nil {
t.Fatal("a file with no number gave a count")
}
}
// The script's `module check` run over the real tree (novox/hq ADR 0240 rule 8, ADR 0529 clause 2 and 4): the long-running
// resources that do not say how they are ready, counted as the controller counts them, are the number health-undeclared keeps.
func TestTheCatalogueSaysHowItsLongRunningResourcesAreReady(t *testing.T) {
kept, err := Kept(filepath.Join("..", "..", "health-undeclared"))
if err != nil {
t.Fatal(err)
}
counted, err := Count(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if say := Judge(kept, counted); say != "" {
t.Fatal(say)
}
}
-6
View File
@@ -1,6 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# every manifest, decoded; and the root's kept count of resources that do not say how they are ready.
modules/*/module.json
health-undeclared
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/imagebuild
go 1.22
-92
View File
@@ -1,92 +0,0 @@
// Command imagebuild refuses any manifest but modules/buildah providing the rootless-image-build provision
// (novox/hq ADR 0487 §3, issue 1704): the image builder's tool is buildah alone, and docker, whose daemon holds
// the machine's root, never stands in for it. A second provider would let a node compose mesh-build-image over
// something that is not the rootless builder it was designed against.
//
// go run . <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
)
// Provision is the provision only Provider may provide.
const Provision = "rootless-image-build"
// Provider is the one module allowed to provide it.
const Provider = "buildah"
// Finding is one manifest refused: a module other than buildah providing the provision.
type Finding struct {
Module string
}
// Check reads every manifest under root/modules and answers which ones it refuses, and whether buildah provides.
func Check(root string) ([]Finding, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Finding
provided := false
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Provides []struct {
Name string `json:"name"`
} `json:"provides"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
for _, p := range m.Provides {
if p.Name != Provision {
continue
}
if m.Module == Provider {
provided = true
} else {
out = append(out, Finding{Module: m.Module})
}
break
}
}
if !provided {
out = append(out, Finding{Module: Provider})
}
return out, nil
}
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)
}
for _, f := range found {
if f.Module == Provider {
fmt.Printf("%s does not provide %s (novox/hq ADR 0487 §3)\n", Provider, Provision)
continue
}
fmt.Printf("%s provides %s: only %s may, and docker never (novox/hq ADR 0487 §3)\n", f.Module, Provision, Provider)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("imagebuild: rootless-image-build is provided only by buildah")
}
-74
View File
@@ -1,74 +0,0 @@
package main
import (
"os"
"path/filepath"
"reflect"
"testing"
)
func catalogue(t *testing.T, manifests map[string]string) string {
t.Helper()
root := t.TempDir()
for name, body := range manifests {
dir := filepath.Join(root, "modules", name)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
return root
}
const buildah = `{"module":"buildah","provides":[{"name":"rootless-image-build","reach":"machine"}]}`
func TestOnlyBuildahProvidesTheRootlessImageBuild(t *testing.T) {
root := catalogue(t, map[string]string{"buildah": buildah, "go": `{"module":"go","provides":[{"name":"go-toolchain"}]}`})
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("found %v, want none", got)
}
}
func TestDockerAndAnySecondProviderAreRefused(t *testing.T) {
root := catalogue(t, map[string]string{
"buildah": buildah,
"docker": `{"module":"docker","provides":[{"name":"rootless-image-build"}]}`,
"other": `{"module":"other","provides":[{"name":"x"},{"name":"rootless-image-build"}]}`,
})
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
want := []Finding{{Module: "docker"}, {Module: "other"}}
if !reflect.DeepEqual(got, want) {
t.Errorf("found %v, want %v", got, want)
}
}
func TestBuildahMustProvideIt(t *testing.T) {
root := catalogue(t, map[string]string{"buildah": `{"module":"buildah"}`})
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
if want := []Finding{{Module: "buildah"}}; !reflect.DeepEqual(got, want) {
t.Errorf("found %v, want %v", got, want)
}
}
// The catalogue as it stands: this is the check itself, run here too.
func TestTheCatalogueGivesTheProvisionToBuildahAlone(t *testing.T) {
got, err := Check("../..")
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("the catalogue's providers of rootless-image-build are wrong: %v", got)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest.
modules/*/module.json
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/imageplace
go 1.22
-91
View File
@@ -1,91 +0,0 @@
// Command imageplace refuses an image artifact whose Dockerfile is not a file inside its own module's directory
// (novox/hq ADR 0487 §1, issue 1707): the image builder builds a module from that module's directory alone, so
// a `from` that leaves it (../other/Dockerfile) or names a file that is not there could never build.
//
// go run . <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
// Finding is one image refused: Module's artifact Artifact names From, and Why says what is wrong with it.
type Finding struct {
Module, Artifact, From, Why string
}
// Check reads every manifest under root/modules and answers which images it refuses.
func Check(root string) ([]Finding, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Finding
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Build struct {
Artifacts []struct {
Name string `json:"name"`
Kind string `json:"kind"`
From string `json:"from"`
} `json:"artifacts"`
} `json:"build"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
dir := filepath.Dir(path)
for _, a := range m.Build.Artifacts {
if a.Kind != "image" {
continue
}
f := Finding{Module: m.Module, Artifact: a.Name, From: a.From}
clean := filepath.Clean(a.From)
switch {
case a.From == "" || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)):
f.Why = "is not a path inside the module's own directory"
default:
info, err := os.Stat(filepath.Join(dir, clean))
if err != nil || info.IsDir() {
f.Why = "is not a file in the module's directory"
}
}
if f.Why != "" {
out = append(out, f)
}
}
}
return out, nil
}
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)
}
for _, f := range found {
fmt.Printf("%s: image %s builds from %q, which %s (novox/hq ADR 0487 §1)\n", f.Module, f.Artifact, f.From, f.Why)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("imageplace: every image builds from a Dockerfile in its own module's directory")
}
-84
View File
@@ -1,84 +0,0 @@
package main
import (
"os"
"path/filepath"
"reflect"
"testing"
)
// module writes modules/<name>/module.json with one image artifact from `from`, and the files named.
func module(t *testing.T, root, name, from string, files ...string) {
t.Helper()
dir := filepath.Join(root, "modules", name)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
body := `{"module":"` + name + `","build":{"artifacts":[{"name":"server","kind":"image","from":"` + from + `"},{"name":"files","kind":"archive","from":"../elsewhere"}]}}`
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
for _, f := range files {
p := filepath.Join(dir, f)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte("FROM scratch\n"), 0o644); err != nil {
t.Fatal(err)
}
}
}
func TestADockerfileInTheModulesOwnDirectoryPasses(t *testing.T) {
root := t.TempDir()
module(t, root, "own", "Dockerfile", "Dockerfile")
module(t, root, "nested", "image/Dockerfile", "image/Dockerfile")
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("found %v, want none", got)
}
}
func TestADockerfileOutsideTheModulesDirectoryFails(t *testing.T) {
root := t.TempDir()
module(t, root, "other", "Dockerfile", "Dockerfile")
module(t, root, "leaves", "../other/Dockerfile")
module(t, root, "absolute", "/etc/Dockerfile")
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
var mods []string
for _, f := range got {
mods = append(mods, f.Module)
}
if want := []string{"absolute", "leaves"}; !reflect.DeepEqual(mods, want) {
t.Errorf("refused %v (%v), want %v", mods, got, want)
}
}
func TestAMissingDockerfileFails(t *testing.T) {
root := t.TempDir()
module(t, root, "gone", "Dockerfile")
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(got) != 1 || got[0].Module != "gone" {
t.Errorf("found %v, want gone refused", got)
}
}
// The catalogue as it stands: this is the check itself, run here too.
func TestTheCataloguesImagesBuildFromTheirOwnDirectories(t *testing.T) {
got, err := Check("../..")
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("an image builds from outside its module: %v", got)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest, and whether the path each image artifact builds from is there.
modules
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/locks
go 1.22
-105
View File
@@ -1,105 +0,0 @@
// Command locks refuses a TypeScript module that names packages beyond the SDK in its package.json and commits no
// package-lock.json (novox/hq ADR 0469 §5, issue 1868): the Node build module installs a module's own packages only
// from its committed lock, so such a module is refused by the builder and could not be delivered.
//
// go run . <catalogue root>
//
// Exits 1 on a finding. The packages that count are those the build module counts (mesh-build-node fetch.go
// ownDependencies): the `dependencies` beyond the SDK, typescript and the @types packages.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
// Finding is one module refused: it names Packages beyond the SDK and has no lock file.
type Finding struct {
Module string
Packages []string
}
// Check reads every module under root/modules that builds a TypeScript artifact.
func Check(root string) ([]Finding, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Finding
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Build struct {
Artifacts []struct {
Language string `json:"language"`
} `json:"artifacts"`
} `json:"build"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
typescript := false
for _, a := range m.Build.Artifacts {
typescript = typescript || a.Language == "typescript"
}
if !typescript {
continue
}
dir := filepath.Dir(path)
pkgRaw, err := os.ReadFile(filepath.Join(dir, "package.json"))
if os.IsNotExist(err) {
continue
}
if err != nil {
return nil, err
}
var pkg struct {
Dependencies map[string]json.RawMessage `json:"dependencies"`
}
if err := json.Unmarshal(pkgRaw, &pkg); err != nil {
return nil, fmt.Errorf("%s: package.json: %w", m.Module, err)
}
var names []string
for n := range pkg.Dependencies {
if n != "@novox/mesh-sdk" && n != "typescript" && !strings.HasPrefix(n, "@types/") {
names = append(names, n)
}
}
if len(names) == 0 {
continue
}
if _, err := os.Stat(filepath.Join(dir, "package-lock.json")); err != nil {
sort.Strings(names)
out = append(out, Finding{Module: m.Module, Packages: names})
}
}
return out, nil
}
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)
}
for _, f := range found {
fmt.Printf("%s names packages beyond the SDK (%s) and has no package-lock.json: commit one made by the build module's toolchain (novox/hq ADR 0469 §5)\n", f.Module, strings.Join(f.Packages, ", "))
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("locks: every TypeScript module that names packages beyond the SDK commits its lock")
}
-75
View File
@@ -1,75 +0,0 @@
package main
import (
"os"
"path/filepath"
"reflect"
"testing"
)
// module writes modules/<name> with a TypeScript artifact, the package.json given ("" for none) and, when lock, a lock.
func module(t *testing.T, root, name, language, pkg string, lock bool) {
t.Helper()
dir := filepath.Join(root, "modules", name)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
write := func(file, body string) {
if err := os.WriteFile(filepath.Join(dir, file), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
write("module.json", `{"module":"`+name+`","build":{"artifacts":[{"name":"tools","kind":"bundle","language":"`+language+`","from":"index.ts"}]}}`)
if pkg != "" {
write("package.json", pkg)
}
if lock {
write("package-lock.json", "{}")
}
}
const sdkOnly = `{"dependencies":{"@novox/mesh-sdk":"^0.1.1"},"devDependencies":{"typescript":"^5","@types/node":"^22"}}`
const withPg = `{"dependencies":{"@novox/mesh-sdk":"^0.1.1","pg":"^8","@types/pg":"^8"}}`
func TestAModuleNamingOnlyTheSDKNeedsNoLock(t *testing.T) {
root := t.TempDir()
module(t, root, "plain", "typescript", sdkOnly, false)
module(t, root, "none", "typescript", "", false)
got, err := Check(root)
if err != nil || len(got) != 0 {
t.Fatalf("%v %v", got, err)
}
}
func TestAModuleNamingMorePackagesWithoutALockIsRefusedNamingThem(t *testing.T) {
root := t.TempDir()
module(t, root, "db", "typescript", withPg, false)
module(t, root, "db-locked", "typescript", withPg, true)
got, err := Check(root)
if err != nil {
t.Fatal(err)
}
if want := []Finding{{Module: "db", Packages: []string{"pg"}}}; !reflect.DeepEqual(got, want) {
t.Fatalf("got %v, want %v", got, want)
}
}
func TestAModuleThatIsNotTypeScriptIsNotJudged(t *testing.T) {
root := t.TempDir()
module(t, root, "go", "go", withPg, false)
if got, err := Check(root); err != nil || len(got) != 0 {
t.Fatalf("%v %v", got, err)
}
}
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2): every TypeScript module that names packages
// beyond the SDK commits its lock.
func TestTheCatalogueCommitsTheLocksItNeeds(t *testing.T) {
found, err := Check("../..")
if err != nil {
t.Fatal(err)
}
for _, f := range found {
t.Errorf("%s names packages beyond the SDK (%v) and has no package-lock.json: commit one made by the build module's toolchain (novox/hq ADR 0469 §5)", f.Module, f.Packages)
}
}
-7
View File
@@ -1,7 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest, a TypeScript module's package.json and whether its lock is there.
modules/*/module.json
modules/*/package.json
modules/*/package-lock.json
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/mailstore
go 1.22
-81
View File
@@ -1,81 +0,0 @@
// Command mailstore refuses any manifest but modules/mesh-mail claiming the mail-store seat (novox/hq ADR 0378
// §3, issue 800 part A): the operator's mail is read, searched and sent through this one seat, and a second
// claimant would be a second path to the same mail that ADR 0378 never accounted for.
//
// go run . <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
)
// MailStoreSeat is the seat only mesh-mail may claim.
const MailStoreSeat = "mail-store"
// MailStoreModule is the one module allowed to claim it.
const MailStoreModule = "mesh-mail"
// Finding is one manifest refused: a module other than mesh-mail claiming mail-store.
type Finding struct {
Module string
}
// Check reads every manifest under root/modules and answers which ones it refuses.
func Check(root string) ([]Finding, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
var out []Finding
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Claims []struct {
Name string `json:"name"`
} `json:"claims"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
if m.Module == MailStoreModule {
continue
}
for _, c := range m.Claims {
if c.Name == MailStoreSeat {
out = append(out, Finding{Module: m.Module})
break
}
}
}
return out, nil
}
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)
}
for _, f := range found {
fmt.Printf("%s claims the mail-store seat: only %s may (novox/hq ADR 0378 §3)\n", f.Module, MailStoreModule)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("mail-store: claimed only by mesh-mail")
}
-43
View File
@@ -1,43 +0,0 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func manifest(t *testing.T, root, module, claims string) {
t.Helper()
dir := filepath.Join(root, "modules", module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
body := `{"module":"` + module + `","claims":[` + claims + `]}`
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
func TestAnotherModuleClaimingMailStoreIsRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "mesh-mail", `{"name":"mail-store","scope":"mesh"}`)
manifest(t, root, "betterbird", `{"name":"mail-store","scope":"mesh"}`)
manifest(t, root, "dunst", `{"name":"node-notifier","scope":"node"}`)
found, err := Check(root)
if err != nil {
t.Fatal(err)
}
if len(found) != 1 || found[0].Module != "betterbird" {
t.Fatalf("refused %+v", found)
}
}
func TestTheCatalogueHasNone(t *testing.T) {
found, err := Check("../..")
if err != nil {
t.Fatal(err)
}
if len(found) != 0 {
t.Fatalf("%+v", found)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest.
modules/*/module.json
-5
View File
@@ -1,5 +0,0 @@
module checks/manifests
go 1.26.0
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
-2
View File
@@ -1,2 +0,0 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
-48
View File
@@ -1,48 +0,0 @@
// Package manifests reads every module's manifest as the catalogue's tree holds it and refuses what no judge could
// read: a module.json that does not decode, and two manifests that name one module.
//
// It judges no manifest (novox/hq ADR 0578 §2): the checker judges every change's manifests with the indexer's judge, the
// one judge, so the field, seat, bus and catalogue rules are said once, there. Until 2026-11 this check parsed each
// manifest with mesh-sdk's strict parser and ran the rules between manifests (issue 774, power's bar item of
// mesh-catalog ca8d6f33); that judgement moved to the checker, and what stays is the two refusals that are about the
// tree, not a manifest's rules.
package manifests
import (
"fmt"
"os"
"path/filepath"
"sort"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Problems is what is wrong with the tree's manifests under root/modules, one line each: a module.json whose bytes are
// no manifest (catalogue.Decode: not JSON, or a field of the wrong shape), then a module named by two manifests.
func Problems(root string) ([]string, error) {
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(files)
var out []string
named := map[string]bool{}
for _, f := range files {
raw, err := os.ReadFile(f)
if err != nil {
return nil, err
}
rel, _ := filepath.Rel(root, f)
m, err := catalogue.Decode(raw)
if err != nil {
out = append(out, fmt.Sprintf("%s: %v", rel, err))
continue
}
if named[m.Module] {
out = append(out, fmt.Sprintf("%s: %s was already given; two manifests name one module", rel, m.Module))
continue
}
named[m.Module] = true
}
return out, nil
}
-50
View File
@@ -1,50 +0,0 @@
package manifests
import (
"os"
"path/filepath"
"strings"
"testing"
)
// The tree's manifests are ones a judge can read, and no module is named twice. A row is a catalogue of its own: a
// manifest that is no JSON and two manifests of one module are refused, naming the file; a manifest with a field this
// check does not know is not (it judges nothing: the checker does, ADR 0578 §2). The catalogue itself passes.
func TestEveryManifestDecodesAndNamesOneModule(t *testing.T) {
for _, row := range []struct {
name string
files map[string]string
refused string
}{
{name: "a manifest the judge can read", files: map[string]string{"a": `{"module":"a","version":"1"}`}},
{name: "a field no part knows is the judge's to refuse", files: map[string]string{"a": `{"module":"a","version":"1","unheard":true}`}},
{name: "bytes that are no manifest", files: map[string]string{"a": `{"module":`}, refused: "modules/a/module.json"},
{name: "two manifests of one module", files: map[string]string{"a": `{"module":"x","version":"1"}`, "b": `{"module":"x","version":"1"}`},
refused: "two manifests name one module"},
} {
root := t.TempDir()
for dir, manifest := range row.files {
if err := os.MkdirAll(filepath.Join(root, "modules", dir), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "modules", dir, "module.json"), []byte(manifest), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Problems(root)
if err != nil {
t.Fatal(err)
}
joined := strings.Join(got, "\n")
if (row.refused == "") != (len(got) == 0) || !strings.Contains(joined, row.refused) {
t.Errorf("%s: refused %q, want %q", row.name, joined, row.refused)
}
}
real, err := Problems(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if len(real) != 0 {
t.Fatalf("the catalogue holds manifests no judge can read:\n%s", strings.Join(real, "\n"))
}
}
-3
View File
@@ -1,3 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (issue 1936).
# every module's directory (its manifest), decoded.
modules
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/nobinaries
go 1.22
-84
View File
@@ -1,84 +0,0 @@
// Package nobinaries refuses a compiled program anywhere in the repository (issue 1577; first a checks/ binary slipped past a modules/-only rule): it is build output, made by the module's build step,
// and `go build` in a module's folder writes one beside go.mod where `git add -A` takes it.
package nobinaries
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Kind says what executable format the first bytes of a file are, or "" when they are none.
func Kind(head []byte) string {
switch {
case bytes.HasPrefix(head, []byte("\x7fELF")):
return "ELF"
case bytes.HasPrefix(head, []byte("MZ")):
return "PE"
case bytes.HasPrefix(head, []byte{0xcf, 0xfa, 0xed, 0xfe}), bytes.HasPrefix(head, []byte{0xce, 0xfa, 0xed, 0xfe}),
bytes.HasPrefix(head, []byte{0xfe, 0xed, 0xfa, 0xcf}), bytes.HasPrefix(head, []byte{0xfe, 0xed, 0xfa, 0xce}),
bytes.HasPrefix(head, []byte{0xca, 0xfe, 0xba, 0xbe}) && len(head) >= 8 && head[7] < 0x20 && head[6] == 0 && head[5] == 0 && head[4] == 0:
return "Mach-O"
}
return ""
}
// Files are the files of the catalogue's root that the change holds, anywhere: the tracked ones when the root is a git
// checkout (an ignored binary a local build left is not the change's), else every file there but .git's.
func Files(root string) ([]string, error) {
cmd := exec.Command("git", "-C", root, "ls-files", "-z")
if out, err := cmd.Output(); err == nil && len(out) > 0 {
var files []string
for _, f := range strings.Split(strings.TrimRight(string(out), "\x00"), "\x00") {
if _, err := os.Lstat(filepath.Join(root, f)); err == nil { // a file the change deletes is not there
files = append(files, f)
}
}
return files, nil
}
var files []string
err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() && d.Name() == ".git" {
return filepath.SkipDir
}
if d.IsDir() || !d.Type().IsRegular() {
return nil
}
rel, _ := filepath.Rel(root, p)
files = append(files, rel)
return nil
})
return files, err
}
// Find names each file of files (paths from root) that is a compiled program, with its format.
func Find(root string, files []string) ([]string, error) {
var found []string
for _, f := range files {
p := filepath.Join(root, f)
if st, err := os.Lstat(p); err != nil || !st.Mode().IsRegular() {
continue
}
fh, err := os.Open(p)
if err != nil {
return nil, err
}
head := make([]byte, 8)
n, err := io.ReadFull(fh, head)
fh.Close()
if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
return nil, err
}
if k := Kind(head[:n]); k != "" {
found = append(found, fmt.Sprintf("%s (%s)", f, k))
}
}
return found, nil
}
-89
View File
@@ -1,89 +0,0 @@
package nobinaries
import (
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestFindNamesEachCompiledProgramAndNothingElse(t *testing.T) {
root := t.TempDir()
files := map[string][]byte{
"modules/a/a": []byte("\x7fELF\x02\x01\x01\x00rest"),
"modules/b/cmd/b/b.exe": []byte("MZ\x90\x00"),
"modules/c/c": {0xcf, 0xfa, 0xed, 0xfe, 7, 0, 0, 1},
"modules/d/main.go": []byte("package main\n"),
"modules/e/short": []byte("\x7f"),
"modules/f/empty": {},
"modules/g/Foo.class": {0xca, 0xfe, 0xba, 0xbe, 0, 0, 0, 52}, // Java, not Mach-O
"checks/words/words": []byte("\x7fELF\x02\x01\x01\x00rest"), // outside modules/ too
}
var names []string
for f, b := range files {
p := filepath.Join(root, f)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, b, 0o644); err != nil {
t.Fatal(err)
}
names = append(names, f)
}
got, err := Find(root, names)
if err != nil {
t.Fatal(err)
}
if len(got) != 4 {
t.Fatalf("found %v; wanted modules/a/a, modules/b/cmd/b/b.exe and modules/c/c and checks/words/words only", got)
}
}
// The catalogue's own tree holds no compiled program: a compiled program is build output, so remove it and add it to .gitignore.
func TestTheCatalogueHoldsNoCompiledProgram(t *testing.T) {
root := filepath.Join("..", "..")
files, err := Files(root)
if err != nil {
t.Fatal(err)
}
found, err := Find(root, files)
if err != nil {
t.Fatal(err)
}
for _, f := range found {
t.Errorf("%s: a compiled program is build output: remove it and add it to .gitignore (issue 1577)", f)
}
}
// A compiled program outside modules/ is refused too (a binary under checks/ slipped past the modules/-only rule): Files holds
// every tracked file of a git checkout and every file of a plain folder, and Find names the binary in both.
func TestAProgramUnderChecksIsRefused(t *testing.T) {
for _, git := range []bool{true, false} {
root := t.TempDir()
p := filepath.Join(root, "checks", "words", "words")
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte("\x7fELF\x02\x01\x01\x00rest"), 0o644); err != nil {
t.Fatal(err)
}
if git {
for _, args := range [][]string{{"init", "-q"}, {"add", "-A"}} {
if out, err := exec.Command("git", append([]string{"-C", root}, args...)...).CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", args, err, out)
}
}
}
files, err := Files(root)
if err != nil {
t.Fatal(err)
}
found, err := Find(root, files)
if err != nil {
t.Fatal(err)
}
if len(found) != 1 {
t.Errorf("git=%v: found %v; wanted checks/words/words (ELF)", git, found)
}
}
}
-1
View File
@@ -1 +0,0 @@
# every tracked file of the repository (git ls-files, no path limit): the whole tree, so this package's verdict is never kept.
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/noplacement
go 1.22
-103
View File
@@ -1,103 +0,0 @@
// 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`;
//
// The controller's assignments, which item 4 once listed readers of, are refused with every other name of the
// controller by checks/controllerdeps (novox/hq ADR 0590 §5).
//
// go run . <catalogue root>
//
// 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))
}
}
sort.Strings(out)
return out, 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 <catalogue root>")
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, and no manifest reads the indexer's placement")
}
-55
View File
@@ -1,55 +0,0 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestFindingsRefuseWhatThePlacementPathLeft(t *testing.T) {
for _, row := range []struct {
name string
files map[string]string
want string // empty: clean
}{
{"clean", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-delivery-assigner.placement"]}`}, ""},
{"a placement directory", map[string]string{"placement/ace.json": `{}`}, "placement: the repository holds no placement"},
{"a particulars file", map[string]string{"particulars": "ace\n"}, "particulars: the repository lists no node names"},
{"a read of the indexer's placement", map[string]string{"modules/a/module.json": `{"module":"a","reads":["mesh-delivery-indexer.placement"]}`}, `a: names "mesh-delivery-indexer.placement"`},
{"a read of its candidates", map[string]string{"modules/b/module.json": `{"module":"b","reads":["mesh-delivery-indexer.placement-candidates"]}`}, "placement-candidates"},
{"its event", map[string]string{"modules/c/module.json": `{"module":"c","on":[{"event":"mesh-delivery-indexer.placement-changed"}]}`}, "placement-changed"},
{"the seat's own state is allowed", map[string]string{"modules/d/module.json": `{"module":"d","reads":["mesh-placement.placement"]}`}, ""},
} {
t.Run(row.name, func(t *testing.T) {
root := t.TempDir()
for path, text := range row.files {
p := filepath.Join(root, path)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Findings(root)
if err != nil {
t.Fatal(err)
}
if all := strings.Join(got, "\n"); (row.want == "") != (all == "") || !strings.Contains(all, row.want) {
t.Fatalf("findings = %q, want one containing %q", all, row.want)
}
})
}
}
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2).
func TestTheCatalogueHoldsNoPlacementOfTheRepositoryPath(t *testing.T) {
got, err := Findings(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
for _, f := range got {
t.Error(f)
}
}
-6
View File
@@ -1,6 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# the root, for a placement directory, and every manifest, for a read of the indexer's placement states.
?placement
modules/*/module.json
-3
View File
@@ -1,3 +0,0 @@
module checks/noscripts
go 1.22
-19
View File
@@ -1,19 +0,0 @@
package noscripts
import (
"errors"
"os"
"path/filepath"
"testing"
)
// The catalogue's checks are Go tests (novox/hq ADR 0529 clause 5, ADR 0445): the checker runs every nested Go module's tests by
// the language's convention and no script at the root says what else to run. A script back at the root would be a check nobody
// runs (ADR 0503 §3: since R4e the checker has not run them), so it is refused.
func TestNoScriptOfTheOldCheckStandsAtTheRoot(t *testing.T) {
for _, name := range []string{"merge-check.sh", "merge-check-typescript.sh", "check-nested-modules.sh"} {
if _, err := os.Stat(filepath.Join("..", "..", name)); !errors.Is(err, os.ErrNotExist) {
t.Errorf("%s stands at the catalogue's root (%v): what it ran is a Go test in checks/ or a step of the build module", name, err)
}
}
}
-7
View File
@@ -1,7 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# that none of the old check scripts stands at the root.
?merge-check.sh
?merge-check-typescript.sh
?check-nested-modules.sh
-7
View File
@@ -1,7 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/owned
go 1.22
require git.novox.be/novox/mesh-catalog/checks/trunk v0.0.0
replace git.novox.be/novox/mesh-catalog/checks/trunk => ../trunk
-410
View File
@@ -1,410 +0,0 @@
// Command owned judges how the catalogue's directories and archives treat files they did not write (novox/hq
// ADR 0422 §8, issue 1024). It refuses, naming the module, the resource and the reason:
//
// 1. a directory or archive without `unknown`, or with a value but move-out, module or program;
//
// 2. `move-out` on a path not under ${machine:account-home} or ${machine:agent-home};
//
// 3. `knows` with an empty, absolute, `.`/`..`/empty-component or repeated path;
//
// 4. a path one module declares (a resource's path, or a fact with "home": true) lying inside a directory of
// another module, after ${dir:<id>} is resolved within each module and both homes are read as one root.
// Archives are not containers for this rule. This holds for ~/.ssh what checks/sshdir held (ADR 0415 §6).
//
// 5. a directory that is, lies inside or holds a directory a system package owns under /var/lib, or any other
// resource inside one (issue 1841: pacman's state directory, placed with "place": ".", was /var/lib/pacman, the
// package manager's own database directory). A placed directory is resolved as the mesh resolves it with the
// default data root: "." is /var/lib/<module>, "mesh" /var/lib/mesh/<module>, neither /var/lib/<module>/<id>.
// The system directories are checks/owned/system, captured from a machine (pacman -Qo, /usr/lib/tmpfiles.d);
// checks/owned/system-known names the exceptions still to fix, by module, resource and issue. That list only
// shrinks: an entry that no longer collides is refused, and with -base one the trunk's copy does not hold.
//
// go run . [-base <trunk's system-known>] <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
)
// The lists of rule 5, relative to the catalogue's root.
const (
SystemList = "checks/owned/system"
SystemKnown = "checks/owned/system-known"
)
// Home is the one root both machine homes are read as.
const Home = "${home}"
var (
ofDir = regexp.MustCompile(`\$\{dir:([a-z0-9][a-z0-9_-]*)\}`)
homeVar = strings.NewReplacer("${machine:account-home}", Home, "${machine:agent-home}", Home)
values = map[string]bool{"move-out": true, "module": true, "program": true}
)
// Finding is one refusal.
type Finding struct {
Rule int
Module, ID, Why string
}
type resource struct {
ID, Type, Path, Unknown, Place string
Knows []string
HasKnows bool
}
type parsed struct {
Module string
Resources []resource
Facts []string // paths of facts with "home": true, under the home
}
func load(file string) (*parsed, error) {
raw, err := os.ReadFile(file)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Resources []struct {
ID string `json:"id"`
Type string `json:"type"`
Path string `json:"path"`
Place string `json:"place"`
Unknown *string `json:"unknown"`
Knows []string `json:"knows"`
} `json:"resources"`
Facts map[string]struct {
Path string `json:"path"`
Home bool `json:"home"`
} `json:"facts"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", file, err)
}
out := &parsed{Module: m.Module}
if out.Module == "" {
out.Module = filepath.Base(filepath.Dir(file))
}
for _, r := range m.Resources {
x := resource{ID: r.ID, Type: r.Type, Path: r.Path, Place: r.Place, Knows: r.Knows, HasKnows: r.Knows != nil}
if r.Unknown != nil {
x.Unknown = *r.Unknown
if x.Unknown == "" {
x.Unknown = "(empty)"
}
}
out.Resources = append(out.Resources, x)
}
keys := make([]string, 0, len(m.Facts))
for k := range m.Facts {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if f := m.Facts[k]; f.Home && f.Path != "" {
out.Facts = append(out.Facts, f.Path)
}
}
return out, nil
}
// norm resolves ${dir:<id>} from the module's own resources, reads both homes as one root and cleans the path.
func norm(p string, dirs map[string]string) string {
for i := 0; i < 8 && ofDir.MatchString(p); i++ {
p = ofDir.ReplaceAllStringFunc(p, func(w string) string {
if to, ok := dirs[ofDir.FindStringSubmatch(w)[1]]; ok {
return to
}
return w
})
}
p = homeVar.Replace(p)
if p == "" {
return ""
}
return path.Clean(p)
}
func underHome(p string) bool { return strings.HasPrefix(p, Home+"/") }
// badKnows says why a knows entry is refused, or "".
func badKnows(k string) string {
if k == "" {
return "is empty"
}
if strings.HasPrefix(k, "/") {
return "is absolute"
}
for _, c := range strings.Split(strings.TrimSuffix(k, "/"), "/") {
if c == "" || c == "." || c == ".." {
return "has an empty, . or .. component"
}
}
return ""
}
type declared struct {
module, id, path string
}
// Check reads every manifest under root/modules and answers what it refuses.
func Check(root string, base []string) ([]Finding, error) {
files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(files)
system, err := readSystem(filepath.Join(root, SystemList))
if err != nil {
return nil, err
}
known, err := readLines(filepath.Join(root, SystemKnown))
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
var out []Finding
var paths []declared
collided := map[string]bool{}
type container struct{ module, path string }
var containers []container
for _, file := range files {
m, err := load(file)
if err != nil {
return nil, err
}
dirs := map[string]string{}
for _, r := range m.Resources {
switch {
case r.ID != "" && r.Path != "":
dirs[r.ID] = r.Path
case r.ID != "" && r.Type == "directory":
// A directory the mesh places for the module (`place`): where it lies is the node-engine's, but it is
// the module's own, so a path under it belongs to this module and to no other's directory of the same
// id (issue 1484: two runs-as modules each keep a .ssh in their own state directory).
dirs[r.ID] = "/placed/" + m.Module + "/" + r.ID
}
}
out = append(out, inSystem(m, system, known, collided)...)
for _, r := range m.Resources {
p := norm(r.Path, dirs)
if p != "" {
paths = append(paths, declared{m.Module, r.ID, p})
}
if r.Type != "directory" && r.Type != "archive" {
continue
}
if r.Type == "directory" && p != "" {
containers = append(containers, container{m.Module, p})
}
add := func(rule int, why string) { out = append(out, Finding{rule, m.Module, r.ID, why}) }
switch {
case r.Unknown == "":
add(1, "a "+r.Type+" without `unknown` (move-out, module or program)")
case !values[r.Unknown]:
add(1, fmt.Sprintf("`unknown` is %q, not move-out, module or program", r.Unknown))
case r.Unknown == "move-out" && !underHome(p):
add(2, fmt.Sprintf("`move-out` on %q, which is not under ${machine:account-home} or ${machine:agent-home}", r.Path))
}
seen := map[string]bool{}
for _, k := range r.Knows {
if why := badKnows(k); why != "" {
add(3, fmt.Sprintf("`knows` entry %q %s", k, why))
continue
}
c := strings.TrimSuffix(k, "/")
if seen[c] {
add(3, fmt.Sprintf("`knows` entry %q is repeated", k))
}
seen[c] = true
}
if r.HasKnows && len(r.Knows) == 0 {
add(3, "`knows` is empty")
}
}
for _, f := range m.Facts {
if p := norm(Home+"/"+f, nil); p != "" {
paths = append(paths, declared{m.Module, "fact " + f, p})
}
}
}
for _, k := range known {
if !collided[k] {
module, id, _ := strings.Cut(k, " ")
id, _, _ = strings.Cut(id, " ")
out = append(out, Finding{5, module, id, fmt.Sprintf("is listed in %s, and no longer lies in a system "+
"package's directory: take the line out, so the list only shrinks", SystemKnown)})
}
}
if base != nil {
held := map[string]bool{}
for _, b := range base {
held[b] = true
}
for _, k := range known {
if !held[k] {
module, id, _ := strings.Cut(k, " ")
id, _, _ = strings.Cut(id, " ")
out = append(out, Finding{5, module, id, fmt.Sprintf("is a new line in %s, which the trunk's copy "+
"does not hold: a module's directory is placed outside a system package's, never listed", SystemKnown)})
}
}
}
for _, d := range paths {
for _, c := range containers {
if c.module != d.module && (d.path == c.path || strings.HasPrefix(d.path, c.path+"/")) {
out = append(out, Finding{4, d.module, d.id, fmt.Sprintf("declares %q, inside a directory of %s (%q)", d.path, c.module, c.path)})
}
}
}
return out, nil
}
// systemDir is one directory a system package owns, and what says so.
type systemDir struct{ Path, Source string }
// readLines is a list's lines, without comments and blank lines.
func readLines(file string) ([]string, error) {
raw, err := os.ReadFile(file)
if err != nil {
return nil, err
}
var lines []string
for _, line := range strings.Split(string(raw), "\n") {
if line = strings.TrimSpace(line); line != "" && !strings.HasPrefix(line, "#") {
lines = append(lines, line)
}
}
return lines, nil
}
// readSystem is the list of system directories: "<absolute path> <package>: <how it was read>" per line. Absent or
// empty, the check fails: a list that says nothing would let every module through.
func readSystem(file string) ([]systemDir, error) {
lines, err := readLines(file)
if err != nil {
return nil, fmt.Errorf("rule 5 reads the system packages' directories from %s: %w", file, err)
}
if len(lines) == 0 {
return nil, fmt.Errorf("%s names no system package's directory, so rule 5 would pass everything", file)
}
var out []systemDir
for _, line := range lines {
p, source, _ := strings.Cut(line, " ")
if !strings.HasPrefix(p, "/var/lib/") || strings.TrimSpace(source) == "" {
return nil, fmt.Errorf("%s: %q is not \"/var/lib/<directory> <package>: <how it was read>\"", file, line)
}
out = append(out, systemDir{path.Clean(p), strings.TrimSpace(source)})
}
return out, nil
}
// placedAt is where the mesh places a pathless directory with the default data root (mesh-sdk catalogue dirsFor).
func placedAt(module string, r resource) string {
switch r.Place {
case ".":
return "/var/lib/" + module
case "mesh":
return "/var/lib/mesh/" + module
}
return "/var/lib/" + module + "/" + r.ID
}
// inSystem is rule 5 for one module: each resource whose resolved path collides with a system package's directory,
// unless system-known lists it (then it is marked collided, so a stale line is found).
func inSystem(m *parsed, system []systemDir, known []string, collided map[string]bool) []Finding {
real := map[string]string{}
for _, r := range m.Resources {
if r.Type == "directory" && r.Path == "" && r.ID != "" {
real[r.ID] = placedAt(m.Module, r)
}
}
for _, r := range m.Resources {
if r.ID != "" && r.Path != "" {
real[r.ID] = r.Path
}
}
var out []Finding
for _, r := range m.Resources {
p := ""
if r.Path != "" {
p = norm(r.Path, real)
} else if r.Type == "directory" {
p = real[r.ID]
}
if !strings.HasPrefix(p, "/") {
continue
}
for _, s := range system {
inside := p == s.Path || strings.HasPrefix(p, s.Path+"/")
holds := r.Type == "directory" && strings.HasPrefix(s.Path, p+"/")
if !inside && !holds {
continue
}
listed := ""
for _, k := range known {
if strings.HasPrefix(k, m.Module+" "+r.ID+" ") || k == m.Module+" "+r.ID {
listed = k
}
}
if listed != "" {
collided[listed] = true
break
}
how := "is or lies inside"
if holds {
how = "holds"
}
out = append(out, Finding{5, m.Module, r.ID, fmt.Sprintf("resolves to %q, which %s %s, the directory of the "+
"system package %s; place it under /var/lib/mesh/%s (\"place\": \"mesh\") or a path of its own", p, how,
s.Path, s.Source, m.Module)})
break
}
}
return out
}
func main() {
baseFile := flag.String("base", "", "the trunk's copy of "+SystemKnown+": a line it does not hold is refused")
flag.Parse()
root := "."
if flag.NArg() > 0 {
root = flag.Arg(0)
}
var base []string
if *baseFile != "" {
lines, err := readLines(*baseFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
base = append([]string{}, lines...)
}
found, err := Check(root, base)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
for _, f := range found {
cite := "novox/hq ADR 0422 §8, issue 1024"
if f.Rule == 5 {
cite = "issue 1841"
}
fmt.Printf("%s: %s: %s (rule %d, %s)\n", f.Module, f.ID, f.Why, f.Rule, cite)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("owned paths: every directory and archive says how it treats unknown files; no module declares inside another's directory, or in a system package's")
}
-248
View File
@@ -1,248 +0,0 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func manifest(t *testing.T, root, module, body string) {
t.Helper()
dir := filepath.Join(root, "modules", module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
func check(t *testing.T, root string) []Finding {
t.Helper()
if _, err := os.Stat(filepath.Join(root, SystemList)); os.IsNotExist(err) {
lists(t, root, "/var/lib/pacman pacman: pacman -Qo\n", "")
}
found, err := Check(root, nil)
if err != nil {
t.Fatal(err)
}
return found
}
// only asserts exactly one finding, of the rule, naming the module and the resource (issue 1024).
func only(t *testing.T, found []Finding, rule int, module, id string) {
t.Helper()
if len(found) != 1 || found[0].Rule != rule || found[0].Module != module || found[0].ID != id || found[0].Why == "" {
t.Fatalf("found %+v; want one finding of rule %d for %s/%s with a reason", found, rule, module, id)
}
}
// lists writes the catalogue's list of system directories and its known exceptions.
func lists(t *testing.T, root, system, known string) {
t.Helper()
if err := os.MkdirAll(filepath.Join(root, "checks", "owned"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, SystemList), []byte(system), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, SystemKnown), []byte(known), 0o644); err != nil {
t.Fatal(err)
}
}
func dir(id, path, rest string) string {
return `{"id":"` + id + `","type":"directory","path":"` + path + `"` + rest + `}`
}
func mod(name string, resources ...string) string {
return `{"module":"` + name + `","resources":[` + strings.Join(resources, ",") + `]}`
}
func TestMissingOrUnknownValueIsRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "a", mod("a", dir("d", "/var/lib/a", "")))
only(t, check(t, root), 1, "a", "d")
root = t.TempDir()
manifest(t, root, "a", mod("a", `{"id":"z","type":"archive","path":"/opt/a","unknown":"delete"}`))
only(t, check(t, root), 1, "a", "z")
}
func TestMoveOutOutsideAHomeIsRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "a", mod("a", dir("d", "/etc/a.d", `,"unknown":"move-out"`)))
only(t, check(t, root), 2, "a", "d")
}
func TestMoveOutUnderAHomeIsAllowed(t *testing.T) {
root := t.TempDir()
manifest(t, root, "a", mod("a",
dir("d", "${machine:account-home}/.config/a", `,"unknown":"move-out"`),
dir("e", "${dir:d}/sub", `,"unknown":"move-out"`),
dir("f", "${machine:agent-home}/x", `,"unknown":"move-out"`),
dir("g", "/var/lib/a", `,"unknown":"program"`),
dir("h", "${machine:account-home}/.ssh", `,"unknown":"module"`)))
if found := check(t, root); len(found) != 0 {
t.Fatalf("refused %+v", found)
}
}
func TestBadKnowsIsRefused(t *testing.T) {
for _, knows := range []string{`[""]`, `["/abs"]`, `["a/../b"]`, `["./a"]`, `["a//b"]`, `["a","a"]`, `["a","a/"]`} {
root := t.TempDir()
manifest(t, root, "a", mod("a", dir("d", "${machine:account-home}/.config/a", `,"unknown":"move-out","knows":`+knows)))
only(t, check(t, root), 3, "a", "d")
}
root := t.TempDir()
manifest(t, root, "a", mod("a", dir("d", "${machine:account-home}/.config/a", `,"unknown":"move-out","knows":["forge.credentials","kept/"]`)))
if found := check(t, root); len(found) != 0 {
t.Fatalf("refused %+v", found)
}
}
func TestAnotherModulesPathInsideADirectoryIsRefused(t *testing.T) {
root := t.TempDir()
manifest(t, root, "owner", mod("owner", dir("ssh", "${machine:account-home}/.ssh", `,"unknown":"module"`)))
manifest(t, root, "intruder", mod("intruder", `{"id":"f","type":"file","path":"${machine:account-home}/.ssh/config.d/20"}`))
manifest(t, root, "viahome", mod("viahome", `{"id":"g","type":"file","path":"${machine:agent-home}/.ssh/x"}`))
manifest(t, root, "facty", `{"module":"facty","facts":{"hosts":{"path":".ssh/config.d/00","home":true}}}`)
manifest(t, root, "outside", mod("outside", `{"id":"o","type":"file","path":"${machine:account-home}/.sshrc"}`))
got := map[string]int{}
for _, f := range check(t, root) {
if f.Rule != 4 || f.Why == "" {
t.Fatalf("finding %+v", f)
}
got[f.Module]++
}
if len(got) != 3 || got["intruder"] != 1 || got["viahome"] != 1 || got["facty"] != 1 {
t.Fatalf("refused %v; want intruder, viahome, facty", got)
}
}
func TestDirReferencesAreResolvedPerModule(t *testing.T) {
root := t.TempDir()
manifest(t, root, "owner", mod("owner", dir("base", "${machine:account-home}/.config/o", `,"unknown":"move-out"`)))
manifest(t, root, "other", mod("other",
dir("base", "${machine:account-home}/.config/p", `,"unknown":"move-out"`),
`{"id":"f","type":"file","path":"${dir:base}/x"}`))
if found := check(t, root); len(found) != 0 {
t.Fatalf("refused %+v", found)
}
manifest(t, root, "third", mod("third",
dir("d", "${machine:account-home}/.config/t", `,"unknown":"move-out"`),
`{"id":"f","type":"file","path":"${dir:d}/../o/x"}`))
only(t, check(t, root), 4, "third", "f")
}
func TestAnArchiveIsNotAContainerAndSameModuleIsFine(t *testing.T) {
root := t.TempDir()
manifest(t, root, "a", mod("a",
`{"id":"arc","type":"archive","path":"/opt/a","unknown":"program"}`,
dir("d", "/opt/a2", `,"unknown":"program"`),
`{"id":"f","type":"file","path":"/opt/a2/x"}`))
manifest(t, root, "b", mod("b", `{"id":"f","type":"file","path":"/opt/a/x"}`))
if found := check(t, root); len(found) != 0 {
t.Fatalf("refused %+v", found)
}
}
// Issue 1484: a directory the mesh places for a module (no path, `place`) is that module's own: another module's
// directory of the same id, and a path under it, are not inside it.
func TestPlacedDirectoriesOfTwoModulesAreTheirOwn(t *testing.T) {
root := t.TempDir()
for _, name := range []string{"first", "second"} {
manifest(t, root, name, mod(name,
`{"id":"state","type":"directory","place":".","unknown":"program"}`,
dir("ssh", "${dir:state}/.ssh", `,"unknown":"program"`),
`{"id":"conf","type":"file","path":"${dir:ssh}/config"}`))
}
if found := check(t, root); len(found) != 0 {
t.Fatalf("refused %+v", found)
}
}
// 1841/AC18: a module's directory that resolves to a /var/lib directory a system package owns, lies inside one or
// holds one, is refused, whether placed (place ".", or by default beneath the module's own) or stated; a placed
// directory saying place "mesh" lies under /var/lib/mesh and passes (issue 1841: pacman's state was /var/lib/pacman).
func TestADirectoryOfASystemPackageIsRefused(t *testing.T) {
system := "/var/lib/pacman pacman: pacman -Qo\n/var/lib/systemd/coredump systemd: tmpfiles.d/systemd.conf\n"
for name, resources := range map[string][]string{
"placed-own": {`{"id":"state","type":"directory","place":".","unknown":"program"}`},
"placed-beneath": {`{"id":"coredump","type":"directory","unknown":"program"}`},
"stated-inside": {dir("d", "/var/lib/pacman/keeper", `,"unknown":"program"`)},
"stated-holding": {dir("d", "/var/lib/systemd", `,"unknown":"program"`)},
"file-inside": {`{"id":"state","type":"directory","place":"mesh","unknown":"program"}`,
`{"id":"f","type":"file","path":"/var/lib/pacman/keeper.json"}`},
} {
module := map[string]string{"placed-own": "pacman", "placed-beneath": "systemd"}[name]
if module == "" {
module = "keeper"
}
root := t.TempDir()
lists(t, root, system, "")
manifest(t, root, module, mod(module, resources...))
found := check(t, root)
if len(found) != 1 || found[0].Rule != 5 || found[0].Module != module ||
!strings.Contains(found[0].Why, "/var/lib/") || !strings.Contains(found[0].Why, "package") {
t.Errorf("%s: found %+v; want one finding of rule 5 for %s naming the path and the package", name, found, module)
}
}
root := t.TempDir()
lists(t, root, system, "")
manifest(t, root, "pacman", mod("pacman",
`{"id":"state","type":"directory","place":"mesh","unknown":"program"}`,
`{"id":"f","type":"file","path":"${dir:state}/keeper.json"}`,
dir("other", "/var/lib/pacman-other", `,"unknown":"program"`)))
if found := check(t, root); len(found) != 0 {
t.Fatalf("a directory under /var/lib/mesh/pacman was refused: %+v", found)
}
}
// 1841/AC18: a known exception passes; one that no longer collides is refused, so the list only shrinks; one the trunk's
// copy does not hold is refused; and an absent list of system directories fails the check rather than passing it.
func TestKnownSystemExceptionsOnlyShrink(t *testing.T) {
system := "/var/lib/systemd systemd: tmpfiles.d/systemd.conf\n"
known := "# comment\nsystemd state issue 1873\n"
root := t.TempDir()
lists(t, root, system, known)
manifest(t, root, "systemd", mod("systemd", `{"id":"state","type":"directory","place":".","unknown":"program"}`))
if found := check(t, root); len(found) != 0 {
t.Fatalf("a known exception was refused: %+v", found)
}
if found, err := Check(root, []string{"systemd state issue 1873"}); err != nil || len(found) != 0 {
t.Fatalf("a known exception held by the trunk was refused: %+v %v", found, err)
}
if found, err := Check(root, []string{}); err != nil || len(found) != 1 || found[0].Rule != 5 {
t.Fatalf("an exception the trunk does not hold passed: %+v %v", found, err)
}
manifest(t, root, "systemd", mod("systemd", `{"id":"state","type":"directory","place":"mesh","unknown":"program"}`))
only(t, check(t, root), 5, "systemd", "state")
root = t.TempDir()
manifest(t, root, "a", mod("a"))
if err := os.MkdirAll(filepath.Join(root, "checks", "owned"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, SystemList), []byte("# nothing\n"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := Check(root, nil); err == nil {
t.Fatal("an empty list of system directories passed")
}
}
// 1841/AC18: the catalogue as it stands passes rule 5 with its own lists.
func TestTheCatalogueKeepsOutOfSystemDirectories(t *testing.T) {
found, err := Check("../..", nil)
if err != nil {
t.Fatal(err)
}
for _, f := range found {
if f.Rule == 5 {
t.Errorf("%s: %s: %s", f.Module, f.ID, f.Why)
}
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest; its lists are this package's own files.
modules/*/module.json
-41
View File
@@ -1,41 +0,0 @@
package main
import (
"testing"
"git.novox.be/novox/mesh-catalog/checks/trunk"
)
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2): every rule holds.
func TestTheCatalogueKeepsEveryRuleOfOwnedPaths(t *testing.T) {
found, err := Check("../..", nil)
if err != nil {
t.Fatal(err)
}
for _, f := range found {
t.Errorf("%s: %s: %s (rule %d, novox/hq ADR 0422 §8)", f.Module, f.ID, f.Why, f.Rule)
}
}
// And the list of system directories' known exceptions holds no line the trunk's copy at the merge base does not (rule 5). A trunk
// with none had no list: its first copy. Skipped, and said, where no trunk is beside.
func TestTheSystemListHoldsNoLineTheTrunksDoesNot(t *testing.T) {
path, ok := trunk.Copy(t, "checks/owned/system-known")
var base []string
if ok {
lines, err := readLines(path)
if err != nil {
t.Fatal(err)
}
base = append([]string{}, lines...)
}
found, err := Check("../..", base)
if err != nil {
t.Fatal(err)
}
for _, f := range found {
if f.Rule == 5 {
t.Errorf("%s: %s: %s (rule %d, issue 1841)", f.Module, f.ID, f.Why, f.Rule)
}
}
}
-23
View File
@@ -1,23 +0,0 @@
# The directories under /var/lib that a system package owns (rule 5 of checks/owned, issue 1841). A module's
# directory, placed or stated, may not be one of these, lie inside one or hold one: the package's program and the
# package manager keep their own files there, and the node-engine would judge, chmod or chown the directory.
#
# One line per directory: "<path> <package>: <how it was read>". Captured on the workstation on 2026-10-22, through
# the mesh: pacman's own database by `pacman -Qo` (pacman_owns), the rest from the `d`, `D` and `Q` lines of
# /usr/lib/tmpfiles.d (node-reads.search for "/var/lib/"). A directory found to belong to a package is added here.
/var/lib/pacman pacman: pacman -Qo /var/lib/pacman/ (pacman 7.1.0)
/var/lib/alsa alsa-utils: tmpfiles.d/alsa-utils.conf
/var/lib/colord colord: tmpfiles.d/colord.conf
/var/lib/containers podman: tmpfiles.d/podman.conf
/var/lib/dbus dbus: tmpfiles.d/dbus.conf
/var/lib/geoclue geoclue: tmpfiles.d/geoclue.conf
/var/lib/lastlog util-linux: tmpfiles.d/lastlog2.conf
/var/lib/libuuid util-linux: tmpfiles.d/uuidd.conf
/var/lib/machines systemd: tmpfiles.d/systemd-nspawn.conf
/var/lib/openvpn/chroot networkmanager-openvpn: tmpfiles.d/nm-openvpn-tmpfiles.conf
/var/lib/portables systemd: tmpfiles.d/portables.conf
/var/lib/postgres postgresql: tmpfiles.d/postgresql.conf
/var/lib/private systemd: tmpfiles.d/systemd.conf
/var/lib/rpcbind rpcbind: tmpfiles.d/rpcbind.conf
/var/lib/systemd systemd: tmpfiles.d/systemd.conf
/var/lib/tpm2-tss/system/keystore tpm2-tss: tmpfiles.d/tpm2-tss-fapi.conf
-5
View File
@@ -1,5 +0,0 @@
# Modules whose directory lies in a system package's directory today (rule 5 of checks/owned, issue 1841), each with
# the issue that moves it out: "<module> <resource id> issue <number>". This list only shrinks: a line whose module
# no longer collides is refused, and so is a line the trunk's copy does not hold.
postgres state issue 1873
systemd state issue 1873
-3
View File
@@ -1,3 +0,0 @@
# The modules the check covers (novox/hq ADR 0507 §9, issue 1838), one a line; issue 1838 widens it.
pacman
kernel
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/particulars
go 1.22
-174
View File
@@ -1,174 +0,0 @@
// Command particulars keeps this mesh's node names out of the modules it covers (novox/hq ADR 0507 §9,
// issue 1838): a module is for any mesh, so no manifest, file, code string, identifier, comment, test name or
// fixture of a covered module names a node of this one.
//
// The names are the mesh's own, not the repository's: the checker hands them in the environment value
// MESH_NODE_NAMES, comma-separated, from the node records it reads (novox/hq ADR 0567 §16). A run with none fails
// loudly, since it would pass on anything. The modules covered are in `covered` at checks/particulars/covered, one a line. A name is matched
// whole and in any case; a hyphen, an underscore or a CamelCase boundary parts one, so a longer word holding a name is another word.
// The forge is not a node name: its host (git.novox.be) and the organisation path (novox/<repo>) are blanked
// before a line is read.
//
// go run . <catalogue root>
//
// Exits 1 naming file:line and the word, 2 when a list cannot be read.
package main
import (
"bufio"
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// forge is the forge's host and organisation path, blanked before a line is read; a path segment novox/ elsewhere
// (testdata/novox/x, /srv/novox/data) is not blanked.
var forge = regexp.MustCompile(`(?i)git\.novox\.be(?:/novox/[\w.-]+)?|(?:^|[^/\w.-])novox/[\w.-]+`)
func main() {
root := "."
if len(os.Args) > 1 {
root = os.Args[1]
}
os.Exit(check(root, namesFromEnv()))
}
// NamesEnv is the environment value that carries the mesh's node names (mesh-sdk delivery.NodeNamesEnv).
const NamesEnv = "MESH_NODE_NAMES"
// namesFromEnv is the names NamesEnv holds, comma-separated, trimmed, empty entries left out.
func namesFromEnv() []string {
var out []string
for _, n := range strings.Split(os.Getenv(NamesEnv), ",") {
if n = strings.TrimSpace(n); n != "" {
out = append(out, n)
}
}
return out
}
// check runs the check over a catalogue and returns the exit code: 0 clean, 1 a finding, 2 a list unreadable.
func check(root string, names []string) int {
var err error
if len(names) == 0 {
err = fmt.Errorf("no node names (%s is empty) — the check would pass on anything", NamesEnv)
}
var covered []string
if err == nil {
covered, err = readList(filepath.Join(root, "checks/particulars/covered"))
if err == nil && len(covered) == 0 {
err = fmt.Errorf("covered lists no module — the check would pass on anything")
}
}
if err != nil {
fmt.Fprintln(os.Stderr, "particulars:", err)
return 2
}
findings, err := run(root, names, covered)
if err != nil {
fmt.Fprintln(os.Stderr, "particulars:", err)
return 2
}
for _, f := range findings {
fmt.Println(f)
}
if len(findings) > 0 {
fmt.Printf("particulars: %d node name(s) in a module for any mesh (novox/hq ADR 0507 §9) — say what it shows, not where it was seen\n", len(findings))
return 1
}
fmt.Printf("particulars: none of %d node names in the %d covered modules\n", len(names), len(covered))
return 0
}
// readList reads a list: one entry a line; blank lines and lines starting with # are not entries.
func readList(path string) ([]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var out []string
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
out = append(out, line)
}
return out, sc.Err()
}
// run reads every text file of each covered module and returns the findings, sorted.
func run(root string, names, covered []string) ([]string, error) {
var out []string
for _, m := range covered {
dir := filepath.Join(root, "modules", m)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
switch d.Name() {
case "node_modules", "dist", "vendor", ".git":
return filepath.SkipDir
}
return nil
}
rel, _ := filepath.Rel(root, path)
// A path is read too: a testdata directory named after a node is a finding.
for _, n := range hits(filepath.ToSlash(rel), names) {
out = append(out, fmt.Sprintf("%s:0: node name %q in the path", filepath.ToSlash(rel), n))
}
if d.Name() == "go.sum" {
return nil
}
b, err := os.ReadFile(path)
if err != nil {
return err
}
if bytes.IndexByte(b, 0) >= 0 {
return nil
}
out = append(out, scan(filepath.ToSlash(rel), string(b), names)...)
return nil
})
if err != nil {
return nil, err
}
}
sort.Strings(out)
return out, nil
}
// scan returns a finding for each node name in src, whole-word and in any case.
func scan(file, src string, names []string) []string {
var out []string
for i, line := range strings.Split(src, "\n") {
for _, n := range hits(line, names) {
out = append(out, fmt.Sprintf("%s:%d: node name %q — %s", file, i+1, n, strings.Join(strings.Fields(line), " ")))
}
}
return out
}
// hits returns the node names one line holds: as a word (a hyphen or underscore parts it), as a part of a
// CamelCase identifier (TestAce, novoxTree), the forge's host and organisation path blanked first.
func hits(line string, names []string) []string {
line = forge.ReplaceAllStringFunc(line, func(s string) string { return strings.Repeat(" ", len(s)) })
var out []string
for _, n := range names {
q := regexp.QuoteMeta(n)
camel := regexp.QuoteMeta(strings.ToUpper(n[:1]) + n[1:])
rx := regexp.MustCompile(`(?i:(?:^|[^A-Za-z0-9])` + q + `(?:$|[^A-Za-z0-9]))|[a-z0-9]` + camel + `(?:$|[^a-z])|(?:^|[^A-Za-z0-9])` + q + `[A-Z0-9]`)
if rx.MatchString(line) {
out = append(out, strings.ToLower(n))
}
}
return out
}
-124
View File
@@ -1,124 +0,0 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
var names = []string{"novox", "ace", "shanks", "g14"}
func TestANodeNameIsRefusedWhereItIsWritten(t *testing.T) {
cases := map[string]struct{ file, src string }{
"manifest string": {"modules/kernel/module.json", "{\n \"description\": \"as on Shanks\"\n}\n"},
"file line": {"modules/kernel/files/boot.sh", "#!/bin/sh\necho run on g14\n"},
"go identifier": {"modules/kernel/cmd/x/a.go", "package main\n\nfunc novox() {}\n"},
"test name": {"modules/kernel/cmd/x/a_test.go", "package main\n\nfunc TestAce(t *testing.T) {}\n\nfunc TestOnAce_x(t *testing.T) {}\n"},
}
for name, c := range cases {
if got := scan(c.file, c.src, names); len(got) == 0 {
t.Errorf("%s: %q not refused", name, c.src)
}
}
}
func TestTheFindingNamesTheLineAndTheWord(t *testing.T) {
got := scan("f.sh", "a\nb\nrun on ACE now\n", names)
if len(got) != 1 || !strings.HasPrefix(got[0], "f.sh:3:") || !strings.Contains(got[0], "ace") {
t.Errorf("got %v", got)
}
}
func TestTheForgeIsNotANodeName(t *testing.T) {
for _, s := range []string{
"see novox/hq issue 5",
"module git.novox.be/novox/mesh-sdk/go v1.2.3",
"https://git.novox.be/novox/mesh-catalog",
"git.novox.be",
"space trace surface grace g14x",
} {
if got := scan("f", s, names); len(got) != 0 {
t.Errorf("%q refused: %v", s, got)
}
}
}
func TestANodeNameInAPathOrJoinedIsRefused(t *testing.T) {
for _, s := range []string{
"modules/kernel/testdata/novox/meminfo",
"/srv/novox/data",
"testdata/shanks-meminfo.txt",
"on-shanks",
"shanks-desktop",
"mesh-novox",
"func novoxTree() {}",
"aceFiles := 1",
"g14Meminfo := 2",
"novox_tree := 3",
} {
if got := scan("f", s, names); len(got) == 0 {
t.Errorf("%q not refused", s)
}
}
}
func TestANodeNamedPathIsRefusedThroughRun(t *testing.T) {
root := t.TempDir()
p := filepath.Join(root, "modules/kernel/testdata/ace/x")
os.MkdirAll(filepath.Dir(p), 0o755)
os.WriteFile(p, []byte("clean\n"), 0o644)
got, err := run(root, names, []string{"kernel"})
if err != nil || len(got) != 1 || !strings.HasPrefix(got[0], "modules/kernel/testdata/ace/x:0: ") {
t.Errorf("got %v, %v", got, err)
}
}
func TestCheckReturnsOneOnAFindingAndTwoOnNoNames(t *testing.T) {
root := t.TempDir()
write := func(p, s string) {
p = filepath.Join(root, p)
os.MkdirAll(filepath.Dir(p), 0o755)
os.WriteFile(p, []byte(s), 0o644)
}
write("checks/particulars/covered", "kernel\n")
write("modules/kernel/a", "clean\n")
if got := check(root, []string{"ace"}); got != 0 {
t.Errorf("clean = %d, want 0", got)
}
write("modules/kernel/a", "on ace\n")
if got := check(root, []string{"ace"}); got != 1 {
t.Errorf("finding = %d, want 1", got)
}
if got := check(root, nil); got != 2 {
t.Errorf("no names = %d, want 2", got)
}
}
func TestOnlyCoveredModulesAreRead(t *testing.T) {
root := t.TempDir()
write := func(p, s string) {
p = filepath.Join(root, p)
os.MkdirAll(filepath.Dir(p), 0o755)
os.WriteFile(p, []byte(s), 0o644)
}
write("modules/kernel/files/a", "on novox\n")
write("modules/other/files/a", "on novox\n")
got, err := run(root, names, []string{"kernel"})
if err != nil {
t.Fatal(err)
}
if len(got) != 1 || !strings.Contains(got[0], "modules/kernel/files/a:1") {
t.Errorf("got %v", got)
}
}
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2): no node name of this mesh in a covered module.
func TestTheCoveredModulesNameNoNode(t *testing.T) {
if _, set := os.LookupEnv(NamesEnv); !set {
t.Skip("ADR 0567 §16: this checker hands no node names (before D14 is delivered); the skip goes once it is")
}
if code := check("../..", namesFromEnv()); code != 0 {
t.Fatalf("the check of the real catalogue exits %d (see what it printed above)", code)
}
}
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# check("../.."): every file of the modules the covered list names. The node names come from the run (MESH_NODE_NAMES).
modules
-4
View File
@@ -1,7 +1,3 @@
module git.novox.be/novox/mesh-catalog/checks/paths
go 1.22
require git.novox.be/novox/mesh-catalog/checks/trunk v0.0.0
replace git.novox.be/novox/mesh-catalog/checks/trunk => ../trunk
+1 -2
View File
@@ -251,8 +251,7 @@ func main() {
}
var grown []string
if baseDir == "" {
fmt.Fprintln(os.Stderr, "NOT COMPARED: no base branch's lists were given (-base <dir>), so additions to them were not judged; that is a failure, not a pass (issue 1295)")
os.Exit(2)
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"))
-5
View File
@@ -1,5 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# Check("../.."): every manifest and every file of a module.
modules
-63
View File
@@ -1,63 +0,0 @@
package main
import (
"path/filepath"
"testing"
"git.novox.be/novox/mesh-catalog/checks/trunk"
)
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2): no module names another's state on its machine,
// and the known list holds only what still occurs.
func TestNoModuleNamesAnothersState(t *testing.T) {
root := filepath.Join("..", "..")
found, err := Check(root)
if err != nil {
t.Fatal(err)
}
shared, _, err := list("shared")
if err != nil {
t.Fatal(err)
}
known, _, err := list("known")
if err != nil {
t.Fatal(err)
}
refused, gone := Judge(found, shared, known)
for _, f := range refused {
t.Error(f)
}
for _, k := range gone {
t.Errorf("%q is in checks/paths/known and no longer occurs: take it out, so it cannot come back", k)
}
}
// And neither list gains a line the trunk's copies at the merge base do not hold, unless the line says why after `# allow:`. Fails, saying
// NOT COMPARED, where no trunk is beside (issue 1295).
func TestTheListsOfPathsHoldNoLineTheTrunksDoNot(t *testing.T) {
sharedPath, okShared := trunk.Copy(t, "checks/paths/shared")
knownPath, okKnown := trunk.Copy(t, "checks/paths/known")
if !okShared || !okKnown {
t.Log("the trunk holds not both lists: their first copies, to which nothing was added")
return
}
shared, sharedWhy, err := list("shared")
if err != nil {
t.Fatal(err)
}
known, knownWhy, err := list("known")
if err != nil {
t.Fatal(err)
}
baseShared, _, err := list(sharedPath)
if err != nil {
t.Fatal(err)
}
baseKnown, _, err := list(knownPath)
if err != nil {
t.Fatal(err)
}
for _, g := range append(Grown("shared", shared, sharedWhy, baseShared), Grown("known", known, knownWhy, baseKnown)...) {
t.Error(g)
}
}
-1
View File
@@ -1,4 +1,3 @@
# Paths every program on a machine reads as the machine's own, not as a message from the module that
# writes them (checks/paths, novox/hq ADR 0260). One per line, with why.
/etc/hosts # every resolver on the machine reads it; the hostname module writes it for all of them
/var/lib/mesh-build-node-cache # allow: the Go build module gives its integration level the node's installed Node toolchains read-only, so the Node build module's tests build offline (issue 1891, ADR 0533)
-3
View File
@@ -1,3 +0,0 @@
module checks/planhands
go 1.26.0
-48
View File
@@ -1,48 +0,0 @@
// Package planhands refuses a module's line that takes a plan existing for its module handed to the provisioner (issue
// 774). Whether a plan hands its module is mesh-sdk's one rule, catalogue.Plan.Hands: since the seat mesh-provisioner keys a
// provision its provider's helper answers for a module not handed, a plan naming provisions alone exists for modules
// whose files and own secrets stay their node's (on 2026-10-30 the vault read every such plan as handed and answered
// twenty modules' own secrets to nobody, and the provisioner client refused each as a plan it could not act on). A line
// that tests whether a plan exists either asks Hands on the same line, or says "not handing": it reads only who binds
// whom, or that there is no plan at all.
package planhands
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// existence is a test of whether an assignment's or a plan's `provisioning` is there: nil, or an empty raw value.
var existence = regexp.MustCompile(`Provisioning\s*[!=]=\s*nil|len\([^)]*Provisioning\)\s*[!=]=\s*0`)
// Problems is each such line under root's modules/ that neither asks Hands nor says "not handing", as
// `path:line: text`, sorted. Test files are left out: a test may build a plan by hand.
func Problems(root string) ([]string, error) {
var out []string
err := filepath.WalkDir(filepath.Join(root, "modules"), func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") {
return nil
}
raw, err := os.ReadFile(p)
if err != nil {
return err
}
rel, _ := filepath.Rel(root, p)
for i, line := range strings.Split(string(raw), "\n") {
if existence.MatchString(line) && !strings.Contains(line, ".Hands()") && !strings.Contains(line, "not handing") {
out = append(out, fmt.Sprintf("%s:%d: %s", rel, i+1, strings.TrimSpace(line)))
}
}
return nil
})
sort.Strings(out)
return out, err
}
-45
View File
@@ -1,45 +0,0 @@
package planhands
import (
"os"
"path/filepath"
"strings"
"testing"
)
// A line that takes a plan existing for its module handed is refused and named; one that asks Hands, one marked "not
// handing" and any test file pass; and the real catalogue is clean.
func TestNoModuleTakesAPlanExistingForItsModuleHanded(t *testing.T) {
root := t.TempDir()
for f, body := range map[string]string{
"modules/vault/own.go": "if a.Provisioning != nil {\n",
"modules/client/plan.go": "if len(a.Provisioning) == 0 {\n",
"modules/vault/rule.go": "return a.Provisioning != nil && a.Provisioning.Hands(), nil\n",
"modules/client/binds.go": "if a.Provisioning != nil { // binds, not handing\n",
"modules/vault/own_test.go": "if a.Provisioning == nil {\n",
"modules/client/notgo/plan.md": "if a.Provisioning == nil {\n",
} {
p := filepath.Join(root, f)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Problems(root)
if err != nil {
t.Fatal(err)
}
if len(got) != 2 || !strings.HasPrefix(got[0], "modules/client/plan.go:1:") || !strings.HasPrefix(got[1], "modules/vault/own.go:1:") {
t.Fatalf("refused %v, wanted the client's and the vault's existence tests alone", got)
}
real, err := Problems(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
if len(real) > 0 {
t.Errorf("a module takes a plan existing for its module handed; ask catalogue.Plan.Hands, or mark a line that reads "+
"only who binds whom \"not handing\":\n%s", strings.Join(real, "\n"))
}
}
-3
View File
@@ -1,3 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (issue 1936).
# every module whole (a directory is read whole, so a module's Go source at any depth enters the digest), for a line that takes a plan existing for its module handed.
modules
-7
View File
@@ -1,7 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/predefined
go 1.22
require git.novox.be/novox/mesh-catalog/checks/trunk v0.0.0
replace git.novox.be/novox/mesh-catalog/checks/trunk => ../trunk
-153
View File
@@ -1,153 +0,0 @@
// Command predefined refuses a change to predefined.json that removes a key a module still names (novox/hq ADR
// 0348 clause 7, design 57 package K2, issue 661).
//
// Mesh config and node config hold values only for the keys on the catalogue's list of predefined keys, at the
// root of the repository; a module's config value may fall back to one, `"predefined": "<key>"`. A key removed
// while a manifest still names it would leave that module falling back to nothing: the controller refuses its
// build then, at the next merge that touches it. So the removal is refused here, in the pull request that makes
// it, naming each module that names the key.
//
// **What it cannot know.** Whether a key still holds a value at the mesh or a node is mesh-config's to say (design
// 57 package K3): it holds the values, and the catalogue's check reads no store. Until mesh-config refuses that
// itself, removing a key nobody names is let through here, and said.
//
// The list it compares with is the trunk's at the merge base, given with -base; without one, nothing was removed.
// The controller the mesh runs reads the list itself (type, validation, every manifest against it) in the
// module check; this check reads only its keys.
//
// go run . [-base <the trunk's predefined.json>] <catalogue root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
)
// File is the list's file at the root of the catalogue.
const File = "predefined.json"
// Keys is the keys of a list, read as a JSON object; an absent file has none.
func Keys(path string) (map[string]bool, error) {
raw, err := os.ReadFile(path)
if errors.Is(err, fs.ErrNotExist) {
return map[string]bool{}, nil
}
if err != nil {
return nil, err
}
var keys map[string]json.RawMessage
if err := json.Unmarshal(raw, &keys); err != nil {
return nil, fmt.Errorf("%s is not a JSON object of keys: %w", path, err)
}
out := map[string]bool{}
for k := range keys {
out[k] = true
}
return out, nil
}
// Named is, per predefined key, the modules whose manifests name it: under `config`, or its alias `settings`.
func Named(root string) (map[string][]string, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
sort.Strings(manifests)
named := map[string][]string{}
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Config map[string]json.RawMessage `json:"config"`
Settings map[string]json.RawMessage `json:"settings"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
for _, declared := range []map[string]json.RawMessage{m.Config, m.Settings} {
for _, d := range declared {
var value struct {
Predefined string `json:"predefined"`
}
if json.Unmarshal(d, &value) == nil && value.Predefined != "" {
named[value.Predefined] = append(named[value.Predefined], m.Module)
}
}
}
}
return named, nil
}
// Removed is each key the base list holds and the tree's does not, with the modules still naming it; a removed
// key nobody names is answered with none.
func Removed(base, tree map[string]bool, named map[string][]string) map[string][]string {
out := map[string][]string{}
for k := range base {
if !tree[k] {
out[k] = named[k]
}
}
return out
}
func main() {
basePath := flag.String("base", "", "the trunk's predefined.json at the merge base")
flag.Parse()
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: predefined [-base <the trunk's predefined.json>] <catalogue root>")
os.Exit(2)
}
root := flag.Arg(0)
tree, err := Keys(filepath.Join(root, File))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if *basePath == "" {
fmt.Printf("%s: %d key(s); no trunk list given, so none was removed\n", File, len(tree))
return
}
base, err := Keys(*basePath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
named, err := Named(root)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
removed := Removed(base, tree, named)
keys := make([]string, 0, len(removed))
for k := range removed {
keys = append(keys, k)
}
sort.Strings(keys)
failed := false
for _, k := range keys {
if modules := removed[k]; len(modules) > 0 {
failed = true
fmt.Printf("%s: the key %q is removed and still named by %s: move each off it first (novox/hq ADR 0348 "+
"clause 7)\n", File, k, strings.Join(modules, ", "))
} else {
fmt.Printf("%s: the key %q is removed and no module names it; whether the mesh or a node still holds a "+
"value for it is mesh-config's to refuse, not this check's\n", File, k)
}
}
if failed {
os.Exit(1)
}
fmt.Printf("%s: %d key(s), none removed that a module names\n", File, len(tree))
}
-100
View File
@@ -1,100 +0,0 @@
package main
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// A catalogue whose dunst falls back to font-size, and the trunk's list holding font-size and
// agent-managed-settings.
func aCatalogue(t *testing.T, list string) (root, base string) {
t.Helper()
root = t.TempDir()
write := func(path, text string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
write(filepath.Join(root, "modules", "dunst", "module.json"), `{"module": "dunst", "config": {"font-size":
{"type": "number", "description": "points", "default": 10, "predefined": "font-size"}}}`)
write(filepath.Join(root, "modules", "claude-code", "module.json"), `{"module": "claude-code", "settings": {
"managed_settings": {"kind": "preference", "default": "{}", "why": "none"}}}`)
write(filepath.Join(root, "modules", "plain", "module.json"), `{"module": "plain"}`)
if list != "" {
write(filepath.Join(root, File), list)
}
base = filepath.Join(t.TempDir(), "base.json")
write(base, `{"font-size": {"type": "number"}, "agent-managed-settings": {"type": "object"}}`)
return root, base
}
func run(t *testing.T, args ...string) (string, error) {
t.Helper()
out, err := exec.Command("go", append([]string{"run", "."}, args...)...).CombinedOutput()
return string(out), err
}
func TestARemovalOfAKeyAModuleStillNamesIsRefused(t *testing.T) {
// The planted removal: font-size goes while dunst still falls back to it.
root, base := aCatalogue(t, `{"agent-managed-settings": {"type": "object"}}`)
out, err := run(t, "-base", base, root)
if err == nil || !strings.Contains(out, `the key "font-size" is removed and still named by dunst`) {
t.Fatalf("a removal of a named key passed:\n%s", out)
}
}
func TestARemovalNobodyNamesPassesAndSaysWhatItCannotKnow(t *testing.T) {
root, base := aCatalogue(t, `{"font-size": {"type": "number"}}`)
out, err := run(t, "-base", base, root)
if err != nil || !strings.Contains(out, `"agent-managed-settings" is removed and no module names it`) ||
!strings.Contains(out, "mesh-config's to refuse") {
t.Fatalf("a removal nobody names: %v\n%s", err, out)
}
}
func TestAListUnchangedOrGrownPasses(t *testing.T) {
for _, list := range []string{
`{"font-size": {"type": "number"}, "agent-managed-settings": {"type": "object"}}`,
`{"font-size": {"type": "number"}, "agent-managed-settings": {"type": "object"}, "accent": {"type": "string"}}`,
} {
root, base := aCatalogue(t, list)
if out, err := run(t, "-base", base, root); err != nil || !strings.Contains(out, "none removed") {
t.Fatalf("%s: %v\n%s", list, err, out)
}
}
// No trunk list given: nothing was removed.
root, _ := aCatalogue(t, `{}`)
if out, err := run(t, root); err != nil || !strings.Contains(out, "none was removed") {
t.Fatalf("without a base: %v\n%s", err, out)
}
}
func TestTheFileRemovedWholeIsARemovalOfEveryKey(t *testing.T) {
root, base := aCatalogue(t, "")
out, err := run(t, "-base", base, root)
if err == nil || !strings.Contains(out, `"font-size" is removed and still named by dunst`) {
t.Fatalf("the list removed whole passed:\n%s", out)
}
}
func TestNamedReadsBothForms(t *testing.T) {
root, _ := aCatalogue(t, "")
if err := os.WriteFile(filepath.Join(root, "modules", "plain", "module.json"), []byte(`{"module": "plain",
"settings": {"size": {"predefined": "font-size"}}}`), 0o644); err != nil {
t.Fatal(err)
}
named, err := Named(root)
if err != nil {
t.Fatal(err)
}
if got := strings.Join(named["font-size"], ","); got != "dunst,plain" {
t.Fatalf("font-size named by %s", got)
}
}
-6
View File
@@ -1,6 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# the root's list of predefined keys and every manifest that names one.
predefined.json
modules/*/module.json
-41
View File
@@ -1,41 +0,0 @@
package main
import (
"path/filepath"
"sort"
"testing"
"git.novox.be/novox/mesh-catalog/checks/trunk"
)
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2): predefined.json removes no key a module still
// names, judged against the trunk's copy at the merge base. A trunk with none had no keys; with no trunk to read, nothing can be
// seen removed, and the test says so.
func TestThePredefinedListRemovesNoKeyAModuleStillNames(t *testing.T) {
tree, err := Keys(filepath.Join("..", "..", File))
if err != nil {
t.Fatal(err)
}
path, ok := trunk.Copy(t, File)
if !ok {
t.Log("no trunk copy of " + File + ": no removal can be seen")
return
}
base, err := Keys(path)
if err != nil {
t.Fatal(err)
}
named, err := Named(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
removed := Removed(base, tree, named)
keys := make([]string, 0, len(removed))
for k := range removed {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
t.Errorf("%q is removed from %s and still named by %v", k, File, removed[k])
}
}
-5
View File
@@ -1,5 +0,0 @@
module checks/readable
go 1.26.0
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128
-2
View File
@@ -1,2 +0,0 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
-333
View File
@@ -1,333 +0,0 @@
// Command readable refuses a catalogue where a module reads another module's state that the owner does not announce
// readable (novox/hq ADR 0495 clause 3, issue 1790): the controller grants a read only from the owner's announcement, so
// such a read is refused on the live mesh, said only in the controller's journal. The sweep of 2026-10-30 (issue 774)
// found three: gitea's `bound` (read by mesh-provisioner, so no forge account was bound), disk-load's `readings` (read by
// restic, nfs-server and mounts) and restic's `good` (read by the self-check). The merge gate's check of reads against
// the owner's manifest went with clause 5, and announcetest holds a module to its own list, not to its readers.
//
// An owner announces from its code. A Go module announces when its code imports mesh-sdk's go/announce: every
// announce.State literal there is read, its Name a string, a constant of the package, or a constant of mesh-sdk this
// check knows (Known). A TypeScript module announces when its code says the event `states` or calls the SDK's keeps:
// every `{ name: "...", readable: true }` there is read. An owner that announces nothing from code is read as before
// on the mesh, and is not judged; neither is an owner outside the catalogue (another repository's).
//
// go run . <catalog root>
//
// Exits 1 on a finding.
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"git.novox.be/novox/mesh-sdk/go/health"
)
// Known are the mesh-sdk constants an announcement may name a state by, by the selector the code writes.
var Known = map[string]string{
"health.GoodState": health.GoodState,
"health.ReadingsState": health.ReadingsState,
}
const announcePkg = `"git.novox.be/novox/mesh-sdk/go/announce"`
// Announced is what one owner's code announces: each state, readable or not.
type Announced map[string]bool
// Findings is every refusal in the catalogue at root, sorted.
func Findings(root string) ([]string, error) {
manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
dirs := map[string]string{}
reads := map[string][]string{}
for _, path := range manifests {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var m struct {
Module string `json:"module"`
Reads []json.RawMessage `json:"reads"`
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
dirs[m.Module] = filepath.Dir(path)
for _, r := range m.Reads {
var s string
if json.Unmarshal(r, &s) == nil {
reads[m.Module] = append(reads[m.Module], s)
}
}
}
var out []string
owners := map[string]Announced{}
for reader, rs := range reads {
for _, r := range rs {
owner, state, ok := strings.Cut(r, ".")
dir, inCatalogue := dirs[owner]
if !ok || !inCatalogue || owner == reader {
continue
}
a, seen := owners[owner]
if !seen {
var problems []string
a, problems, err = AnnouncedBy(dir)
if err != nil {
return nil, err
}
owners[owner] = a
for _, p := range problems {
out = append(out, owner+": "+p)
}
}
if a == nil {
continue // announces nothing from code: read as before
}
readable, keeps := a[state]
switch {
case !keeps:
out = append(out, fmt.Sprintf("%s reads %s.%s, which %s's code does not announce: the read is refused on the mesh", reader, owner, state, owner))
case !readable:
out = append(out, fmt.Sprintf("%s reads %s.%s, which %s's code announces not readable: the read is refused on the mesh; announce it readable", reader, owner, state, owner))
}
}
}
sort.Strings(out)
return dedupe(out), nil
}
func dedupe(in []string) []string {
var out []string
for i, s := range in {
if i == 0 || s != in[i-1] {
out = append(out, s)
}
}
return out
}
var (
tsAnnounces = regexp.MustCompile(`emit\(\s*"states"|\bkeeps\(`)
tsState = regexp.MustCompile(`\{\s*name:\s*"([a-z0-9][a-z0-9-]*)"\s*(,\s*readable:\s*(true|false)\s*)?\}`)
)
// AnnouncedBy is what the module in dir announces from its code, nil when it announces nothing; problems are
// announcements whose state's name cannot be read.
func AnnouncedBy(dir string) (Announced, []string, error) {
var out Announced
var problems []string
pkgs := map[string][]*ast.File{}
fset := token.NewFileSet()
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if n := d.Name(); path != dir && (n == "node_modules" || n == "dist" || n == "test" || n == "testdata" || strings.HasPrefix(n, ".")) {
return filepath.SkipDir
}
return nil
}
name := d.Name()
switch {
case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return err
}
pkgs[filepath.Dir(path)] = append(pkgs[filepath.Dir(path)], f)
case strings.HasSuffix(name, ".ts") && !strings.HasSuffix(name, ".d.ts") && !strings.HasSuffix(name, ".test.ts"):
raw, err := os.ReadFile(path)
if err != nil {
return err
}
if !tsAnnounces.Match(raw) && !strings.Contains(string(raw), "KEPT") {
return nil
}
for _, m := range tsState.FindAllStringSubmatch(string(raw), -1) {
if out == nil {
out = Announced{}
}
out[m[1]] = out[m[1]] || m[3] == "true"
}
}
return nil
})
if err != nil {
return nil, nil, err
}
for _, files := range pkgs {
imports := false
consts := map[string]ast.Expr{}
for _, f := range files {
for _, im := range f.Imports {
if im.Path.Value == announcePkg && (im.Name == nil || im.Name.Name == "announce") {
imports = true
}
}
for _, decl := range f.Decls {
g, ok := decl.(*ast.GenDecl)
if !ok || g.Tok != token.CONST {
continue
}
for _, spec := range g.Specs {
v := spec.(*ast.ValueSpec)
for i, n := range v.Names {
if i < len(v.Values) {
consts[n.Name] = v.Values[i]
}
}
}
}
}
if !imports {
continue
}
if out == nil {
out = Announced{}
}
for _, f := range files {
ast.Inspect(f, func(n ast.Node) bool {
lit, ok := n.(*ast.CompositeLit)
if !ok {
return true
}
states := []*ast.CompositeLit{}
switch {
case isAnnounceState(lit.Type):
states = append(states, lit)
case isStateSlice(lit.Type):
for _, e := range lit.Elts {
if c, ok := e.(*ast.CompositeLit); ok && (c.Type == nil || isAnnounceState(c.Type)) {
states = append(states, c)
}
}
default:
return true
}
for _, s := range states {
name, readable, problem := stateOf(s, consts)
if problem != "" {
problems = append(problems, fmt.Sprintf("%s: %s", fset.Position(s.Pos()), problem))
continue
}
out[name] = out[name] || readable
}
return false
})
}
}
return out, problems, nil
}
func isAnnounceState(e ast.Expr) bool {
s, ok := e.(*ast.SelectorExpr)
if !ok {
return false
}
x, ok := s.X.(*ast.Ident)
return ok && x.Name == "announce" && s.Sel.Name == "State"
}
func isStateSlice(e ast.Expr) bool {
a, ok := e.(*ast.ArrayType)
return ok && isAnnounceState(a.Elt)
}
// stateOf is one announce.State literal's name and whether it is readable (Readable true, or Readers named).
func stateOf(lit *ast.CompositeLit, consts map[string]ast.Expr) (string, bool, string) {
var name string
var readable bool
named := false
for _, e := range lit.Elts {
kv, ok := e.(*ast.KeyValueExpr)
if !ok {
return "", false, "an announce.State without field names cannot be read; name its fields"
}
key, _ := kv.Key.(*ast.Ident)
if key == nil {
continue
}
switch key.Name {
case "Name":
v, ok := resolve(kv.Value, consts, 0)
if !ok {
return "", false, fmt.Sprintf("the state's name %s cannot be read: name it by a string, a constant of the package, or a mesh-sdk constant in checks/readable's Known", exprString(kv.Value))
}
name, named = v, true
case "Readable":
if id, ok := kv.Value.(*ast.Ident); ok {
readable = readable || id.Name == "true"
}
case "Readers":
readable = true
}
}
if !named {
return "", false, "an announce.State without a Name"
}
return name, readable, ""
}
func resolve(e ast.Expr, consts map[string]ast.Expr, depth int) (string, bool) {
if depth > 8 {
return "", false
}
switch v := e.(type) {
case *ast.BasicLit:
if v.Kind == token.STRING {
s, err := strconv.Unquote(v.Value)
return s, err == nil
}
case *ast.Ident:
if c, ok := consts[v.Name]; ok {
return resolve(c, consts, depth+1)
}
case *ast.SelectorExpr:
s, ok := Known[exprString(v)]
return s, ok
}
return "", false
}
func exprString(e ast.Expr) string {
switch v := e.(type) {
case *ast.Ident:
return v.Name
case *ast.SelectorExpr:
return exprString(v.X) + "." + v.Sel.Name
case *ast.BasicLit:
return v.Value
}
return fmt.Sprintf("%T", e)
}
func main() {
root := "."
if len(os.Args) > 1 {
root = os.Args[1]
}
found, err := Findings(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
for _, f := range found {
fmt.Println(f)
}
if len(found) > 0 {
os.Exit(1)
}
}
-90
View File
@@ -1,90 +0,0 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// A read of another module's state is refused here when the owner's code announces that state not readable, or not at
// all (the sweep of 2026-10-30, issue 774); an owner that announces nothing from code, or lives in another repository,
// is not judged.
func TestFindings(t *testing.T) {
const goOwner = "package main\n\nimport \"git.novox.be/novox/mesh-sdk/go/announce\"\n\n"
for _, row := range []struct {
name string
files map[string]string
want string // empty: clean
}{
{"go, readable by a constant", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.s"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": goOwner + "const S = \"s\"\n\nvar _ = announce.State{Name: S, Readable: true}\n",
"modules/o/cmd/o/main_test.go": goOwner + "var _ = announce.State{Name: \"s\"}\n"}, ""},
{"go, not readable", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.s"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": goOwner + "var Kept = []announce.State{{Name: \"t\", Readable: true}, {Name: \"s\"}}\n"},
"r reads o.s, which o's code announces not readable"},
{"go, a known mesh-sdk name and readers", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.good","o.s"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": goOwner + "var _ = []announce.State{{Name: health.GoodState, Readable: true}, {Name: \"s\", Readers: []string{\"r\"}}}\n"}, ""},
{"go, a name that cannot be read", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.s"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": goOwner + "var _ = announce.State{Name: other.S, Readable: true}\n"},
"the state's name other.S cannot be read"},
{"go, a state not announced", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.s"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": goOwner + "var _ = announce.State{Name: \"t\"}\n"},
"r reads o.s, which o's code does not announce"},
{"typescript, not readable", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.bound"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/kept.ts": `export const KEPT = [{ name: "announced" }, { name: "bound" }];`},
"r reads o.bound, which o's code announces not readable"},
{"typescript, readable", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.bound"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/index.ts": `await keeps([{ name: "bound", readable: true }]);`}, ""},
{"an owner that announces nothing, and one outside the catalogue", map[string]string{
"modules/r/module.json": `{"module":"r","reads":["o.s","mesh-controller.lease"]}`,
"modules/o/module.json": `{"module":"o"}`,
"modules/o/cmd/o/main.go": "package main\n\nvar _ = 1\n"}, ""},
} {
t.Run(row.name, func(t *testing.T) {
root := t.TempDir()
for path, text := range row.files {
p := filepath.Join(root, path)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
got, err := Findings(root)
if err != nil {
t.Fatal(err)
}
joined := strings.Join(got, "\n")
if row.want == "" && len(got) != 0 || row.want != "" && !strings.Contains(joined, row.want) {
t.Errorf("findings %q, want %q", joined, row.want)
}
})
}
}
// The catalogue itself: every state a module reads of another module that announces from code is announced readable.
func TestTheCatalogueReadsOnlyWhatItsOwnersAnnounceReadable(t *testing.T) {
got, err := Findings(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
for _, f := range got {
t.Error(f)
}
}
-6
View File
@@ -1,6 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (issue 1936).
# every manifest, for what it reads; and every module's Go and TypeScript code, for the states it announces.
modules/*/module.json
modules/*/cmd
modules/*/*.go
modules/*/*.ts
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-catalog/checks/removedpackages
go 1.22
-166
View File
@@ -1,166 +0,0 @@
// Command removedpackages refuses a Go source of the catalogue that imports what mesh-sdk gave up in the release that
// narrowed it (novox/hq ADR 0593 §1): a package that left for its owner, and the build runner's names that left
// mesh-sdk's buildstep for mesh-build-go's package.
//
// A module compiles against the mesh-sdk version it pins, so a source that still names a removed package builds until the
// pin moves, and then breaks in a delivery of its own; and a later change could bring the import back with the old pin.
// This check refuses it at once, from the source alone:
//
// - an import of a package in Removed (or one under it);
// - in a file importing mesh-sdk's buildstep (under any name), a selector naming anything that is not in
// BuildstepContract: what is left of that package is what the delivery parts read, and the runner is
// mesh-build-go's package (git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep).
//
// It reads the non-test and test Go files under modules/ and checks/ (testdata, vendor and node_modules left out).
//
// go run . <catalogue root>
//
// Exits 1 on a finding. The lists are the release's: a package that goes later is added here with the change.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
const sdk = "git.novox.be/novox/mesh-sdk/go"
// Removed are the packages of mesh-sdk that left it, each with where its logic is now.
var Removed = map[string]string{
"buildcpu": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu",
"buildstep/conformance": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance",
"delivery/buildrun": "the builder's package (mesh-delivery-builder), imported by its repository's modules",
"compose": "the node-engine, which alone composes (ADR 0593 §6)",
"compose/frombus": "the node-engine, which alone composes (ADR 0593 §6)",
"compose/fromruntime": "the node-engine, which alone composes (ADR 0593 §6)",
"compose/judge": "the assigner's own package",
"takein": "the node-engine, which alone composes (ADR 0593 §6)",
"validate": "the node-engine, which alone composes (ADR 0593 §6)",
"facts": "nothing: the controller they served is ended (ADR 0590)",
"example": "nothing: no module imports it (ADR 0593 §12)",
"packageguard": "nothing: a test helper of mesh-sdk's own",
}
// BuildstepContract are the names mesh-sdk's buildstep keeps: the failure contract, the fetch request, the stale-step sweep,
// the step id and the module tree shape the delivery parts read.
var BuildstepContract = map[string]bool{
"AskWhat": true, "Boot": true, "By": true, "CapacitySetting": true, "Clip": true, "ConsoleFailure": true, "CutLine": true,
"EndOne": true, "EndStale": true, "EndStaleAtStart": true, "EndStaleRound": true, "Failure": true, "FetchRequest": true,
"FetchRequestProperties": true, "Hidden": true, "Launchers": true, "LauncherSuffix": true, "LineBytes": true,
"ModuleTree": true, "NewID": true, "OutputBytes": true, "Owner": true, "PackagesArg": true, "ParseFetchRequest": true,
"PassingLine": true, "RestartReason": true, "Scan": true, "ScanOf": true, "StaleAttempts": true, "StaleRetry": true,
"StaleServed": true,
}
// Findings is every refusal in the Go sources under root, sorted.
func Findings(root string) ([]string, error) {
var out []string
for _, top := range []string{"modules", "checks"} {
err := filepath.WalkDir(filepath.Join(root, top), func(path string, d fs.DirEntry, err error) error {
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if d.IsDir() {
switch d.Name() {
case "testdata", "vendor", "node_modules", ".mesh-build":
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(path, ".go") {
return nil
}
rel, _ := filepath.Rel(root, path)
found, err := fileFindings(path, filepath.ToSlash(rel))
if err != nil {
return err
}
out = append(out, found...)
return nil
})
if err != nil {
return nil, err
}
}
sort.Strings(out)
return out, nil
}
func fileFindings(path, rel string) ([]string, error) {
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution)
if err != nil {
return nil, fmt.Errorf("%s: %w", rel, err)
}
var out []string
buildstep := ""
for _, imp := range file.Imports {
p, err := strconv.Unquote(imp.Path.Value)
if err != nil {
continue
}
sub, ok := strings.CutPrefix(p, sdk+"/")
if !ok {
continue
}
for pkg, to := range Removed {
if sub == pkg || strings.HasPrefix(sub, pkg+"/") {
out = append(out, fmt.Sprintf("%s: imports %s, which left mesh-sdk; it is %s (novox/hq ADR 0593 §1)", rel, p, to))
break
}
}
if sub == "buildstep" {
buildstep = "buildstep"
if imp.Name != nil {
buildstep = imp.Name.Name
}
}
}
if buildstep == "" || buildstep == "_" || buildstep == "." {
return out, nil
}
seen := map[string]bool{}
ast.Inspect(file, func(n ast.Node) bool {
sel, ok := n.(*ast.SelectorExpr)
if !ok {
return true
}
id, ok := sel.X.(*ast.Ident)
if !ok || id.Name != buildstep || !sel.Sel.IsExported() || BuildstepContract[sel.Sel.Name] || seen[sel.Sel.Name] {
return true
}
seen[sel.Sel.Name] = true
out = append(out, fmt.Sprintf("%s: uses %s.%s, which is the build runner's, no longer mesh-sdk's buildstep; import git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep (novox/hq ADR 0593 §1)", rel, buildstep, sel.Sel.Name))
return true
})
return out, nil
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: removedpackages <catalogue root>")
os.Exit(2)
}
found, err := Findings(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
for _, f := range found {
fmt.Println(f)
}
if len(found) > 0 {
os.Exit(1)
}
fmt.Println("no source imports a package mesh-sdk gave up")
}
-57
View File
@@ -1,57 +0,0 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// ADR 0593 §1: a source that imports a package mesh-sdk gave up, or names the build runner through mesh-sdk's buildstep, is
// refused naming where it went; the contract that stays, the runner's own package and a testdata copy are not.
func TestFindingsRefuseWhatMeshSDKGaveUp(t *testing.T) {
const head = "package x\n\nimport (\n"
for _, row := range []struct {
name, file, src string
want string // empty: clean
}{
{"clean", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/delivery\"\n)\n\nvar _ = delivery.StepsState\n", ""},
{"a removed package", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose\"\n)\n\nvar _ = compose.Resolve\n", "imports git.novox.be/novox/mesh-sdk/go/compose, which left mesh-sdk; it is the node-engine"},
{"under a removed package", "modules/a/x_test.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose/frombus\"\n)\n", "the node-engine"},
{"buildcpu", "checks/c/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildcpu\"\n)\n", "buildstep/buildcpu"},
{"the runner through mesh-sdk's buildstep", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = buildstep.RunAsk\n", "uses buildstep.RunAsk, which is the build runner's"},
{"the runner through an alias", "modules/a/x.go", head + "\tsdkstep \"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = sdkstep.Puller{}\n", "uses sdkstep.Puller"},
{"the contract of buildstep", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/buildstep\"\n)\n\nvar _ = buildstep.NewID\nvar _ buildstep.Failure\n", ""},
{"the runner's own package", "modules/a/x.go", head + "\t\"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep\"\n)\n\nvar _ = buildstep.RunAsk\n", ""},
{"a copy in testdata", "modules/a/testdata/x.go", head + "\t\"git.novox.be/novox/mesh-sdk/go/compose\"\n)\n", ""},
} {
t.Run(row.name, func(t *testing.T) {
root := t.TempDir()
p := filepath.Join(root, row.file)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(row.src), 0o644); err != nil {
t.Fatal(err)
}
got, err := Findings(root)
if err != nil {
t.Fatal(err)
}
if all := strings.Join(got, "\n"); (row.want == "") != (all == "") || !strings.Contains(all, row.want) {
t.Fatalf("findings = %q, want one containing %q", all, row.want)
}
})
}
}
// The script's run of this check over the real tree (novox/hq ADR 0529 clause 2).
func TestTheCatalogueImportsNothingMeshSDKGaveUp(t *testing.T) {
got, err := Findings(filepath.Join("..", ".."))
if err != nil {
t.Fatal(err)
}
for _, f := range got {
t.Error(f)
}
}
-6
View File
@@ -1,6 +0,0 @@
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# every Go source of the modules and of the checks, whose imports are read.
modules
checks

Some files were not shown because too many files have changed in this diff Show More