//go:build integration package main import ( "context" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) // ADR 0501 §6 (issue 1851, A5): the live case of 2026-10-07 (novox/hq issue 300), on keys. A merge of the catalogue adds // one module's directory and touches nothing else the mesh holds: nothing asks its first build, it opens no plan, and // the module is registered from the builder's first done key of it, assigned nowhere, with no build request written. func TestAMergeAddingAModuleEndsRegisteredFromItsKeyAndAsksNoBuild(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory noBuildAsked(t) b := aBuilderOnTheBus(t) if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "networkmanager", Version: "1"}, inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/networkmanager", Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil { t.Fatal(err) } watching, stop := context.WithCancel(context.Background()) t.Cleanup(stop) watchNewModules(watching, open) const commit = "3da80a4b00000000" m := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: commit, Paths: []string{"modules/systemd-resolved/module.json", "modules/systemd-resolved/cmd/main.go"}, ModuleDirs: []string{"modules/systemd-resolved"}, ModuleDirsSaid: true} if err := (following{open: open}).SourceMoved(ctx, m); err != nil { t.Fatal(err) } if plans, err := inv.OpenPlans(ctx); err != nil || len(plans) != 0 { t.Fatalf("a merge moving nothing the mesh holds opened a plan: %+v %v", plans, err) } // The indexer writes the module's trunk value; the builder builds it, then its key is done. fp := fpOf('7') manifest := b.indexed(t, "systemd-resolved", "1", "novox/mesh-catalog", "modules/systemd-resolved", commit, fp) b.built(t, "systemd-resolved", "1", fp, manifest, sdkdelivery.BuildBuilding) time.Sleep(300 * time.Millisecond) if _, known := inventoryHolds(t, inv, "systemd-resolved"); known { t.Fatal("0501 §1: registered from a key still building") } if obs := newModuleObservations(time.Now().Add(time.Hour)); len(obs) != 1 || obs[0].Kind != kindNewModuleWaits { t.Fatalf("0501 §6: the new module waiting on its key past its bound is named by %+v", obs) } b.built(t, "systemd-resolved", "1", fp, manifest, sdkdelivery.BuildDone) becomes(t, 10*time.Second, "the new module registered", func() bool { _, known := inventoryHolds(t, inv, "systemd-resolved") return known }) // The take-in forgets the wait just after the registration it made is seen. becomes(t, 5*time.Second, "the registered module no longer named waiting", func() bool { return len(newModuleObservations(time.Now().Add(time.Hour))) == 0 }) requests, err := inv.RequestsNamed(ctx, inventory.BuildRequest{Repository: "novox/mesh-catalog", Path: "modules/systemd-resolved"}.Name()) if err != nil || len(requests) != 0 { t.Fatalf("0501 §6: a build request was written: %+v (%v)", requests, err) } } // ADR 0501 §7: a new module whose manifest uses a word this controller does not read is named waiting at once, never // failed, and registered from the same key, with no second build, when a controller that reads it looks. func TestANewModuleWithAnUnreadWordWaitsForTheControllerThatReadsIt(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory noBuildAsked(t) b := aBuilderOnTheBus(t) if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "networkmanager", Version: "1"}, inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/networkmanager", Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil { t.Fatal(err) } fp := fpOf('8') raw := `{"module":"watchman","version":"1","not-a-word-yet":"5m","build":{"artifacts":[{"name":"a","kind":"archive","from":"files"}]},` + `"resources":[{"id":"a","type":"archive","path":"/opt/watchman","artifact":"a"}]}` digest := sdkdelivery.ManifestDigest([]byte(raw)) value, _ := sdkdelivery.EncodeManifest(sdkdelivery.Manifest{Module: "watchman", JSON: raw}) if _, err := b.manifests.Put(ctx, digest, value); err != nil { t.Fatal(err) } trunk, _ := sdkdelivery.EncodeModule(sdkdelivery.Module{FP: "src1:" + fp[5:], FP2: fp, Manifest: digest, Repository: "novox/mesh-catalog", Commit: "4444444400000000", Path: "modules/watchman"}) if _, err := b.modules.Put(ctx, "watchman", trunk); err != nil { t.Fatal(err) } b.built(t, "watchman", "1", fp, digest, sdkdelivery.BuildDone) takeNewModules(ctx, open, "watchman") if _, known := inventoryHolds(t, inv, "watchman"); known { t.Fatal("0501 §7: a manifest with an unread word was registered") } obs := newModuleObservations(time.Now()) if len(obs) != 1 || obs[0].Kind != kindNewModuleWaits || !strings.Contains(obs[0].Summary, `"not-a-word-yet"`) { t.Fatalf("0501 §7: the module waiting for a reader of its word is named by %+v", obs) } builds, _ := inv.Builds(ctx, "watchman", 5) for _, b := range builds { if !b.Worked() { t.Fatalf("0501 §7: a failed build was recorded: %+v", b) } } }