Issue 127 stood because nothing compared the two halves. Every manifest was well-formed on its own and every derivation correct on its own, and no cross-module subscription in the mesh matched anything — a subscription that matches nothing is not an error, it is silence. Two checks, because the mistake is possible at two scales. Per manifest: an event is a local name, and `module.` is refused with the name to write instead. A module emitting under what reads as another module's name is refused too, pointing at the seat, where a name outlives whoever holds it. Across the catalogue: where a consumed event's emitter is present, it must emit that event. It cannot demand a live emitter for everything — a module lives in its own repository and may be installed long before the one whose events it wants — so the rule is narrower and still catches this. It found two real dangling subscriptions the moment it ran. Wildcards were undecided and two manifests needed them: `*` is one name and `**` is the rest, spelled the mesh's way and derived to `>` here and `#` on the old bus. A manifest naming either would stop being true when the wire changed, which is the whole reason names are local. And the field documentation taught the old form, examples included — which is why the drift was uniform across 37 manifests rather than scattered. Nobody was guessing; everybody followed the comment.
122 lines
3.5 KiB
Go
122 lines
3.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// **A manifest holds no subject** (novox/hq design 29 §1).
|
|
//
|
|
// A module names its events, tools and seats locally, and the mesh derives where they land. The
|
|
// property that buys: reorganise the subject space and every manifest in the catalogue is still
|
|
// correct. It holds today by construction — nothing reads a subject from a manifest — and a rule
|
|
// held by construction is one a later field breaks quietly, with the symptom appearing as a
|
|
// permission that does not match a subject rather than as a manifest that was wrong.
|
|
func TestNoManifestContainsASubject(t *testing.T) {
|
|
root := filepath.Join("..", "..", "..", "mesh-catalog", "modules")
|
|
entries, err := os.ReadDir(root)
|
|
if err != nil {
|
|
t.Skipf("catalogue sibling not present: %v", err)
|
|
}
|
|
|
|
// Anything in the mesh's own subject space, and anything shaped like a wire address.
|
|
subject := regexp.MustCompile(`^(mesh|\$JS)\.[a-zA-Z0-9_*>.-]+$`)
|
|
|
|
var found []string
|
|
var walk func(module string, path string, v any)
|
|
walk = func(module, path string, v any) {
|
|
switch t := v.(type) {
|
|
case string:
|
|
if subject.MatchString(t) {
|
|
found = append(found, module+" "+path+" = "+t)
|
|
}
|
|
case map[string]any:
|
|
for k, inner := range t {
|
|
walk(module, path+"."+k, inner)
|
|
}
|
|
case []any:
|
|
for _, inner := range t {
|
|
walk(module, path+"[]", inner)
|
|
}
|
|
}
|
|
}
|
|
|
|
checked := 0
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(root, e.Name(), "module.json"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var m any
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Errorf("%s: %v", e.Name(), err)
|
|
continue
|
|
}
|
|
checked++
|
|
walk(e.Name(), "", m)
|
|
}
|
|
if checked == 0 {
|
|
t.Skip("no manifests read")
|
|
}
|
|
if len(found) > 0 {
|
|
t.Errorf("a manifest names a subject, so reorganising the subject space would mean "+
|
|
"editing the catalogue:\n %s", strings.Join(found, "\n "))
|
|
}
|
|
t.Logf("%d manifests hold no subject", checked)
|
|
}
|
|
|
|
// **Every module's event names are what design 29 says, across the whole catalogue.**
|
|
//
|
|
// The rule above holds by construction and turned out to be weaker than it reads: a manifest holds
|
|
// no subject, and every manifest in the catalogue still held the old bus's routing key, which
|
|
// derives into a namespace nobody owns (novox/hq 04-ISSUES/127). Nothing failed — the services
|
|
// started and none of them reacted. This is the check that was missing.
|
|
func TestEveryManifestsEventNamesAreLocal(t *testing.T) {
|
|
manifests := theCatalogue(t)
|
|
|
|
var problems []string
|
|
for _, m := range manifests {
|
|
problems = append(problems, EventProblems(m)...)
|
|
}
|
|
if len(problems) > 0 {
|
|
t.Fatalf("the catalogue holds %d event name(s) the mesh would derive wrongly:\n %s",
|
|
len(problems), strings.Join(problems, "\n "))
|
|
}
|
|
}
|
|
|
|
// theCatalogue is every manifest beside this checkout, parsed the way registration parses one.
|
|
func theCatalogue(t *testing.T) []Manifest {
|
|
t.Helper()
|
|
root := filepath.Join("..", "..", "..", "mesh-catalog", "modules")
|
|
entries, err := os.ReadDir(root)
|
|
if err != nil {
|
|
t.Skipf("catalogue sibling not present: %v", err)
|
|
}
|
|
var out []Manifest
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(root, e.Name(), "module.json"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var m Manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatalf("%s: %v", e.Name(), err)
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
if len(out) == 0 {
|
|
t.Skip("no manifests found beside this checkout")
|
|
}
|
|
return out
|
|
}
|