Name plans-building among the controller's readable states and pin the SDK that does (issue 1687, ADR 0483 clause 2)
mesh/repo-check head b9ec1919b865 merged into trunk a3c965b9a0d5 beside mesh-catalog#423@12ff1cfb — pass: its merge-check.sh passed
mesh/merge-gate head b9ec1919b865 merged into trunk a3c965b9a0d5 — pass: group feat/the-checker composed: every machine composes with the change as it did …
mesh/delivery delivered
mesh/delivery-group group feat/the-checker delivered: every member is delivered
mesh/repo-check head b9ec1919b865 merged into trunk a3c965b9a0d5 beside mesh-catalog#423@12ff1cfb — pass: its merge-check.sh passed
mesh/merge-gate head b9ec1919b865 merged into trunk a3c965b9a0d5 — pass: group feat/the-checker composed: every machine composes with the change as it did …
mesh/delivery delivered
mesh/delivery-group group feat/the-checker delivered: every member is delivered
This commit is contained in:
@@ -133,7 +133,9 @@ func StateProblems(m Manifest) []string {
|
||||
// controller's account alone (the writers table); declared as a module's state, they would be asserted a
|
||||
// second way and granted to the module's own principal as well. Named here so a read of one is a read of
|
||||
// kept state. The bus's list is broker.ControllerMirrors; a test keeps the two agreeing.
|
||||
var ControllerMirrors = []string{"assignments", "declared-config", "predefined", "builds", "rollouts", "checks-running"}
|
||||
// plans-building is the plans in their build phase, read by the checker to hold checks while a delivery builds (ADR 0483
|
||||
// clause 2, issue 1687).
|
||||
var ControllerMirrors = []string{"assignments", "declared-config", "predefined", "builds", "rollouts", "checks-running", "plans-building"}
|
||||
|
||||
// StateReadsNothingDeclares is every read across a catalogue whose owner is present and declares no
|
||||
// such state. An absent owner says nothing — a module may be installed long before the one whose
|
||||
|
||||
+65
-4
@@ -9,9 +9,11 @@ import (
|
||||
)
|
||||
|
||||
// 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; the
|
||||
// forge bridge writes the status `mesh/check` from a verdict (unrequired until R4f), the gate reads it from R4f, and
|
||||
// the view and the watchman read them (a running check carries a pulse).
|
||||
// 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.<g>.check.<fpset>`; ADR 0447 clause 1 judges a pull request's head merged onto its
|
||||
@@ -88,6 +90,25 @@ func ParseCheckerVerdictKey(key string) (group, headsHash string, err error) {
|
||||
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,
|
||||
@@ -123,6 +144,33 @@ type Verdict struct {
|
||||
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 {
|
||||
@@ -141,10 +189,23 @@ func (v Verdict) check() error {
|
||||
if err := checkLevel(level); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Run < 0 || r.Kept < 0 {
|
||||
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 {
|
||||
|
||||
+26
-1
@@ -28,6 +28,18 @@ const (
|
||||
AnswersHistory = 5
|
||||
)
|
||||
|
||||
// The contexts of the statuses the mesh owes the forge: the gate's, the delivery's view of a head and of a group
|
||||
// (design 47), and the checker's, which is not required until R4f (ADR 0484 clause 2, ADR 0453 clause 4).
|
||||
const (
|
||||
StatusContextMergeGate = "mesh/merge-gate"
|
||||
StatusContextDelivery = "mesh/delivery"
|
||||
StatusContextDeliveryGroup = "mesh/delivery-group"
|
||||
StatusContextRepoCheck = "mesh/repo-check" // the gate's pending status of a recheck (mesh-delivery.recheck)
|
||||
StatusContextCheck = "mesh/check"
|
||||
)
|
||||
|
||||
var statusContexts = []string{StatusContextMergeGate, StatusContextDelivery, StatusContextDeliveryGroup, StatusContextRepoCheck, StatusContextCheck}
|
||||
|
||||
// AskKindMerge is the kind of the gate's ask for a group's merge, the first part of its key.
|
||||
const AskKindMerge = "merge"
|
||||
|
||||
@@ -131,6 +143,16 @@ func AskingID(askKey string, asking int) string {
|
||||
return AskID(askKey + "." + strconv.Itoa(asking))
|
||||
}
|
||||
|
||||
// checkStatusContext refuses a status context the mesh does not owe the forge.
|
||||
func checkStatusContext(context string) error {
|
||||
for _, c := range statusContexts {
|
||||
if context == c {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("delivery: the status context %q is not one of %s", context, strings.Join(statusContexts, ", "))
|
||||
}
|
||||
|
||||
func checkOwedAct(act string) (kind, context string, err error) {
|
||||
kind, context, hasContext := strings.Cut(act, "=")
|
||||
switch kind {
|
||||
@@ -138,7 +160,7 @@ func checkOwedAct(act string) (kind, context string, err error) {
|
||||
if !hasContext {
|
||||
return "", "", fmt.Errorf("delivery: the owed act %q is a status without its context, status=<context>", act)
|
||||
}
|
||||
if err := checkName("status context", context); err != nil {
|
||||
if err := checkStatusContext(context); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return kind, context, nil
|
||||
@@ -345,6 +367,9 @@ func (o Owed) check() error {
|
||||
if o.Context == "" {
|
||||
return fmt.Errorf("delivery: an owed status has no context")
|
||||
}
|
||||
if err := checkStatusContext(o.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Target == "" {
|
||||
return fmt.Errorf("delivery: an owed status has no target")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The checker's other buckets (ADR 0484 clauses 2 and 3): owed, the acts it owes the forge, which the forge bridge
|
||||
// performs once per revision of a key, and previews, what the fetch step answered for a head merged onto a trunk commit.
|
||||
const (
|
||||
// CheckerOwedBucket is the checker's bucket of owed acts, in the gate's owed shape: a status with the context
|
||||
// StatusContextCheck for each head it judged. One part writes each key: the checker.
|
||||
CheckerOwedBucket = CheckerModule + "_owed"
|
||||
// CheckerOwedHistory is the revisions the checker's owed bucket keeps per key.
|
||||
CheckerOwedHistory = 5
|
||||
// PreviewsBucket is the checker's bucket of merge previews; the indexer reads it for the candidates' fingerprints.
|
||||
PreviewsBucket = CheckerModule + "_previews"
|
||||
// PreviewsHistory is the revisions the previews bucket keeps per key.
|
||||
PreviewsHistory = 5
|
||||
)
|
||||
|
||||
// CheckerOwedKey is the key, in CheckerOwedBucket, of the status the checker owes on a pull request at a head: the
|
||||
// gate's OwedKey with the act `status=mesh/check`, the only act the checker owes.
|
||||
func CheckerOwedKey(repository string, number int, head string) (string, error) {
|
||||
return OwedKey(repository, number, head, OwedStatus+"="+StatusContextCheck)
|
||||
}
|
||||
|
||||
// ParseCheckerOwedKey is the inverse of CheckerOwedKey; it refuses an owed key of any other act.
|
||||
func ParseCheckerOwedKey(key string) (repository string, number int, head string, err error) {
|
||||
repository, number, head, act, err := ParseOwedKey(key)
|
||||
if err != nil {
|
||||
return "", 0, "", err
|
||||
}
|
||||
if act != OwedStatus+"="+StatusContextCheck {
|
||||
return "", 0, "", fmt.Errorf("delivery: %q is not a checker owed key: the checker owes only status=%s, not %q", key, StatusContextCheck, act)
|
||||
}
|
||||
return repository, number, head, nil
|
||||
}
|
||||
|
||||
// EncodeCheckerOwed is o as JSON at Version; it refuses what EncodeOwed refuses and any act but a status with the
|
||||
// context StatusContextCheck.
|
||||
func EncodeCheckerOwed(o Owed) ([]byte, error) {
|
||||
if err := checkCheckerOwed(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return EncodeOwed(o)
|
||||
}
|
||||
|
||||
func checkCheckerOwed(o Owed) error {
|
||||
if o.Act != OwedStatus || o.Context != StatusContextCheck {
|
||||
return fmt.Errorf("delivery: the checker owes only a status with the context %s, not the act %q with the context %q", StatusContextCheck, o.Act, o.Context)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeCheckerOwed reads an owed act of the checker's bucket.
|
||||
func DecodeCheckerOwed(data []byte) (Owed, error) {
|
||||
o, err := DecodeOwed(data)
|
||||
if err != nil {
|
||||
return Owed{}, err
|
||||
}
|
||||
if err := checkCheckerOwed(o); err != nil {
|
||||
return Owed{}, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// PreviewKey is the key, in PreviewsBucket, of a head merged onto a trunk commit: `<repository>.<head>.<trunk>`.
|
||||
func PreviewKey(repository, head, trunk string) (string, error) {
|
||||
repo, err := escapeRepository(repository)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := checkName("head", head); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := checkName("trunk commit", trunk); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return repo + "." + head + "." + trunk, nil
|
||||
}
|
||||
|
||||
// ParsePreviewKey is the inverse of PreviewKey.
|
||||
func ParsePreviewKey(key string) (repository, head, trunk string, err error) {
|
||||
parts := strings.Split(key, ".")
|
||||
if len(parts) != 3 {
|
||||
return "", "", "", fmt.Errorf("delivery: %q is not a preview key, <repository>.<head>.<trunk>", key)
|
||||
}
|
||||
repository, err = unescapeRepository(parts[0])
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("delivery: %q is not a preview key: %w", key, err)
|
||||
}
|
||||
if _, err := PreviewKey(repository, parts[1], parts[2]); err != nil {
|
||||
return "", "", "", fmt.Errorf("delivery: %q is not a preview key: %w", key, err)
|
||||
}
|
||||
return repository, parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
// PreviewModule is what the preview holds of one module directory: the tree sha of the directory, the digest of its
|
||||
// manifest (the key in the manifests bucket) and the hex sha256 of its lock files by path relative to it.
|
||||
type PreviewModule struct {
|
||||
Tree string `json:"tree"`
|
||||
Manifest string `json:"manifest"`
|
||||
Locks map[string]string `json:"locks,omitempty"`
|
||||
}
|
||||
|
||||
// Preview is the value of a preview key: the repository, the head and the trunk commit it was merged onto, the merge
|
||||
// commit and, for each module directory of the merge, a PreviewModule. A head that does not merge is a preview too: it
|
||||
// names the files in Conflicts and has no merge commit and no modules (issue 1445).
|
||||
type Preview struct {
|
||||
V int `json:"v"`
|
||||
Repository string `json:"repository"`
|
||||
Head string `json:"head"`
|
||||
Trunk string `json:"trunk"`
|
||||
Merge string `json:"merge,omitempty"`
|
||||
Conflicts []string `json:"conflicts,omitempty"`
|
||||
Modules map[string]PreviewModule `json:"modules,omitempty"`
|
||||
}
|
||||
|
||||
func (p Preview) check() error {
|
||||
if _, err := PreviewKey(p.Repository, p.Head, p.Trunk); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range p.Conflicts {
|
||||
if f == "" {
|
||||
return fmt.Errorf("delivery: 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("delivery: a preview of a head in conflict has no merge commit and no modules")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if p.Merge == "" {
|
||||
return fmt.Errorf("delivery: a preview names no merge commit")
|
||||
}
|
||||
for dir, m := range p.Modules {
|
||||
if dir == "" {
|
||||
return fmt.Errorf("delivery: a preview names a module with an empty directory")
|
||||
}
|
||||
if m.Tree == "" {
|
||||
return fmt.Errorf("delivery: the preview's module %q has no tree", dir)
|
||||
}
|
||||
if _, err := ManifestsKey(m.Manifest); err != nil {
|
||||
return fmt.Errorf("delivery: the preview's module %q: %w", dir, err)
|
||||
}
|
||||
for path, hash := range m.Locks {
|
||||
if path == "" || hash == "" {
|
||||
return fmt.Errorf("delivery: the lock %q of the preview's module %q has an empty path or hash", path, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodePreview is p as JSON at Version.
|
||||
func EncodePreview(p Preview) ([]byte, error) {
|
||||
if err := p.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.V = Version
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
// DecodePreview reads a preview.
|
||||
func DecodePreview(data []byte) (Preview, error) {
|
||||
var p Preview
|
||||
if err := decode(data, &p); err != nil {
|
||||
return Preview{}, err
|
||||
}
|
||||
if err := p.check(); err != nil {
|
||||
return Preview{}, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// SamePreview says whether two previews are the same value.
|
||||
func SamePreview(a, b Preview) bool { return reflect.DeepEqual(a, b) }
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021173436-07a65927c362
|
||||
# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021204452-580da61a9107
|
||||
## explicit; go 1.26.0
|
||||
git.novox.be/novox/mesh-sdk/go/asks
|
||||
git.novox.be/novox/mesh-sdk/go/catalogue
|
||||
|
||||
Reference in New Issue
Block a user