diff --git a/checks/manifests/main_test.go b/checks/manifests/main_test.go index 0cee3e20..252fa559 100644 --- a/checks/manifests/main_test.go +++ b/checks/manifests/main_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" ) // The tree's manifests are ones a judge can read, and no module is named twice. A row is a catalogue of its own: a @@ -48,3 +50,45 @@ func TestEveryManifestDecodesAndNamesOneModule(t *testing.T) { t.Fatalf("the catalogue holds manifests no judge can read:\n%s", strings.Join(real, "\n")) } } + +// No manifest of this catalogue claims the seat mesh-catalog: the indexer of the delivery holds it, and the module that +// held it before has dropped its claim (ADR 0604 §6). A row is a tree of its own: a manifest claiming it is named. +func TestNoManifestClaimsTheSeatMeshCatalog(t *testing.T) { + claimants := func(root string) []string { + files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) + if err != nil { + t.Fatal(err) + } + var out []string + for _, f := range files { + raw, err := os.ReadFile(f) + if err != nil { + t.Fatal(err) + } + m, err := catalogue.Decode(raw) + if err != nil { + continue // Problems names it + } + for _, c := range m.Claims { + if c.Name == "mesh-catalog" { + out = append(out, m.Module) + } + } + } + return out + } + row := t.TempDir() + if err := os.MkdirAll(filepath.Join(row, "modules", "a"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(row, "modules", "a", "module.json"), + []byte(`{"module":"a","version":"1","claims":[{"name":"mesh-catalog","scope":"mesh"}]}`), 0o644); err != nil { + t.Fatal(err) + } + if got := claimants(row); len(got) != 1 || got[0] != "a" { + t.Fatalf("a manifest claiming the seat is named, got %v", got) + } + if got := claimants(filepath.Join("..", "..")); len(got) != 0 { + t.Errorf("the catalogue's manifests %v claim the seat mesh-catalog: the indexer of the delivery holds it", got) + } +} diff --git a/modules/artifact-store-tools/README.md b/modules/artifact-store-tools/README.md index 1b747f8f..5a38db8f 100644 --- a/modules/artifact-store-tools/README.md +++ b/modules/artifact-store-tools/README.md @@ -61,7 +61,7 @@ record names leaves its platform manifests unrecorded with it. **What the definitions pin** (hq issue 323). A definition may pin an image of the store the mesh did not make: pushed before the mesh built it, and pinned under the store's current name or a name it had before. No record of the controller names it, yet a machine runs it, so `artifact_store_references` reads every -definition the catalogue holds (`mesh-catalog.catalog_modules`, then `catalog_module` for each, eight at +definition the catalogue holds (the seat mesh-catalog's verbs `modules`, then `module` for each, eight at a time and within twenty seconds) and finds every `/@sha256:…` in any of its strings, under each of the store's names: `artifact-store://`, the store's address as the controller answers it, and the names it had before. Those are the mesh's setting `older-store-names` (space-separated, `none` by diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/definitions.go b/modules/artifact-store-tools/cmd/artifact-store-tools/definitions.go index ef4ef966..d62bf5a6 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/definitions.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/definitions.go @@ -23,10 +23,11 @@ import ( // manifest's. const NamesFileEnv = "MESH_ARTIFACT_STORE_NAMES_FILE" -// CatalogueModules and CatalogueModule are the catalog's tools these definitions are read through. +// CatalogueModules and CatalogueModule are the verbs of the seat mesh-catalog these definitions are read through +// (ADR 0604 §3); the answers are catalogue.CatalogModules and catalogue.CatalogOne. const ( - CatalogueModules = "mesh-catalog.catalog_modules" - CatalogueModule = "mesh-catalog.catalog_module" + CatalogueModules = "seat:mesh-catalog.modules" + CatalogueModule = "seat:mesh-catalog.module" ) // DefinitionsTimeout bounds reading every definition, below the runtime's thirty-second call limit; what @@ -279,11 +280,8 @@ func (c *Catalogue) definition(m catalogueEntry, names StoreNames, nameKey strin return pins, "" } } - args := map[string]any{"module": m.Module} - if m.Commit != "" { - args["commit"] = m.Commit - } - raw, err := c.Ask(CatalogueModule, args) + // The verb takes no commit: it answers the trunk's module, and the answer names the commit it is at. + raw, err := c.Ask(CatalogueModule, map[string]any{"module": m.Module}) if err != nil { return nil, "could not be asked: " + err.Error() } @@ -293,6 +291,7 @@ func (c *Catalogue) definition(m catalogueEntry, names StoreNames, nameKey strin } var one struct { Module *struct { + Commit string `json:"commit"` Manifest json.RawMessage `json:"manifest"` } `json:"module"` Error string `json:"error"` @@ -301,9 +300,11 @@ func (c *Catalogue) definition(m catalogueEntry, names StoreNames, nameKey strin return nil, "not readable: " + firstOf(one.Error, errText(err), "no manifest in the answer") } pins := PinsIn(one.Module.Manifest, names) - if m.Commit != "" { + // Kept by the commit the answer names, which may be newer than the list's: a definition and its commit never + // change together. + if at := firstOf(one.Module.Commit, m.Commit); at != "" { c.mu.Lock() - c.kept[key] = pins + c.kept[m.Module+"@"+at+"|"+nameKey] = pins c.mu.Unlock() } return pins, "" diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/definitions_test.go b/modules/artifact-store-tools/cmd/artifact-store-tools/definitions_test.go index 316baf2c..c46cb12c 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/definitions_test.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/definitions_test.go @@ -211,7 +211,7 @@ func TestReferencesReadTheDefinitionsUnderTheStoresOlderNames(t *testing.T) { } } if before != 2 || after != 2 { - t.Errorf("asked catalog_module %d times, then %d: each definition at its commit is read once", before, after) + t.Errorf("asked the seat's module %d times, then %d: each definition at its commit is read once", before, after) } } diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go b/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go index c8a527d3..ea6fdccd 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go @@ -366,8 +366,8 @@ func (a *asked) ask(key string, body any) (json.RawMessage, error) { if a.failing[name] { return nil, fmt.Errorf("no answer") } - if args["commit"] != "c-"+name { - return nil, fmt.Errorf("asked %s at %v", name, args["commit"]) + if _, has := args["commit"]; has { + return nil, fmt.Errorf("asked %s at %v: the seat's verb takes no commit", name, args["commit"]) } manifest, has := a.definitions[name] if !has { @@ -375,7 +375,8 @@ func (a *asked) ask(key string, body any) (json.RawMessage, error) { break } // The catalogue's tool answers through the runtime as the protocol's content list. - raw, _ := json.Marshal(map[string]any{"module": map[string]any{"module": name, "manifest": json.RawMessage(manifest)}}) + raw, _ := json.Marshal(catalogue.CatalogOne{Module: catalogue.CatalogModule{ + CatalogEntry: catalogue.CatalogEntry{Module: name, Commit: "c-" + name}, Manifest: json.RawMessage(manifest)}}) return json.Marshal(map[string]any{"content": []map[string]any{{"type": "text", "text": string(raw)}}}) case "collect": if args["confirm"] == "true" { diff --git a/modules/artifact-store-tools/go.mod b/modules/artifact-store-tools/go.mod index dfdeecbe..f2499370 100644 --- a/modules/artifact-store-tools/go.mod +++ b/modules/artifact-store-tools/go.mod @@ -3,7 +3,7 @@ module artifactstoretools go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/artifact-store-tools/go.sum b/modules/artifact-store-tools/go.sum index 43cc4ec7..7679a5dc 100644 --- a/modules/artifact-store-tools/go.sum +++ b/modules/artifact-store-tools/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128 h1:aI1DhZAuqVBXbybSqhLNOGX5rUOrn9T0cHNCDUQF7IQ= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261102191058-f02f142af128/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/artifact-store-tools/module.json b/modules/artifact-store-tools/module.json index eda3a100..54cc5916 100644 --- a/modules/artifact-store-tools/module.json +++ b/modules/artifact-store-tools/module.json @@ -19,8 +19,8 @@ ], "invokes": [ "seat:mesh-hand-acts.hand-act", - "mesh-catalog.catalog_modules", - "mesh-catalog.catalog_module" + "seat:mesh-catalog.modules", + "seat:mesh-catalog.module" ], "reads": [ "mesh-delivery-assigner.wants", diff --git a/modules/mesh-catalog/README.md b/modules/mesh-catalog/README.md index 32e34d2f..a058f007 100644 --- a/modules/mesh-catalog/README.md +++ b/modules/mesh-catalog/README.md @@ -1,6 +1,6 @@ # mesh-catalog -The `mesh-catalog` module of the mesh catalogue: claims the seat `mesh-catalog`. +The `mesh-catalog` module of the mesh catalogue: deprecated. It no longer claims the seat `mesh-catalog`: the indexer of the delivery claims it and serves its verbs from the bus (ADR 0604). This module stays, with its tools, until it is retired. ## Files it does not know diff --git a/modules/mesh-catalog/module.json b/modules/mesh-catalog/module.json index c5ad61a4..fe30560e 100644 --- a/modules/mesh-catalog/module.json +++ b/modules/mesh-catalog/module.json @@ -5,12 +5,6 @@ "capabilities": [ "container-runtime" ], - "claims": [ - { - "name": "mesh-catalog", - "scope": "mesh" - } - ], "requires": [ "postgres-database" ], diff --git a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go index 44c8e504..ee345c4a 100644 --- a/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go +++ b/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/client.go @@ -970,7 +970,11 @@ func (c *Client) report(ctx context.Context, kvs map[string]jetstream.KeyValue, logf("[provisioner-client] the report of %s: %v", r.Module, err) return } - if _, err := kvs[AppliedBucket].Put(ctx, c.Node+"."+r.Module, body); err != nil { + // Kept under a context the client's stop does not cancel, bounded: a report made as the client stops is still kept, + // or s.applied would never name the module and a later pass would never remove it (leftModules). + keep, done := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second) + defer done() + if _, err := kvs[AppliedBucket].Put(keep, c.Node+"."+r.Module, body); err != nil { logf("[provisioner-client] the report of %s could not be kept: %v", r.Module, err) } else { s.applied[r.Module] = true diff --git a/modules/mesh-provisioner-client/go.mod b/modules/mesh-provisioner-client/go.mod index ea5c9d1f..ed1b3885 100644 --- a/modules/mesh-provisioner-client/go.mod +++ b/modules/mesh-provisioner-client/go.mod @@ -3,7 +3,7 @@ module mesh-provisioner-client go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 golang.org/x/sys v0.48.0 diff --git a/modules/mesh-provisioner-client/go.sum b/modules/mesh-provisioner-client/go.sum index abad2969..7679a5dc 100644 --- a/modules/mesh-provisioner-client/go.sum +++ b/modules/mesh-provisioner-client/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-provisioner-client/module.json b/modules/mesh-provisioner-client/module.json index 095128a8..61959a9f 100644 --- a/modules/mesh-provisioner-client/module.json +++ b/modules/mesh-provisioner-client/module.json @@ -1,12 +1,13 @@ { "module": "mesh-provisioner-client", - "version": "1", + "version": "2", "claims": [ { "name": "node-provisioner", "scope": "node" } ], + "core-credentials": true, "own-secrets": { "provisioner-broker": "${dir:state}/broker" }, diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go index 1328bde9..c856a63b 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go @@ -295,11 +295,6 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) { if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } - // No "core-credentials" until the indexer whose judge knows the word runs (issue 774): the running judge refuses it, - // and a refused manifest holds the bus's user list, so the indexer that knows it is never granted. It comes back then. - if strings.Contains(string(raw), `"core-credentials"`) { - t.Error("the manifest says \"core-credentials\" before the judge that reads it runs") - } if m.Module != "mesh-provisioner" || m.RunsAs != "mesh-provisioner" || m.OwnSecrets["broker"] == "" { t.Errorf("an account of its own: module %q, runs-as %q, own secrets %v", m.Module, m.RunsAs, m.OwnSecrets) } diff --git a/modules/mesh-provisioner/go.mod b/modules/mesh-provisioner/go.mod index 03d2bf48..e16ce001 100644 --- a/modules/mesh-provisioner/go.mod +++ b/modules/mesh-provisioner/go.mod @@ -3,7 +3,7 @@ module mesh-provisioner go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-provisioner/go.sum b/modules/mesh-provisioner/go.sum index abad2969..7679a5dc 100644 --- a/modules/mesh-provisioner/go.sum +++ b/modules/mesh-provisioner/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-provisioner/module.json b/modules/mesh-provisioner/module.json index 12d4c27c..1c6f4c74 100644 --- a/modules/mesh-provisioner/module.json +++ b/modules/mesh-provisioner/module.json @@ -1,6 +1,6 @@ { "module": "mesh-provisioner", - "version": "1", + "version": "2", "runs-as": "mesh-provisioner", "claims": [ { @@ -46,6 +46,7 @@ "plans", "refused" ], + "core-credentials": true, "own-secrets": { "broker": "${dir:state}/broker" }, diff --git a/modules/nats/cmd/nats-tools/manifest_test.go b/modules/nats/cmd/nats-tools/manifest_test.go index 9ac143ba..93e0a8b3 100644 --- a/modules/nats/cmd/nats-tools/manifest_test.go +++ b/modules/nats/cmd/nats-tools/manifest_test.go @@ -32,6 +32,13 @@ func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) { } `json:"backup"` } `json:"own"` } `json:"data"` + Provides []struct { + Name string `json:"name"` + Scope string `json:"scope"` + Credential struct { + Form string `json:"form"` + } `json:"credential"` + } `json:"provides"` Tools []string `json:"tools"` Emits []string `json:"emits"` Resources []map[string]any `json:"resources"` @@ -43,10 +50,15 @@ func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) { if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } - // No "core-credentials" until the indexer whose judge knows the word runs (issue 774): the running judge refuses it, - // and a refused manifest holds the bus's user list, so the indexer that knows it is never granted. It comes back then. - if strings.Contains(string(raw), `"core-credentials"`) { - t.Error("the manifest says \"core-credentials\" before the judge that reads it runs") + // The bus provides "bus-state" beside "mesh-bus" (ADR 0614 §5): a module whose manifest keeps bus state requires it. + provided := map[string]string{} + for _, p := range m.Provides { + provided[p.Name] = p.Scope + "/" + p.Credential.Form + } + for _, name := range []string{"mesh-bus", "bus-state"} { + if provided[name] != "mesh/none" { + t.Errorf("provision %q is %q: the bus provides it mesh-wide with no credential", name, provided[name]) + } } if m.Build.On != nil { t.Error("build.on is declared: nothing is built on a base image any more") diff --git a/modules/nats/go.mod b/modules/nats/go.mod index a6aa5c98..609dc37e 100644 --- a/modules/nats/go.mod +++ b/modules/nats/go.mod @@ -3,7 +3,7 @@ module nats-tools go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 github.com/klauspost/compress v1.20.0 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/nats/go.sum b/modules/nats/go.sum index abad2969..7679a5dc 100644 --- a/modules/nats/go.sum +++ b/modules/nats/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/nats/module.json b/modules/nats/module.json index f9083c93..3e58475c 100644 --- a/modules/nats/module.json +++ b/modules/nats/module.json @@ -12,6 +12,13 @@ "credential": { "form": "none" } + }, + { + "name": "bus-state", + "scope": "mesh", + "credential": { + "form": "none" + } } ], "claims": [ @@ -21,6 +28,7 @@ } ], "bus-users": "/var/lib/nats-module/conf/accounts.conf", + "core-credentials": true, "own-secrets": { "broker": "${dir:mesh-state}/broker", "broker-nats-tools": "${dir:tools-secret}/broker-nats-tools"