B0's mesh-sdk go/delivery run lines move to their registry homes: the run job, the one-job rule, the Go check stream alone, the input's bound, digest and key in sdk/buildstep; AskWhat's run case in sdk/buildrun; the ask of the verb input and the check of a verdict's judged-by in meshdeliverycheckerclient. B0's tests come in as rows of the existing tables. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
342 lines
13 KiB
Go
342 lines
13 KiB
Go
// Package meshdeliverycheckerclient holds the logic the holders and callers of the seat mesh-delivery-checker share over its
|
|
// contract (novox/hq ADR 0625 §4, ADR 0634 §2): the rules a verdict and a kept verdict must keep to be written or read. The
|
|
// checker writes them, and the view, the gate and the indexer read them; the contract's decoding is strict by schema only, so
|
|
// these rules are written once here and no caller copies them.
|
|
package meshdeliverycheckerclient
|
|
|
|
import (
|
|
"fmt"
|
|
pathpkg "path"
|
|
"strings"
|
|
|
|
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
|
)
|
|
|
|
// The states of a verdict, the levels of a check and the compositions' states.
|
|
const (
|
|
VerdictRunning = "running"
|
|
VerdictPass = "pass"
|
|
VerdictFail = "fail"
|
|
|
|
LevelUnit = "unit"
|
|
LevelIntegration = "integration"
|
|
|
|
CompositionNotRun = "not-run"
|
|
CompositionPass = "pass"
|
|
CompositionFail = "fail"
|
|
)
|
|
|
|
// MaxFailures is the most failures a verdict names; it names the count of the rest beside them.
|
|
const MaxFailures = 50
|
|
|
|
func checkLevel(level string) error {
|
|
if level != LevelUnit && level != LevelIntegration {
|
|
return fmt.Errorf("meshdeliverycheckerclient: 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") == "" }
|
|
|
|
// CheckRepository refuses a repository that is not <owner>/<name> with exactly one slash, each part a key part.
|
|
func CheckRepository(repository string) error {
|
|
owner, name, ok := strings.Cut(repository, "/")
|
|
if !ok || strings.Contains(name, "/") {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the repository %q is not <owner>/<name>, with exactly one slash", repository)
|
|
}
|
|
if err := keys.CheckName("repository owner", owner); err != nil {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
|
|
}
|
|
if err := keys.CheckName("repository name", name); err != nil {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkComposition(c *checker.VerdictsByGroupHeadsValueComposition) error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
switch c.State {
|
|
case CompositionNotRun:
|
|
if c.Sdk != "" || len(c.Machines) > 0 {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition is not run and names no mesh-sdk commit or machines")
|
|
}
|
|
case CompositionPass:
|
|
if c.Sdk == "" {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition passed and names no mesh-sdk commit")
|
|
}
|
|
case CompositionFail:
|
|
if c.Words == "" {
|
|
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition failed and gives no words")
|
|
}
|
|
default:
|
|
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition state %q is not %s, %s or %s", c.State, CompositionNotRun, CompositionPass, CompositionFail)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkJudgedBy refuses a checking program named at no repository, full commit and package, kept under no program key,
|
|
// or over no head or a head at no full commit (novox/hq ADR 0644, ADR 0656; issue 1921).
|
|
func checkJudgedBy(j *checker.VerdictsByGroupHeadsValueJudgedBy) error {
|
|
const who = "meshdeliverycheckerclient: "
|
|
if j == nil {
|
|
return nil
|
|
}
|
|
if CheckRepository(j.Program.Repository) != nil || !isCommit(j.Program.Commit) || j.Program.Package == "" {
|
|
return fmt.Errorf(who+"the verdict is judged by a checking program built at no repository, full commit and package (%+v)", j.Program)
|
|
}
|
|
if !strings.HasPrefix(j.Key, "program-") {
|
|
return fmt.Errorf(who+"the verdict's checking program is kept under %q, which is no program key", j.Key)
|
|
}
|
|
if len(j.Heads) == 0 {
|
|
return fmt.Errorf(who + "the verdict's checking program read no head")
|
|
}
|
|
for _, h := range j.Heads {
|
|
if CheckRepository(h.Repository) != nil || !isCommit(h.Commit) {
|
|
return fmt.Errorf(who+"the verdict's checking program read %s at %q, which is no full commit", h.Repository, h.Commit)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isCommit(s string) bool { return len(s) == 40 && strings.Trim(s, "0123456789abcdef") == "" }
|
|
|
|
// CheckVerdict refuses a verdict a reader could not place: no group, heads that are not 12 lowercase hex digits, a repository
|
|
// both merged and read beside, a carried-from that carries nothing, a series change from a series to itself or named twice, a
|
|
// level that is neither unit nor integration or counts a negative number, a time below zero, a pass that says nothing of a
|
|
// level, a fail that names neither a test nor a machine, and failures on a verdict that did not fail.
|
|
func CheckVerdict(v checker.VerdictsByGroupHeadsValue) error {
|
|
const who = "meshdeliverycheckerclient: "
|
|
if v.Group == "" {
|
|
return fmt.Errorf(who + "a verdict names no group")
|
|
}
|
|
if !isHex12(v.Heads) {
|
|
return fmt.Errorf(who+"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(who+"the verdict's trunk head of %s is empty", repository)
|
|
}
|
|
}
|
|
for repository, commit := range v.Beside {
|
|
if commit == "" {
|
|
return fmt.Errorf(who+"the verdict's sibling commit of %s is empty", repository)
|
|
}
|
|
if _, ok := v.Trunk[repository]; ok {
|
|
return fmt.Errorf(who+"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(who + "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(who+"the verdict's carried-from names %s, which it judged on no commit of", repository)
|
|
}
|
|
moved = moved || commit != now
|
|
}
|
|
if !moved {
|
|
return fmt.Errorf(who + "the verdict's carried-from names the commits it is judged on: nothing moved, so nothing was carried")
|
|
}
|
|
}
|
|
changed := map[string]bool{}
|
|
for _, c := range v.Series {
|
|
if c.Module == "" || c.Artifact == "" || c.From == "" || c.To == "" {
|
|
return fmt.Errorf(who+"the verdict's series change %+v names no module, artifact, from or to", c)
|
|
}
|
|
if c.From == c.To {
|
|
return fmt.Errorf(who+"the verdict's series change of %s/%s is from %s to the same series", c.Module, c.Artifact, c.From)
|
|
}
|
|
if changed[c.Module+"/"+c.Artifact] {
|
|
return fmt.Errorf(who+"the verdict names the series change of %s/%s twice", c.Module, c.Artifact)
|
|
}
|
|
changed[c.Module+"/"+c.Artifact] = true
|
|
}
|
|
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(who+"the verdict's %s level counts a negative number of tests", level)
|
|
}
|
|
}
|
|
if err := checkComposition(v.Composition); err != nil {
|
|
return err
|
|
}
|
|
if err := checkJudgedBy(v.JudgedBy); err != nil {
|
|
return err
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, level := range v.OverBound {
|
|
if err := checkLevel(level); err != nil {
|
|
return fmt.Errorf(who+"the verdict's over-bound names: %w", err)
|
|
}
|
|
if seen[level] {
|
|
return fmt.Errorf(who+"the verdict's over-bound names the level %s twice", level)
|
|
}
|
|
seen[level] = true
|
|
}
|
|
if t := v.Took; t != nil {
|
|
for part, ms := range map[string]int64{"queue": t.Queue, "clone": t.Clone, "unit": t.Unit, "lint": t.Lint,
|
|
"integration-setup": t.IntegrationSetup, "integration-run": t.IntegrationRun, "composition": t.Composition} {
|
|
if ms < 0 {
|
|
return fmt.Errorf(who+"the verdict's time of the part %s is negative", part)
|
|
}
|
|
}
|
|
}
|
|
named := v.Failed != nil && (v.Failed.Test != "" || v.Failed.Machine != "")
|
|
if len(v.Failures) > 0 && v.State != VerdictFail {
|
|
return fmt.Errorf(who+"a %s verdict names no failures", v.State)
|
|
}
|
|
if len(v.Failures) > MaxFailures+1 {
|
|
return fmt.Errorf(who+"the verdict names %d failures, more than %d and the count of the rest", len(v.Failures), MaxFailures)
|
|
}
|
|
switch v.State {
|
|
case VerdictRunning:
|
|
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
|
|
return fmt.Errorf(who + "a running verdict names no failure")
|
|
}
|
|
case VerdictPass:
|
|
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
|
|
return fmt.Errorf(who + "a pass names no failure")
|
|
}
|
|
for _, level := range []string{LevelUnit, LevelIntegration} {
|
|
if _, ok := v.Levels[level]; !ok {
|
|
return fmt.Errorf(who+"a pass says nothing of the %s level", level)
|
|
}
|
|
}
|
|
case VerdictFail:
|
|
if !named {
|
|
return fmt.Errorf(who + "a fail names neither a test nor a machine")
|
|
}
|
|
default:
|
|
return fmt.Errorf(who+"the verdict state %q is not running, pass or fail", v.State)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckKept refuses a kept verdict that is not a pass of a repository's package at a level, with its toolchain and its time:
|
|
// a failure is never kept.
|
|
func CheckKept(k checker.KeptByLevelDigestValue) error {
|
|
const who = "meshdeliverycheckerclient: "
|
|
if k.State != VerdictPass {
|
|
return fmt.Errorf(who+"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(who + "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(who + "a kept verdict counts a negative number of tests or a negative time")
|
|
}
|
|
if k.Toolchain == "" {
|
|
return fmt.Errorf(who + "a kept verdict names no toolchain version")
|
|
}
|
|
if k.At == "" {
|
|
return fmt.Errorf(who + "a kept verdict says no time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckPreview refuses a preview a reader could not place (the checker writes it, the indexer reads it for the candidates'
|
|
// fingerprints): a repository, head or trunk commit that does not key, a head in conflict that names no file or has a merge
|
|
// commit or modules, a head that merged and names no merge commit, and a module with no directory, no tree, a manifest
|
|
// digest that is not 64 lowercase hex digits, a lock without path or hash, or a named path that is not clean and inside the
|
|
// module's directory or names no git object.
|
|
func CheckPreview(p checker.PreviewsByRepositoryHeadTrunkValue) error {
|
|
const who = "meshdeliverycheckerclient: "
|
|
if err := CheckRepository(p.Repository); err != nil {
|
|
return err
|
|
}
|
|
if err := keys.CheckName("head", p.Head); err != nil {
|
|
return fmt.Errorf(who+"the preview: %w", err)
|
|
}
|
|
if err := keys.CheckName("trunk commit", p.Trunk); err != nil {
|
|
return fmt.Errorf(who+"the preview: %w", err)
|
|
}
|
|
for _, f := range p.Conflicts {
|
|
if f == "" {
|
|
return fmt.Errorf(who + "a preview names a conflicting file with an empty path")
|
|
}
|
|
}
|
|
if len(p.Conflicts) > 0 {
|
|
if p.Merge != "" || len(p.Modules) > 0 {
|
|
return fmt.Errorf(who + "a preview of a head in conflict has no merge commit and no modules")
|
|
}
|
|
return nil
|
|
}
|
|
if p.Merge == "" {
|
|
return fmt.Errorf(who + "a preview names no merge commit")
|
|
}
|
|
for dir, m := range p.Modules {
|
|
if dir == "" {
|
|
return fmt.Errorf(who + "a preview names a module with an empty directory")
|
|
}
|
|
if m.Tree == "" {
|
|
return fmt.Errorf(who+"the preview's module %q has no tree", dir)
|
|
}
|
|
if !keys.IsHex64(m.Manifest) {
|
|
return fmt.Errorf(who+"the preview's module %q: %q is not a manifest digest, 64 lowercase hex digits", dir, m.Manifest)
|
|
}
|
|
for path, hash := range m.Locks {
|
|
if path == "" || hash == "" {
|
|
return fmt.Errorf(who+"the lock %q of the preview's module %q has an empty path or hash", path, dir)
|
|
}
|
|
}
|
|
for p, object := range m.Paths {
|
|
if p == "" || p != pathpkg.Clean(p) || strings.HasPrefix(p, "/") || p == ".." || strings.HasPrefix(p, "../") {
|
|
return fmt.Errorf(who+"the preview's module %q: the named path %q is not a clean path inside the module's directory", dir, p)
|
|
}
|
|
if object == "" {
|
|
return fmt.Errorf(who+"the preview's module %q: the named path %q names no git object", dir, p)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VerdictKey is the key, in the state verdicts, of a group's check at its heads: `<escaped group>.<heads hash>`, the
|
|
// group escaped so a branch such as `fix/1.2` keys, the heads hash as the mesh-delivery client's HeadsHash gives it
|
|
// (12 lowercase hex digits).
|
|
func VerdictKey(group, headsHash string) (string, error) {
|
|
if group == "" {
|
|
return "", fmt.Errorf("meshdeliverycheckerclient: the group name is empty")
|
|
}
|
|
if !isHex12(headsHash) {
|
|
return "", fmt.Errorf("meshdeliverycheckerclient: %q is not a heads hash, 12 lowercase hex digits", headsHash)
|
|
}
|
|
return keys.EscapeName(group) + "." + headsHash, nil
|
|
}
|
|
|
|
// ParseVerdictKey is the inverse of VerdictKey.
|
|
func ParseVerdictKey(key string) (group, headsHash string, err error) {
|
|
i := strings.LastIndex(key, ".")
|
|
if i < 0 {
|
|
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key, <group>.<heads hash>", key)
|
|
}
|
|
part, headsHash := key[:i], key[i+1:]
|
|
if !isHex12(headsHash) {
|
|
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: %q is not a heads hash, 12 lowercase hex digits", key, headsHash)
|
|
}
|
|
if part == "" || strings.Contains(part, ".") {
|
|
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: the group part is empty or holds a dot", key)
|
|
}
|
|
if group, err = keys.UnescapeName(part); err != nil {
|
|
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: %w", key, err)
|
|
}
|
|
return group, headsHash, nil
|
|
}
|