The checking program answers the seat list of the heads laid for it (issue 1921)

Asked -seats, it reads every seat definition the heads carry, resolves the parts it includes and answers the list
with what it left out and why, so the running checker hands the manifest validator and the composer a list it does not
read itself (ADR 0636 §4, ADR 0644 §1).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-06 22:35:40 +01:00
parent 1b9a2bc302
commit 35da943fb5
3 changed files with 212 additions and 11 deletions
@@ -28,6 +28,7 @@ func run(args []string, getenv func(string) string, stdout, stderr io.Writer) in
fs := flag.NewFlagSet("mesh-checking-program", flag.ContinueOnError)
fs.SetOutput(stderr)
verdict := fs.Bool("verdict", false, "answer the verdict of the facts in the input")
seats := fs.Bool("seats", false, "answer the seat list of the heads laid for it")
if err := fs.Parse(args); err != nil {
return 2
}
@@ -35,8 +36,24 @@ func run(args []string, getenv func(string) string, stdout, stderr io.Writer) in
fmt.Fprintf(stderr, "mesh-checking-program: "+format+"\n", a...)
return 2
}
if *seats {
dir := getenv(delivery.RunHeadsEnv)
if dir == "" {
return refuse("no heads: %s names no directory", delivery.RunHeadsEnv)
}
answer, err := rules.SeatsOf(os.DirFS(dir))
if err != nil {
return refuse("%v", err)
}
line, err := json.Marshal(answer)
if err != nil {
return refuse("the seat list cannot be written: %v", err)
}
fmt.Fprintf(stdout, "%s\n", line)
return 0
}
if !*verdict {
return refuse("nothing asked: give -verdict")
return refuse("nothing asked: give -verdict or -seats")
}
file := getenv(delivery.RunInputEnv)
if file == "" {
@@ -15,6 +15,8 @@ import (
// 1921 (B0, D2): the checking program answers, on its last line, the verdict the rules give the facts in its input, and
// refuses facts of a version it does not read, an input it is not given, and a question it is not asked, naming each.
// 1921 (B0, D3): asked -seats, it answers the seat list of the heads laid for it: a definition a head adds, resolved with
// the parts it includes; what it left out, and why; and an empty list for heads with no definition.
func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
failing, _ := json.Marshal(rules.Tested{Level: delivery.LevelUnit, Tests: 3, Failed: []rules.Failure{{Package: "x/a", Test: "TestA", Cause: "a_test.go:9: boom"}}})
facts := rules.Facts{V: rules.FactsVersion, Group: "feat/x", Heads: "0123456789ab", Members: []rules.Member{{Repository: "novox/x", Number: 3, Head: "h"}},
@@ -35,22 +37,45 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
later := facts
later.V = rules.FactsVersion + 1
v2 := write("v2.json", later)
heads := filepath.Join(dir, "heads")
for p, body := range map[string]string{
"novox/mesh-contracts/contracts/seats/node-build-x/seat.json": `{"format":1,"seat":"node-build-x","scope":"node","description":"kept","includes":["build"],"verbs":[{"name":"own"}]}`,
"novox/mesh-contracts/contracts/parts/build.json": `{"part":"build","verbs":[{"name":"current"}],"events":[{"name":"ended"}]}`,
"novox/mesh-contracts/contracts/seats/broken/seat.json": `{"format":1,"seat":"broken","scope":"nowhere"}`,
} {
if err := os.MkdirAll(filepath.Dir(filepath.Join(heads, p)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(heads, p), []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
none := filepath.Join(dir, "no-heads")
if err := os.MkdirAll(filepath.Join(none, "novox/x"), 0o755); err != nil {
t.Fatal(err)
}
for name, c := range map[string]struct {
args []string
input string
exit int
said string
args []string
input, heads string
exit int
said string
}{
"the facts": {[]string{"-verdict"}, v1, 0, ""},
"a later version": {[]string{"-verdict"}, v2, 2, "facts of version 2, this checking program reads version 1"},
"no input": {[]string{"-verdict"}, "", 2, delivery.RunInputEnv + " names no file"},
"nothing asked": {nil, v1, 2, "nothing asked"},
"an unknown asking": {[]string{"-judge"}, v1, 2, "-judge"},
"the seats": {args: []string{"-seats"}, heads: heads},
"no seats": {args: []string{"-seats"}, heads: none},
"no heads laid": {args: []string{"-seats"}, exit: 2, said: delivery.RunHeadsEnv + " names no directory"},
"the facts": {args: []string{"-verdict"}, input: v1},
"a later version": {args: []string{"-verdict"}, input: v2, exit: 2, said: "facts of version 2, this checking program reads version 1"},
"no input": {args: []string{"-verdict"}, exit: 2, said: delivery.RunInputEnv + " names no file"},
"nothing asked": {input: v1, exit: 2, said: "nothing asked"},
"an unknown asking": {args: []string{"-judge"}, input: v1, exit: 2, said: "-judge"},
} {
var out, errs bytes.Buffer
exit := run(c.args, func(k string) string {
if k == delivery.RunInputEnv {
switch k {
case delivery.RunInputEnv:
return c.input
case delivery.RunHeadsEnv:
return c.heads
}
return ""
}, &out, &errs)
@@ -62,6 +87,23 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
continue
}
lines := strings.Split(strings.TrimSpace(out.String()), "\n")
if c.heads != "" {
var sa rules.SeatsAnswer
if err := json.Unmarshal([]byte(lines[len(lines)-1]), &sa); err != nil || sa.V != rules.SeatsVersion || sa.Seats == nil {
t.Fatalf("%s: the last line %q: %v", name, lines[len(lines)-1], err)
}
if c.heads == none {
if len(sa.Seats) != 0 || len(sa.Left) != 0 {
t.Errorf("%s: %+v, want an empty list", name, sa)
}
continue
}
want := `{"description":"kept","events":[{"name":"ended"}],"format":1,"scope":"node","seat":"node-build-x","verbs":[{"name":"current"},{"name":"own"}]}`
if len(sa.Seats) != 1 || string(sa.Seats[0]) != want || len(sa.Left) != 1 || !strings.Contains(sa.Left[0].Why, `not "broken" and "nowhere"`) {
t.Errorf("%s: seats %s, left %+v; want %s and broken left out", name, sa.Seats, sa.Left, want)
}
continue
}
var a rules.Answer
if err := json.Unmarshal([]byte(lines[len(lines)-1]), &a); err != nil || a.V != rules.AnswerVersion {
t.Fatalf("%s: the last line %q: %v", name, lines[len(lines)-1], err)