Take the catalogue from mesh-sdk in internal/recordmirror, finish the own-manifest tests, vendor/modules.txt for the pin, the capture note and the slow-package comment (issue 1381, 6 of 6 import swaps)

With this commit no Go file outside vendor/ and the two packages about to go imports internal/catalogue, internal/overlay or mesh-host/validate, go.mod and vendor/modules.txt agree on mesh-sdk 14fd5cb68fd1, and the tree builds. testdata/beside/CAPTURED names the pinned SDK commit (conformance/events is unchanged since aa68b673). merge-check.sh's comment no longer names internal/catalogue as a slow package.
This commit is contained in:
2026-10-20 08:29:11 +00:00
parent af267cb557
commit 928942c78c
10 changed files with 449 additions and 8 deletions
@@ -0,0 +1,77 @@
package ownmanifest
import (
"strings"
"testing"
. "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// agentBuilt is the build agent as the mesh holds it once built: its image pinned.
func agentBuilt(t *testing.T) Manifest {
t.Helper()
return catalogueResolved(t, "build-agent", Built{Name: "server", Kind: ArtifactImage,
Reference: "mesh-builder@sha256:" + strings.Repeat("a", 64)})
}
func TestGenesisAssignsTheBuildAgentWithNothingProvidingTheRegistry(t *testing.T) {
agent := agentBuilt(t)
if !agent.IsOptional("npm-package-registry") {
t.Fatalf("the build agent's npm-package-registry is hard (ADR 0336 §1): %v", agent.Requires)
}
if agent.IsOptional("artifact-store") {
t.Fatal("the build agent's artifact-store is optional; it stays hard")
}
store := map[string][]Provider{"artifact-store": {
{Node: "anchor", At: "anchor.internal", Module: "distribution", Serves: map[string]any{"port": float64(5000)}}}}
books := shelf(agent, catalogueManifest(t, "gitea"), catalogueManifest(t, "distribution"))
// Genesis's node at step 13: the store answers, nothing provides the registry.
got, err := Resolve(books, []string{"build-agent"}, reachable(), World{Offered: store})
if err != nil {
t.Fatalf("assigning the build agent with no registry was refused: %v", err)
}
if have := strings.Join(names(got), " "); have != "build-agent" {
t.Fatalf("assigning the build agent put %s on the machine; nothing is installed for the registry", have)
}
if len(got.Absent) != 1 || got.Absent[0] != (Absent{Consumer: "build-agent", Requirement: "npm-package-registry"}) {
t.Fatalf("the registry is not listed unbound for the build agent: %+v", got.Absent)
}
out, err := got.Declaration(Rendering{ArtifactStore: "anchor.internal:5000",
Needed: map[string]map[string]string{"build-agent": {"broker": "sealed-broker"}}})
if err != nil {
t.Fatalf("the build agent does not compose with the registry unbound: %v", err)
}
for _, id := range []string{"build-agent." + BoundID("npm-package-registry"), "build-agent." + SecretID("npm-package-registry"),
"build-agent." + NeedID("genesis-package-registry")} {
if fileNamed(out, id) != nil {
t.Errorf("%s was delivered with the registry unbound and no genesis document given", id)
}
}
// The genesis document, once the installer seals it at the packages step (ADR 0336 §3).
out, err = got.Declaration(Rendering{ArtifactStore: "anchor.internal:5000",
Needed: map[string]map[string]string{"build-agent": {"broker": "sealed-broker", "genesis-package-registry": "sealed-genesis"}}})
if err != nil {
t.Fatal(err)
}
doc := fileNamed(out, "build-agent."+NeedID("genesis-package-registry"))
if doc == nil || doc["sealed"] != "sealed-genesis" || !strings.HasSuffix(doc["path"].(string), "/genesis-package-registry.json") {
t.Fatalf("the genesis document is not delivered where the agent reads it: %v", doc)
}
// The gitea module assigned: the requirement binds, with no act on the build agent (ADR 0336 §2).
registry := map[string][]Provider{"artifact-store": store["artifact-store"], "npm-package-registry": {
{Node: "anchor", At: "anchor.internal", Module: "gitea",
Serves: map[string]any{"port": float64(3000), "scheme": "http", "npm-path": "/api/packages/novox/npm/"}}}}
bound, err := Resolve(books, []string{"build-agent"}, reachable(), World{Offered: registry})
if err != nil {
t.Fatal(err)
}
if n := boundFrom(t, bound, "build-agent", "npm-package-registry"); n.Module != "gitea" {
t.Fatalf("bound to %s", n.Module)
}
if len(bound.Absent) != 0 {
t.Fatalf("the registry is still listed unbound once gitea is assigned: %+v", bound.Absent)
}
}
+109
View File
@@ -0,0 +1,109 @@
package ownmanifest
import (
"os"
"path/filepath"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-controller/internal/beside"
)
// Moved from internal/catalogue (issue 1381) with the tests below; each helper is the one the old package's tests
// shared, over the SDK's exported API.
var bundleDigest = "sha256:" + strings.Repeat("b", 64)
func fileNamed(out []map[string]any, id string) map[string]any {
for _, r := range out {
if r["id"] == id {
return r
}
}
return nil
}
func shelf(ms ...catalogue.Manifest) map[string]catalogue.Manifest {
out := map[string]catalogue.Manifest{}
for _, m := range ms {
out[m.Module] = m
}
return out
}
func reachable() catalogue.Node {
return catalogue.Node{Name: "workstation", Site: "house", At: "workstation.internal",
Capabilities: map[string]bool{"seat": true, "container-runtime": true}}
}
func names(r catalogue.Resolution) []string {
var out []string
for _, m := range r.Modules {
out = append(out, m.Module)
}
return out
}
func boundFrom(t *testing.T, r catalogue.Resolution, consumer, provision string) catalogue.Needed {
t.Helper()
for _, n := range r.Needs {
if n.For == consumer && n.Name == provision {
return n
}
}
t.Fatalf("no binding of %s for %s: %+v", consumer, provision, r.Needs)
return catalogue.Needed{}
}
// moduleTree is where a module is read from: this repository's own modules/<module> when it lives whole here
// (novox/hq ADR 0420 §1, issue 1019: build-agent and route-proxy), with this repository as the tree root;
// otherwise the catalogue beside, with the catalogue as the root.
func moduleTree(t *testing.T, module string) (root, dir string) {
t.Helper()
own := filepath.Join("..", "..", "modules", module)
if _, err := os.Stat(filepath.Join(own, "module.json")); err == nil {
return filepath.Join("..", ".."), own
}
return beside.Dir(t, "mesh-catalog"), filepath.Join(beside.Catalogue(t), module)
}
func catalogueManifest(t *testing.T, module string) catalogue.Manifest {
t.Helper()
root, dir := moduleTree(t, module)
m, err := catalogue.ReadTreeManifest(root, dir)
if err != nil {
t.Fatalf("%s does not parse:\n%v", module, err)
}
return m
}
// catalogueResolved is a catalogue module as the mesh holds it once built: the manifest as written, resolved against
// what the build produced besides the archive its templates are packed into (novox/hq ADR 0349 §2).
func catalogueResolved(t *testing.T, module string, built ...catalogue.Built) catalogue.Manifest {
t.Helper()
root, dir := moduleTree(t, module)
m, err := catalogue.ReadTreeManifestWritten(root, dir)
if err != nil {
t.Fatalf("%s does not parse:\n%v", module, err)
}
if name := catalogue.TemplatesArtifact(m); name != "" {
read, err := catalogue.ReadTemplates(m, dir)
if err != nil {
t.Fatalf("%s: %v", module, err)
}
// By the path inside the archive, as the build reads it back, not by the path in the module's directory.
texts := map[string]string{}
for path, text := range read {
texts[strings.TrimPrefix(path, m.TemplatesDir()+"/")] = text
}
built = append(built, catalogue.Built{Name: name, Kind: catalogue.ArtifactArchive,
Reference: catalogue.ArtifactStoreScheme + module + "/" + name + "/blobs/" + bundleDigest, Digest: bundleDigest,
Texts: texts})
}
held, err := m.Resolve(built)
if err != nil {
t.Fatalf("%s does not resolve against its own build: %v", module, err)
}
return held
}
@@ -0,0 +1,123 @@
package ownmanifest
import (
"os"
"path/filepath"
"strings"
"testing"
. "git.novox.be/novox/mesh-sdk/go/catalogue"
)
func TestTheControllersOwnManifestClaimsItsSeat(t *testing.T) {
// The seat test above fabricates manifests, so deleting the claim from the real module.json
// would fail nothing (novox/hq issue 059's review). This binds the one manifest this
// repository owns: the controller claims the mesh-scoped seat named after its server
// (ADR 0079), or the one-controller property is convention again.
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatalf("the controller's own manifest is unreadable: %v", err)
}
m, err := ParseManifest(raw)
if err != nil {
t.Fatalf("the controller's own manifest does not parse: %v", err)
}
for _, c := range m.Claims {
if c.Name == "mesh-controller" && c.At() == ScopeMesh {
return
}
}
t.Fatalf("module.json no longer claims the mesh-scoped mesh-controller seat: %+v", m.Claims)
}
// **The control plane's own manifest, composed through the whole path.**
//
// The store was given 6852 and the broker's plain port 5679 (the control-node's migration, novox/hq
// 04-ISSUES/102). The control plane's own connections are sealed at genesis with the ports genesis
// wrote; what its container is told beside them is where this machine put the store and the
// broker now, read from the node's settings exactly as every consumer's binding is.
func TestTheControlPlanesOwnAddressesFollowTheNodesPorts(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
m, err := ParseManifest(raw)
if err != nil {
t.Fatalf("the control plane's own manifest does not parse:\n%v", err)
}
// The manifest itself names them now; withSeatPorts is a no-op on it, and this holds it so.
for _, r := range m.Resources {
if r["type"] != "process" {
continue
}
env, _ := r["env"].(map[string]any)
for key, want := range seatPorts {
if env[key] != want {
t.Errorf("module.json says %s=%v, not %q", key, env[key], want)
}
}
}
control, err := m.Resolve([]Built{{
Name: "controller", Kind: ArtifactBundle,
Reference: ArtifactStoreScheme + "mesh-controller/controller@sha256:" + strings.Repeat("c", 64),
Digest: "sha256:" + strings.Repeat("c", 64),
}})
if err != nil {
t.Fatal(err)
}
r := Resolution{Node: "anchor", Modules: []Manifest{control}}
needed := map[string]map[string]string{"mesh-controller": {}}
for name := range m.OwnSecrets {
needed["mesh-controller"][name] = "sealed-" + name
}
out, err := r.Declaration(Rendering{
Needed: needed,
ArtifactStore: "anchor.internal:5100",
Seats: map[string]map[int]int{
"mesh-store": {5432: 6852},
"mesh-broker": {5671: 5671, 5672: 5679, 15672: 15673},
},
})
if err != nil {
t.Fatalf("the control plane does not compose: %v", err)
}
server := fileNamed(out, "mesh-controller.controller")
if server == nil {
t.Fatalf("the control plane's process is not in the declaration: %v", out)
}
env, _ := server["env"].(map[string]any)
for key, want := range map[string]string{
"MESH_STORE_INVENTORY_PORT": "6852",
"MESH_STORE_IDENTITY_PORT": "6852",
"MESH_STORE_LICENCES_PORT": "6852",
"MESH_BROKER_MANAGEMENT_PORT": "15673",
"MESH_BROKER_ADDRESS_PORT": "5671",
} {
if env[key] != want {
t.Errorf("the control plane is told %s=%v; the node put it on %s", key, env[key], want)
}
}
if got := server["source"]; got != "anchor.internal:5100/mesh-controller/controller@sha256:"+strings.Repeat("c", 64) {
t.Errorf("the control plane's own bundle is fetched from %v, not routed through the store", got)
}
// And on a mesh where the foundation is where genesis raised it, nothing is added.
out, err = r.Declaration(Rendering{Needed: needed, ArtifactStore: "anchor.internal:5100"})
if err != nil {
t.Fatal(err)
}
env, _ = fileNamed(out, "mesh-controller.controller")["env"].(map[string]any)
if env["MESH_STORE_INVENTORY_PORT"] != "" {
t.Errorf("with no settings, the control plane is told %v", env)
}
}
// seatPorts is what the control plane's manifest says beside each sealed connection: the port
// this machine put the seat's holder at (novox/hq 04-ISSUES/102).
var seatPorts = map[string]string{
"MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}",
"MESH_STORE_IDENTITY_PORT": "${seat:mesh-store:5432}",
"MESH_STORE_LICENCES_PORT": "${seat:mesh-store:5432}",
"MESH_BROKER_MANAGEMENT_PORT": "${seat:mesh-broker:15672}",
"MESH_BROKER_ADDRESS_PORT": "${seat:mesh-broker:5671}",
}
@@ -0,0 +1,88 @@
package ownmanifest
import (
"testing"
. "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// The public issuer is the proxy's own fact (novox/hq ADR 0226), and the folding of it must not
// move the proxy's account.
//
// route-proxy keeps each ACME authority's account and certificates in a directory named after a
// digest of the directory URL and of the root bundle its `trust` container copies in
// (examples/route-proxy, forThisAuthority). Until ADR 0226 the URL was rendered from a binding to
// `public-acme`'s `acme-ca`, spelled with the port. A URL spelled any other way — even the same
// authority without `:443` — or a root bundle from another image is a new authority to the proxy:
// a new account, and every routed name ordered again, on two machines at once, against Let's
// Encrypt's rate limits. So what the module now states is held to exactly what the binding rendered.
// renderedBeforeTheFold is route-proxy's acme.env as the binding to public-acme rendered it on every
// machine running the proxy, read from the controller's plan on 2026-10-06.
const renderedBeforeTheFold = "ACME_DIRECTORY=https://acme-v02.api.letsencrypt.org:443/directory\n" +
"ACME_ROOTS=https://acme-v02.api.letsencrypt.org:443\n" +
"ACME_ROOTS_PATH=\n"
// trustImage is the image whose system bundle becomes the public root the account directory is
// named after. Moving it renames that directory.
const trustImage = "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b"
func TestRouteProxyKeepsItsPublicAccountDirectory(t *testing.T) {
m := catalogueManifest(t, "route-proxy")
for _, r := range m.Requires {
if r == "acme-ca" {
t.Fatal("route-proxy still asks for acme-ca; the public issuer is its own fact (ADR 0226)")
}
}
// As a build would leave it: each artifact a container names resolved to an image. Which image
// does not matter to the file checked here.
for _, res := range m.Resources {
if artifact, ok := res["artifact"].(string); ok {
delete(res, "artifact")
res["image"] = "registry.invalid/route-proxy/" + artifact +
"@sha256:1111111111111111111111111111111111111111111111111111111111111111"
}
}
r := Resolution{
Node: "anchor",
Modules: []Manifest{m},
Needs: []Needed{{
Name: "internal-acme-ca", From: "home", At: "home.internal", For: "route-proxy",
Serves: map[string]any{
"port": float64(9000), "path": "/acme/acme/directory", "roots": "/roots.pem",
},
}},
}
// Its own broker credential, sealed as the mesh would; what it is does not matter here.
out, err := r.Declaration(Rendering{Needed: map[string]map[string]string{
"route-proxy": {"broker": "sealed"}}})
if err != nil {
t.Fatalf("route-proxy could not be composed for a machine: %v", err)
}
env := fileNamed(out, "route-proxy.acme-env")
if env == nil {
t.Fatalf("nothing writes the public issuer's environment: %v", out)
}
if got, _ := env["content"].(string); got != renderedBeforeTheFold {
t.Fatalf("acme.env changed, which moves the proxy's account and reorders every certificate:\n"+
"got %q\nwant %q", got, renderedBeforeTheFold)
}
if fileNamed(out, "route-proxy.bound-acme-ca") != nil {
t.Error("a binding to acme-ca is still written")
}
var trust string
if m.Build != nil {
for _, a := range m.Build.Artifacts {
if a.Name == "trust" {
trust = a.From
}
}
}
if trust != trustImage {
t.Errorf("the trust image is %q, not %q: its system bundle is the public root the account "+
"directory is named after, so moving it reorders every certificate. Move it only with a "+
"plan for the account (ADR 0226)", trust, trustImage)
}
}
+41
View File
@@ -0,0 +1,41 @@
package ownmanifest
import (
"os"
"path/filepath"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-controller/internal/beside"
)
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
//
// Read from the catalogue (internal/beside) and from this repository's own manifest, the two places a
// manifest lives (ADR 0069). The private-network module's manifest is composed in code, and its claim
// is checked where it is composed.
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
paths, _ := filepath.Glob(filepath.Join(beside.Catalogue(t), "*", "module.json"))
if len(paths) == 0 {
t.Fatal("no manifests in the catalogue, so this proved nothing")
}
paths = append(paths, "../../module.json")
var checked int
for _, path := range paths {
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
// The SDK's claimProblems is unexported; the parser runs it on every manifest whose claims are well formed
// (go/catalogue manifest.go), so a manifest that parses claims only seats the mesh defines or declares.
m, err := catalogue.ParseManifest(raw)
if err != nil {
t.Errorf("%s: %v", path, err)
continue
}
checked += len(m.Claims)
}
if checked == 0 {
t.Fatal("no claims were checked, so this proved nothing")
}
}
+1 -1
View File
@@ -7,8 +7,8 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/catalogue"
"github.com/novox/mesh-controller/internal/inventory"
)
+1 -2
View File
@@ -17,7 +17,7 @@ import (
// mayNameAMirror are the files of the controller that may name a record mirror's bucket outside their tests:
// where the buckets are defined, asserted and granted (internal/broker), the list a reader names in `reads`
// (internal/catalogue/state.go), and each mirror's own writer (this package and the loop that runs it). The
// (the catalogue's state.go, now mesh-sdk's), and each mirror's own writer (this package and the loop that runs it). The
// audit of a mirror against its store (ADR 0289 §4, issue 419) joins this list when it is built.
var mayNameAMirror = []string{
// The chain opens its buckets, the assignments' among them, which stopped being a mirror when the chain took them
@@ -28,7 +28,6 @@ var mayNameAMirror = []string{
// The grant of a machine's own keys of the assignments mirror to its provisioner client (design 57 P3).
"internal/broker/provisioner.go",
"internal/broker/writers.go",
"internal/catalogue/state.go",
}
// The packages whose names stand for a mirror, by import path, and the names in each.