package delivery import ( "encoding/json" "fmt" "reflect" "strings" "time" ) // The checker's keys (novox/hq ADR 0447 clauses 1 to 3, ADR 0444, ADR 0446 clause 5, ADR 0453 clause 4): the verdicts of // its checks and the passing verdicts it keeps per package and level. The checker writes them from R4c, dark, and // writes the status `mesh/check` it decides as an owed act in its own bucket (CheckerOwedBucket, the gate's owed shape); // the forge bridge, the only part that calls the forge, performs that act (unrequired until R4f), once per revision of // the key (ADR 0484 clause 2). The gate reads the status from R4f, and the view and the watchman read the verdicts (a // running check carries a pulse). // // **The verdict's key is the group and the heads, not the fingerprints** (the lead's limit for R4a). Research 063 // section 01 drew `verdicts..check.`; ADR 0447 clause 1 judges a pull request's head merged onto its // trunk's head, and a repository without modules has no fingerprint set, so the key is `.` as the // heads hash of HeadsHash gives it. The fpset (FingerprintSet) is not part of it. const ( // CheckerModule is the module of the checker part. CheckerModule = "mesh-delivery-checker" // CheckerVerdictsBucket is the checker's bucket of verdicts, `_`. CheckerVerdictsBucket = CheckerModule + "_verdicts" // CheckerVerdictsHistory is the revisions the verdicts bucket keeps per key. CheckerVerdictsHistory = 5 // CheckerKeptBucket is the checker's bucket of kept verdicts. A key is written once, by a create that fails when // the key exists, by the checker's credential alone, from the result the checker reads from the test run and // never from anything the code under test writes (ADR 0444 clause 5); nothing changes it afterwards. CheckerKeptBucket = CheckerModule + "_kept" // CheckerKeptHistory is the revisions the kept bucket keeps per key. CheckerKeptHistory = 1 ) // The test levels (ADR 0445 clause 2) and the states of a verdict. const ( LevelUnit = "unit" LevelIntegration = "integration" VerdictRunning = "running" VerdictPass = "pass" VerdictFail = "fail" ) func checkLevel(level string) error { if level != LevelUnit && level != LevelIntegration { return fmt.Errorf("delivery: the level %q is not %s or %s", level, LevelUnit, LevelIntegration) } return nil } func isHex12(s string) bool { return len(s) == 12 && strings.Trim(s, "0123456789abcdef") == "" } // CheckerVerdictKey is the key, in CheckerVerdictsBucket, of a group's check at its heads: `.`, the group escaped as CandidatesKey does and the heads hash as HeadsHash gives it (12 lowercase hex digits). func CheckerVerdictKey(group, headsHash string) (string, error) { g, err := groupKey(group) if err != nil { return "", err } if !isHex12(headsHash) { return "", fmt.Errorf("delivery: %q is not a heads hash, 12 lowercase hex digits", headsHash) } return g + "." + headsHash, nil } // ParseCheckerVerdictKey is the inverse of CheckerVerdictKey. func ParseCheckerVerdictKey(key string) (group, headsHash string, err error) { i := strings.LastIndex(key, ".") if i < 0 { return "", "", fmt.Errorf("delivery: %q is not a verdict key, .", key) } part, headsHash := key[:i], key[i+1:] if !isHex12(headsHash) { return "", "", fmt.Errorf("delivery: %q is not a verdict key: %q is not a heads hash, 12 lowercase hex digits", key, headsHash) } if group, err = parseGroupKey(part); err != nil { return "", "", fmt.Errorf("delivery: %q is not a verdict key: %w", key, err) } if strings.Contains(part, ".") { return "", "", fmt.Errorf("delivery: %q is not a verdict key: the group part holds a dot", key) } return group, headsHash, nil } // LevelResult is a level's tests: how many ran in this check and how many were taken from kept verdicts (ADR 0444 // clause 2). type LevelResult struct { Run int `json:"run"` Kept int `json:"kept"` // Skipped is the tests the run skipped, so a pass never hides them (issue 1251, ADR 0484 clause 7). Skipped int `json:"skipped,omitempty"` } // The states of a Composition (ADR 0484 clause 6): the composition check is the assigner's, so until it delivers a // verdict says CompositionNotRun, and a line never says the composition passed on that. const ( CompositionNotRun = "not-run" CompositionPass = "pass" CompositionFail = "fail" ) // Composition is what the check of the machines' composition made of a verdict: its State (not-run, pass or fail), the // Words of a refusal, the mesh-sdk commit it composed with and the machines composed (ADR 0482 clause 9, ADR 0484 clause 6). type Composition struct { State string `json:"state"` Words string `json:"words,omitempty"` SDK string `json:"sdk,omitempty"` Machines []string `json:"machines,omitempty"` } // Took is the time of each part of a check in integer milliseconds (ADR 0447 clause 3): waiting in the queue, cloning, // the unit level, the integration level's setup and its running, and the composition. type Took struct { Queue int64 `json:"queue"` Clone int64 `json:"clone"` Unit int64 `json:"unit"` IntegrationSetup int64 `json:"integration-setup"` IntegrationRun int64 `json:"integration-run"` Composition int64 `json:"composition"` } // FailedFirst names what failed first: a test (its package and name) or a machine (and the words of the composition's // refusal); one of them, with Words optional. type FailedFirst struct { Test string `json:"test,omitempty"` Machine string `json:"machine,omitempty"` Words string `json:"words,omitempty"` } // Verdict is the value of a verdict key: the group and its heads hash, the trunk head of each repository it was judged // against (repository to full sha), the State (running, pass or fail), what failed first, the tests run and kept per // level, each part's time, the machines composed and the mesh-sdk version the composition ran with (ADR 0446 clause 5). type Verdict struct { V int `json:"v"` Group string `json:"group"` Heads string `json:"heads"` Trunk map[string]string `json:"trunk,omitempty"` State string `json:"state"` Failed FailedFirst `json:"failed,omitzero"` Levels map[string]LevelResult `json:"levels,omitempty"` Took Took `json:"took,omitzero"` Machines []string `json:"machines,omitempty"` SDK string `json:"sdk,omitempty"` // Composition is the composition check's result; absent in a value written before it (ADR 0484 clause 6). 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"` } func (c *Composition) check() error { if c == nil { return nil } switch c.State { case CompositionNotRun: if c.Words != "" || c.SDK != "" || len(c.Machines) > 0 { return fmt.Errorf("delivery: the verdict's composition is not run and says nothing of words, the mesh-sdk commit or machines") } case CompositionPass: if c.SDK == "" { return fmt.Errorf("delivery: the verdict's composition passed and names no mesh-sdk commit") } case CompositionFail: if c.Words == "" { return fmt.Errorf("delivery: the verdict's composition failed and gives no words") } default: return fmt.Errorf("delivery: the verdict's composition state %q is not %s, %s or %s", c.State, CompositionNotRun, CompositionPass, CompositionFail) } return nil } func (v Verdict) check() error { if v.Group == "" { return fmt.Errorf("delivery: a verdict names no group") } if !isHex12(v.Heads) { return fmt.Errorf("delivery: the verdict's heads hash %q is not 12 lowercase hex digits", v.Heads) } for repository, head := range v.Trunk { if head == "" { return fmt.Errorf("delivery: the verdict's trunk head of %s is empty", repository) } } for level, r := range v.Levels { if err := checkLevel(level); err != nil { return err } if r.Run < 0 || r.Kept < 0 || r.Skipped < 0 { return fmt.Errorf("delivery: the verdict's %s level counts a negative number of tests", level) } } if err := v.Composition.check(); err != nil { return err } seen := map[string]bool{} for _, level := range v.OverBound { if err := checkLevel(level); err != nil { return fmt.Errorf("delivery: the verdict's over-bound names: %w", err) } if seen[level] { return fmt.Errorf("delivery: the verdict's over-bound names the level %s twice", level) } seen[level] = true } for part, ms := range map[string]int64{"queue": v.Took.Queue, "clone": v.Took.Clone, "unit": v.Took.Unit, "integration-setup": v.Took.IntegrationSetup, "integration-run": v.Took.IntegrationRun, "composition": v.Took.Composition} { if ms < 0 { return fmt.Errorf("delivery: the verdict's time of the part %s is negative", part) } } named := v.Failed.Test != "" || v.Failed.Machine != "" switch v.State { case VerdictRunning: if v.Failed != (FailedFirst{}) { return fmt.Errorf("delivery: a running verdict names no failure") } case VerdictPass: if v.Failed != (FailedFirst{}) { return fmt.Errorf("delivery: a pass names no failure") } for _, level := range []string{LevelUnit, LevelIntegration} { if _, ok := v.Levels[level]; !ok { return fmt.Errorf("delivery: a pass says nothing of the %s level", level) } } case VerdictFail: if !named { return fmt.Errorf("delivery: a fail names neither a test nor a machine") } default: return fmt.Errorf("delivery: the verdict state %q is not running, pass or fail", v.State) } return nil } // EncodeVerdict is v as JSON at Version; it refuses a fail that names neither a test nor a machine, a pass without both // levels, an unknown state or level, and a negative count or time. func EncodeVerdict(v Verdict) ([]byte, error) { if err := v.check(); err != nil { return nil, err } v.V = Version return json.Marshal(v) } // DecodeVerdict reads a verdict; it refuses an unknown version (ErrUnknownVersion) and what EncodeVerdict refuses. func DecodeVerdict(data []byte) (Verdict, error) { var v Verdict if err := decode(data, &v); err != nil { return Verdict{}, err } if err := v.check(); err != nil { return Verdict{}, err } return v, nil } // SameVerdict says whether two verdicts are the same value, so the checker writes only when they differ. func SameVerdict(a, b Verdict) bool { return reflect.DeepEqual(a, b) } // KeptKey is the key, in CheckerKeptBucket, of a kept verdict: `.`, the level unit or integration (ADR // 0445 clause 2) and the digest, lowercase hex, of everything that package's tests read at that level as the // language's build module names it (ADR 0444 clause 1, ADR 0445 clause 5). func KeptKey(level, digest string) (string, error) { if err := checkLevel(level); err != nil { return "", err } if digest == "" || strings.Trim(digest, "0123456789abcdef") != "" { return "", fmt.Errorf("delivery: %q is not a digest, lowercase hex", digest) } return level + "." + digest, nil } // ParseKeptKey is the inverse of KeptKey. func ParseKeptKey(key string) (level, digest string, err error) { level, digest, ok := strings.Cut(key, ".") if !ok { return "", "", fmt.Errorf("delivery: %q is not a kept key, .", key) } if _, err = KeptKey(level, digest); err != nil { return "", "", fmt.Errorf("delivery: %q is not a kept key: %w", key, err) } return level, digest, nil } // Kept is the value of a kept key: a passing verdict of one package at one level (State is pass and nothing else: a // failure is never kept, ADR 0444 clause 1), with the repository, the package, the number of tests, the time they took // in milliseconds, the toolchain's version and when it was kept. type Kept struct { V int `json:"v"` State string `json:"state"` Repository string `json:"repository"` Package string `json:"package"` Level string `json:"level"` Tests int `json:"tests"` Took int64 `json:"took"` Toolchain string `json:"toolchain"` At time.Time `json:"at"` } func (k Kept) check() error { if k.State != VerdictPass { return fmt.Errorf("delivery: a kept verdict is a pass, never %q: a failure is never kept", k.State) } if err := checkRepository(k.Repository); err != nil { return err } if k.Package == "" { return fmt.Errorf("delivery: a kept verdict names no package") } if err := checkLevel(k.Level); err != nil { return err } if k.Tests < 0 || k.Took < 0 { return fmt.Errorf("delivery: a kept verdict counts a negative number of tests or a negative time") } if k.Toolchain == "" { return fmt.Errorf("delivery: a kept verdict names no toolchain version") } if k.At.IsZero() { return fmt.Errorf("delivery: a kept verdict says no time") } return nil } // EncodeKept is k as JSON at Version; it refuses a kept value that is not a pass. func EncodeKept(k Kept) ([]byte, error) { if err := k.check(); err != nil { return nil, err } k.V = Version return json.Marshal(k) } // DecodeKept reads a kept value; it refuses an unknown version (ErrUnknownVersion) and what EncodeKept refuses. func DecodeKept(data []byte) (Kept, error) { var k Kept if err := decode(data, &k); err != nil { return Kept{}, err } if err := k.check(); err != nil { return Kept{}, err } return k, nil } // SameKept says whether two kept values are the same value. func SameKept(a, b Kept) bool { at := a.At.Equal(b.At) a.At, b.At = time.Time{}, time.Time{} return at && a == b }