The controller takes the indexer's verdict instead of judging manifests; route-proxy declares its credential form (ADR 0578, 0579) #489

Merged
mesh-tools merged 9 commits from feat/judge-once into main 2026-11-01 07:54:24 +00:00
5 changed files with 62 additions and 38 deletions
Showing only changes of commit b564c315d0 - Show all commits
@@ -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")
@@ -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 {
@@ -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 {
+13 -5
View File
@@ -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)
}
@@ -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)
}
}