IX21: the validator reads a manifest's version MAJOR.MINOR and the finding stream and ask of a findings state

The refusal of a status in a manifest (IX20) relies on strict decoding: a manifest
field naming a status is an unknown field and is refused. The `*` emitter of
consumes (design 47 G §386) is held for the design writer's answer on model-usage's
`*.usage.*`.

Builds: design 47 G §89, §368, §387

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 11:32:05 +01:00
parent 6b85c9045e
commit 540b88a946
21 changed files with 427 additions and 321 deletions
@@ -206,7 +206,7 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
m1 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: h1,
Merge: "1111111111111111111111111111111111111111", Root: "r0",
Trees: map[string]string{"modules/" + ix: "tree-indexer-2", "modules/a": "tree-a-2"},
Manifests: map[string]string{"modules/" + ix: string(manifest), "modules/a": `{"module":"a","version":"1"}`},
Manifests: map[string]string{"modules/" + ix: string(manifest), "modules/a": `{"module":"a","version":"1.0"}`},
Locks: map[string]map[string]string{"modules/" + ix: {"go.sum": "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"}}}
merged := func(f forgemsg.Forge) uint64 {
t.Helper()
@@ -305,10 +305,10 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
{Repository: "novox/mesh-controller", Number: 41, Head: "4141414141414141414141414141414141414141"}}
g1 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/g", Number: 40, Head: members[0].Head,
Merge: "4000000000000000000000000000000000000000", Root: "r0", Trees: map[string]string{"modules/c": "tree-c-2"},
Manifests: map[string]string{"modules/c": `{"module":"c","version":"1"}`}, Group: "feat/g", Members: members}
Manifests: map[string]string{"modules/c": `{"module":"c","version":"1.0"}`}, Group: "feat/g", Members: members}
g2 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-controller", Branch: "feat/g", Number: 41, Head: members[1].Head,
Merge: "4100000000000000000000000000000000000000", Root: "r0", Trees: map[string]string{"modules/k": "tree-k-1"},
Manifests: map[string]string{"modules/k": `{"module":"k","version":"1"}`}, Group: "feat/g", Members: members}
Manifests: map[string]string{"modules/k": `{"module":"k","version":"1.0"}`}, Group: "feat/g", Members: members}
g1Key, groupKey := takein.PendingKey(merged(g1)), takein.GroupTakeInKey("feat/g")
if gathered, ok := said(takein.TakeInPending, groupKey); !ok || gathered.What != takein.TakeInGroup || len(gathered.Merges) != 1 {
t.Fatalf("take-in.pending.%s = %+v (said %v) within 20 s, want the group with its first member", groupKey, gathered, ok)
@@ -618,7 +618,7 @@ func TestIndexerWritesModulesAtAMerge(t *testing.T) { // 1239/AC9; I1, I3: the m
const merge = "89abcdef0123456789abcdef0123456789abcdef"
data, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
Head: "0123456789abcdef0123456789abcdef01234567", Merge: merge, Root: "r0", Trees: map[string]string{"modules/a": "newtree"},
Manifests: map[string]string{"modules/a": `{"module":"a","version":"1"}`}})
Manifests: map[string]string{"modules/a": `{"module":"a","version":"1.0"}`}})
if err != nil {
t.Fatal(err)
}
@@ -655,9 +655,9 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
}
const goSum = "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"
judged := map[string]string{
"good": `{"module":"good","version":"1","resources":[{"id":"d","type":"directory","path":"/srv/good","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
"bad": `{"module":"bad","version":"1","provides":[{"name":"amqp"}],"resources":[{"id":"d","type":"directory","path":"/srv/bad","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
"later": `{"module":"later","version":"1","a-field-from-later":1,"resources":[{"id":"d","type":"directory","path":"/srv/later","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
"good": `{"module":"good","version":"1.0","resources":[{"id":"d","type":"directory","path":"/srv/good","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
"bad": `{"module":"bad","version":"1.0","provides":[{"name":"amqp"}],"resources":[{"id":"d","type":"directory","path":"/srv/bad","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
"later": `{"module":"later","version":"1.0","a-field-from-later":1,"resources":[{"id":"d","type":"directory","path":"/srv/later","mode":"0700"}],"data":{"own":[{"id":"d","path":"${dir:d}","class":"valuable"}]}}`,
}
js, ctx, _, modules := startIndexer(t, func(ctx context.Context, builds buildLog) {
builds.end(t, "novox/mesh-catalog", "avahi", "modules/avahi")
@@ -226,7 +226,7 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T)
if got, err := index.SeatDefinitionsOf(jsSource{ctx, seatDefs}, nil, ""); err != nil || func() string { raw, _ := json.Marshal(got); return string(raw) }() != `{"revision":0,"seats":[]}` {
t.Errorf("seat-definitions of an indexer that kept none = %+v, %v", got, err)
}
contracts := `{"module":"mesh-contracts","version":"1","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
contracts := `{"module":"mesh-contracts","version":"1.0","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
merged("novox/mesh-contracts", 80, contractsMerge, forgemsg.Forge{Root: "dddddddddddddddddddddddddddddddddddddddd",
Trees: map[string]string{"": "dddddddddddddddddddddddddddddddddddddddd"}, Manifests: map[string]string{"": contracts}})
entry := func(key string, ok func(index.SeatDefinitionEntry) bool) index.SeatDefinitionEntry {
@@ -76,8 +76,8 @@ func TestTheControlRecords(t *testing.T) {
}
modules[name] = &views.Entry{Value: raw, Revision: 1}
}
asks := `{"module":"cache","version":"1","requires":[{"name":"c","storage":"key-value","template":"state"}]}`
for digest, raw := range map[string]string{dAsk: asks, dNone: `{"module":"plain","version":"1"}`, dRefused: asks} {
asks := `{"module":"cache","version":"1.0","requires":[{"name":"c","storage":"key-value","template":"state"}]}`
for digest, raw := range map[string]string{dAsk: asks, dNone: `{"module":"plain","version":"1.0"}`, dRefused: asks} {
put(manifests, digest, 1, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: "m", Manifest: raw})
}
putVerdict(dAsk, 7, meshcatalog.VerdictsByDigestValue{Module: "cache", State: meshcatalogclient.VerdictAccepted, Judge: "j"})
@@ -127,7 +127,7 @@ func TestTheControlRecords(t *testing.T) {
// raises generated-types-out-of-step from a trunk build end, cleared only by a clean one of the same module.
t.Run("seat definitions", func(t *testing.T) {
const repo, m1, m2, m3, head = "novox/mesh-contracts", "m1", "m2", "m3", "h1"
manifest := `{"module":"mesh-contracts","version":"1","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
manifest := `{"module":"mesh-contracts","version":"1.0","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
git := `{"format":1,"seat":"git","scope":"mesh","grain":"seat-list","decision":"novox/hq ADR 0111","domain":"Core","delivers":"git"}`
files := map[string]map[string]string{
m1: {"seats/git/seat.json": git, "seats/gone/seat.json": strings.Replace(git, `"git"`, `"gone"`, 1)},
@@ -162,7 +162,7 @@ func TestTheControlRecords(t *testing.T) {
merged := func(merge string) {
t.Helper()
if err := ix.ApplySeatDefinitions(ctx, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: repo, Number: 1, Merge: merge,
Manifests: map[string]string{meshcatalogclient.RootPath: manifest, "modules/other": `{"module":"other","version":"1"}`}}); err != nil {
Manifests: map[string]string{meshcatalogclient.RootPath: manifest, "modules/other": `{"module":"other","version":"1.0"}`}}); err != nil {
t.Fatal(err)
}
}
@@ -284,7 +284,7 @@ func TestTheControlRecords(t *testing.T) {
// Ruling (lead, on A2i's finding 3): indexer_seat_definitions_again for a pull request reads the head's own manifest's
// generated-from: a trunk that names none, a head that names it, is read as the contracts module the head is.
ix.Modules, ix.Manifests = newMem(), newMem()
plain := `{"module":"mesh-contracts","version":"1"}`
plain := `{"module":"mesh-contracts","version":"1.0"}`
kept, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "mesh-contracts", Manifest: plain})
mkey, _ := meshcatalogclient.ManifestsKey(meshcatalogclient.ManifestDigest([]byte(plain)))
ix.Manifests.(*memStore).vals[mkey] = kept
@@ -425,7 +425,7 @@ func TestTheControlRecords(t *testing.T) {
}
return nil, nil
}}
sdk := `{"module":"mesh-sdk","version":"1","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]},` +
sdk := `{"module":"mesh-sdk","version":"1.0","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]},` +
`{"name":"ts","kind":"package","language":"typescript","paths":["src","package.json"],"series":"0.3"}]}}`
if err := ix.KeepPackageNames(ctx, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-sdk", Merge: "m1",
Manifests: map[string]string{meshcatalogclient.RootPath: sdk}}); err != nil {
@@ -498,11 +498,11 @@ func TestTheControlRecords(t *testing.T) {
pkgs, imports []string
pinned string
}{
"new": {"novox/mesh-catalog", `{"module":"new","version":"1","packages":{"git.novox.be/novox/mesh-sdk/go":"0.2","@novox/ui":"1.0"}}`, nil, nil, ""},
"old": {"novox/mesh-catalog", `{"module":"old","version":"1"}`, []string{sdkGo}, nil, "v0.1.15"},
"imports": {"novox/mesh-tools", `{"module":"imports","version":"1"}`, nil, []string{sdkGo + "/views"}, ""},
"sibling": {"novox/mesh-sdk", `{"module":"sibling","version":"1"}`, []string{sdkGo}, nil, ""},
"none": {"novox/mesh-catalog", `{"module":"none","version":"1"}`, nil, nil, ""},
"new": {"novox/mesh-catalog", `{"module":"new","version":"1.0","packages":{"git.novox.be/novox/mesh-sdk/go":"0.2","@novox/ui":"1.0"}}`, nil, nil, ""},
"old": {"novox/mesh-catalog", `{"module":"old","version":"1.0"}`, []string{sdkGo}, nil, "v0.1.15"},
"imports": {"novox/mesh-tools", `{"module":"imports","version":"1.0"}`, nil, []string{sdkGo + "/views"}, ""},
"sibling": {"novox/mesh-sdk", `{"module":"sibling","version":"1.0"}`, []string{sdkGo}, nil, ""},
"none": {"novox/mesh-catalog", `{"module":"none","version":"1.0"}`, nil, nil, ""},
} {
digest := fmt.Sprintf("%064x", len(name)*16+int(name[0]))
putJSON(manifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: name, Manifest: u.manifest})
@@ -569,7 +569,7 @@ func TestTheControlRecords(t *testing.T) {
ix, _, _ := manifestsIndexer(t, rec(t, "build-1", "avahi", catalogueRepo, "modules/avahi", "main", "", "2026-10-19T09:00:00Z", aInputs))
cands := newMem()
ix.ConfigCandidates, ix.Now = cands, func() time.Time { return at }
trunk := `{"module":"avahi","version":"1","config":{"port":{"type":"integer","default":5353,"why":"mDNS"}}}`
trunk := `{"module":"avahi","version":"1.0","config":{"port":{"type":"integer","default":5353,"why":"mDNS"}}}`
if err := ix.Apply(ctx, withManifests(mergedMsg(catalogueRepo, map[string]string{"modules/avahi": "t1"}), map[string]string{"modules/avahi": trunk}, nil)); err != nil {
t.Fatal(err)
}
@@ -34,10 +34,10 @@ func (b fakeBounds) Disarm(_ context.Context, key string) error { delete(b, key)
func storeHoldersNewManifestWaitsForEveryCountedEngine(t *testing.T) {
ctx := context.Background()
const dir, dockerDir = "modules/distribution", "modules/docker"
old := `{"module":"distribution","version":"1","listens":[{"name":"registry","port":5000,"from":"mesh"}]}`
next := `{"module":"distribution","version":"1","claims":[{"name":"container-image-registry","scope":"mesh"}],` +
old := `{"module":"distribution","version":"1.0","listens":[{"name":"registry","port":5000,"from":"mesh"}]}`
next := `{"module":"distribution","version":"1.0","claims":[{"name":"container-image-registry","scope":"mesh"}],` +
`"listens":[{"name":"registry","port":5000,"from":"mesh"}]}`
docker := `{"module":"docker","version":"1","resources":[{"id":"daemon","type":"file","path":"/etc/docker/daemon.json","template":"files/daemon.json"}]}`
docker := `{"module":"docker","version":"1.0","resources":[{"id":"daemon","type":"file","path":"/etc/docker/daemon.json","template":"files/daemon.json"}]}`
dockerNeeds, _ := catalogue.ModuleNeeds([]byte(docker), map[string]string{"files/daemon.json": `"${seat:container-image-registry:reach}"`})
if needs, _ := catalogue.ManifestNeeds([]byte(next)); !slices.Contains(needs, "form:seat-reach") || !slices.Contains(dockerNeeds, "form:seat-reach") {
t.Fatalf("the new manifest needs %v and docker's %v, want form:seat-reach in both (mesh-sdk ecdbf1a, ModuleNeeds)", needs, dockerNeeds)
@@ -185,7 +185,7 @@ func TestModuleOfADirectory(t *testing.T) { // 1238/AC3, as R4e has it: the modu
ctx := context.Background()
// The trunk holds modules/odd as the module odd-name (its manifest names it so); modules/plain is held by none.
merged := mergedMsg("novox/mesh-delivery", map[string]string{"modules/odd": "t"})
merged.Manifests["modules/odd"] = `{"module":"odd-name","version":"1"}`
merged.Manifests["modules/odd"] = `{"module":"odd-name","version":"1.0"}`
if err := ix.Apply(ctx, merged); err != nil {
t.Fatal(err)
}
@@ -202,7 +202,7 @@ func TestModuleOfADirectory(t *testing.T) { // 1238/AC3, as R4e has it: the modu
}
// A manifest the pull message carries names its module, whatever directory it is in.
f := pull("novox/mesh-delivery", 13, "feat/y", h2, "modules/odd")
f.Manifests = map[string]string{"modules/odd": `{"module":"renamed","version":"1"}`}
f.Manifests = map[string]string{"modules/odd": `{"module":"renamed","version":"1.0"}`}
if name := moduleOfDir(held, f, "modules/odd"); name != "renamed" {
t.Errorf("modules/odd = %q, want the module its manifest names", name)
}
@@ -144,7 +144,7 @@ func TestAnArtifactOfNoToolchainHasNoLanguageAndNoLock(t *testing.T) { // 1450/A
func TestAManifestTheParserRefusesLeavesNoInputsAndSaysWhy(t *testing.T) { // 1450/AC5
for name, raw := range map[string]string{
"not json": "not json",
"unknown field": strings.Replace(catalogueManifest(t, "avahi"), `"version": "1",`, `"version": "1", "scope": "everything",`, 1),
"unknown field": strings.Replace(catalogueManifest(t, "avahi"), `"version":"1.0",`, `"version":"1.0", "scope": "everything",`, 1),
} {
in := inputsOf("avahi", raw, "t", nil, noBases)
if in.Kept != nil || !strings.HasPrefix(in.None2, "the manifest cannot be read: ") || len(in.None2) <= len("the manifest cannot be read: ") {
@@ -82,7 +82,7 @@ func rec(t *testing.T, id, module, repo, path, ref, state, at, inputs string) [2
func mergedMsg(repo string, trees map[string]string) forgemsg.Forge {
manifests := map[string]string{}
for dir := range trees {
manifests[dir] = `{"module":"` + nameOfDir(dir) + `","version":"1"}`
manifests[dir] = `{"module":"` + nameOfDir(dir) + `","version":"1.0"}`
}
return forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgeMerged, Repository: repo, Branch: "feat/x", Number: 12, Head: h1, Merge: merge, Root: "r0", Trees: trees, Manifests: manifests}
}
@@ -515,7 +515,7 @@ func TestAModuleMovesBetweenRepositoriesOnlyWhenItsTrunkDropsIt(t *testing.T) {
if repository != to || sha != toHead || dir != "modules/a" {
t.Errorf("read %s at %s %q, want only %s at %s modules/a", repository, sha, dir, to, toHead)
}
return RepoRead{Root: "r2", Trees: map[string]string{dir: "t2"}, Manifests: map[string]string{dir: `{"module":"a","version":"1"}`}}, nil
return RepoRead{Root: "r2", Trees: map[string]string{dir: "t2"}, Manifests: map[string]string{dir: `{"module":"a","version":"1.0"}`}}, nil
}
}
for _, f := range tc.merges {
@@ -106,7 +106,7 @@ func TestTheIndexerAsksTheForgeForTheNamedPathsObjectsOfAMerge(t *testing.T) { /
return map[string]map[string]string{"": sdkObjects}, nil
}
raw := fixture(t, "sdk-9cb4d59-mesh-sdk")
f := withManifests(mergedMsg("novox/mesh-sdk", map[string]string{"": "roottree", "modules/a": "t"}), map[string]string{"": raw, "modules/a": `{"module":"a","version":"1"}`}, nil)
f := withManifests(mergedMsg("novox/mesh-sdk", map[string]string{"": "roottree", "modules/a": "t"}), map[string]string{"": raw, "modules/a": `{"module":"a","version":"1.0"}`}, nil)
if err := ix.Apply(context.Background(), f); err != nil {
t.Fatal(err)
}
@@ -365,7 +365,7 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
f.Locks[d] = l
}
f.Locks[contracts] = map[string]string{}
f.Manifests[contracts] = `{"module":"` + row.module + `","version":"1","build":{"artifacts":[{"name":"go-seats","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
f.Manifests[contracts] = `{"module":"` + row.module + `","version":"1.0","build":{"artifacts":[{"name":"go-seats","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}`
b.ix.Files = func(_ context.Context, repository, commit string, dirs []string) (map[string]map[string]string, error) {
if repository != f.Repository || commit != f.Merge || !slices.Equal(dirs, []string{contracts + "/seats"}) {
t.Errorf("listed %v of %s at %s", dirs, repository, commit)
@@ -15,7 +15,7 @@ import (
func TestARegisteredModuleIsIndexedFromItsRepository(t *testing.T) { // the grants part's gaps: modules registered from their own repositories had no manifest here
const forge = "http://novox.internal:20000/jschoubben/snake.git"
snake := `{"module":"snake","version":"1"}`
snake := `{"module":"snake","version":"1.0"}`
for _, tc := range []struct {
name string
path string
@@ -41,7 +41,7 @@ func TestARegisteredModuleIsIndexedFromItsRepository(t *testing.T) { // the gran
{wantRead: true, name: "no manifest at the path", path: "modules/x",
read: RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": snake}}},
{wantRead: true, name: "a manifest of another module", path: "",
read: RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": `{"module":"other","version":"1"}`}}},
read: RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": `{"module":"other","version":"1.0"}`}}},
{wantRead: true, name: "a repository whose default branch is master", path: "", ref: "master", want: true,
read: RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": snake}}},
{wantRead: true, name: "a repository built from a branch of its own (nox-mesh)", path: "", ref: "nox-mesh", want: true,
@@ -151,7 +151,7 @@ func TestARegisteredModuleIsIndexedFromItsRepository(t *testing.T) { // the gran
var asked []string
ix.Read = func(_ context.Context, _, sha, dir string) (RepoRead, error) {
asked = append(asked, sha+":"+dir)
return RepoRead{Root: "r1", Trees: map[string]string{"modules/a": "treeA2"}, Manifests: map[string]string{"modules/a": `{"module":"a","version":"2"}`}}, nil
return RepoRead{Root: "r1", Trees: map[string]string{"modules/a": "treeA2"}, Manifests: map[string]string{"modules/a": `{"module":"a","version":"2.0"}`}}, nil
}
got := ix.TakeIn(ctx, "a")
m, ok := moduleOf(t, mods, "a")
@@ -202,7 +202,7 @@ func TestARegistrationOfAnotherRepositoryDoesNotHideTheRealOne(t *testing.T) { /
ix.Read = func(_ context.Context, repo, _, dir string) (RepoRead, error) {
asked = append(asked, repo+":"+dir)
if repo == "novox/Link2Pay" {
return RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": `{"module":"link2pay","version":"1"}`}}, nil
return RepoRead{Root: "r0", Trees: map[string]string{"": "t1"}, Manifests: map[string]string{"": `{"module":"link2pay","version":"1.0"}`}}, nil
}
return RepoRead{Root: "r0"}, nil
}
@@ -221,7 +221,7 @@ func TestARegistrationOfAnotherRepositoryDoesNotHideTheRealOne(t *testing.T) { /
func TestAForgeThatCannotSayLeavesTheModuleUnwrittenAndAsksAgain(t *testing.T) { // a timeout or a 404 at the head: no read at an older commit
// Issue 1945: car-hunter and snake answered 404 until the forge's account could read them, and were read again only when
// the indexer started again. Each row's trigger must now take the module in once the forge reads it.
const snake = `{"module":"snake","version":"1"}`
const snake = `{"module":"snake","version":"1.0"}`
ctx := context.Background()
for _, tc := range []struct{ failure, trigger string }{
{"timeout", "the indexer starts again"},
@@ -10,7 +10,7 @@ import (
// not read.
func unreadableAvahi(t *testing.T) string {
t.Helper()
raw := strings.Replace(catalogueManifest(t, "avahi"), `"version": "1",`, `"version": "1", "scope": "everything",`, 1)
raw := strings.Replace(catalogueManifest(t, "avahi"), `"version":"1.0",`, `"version":"1.0", "scope": "everything",`, 1)
if raw == catalogueManifest(t, "avahi") {
t.Fatal("the fixture has no version line")
}
@@ -344,7 +344,7 @@ func TestBasesAreComputedFirstAndNoFingerprintTakesTheBuilder(t *testing.T) { //
// verb edges of two modules calling each other, from either side; a third calling one of them keeps its edge.
invoking := func(name, invokes string) mod {
raw := renamed(t, "avahi", name)
return mod{name, strings.Replace(raw, `"version": "1",`, `"version": "1", "invokes": [`+invokes+`],`, 1)}
return mod{name, strings.Replace(raw, `"version":"1.0",`, `"version":"1.0", "invokes": [`+invokes+`],`, 1)}
}
vx, _ := newFormIndexer(t, invoking("caller", `"callee.avahi_status"`), invoking("callee", `"caller.avahi_browse", "*"`),
invoking("third", `"callee.avahi_status"`))
@@ -505,7 +505,7 @@ func TestAModuleKeepsTheToolchainsItsFingerprintWasComputedWith(t *testing.T) {
sdkMerge := func(commit, tree string) meshcatalog.ModulesByModuleValue {
t.Helper()
f := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 3, Head: h1,
Merge: commit, Root: tree, Trees: map[string]string{"": tree}, Manifests: map[string]string{"": `{"module":"mesh-sdk","version":"1"}`}}
Merge: commit, Root: tree, Trees: map[string]string{"": tree}, Manifests: map[string]string{"": `{"module":"mesh-sdk","version":"1.0"}`}}
if err := ix.Apply(context.Background(), f); err != nil {
t.Fatal(err)
}
@@ -40,8 +40,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
ix.Modules.(*memStore).put(module, value)
return digest
}
good := hold("good", `{"module":"good","version":"1"}`)
bad := hold("bad", `{"module":"bad","version":"1","requires":["amqp"]}`)
good := hold("good", `{"module":"good","version":"1.0"}`)
bad := hold("bad", `{"module":"bad","version":"1.0","requires":["amqp"]}`)
// Both taken in by a merge: judged in full.
j, err := ix.JudgeAll(ctx, map[string]bool{good: true, bad: true})
if err != nil || j.Judged != 2 || j.Written != 2 || len(j.Refused) != 1 {
@@ -91,7 +91,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
t.Fatalf("the new indexer at its start: %+v %v, said %d, want both manifests judged and nothing written", j, err, len(said))
}
// The manifest mended: its new digest is accepted and the module's refusal goes.
mended := hold("bad", `{"module":"bad","version":"2"}`)
mended := hold("bad", `{"module":"bad","version":"2.0"}`)
if j, err := ix.JudgeAll(ctx, map[string]bool{mended: true}); err != nil || j.Written != 1 || len(j.Refused) != 0 {
t.Fatalf("mended: %+v %v", j, err)
}
@@ -101,7 +101,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// ADR 0579 §1: a manifest held before any verdict (taken in under the judges until then), which this judge finds a
// problem in, stays accepted with the problem as a warning, carried in its value and said nowhere as a refusal; the
// same problem in a manifest a merge takes in now, or in one whose digest changed, is refused in full.
old := hold("old", `{"module":"old","version":"1","requires":["amqp"]}`)
old := hold("old", `{"module":"old","version":"1.0","requires":["amqp"]}`)
if j, err := ix.JudgeAll(ctx, nil); err != nil || len(j.Outgrown) != 1 || j.Outgrown[0] != "old" {
t.Fatalf("an outgrown manifest: %+v %v", j, err)
}
@@ -111,8 +111,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
if m := module("old"); m.Verdict != "" || len(m.Warnings) != 1 || len(said) != 1 {
t.Fatalf("modules.old carries %+v, said %d", m, len(said))
}
changed := hold("old", `{"module":"old","version":"2","requires":["amqp"]}`)
fresh := hold("new", `{"module":"new","version":"1","requires":["amqp"]}`)
changed := hold("old", `{"module":"old","version":"2.0","requires":["amqp"]}`)
fresh := hold("new", `{"module":"new","version":"1.0","requires":["amqp"]}`)
if j, err := ix.JudgeAll(ctx, map[string]bool{changed: true, fresh: true}); err != nil || len(j.Outgrown) != 0 {
t.Fatalf("a changed and a new manifest: %+v %v", j, err)
}
@@ -142,7 +142,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// manifest a judge refuses in full, its judge pass fails on the verdicts state, and the next pass, which knows nothing
// of that take-in (a start, a pass kicked by a message), still refuses it in full, never as a warning, and ends the key.
ix.Unjudged = newMem()
keptDigest, err := ix.keepManifest(ctx, "kept", `{"module":"kept","version":"1","requires":["amqp"]}`)
keptDigest, err := ix.keepManifest(ctx, "kept", `{"module":"kept","version":"1.0","requires":["amqp"]}`)
if err != nil {
t.Fatal(err)
}
@@ -182,7 +182,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
accepted, ids = append(accepted, a), append(ids, id)
return nil
}
moved := hold("moved", `{"module":"moved","version":"1","requires":[`+
moved := hold("moved", `{"module":"moved","version":"1.0","requires":[`+
`{"name":"cache","storage":"key-value","template":"state","options":{"history":8}},`+
`{"name":"audit","storage":"stream","template":"log","options":{"max-mib":64}}]}`)
for pass := 0; pass < 2; pass++ {
@@ -208,7 +208,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
t.Fatalf("said accepted %+v", accepted)
}
// DLV-I9 (design 25 § 12.2): a manifest asking for none, newly accepted, is said too, its entries an empty list.
plain := hold("plain", `{"module":"plain","version":"1"}`)
plain := hold("plain", `{"module":"plain","version":"1.0"}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{plain: true}); err != nil {
t.Fatal(err)
}
@@ -217,8 +217,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
}
// Review round 1 of issue 1966: a trunk's held manifest judged by its digest (indexer_judge, judgeStale) is said as the
// full pass says it, accepted or refused; a candidate's digest judged over the trunk is said nowhere.
byDigest := hold("bydigest", `{"module":"bydigest","version":"1"}`)
refusedBy := hold("refusedby", `{"module":"refusedby","version":"1","requires":["amqp"]}`)
byDigest := hold("bydigest", `{"module":"bydigest","version":"1.0"}`)
refusedBy := hold("refusedby", `{"module":"refusedby","version":"1.0","requires":["amqp"]}`)
saidBefore := len(said)
for module, digest := range map[string]string{"bydigest": byDigest, "refusedby": refusedBy} {
if _, err := ix.judgeOver(ctx, nil, module, digest); err != nil {
@@ -228,7 +228,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
if last := accepted[len(accepted)-1]; last.Digest != byDigest || len(said) != saidBefore+1 || said[len(said)-1].Digest != refusedBy {
t.Fatalf("judged by its digest, the trunk's manifest was said as %+v, refusals %d (were %d)", last, len(said), saidBefore)
}
candidate, err := ix.keepManifest(ctx, "bydigest", `{"module":"bydigest","version":"2"}`)
candidate, err := ix.keepManifest(ctx, "bydigest", `{"module":"bydigest","version":"2.0"}`)
if err != nil {
t.Fatal(err)
}
@@ -239,12 +239,12 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// judging pass says again and clears.
ix.Findings = newMem()
failing = 1
once := hold("once", `{"module":"once","version":"1"}`)
once := hold("once", `{"module":"once","version":"1.0"}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{once: true}); err != nil || accepted[len(accepted)-1].Module != "once" {
t.Fatalf("a publish failing once was not published again: %v %+v", err, accepted[len(accepted)-1])
}
failing = 2
flaky := hold("flaky", `{"module":"flaky","version":"1"}`)
flaky := hold("flaky", `{"module":"flaky","version":"1.0"}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{flaky: true}); err != nil {
t.Fatal(err)
}
@@ -277,13 +277,13 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
}
// DLV-I15: a change's renamed-from is judged against the trunk's manifest of the module: a name the trunk declares is
// accepted, one it does not is refused naming the key.
trunkRenamer := hold("renamer", `{"module":"renamer","version":"1","config":{"font-name":{"type":"string","description":"The font.","required":true,"runtime":true}}}`)
trunkRenamer := hold("renamer", `{"module":"renamer","version":"1.0","config":{"font-name":{"type":"string","description":"The font.","required":true,"runtime":true}}}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{trunkRenamer: true}); err != nil {
t.Fatal(err)
}
for raw, want := range map[string]string{
`{"module":"renamer","version":"1","config":{"font":{"type":"string","description":"The font.","required":true,"runtime":true,"renamed-from":"font-name"}}}`: "",
`{"module":"renamer","version":"1","config":{"font":{"type":"string","description":"The font.","required":true,"runtime":true,"renamed-from":"typeface"}}}`: `renamed from "typeface"`,
`{"module":"renamer","version":"1.0","config":{"font":{"type":"string","description":"The font.","required":true,"runtime":true,"renamed-from":"font-name"}}}`: "",
`{"module":"renamer","version":"1.0","config":{"font":{"type":"string","description":"The font.","required":true,"runtime":true,"renamed-from":"typeface"}}}`: `renamed from "typeface"`,
} {
head, err := ix.keepManifest(ctx, "renamer", raw)
if err != nil {
@@ -305,8 +305,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
ix.Packages.(*memStore).put(SeriesKey(sdkGo, "0.1"), value)
}
for raw, refusedAtMerge := range map[string]bool{
`{"module":"pkguser","version":"1","packages":{"@novox/ui":"0.1"}}`: true,
`{"module":"pkguser","version":"2","packages":{"` + sdkGo + `":"0.1"}}`: false,
`{"module":"pkguser","version":"1.0","packages":{"@novox/ui":"0.1"}}`: true,
`{"module":"pkguser","version":"2.0","packages":{"` + sdkGo + `":"0.1"}}`: false,
} {
head, err := ix.keepManifest(ctx, "pkguser", raw)
if err != nil {
@@ -328,8 +328,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// 1966/AC11: modules using state and none using logs but the foundation: logs counts 0, so a manifest of another
// module using logs is refused and the foundation's is accepted.
ix.OldSections = newMem()
hold("user", `{"module":"user","version":"1","state":["x"]}`)
hold("mesh-grants", `{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)
hold("user", `{"module":"user","version":"1.0","state":["x"]}`)
hold("mesh-grants", `{"module":"mesh-grants","version":"1.0","logs":[{"name":"l"}]}`)
if _, err := ix.JudgeAll(ctx, nil); err != nil {
t.Fatal(err)
}
@@ -348,7 +348,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
t.Fatalf("the old sections count %v, want state 1 and the rest 0", c)
}
// DLV-J2, DLV-J3 (ADR 0615 §1): one reads entry on the assigner's state counts 1 for the assigner and 0 for the indexer.
hold("reader", `{"module":"reader","version":"1","state":["x"],"reads":["mesh-delivery-assigner.wants"]}`)
hold("reader", `{"module":"reader","version":"1.0","state":["x"],"reads":["mesh-delivery-assigner.wants"]}`)
if _, err := ix.JudgeAll(ctx, nil); err != nil {
t.Fatal(err)
}
@@ -362,7 +362,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// Design 47 *A module's registration* (b229f5c4 lines 480-481): a template in the manifest of every part on the list is
// refused, the runner's included.
for _, part := range []string{"mesh-config", "mesh-delivery-pipeline"} {
d := hold(part, `{"module":"`+part+`","version":"1","requires":[{"name":"cache","storage":"key-value","template":"state"}]}`)
d := hold(part, `{"module":"`+part+`","version":"1.0","requires":[{"name":"cache","storage":"key-value","template":"state"}]}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{d: true}); err != nil {
t.Fatal(err)
}
@@ -374,8 +374,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault", "mesh-delivery-pipeline"}) {
t.Fatalf("the foundation is %v, want exactly the nine of ADR 0619 and the pipeline runner (ADR 0666 §4)", judge.Foundation)
}
late := hold("late", `{"module":"late","version":"1","logs":[{"name":"l"}]}`)
grants := hold("mesh-grants", `{"module":"mesh-grants","version":"2","logs":[{"name":"l"},{"name":"m"}]}`)
late := hold("late", `{"module":"late","version":"1.0","logs":[{"name":"l"}]}`)
grants := hold("mesh-grants", `{"module":"mesh-grants","version":"2.0","logs":[{"name":"l"},{"name":"m"}]}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{late: true, grants: true}); err != nil {
t.Fatal(err)
}
@@ -387,11 +387,11 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
}
// 1966/AC11: the sole user of an old section changes its manifest and keeps the section: accepted, since it was accepted
// with it (the count is the accepted set held from the last pass, not recounted from fresh digests).
hold("solo", `{"module":"solo","version":"1","objects":[{"name":"o","max-mib":1}]}`)
hold("solo", `{"module":"solo","version":"1.0","objects":[{"name":"o","max-mib":1}]}`)
if _, err := ix.JudgeAll(ctx, nil); err != nil {
t.Fatal(err)
}
solo2 := hold("solo", `{"module":"solo","version":"2","objects":[{"name":"o","max-mib":2}]}`)
solo2 := hold("solo", `{"module":"solo","version":"2.0","objects":[{"name":"o","max-mib":2}]}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{solo2: true}); err != nil {
t.Fatal(err)
}
@@ -403,7 +403,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
ix.Logf = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) }
held := ix.OldSections
ix.OldSections = unreadable{held}
retry := hold("late", `{"module":"late","version":"2","logs":[{"name":"l"}]}`)
retry := hold("late", `{"module":"late","version":"2.0","logs":[{"name":"l"}]}`)
if _, err := ix.JudgeAll(ctx, map[string]bool{retry: true}); err != nil {
t.Fatal(err)
}
@@ -415,9 +415,9 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
// event) while a newer build of the indexer is on the trunk, and is judged again at the next judge; once this program's
// build is the trunk's newest it is refused; a wait is written into the module's value as a refusal naming the fields.
ownFP := "src2:" + strings.Repeat("e", 64)
hold(Module, `{"module":"mesh-delivery-indexer","version":"1"}`)
hold(Module, `{"module":"mesh-delivery-indexer","version":"1.0"}`)
ix.ownFP = ownFP // this program started when the trunk held another build of the indexer than the one it holds now
later := hold("later", `{"module":"later","version":"1","a-field-from-later":1,"another":{"x":1}}`)
later := hold("later", `{"module":"later","version":"1.0","a-field-from-later":1,"another":{"x":1}}`)
said = nil
before := len(said)
j, err = ix.JudgeAll(ctx, map[string]bool{later: true})
@@ -450,7 +450,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
}
ix.ownFP = ownFP
// A renamed field is a known mistake: refused at once.
renamed := hold("renamed", `{"module":"renamed","version":"1","needs":{"x":"y"}}`)
renamed := hold("renamed", `{"module":"renamed","version":"1.0","needs":{"x":"y"}}`)
if j, err := ix.JudgeAll(ctx, map[string]bool{renamed: true}); err != nil || len(j.Waiting) != 1 || !meshcatalogclient.VerdictRefuses(verdict(renamed)) {
t.Fatalf("a renamed field: %+v %v", j, err)
}
@@ -513,8 +513,8 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
}
return digest
}
atStart := stale("tools", `{"module":"tools","version":"7"}`)
ownRefusal := stale("client", `{"module":"client","version":"7"}`)
atStart := stale("tools", `{"module":"tools","version":"7.0"}`)
ownRefusal := stale("client", `{"module":"client","version":"7.0"}`)
if _, _, err := ix.writeVerdict(ctx, ownRefusal, meshcatalog.VerdictsByDigestValue{Module: "client", State: meshcatalogclient.VerdictRefused,
Problems: []string{"x"}, Judge: ix.judgeID(), At: ptr(time.Now())}); err != nil {
t.Fatal(err)
@@ -522,13 +522,13 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
if _, err := ix.JudgeAtStart(ctx); err != nil || meshcatalogclient.VerdictRefuses(verdict(atStart)) || verdict(atStart).Judge != ix.judgeID() || !meshcatalogclient.VerdictRefuses(verdict(ownRefusal)) {
t.Fatalf("the start pass: %+v, this judge's own refusal %+v (%v)", verdict(atStart), verdict(ownRefusal), err)
}
byPull := stale("tools", `{"module":"tools","version":"8"}`)
byPull := stale("tools", `{"module":"tools","version":"8.0"}`)
pull := forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-tools", Number: 3, Branch: "main",
Manifests: map[string]string{"modules/tools": `{"module":"tools","version":"8"}`}}
Manifests: map[string]string{"modules/tools": `{"module":"tools","version":"8.0"}`}}
if _, err := ix.ApplyMessage(ctx, pull); err != nil || meshcatalogclient.VerdictRefuses(verdict(byPull)) || verdict(byPull).Judge != ix.judgeID() {
t.Fatalf("a pull carrying a digest an older judge refused: %+v %v", verdict(byPull), err)
}
byTool := stale("tools", `{"module":"tools","version":"9"}`)
byTool := stale("tools", `{"module":"tools","version":"9.0"}`)
if got := ix.JudgeAgain(ctx, "client", byTool); got.Stored || !strings.Contains(got.Answer, "tools's, not client's") || !meshcatalogclient.VerdictRefuses(verdict(byTool)) {
t.Fatalf("indexer_judge with another module's digest: %+v", got)
}
@@ -17,7 +17,7 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
const ledger = `{"name":"records","storage":"ledger","template":"ledger","options":{"max-mib":512},"types":[` +
`{"name":"decision","schema":{"title":{"type":"text"}},"operations":[{"name":"propose","effect":"set-fields","fields":["title"]}]}]}`
manifest := func(entries string) string {
return `{"module":"a","version":"1","requires":[` + entries + `]}`
return `{"module":"a","version":"1.0","requires":[` + entries + `]}`
}
for _, c := range []struct {
name string
@@ -30,7 +30,7 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
`{"name":"files","storage":"object-store","template":"objects","options":{"max-mib":2048}},` + ledger), nil},
// Design 47: every kept data may say its form and its catch-up tool, in an old section and in `requires` by template,
// and is accepted as read; one saying neither reads as before.
{"a form and a catch-up tool on every kept kind", `{"module":"a","version":"1","tools":["a_take_in"],` +
{"a form and a catch-up tool on every kept kind", `{"module":"a","version":"1.0","tools":["a_take_in"],` +
`"state":["seen",{"name":"modules","form":1,"catch-up":"a_take_in"}],"logs":[{"name":"acts","form":2}],` +
`"streams":[{"name":"forge","events":["merged"],"form":1,"catch-up":"a_take_in"}],` +
`"objects":[{"name":"revisions","max-mib":64,"form":1,"catch-up":"a_take_in"}],"requires":[` +
@@ -40,9 +40,9 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
`{"name":"blobs","storage":"object-store","template":"objects","options":{"max-mib":64},"form":4,"catch-up":"a_take_in"},` +
`{"name":"records","storage":"ledger","template":"ledger","options":{"max-mib":512},"form":3,"catch-up":"a_take_in","types":[` +
`{"name":"decision","schema":{"title":{"type":"text"}},"operations":[{"name":"propose","effect":"set-fields","fields":["title"]}]}]}]}`, nil},
{"a catch-up tool the module lacks", `{"module":"a","version":"1","tools":["a_read"],"logs":[{"name":"acts","form":1,"catch-up":"a_take_in"}]}`,
{"a catch-up tool the module lacks", `{"module":"a","version":"1.0","tools":["a_read"],"logs":[{"name":"acts","form":1,"catch-up":"a_take_in"}]}`,
[]string{"a's kept log \"acts\"", "catch-up tool \"a_take_in\" is not among the module's tools (a_read)"}},
{"a catch-up tool the module lacks, in requires", `{"module":"a","version":"1","tools":["a_read"],"requires":[` +
{"a catch-up tool the module lacks, in requires", `{"module":"a","version":"1.0","tools":["a_read"],"requires":[` +
`{"name":"holds","storage":"key-value","template":"state","form":1,"catch-up":"nonexistent"}]}`,
[]string{"a's kept state \"holds\"", "catch-up tool \"nonexistent\""}},
{"a form below 0", manifest(`{"name":"x","storage":"key-value","template":"state","form":-1}`), []string{"requires entry \"x\"", "its form is -1"}},
@@ -127,13 +127,13 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
"reader": {Module: "reader", Reads: []string{"owner.cache"}}}, ""},
// DLV-I14 (design 32): the packages section names a known package, by its name in its own language (the packages
// state's names, and a group member's at its head: known), in a well-formed series; through a judge request too.
{"packages naming a known package", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Packages: map[string]string{sdkGo: "0.2"}}}, ""},
{"packages naming a known package, by request", catalogue.Shelf{"user": {Module: "user", Packages: map[string]string{sdkGo: "0.2"}}}, ""},
{"packages naming a package by the builder's name", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Packages: map[string]string{"mesh-sdk/go": "0.1"}}},
{"packages naming a known package", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Version: "1.0", Packages: map[string]string{sdkGo: "0.2"}}}, ""},
{"packages naming a known package, by request", catalogue.Shelf{"user": {Module: "user", Version: "1.0", Packages: map[string]string{sdkGo: "0.2"}}}, ""},
{"packages naming a package by the builder's name", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Version: "1.0", Packages: map[string]string{"mesh-sdk/go": "0.1"}}},
`user's packages name "mesh-sdk/go", which no package of the mesh is named`},
{"packages naming an unknown package, by request", catalogue.Shelf{"user": {Module: "user", Packages: map[string]string{"@novox/ui": "0.1"}}},
{"packages naming an unknown package, by request", catalogue.Shelf{"user": {Module: "user", Version: "1.0", Packages: map[string]string{"@novox/ui": "0.1"}}},
`user's packages name "@novox/ui", which no package of the mesh is named`},
{"packages in an ill-formed series", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Packages: map[string]string{sdkGo: "v0.2"}}},
{"packages in an ill-formed series", catalogue.Shelf{"mesh-sdk": sdk, "user": {Module: "user", Version: "1.0", Packages: map[string]string{sdkGo: "v0.2"}}},
`user's packages accept "` + sdkGo + `" in the series "v0.2"`},
} {
got := strings.Join(append(BusStateCatalogueProblems(c.shelf), PackagesCatalogueProblems(c.shelf, known)...), "\n")
@@ -0,0 +1,33 @@
package judge
import (
"git.novox.be/novox/mesh-sdk/go/catalogue"
"strings"
"testing"
)
// Design 47 G §368, §386, §387, §89: the manifest validator reads the manifests' new words before any manifest uses them.
func TestTheJudgeReadsMilestoneGsWords(t *testing.T) {
for _, row := range []struct {
name string
raw string
want string // a word of the first problem; empty: accepted
}{
{name: "MAJOR.MINOR", raw: `{"module":"a","version":"1.4"}`},
{name: "a pre-release suffix", raw: `{"module":"a","version":"1.4-beta"}`, want: "version"},
{name: "three numbers", raw: `{"module":"a","version":"1.4.2"}`, want: "version"},
{name: "a findings state without its stream", raw: `{"module":"a","version":"1.0","state":["findings"]}`, want: "findings"},
{name: "a findings state with its stream and ask", raw: `{"module":"a","version":"1.0","state":["findings"],` +
`"streams":[{"name":"a-findings","events":["finding-written"],"retention":"history"}],"consumes":["mesh-conditions.findings-asked"]}`},
} {
raws := map[string][]byte{"a": []byte(row.raw)}
var refused map[string][]string
WithSeats([]catalogue.Seat{{Name: "thing", Scope: catalogue.ScopeMesh, Emits: []string{"finding-written"}}}, func() {
refused, _ = Shelf(raws, nil, 0)
})
got := strings.Join(refused["a"], "\n")
if (row.want == "") != (got == "") || !strings.Contains(got, row.want) {
t.Errorf("%s: refused with %q, want %q", row.name, got, row.want)
}
}
}
@@ -0,0 +1,54 @@
package judge
import (
"fmt"
"regexp"
"slices"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// The manifests' words milestone G adds, read before any manifest uses them (design 47 G §89: the manifest validator's
// reading of the new words is an edge of kind 4 that the delivery plan derives).
// majorMinor is a manifest's version: two numbers (design 47 G §368). No pre-release or custom suffix exists.
var majorMinor = regexp.MustCompile(`^[0-9]+\.[0-9]+$`)
// FindingsAsked is the event an observer consumes to say its findings again (design 47 G §387).
const FindingsAsked = "mesh-conditions.findings-asked"
// FindingStreamOf is the stream an observer says its findings on (design 47 G §383).
func FindingStreamOf(module string) string { return module + "-findings" }
// versionProblems refuses a manifest whose `version` is not two numbers MAJOR.MINOR, or is missing (design 47 G §368). The
// judge's entry (Manifest) holds a manifest to it; the parser the inputs of a fingerprint are read by does not.
func versionProblems(m catalogue.Manifest) []string {
if majorMinor.MatchString(m.Version) {
return nil
}
return []string{fmt.Sprintf("%s says its version as %q; a manifest names `version` as two numbers MAJOR.MINOR such as 1.4, "+
"and no pre-release or custom suffix exists (design 47 G §368)", m.Module, m.Version)}
}
// gWordsProblems are the problems of a manifest's words milestone G adds: the finding stream and
// ask of a module that declares a state `findings` (§387). The `*` emitter of `consumes` (§386) is judged with the
// other consumed patterns (consumePattern).
func gWordsProblems(m catalogue.Manifest) []string {
var problems []string
for _, s := range m.State {
if s.Name != "findings" {
continue
}
stream := FindingStreamOf(m.Module)
if !slices.ContainsFunc(m.Streams, func(d catalogue.StreamDeclaration) bool { return d.Name == stream }) {
problems = append(problems, fmt.Sprintf("%s declares the state findings without the stream %q its findings are said on "+
"(design 47 G §387)", m.Module, stream))
}
if !slices.Contains(m.Consumes, FindingsAsked) {
problems = append(problems, fmt.Sprintf("%s declares the state findings without consuming %q, with which the "+
"keeper asks its findings again (design 47 G §387)", m.Module, FindingsAsked))
}
break
}
return problems
}
@@ -45,6 +45,9 @@ func Manifest(raw []byte, predefined catalogue.PredefinedList) (m catalogue.Mani
if err != nil {
return catalogue.Manifest{}, problemsOf(err)
}
if problems = versionProblems(m); len(problems) > 0 {
return catalogue.Manifest{}, problems
}
return m, nil
}
@@ -20,30 +20,30 @@ func TestTheJudge(t *testing.T) {
raws map[string]string
want map[string]string // module: a word of its first problem; absent: accepted
}{
{name: "a valid manifest", raws: map[string]string{"a": `{"module":"a","version":"1"}`}},
{name: "a refused manifest", raws: map[string]string{"a": `{"module":"a","version":"1","provides":[{"name":"amqp"}]}`},
{name: "a valid manifest", raws: map[string]string{"a": `{"module":"a","version":"1.0"}`}},
{name: "a refused manifest", raws: map[string]string{"a": `{"module":"a","version":"1.0","provides":[{"name":"amqp"}]}`},
want: map[string]string{"a": `provides "amqp", which is not a provision`}},
{name: "a provision named as a credential's", raws: map[string]string{"a": `{"module":"a","version":"1","provides":[{"name":"secret-db","scope":"mesh"}]}`},
{name: "a provision named as a credential's", raws: map[string]string{"a": `{"module":"a","version":"1.0","provides":[{"name":"secret-db","scope":"mesh"}]}`},
want: map[string]string{"a": `secret-`}},
{name: "an unknown field", raws: map[string]string{"a": `{"module":"a","version":"1","a-field-from-later":1}`},
{name: "an unknown field", raws: map[string]string{"a": `{"module":"a","version":"1.0","a-field-from-later":1}`},
want: map[string]string{"a": `"a-field-from-later"`}},
// Design 47 G §23, §28: a step's status is kept in the registration and never in a manifest.
{name: "a manifest naming a step's status", raws: map[string]string{"a": `{"module":"a","version":"1","policy":{"lint":"bypass"}}`},
{name: "a manifest naming a step's status", raws: map[string]string{"a": `{"module":"a","version":"1.0","policy":{"lint":"bypass"}}`},
want: map[string]string{"a": `"policy"`}},
{name: "a manifest under another name", raws: map[string]string{"b": `{"module":"a","version":"1"}`},
{name: "a manifest under another name", raws: map[string]string{"b": `{"module":"a","version":"1.0"}`},
want: map[string]string{"b": `of module "a", not "b"`}},
{name: "a seat declared twice",
raws: map[string]string{
"one": `{"module":"one","version":"1","seats":[{"name":"thing","scope":"mesh"}]}`,
"two": `{"module":"two","version":"1","seats":[{"name":"thing","scope":"mesh"}]}`,
"ok": `{"module":"ok","version":"1"}`},
"one": `{"module":"one","version":"1.0","seats":[{"name":"thing","scope":"mesh"}]}`,
"two": `{"module":"two","version":"1.0","seats":[{"name":"thing","scope":"mesh"}]}`,
"ok": `{"module":"ok","version":"1.0"}`},
want: map[string]string{"one": "already declares", "two": "already declares"}},
// The lead's ruling 5 for A2h: a module that claims a seat and serves its verbs keeps tools of its own beside them
// (the enrolment's first-view tools, DLV-M6), and the judge takes it.
{name: "a claimed seat's module with tools of its own",
raws: map[string]string{
"def": `{"module":"def","version":"1","seats":[{"name":"enrolment","scope":"mesh","serves":["issue","again"]}]}`,
"enrolment": `{"module":"enrolment","version":"1","claims":[{"name":"enrolment","scope":"mesh","serves":["issue","again"]}],` +
"def": `{"module":"def","version":"1.0","seats":[{"name":"enrolment","scope":"mesh","serves":["issue","again"]}]}`,
"enrolment": `{"module":"enrolment","version":"1.0","claims":[{"name":"enrolment","scope":"mesh","serves":["issue","again"]}],` +
`"tools":["enrolment_first_views","enrolment_first_view_again"]}`}},
} {
raws := map[string][]byte{}
@@ -67,10 +67,10 @@ func TestTheJudge(t *testing.T) {
// A field the judge does not know waits in the indexer's judge and stays a refusal in the checker's (clause 3 of the
// record on a release's Approve); a renamed field and any other problem are refused by both.
waitRaws := map[string][]byte{
"later": []byte(`{"module":"later","version":"1","later-field":true,"other":1}`),
"renamed": []byte(`{"module":"renamed","version":"1","needs":{"x":"y"}}`),
"bad": []byte(`{"module":"bad","version":"1","provides":[{"name":"amqp"}]}`),
"ok": []byte(`{"module":"ok","version":"1"}`),
"later": []byte(`{"module":"later","version":"1.0","later-field":true,"other":1}`),
"renamed": []byte(`{"module":"renamed","version":"1.0","needs":{"x":"y"}}`),
"bad": []byte(`{"module":"bad","version":"1.0","provides":[{"name":"amqp"}]}`),
"ok": []byte(`{"module":"ok","version":"1.0"}`),
}
refused, waits, _ := ShelfWaiting(waitRaws, nil, 0, nil, nil)
if !reflect.DeepEqual(waits, map[string][]string{"later": {"later-field", "other"}}) || len(refused) != 2 || refused["renamed"] == nil || refused["bad"] == nil {
@@ -82,7 +82,7 @@ func TestTheJudge(t *testing.T) {
// The private network's manifest, which the mesh provides itself, is accepted (moved from mesh-sdk overlay's test).
if raw, err := json.Marshal(overlay.Manifest()); err != nil {
t.Fatal(err)
} else if _, problems := Manifest(raw, nil); len(problems) > 0 {
} else if _, problems := Manifest(versioned(t, raw), nil); len(problems) > 0 {
t.Errorf("the provided private network is refused: %v", problems)
}
shelf := catalogue.Shelf{"nats": {}, "mesh-nats": {}, "a.b": {}}
@@ -103,8 +103,8 @@ func TestTheJudge(t *testing.T) {
// What cmd/mesh-judge answers the checker (ADR 0578 §2): a composer request in, a verdict out, each manifest accepted
// or refused; a manifest left as a digest is a request it cannot run.
r := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{
"a": {Raw: json.RawMessage(`{"module":"a","version":"1"}`)},
"b": {Raw: json.RawMessage(`{"module":"b","version":"1","requires":["amqp"]}`)},
"a": {Raw: json.RawMessage(`{"module":"a","version":"1.0"}`)},
"b": {Raw: json.RawMessage(`{"module":"b","version":"1.0","requires":["amqp"]}`)},
}}}}
v, err := Request(r)
if err != nil || v.V != composer.Version || len(v.Cases) != 1 || v.SDK != Identity() || !v.Cases[0].Manifests["a"].Accepted ||
@@ -116,13 +116,13 @@ func TestTheJudge(t *testing.T) {
// state, which a trunk module uses, is accepted.
old := composer.Request{V: composer.Version, Cases: []composer.Case{
{Name: "change", Manifests: map[string]composer.Manifest{
"user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"]}`)},
"late": {Raw: json.RawMessage(`{"module":"late","version":"1","logs":[{"name":"l"}]}`)},
"newer": {Raw: json.RawMessage(`{"module":"newer","version":"1","state":["y"]}`)},
"mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)}}},
"user": {Raw: json.RawMessage(`{"module":"user","version":"1.0","state":["x"]}`)},
"late": {Raw: json.RawMessage(`{"module":"late","version":"1.0","logs":[{"name":"l"}]}`)},
"newer": {Raw: json.RawMessage(`{"module":"newer","version":"1.0","state":["y"]}`)},
"mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1.0","logs":[{"name":"l"}]}`)}}},
{Name: "trunk", Manifests: map[string]composer.Manifest{
"user": {Raw: json.RawMessage(`{"module":"user","version":"1","state":["x"]}`)},
"mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1","logs":[{"name":"l"}]}`)}}}}}
"user": {Raw: json.RawMessage(`{"module":"user","version":"1.0","state":["x"]}`)},
"mesh-grants": {Raw: json.RawMessage(`{"module":"mesh-grants","version":"1.0","logs":[{"name":"l"}]}`)}}}}}
ov, err := Request(old)
if err != nil {
t.Fatal(err)
@@ -137,3 +137,19 @@ func TestTheJudge(t *testing.T) {
t.Fatal("a manifest left as a digest was judged")
}
}
// versioned is a manifest whose `version` is a MAJOR.MINOR (design 47 G §368): the private network's manifest is
// mesh-networking's, which G's change gives its version.
func versioned(t *testing.T, raw []byte) []byte {
t.Helper()
var fields map[string]any
if err := json.Unmarshal(raw, &fields); err != nil {
t.Fatal(err)
}
fields["version"] = "1.0"
out, err := json.Marshal(fields)
if err != nil {
t.Fatal(err)
}
return out
}
@@ -10,7 +10,7 @@ import (
// knowledge is mesh-knowledge's manifest as ADR 0592 has it: an object bucket granted by its trusted value readers (a
// trusted file takes it), and
// two streams that keep their history.
const knowledge = `{"module":"mesh-knowledge","version":"1",
const knowledge = `{"module":"mesh-knowledge","version":"1.0",
"resources":[{"id":"state","type":"directory","mode":"0700","place":"."},
{"id":"readers","trusted":true,"type":"file","path":"${dir:state}/readers.json","mode":"0600",
"merge":"json","takes-settings":["readers"],"content":"{\"readers\": {}}"}],
@@ -41,7 +41,7 @@ func TestObjectBucketsAreJudgedAsADR0592Says(t *testing.T) {
}
}
// A bucket whose name on the bus starts with what the mesh derives names with is refused.
if _, err := ParseManifest([]byte(`{"module":"kv","version":"1","objects":[{"name":"x","max-mib":1}]}`)); err == nil ||
if _, err := ParseManifest([]byte(`{"module":"kv","version":"1.0","objects":[{"name":"x","max-mib":1}]}`)); err == nil ||
!strings.Contains(err.Error(), "KV_") {
t.Errorf("a bucket kv_x was taken: %v", err)
}
@@ -54,7 +54,7 @@ func TestObjectBucketsAreJudgedAsADR0592Says(t *testing.T) {
return m
}
owner := parse(knowledge)
reader := parse(`{"module":"records-page","version":"1","reads":["mesh-knowledge.sections"]}`)
reader := parse(`{"module":"records-page","version":"1.0","reads":["mesh-knowledge.sections"]}`)
shelf := catalogue.Shelf{"mesh-knowledge": owner, "records-page": reader}
if p := objectProblemsOf(CatalogueProblems(shelf)); len(p) > 0 {
t.Fatalf("a granted read was refused: %v", p)
+1 -1
View File
@@ -2536,7 +2536,7 @@ func parseManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.M
}
m.SetPredefined(predefined)
var problems []string
problems := gWordsProblems(m)
if !name.MatchString(m.Module) {
problems = append(problems, fmt.Sprintf(
"%q is not a usable module name: lower-case letters, digits, dashes and dots", m.Module))
File diff suppressed because it is too large Load Diff