Give the mesh one judge of manifests, in the indexer, and a command the checker runs (ADR 0578 §1, §2)

Issue 1585, S5: judge.Shelf judges each manifest alone and the rules between them, giving a problem between
manifests to each module whose removal ends it (naming alone blamed the module mounts for f1-circuits' problem).
cmd/mesh-judge answers a composer request so a Go build module runs it as it runs the composer.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 02:08:21 +01:00
parent b1f262d7d0
commit 4837548349
6 changed files with 402 additions and 1 deletions
@@ -0,0 +1,102 @@
// Command mesh-judge is the indexer's judge as a program the checker runs (novox/hq ADR 0578 §2): built at the trunk, or
// at a group's mesh-delivery head when that head changes the judge, and run by a Go build module's compose step in its
// sandbox exactly as the composer is (ADR 0482 §4; delivery.ComposeJob with this package). It reads a composer request
// (mesh-sdk composer.Request; `-request <file>`, or its standard input) and writes one line, a composer verdict, whose
// cases say of each manifest whether the judge accepts it and, when not, every problem. It reads nothing else: no bus,
// store, network or environment.
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"os"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/composer"
"mesh-delivery-indexer/judge"
)
// Package is this command's import path in the indexer's Go module (the directory delivery.ComposeJob's Dir names).
const Package = "mesh-delivery-indexer/cmd/mesh-judge"
// Between names, in a case's verdict, the problems between manifests that name no module of the case.
const Between = "(between manifests)"
func main() { os.Exit(run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)) }
func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
flags := flag.NewFlagSet("mesh-judge", flag.ContinueOnError)
flags.SetOutput(stderr)
file := flags.String("request", "", "the request's file; without it, the standard input")
if err := flags.Parse(args); err != nil {
return 2
}
var raw []byte
var err error
if *file != "" {
raw, err = os.ReadFile(*file)
} else {
raw, err = io.ReadAll(stdin)
}
if err != nil {
fmt.Fprintln(stderr, "mesh-judge:", err)
return 1
}
r, err := composer.Decode(raw)
if err != nil {
fmt.Fprintln(stderr, "mesh-judge:", err)
return 1
}
v, err := Judge(r)
if err != nil {
fmt.Fprintln(stderr, "mesh-judge:", err)
return 1
}
out, err := json.Marshal(v)
if err != nil {
fmt.Fprintln(stderr, "mesh-judge:", err)
return 1
}
fmt.Fprintln(stdout, string(out))
return 0
}
// Judge judges every case of r: each case's manifests as one catalogue (judge.Shelf), every manifest accepted or
// refused with its problems; problems between manifests that name none of them under Between. The verdict's SDK
// names this judge (judge.Identity). A manifest left as a digest is refused as a request it cannot run.
func Judge(r composer.Request) (composer.Verdict, error) {
v := composer.Verdict{V: composer.Version, SDK: judge.Identity()}
seen := map[string]bool{}
for _, c := range r.Cases {
if seen[c.Name] {
return composer.Verdict{}, fmt.Errorf("the case %q is named twice", c.Name)
}
seen[c.Name] = true
raws := map[string][]byte{}
for name, m := range c.Manifests {
if len(m.Raw) == 0 {
return composer.Verdict{}, fmt.Errorf("%w: %s (digest %s)", composer.ErrUnfilled, name, m.Digest)
}
raws[name] = m.Raw
}
refused, unnamed := judge.Shelf(raws, r.Predefined, 0)
cv := composer.CaseVerdict{Name: c.Name, Manifests: map[string]composer.ManifestRead{}}
for name := range raws {
if problems := refused[name]; len(problems) > 0 {
cv.Manifests[name] = composer.ManifestRead{Refused: strings.Join(problems, "\n")}
continue
}
cv.Manifests[name] = composer.ManifestRead{Accepted: true}
}
if len(unnamed) > 0 {
sort.Strings(unnamed)
cv.Manifests[Between] = composer.ManifestRead{Refused: strings.Join(unnamed, "\n")}
}
v.Cases = append(v.Cases, cv)
}
return v, nil
}
@@ -0,0 +1,40 @@
package main
import (
"bytes"
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/composer"
)
// The command the checker runs (novox/hq ADR 0578 §2): a composer request in, one line of a composer verdict out, each
// manifest accepted or refused with its problems; a request it cannot run exits non-zero.
func TestTheCommandJudgesARequest(t *testing.T) {
r := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{
"a": {Raw: json.RawMessage(`{"module":"a","version":"1"}`)},
"b": {Raw: json.RawMessage(`{"module":"b","version":"1","requires":["amqp"]}`)},
}}}}
raw, _ := json.Marshal(r)
var out, errs bytes.Buffer
if code := run(nil, bytes.NewReader(raw), &out, &errs); code != 0 {
t.Fatalf("exit %d: %s", code, errs.String())
}
var v composer.Verdict
if err := json.Unmarshal(out.Bytes(), &v); err != nil || v.V != composer.Version || len(v.Cases) != 1 || v.SDK == "" {
t.Fatalf("verdict %s: %v", out.String(), err)
}
c := v.Cases[0]
if !c.Manifests["a"].Accepted || c.Manifests["b"].Accepted || !strings.Contains(c.Manifests["b"].Refused, `requires "amqp"`) {
t.Fatalf("case: %+v", c)
}
r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)}
raw, _ = json.Marshal(r)
if code := run(nil, bytes.NewReader(raw), &out, &errs); code == 0 {
t.Fatal("a manifest left as a digest was judged")
}
if code := run(nil, strings.NewReader("{"), &out, &errs); code == 0 {
t.Fatal("a request that is no JSON was judged")
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-delivery-indexer
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005736-7b09de1e5502
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
)
+2
View File
@@ -1,5 +1,7 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005736-7b09de1e5502 h1:XndvkzyAFNde/821lEfYObqMTURzmve/w9Ks+QrBBJ8=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005736-7b09de1e5502/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
@@ -0,0 +1,190 @@
// Package judge is the mesh's one judge of module manifests (novox/hq ADR 0578 §1, §2): strict decoding, the seat and
// namespace rules, the bus and stream limits, and the rules between the manifests of one catalogue. Only the indexer,
// which judges every manifest it takes in and writes the verdicts (delivery.VerdictsBucket), and the checker, which
// judges every manifest a change touches by running this judge built at the trunk or a group's head (cmd/mesh-judge,
// as the composer is run), use it. Every other part decodes a manifest leniently (catalogue.Decode) and takes the
// verdict; it never judges (ADR 0578 §3).
//
// Its rules are still mesh-sdk's catalogue functions it calls; they leave mesh-sdk for this package in the group's last
// mesh-sdk commit, once no reader imports them (ADR 0578 §7, issue 1585 S19). Callers depend only on what this package
// answers, so that move changes nothing for them.
package judge
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"maps"
"os"
"regexp"
"slices"
"sort"
"strings"
"sync"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Manifest judges one manifest alone: decoded strictly, and every rule a manifest is held to by itself. The list of
// predefined keys is the one a config value falling back to a key is judged against (ADR 0348 §7); nil judges each
// fallback as its value declares it, where no list is at hand. Problems is empty when the manifest is accepted, and
// then m is the manifest as the judge read it.
func Manifest(raw []byte, predefined catalogue.PredefinedList) (m catalogue.Manifest, problems []string) {
var err error
if predefined != nil {
m, err = catalogue.ParseManifestWith(raw, predefined)
} else {
m, err = catalogue.ParseManifestForInputs(raw)
}
if err != nil {
return catalogue.Manifest{}, problemsOf(err)
}
return m, nil
}
// listed is how the strict parser words a refusal of several problems, one per line after its header.
const listed = "this manifest cannot be used:\n - "
// problemsOf splits the strict parser's refusal into its problems, one each.
func problemsOf(err error) []string {
text := err.Error()
if rest, ok := strings.CutPrefix(text, listed); ok {
return strings.Split(rest, "\n - ")
}
return []string{text}
}
// Shelf judges every manifest of one catalogue, by the name it is given under (a module's name): each alone (Manifest),
// then the rules between the manifests accepted alone (Between). A problem between manifests is a problem of each module
// whose removal from the catalogue ends it (a rule two modules break together refuses both); one no removal ends is in
// unnamed, for the caller to say. A module not in the answer is accepted.
func Shelf(raws map[string][]byte, predefined catalogue.PredefinedList, longestMachine int) (refused map[string][]string, unnamed []string) {
refused = map[string][]string{}
shelf := catalogue.Shelf{}
for name, raw := range raws {
m, problems := Manifest(raw, predefined)
if len(problems) > 0 {
refused[name] = problems
continue
}
if m.Module != name {
refused[name] = []string{fmt.Sprintf("the manifest is of module %q, not %q", m.Module, name)}
continue
}
shelf[name] = m
}
between := Between(shelf, longestMachine)
without := map[string]map[string]bool{} // by module: the problems between the rest, once asked
ends := func(module, problem string) bool {
if without[module] == nil {
rest := maps.Clone(shelf)
delete(rest, module)
without[module] = map[string]bool{}
for _, p := range Between(rest, longestMachine) {
without[module][p] = true
}
}
return !without[module][problem]
}
for _, p := range between {
// The modules it names are asked first; only when none of them ends it is every module asked.
var of []string
for _, name := range Named(p, shelf) {
if ends(name, p) {
of = append(of, name)
}
}
if len(of) == 0 {
for _, name := range slices.Sorted(maps.Keys(shelf)) {
if ends(name, p) {
of = append(of, name)
}
}
}
if len(of) == 0 {
unnamed = append(unnamed, p)
continue
}
for _, name := range of {
refused[name] = append(refused[name], p)
}
}
for name := range refused {
sort.Strings(refused[name])
}
return refused, unnamed
}
// Between is every problem between the manifests of shelf, sorted: catalogue.CatalogueProblems, IdentityProblems over
// the longest machine name (catalogue.DefaultLongestMachine when not above 0) and DataProblems.
func Between(shelf catalogue.Shelf, longestMachine int) []string {
if longestMachine <= 0 {
longestMachine = catalogue.DefaultLongestMachine
}
between := catalogue.CatalogueProblems(shelf)
between = append(between, catalogue.IdentityProblems(shelf, longestMachine)...)
between = append(between, catalogue.DataProblems(shelf)...)
sort.Strings(between)
return slices.Compact(between)
}
// nameChar is a character a module's name may hold; a name is named in a text only where none borders it.
var nameChar = regexp.MustCompile(`[a-z0-9.-]`)
// Named is every module of shelf a problem names, as a word of its own (not part of a longer name), sorted: the modules
// Shelf asks first whether their removal ends the problem. A module named only as a word ("mounts" in "f1-circuits
// mounts its directory") is not blamed for it, since its removal ends nothing.
func Named(problem string, shelf catalogue.Shelf) []string {
var out []string
for name := range shelf {
for at := 0; ; {
i := strings.Index(problem[at:], name)
if i < 0 {
break
}
i += at
end := i + len(name)
before := i == 0 || !nameChar.MatchString(problem[i-1:i])
after := end == len(problem) || !nameChar.MatchString(problem[end:end+1]) ||
// A sentence's full stop after a name ends it.
(problem[end] == '.' && (end+1 == len(problem) || problem[end+1] == ' '))
if before && after {
out = append(out, name)
break
}
at = i + 1
}
}
sort.Strings(out)
return out
}
var (
identityOnce sync.Once
identity string
)
// Identity names this judge: the sha256 of the running program, "exe:" and 16 hex digits, so a verdict says which judge
// made it and the indexer knows its judge changed when the program it runs does. "unknown" when the program cannot be
// read.
func Identity() string {
identityOnce.Do(func() {
identity = "unknown"
path, err := os.Executable()
if err != nil {
return
}
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return
}
identity = "exe:" + hex.EncodeToString(h.Sum(nil)[:8])
})
return identity
}
@@ -0,0 +1,67 @@
package judge
import (
"reflect"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// The judge (novox/hq ADR 0578 §1): a manifest alone is accepted, or refused with each problem; an unknown field is
// refused (strict decoding); a rule between manifests refuses every module it names, and a word inside a longer name
// is no naming.
func TestTheJudge(t *testing.T) {
for _, row := range []struct {
name string
raws map[string]string
want map[string]string // module: a word of its first problem; absent: accepted
}{
{name: "a valid manifest", raws: map[string]string{"a": `{"module":"a","version":"1"}`}},
{name: "a refused manifest", raws: map[string]string{"a": `{"module":"a","version":"1","provides":[{"name":"amqp"}]}`},
want: map[string]string{"a": `provides "amqp", which is not a provision`}},
{name: "an unknown field", raws: map[string]string{"a": `{"module":"a","version":"1","a-field-from-later":1}`},
want: map[string]string{"a": `"a-field-from-later"`}},
{name: "a manifest under another name", raws: map[string]string{"b": `{"module":"a","version":"1"}`},
want: map[string]string{"b": `of module "a", not "b"`}},
{name: "a seat declared twice",
raws: map[string]string{
"one": `{"module":"one","version":"1","seats":[{"name":"thing","scope":"mesh"}]}`,
"two": `{"module":"two","version":"1","seats":[{"name":"thing","scope":"mesh"}]}`,
"ok": `{"module":"ok","version":"1"}`},
want: map[string]string{"one": "already declares", "two": "already declares"}},
} {
raws := map[string][]byte{}
for k, v := range row.raws {
raws[k] = []byte(v)
}
refused, unnamed := Shelf(raws, nil, 0)
if len(unnamed) > 0 {
t.Errorf("%s: unnamed %v", row.name, unnamed)
}
if len(refused) != len(row.want) {
t.Errorf("%s: refused %v, want %v", row.name, refused, row.want)
continue
}
for m, word := range row.want {
if len(refused[m]) == 0 || !strings.Contains(strings.Join(refused[m], "\n"), word) {
t.Errorf("%s: %s refused with %v, want %q", row.name, m, refused[m], word)
}
}
}
shelf := catalogue.Shelf{"nats": {}, "mesh-nats": {}, "a.b": {}}
for problem, want := range map[string][]string{
"nats removes a stream": {"nats"},
"mesh-nats reads nats.": {"mesh-nats", "nats"},
"the state nats.modules and mesh-natsy": nil,
"a.b and a.bc": {"a.b"},
`"nats" is quoted, (mesh-nats) bracketed`: {"mesh-nats", "nats"},
} {
if got := Named(problem, shelf); !reflect.DeepEqual(got, want) {
t.Errorf("Named(%q) = %v, want %v", problem, got, want)
}
}
if id := Identity(); !strings.HasPrefix(id, "exe:") || id != Identity() {
t.Errorf("identity %q", id)
}
}