From f5c233a9288c097840fd51df4567dc91f3b98fb2 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 11:25:07 +0100 Subject: [PATCH] =?UTF-8?q?delivery:=20a=20verdict=20names=20the=20sibling?= =?UTF-8?q?s=20it=20read=20and=20the=20commits=20it=20was=20carried=20from?= =?UTF-8?q?,=20and=20a=20composition=20its=20fingerprint=20set,=20so=20a?= =?UTF-8?q?=20trunk=20or=20sibling=20move=20is=20carried,=20not=20judged?= =?UTF-8?q?=20again=20(ADR=200548=20=C2=A75,=20=C2=A76;=20issue=20568=20S2?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/delivery/checker.go | 39 +++++++++++++++++++++++++++++++++++++ go/delivery/checker_test.go | 28 +++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/go/delivery/checker.go b/go/delivery/checker.go index e23a51e..6b07ede 100644 --- a/go/delivery/checker.go +++ b/go/delivery/checker.go @@ -115,6 +115,10 @@ type Composition struct { Words string `json:"words,omitempty"` SDK string `json:"sdk,omitempty"` Machines []string `json:"machines,omitempty"` + // FPSet is the candidate's fingerprint set (Candidate.FPSet) the composition judged: a carry keeps a composition that + // passed while the candidate's set is the same, and composes again when a fingerprint changed (novox/hq ADR 0548 §5). + // Absent in a value written before it, which a carry composes again. + FPSet string `json:"fpset,omitempty"` } // Took is the time of each part of a check in integer milliseconds (ADR 0447 clause 3): waiting in the queue, cloning, @@ -166,6 +170,14 @@ type Verdict struct { Composition *Composition `json:"composition,omitempty"` // OverBound names the levels whose running went over their bound; it does not change the State (ADR 0453 clause 9). OverBound []string `json:"over-bound,omitempty"` + // Beside is the commit of each sibling repository the check read beside the repositories it merged (ADR 0484 §4), by + // repository: a move of one puts the verdict out of date as a trunk move does, and it is carried (novox/hq ADR 0548 §6). + // A repository is in Trunk or here, never both. Absent in a value written before it. + Beside map[string]string `json:"beside,omitempty"` + // CarriedFrom names, of a carried verdict, the trunk and sibling commits it was judged on before the moves it was + // carried over, by repository (ADR 0548 §5): the verdict was rewritten for Trunk and Beside by running only the packages + // whose digest the moves changed. Absent from a verdict judged whole, and from one still running. + CarriedFrom map[string]string `json:"carried-from,omitempty"` } func (c *Composition) check() error { @@ -203,6 +215,33 @@ func (v Verdict) check() error { return fmt.Errorf("delivery: the verdict's trunk head of %s is empty", repository) } } + for repository, commit := range v.Beside { + if commit == "" { + return fmt.Errorf("delivery: the verdict's sibling commit of %s is empty", repository) + } + if _, ok := v.Trunk[repository]; ok { + return fmt.Errorf("delivery: the verdict names %s as a trunk it merged and as a sibling it read", repository) + } + } + if len(v.CarriedFrom) > 0 { + if v.State == VerdictRunning { + return fmt.Errorf("delivery: a running verdict is carried from nothing: carried-from is written with its end") + } + moved := false + for repository, commit := range v.CarriedFrom { + now, inTrunk := v.Trunk[repository] + if !inTrunk { + now = v.Beside[repository] + } + if commit == "" || now == "" { + return fmt.Errorf("delivery: the verdict's carried-from names %s, which it judged on no commit of", repository) + } + moved = moved || commit != now + } + if !moved { + return fmt.Errorf("delivery: the verdict's carried-from names the commits it is judged on: nothing moved, so nothing was carried") + } + } for level, r := range v.Levels { if err := checkLevel(level); err != nil { return err diff --git a/go/delivery/checker_test.go b/go/delivery/checker_test.go index 815577a..b20af2e 100644 --- a/go/delivery/checker_test.go +++ b/go/delivery/checker_test.go @@ -29,7 +29,12 @@ func verdictSamples() map[string]Verdict { failTest.Levels = map[string]LevelResult{LevelUnit: {Run: 1}} failMachine := passVerdict() failMachine.State, failMachine.Failed = VerdictFail, FailedFirst{Machine: "ace", Words: "path /etc/x is owned by two modules"} - return map[string]Verdict{"running": running, "pass": passVerdict(), "fail by a test": failTest, "fail by a machine": failMachine} + carried := passVerdict() + carried.Beside = map[string]string{"novox/mesh-catalog": "cccc"} + carried.CarriedFrom = map[string]string{"novox/mesh-sdk": "9999", "novox/mesh-control": "bbbb", "novox/mesh-catalog": "cccc"} + carried.Composition = &Composition{State: CompositionPass, SDK: carried.SDK, Machines: []string{"ace"}, FPSet: "fps1:00"} + return map[string]Verdict{"running": running, "pass": passVerdict(), "fail by a test": failTest, "fail by a machine": failMachine, + "carried": carried} } func keptSample() Kept { @@ -248,7 +253,14 @@ func TestVerdictCarriesSkippedCompositionAndOverBound(t *testing.T) { if err != nil { t.Fatal(err) } - for _, field := range []string{`"skipped":4`, `"composition":{"state":"pass"`, `"over-bound":["integration"]`} { + v.Beside, v.CarriedFrom = map[string]string{"novox/mesh-catalog": "cccc"}, map[string]string{"novox/mesh-catalog": "cbcb"} + v.Composition.FPSet = "fps1:ab" + data, err = EncodeVerdict(v) + if err != nil { + t.Fatal(err) + } + for _, field := range []string{`"skipped":4`, `"composition":{"state":"pass"`, `"over-bound":["integration"]`, `"beside":{"novox/mesh-catalog":"cccc"}`, + `"carried-from":{"novox/mesh-catalog":"cbcb"}`, `"fpset":"fps1:ab"`} { if !strings.Contains(string(data), field) { t.Errorf("the value lacks %s: %s", field, data) } @@ -295,6 +307,13 @@ func TestVerdictNewFieldsChecked(t *testing.T) { "a not-run composition naming machines": {func(v *Verdict) { v.Composition = &Composition{State: CompositionNotRun, Machines: []string{"ace"}} }, []string{"composition", "not run"}}, "an unknown over-bound level": {func(v *Verdict) { v.OverBound = []string{"e2e"} }, []string{"e2e"}}, "an over-bound level named twice": {func(v *Verdict) { v.OverBound = []string{LevelUnit, LevelUnit} }, []string{"over-bound", "unit"}}, + // ADR 0548 §5, §6: the siblings read and the commits a verdict was carried from. + "a sibling with no commit": {func(v *Verdict) { v.Beside = map[string]string{"novox/mesh-catalog": ""} }, []string{"sibling", "mesh-catalog"}}, + "a trunk named as a sibling": {func(v *Verdict) { v.Beside = map[string]string{"novox/mesh-sdk": "aaaa"} }, []string{"trunk", "sibling", "mesh-sdk"}}, + "carried from an unjudged repo": {func(v *Verdict) { v.CarriedFrom = map[string]string{"novox/other": "1111"} }, []string{"carried-from", "novox/other"}}, + "carried from an empty commit": {func(v *Verdict) { v.CarriedFrom = map[string]string{"novox/mesh-sdk": ""} }, []string{"carried-from", "mesh-sdk"}}, + "carried over no move": {func(v *Verdict) { v.CarriedFrom = map[string]string{"novox/mesh-sdk": "aaaa"} }, []string{"nothing moved"}}, + "a running verdict carried": {func(v *Verdict) { v.State, v.CarriedFrom = VerdictRunning, map[string]string{"novox/mesh-sdk": "9999"} }, []string{"running", "carried"}}, } for name, c := range cases { v := passVerdict() @@ -319,7 +338,7 @@ func TestVerdictOldAndNewReadersAgree(t *testing.T) { if err != nil { t.Fatalf("the new reader of an old value: %v", err) } - if v.Composition != nil || v.OverBound != nil || v.Levels[LevelUnit].Skipped != 0 || v.Levels[LevelUnit].Run != 12 { + if v.Composition != nil || v.OverBound != nil || v.Beside != nil || v.CarriedFrom != nil || v.Levels[LevelUnit].Skipped != 0 || v.Levels[LevelUnit].Run != 12 { t.Errorf("an old value grew fields: %+v", v) } again, err := EncodeVerdict(v) @@ -330,6 +349,9 @@ func TestVerdictOldAndNewReadersAgree(t *testing.T) { n.Levels[LevelUnit] = LevelResult{Run: 12, Kept: 301, Skipped: 4} n.Composition = &Composition{State: CompositionNotRun} n.OverBound = []string{LevelUnit} + n.Beside = map[string]string{"novox/mesh-catalog": "cccc"} + n.CarriedFrom = map[string]string{"novox/mesh-sdk": "9999"} + n.Composition.FPSet = "fps1:00" data, err := EncodeVerdict(n) if err != nil { t.Fatal(err)