A controller restart lost every call's outcome, `status` composed the mesh while its caller waited (18.6s live on 2026-10-06, past the 10s window), a repair by hand left no trace, and the core's bounds had nothing measured to be set from. - calls: kept in the controller's bucket mesh-controller_calls (last 1000 or 14 days, answers bounded to 64 KiB), read by id across a restart; a controller starting marks a stopped one's running calls abandoned; each call names its caller from the inbox its answer goes to. - status: the serving controller composes it at start, after news from a machine, a build or an acting verb, and every minute; the verb answers the last composition at once with when and how long it took. Composing resolves each machine once instead of twice. - hand-act log in mesh-controller_hand-acts: push (required through the seat), plans stop/close, broker consumer-reset and the new hand-act record take --why/--cause/--condition; `hand-acts` lists them and repeated causes; status counts the week's. - durations (migration 0066): apply (send to first report), heartbeat gap, plan tier and build, recorded as heard; `durations` summarises them. - the controller's seat row takes this binary's definition of its own verbs, so the console no longer judges calls against an older build's schema. - the controller is granted its two buckets' subjects.
100 lines
4.6 KiB
Go
100 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// novox/hq issue 254, ADR 0218: a newer plan takes over what the older open plans of its repository
|
|
// and branch had not built, and closes them as superseded; another repository's plan, another
|
|
// branch's, and a plan made after it are left alone.
|
|
func TestANewerPlanSupersedesTheOlderOpenPlansOfItsRepository(t *testing.T) {
|
|
at := time.Date(2026, 10, 5, 12, 0, 0, 0, time.UTC)
|
|
sent := at.Add(time.Minute)
|
|
plan := func(id, repository, branch string, created time.Time, modules map[string]*inventory.PlanModule) inventory.Plan {
|
|
return inventory.Plan{ID: id, Repository: repository, Branch: branch, Commit: id + "-commit",
|
|
Created: created, State: inventory.PlanRolling, Modules: modules}
|
|
}
|
|
older := plan("plan-1", "novox/mesh-catalog", "main", at, map[string]*inventory.PlanModule{
|
|
"gitea": {State: "built", SentAt: &sent}, // done with: stays done
|
|
"keycloak": {State: "asked"}, // asked, not answered: folded
|
|
"plex": {}, // not yet asked: folded
|
|
"agent": {State: "built"}, // built, rolls out, not sent: folded
|
|
"notes": {State: "built"}, // built, records: nothing to send
|
|
})
|
|
stuck := plan("plan-0", "Novox/Mesh-Catalog", "", at.Add(-time.Hour), map[string]*inventory.PlanModule{
|
|
"runtime": {State: "asked"},
|
|
})
|
|
other := plan("plan-2", "novox/mesh-controller", "main", at, map[string]*inventory.PlanModule{"mesh-controller": {}})
|
|
release := plan("plan-3", "novox/mesh-catalog", "release", at, map[string]*inventory.PlanModule{"lemurs": {}})
|
|
later := plan("plan-5", "novox/mesh-catalog", "main", at.Add(2*time.Hour), map[string]*inventory.PlanModule{"later": {}})
|
|
done := plan("plan-6", "novox/mesh-catalog", "main", at, map[string]*inventory.PlanModule{"finished": {}})
|
|
done.State = inventory.PlanDone
|
|
|
|
newer := plan("plan-4", "novox/mesh-catalog", "main", at.Add(time.Hour), nil)
|
|
newer.Commit = "97b1b2b0c0ffee"
|
|
rollsOut := func(m string) bool { return m != "notes" }
|
|
folded, closed := supersededBy(newer, []inventory.Plan{stuck, older, other, release, later, done, newer}, rollsOut)
|
|
|
|
if want := []string{"agent", "keycloak", "plex", "runtime"}; !reflect.DeepEqual(folded, want) {
|
|
t.Fatalf("folded %v, wanted %v", folded, want)
|
|
}
|
|
var ids []string
|
|
for _, p := range closed {
|
|
ids = append(ids, p.ID)
|
|
if p.State != inventory.PlanSuperseded || p.Open() {
|
|
t.Errorf("%s was left %s", p.ID, p.State)
|
|
}
|
|
if !strings.Contains(p.Note, "plan-4") || !strings.Contains(p.Note, "97b1b2b0") {
|
|
t.Errorf("%s does not name the plan that superseded it: %q", p.ID, p.Note)
|
|
}
|
|
}
|
|
if want := []string{"plan-0", "plan-1"}; !reflect.DeepEqual(ids, want) {
|
|
t.Fatalf("superseded %v, wanted %v — another repository, another branch, a later plan and a "+
|
|
"finished one are left alone", ids, want)
|
|
}
|
|
if other.State != inventory.PlanRolling {
|
|
t.Fatal("the plan handed in was changed in place")
|
|
}
|
|
if line := planLine(closed[1], time.Now()); !strings.Contains(line, "superseded") {
|
|
t.Fatalf("a superseded plan reads %q", line)
|
|
}
|
|
}
|
|
|
|
// novox/hq issue 254: a person closes a plan that will not move again, by its id.
|
|
func TestAPersonClosesAStuckPlan(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
stuck := inventory.Plan{ID: "plan-97b1b2b", Repository: "novox/mesh-catalog", Commit: "97b1b2b",
|
|
Created: time.Now().UTC(), State: inventory.PlanRolling, Tier: 1, Tiers: [][]string{{"a"}, {"b"}},
|
|
Modules: map[string]*inventory.PlanModule{"a": {State: "built"}, "b": {}}}
|
|
if err := open.inventory.SavePlan(ctx, stuck); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := plansCommand(ctx, []string{"close", stuck.ID}); err == nil || !strings.Contains(err.Error(), "--why") {
|
|
t.Fatalf("a plan was closed by hand without saying why: %v", err)
|
|
}
|
|
if err := plansCommand(ctx, []string{"close", stuck.ID, "--why", "its report will not come"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
closed, err := open.inventory.PlanByID(ctx, stuck.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if closed.State != inventory.PlanFailed || !strings.Contains(closed.Note, "closed by hand") ||
|
|
!strings.Contains(closed.Note, "its report will not come") {
|
|
t.Fatalf("the plan was left %s: %q", closed.State, closed.Note)
|
|
}
|
|
if err := plansCommand(ctx, []string{"close", stuck.ID, "--why", "again"}); err == nil {
|
|
t.Fatal("a plan already closed was closed again")
|
|
}
|
|
if argv, err := argvFor("plans", map[string]any{"close": stuck.ID, "why": "w"}); err != nil ||
|
|
!reflect.DeepEqual(argv, []string{"plans", "close", stuck.ID, "--why", "w"}) {
|
|
t.Fatalf("the seat's verb does not close a plan: %v %v", argv, err)
|
|
}
|
|
}
|