// 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 . // // 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) } }