DLV-C4: the checker names each change of a package series a pull request makes in its status line
Design 32 (A package, its series): the review of a change sees every series it raises. For each module a pull request touches, every artifact its trunk manifest and its head's manifest both declare whose series differs is named as "<module>/<artifact> series <trunk> → <head>" in the pull request's mesh/check line, never dropped when the line is cut; a manifest that cannot be read is said in the same place. Rows in TestTheIndexersJudgeJudgesTheChangesManifests (raised: named; kept: not named); the raised row failed first. gofmt of the integration test's imports (M2). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -27,8 +27,8 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/firstview/firstviewtest"
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ type Case struct {
|
||||
notesMu sync.Mutex
|
||||
// warnings are, by pull request, what part of its check did not run (Case.warn).
|
||||
warnings map[string]string
|
||||
// seriesOf are, by pull request, the changes of a package series it makes (series.go), named in its status line.
|
||||
seriesOf map[string][]string
|
||||
notes []string
|
||||
// asks are the ids of the asks the check put on the build seats' streams, in order: a try names them (Try.Asks).
|
||||
asks []string
|
||||
|
||||
@@ -1035,8 +1035,12 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
// mail's tool while mail awaits web's database: both left out, so neither is refused (ADR 0614 §3, §6).
|
||||
cycle string
|
||||
trunkCycle bool
|
||||
// series is the series of web's package the change raises it to, and the words its status line names (DLV-C4).
|
||||
series, seriesWords string
|
||||
}{
|
||||
{name: "accepted"},
|
||||
{name: "a package series raised", series: "0.2", seriesWords: "web/pkg series 0.1 → 0.2"},
|
||||
{name: "a package series kept", series: "0.1"},
|
||||
{name: "an untouched manifest's problem the trunk had", untouched: "mail says no credential form", trunkMail: "mail says no credential form"},
|
||||
{name: "a problem the change causes in an untouched manifest", untouched: "mail reads a state web no longer keeps",
|
||||
fails: "refuses the manifest of mail: mail reads a state web no longer keeps"},
|
||||
@@ -1077,6 +1081,13 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
}
|
||||
b.ex.Read = fixedManifests{strings.Repeat("a", 64): mail, strings.Repeat("b", 64): trunkWeb, web: changed}
|
||||
}
|
||||
if row.series != "" {
|
||||
pkg := func(series string) *catalogue.Build {
|
||||
return &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "pkg", Kind: catalogue.ArtifactPackage, Language: "go", Series: series}}}
|
||||
}
|
||||
b.ex.Read = fixedManifests{strings.Repeat("a", 64): {Module: "mail"}, strings.Repeat("b", 64): {Module: "web", Build: pkg("")},
|
||||
web: {Module: "web", Build: pkg(row.series)}}
|
||||
}
|
||||
var judged []delivery.ComposeJob
|
||||
answer := b.asks.Answer
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
@@ -1094,7 +1105,11 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
p := pullOf(repoA, 7, head1)
|
||||
p.Modules = []string{"web"}
|
||||
p.Fps = map[string]delivery.CandidateModule{"web": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: web}}
|
||||
v := b.judge(t, caseOf(t, p)).Verdict
|
||||
cs := caseOf(t, p)
|
||||
v := b.judge(t, cs).Verdict
|
||||
if line := Line(v, cs, cs.Pulls[0], nil, nil, nil); row.seriesWords != "" && !strings.Contains(line, row.seriesWords) || row.seriesWords == "" && strings.Contains(line, "series") {
|
||||
t.Errorf("%s: the line %q, want it to name %q", row.name, line, row.seriesWords)
|
||||
}
|
||||
if len(judged) != 1 || judged[0].Fetch["repository"] != DeliveryRepository || judged[0].Fetch["commit"] != trunk ||
|
||||
judged[0].Package != JudgePackage || judged[0].Dir != JudgeDir {
|
||||
t.Fatalf("%s: judged %+v (verdict %+v)", row.name, judged, v)
|
||||
|
||||
@@ -269,6 +269,7 @@ func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) {
|
||||
// Every member and level runs to its end, and every manifest the change touches is judged by the indexer's judge (ADR
|
||||
// 0578 §2), also after a failure: the verdict names every failure, not only the first (the operator, 2026-11-02).
|
||||
tests := st.judgeAll(ctx)
|
||||
st.seriesChanges(ctx) // DLV-C4: named in the verdict's line, whatever the verdict
|
||||
if v := settled(joinFailures(tests, st.judgeManifests(ctx), st.refuseCycle(ctx))); v != nil {
|
||||
return st.finish(ctx, *v), nil
|
||||
}
|
||||
|
||||
@@ -117,18 +117,20 @@ type pieces struct {
|
||||
carried string
|
||||
// warning says that a part of the check did not run, for instance that an older build module could not refuse a compiled program; never dropped.
|
||||
warning string
|
||||
// series names each change of a package series the pull request makes (design 32, DLV-C4); never dropped.
|
||||
series string
|
||||
}
|
||||
|
||||
// pullKey names a pull request of a case: `<owner/repo>#<number>`.
|
||||
func pullKey(p Pull) string { return fmt.Sprintf("%s#%d", p.Repository, p.Number) }
|
||||
|
||||
func (p pieces) join(drop int) string {
|
||||
parts := []string{p.head, p.commits, p.carried, p.warning, p.counts, p.judged}
|
||||
parts := []string{p.head, p.commits, p.carried, p.warning, p.counts, p.judged, p.series}
|
||||
switch {
|
||||
case drop < 3 && p.times != "":
|
||||
parts = append(parts, p.times)
|
||||
case drop == 3 && p.shortTimes != "":
|
||||
parts = []string{p.head, p.commits, p.carried, p.warning, p.shortCounts, p.judged, p.shortTimes}
|
||||
parts = []string{p.head, p.commits, p.carried, p.warning, p.shortCounts, p.judged, p.series, p.shortTimes}
|
||||
}
|
||||
if p.over != "" {
|
||||
parts = append(parts, p.over)
|
||||
@@ -244,6 +246,7 @@ func carriedWords(from map[string]string, v delivery.Verdict, cs *Case, p Pull)
|
||||
func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) pieces {
|
||||
var pc pieces
|
||||
pc.judged = judged[pullKey(p)]
|
||||
pc.series = seriesWords(cs, p)
|
||||
if cs != nil {
|
||||
pc.warning = cs.Warning(pullKey(p))
|
||||
}
|
||||
@@ -356,7 +359,7 @@ func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []st
|
||||
pc := parts(v, cs, p, builds, slowest, judged)
|
||||
if v.State == delivery.VerdictFail {
|
||||
// The head gives way first to the commits and what is never dropped: its count and "and N more" stay whole.
|
||||
kept := joinSet(pc.commits, pc.carried, pc.warning, pc.judged)
|
||||
kept := joinSet(pc.commits, pc.carried, pc.warning, pc.judged, pc.series)
|
||||
pc.head = failedHead(v, MaxLine-utf8.RuneCountInString(" | "+kept))
|
||||
}
|
||||
for drop := 0; drop <= 4; drop++ {
|
||||
@@ -364,13 +367,13 @@ func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []st
|
||||
return s
|
||||
}
|
||||
}
|
||||
if pc.judged != "" {
|
||||
if pc.judged != "" || pc.series != "" {
|
||||
for _, counts := range []string{pc.shortCounts, ""} {
|
||||
if s := joinSet(pc.head, pc.commits, counts, pc.judged); utf8.RuneCountInString(s) <= MaxLine {
|
||||
if s := joinSet(pc.head, pc.commits, counts, pc.judged, pc.series); utf8.RuneCountInString(s) <= MaxLine {
|
||||
return s
|
||||
}
|
||||
}
|
||||
rest := " | " + joinSet(pc.commits, pc.judged)
|
||||
rest := " | " + joinSet(pc.commits, pc.judged, pc.series)
|
||||
room := MaxLine - utf8.RuneCountInString(rest) - 1
|
||||
if head := []rune(pc.head); room > 0 && len(head) > room {
|
||||
return string(head[:room]) + "…" + rest
|
||||
@@ -378,7 +381,7 @@ func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []st
|
||||
}
|
||||
if v.State == delivery.VerdictFail {
|
||||
// A failure's head, sized above, and the commits fit: the counts and the composition are in the report.
|
||||
if s := joinSet(pc.head, pc.commits, pc.carried, pc.warning, pc.judged); utf8.RuneCountInString(s) <= MaxLine {
|
||||
if s := joinSet(pc.head, pc.commits, pc.carried, pc.warning, pc.judged, pc.series); utf8.RuneCountInString(s) <= MaxLine {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// A change of a package series is named in the verdict, so the review of the change sees it (design 32 *A package, its
|
||||
// series*; DLV-C4): for each module a pull request touches, every artifact both its trunk manifest and its head's
|
||||
// manifest declare whose series differs, as `<module>/<artifact> series <trunk> → <head>`. An artifact that names none
|
||||
// is in catalogue.DefaultSeries. A manifest that cannot be read is said in the same words, never left out.
|
||||
|
||||
// seriesChanges names, on the case per pull request, each change of an artifact's series the pull request makes.
|
||||
func (st *state) seriesChanges(ctx context.Context) {
|
||||
if st.j.Read == nil {
|
||||
return
|
||||
}
|
||||
var trunk map[string]string
|
||||
if st.j.Manifests != nil {
|
||||
trunk = st.j.Manifests.ManifestDigests()
|
||||
}
|
||||
for _, p := range st.c.Pulls {
|
||||
var words []string
|
||||
for _, module := range p.Modules {
|
||||
if trunk[module] == "" {
|
||||
continue // new on this pull request: no series is changed
|
||||
}
|
||||
was, err := st.j.Read.Manifest(ctx, trunk[module])
|
||||
if err != nil {
|
||||
words = append(words, fmt.Sprintf("%s: the trunk's series not read (%v)", module, err))
|
||||
continue
|
||||
}
|
||||
var now catalogue.Manifest
|
||||
switch digest := p.Fps[module].Manifest; {
|
||||
case digest == trunk[module]:
|
||||
continue
|
||||
case digest != "":
|
||||
if now, err = st.j.Read.Manifest(ctx, digest); err != nil {
|
||||
words = append(words, fmt.Sprintf("%s: the head's series not read (%v)", module, err))
|
||||
continue
|
||||
}
|
||||
case st.j.Forge != nil:
|
||||
var found bool
|
||||
if now, _, found, err = st.manifestAtHead(ctx, p, module); err != nil {
|
||||
words = append(words, fmt.Sprintf("%s: the head's series not read (%v)", module, err))
|
||||
continue
|
||||
} else if !found {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
words = append(words, seriesMoved(module, was, now)...)
|
||||
}
|
||||
if len(words) > 0 {
|
||||
st.c.series(pullKey(p), words)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seriesMoved are the artifacts of module whose series the head's manifest changes from the trunk's.
|
||||
func seriesMoved(module string, was, now catalogue.Manifest) []string {
|
||||
if was.Build == nil || now.Build == nil {
|
||||
return nil
|
||||
}
|
||||
before := map[string]string{}
|
||||
for _, a := range was.Build.Artifacts {
|
||||
before[a.Name] = a.SeriesOf()
|
||||
}
|
||||
var out []string
|
||||
for _, a := range now.Build.Artifacts {
|
||||
if old, ok := before[a.Name]; ok && old != a.SeriesOf() {
|
||||
out = append(out, fmt.Sprintf("%s/%s series %s → %s", module, a.Name, old, a.SeriesOf()))
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// series keeps, of a pull request (pullKey), the words of the series changes it makes.
|
||||
func (c *Case) series(key string, words []string) {
|
||||
c.notesMu.Lock()
|
||||
defer c.notesMu.Unlock()
|
||||
if c.seriesOf == nil {
|
||||
c.seriesOf = map[string][]string{}
|
||||
}
|
||||
c.seriesOf[key] = words
|
||||
}
|
||||
|
||||
// Series are the series changes a pull request (pullKey) makes, as the check named them; none when it makes none.
|
||||
func (c *Case) Series(key string) []string {
|
||||
c.notesMu.Lock()
|
||||
defer c.notesMu.Unlock()
|
||||
return slices.Clone(c.seriesOf[key])
|
||||
}
|
||||
|
||||
// seriesWords is a pull request's series changes for its status line.
|
||||
func seriesWords(c *Case, p Pull) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(c.Series(pullKey(p)), ", ")
|
||||
}
|
||||
Reference in New Issue
Block a user