The view's reach carries the reached head and each change's source revision; nothing records a merge as delivered (ADR 0556 v8)
This commit is contained in:
@@ -77,8 +77,8 @@ func deliveryVerbs() []Verb {
|
||||
deliveryRemovedInR6("retire-history"),
|
||||
{Name: "settle", Description: "Ask the operator to settle deliveries held because their merge reached the " +
|
||||
"trunk without a passing check: one ask on the operator's channel for all of them, as an Approve is asked. " +
|
||||
"The verb moves nothing; on the operator's answer each goes to merged, and to delivered once every node " +
|
||||
"runs a build containing it (novox/hq ADR 0556). Refused for a delivery not held, or held for another " +
|
||||
"The verb moves nothing; on the operator's answer each goes to merged, and whether it is on the machines " +
|
||||
"is derived as for any merge (novox/hq ADR 0556). Refused for a delivery not held, or held for another " +
|
||||
"reason, naming why.",
|
||||
Input: map[string]any{"type": "object", "additionalProperties": false, "required": []string{"ids", "why"},
|
||||
"properties": map[string]any{
|
||||
|
||||
+20
-7
@@ -9,10 +9,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// How far a merge reached (novox/hq ADR 0556, issue 1280). The indexer says, per repository, the last merge it took in
|
||||
// (TakenBucket); the delivery view derives, per repository, how far each of its modules reached on the nodes that want it
|
||||
// (ReachBucket); the gate, the one writer of a delivery's record, watches the view's state and moves a merged delivery to
|
||||
// delivered when every module its merge changed runs, on every node that wants it, at a build containing the merge.
|
||||
// How far a repository reached (novox/hq ADR 0556, issue 1280). The indexer says, per repository, the last merge it took in
|
||||
// (TakenBucket); the delivery view keeps, per repository, how far each of its modules reached on the nodes that want it, and
|
||||
// derives from it the repository's reached head (ReachBucket): the newest head of the repository at which every wanted
|
||||
// module runs, on every node, a build at or after its last change. A merge at or before the reached head is on the
|
||||
// machines. Nothing records a merge as delivered (ADR 0429 §1): the gate and the tracker derive it from this state.
|
||||
|
||||
// TakenBucket is the indexer's state `taken`: one key per repository (RepositoryKey), the last merge it took in.
|
||||
const TakenBucket = IndexerModule + "_taken"
|
||||
@@ -63,12 +64,18 @@ func DecodeTaken(data []byte) (Taken, error) {
|
||||
|
||||
// Reach is the value of a reach key: how far the modules of one repository reached on the nodes. Indexed is the last
|
||||
// merge the indexer took in for it (Taken.Merge), empty while it took in none. A repository the indexer holds no module
|
||||
// of (a library) has no Modules.
|
||||
// of (a library) has no Modules. Reached is the reached head: the newest head of the repository in the source's history,
|
||||
// not past Indexed, at which every module with a node that wants it has its floor at or after its last change at or before
|
||||
// that head; ReachedSrc is the source revision that named it. Holds says, in words, what keeps the next head of the
|
||||
// repository from being reached (a module on a node, the indexer's lag); empty when the repository's newest head is reached.
|
||||
type Reach struct {
|
||||
V int `json:"v"`
|
||||
Repository string `json:"repository"`
|
||||
Indexed string `json:"indexed,omitempty"`
|
||||
Modules map[string]ModuleReach `json:"modules,omitempty"`
|
||||
Reached string `json:"reached,omitempty"`
|
||||
ReachedSrc uint64 `json:"reached_src,omitempty"`
|
||||
Holds []string `json:"holds,omitempty"`
|
||||
}
|
||||
|
||||
// ModuleReach is how far one module reached. FP is its fingerprint at the indexer now; Nodes how many nodes want it
|
||||
@@ -84,10 +91,12 @@ type ModuleReach struct {
|
||||
Waiting string `json:"waiting,omitempty"`
|
||||
}
|
||||
|
||||
// Change is one fingerprint of a module and the merge commit it first appeared at (Module.Commit).
|
||||
// Change is one fingerprint of a module, the merge commit it first appeared at (Module.Commit), and the source revision
|
||||
// that named that commit as the repository's head (Module.Src, or the source's history), 0 when not known.
|
||||
type Change struct {
|
||||
Commit string `json:"commit"`
|
||||
FP string `json:"fp"`
|
||||
Src uint64 `json:"src,omitempty"`
|
||||
}
|
||||
|
||||
// MostChanges is how many changes of a module a reach value keeps: as many revisions as the indexer's modules bucket keeps.
|
||||
@@ -97,6 +106,9 @@ func (r Reach) check() error {
|
||||
if r.Repository == "" {
|
||||
return fmt.Errorf("delivery: a reach value names no repository")
|
||||
}
|
||||
if (r.Reached == "") != (r.ReachedSrc == 0) {
|
||||
return fmt.Errorf("delivery: the reach of %s names its reached head and its source revision together", r.Repository)
|
||||
}
|
||||
for name, m := range r.Modules {
|
||||
if name == "" {
|
||||
return fmt.Errorf("delivery: a reach value names a module with no name")
|
||||
@@ -139,7 +151,8 @@ func DecodeReach(data []byte) (Reach, error) {
|
||||
|
||||
// SameReach says whether two reach values are the same, so the view writes only when they differ.
|
||||
func SameReach(a, b Reach) bool {
|
||||
if a.Repository != b.Repository || a.Indexed != b.Indexed || len(a.Modules) != len(b.Modules) {
|
||||
if a.Repository != b.Repository || a.Indexed != b.Indexed || len(a.Modules) != len(b.Modules) ||
|
||||
a.Reached != b.Reached || a.ReachedSrc != b.ReachedSrc || !slices.Equal(a.Holds, b.Holds) {
|
||||
return false
|
||||
}
|
||||
for _, name := range slices.Sorted(maps.Keys(a.Modules)) {
|
||||
|
||||
@@ -325,12 +325,16 @@ func TestSourceValuesRefused(t *testing.T) { // 1200/AC4
|
||||
r.Modules["gitea"] = m
|
||||
_, err = EncodeReach(r)
|
||||
wantErr("reach keeping too many changes", err, "more than")
|
||||
r = sampleReach()
|
||||
r.ReachedSrc = 0
|
||||
_, err = EncodeReach(r)
|
||||
wantErr("reach with a head and no revision", err, "reached head")
|
||||
}
|
||||
|
||||
// sampleReach is a reach value of one repository with a module at its floor and a library-like empty one.
|
||||
func sampleReach() Reach {
|
||||
return Reach{V: Version, Repository: "novox/mesh-catalog", Indexed: "cccc", Modules: map[string]ModuleReach{
|
||||
"gitea": {FP: "src2:a", Nodes: 1, Floor: "cccc", Changes: []Change{{Commit: "cccc", FP: "src2:a"}, {Commit: "bbbb", FP: "src2:b"}}},
|
||||
return Reach{V: Version, Repository: "novox/mesh-catalog", Indexed: "cccc", Reached: "cccc", ReachedSrc: 9, Modules: map[string]ModuleReach{
|
||||
"gitea": {FP: "src2:a", Nodes: 1, Floor: "cccc", Changes: []Change{{Commit: "cccc", FP: "src2:a", Src: 9}, {Commit: "bbbb", FP: "src2:b", Src: 7}}},
|
||||
"zsh": {FP: "src2:z", Changes: []Change{{Commit: "aaaa", FP: "src2:z"}}},
|
||||
}}
|
||||
}
|
||||
@@ -373,6 +377,8 @@ func TestSourceSame(t *testing.T) { // 1200/AC5
|
||||
"floor": func(r *Reach) { m := r.Modules["gitea"]; m.Floor = "bbbb"; r.Modules["gitea"] = m },
|
||||
"changes": func(r *Reach) { m := r.Modules["zsh"]; m.Changes = nil; r.Modules["zsh"] = m },
|
||||
"modules": func(r *Reach) { delete(r.Modules, "zsh") },
|
||||
"reached": func(r *Reach) { r.Reached, r.ReachedSrc = "bbbb", 7 },
|
||||
"holds": func(r *Reach) { r.Holds = []string{"gitea on ace does not yet run src2:a"} },
|
||||
} {
|
||||
r := sampleReach()
|
||||
change(&r)
|
||||
|
||||
Reference in New Issue
Block a user