A checker that hands no seat digests gets the heads' definitions with only each seat's own description (issue 1921, milestone 47/G, review round 2)
mesh/delivery-group group feat/registry-milestone checking: 7 of 7 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check failed (3): ace: the check of novox/mesh-catalog#612 failed: the unit level: the candidate's mes…; and 2 more | 932e7ccf0255 on bab05c66b3cb
mesh/delivery-group group feat/registry-milestone checking: 7 of 7 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check failed (3): ace: the check of novox/mesh-catalog#612 failed: the unit level: the candidate's mes…; and 2 more | 932e7ccf0255 on bab05c66b3cb
G's own check is made by the running B0 checker, which hands no digests, so the program answers every head definition: 342 KB, over the 256 KiB a run's answer may be. Without digests the descriptions of verbs, events and their schemas are left out (171 KB for the group's heads); neither the manifest validator nor the composer reads one, and the validator's verdicts over the group are the same either way. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
)
|
||||
|
||||
// 1921 (B0, D2): the checking program answers, on its last line, the verdict the rules give the facts in its input, and
|
||||
@@ -146,7 +147,8 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
|
||||
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)
|
||||
// An empty list of digests answers every definition whole (a checker that hands its list's digests, holding none).
|
||||
whole, err := rules.SeatsOf(real, rules.Facts{V: rules.FactsVersion, Members: group.Members, SeatDigests: map[string]string{}})
|
||||
if err != nil || len(whole.Seats) < 10 {
|
||||
t.Fatalf("the repository's own definitions: %d, %v", len(whole.Seats), err)
|
||||
}
|
||||
@@ -162,6 +164,34 @@ func TestTheCheckingProgramAnswersTheVerdictOfItsFacts(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
// Review round 2 of G: a checker from before G (B0's, which makes G's own check) hands no digests, and the answer
|
||||
// then keeps each seat's own description only, so a group's whole list stays within the bound; each still parses.
|
||||
b0, err := rules.SeatsOf(real, rules.Facts{V: rules.FactsVersion, Members: group.Members})
|
||||
if err != nil || len(b0.Seats) != len(whole.Seats) {
|
||||
t.Fatalf("B0-shaped facts: %d definitions, %v", len(b0.Seats), err)
|
||||
}
|
||||
for i, raw := range b0.Seats {
|
||||
var own struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
_ = json.Unmarshal(whole.Seats[i], &own)
|
||||
var d struct {
|
||||
Description string `json:"description"`
|
||||
Verbs []json.RawMessage `json:"verbs"`
|
||||
Events []json.RawMessage `json:"events"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &d)
|
||||
inner := 0
|
||||
for _, m := range append(d.Verbs, d.Events...) {
|
||||
inner += strings.Count(string(m), `"description":"`)
|
||||
}
|
||||
if _, perr := seatdef.Parse(raw); inner > 0 || d.Description != own.Description || perr != nil {
|
||||
t.Errorf("B0-shaped facts answer %s with %d inner descriptions, its own %q: %v; want only its own", raw[:min(len(raw), 80)], inner, d.Description, perr)
|
||||
}
|
||||
}
|
||||
if answer, _ := json.Marshal(b0); len(answer) > buildstep.RunInputBound || len(answer) >= len(mustJSON(t, whole))*3/4 {
|
||||
t.Errorf("B0-shaped facts answer %d bytes (whole %d), want within %d and far below the whole", len(answer), len(mustJSON(t, whole)), buildstep.RunInputBound)
|
||||
}
|
||||
// One definition the heads change is answered alone.
|
||||
for seat := range group.SeatDigests {
|
||||
group.SeatDigests[seat] = "changed"
|
||||
@@ -214,3 +244,12 @@ func (u under) Open(name string) (fs.File, error) {
|
||||
}
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
||||
}
|
||||
|
||||
func mustJSON(t *testing.T, v any) []byte {
|
||||
t.Helper()
|
||||
out, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1474,6 +1474,20 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
t.Fatalf("%s: the request %+v: %v", row.name, req, err)
|
||||
}
|
||||
if row.heads != "" {
|
||||
// Review round 2 of G (programs.go's digest side): the checking program's -seats input carries the digest of
|
||||
// every definition the reader holds, never the list, and its answer is laid over the list (below).
|
||||
var digests map[string]string
|
||||
b.inputs.mu.Lock()
|
||||
for _, body := range b.inputs.vals {
|
||||
var f rules.Facts
|
||||
if json.Unmarshal(body, &f) == nil && f.SeatDigests != nil {
|
||||
digests = f.SeatDigests
|
||||
}
|
||||
}
|
||||
b.inputs.mu.Unlock()
|
||||
if want := map[string]string{"a": rules.SeatDigest(json.RawMessage(`{"seat":"a"}`)), "b": rules.SeatDigest(json.RawMessage(`{"seat": "b"}`))}; !reflect.DeepEqual(digests, want) {
|
||||
t.Errorf("%s: the -seats input carries the digests %v, want the reader's list's %v", row.name, digests, want)
|
||||
}
|
||||
var got []string
|
||||
for _, raw := range req.Seats {
|
||||
var d struct {
|
||||
@@ -1522,21 +1536,6 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
// fakeSeatList is the seat list reader as a test gives it: two definitions once read, else the reader's words.
|
||||
type fakeSeatList struct{ read bool }
|
||||
|
||||
// headTrees are the forge's listings of a directory at a commit, by "<repository> <directory> <commit>".
|
||||
type headTrees map[string]map[string]string
|
||||
|
||||
func (h headTrees) Files(_ context.Context, repository, commit string, dirs []string) (map[string]map[string]string, error) {
|
||||
out := map[string]map[string]string{}
|
||||
for _, d := range dirs {
|
||||
files, ok := h[repository+" "+d+" "+commit]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
out[d] = files
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f fakeSeatList) Definitions() ([]json.RawMessage, error) {
|
||||
if !f.read {
|
||||
return nil, errors.New("the seat list is held from the bootstrap (2 seats) and nothing new is acted on")
|
||||
|
||||
@@ -136,7 +136,14 @@ func SeatsOf(heads fs.FS, f Facts) (SeatsAnswer, error) {
|
||||
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)
|
||||
raw := by[seat].raw
|
||||
if f.SeatDigests == nil {
|
||||
// A checker from before G hands no digests, so every definition is answered: whole they are over a run answer's
|
||||
// bound, so every description but the seat's own is left out, of its verbs, events and their schemas, which
|
||||
// decides no verdict (neither the manifest validator nor the composer reads one); review round 2 of G.
|
||||
raw = withoutInnerDescriptions(raw)
|
||||
}
|
||||
out.Seats = append(out.Seats, raw)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -205,3 +212,48 @@ func localForge(head fs.FS) seatdefs.Forge {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// schemaData are the keywords of a schema whose value is data, never a schema: what they hold is kept as written.
|
||||
var schemaData = map[string]bool{"examples": true, "enum": true, "const": true, "default": true}
|
||||
|
||||
// withoutInnerDescriptions is a definition with only the seat's own description kept: every other `description` string,
|
||||
// of a verb, an event or anything inside their schemas, is left out, every other field kept (a property named
|
||||
// description is a schema, kept; a schema's data keywords are kept whole). Bytes that are not a JSON object are given
|
||||
// back as they are.
|
||||
func withoutInnerDescriptions(raw json.RawMessage) json.RawMessage {
|
||||
d := json.NewDecoder(bytes.NewReader(raw))
|
||||
d.UseNumber()
|
||||
var def map[string]any
|
||||
if d.Decode(&def) != nil {
|
||||
return raw
|
||||
}
|
||||
var walk func(v any)
|
||||
walk = func(v any) {
|
||||
switch t := v.(type) {
|
||||
case map[string]any:
|
||||
for k, val := range t {
|
||||
if _, prose := val.(string); prose && k == "description" {
|
||||
delete(t, k)
|
||||
continue
|
||||
}
|
||||
if !schemaData[k] {
|
||||
walk(val)
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, e := range t {
|
||||
walk(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, val := range def {
|
||||
if k != "description" {
|
||||
walk(val)
|
||||
}
|
||||
}
|
||||
out, err := json.Marshal(def)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user