The checking program answers only the head seat definitions the reader does not hold as they are (issue 1921, milestone 47/G, review round 1 finding 3)

The whole heads' list is 342 KB for this group, over the 256 KiB a run's answer
may be. The checker hands the program its reader's seat-to-digest list (5.6 KB)
and lays the answer, the new or changed definitions, over its own list.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-07 03:17:56 +01:00
parent df56af0765
commit 112ca63f38
4 changed files with 98 additions and 5 deletions
@@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"io/fs"
"os"
"path/filepath"
"reflect"
@@ -138,7 +139,39 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
a.Verdict.Failures[1].Words != "novox/x#3 has files in conflict: go.mod" || a.Verdict.Machines[0] != "laptop" || a.Verdict.Levels[buildstep.LevelUnit].Run != 3 {
t.Errorf("%s: the verdict %+v, want %+v", name, a.Verdict, want)
}
} // Review round 1 of milestone 47/B0: the module graph's cycle rule is the program's (ADR 0603 §2, ADR 0656 §5), over the
}
// Review finding 3 of milestone 47/G: the heads of a real group carry every seat definition the reader holds, unchanged
// (this repository's own contracts, laid as its head): the answer leaves them all to the checker's list and stays
// within the bound a run's answer is written under (buildstep.RunInputBound), where the whole list would not.
t.Run("heads carrying every definition the reader holds unchanged answer none of them, within the bound", func(t *testing.T) {
real := under{prefix: "novox/mesh-delivery", fs: os.DirFS("../../../..")}
group := rules.Facts{V: rules.FactsVersion, Members: []rules.Member{{Repository: "novox/mesh-delivery", Head: "h"}}}
whole, err := rules.SeatsOf(real, group)
if err != nil || len(whole.Seats) < 10 {
t.Fatalf("the repository's own definitions: %d, %v", len(whole.Seats), err)
}
group.SeatDigests = map[string]string{}
for _, raw := range whole.Seats {
var d struct{ Seat string }
_ = json.Unmarshal(raw, &d)
group.SeatDigests[d.Seat] = rules.SeatDigest(raw)
}
same, err := rules.SeatsOf(real, group)
answer, _ := json.Marshal(same)
if err != nil || len(same.Seats) != 0 || same.Same != len(whole.Seats) || len(answer) > buildstep.RunInputBound {
t.Errorf("unchanged heads answer %d definitions (%d the same), %d bytes; want none, all %d the same, within %d: %v",
len(same.Seats), same.Same, len(answer), len(whole.Seats), buildstep.RunInputBound, err)
}
// One definition the heads change is answered alone.
for seat := range group.SeatDigests {
group.SeatDigests[seat] = "changed"
break
}
if one, err := rules.SeatsOf(real, group); err != nil || len(one.Seats) != 1 {
t.Errorf("one changed definition: answered %d, %v", len(one.Seats), err)
}
})
// Review round 1 of milestone 47/B0: the module graph's cycle rule is the program's (ADR 0603 §2, ADR 0656 §5), over the
// edges the running checker gathers. A cycle the change makes fails; one the trunk has among the same modules is noted.
web, mail := rules.Edge{From: "web", To: "mail", Kind: rules.EdgeAwaits}, rules.Edge{From: "mail", To: "web", Kind: rules.EdgeAwaits}
only := func(e rules.Edge, where string) rules.Edge { e.Only = where; return e }
@@ -165,3 +198,19 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
}
}
}
// under is a directory laid at a repository's path of the heads, as the build node lays a head.
type under struct {
prefix string
fs fs.FS
}
func (u under) Open(name string) (fs.File, error) {
if name == u.prefix {
return u.fs.Open(".")
}
if rest, ok := strings.CutPrefix(name, u.prefix+"/"); ok {
return u.fs.Open(rest)
}
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
@@ -189,7 +189,21 @@ func (st *state) seatList(ctx context.Context) bool {
}
base = defs
}
body, err := json.Marshal(st.factsHead())
// The program answers only the heads' definitions the list does not hold as they are: the list's digests ride in its
// input, never the list, whose definitions are larger than a run's input or answer may be (review finding 3 of G).
head := st.factsHead()
if len(base) > 0 {
head.SeatDigests = map[string]string{}
for _, raw := range base {
var d struct {
Seat string `json:"seat"`
}
if json.Unmarshal(raw, &d) == nil && d.Seat != "" {
head.SeatDigests[d.Seat] = rules.SeatDigest(raw)
}
}
}
body, err := json.Marshal(head)
if err == nil {
var out json.RawMessage
if out, _, err = st.runProgram(ctx, []string{"-seats"}, body); err == nil {
@@ -200,7 +214,7 @@ func (st *state) seatList(ctx context.Context) bool {
if err == nil {
st.c.Seats = overSeats(base, a.Seats)
if len(a.Seats) > 0 {
st.c.note(fmt.Sprintf("the heads' seat list holds %d seat definitions, laid over the %d the seat list holds", len(a.Seats), len(base)))
st.c.note(fmt.Sprintf("the heads carry %d seat definitions new or changed (and %d the seat list holds as they are), laid over the %d the seat list holds", len(a.Seats), a.Same, len(base)))
}
for _, l := range a.Left {
st.c.note("the heads' seat list leaves out " + l.Path + ": " + l.Why)
@@ -32,6 +32,11 @@ type Facts struct {
Heads string `json:"heads"`
Trunk map[string]string `json:"trunk,omitempty"`
Members []Member `json:"members"`
// SeatDigests are, by seat, the digests (SeatDigest) of the definitions the running checker's reader holds: the
// `-seats` answer leaves out every head definition that is the same (milestone 47/G, the lead's ruling on review
// finding 3: a whole list is over a run answer's bound). None from a checker that hands none: then every head
// definition is answered.
SeatDigests map[string]string `json:"seat-digests,omitempty"`
// Series are the changes of the members' modules' series, as structures, put in the verdict whatever it is (DLV-C4).
Series []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem `json:"series,omitempty"`
// Steps are, in the order met, each level a build module answered and each failure the asking met.
+27 -2
View File
@@ -1,7 +1,10 @@
package rules
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -25,20 +28,38 @@ import (
const SeatsVersion = 1
// SeatsAnswer is the checking program's `-seats` answer: every seat definition the heads carry, resolved, as the requests'
// field `seats` carries them, sorted by seat; and each definition left out, with why.
// field `seats` carries them, sorted by seat, that is new or differs from the reader's (Facts.SeatDigests); how many
// were the same and left out of it (Same); and each definition left out, with why.
type SeatsAnswer struct {
V int `json:"v"`
Seats []json.RawMessage `json:"seats"`
Same int `json:"same,omitempty"`
Left []LeftOut `json:"left,omitempty"`
}
// SeatDigest is a definition's digest, the same for the same definition whatever its bytes' order and spacing: the sha256
// of its JSON with every object's keys sorted, numbers as written. Bytes that are not JSON are digested as given.
func SeatDigest(raw json.RawMessage) string {
d := json.NewDecoder(bytes.NewReader(raw))
d.UseNumber()
var v any
canonical := []byte(raw)
if d.Decode(&v) == nil {
if out, err := json.Marshal(v); err == nil {
canonical = out
}
}
sum := sha256.Sum256(canonical)
return hex.EncodeToString(sum[:])
}
// LeftOut is a seat definition the list does not take from the heads: its file and why.
type LeftOut struct {
Path string `json:"path"`
Why string `json:"why"`
}
// SeatsOf is the seat definitions of the facts' members' heads. heads holds each member's head at its repository's path; a contracts module is a manifest there whose package artifact names
// SeatsOf is the seat definitions of the facts' members' heads that are new or differ from the reader's (Facts.SeatDigests). heads holds each member's head at its repository's path; a contracts module is a manifest there whose package artifact names
// `generated-from` (seatdefs.Of). A seat of the mesh's own a module that is not its domain's defines (seatdefs.Refusal),
// and a seat two modules define where neither hands it over, are left out, said; a definition that does not parse is an
// error.
@@ -111,6 +132,10 @@ func SeatsOf(heads fs.FS, f Facts) (SeatsAnswer, error) {
}
out.Seats = make([]json.RawMessage, 0, len(by))
for _, seat := range slices.Sorted(maps.Keys(by)) {
if held, ok := f.SeatDigests[seat]; ok && held == SeatDigest(by[seat].raw) {
out.Same++ // the reader holds it as the heads carry it: the checker's own list keeps it
continue
}
out.Seats = append(out.Seats, by[seat].raw)
}
return out, nil