Files
mesh-controller/cmd/mesh-controller/recorded_push_test.go
T
jochen e92a3fe237
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 pass: its merge-check.sh passed
mesh/delivery delivered
Record a push that only moves recorded builds as the person's word, not a repair (hq issue 301)
A recorded build moves only by a person's push (ADR 0242), so that push is
the word its upgrade policy asks for; S15 counted it as a repair and wanted a
healer for split-dns, words and uplink-verbs. The push now reads what it
carries before it is recorded and says so in its kind, and the ten pushes of
2026-10-07 are named so their three warnings clear on the next tick.
2026-10-08 00:12:23 +02:00

125 lines
6.0 KiB
Go

package main
import (
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
// **A push says whether it pushed recorded builds only, from what it carries** (novox/hq issue 301):
// every module it moves records rather than rolls out, the machine's last send is known and its last
// report no failure. Anything else is a push by hand, as before.
func TestAPushIsOfRecordedBuildsOnlyWhenEveryBuildItMovesWasHeldForAPersonsWord(t *testing.T) {
current := map[string]inventory.CurrentBuild{
"resolver": {Commit: "r2222222"}, "nftables": {Commit: "n2222222"}, "mail": {Commit: "m2222222"},
"web": {Commit: "w2222222", RollOut: true}, "proxy": {Commit: "p1111111", RollOut: true},
}
same := func(_, a, b string) bool { return a == b }
for _, c := range []struct {
name string
modules []string
sent map[string]string
known bool
failing string
carried string // what it carried, joined; empty when it is not a push of recorded builds
why string
}{
{name: "one held build", modules: []string{"resolver", "proxy"},
sent: map[string]string{"resolver": "r1111111", "proxy": "p1111111"}, known: true,
carried: "resolver from r1111111 → r2222222"},
{name: "two held builds", modules: []string{"mail", "nftables", "proxy"},
sent: map[string]string{"mail": "m1111111", "nftables": "n1111111", "proxy": "p1111111"}, known: true,
carried: "mail from m1111111 → m2222222; nftables from n1111111 → n2222222"},
{name: "a recorded module's first build, newly assigned", modules: []string{"resolver", "proxy"},
sent: map[string]string{"proxy": "p1111111"}, known: true,
carried: "resolver (never sent it) → r2222222"},
{name: "a build that rolls out moves too", modules: []string{"resolver", "web"},
sent: map[string]string{"resolver": "r1111111", "web": "w1111111"}, known: true, why: "rolls out"},
{name: "nothing moves", modules: []string{"resolver", "proxy"},
sent: map[string]string{"resolver": "r2222222", "proxy": "p1111111"}, known: true, why: "a send again"},
{name: "a module taken off", modules: []string{"resolver"},
sent: map[string]string{"resolver": "r1111111", "proxy": "p1111111"}, known: true, why: "taken off"},
{name: "what it was last sent is not known", modules: []string{"resolver"}, why: "not known"},
{name: "its last report failed", modules: []string{"resolver"},
sent: map[string]string{"resolver": "r1111111"}, known: true, failing: "failed", why: "failed"},
} {
carried, why := recordedPush(c.modules, c.sent, c.known, current, same, c.failing)
if got := strings.Join(carried, "; "); got != c.carried {
t.Errorf("%s: carried %q, want %q (%s)", c.name, got, c.carried, why)
}
if c.why != "" && !strings.Contains(why, c.why) {
t.Errorf("%s: not of recorded builds because %q, want it to say %q", c.name, why, c.why)
}
}
}
// **The live case, as the log holds it** (novox/hq issue 301). The ten pushes of 2026-10-07 recorded
// before a push said its kind pushed recorded builds only; the three `healer-wanted` they opened clear.
// The five pushes for the settings beside them, and any push that says no kind, still count; and a push
// recorded as one of recorded builds since, repeated, never does.
func TestThePushesOfRecordedBuildsOf20261007WantNoHealer(t *testing.T) {
now := time.Date(2026, 10, 8, 9, 0, 0, 0, time.UTC)
pushed := func(id, cause string, at time.Time) link.HandAct {
return link.HandAct{ID: id, At: at, By: "operator", Verb: "push", Args: []string{"laptop"},
Why: "operator's yes", Cause: cause}
}
var acts []link.HandAct
at := now.Add(-12 * time.Hour)
causes := map[string]string{
"act-1791404241010309198-1": "split-dns", "act-1791404587689907257-1": "split-dns",
"act-1791408072745552019-1": "split-dns", "act-1791408101286318350-1": "split-dns",
"act-1791404809925218992-1": "words", "act-1791404844504887009-1": "words",
"act-1791409209593713522-1": "uplink-verbs", "act-1791409280125022264-1": "uplink-verbs",
"act-1791409336586475835-1": "uplink-verbs", "act-1791409393494198352-1": "uplink-verbs",
}
if len(causes) != len(recordedBefore) {
t.Fatalf("the live case has %d acts and the record names %d", len(causes), len(recordedBefore))
}
for id, cause := range causes {
if _, named := recordedBefore[id]; !named {
t.Fatalf("%s is not named a push of recorded builds", id)
}
acts = append(acts, pushed(id, cause, at))
at = at.Add(time.Minute)
}
for i := range 2 {
acts = append(acts, pushed("act-settings-"+string(rune('a'+i)), "push", at))
at = at.Add(time.Minute)
}
f := calm(now)
f.handActs = acts
got := watchHandActs(f)
if len(got) != 1 || got[0].ID != "hand-acts.push" {
t.Fatalf("S15 saw %+v; want only the settings pushes, counted as before", got)
}
// A push that says it carried recorded builds, repeated, wants no healer; one recorded with the same word and no
// kind still counts, as does an act of another verb whose ID such a push once had.
ofRecorded := func(at time.Time) link.HandAct {
a := pushed("", "split-dns", at)
a.Kind, a.Carried = link.KindRecordedBuilds, []string{"resolver from r1111111 → r2222222"}
return a
}
f = calm(now)
f.handActs = []link.HandAct{ofRecorded(now.Add(-26 * time.Hour)), ofRecorded(now.Add(-time.Hour)),
ofRecorded(now.Add(-time.Minute))}
if got := watchHandActs(f); len(got) != 0 {
t.Errorf("pushes of recorded builds repeated asked for a healer: %+v", got)
}
f = calm(now)
f.handActs = []link.HandAct{pushed("a", "split-dns", now.Add(-time.Hour)), pushed("b", "split-dns", now.Add(-time.Minute))}
if got := watchHandActs(f); len(got) != 1 {
t.Errorf("pushes that said no kind were not counted: %+v", got)
}
other := actOf(now.Add(-time.Hour), "plans close", "split-dns")
other.ID = "act-1791404241010309198-1"
f = calm(now)
f.handActs = []link.HandAct{other, actOf(now.Add(-time.Minute), "plans close", "split-dns")}
if got := watchHandActs(f); len(got) != 1 {
t.Errorf("another verb under such a push's ID passed for one: %+v", got)
}
}