mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestReplay336 (1.12s)
mesh/delivery superseded: a newer head of the same pull request
The condition and the plan named the verb in command-line form; the operator reaches it through the mesh MCP server, so it is written as that call. A rebuild of the same source moves nothing (issue 280), which the plan cannot know before the build, so its line says so.
169 lines
5.8 KiB
Go
169 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// The change plan (novox/hq ADR 0238): **one commit, one plan** — what a change does to the mesh, computed
|
|
// from its diffset (a repository, the branch it merges into, the commit at hand) by the planner, never by
|
|
// a mapping of its own. reachOfMerge answers what moves and what follows it; this adds where each module
|
|
// goes — the deploy plan, machine by machine in the build plan's order — and what is not an ordinary
|
|
// send. A pull request's check posts it with its verdict; the release a merge makes follows the same
|
|
// planner, so the two can be compared.
|
|
|
|
// policyOf is a module's upgrade policy, as the controller holds it; false when it cannot be read.
|
|
type policyOf func(module string) (inventory.Upgrade, bool)
|
|
|
|
// changePlanOf is the change plan of a reach: pure, so it is tested without a store.
|
|
func changePlanOf(repository, base, head string, r mergeReach, entries []inventory.Entry, policy policyOf) link.ChangePlan {
|
|
p := link.ChangePlan{Repository: repository, Base: base, Head: head, Moved: r.Moved(), Dependents: r.Dependents(),
|
|
New: r.Added, Unread: r.Unread, Tiers: r.Plan.Tiers}
|
|
byName := map[string]inventory.Entry{}
|
|
for _, e := range entries {
|
|
byName[e.Manifest.Module] = e
|
|
}
|
|
machines := map[string]*link.MachinePlan{}
|
|
machine := func(name string) *link.MachinePlan {
|
|
if machines[name] == nil {
|
|
machines[name] = &link.MachinePlan{Machine: name}
|
|
}
|
|
return machines[name]
|
|
}
|
|
for _, tier := range r.Plan.Tiers {
|
|
for _, name := range tier {
|
|
e, held := byName[name]
|
|
if !held {
|
|
continue
|
|
}
|
|
u, known := policy(name)
|
|
waits := known && !u.RollOut
|
|
for _, on := range e.On {
|
|
if waits {
|
|
machine(on).Waits = append(machine(on).Waits, name)
|
|
} else {
|
|
machine(on).Receives = append(machine(on).Receives, name)
|
|
}
|
|
}
|
|
switch {
|
|
case (name == "nats" || catalogue.ProvidesBus(e.Manifest)) && len(e.On) > 0:
|
|
// Said before the merge (novox/hq issue 336): a new bus build holds every send to the bus's machine
|
|
// until a person takes the step, so merging it is a promise to take it.
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s: %s is never sent by an ordinary send, and until %s runs, "+
|
|
"nothing else is sent to %s either — unless the new build turns out the same as the one running there "+
|
|
"(issue 280)", busUpgradeNeeded, name, busUpgradeVerb, strings.Join(e.On, ", ")))
|
|
case name == "nats" || catalogue.ProvidesBus(e.Manifest):
|
|
p.Steps = append(p.Steps, "a planned bus step: the bus is upgraded by `bus upgrade`, never by an ordinary send")
|
|
case waits && len(e.On) > 0:
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s waits for a person: its policy records (%s)", name,
|
|
orNone(u.From)))
|
|
}
|
|
if len(e.Manifest.Provides) > 0 && len(e.On) > 0 {
|
|
var offers []string
|
|
for _, o := range e.Manifest.Provides {
|
|
offers = append(offers, o.Name)
|
|
}
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s provides %s: its consumers are sent again after it",
|
|
name, strings.Join(offers, ", ")))
|
|
}
|
|
if e.Manifest.Data != nil && len(e.On) > 0 {
|
|
p.Steps = append(p.Steps, fmt.Sprintf("%s keeps data (ADR 0233): what it holds is backed up before it moves", name))
|
|
}
|
|
}
|
|
}
|
|
var names []string
|
|
for name := range machines {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
p.Machines = append(p.Machines, *machines[name])
|
|
}
|
|
p.Summary = summaryOf(p)
|
|
return p
|
|
}
|
|
|
|
// busUpgradeNeeded is how a change plan says that merging it holds the bus's machine for a person's step.
|
|
const busUpgradeNeeded = "merging this needs a person's bus upgrade"
|
|
|
|
// summaryOf is a change plan in one line: what it builds, where it goes, and whether the bus moves.
|
|
func summaryOf(p link.ChangePlan) string {
|
|
if len(p.Moved) == 0 && len(p.New) == 0 {
|
|
return "builds nothing: the change touches no module of the mesh's graph"
|
|
}
|
|
var parts []string
|
|
what := strings.Join(p.Moved, ", ")
|
|
if len(p.Dependents) > 0 {
|
|
what += fmt.Sprintf(" (+%d dependent(s))", len(p.Dependents))
|
|
}
|
|
if len(p.New) > 0 {
|
|
if what != "" {
|
|
what += ", "
|
|
}
|
|
what += "new: " + strings.Join(p.New, ", ")
|
|
}
|
|
var to []string
|
|
for _, m := range p.Machines {
|
|
if len(m.Receives) > 0 {
|
|
to = append(to, m.Machine)
|
|
}
|
|
}
|
|
if len(to) > 0 {
|
|
parts = append(parts, "builds "+what+" → "+strings.Join(to, ", "))
|
|
} else {
|
|
parts = append(parts, "builds "+what+", sent nowhere")
|
|
}
|
|
bus := "no bus step"
|
|
for _, s := range p.Steps {
|
|
switch {
|
|
case strings.HasPrefix(s, busUpgradeNeeded):
|
|
bus = busUpgradeNeeded
|
|
case strings.HasPrefix(s, "a planned bus step") && bus == "no bus step":
|
|
bus = "a bus step"
|
|
}
|
|
}
|
|
parts = append(parts, bus)
|
|
waiting := 0
|
|
for _, m := range p.Machines {
|
|
waiting += len(m.Waits)
|
|
}
|
|
if waiting > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d wait(s) for a person", waiting))
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
// planText is a change plan as a person reads it, for the pull request's comment.
|
|
func planText(p link.ChangePlan) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "change plan of %s at %.8s into %s: %s\n", p.Repository, p.Head, p.Base, p.Summary)
|
|
for i, tier := range p.Tiers {
|
|
fmt.Fprintf(&b, " tier %d: %s\n", i, strings.Join(tier, ", "))
|
|
}
|
|
for _, m := range p.Machines {
|
|
line := " " + m.Machine + ": "
|
|
if len(m.Receives) > 0 {
|
|
line += "receives " + strings.Join(m.Receives, ", ")
|
|
}
|
|
if len(m.Waits) > 0 {
|
|
if len(m.Receives) > 0 {
|
|
line += "; "
|
|
}
|
|
line += "waits for a person: " + strings.Join(m.Waits, ", ")
|
|
}
|
|
b.WriteString(line + "\n")
|
|
}
|
|
for _, s := range p.Steps {
|
|
b.WriteString(" - " + s + "\n")
|
|
}
|
|
if len(p.Unread) > 0 {
|
|
fmt.Fprintf(&b, " read by no module's build: %s\n", strings.Join(p.Unread, ", "))
|
|
}
|
|
return b.String()
|
|
}
|