From b564c315d01236fbb64bb095205fc6990bc2354a Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 1 Nov 2026 04:14:44 +0100 Subject: [PATCH] Test the controller as one that judges no manifest, and reset the new-module waits each test leaves (ADR 0578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1585: registration publishes the predefined list and judges nothing against it; a definition naming an installation is the indexer's judge's to refuse; a word the controller does not read is set aside and the module registered (0578 §6 changes 0501 §7); a refused verdict holds the module and names it. The process-wide list of new modules waiting is emptied at each take-in test's start and end, so no test reads another's. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- cmd/mesh-controller/build_integration_test.go | 6 ++- .../builder_on_the_bus_test.go | 3 ++ .../new_module_on_merge_test.go | 45 +++++++++++++++---- cmd/mesh-controller/new_modules_test.go | 18 +++++--- .../predefined_integration_test.go | 28 +++--------- 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/cmd/mesh-controller/build_integration_test.go b/cmd/mesh-controller/build_integration_test.go index c2afe6e7..3e484fdc 100644 --- a/cmd/mesh-controller/build_integration_test.go +++ b/cmd/mesh-controller/build_integration_test.go @@ -49,8 +49,10 @@ func TestABuildHeardIsRecordedAndRegistered(t *testing.T) { "env": map[string]any{"KC_HOSTNAME": "https://login.mesh-one.be"}}}}) _, _, err = takeIn(ctx, open.inventory, link.BuildResult{ ID: "b-2", Repository: "/r", On: "anchor", Commit: "0123456789", Manifest: named}) - if err == nil || !strings.Contains(err.Error(), "does not register it") { - t.Fatalf("a definition naming an installation was taken in: %v", err) + // Not registered from a source the catalogue does not build it from. That it names an installation is no longer the + // controller's to judge (ADR 0578 §3): the indexer's judge refuses it (mesh-delivery-indexer judge's installation rows). + if err == nil || !strings.Contains(err.Error(), "recorded and not registered") { + t.Fatalf("a definition from a source the catalogue does not build it from was taken in: %v", err) } if shelf, _ := open.inventory.Catalogue(ctx); shelf["idp"].Module != "" { t.Fatal("the refused module was registered anyway") diff --git a/cmd/mesh-controller/builder_on_the_bus_test.go b/cmd/mesh-controller/builder_on_the_bus_test.go index 8c6afb64..d91fbf8f 100644 --- a/cmd/mesh-controller/builder_on_the_bus_test.go +++ b/cmd/mesh-controller/builder_on_the_bus_test.go @@ -17,6 +17,8 @@ import ( type builderOnTheBus struct { js *broker.JetStream builds, modules, manifests jetstream.KeyValue + // verdicts is the indexer's state of verdicts on manifests (ADR 0578 §1). + verdicts jetstream.KeyValue } func aBuilderOnTheBus(t *testing.T) builderOnTheBus { @@ -40,6 +42,7 @@ func aBuilderOnTheBus(t *testing.T) builderOnTheBus { {&b.builds, sdkdelivery.BuilderBuildsBucket, sdkdelivery.BuilderBuildsHistory}, {&b.modules, sdkdelivery.ModulesBucket, sdkdelivery.ModulesHistory}, {&b.manifests, sdkdelivery.ManifestsBucket, 1}, + {&b.verdicts, sdkdelivery.VerdictsBucket, sdkdelivery.VerdictsHistory}, } { kv, err := api.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: c.bucket, History: c.history}) if err != nil { diff --git a/cmd/mesh-controller/new_module_on_merge_test.go b/cmd/mesh-controller/new_module_on_merge_test.go index 73db2ae5..a5977194 100644 --- a/cmd/mesh-controller/new_module_on_merge_test.go +++ b/cmd/mesh-controller/new_module_on_merge_test.go @@ -18,6 +18,7 @@ import ( // 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) { + withNoNewModuleWaits(t) open := aMesh(t) ctx := t.Context() inv := open.inventory @@ -66,9 +67,11 @@ func TestAMergeAddingAModuleEndsRegisteredFromItsKeyAndAsksNoBuild(t *testing.T) } } -// 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) { +// ADR 0578 §6 (which changes ADR 0501 §7): a new module whose manifest uses a word this controller does not read is +// registered at once, the word set aside, never failed and with no second build; one whose manifest the indexer's judge +// refused is held, named in the indexer's words, and never registered (ADR 0578 §3). +func TestANewModuleWithAnUnreadWordIsRegisteredAndARefusedOneIsHeld(t *testing.T) { + withNoNewModuleWaits(t) open := aMesh(t) ctx := t.Context() inv := open.inventory @@ -94,12 +97,38 @@ func TestANewModuleWithAnUnreadWordWaitsForTheControllerThatReadsIt(t *testing.T } 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") + if _, known := inventoryHolds(t, inv, "watchman"); !known { + t.Fatalf("0578 §6: a manifest with a word set aside is not registered: %+v", newModuleObservations(time.Now())) } - 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) + // The indexer refused another: held, named, not registered. + refusedFP := fpOf('9') + refusedRaw := `{"module":"refusedmod","version":"1","build":{"artifacts":[{"name":"a","kind":"archive","from":"files"}]},` + + `"resources":[{"id":"a","type":"archive","path":"/opt/refusedmod","artifact":"a"}]}` + refusedDigest := sdkdelivery.ManifestDigest([]byte(refusedRaw)) + value, _ = sdkdelivery.EncodeManifest(sdkdelivery.Manifest{Module: "refusedmod", JSON: refusedRaw}) + if _, err := b.manifests.Put(ctx, refusedDigest, value); err != nil { + t.Fatal(err) + } + verdict, _ := sdkdelivery.EncodeManifestVerdict(sdkdelivery.ManifestVerdict{Module: "refusedmod", State: sdkdelivery.VerdictRefused, + Problems: []string{"it breaks a rule"}, Judge: "exe:1", At: time.Now()}) + if _, err := b.verdicts.Put(ctx, refusedDigest, verdict); err != nil { + t.Fatal(err) + } + trunk, _ = sdkdelivery.EncodeModule(sdkdelivery.Module{FP: "src1:" + refusedFP[5:], FP2: refusedFP, Manifest: refusedDigest, + Repository: "novox/mesh-catalog", Commit: "5555555500000000", Path: "modules/refusedmod"}) + if _, err := b.modules.Put(ctx, "refusedmod", trunk); err != nil { + t.Fatal(err) + } + b.built(t, "refusedmod", "1", refusedFP, refusedDigest, sdkdelivery.BuildDone) + takeNewModules(ctx, open, "refusedmod") + if _, known := inventoryHolds(t, inv, "refusedmod"); known { + t.Fatal("0578 §3: a manifest the indexer refused was registered") + } + // Said at once in the controller's journal; a condition once it waits past the bound (the watchman raises the + // indexer's manifest-refused at once). + obs := newModuleObservations(time.Now().Add(newModuleWaitBound + time.Minute)) + if len(obs) != 1 || obs[0].Kind != kindNewModuleWaits || !strings.Contains(obs[0].Summary, "refused the manifest of refusedmod: it breaks a rule") { + t.Fatalf("0578 §3: the refused module is not held and named: %+v", obs) } builds, _ := inv.Builds(ctx, "watchman", 5) for _, b := range builds { diff --git a/cmd/mesh-controller/new_modules_test.go b/cmd/mesh-controller/new_modules_test.go index cfa845ac..3bd42a4f 100644 --- a/cmd/mesh-controller/new_modules_test.go +++ b/cmd/mesh-controller/new_modules_test.go @@ -9,11 +9,7 @@ import ( // ADR 0501 §6, §7 (issue 1851, A5b): the condition of a new module reads its key's take-in: one waiting for a controller // that reads its manifest's word is named at once, one waiting for its key only past the bound, each reason said once. func TestANewModuleWaitingIsNamedByWhatItWaitsFor(t *testing.T) { - t.Cleanup(func() { - newModulesWaiting.Lock() - newModulesWaiting.by = map[string]newModuleWait{} - newModulesWaiting.Unlock() - }) + withNoNewModuleWaits(t) word := `waits: watchman's manifest uses the word "key-ttl", which this controller does not read: it is registered when a controller that reads it runs` sayNewModuleWait(newModuleWait{Module: "watchman", Commit: "c1", Why: word, Word: wordIn(word)}) sayNewModuleWait(newModuleWait{Module: "view", Commit: "c1", Why: "waits: the build of view at aaaa is building on ace"}) @@ -31,3 +27,15 @@ func TestANewModuleWaitingIsNamedByWhatItWaitsFor(t *testing.T) { t.Fatalf("registered modules are still named: %+v", obs) } } + +// withNoNewModuleWaits empties the controller's list of new modules waiting, for a test, at its start and at its end: the +// list is the process's, so a test that leaves a module in it is read by the next. +func withNoNewModuleWaits(t *testing.T) { + reset := func() { + newModulesWaiting.Lock() + newModulesWaiting.by = map[string]newModuleWait{} + newModulesWaiting.Unlock() + } + reset() + t.Cleanup(reset) +} diff --git a/cmd/mesh-controller/predefined_integration_test.go b/cmd/mesh-controller/predefined_integration_test.go index 9a7c1dd9..6ed05808 100644 --- a/cmd/mesh-controller/predefined_integration_test.go +++ b/cmd/mesh-controller/predefined_integration_test.go @@ -10,7 +10,10 @@ import ( "git.novox.be/novox/mesh-sdk/go/catalogue" ) -func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t *testing.T) { +// Registration publishes the newest trunk's list of predefined keys. It judges no manifest against it (ADR 0578 §3): the +// indexer's judge does, with the list it keeps, and a fallback to a key not on it, or of another type, is refused there +// (mesh-delivery-indexer judge's tests). +func TestRegistrationPublishesTheNewestTrunkList(t *testing.T) { withoutAPublishedList(t) open := aMesh(t) ctx := t.Context() @@ -28,22 +31,6 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t t.Fatalf("the controller judges against %v, not the list published", got) } - // One naming a key not on the list, or of another type, is refused and never enters the catalogue. - for i, c := range []struct{ manifest, says string }{ - {notifierFallingBackTo("font-scale", "number"), "not on the list"}, - {notifierFallingBackTo("font-size", "string"), "which is a number"}, - } { - id := []string{"b-2", "b-3"}[i] - if _, _, err := takeIn(ctx, open.inventory, catalogueBuild(id, c.manifest, theFirstKeys, 10)); err == nil || - !strings.Contains(err.Error(), c.says) { - t.Errorf("wanted the registration refused saying %q, got %v", c.says, err) - } - } - src, err := open.inventory.SourceOf(ctx, "notifier") - if err != nil || src.BuiltFrom != strings.Repeat("1", 12) { - t.Fatalf("a refused build replaced the registered one: %+v %v", src, err) - } - // A newer build's list, which drops agent-managed-settings, replaces the published one. smaller := `{"font-size": {"type": "number", "description": "points", "minimum": 6, "maximum": 72}}` if _, _, err := takeIn(ctx, open.inventory, catalogueBuild("b-4", @@ -60,7 +47,7 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t t.Fatalf("an older build's list heard later replaced the newer one: %+v", kept) } - // A build of another repository publishes nothing, and its manifest is judged against the published list. + // A build of another repository publishes nothing. other := catalogueBuild("b-6", notifierFallingBackTo("font-size", "number"), theFirstKeys, 99) other.Repository, other.Source.Repository = "http://forge.internal:20000/novox/notifier.git", "novox/notifier" other.Manifest = json.RawMessage(strings.Replace(notifierFallingBackTo("font-size", "number"), `"notifier"`, @@ -71,9 +58,4 @@ func TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt(t if kept, _, _ = open.inventory.PredefinedKept(ctx); kept.Build != "b-4" { t.Fatalf("a build of another repository published its list: %+v", kept) } - other.ID, other.Manifest = "b-7", json.RawMessage(strings.Replace(notifierFallingBackTo("agent-managed-settings", - "number"), `"notifier"`, `"chimes"`, 1)) - if _, _, err := takeIn(ctx, open.inventory, other); err == nil || !strings.Contains(err.Error(), "not on the list") { - t.Fatalf("a key only another repository's list holds was registered: %v", err) - } }