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

This commit is contained in:
2026-10-21 22:47:04 +02:00
parent 47e1819101
commit b9ec1919b8
12 changed files with 313 additions and 12 deletions
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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) }
+1 -1
View File
@@ -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