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:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-controller
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
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
|
||||||
github.com/jackc/pgx/v5 v5.10.0
|
github.com/jackc/pgx/v5 v5.10.0
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5 h1:E5stfIeCN6uYg
|
|||||||
git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE=
|
git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5/go.mod h1:4wyoCr6IIIIarEUuROThWDj7DAvJvIidqMjWcpSnOwE=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021173436-07a65927c362 h1:Ud/YHmhlm0qdB45LyDvxq7l4x6Bg2Ye0siHK5JFE+kk=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021173436-07a65927c362 h1:Ud/YHmhlm0qdB45LyDvxq7l4x6Bg2Ye0siHK5JFE+kk=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021173436-07a65927c362/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021173436-07a65927c362/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021204452-580da61a9107 h1:29NSUkDw7cIEwNcE09JZ9ASSD+cwBDoWRN3vWHKvsoQ=
|
||||||
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021204452-580da61a9107/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func ChainBuckets() []string { return []string{BuildsBucket, RolloutsBucket, Ass
|
|||||||
// ControllerMirrors are the local names of the controller's buckets a module may read: the record mirrors and the
|
// ControllerMirrors are the local names of the controller's buckets a module may read: the record mirrors and the
|
||||||
// chain's buckets.
|
// chain's buckets.
|
||||||
func ControllerMirrors() []string {
|
func ControllerMirrors() []string {
|
||||||
return []string{MirrorAssignments, MirrorDeclaredConfig, MirrorPredefined, ChainBuilds, ChainRollouts, ChecksRunning}
|
return []string{MirrorAssignments, MirrorDeclaredConfig, MirrorPredefined, ChainBuilds, ChainRollouts, ChecksRunning, PlansBuilding}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MirrorsWrittenByAnother are the controller's mirrors whose writer is not the controller, each with its reason: the rule
|
// MirrorsWrittenByAnother are the controller's mirrors whose writer is not the controller, each with its reason: the rule
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package broker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Issue 1687 (ADR 0483 clause 2): a module whose manifest reads mesh-controller.plans-building may name it in `reads`, and
|
||||||
|
// reads the bucket and writes none; the controller writes it, so it is no mirror written by another.
|
||||||
|
func TestAModuleReadingPlansBuildingReadsAndWritesNone(t *testing.T) {
|
||||||
|
if !slices.Contains(ControllerMirrors(), "plans-building") || BucketName(ControllerSeat, PlansBuilding) != PlansBuildingBucket {
|
||||||
|
t.Fatalf("a module cannot name %s in `reads`: %v", PlansBuildingBucket, ControllerMirrors())
|
||||||
|
}
|
||||||
|
if _, other := MirrorsWrittenByAnother[PlansBuilding]; other {
|
||||||
|
t.Error("the controller writes the plans building itself")
|
||||||
|
}
|
||||||
|
perms, err := PermissionsFor(Principal{Kind: KindModule, Node: "one", Module: "mesh-delivery-checker", PasswordHash: "x",
|
||||||
|
Reads: []string{"mesh-controller.plans-building"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
stream := "KV_" + PlansBuildingBucket
|
||||||
|
has(t, perms.Publish, "$JS.API.STREAM.INFO."+stream)
|
||||||
|
has(t, perms.Publish, "$JS.API.DIRECT.GET."+stream+".>")
|
||||||
|
for _, p := range perms.Publish {
|
||||||
|
if SubjectsOverlap(p, "$KV."+PlansBuildingBucket+".plan-1") {
|
||||||
|
t.Errorf("the reading module may write %s through %q", PlansBuildingBucket, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,7 +177,7 @@ var WritersTable = []WriterRow{
|
|||||||
KeptIn: "key-value " + CheckRunningBucket + ", aged out when not renewed", Others: "the build seat's holders read it to drop a duplicate request",
|
KeptIn: "key-value " + CheckRunningBucket + ", aged out when not renewed", Others: "the build seat's holders read it to drop a duplicate request",
|
||||||
Subjects: kvOf(CheckRunningBucket), Writes: holdsTheBuildSeat,
|
Subjects: kvOf(CheckRunningBucket), Writes: holdsTheBuildSeat,
|
||||||
Shared: "every machine holding the build seat puts the key of the check it runs and deletes it when the check ends"},
|
Shared: "every machine holding the build seat puts the key of the check it runs and deletes it when the check ends"},
|
||||||
{State: "a delivery plan in its build phase", Writer: "controller (lease holder), at the first build's ask and at each refresh",
|
{State: "the plans-building mirror", Writer: "controller (lease holder), at the first build's ask and at each refresh",
|
||||||
KeptIn: "key-value " + PlansBuildingBucket + ", aged out when not refreshed",
|
KeptIn: "key-value " + PlansBuildingBucket + ", aged out when not refreshed",
|
||||||
Others: "the build seat's holders read it and take no new check while a key is there (ADR 0483)",
|
Others: "the build seat's holders read it and take no new check while a key is there (ADR 0483)",
|
||||||
Subjects: kvOf(PlansBuildingBucket), Writes: isController},
|
Subjects: kvOf(PlansBuildingBucket), Writes: isController},
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ var designRows = []string{
|
|||||||
"a pull request's check asked",
|
"a pull request's check asked",
|
||||||
"a pull request's check running",
|
"a pull request's check running",
|
||||||
// The plans in their build phase, a bucket the build seat's holders read (ADR 0483 clause 2, issue 1687).
|
// The plans in their build phase, a bucket the build seat's holders read (ADR 0483 clause 2, issue 1687).
|
||||||
"a delivery plan in its build phase",
|
"the plans-building mirror",
|
||||||
"a merge announced",
|
"a merge announced",
|
||||||
"a pull request's head announced",
|
"a pull request's head announced",
|
||||||
"a pull request's merge check",
|
"a pull request's merge check",
|
||||||
|
|||||||
Vendored
+1
-1
@@ -9,7 +9,7 @@ a second place to clean besides the catalogue itself.
|
|||||||
|
|
||||||
mesh-catalog e0bc919d4945bb5f13f971f74643a0b714cf45da modules/*/module.json, modules/nats/Dockerfile
|
mesh-catalog e0bc919d4945bb5f13f971f74643a0b714cf45da modules/*/module.json, modules/nats/Dockerfile
|
||||||
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
|
mesh-host 9d9e3a46c4f5943cfffb413d962ce48b75e2fd39 examples/foundation-first-node.lock
|
||||||
mesh-sdk 07a65927c3620390e191cb6ccc97d3770f284526 conformance/events/module-event.json
|
mesh-sdk 580da61a9107f74fbef33388ece982e1b9a2e483 conformance/events/module-event.json
|
||||||
|
|
||||||
To move them, from this repository's root, with the two repositories checked out beside it:
|
To move them, from this repository's root, with the two repositories checked out beside it:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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
|
// 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
|
// its checks and the passing verdicts it keeps per package and level. The checker writes them from R4c, dark, and
|
||||||
// forge bridge writes the status `mesh/check` from a verdict (unrequired until R4f), the gate reads it from R4f, and
|
// writes the status `mesh/check` it decides as an owed act in its own bucket (CheckerOwedBucket, the gate's owed shape);
|
||||||
// the view and the watchman read them (a running check carries a pulse).
|
// 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
|
// **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
|
// 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 {
|
type LevelResult struct {
|
||||||
Run int `json:"run"`
|
Run int `json:"run"`
|
||||||
Kept int `json:"kept"`
|
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,
|
// 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"`
|
Took Took `json:"took,omitzero"`
|
||||||
Machines []string `json:"machines,omitempty"`
|
Machines []string `json:"machines,omitempty"`
|
||||||
SDK string `json:"sdk,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 {
|
func (v Verdict) check() error {
|
||||||
@@ -141,10 +189,23 @@ func (v Verdict) check() error {
|
|||||||
if err := checkLevel(level); err != nil {
|
if err := checkLevel(level); err != nil {
|
||||||
return err
|
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)
|
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,
|
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} {
|
"integration-setup": v.Took.IntegrationSetup, "integration-run": v.Took.IntegrationRun, "composition": v.Took.Composition} {
|
||||||
if ms < 0 {
|
if ms < 0 {
|
||||||
|
|||||||
+26
-1
@@ -28,6 +28,18 @@ const (
|
|||||||
AnswersHistory = 5
|
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.
|
// AskKindMerge is the kind of the gate's ask for a group's merge, the first part of its key.
|
||||||
const AskKindMerge = "merge"
|
const AskKindMerge = "merge"
|
||||||
|
|
||||||
@@ -131,6 +143,16 @@ func AskingID(askKey string, asking int) string {
|
|||||||
return AskID(askKey + "." + strconv.Itoa(asking))
|
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) {
|
func checkOwedAct(act string) (kind, context string, err error) {
|
||||||
kind, context, hasContext := strings.Cut(act, "=")
|
kind, context, hasContext := strings.Cut(act, "=")
|
||||||
switch kind {
|
switch kind {
|
||||||
@@ -138,7 +160,7 @@ func checkOwedAct(act string) (kind, context string, err error) {
|
|||||||
if !hasContext {
|
if !hasContext {
|
||||||
return "", "", fmt.Errorf("delivery: the owed act %q is a status without its context, status=<context>", act)
|
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 "", "", err
|
||||||
}
|
}
|
||||||
return kind, context, nil
|
return kind, context, nil
|
||||||
@@ -345,6 +367,9 @@ func (o Owed) check() error {
|
|||||||
if o.Context == "" {
|
if o.Context == "" {
|
||||||
return fmt.Errorf("delivery: an owed status has no context")
|
return fmt.Errorf("delivery: an owed status has no context")
|
||||||
}
|
}
|
||||||
|
if err := checkStatusContext(o.Context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if o.Target == "" {
|
if o.Target == "" {
|
||||||
return fmt.Errorf("delivery: an owed status has no 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
|
## explicit; go 1.26.0
|
||||||
git.novox.be/novox/mesh-sdk/go/asks
|
git.novox.be/novox/mesh-sdk/go/asks
|
||||||
git.novox.be/novox/mesh-sdk/go/catalogue
|
git.novox.be/novox/mesh-sdk/go/catalogue
|
||||||
|
|||||||
Reference in New Issue
Block a user