On 2026-10-09 a release's gate on the control node read the machine's
report against a newer send another plan had just made there, failed
three builds the machine had reported healthy, and put them back on
every machine to a controller older than the store's schema; that
controller then passed the newer plan's gate from its own health.
- A gate keeps what its send carried (digest, sequence) and reads the
report against it; a report on the last send is on it too.
- A gate judges only the build the machine was last sent: another build
there supersedes the judging — no verdict, nothing put back.
- A controller is told its build (MESH_CONTROLLER_VERSION, ${version}
in a process's env) and records how far it reads the store's schema;
a put-back to a build that reaches less, or never said, is refused
and the current build kept, said as urgent.
- A release's open gate holds other sends of its modules there, and a
plan's own first send waits on it.
198 lines
8.5 KiB
Go
198 lines
8.5 KiB
Go
package inventory
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A plan is a record the mesh keeps and resumes (novox/hq ADR 0162): written whole, read back open,
|
|
// advanced, and gone from the open ones when done.
|
|
func TestAPlanIsKeptAdvancedAndResumedFromTheStore(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
p := Plan{ID: "plan-1", Repository: "novox/mesh-tools", Commit: "abc", Created: time.Now().UTC(),
|
|
State: PlanBuilding, Tiers: [][]string{{"mesh-tools"}, {"builder"}, {"shop"}},
|
|
Modules: map[string]*PlanModule{"mesh-tools": {}, "builder": {}, "shop": {}}}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
open, err := inv.OpenPlans(ctx)
|
|
if err != nil || len(open) != 1 || open[0].ID != "plan-1" || len(open[0].Tiers) != 3 {
|
|
t.Fatalf("the plan was not kept whole: %v %+v", err, open)
|
|
}
|
|
// Another controller picks it up where it was left: a tier advanced and a module built.
|
|
now := time.Now().UTC()
|
|
resumed := open[0]
|
|
resumed.Tier = 1
|
|
resumed.Modules["mesh-tools"].State = "built"
|
|
resumed.Modules["mesh-tools"].BuiltAt = &now
|
|
resumed.State = PlanRolling
|
|
resumed.Note = "tier 0 built; waiting for builder on anchor to be applied"
|
|
if err := inv.SavePlan(ctx, &resumed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
again, err := inv.PlanByID(ctx, "plan-1")
|
|
if err != nil || again.Tier != 1 || again.Modules["mesh-tools"].State != "built" || again.State != PlanRolling {
|
|
t.Fatalf("the advanced plan did not come back as left: %v %+v", err, again)
|
|
}
|
|
again.State = PlanDone
|
|
if err := inv.SavePlan(ctx, &again); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if open, _ = inv.OpenPlans(ctx); len(open) != 0 {
|
|
t.Fatalf("a done plan is not open: %+v", open)
|
|
}
|
|
if recent, _ := inv.RecentPlans(ctx, 5); len(recent) != 1 || recent[0].State != PlanDone {
|
|
t.Fatalf("a done plan is still among the recent ones: %+v", recent)
|
|
}
|
|
}
|
|
|
|
// novox/hq issue 254: a plan keeps the branch its merge went into, and a superseded plan is not open.
|
|
func TestASupersededPlanIsNotOpen(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
p := Plan{ID: "plan-1", Repository: "novox/mesh-catalog", Branch: "main", Commit: "abc",
|
|
Created: time.Now().UTC(), State: PlanBuilding, Tiers: [][]string{{"gitea"}},
|
|
Modules: map[string]*PlanModule{"gitea": {}}}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
kept, err := inv.PlanByID(ctx, "plan-1")
|
|
if err != nil || kept.Branch != "main" {
|
|
t.Fatalf("the branch was not kept: %v %+v", err, kept)
|
|
}
|
|
kept.State = PlanSuperseded
|
|
kept.Note = "superseded at tier 0 by plan-2"
|
|
if err := inv.SavePlan(ctx, &kept); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if open, err := inv.OpenPlans(ctx); err != nil || len(open) != 0 {
|
|
t.Fatalf("a superseded plan is still open: %v %+v", err, open)
|
|
}
|
|
if recent, _ := inv.RecentPlans(ctx, 5); len(recent) != 1 || recent[0].State != PlanSuperseded {
|
|
t.Fatalf("a superseded plan is not among the recent ones as superseded: %+v", recent)
|
|
}
|
|
}
|
|
|
|
// novox/hq issue 349 (review of the follow-up): the newest merge of a branch is the one merged last, whatever
|
|
// order the plans were made in, in any state; a tie is broken by the plan made last; a release, another
|
|
// branch and another repository are never it; and its time is kept to the nanosecond.
|
|
func TestTheNewestMergeOfABranchIsTheOneMergedLast(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
t0 := time.Date(2026, 10, 9, 10, 0, 0, 0, time.UTC)
|
|
save := func(id, repo, branch string, merged, created time.Time, state string, release bool) {
|
|
t.Helper()
|
|
p := Plan{ID: id, Repository: repo, Branch: branch, Commit: id + "-commit", Merged: merged, Created: created,
|
|
State: state, Tiers: [][]string{}, Modules: map[string]*PlanModule{}}
|
|
if release {
|
|
p.Release = &PlanRelease{}
|
|
}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, found, err := inv.NewestMergeOf(ctx, "novox/mesh-catalog", "main"); err != nil || found {
|
|
t.Fatalf("a branch with no plan: %v %v", found, err)
|
|
}
|
|
// Made in the other order than merged: the later merge's plan made first, and done.
|
|
save("plan-later", "novox/mesh-catalog", "main", t0.Add(2*time.Minute+250*time.Millisecond), t0, PlanDone, false)
|
|
save("plan-earlier", "novox/mesh-catalog", "main", t0.Add(time.Minute), t0.Add(5*time.Minute), PlanRolling, false)
|
|
save("plan-other-branch", "novox/mesh-catalog", "release", t0.Add(time.Hour), t0, PlanRolling, false)
|
|
save("plan-other-repo", "novox/mesh-controller", "main", t0.Add(time.Hour), t0, PlanRolling, false)
|
|
save("release-1", "novox/mesh-catalog", "main", t0.Add(time.Hour), t0, PlanRolling, true)
|
|
save("plan-unknown", "novox/mesh-catalog", "main", time.Time{}, t0.Add(time.Hour), PlanRolling, false)
|
|
p, found, err := inv.NewestMergeOf(ctx, "Novox/Mesh-Catalog", "main")
|
|
if err != nil || !found || p.ID != "plan-later" {
|
|
t.Fatalf("the newest merge: %s %v %v", p.ID, found, err)
|
|
}
|
|
if !p.Merged.Equal(t0.Add(2*time.Minute + 250*time.Millisecond)) {
|
|
t.Fatalf("its merge time was not kept to the nanosecond: %s", p.Merged)
|
|
}
|
|
// The same merge time: the plan made last.
|
|
save("plan-again", "novox/mesh-catalog", "main", t0.Add(2*time.Minute+250*time.Millisecond), t0.Add(time.Minute), PlanBuilding, false)
|
|
if p, _, _ := inv.NewestMergeOf(ctx, "novox/mesh-catalog", "main"); p.ID != "plan-again" {
|
|
t.Fatalf("one merge time, two plans: %s", p.ID)
|
|
}
|
|
}
|
|
|
|
// novox/hq issue 352: what a machine was last sent is read back by name with its sequence, a report keeps
|
|
// the declaration it was about and that declaration's sequence, and a gate's sends are kept with the plan.
|
|
func TestASendAndAReportAreKnownByTheirDeclaration(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
record, err := inv.AddNode(ctx, "anchor")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found, err := inv.SentTo(ctx, "anchor"); err != nil || found {
|
|
t.Fatalf("a machine never sent anything: %v %v", found, err)
|
|
}
|
|
if _, _, err := inv.SentTo(ctx, "nobody"); err == nil {
|
|
t.Fatal("a machine that does not exist was answered")
|
|
}
|
|
seq, err := inv.NextSequence(ctx, record.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordSent(ctx, record.ID, "d-1", map[string]string{"app": "c1"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sent, found, err := inv.SentTo(ctx, "anchor")
|
|
if err != nil || !found || sent.Digest != "d-1" || sent.Sequence != seq {
|
|
t.Fatalf("sent %+v %v %v", sent, found, err)
|
|
}
|
|
if _, err := inv.RecordOrderedDoing(ctx, record.ID, Doing{Node: "anchor", Outcome: OutcomeApplied, Declared: "d-1", Applied: 1},
|
|
ReportOrder{Sequence: seq, ReportSequence: 1}, func(ReportOrder) bool { return false }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reports, err := inv.LastReports(ctx)
|
|
if err != nil || len(reports) != 1 || reports[0].Declared != "d-1" || reports[0].ReportedSequence != seq || !reports[0].Current {
|
|
t.Fatalf("reports %+v %v", reports, err)
|
|
}
|
|
// Sent again, unreported: the report is no longer on the last send, and is still on the first.
|
|
if err := inv.RecordSent(ctx, record.ID, "d-2", map[string]string{"app": "c1"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reports, _ = inv.LastReports(ctx)
|
|
if reports[0].Current || !sent.ReportsOn(reports[0]) {
|
|
t.Fatalf("after a newer send: %+v", reports[0])
|
|
}
|
|
at := time.Now().UTC()
|
|
p := Plan{ID: "plan-352", Repository: "novox/x", Commit: "c", Created: at, State: PlanRolling, Tiers: [][]string{{"app"}},
|
|
Modules: map[string]*PlanModule{"app": {Gate: &PlanGate{Machines: []string{"anchor"}, Since: &at,
|
|
Sent: map[string]SentDeclaration{"anchor": sent}}}}}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
kept, err := inv.PlanByID(ctx, "plan-352")
|
|
if err != nil || kept.Modules["app"].Gate.Sent["anchor"] != sent {
|
|
t.Fatalf("the gate's send was not kept with the plan: %+v %v", kept.Modules["app"].Gate, err)
|
|
}
|
|
}
|
|
|
|
// A controller build's reach of the store's schema is kept by its version, and the store's own is read.
|
|
func TestASchemaReachIsKeptByBuild(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
applied, err := inv.SchemaApplied(ctx)
|
|
if err != nil || applied < 87 {
|
|
t.Fatalf("applied %d %v", applied, err)
|
|
}
|
|
if _, known, err := inv.SchemaReachOf(ctx, "ad62528c47c7"); err != nil || known {
|
|
t.Fatalf("an unrecorded build: %v %v", known, err)
|
|
}
|
|
if err := inv.RecordSchemaReach(ctx, "", 87); err == nil {
|
|
t.Fatal("a reach without a build was recorded")
|
|
}
|
|
if err := inv.RecordSchemaReach(ctx, "ad62528c47c7", 86); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordSchemaReach(ctx, "ad62528c47c7", 87); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reach, known, err := inv.SchemaReachOf(ctx, "ad62528c47c7"); err != nil || !known || reach != 87 {
|
|
t.Fatalf("reach %d %v %v", reach, known, err)
|
|
}
|
|
}
|