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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user