diff --git a/modules/mesh-delivery-indexer/judge/judge.go b/modules/mesh-delivery-indexer/judge/judge.go index 2d22f330..bd352368 100644 --- a/modules/mesh-delivery-indexer/judge/judge.go +++ b/modules/mesh-delivery-indexer/judge/judge.go @@ -95,6 +95,12 @@ func ShelfCounting(raws map[string][]byte, predefined catalogue.PredefinedList, // (novox/hq ADR 0578 §1, amended). The indexer judges so; the checker, which runs the judge built at the head of the change, // judges with Shelf. func ShelfWaiting(raws map[string][]byte, predefined catalogue.PredefinedList, longestMachine int, counts OldSectionCounts, known KnownPackages) (refused, waits map[string][]string, unnamed []string) { + return shelfWaiting(raws, predefined, longestMachine, counts, known, nil) +} + +// shelfWaiting is ShelfWaiting where the seats the trunk case declares (A-337) are declared too, each where no module of the +// shelf is its declarer's: a module the shelf holds declares by its own manifest. +func shelfWaiting(raws map[string][]byte, predefined catalogue.PredefinedList, longestMachine int, counts OldSectionCounts, known KnownPackages, trunk trunkSeats) (refused, waits map[string][]string, unnamed []string) { refused, waits = map[string][]string{}, map[string][]string{} shelf := catalogue.Shelf{} for name, raw := range raws { @@ -117,20 +123,26 @@ func ShelfWaiting(raws map[string][]byte, predefined catalogue.PredefinedList, l } shelf[name] = m } - between := Between(shelf, longestMachine, known) + also := trunkSeats{} + for seat, d := range trunk { + if _, held := shelf[d.module]; !held { + also[seat] = d + } + } + problems := between(shelf, longestMachine, known, also) 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, known) { + for _, p := range between(rest, longestMachine, known, also) { without[module][p] = true } } return !without[module][problem] } - for _, p := range between { + for _, p := range problems { // 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) { @@ -163,10 +175,15 @@ func ShelfWaiting(raws map[string][]byte, predefined catalogue.PredefinedList, l // the longest machine name (catalogue.DefaultLongestMachine when not above 0), DataProblems, and // PackagesCatalogueProblems against the known packages (nil judges no name). func Between(shelf catalogue.Shelf, longestMachine int, known KnownPackages) []string { + return between(shelf, longestMachine, known, nil) +} + +// between is Between with the seats the trunk case declares outside the shelf declared too (A-337). +func between(shelf catalogue.Shelf, longestMachine int, known KnownPackages, trunk trunkSeats) []string { if longestMachine <= 0 { longestMachine = catalogue.DefaultLongestMachine } - between := CatalogueProblems(shelf) + between := catalogueProblems(shelf, trunk) between = append(between, PackagesCatalogueProblems(shelf, known)...) between = append(between, IdentityProblems(shelf, longestMachine)...) between = append(between, DataProblems(shelf)...) @@ -228,7 +245,7 @@ func Request(r composer.Request) (composer.Verdict, error) { } v := composer.Verdict{V: composer.Version, SDK: Identity()} seen := map[string]bool{} - counts := trunkOldSections(r) + trunkCase := readTrunk(r) known := knownOf(r.Packages) for _, c := range r.Cases { if seen[c.Name] { @@ -242,7 +259,15 @@ func Request(r composer.Request) (composer.Verdict, error) { } raws[name] = m.Raw } - refused, unnamed := ShelfCounting(raws, r.Predefined, 0, counts, known) + refused, waits, unnamed := shelfWaiting(raws, r.Predefined, 0, trunkCase.counts, known, trunkCase.declared) + for name := range waits { + _, refused[name] = Manifest(raws[name], r.Predefined) + } + if c.Name == TrunkCase { + for name, why := range trunkCase.undecoded { + refused[name] = append(refused[name], undecodedWords(name, why)) + } + } cv := composer.CaseVerdict{Name: c.Name, Manifests: map[string]composer.ManifestRead{}} for name := range raws { if problems := refused[name]; len(problems) > 0 { @@ -270,34 +295,63 @@ func UseSeatList(defs []seatdef.Definition) { // TrunkCase is the case of a request holding the catalogue as the trunk holds it (the checker's JudgeTrunk). const TrunkCase = "trunk" -// trunkOldSections counts the old sections the modules accepted at the trunk use, as the indexer counts them from its -// verdicts (issue 1966 D4, ADR 0595 §11): a change is refused at the merge check exactly as at take-in (judged once, ADR -// 0578). Nil where the request holds no trunk case, which refuses no use of a section. -func trunkOldSections(r composer.Request) OldSectionCounts { +// trunkRead is what the trunk case as it stands gives every case of a request (design 47 G A-337, §712, ADR 0579 §2): every +// trunk-case manifest that decodes counts its old sections and declares its seats, accepted by this judge's rules or not, so +// a change is refused only for what it causes and not for a trunk the new rules refuse; each that does not decode is named, +// with why, for the trunk case's verdict. +type trunkRead struct { + counts OldSectionCounts + declared trunkSeats + undecoded map[string]string +} + +// trunkSeats are the seats the trunk case's manifests declare, by seat: the first declaring module in name order and its +// declaration. +type trunkSeats map[string]trunkSeat + +type trunkSeat struct { + module string + declaration catalogue.SeatDeclaration +} + +// readTrunk reads the request's trunk case (TrunkCase): the old sections its manifests use, as the indexer counts them from +// its verdicts (issue 1966 D4, ADR 0595 §11), so a change is refused at the merge check exactly as at take-in (judged once, +// ADR 0578), and the seats they declare. No count where the request holds no trunk case, which refuses no use of a section. +func readTrunk(r composer.Request) trunkRead { for _, c := range r.Cases { if c.Name != TrunkCase { continue } - raws := map[string][]byte{} - for name, m := range c.Manifests { - if len(m.Raw) > 0 { - raws[name] = m.Raw - } - } - refused, _ := Shelf(raws, r.Predefined, 0) - var accepted []catalogue.Manifest - for name, raw := range raws { - if len(refused[name]) > 0 { + read := trunkRead{declared: trunkSeats{}, undecoded: map[string]string{}} + var decoded []catalogue.Manifest + for _, name := range slices.Sorted(maps.Keys(c.Manifests)) { + raw := c.Manifests[name].Raw + if len(raw) == 0 { continue } - if m, err := catalogue.Decode(raw); err == nil { - m.Module = name - accepted = append(accepted, m) + m, err := catalogue.Decode(raw) + if err != nil { + read.undecoded[name] = err.Error() + continue + } + m.Module = name + decoded = append(decoded, m) + for _, s := range m.DefinesSeats { + if _, taken := read.declared[s.Name]; s.Name != "" && !taken { + read.declared[s.Name] = trunkSeat{module: name, declaration: s} + } } } - return CountOldSections(accepted) + read.counts = CountOldSections(decoded) + return read } - return nil + return trunkRead{} +} + +// undecodedWords names a trunk-case manifest that does not decode, which counts no old section and declares no seat. +func undecodedWords(module, why string) string { + return fmt.Sprintf("%s does not decode, so it counts no old section and declares no seat for the change (design 47 G "+ + "A-337): %s", module, why) } var ( diff --git a/modules/mesh-delivery-indexer/judge/judge_test.go b/modules/mesh-delivery-indexer/judge/judge_test.go index b87cf831..58f7c158 100644 --- a/modules/mesh-delivery-indexer/judge/judge_test.go +++ b/modules/mesh-delivery-indexer/judge/judge_test.go @@ -132,6 +132,29 @@ func TestTheJudge(t *testing.T) { !got["newer"].Accepted || !got["user"].Accepted || !got["mesh-grants"].Accepted { t.Errorf("the change's old sections: %+v", got) } + // A-337 (§712, ADR 0579 §2): the trunk case as it stands counts and declares, every manifest of it that decodes, accepted + // by the new rules or not: a change of migrated manifests against an unmigrated trunk (version "1") refuses nothing for + // a count or a declarer, and a trunk manifest that does not decode is named in the trunk case's verdict. + unmigrated := composer.Request{V: composer.Version, Cases: []composer.Case{ + {Name: "change", Manifests: map[string]composer.Manifest{ + "user": {Raw: json.RawMessage(`{"module":"user","version":"1.0","state":["x"],"seats":[{"name":"channel","scope":"mesh"}]}`)}, + "caller": {Raw: json.RawMessage(`{"module":"caller","version":"1.0","uses":["channel","kept"]}`)}, + "declarer": {Raw: json.RawMessage(`{"module":"declarer","version":"1","seats":[{"name":"kept","scope":"mesh"}]}`)}}}, + {Name: "trunk", Manifests: map[string]composer.Manifest{ + "user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"],"seats":[{"name":"channel","scope":"mesh"}]}`)}, + "caller": {Raw: json.RawMessage(`{"module":"caller","version":"1"}`)}, + "declarer": {Raw: json.RawMessage(`{"module":"declarer","version":"1","seats":[{"name":"kept","scope":"mesh"}]}`)}, + "broken": {Raw: json.RawMessage(`{"module":"broken","version":[`)}}}}} + uv, err := Request(unmigrated) + if err != nil { + t.Fatal(err) + } + if got := uv.Cases[0].Manifests; !got["user"].Accepted || !got["caller"].Accepted || got["declarer"].Accepted { + t.Errorf("a change of migrated manifests against an unmigrated trunk: %+v", got) + } + if got := uv.Cases[1].Manifests["broken"]; got.Accepted || !strings.Contains(got.Refused, "broken does not decode") { + t.Errorf("an undecodable trunk manifest is not named: %+v", got) + } r.Cases[0].Manifests["c"] = composer.Manifest{Digest: strings.Repeat("a", 64)} if _, err := Request(r); err == nil { t.Fatal("a manifest left as a digest was judged") diff --git a/modules/mesh-delivery-indexer/judge/rules.go b/modules/mesh-delivery-indexer/judge/rules.go index eeb762f9..8ec70475 100644 --- a/modules/mesh-delivery-indexer/judge/rules.go +++ b/modules/mesh-delivery-indexer/judge/rules.go @@ -4806,6 +4806,12 @@ const meshSeatPrefix = "mesh-" const retiredLoginShell = "login-shell" func CatalogueProblems(shelf catalogue.Shelf) []string { + return catalogueProblems(shelf, nil) +} + +// catalogueProblems is CatalogueProblems with the seats the trunk case declares (A-337) declared too, each a seat no module +// of the shelf declares: a module the trunk holds and the new rules refuse still declares. +func catalogueProblems(shelf catalogue.Shelf, trunk trunkSeats) []string { var problems []string // A stream name has one owner (novox/hq ADR 0363). problems = append(problems, StreamNameProblems(shelf)...) @@ -4847,6 +4853,12 @@ func CatalogueProblems(shelf catalogue.Shelf) []string { declared[s.Name] = s } } + for seat, d := range trunk { + if _, taken := declaredBy[seat]; !taken { + declaredBy[seat] = d.module + declared[seat] = d.declaration + } + } exists := func(seat string) bool { if _, isMesh := catalogue.SeatNamed(seat); isMesh {