mesh-sdk's catalogue package names no module: the tool runner says it is one (tool-runner), the foundation is what is computed, the kept streams are the judge's, and the ended controller's dependence check goes with it (issue 774, the operator 2026-11-03)
This commit is contained in:
@@ -30,6 +30,9 @@ func ControllerLeftoverConsumersOn(nodes []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// endedController is the name the ended controller had as a module: its buckets are named for it (ADR 0590 §6).
|
||||
const endedController = "mesh-controller"
|
||||
|
||||
// ControllerLeftoverBuckets are the ended controller's buckets, each by its local name (`KV_mesh-controller_<name>`).
|
||||
var ControllerLeftoverBuckets = []string{"asked", "assignments", "builds", "calls", "checks-running", "composed",
|
||||
"condition-history", "conditions", "declared-config", "declared-packages", "delivery-merges", "grants", "hand-acts",
|
||||
@@ -40,7 +43,7 @@ var ControllerLeftoverBuckets = []string{"asked", "assignments", "builds", "call
|
||||
func ControllerLeftoverStreams(nodes []string) []string {
|
||||
out := []string{"CONTROL", AssignmentsStream}
|
||||
for _, b := range ControllerLeftoverBuckets {
|
||||
out = append(out, "KV_"+BucketName(catalogue.EndedControllerName, b))
|
||||
out = append(out, "KV_"+BucketName(endedController, b))
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if safeSubject.MatchString(n) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -19,6 +22,7 @@ import (
|
||||
// design 28 task 5.4, not converted.
|
||||
func TestNoCatalogueManifestNamesAmqp(t *testing.T) {
|
||||
modules, err := filepath.Glob(filepath.Join(beside.Catalogue(t), "*", "module.json"))
|
||||
modules0 := modules
|
||||
if err != nil || len(modules) == 0 {
|
||||
t.Fatalf("no manifests in the catalogue, so this proved nothing: %v", err)
|
||||
}
|
||||
@@ -32,24 +36,48 @@ func TestNoCatalogueManifestNamesAmqp(t *testing.T) {
|
||||
filepath.Base(filepath.Dir(path)))
|
||||
}
|
||||
}
|
||||
// Nor does this package name the modules that once had a rule of their own here (issue 774, the operator 2026-11-03):
|
||||
// what each is, its manifest says. mesh-provisioner is also a seat's name, named where the seat is.
|
||||
// Nor does this package's code name any module (issue 774, the operator 2026-11-03): what a module is, its manifest
|
||||
// says. Every string literal of its non-test files is read; a module's name is refused there unless it is also a
|
||||
// seat's or a provision's name, or one of the words below, which name something else.
|
||||
t.Run("the package names no module", func(t *testing.T) {
|
||||
named := regexp.MustCompile(`"(nats|mesh-provisioner-client|mesh-provisioner)"`)
|
||||
modules := map[string]bool{}
|
||||
for _, path := range modules0 {
|
||||
modules[filepath.Base(filepath.Dir(path))] = true
|
||||
}
|
||||
// The mesh's modules held outside the catalogue: the node-engine, the private network, the tool runner, the
|
||||
// ended controller and the delivery's parts.
|
||||
for _, m := range []string{"mesh-host", "mesh-wireguard", "node-tools", "mesh-controller", "mesh-delivery-assigner",
|
||||
"mesh-delivery-builder", "mesh-delivery-checker", "mesh-delivery-forge-bridge", "mesh-delivery-indexer",
|
||||
"mesh-delivery-teller", "mesh-delivery-view", "mesh-delivery-watchman", "mesh-enrolment", "mesh-grants"} {
|
||||
modules[m] = true
|
||||
}
|
||||
// Words that are a module's name and name something else here: a language, an environment's rendering, a package.
|
||||
elsewhere := map[string]bool{"go": true, "systemd": true, "nftables": true}
|
||||
fset := token.NewFileSet()
|
||||
files, _ := filepath.Glob("*.go")
|
||||
for _, f := range files {
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
continue
|
||||
}
|
||||
raw, err := os.ReadFile(f)
|
||||
parsed, err := parser.ParseFile(fset, f, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, line := range strings.Split(string(raw), "\n") {
|
||||
if named.MatchString(line) && !strings.Contains(line, "{Name: "+named.FindString(line)+", Scope:") {
|
||||
t.Errorf("%s:%d names a module: %s", f, i+1, strings.TrimSpace(line))
|
||||
ast.Inspect(parsed, func(n ast.Node) bool {
|
||||
lit, ok := n.(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
return true
|
||||
}
|
||||
}
|
||||
word, err := strconv.Unquote(lit.Value)
|
||||
if err != nil || !modules[word] || elsewhere[word] {
|
||||
return true
|
||||
}
|
||||
if _, seat := SeatNamed(word); seat {
|
||||
return true
|
||||
}
|
||||
t.Errorf("%s names the module %s", fset.Position(lit.Pos()), word)
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EndedControllerName is the name the ended controller had, as its module and as its seat: no manifest may name it.
|
||||
const EndedControllerName = "mesh-controller"
|
||||
|
||||
// The kinds of a declaration that names the controller.
|
||||
const (
|
||||
DependsRead = "read"
|
||||
DependsInvoke = "invoke"
|
||||
DependsConsume = "consume"
|
||||
DependsUses = "uses"
|
||||
DependsWaits = "waits"
|
||||
DependsClaims = "claims"
|
||||
DependsRequires = "requires"
|
||||
DependsIs = "is"
|
||||
)
|
||||
|
||||
// Dependency is one declaration of a manifest that names the controller.
|
||||
type Dependency struct {
|
||||
Module string `json:"module"`
|
||||
// By is the account declaring it when it is not the module's own: `process <id>` or `tools <binary>`.
|
||||
By string `json:"by,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
What string `json:"what"`
|
||||
}
|
||||
|
||||
// onController says whether a name `<owner>.<rest>` is the controller's.
|
||||
func onController(name string) bool { return strings.HasPrefix(name, EndedControllerName+".") }
|
||||
|
||||
// invokesController says whether an invoke names a verb of the controller's seat or a tool of its module. `*` names none:
|
||||
// it is the console's grant to every tool.
|
||||
func invokesController(invoke string) bool {
|
||||
if seat, _, ok := SeatInvoke(invoke); ok {
|
||||
return seat == EndedControllerName
|
||||
}
|
||||
return onController(invoke)
|
||||
}
|
||||
|
||||
// Dependencies are every declaration naming the controller in the manifests given, by module.
|
||||
func Dependencies(manifests map[string]Manifest) []Dependency {
|
||||
var out []Dependency
|
||||
add := func(module, by, kind, what string) {
|
||||
if d := (Dependency{Module: module, By: by, Kind: kind, What: what}); !slices.Contains(out, d) {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
for name, m := range manifests {
|
||||
if name == EndedControllerName || m.Module == EndedControllerName {
|
||||
add(name, "", DependsIs, EndedControllerName)
|
||||
}
|
||||
for _, r := range m.Reads {
|
||||
if onController(r) {
|
||||
add(name, "", DependsRead, r)
|
||||
}
|
||||
}
|
||||
for _, i := range m.Invokes {
|
||||
if invokesController(i) {
|
||||
add(name, "", DependsInvoke, i)
|
||||
}
|
||||
}
|
||||
for _, c := range m.Consumes {
|
||||
if onController(c) {
|
||||
add(name, "", DependsConsume, c)
|
||||
}
|
||||
}
|
||||
for _, w := range m.Waits {
|
||||
if onController(w) {
|
||||
add(name, "", DependsWaits, w)
|
||||
}
|
||||
}
|
||||
for _, u := range m.Uses {
|
||||
if u == EndedControllerName {
|
||||
add(name, "", DependsUses, u)
|
||||
}
|
||||
}
|
||||
for _, c := range m.Claims {
|
||||
if c.Name == EndedControllerName {
|
||||
add(name, "", DependsClaims, c.Name)
|
||||
}
|
||||
}
|
||||
for _, r := range m.Requires {
|
||||
if r == EndedControllerName || onController(r) {
|
||||
add(name, "", DependsRequires, r)
|
||||
}
|
||||
}
|
||||
for _, a := range m.BusAccounts() {
|
||||
by := "process " + a.ID
|
||||
if a.Tools {
|
||||
by = "tools " + a.ID
|
||||
}
|
||||
for _, i := range a.Bus.Invokes {
|
||||
if invokesController(i) {
|
||||
add(name, by, DependsInvoke, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
a, b := out[i], out[j]
|
||||
if a.Module != b.Module {
|
||||
return a.Module < b.Module
|
||||
}
|
||||
if a.Kind != b.Kind {
|
||||
return a.Kind < b.Kind
|
||||
}
|
||||
if a.What != b.What {
|
||||
return a.What < b.What
|
||||
}
|
||||
return a.By < b.By
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// ManifestRefusals is the check over a repository's manifests, by module name: one reason per declaration naming the
|
||||
// controller, none when it passes. None given fails it, since nothing read can hide a declaration.
|
||||
func ManifestRefusals(manifests map[string]Manifest) []string {
|
||||
if len(manifests) == 0 {
|
||||
return []string{"no manifest was read: with nothing read, nothing naming the controller can be judged absent"}
|
||||
}
|
||||
var out []string
|
||||
for _, d := range Dependencies(manifests) {
|
||||
who := d.Module
|
||||
if d.By != "" {
|
||||
who += " (" + d.By + ")"
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s declares %s %s: the controller ended by its unassignment and nothing may name it "+
|
||||
"(ADR 0590 clause 5)", who, d.Kind, d.What))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Issue 1126 B1/AC1, B2/AC1 (ADR 0590 clause 5): the seat set holds no mesh-controller, and the dependence check refuses
|
||||
// every manifest that reads (the lease too), invokes, consumes, waits on, uses, claims, requires or is mesh-controller.
|
||||
func TestNothingMayNameTheController(t *testing.T) {
|
||||
for _, s := range DefaultSeats() {
|
||||
if s.Name == EndedControllerName {
|
||||
t.Errorf("the seat set still holds %s", s.Name)
|
||||
}
|
||||
}
|
||||
if _, ok := SeatNamed(EndedControllerName); ok {
|
||||
t.Errorf("SeatNamed still finds %s", EndedControllerName)
|
||||
}
|
||||
for _, row := range []struct {
|
||||
name, manifest string
|
||||
want []string // "<kind> <what>"
|
||||
}{
|
||||
{"a read of the lease", `{"module":"mod-l","version":"1","reads":["mesh-controller.lease"]}`, []string{"read mesh-controller.lease"}},
|
||||
{"the controller's own manifest", `{"module":"mesh-controller","version":"1"}`, []string{"is mesh-controller"}},
|
||||
{"a claim of its seat", `{"module":"a-controller","version":"1","claims":[{"name":"mesh-controller","scope":"mesh"}]}`,
|
||||
[]string{"claims mesh-controller"}},
|
||||
{"each declaration kind; every tool (*) is none",
|
||||
`{"module":"mod-i","version":"1","invokes":["seat:mesh-controller.root-free","mesh-controller.status","*","nats.nats_users"],` +
|
||||
`"consumes":["mesh-controller.artifacts-gone","mod-x.thing"],"uses":["mesh-controller"],"waits":["mesh-controller.checked"],` +
|
||||
`"requires":["mesh-controller"]}`,
|
||||
[]string{"consume mesh-controller.artifacts-gone", "invoke mesh-controller.status", "invoke seat:mesh-controller.root-free",
|
||||
"requires mesh-controller", "uses mesh-controller", "waits mesh-controller.checked"}},
|
||||
{"a process's own invoke", `{"module":"mod-p","version":"1","resources":[{"id":"account","type":"user","name":"mod-p"},` +
|
||||
`{"id":"worker","type":"process","run":["./w"],"user":"mod-p","bus":{"invokes":["seat:mesh-controller.held"]}}]}`,
|
||||
[]string{"invoke seat:mesh-controller.held"}},
|
||||
{"a module on its own seats", `{"module":"mod-ok","version":"1","reads":["mesh-grants.accounts"],"invokes":["*"]}`, nil},
|
||||
} {
|
||||
m, err := Decode([]byte(row.manifest))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", row.name, err)
|
||||
}
|
||||
var got []string
|
||||
for _, d := range Dependencies(map[string]Manifest{m.Module: m}) {
|
||||
got = append(got, d.Kind+" "+d.What)
|
||||
}
|
||||
if !slices.Equal(got, row.want) {
|
||||
t.Errorf("%s: dependencies %q, want %q", row.name, got, row.want)
|
||||
}
|
||||
refused := ManifestRefusals(map[string]Manifest{m.Module: m})
|
||||
if len(refused) != len(row.want) || len(refused) > 0 && !strings.Contains(refused[0], "ADR 0590") {
|
||||
t.Errorf("%s: refused %q", row.name, refused)
|
||||
}
|
||||
}
|
||||
if got := ManifestRefusals(map[string]Manifest{}); len(got) != 1 || !strings.Contains(got[0], "no manifest") {
|
||||
t.Errorf("no manifest given: %q", got)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,27 @@ func TestAManifestMaySayWhatItInvokes(t *testing.T) {
|
||||
t.Errorf("%s: carries the core's credentials %v", raw, !core)
|
||||
}
|
||||
}
|
||||
// And that it is the tool runner (issue 774): read, written back, and found among others by that alone.
|
||||
runner, err := decodeForTest([]byte(`{"module":"any-name","version":"1","tool-runner":true}`))
|
||||
if err != nil || !IsToolRunner(runner) || runner.UnknownField() != "" {
|
||||
t.Fatalf("a tool runner: %v %q", err, runner.UnknownField())
|
||||
}
|
||||
if back, _ := json.Marshal(runner); !strings.Contains(string(back), `"tool-runner":true`) {
|
||||
t.Errorf("a tool runner written back as %s", back)
|
||||
}
|
||||
other, _ := decodeForTest([]byte(`{"module":"node-tools","version":"1"}`))
|
||||
if got, ok := ToolRunnerOf([]Manifest{other, runner}); !ok || got.Module != "any-name" {
|
||||
t.Errorf("the tool runner found is %q, %v: by what it says, never by its name", got.Module, ok)
|
||||
}
|
||||
// A module's seats are derived from what it declares, whatever its name: a service on a machine is the service
|
||||
// manager's, the node-engine's name exempts nothing; a module whose resources are computed depends on none.
|
||||
host, _ := decodeForTest([]byte(`{"module":"mesh-host","version":"1","resources":[{"id":"s","type":"service","name":"s.service"}]}`))
|
||||
if got := DependsOn(nil, host); len(got) != 1 || got[0] != ServiceManagerSeat {
|
||||
t.Errorf("a service by the node-engine's name depends on %v", got)
|
||||
}
|
||||
if got := DependsOn(nil, Manifest{Module: "net", Computed: "net", Resources: host.Resources}); len(got) != 0 {
|
||||
t.Errorf("a computed module depends on %v", got)
|
||||
}
|
||||
// And its upgrade policy, which holds for the bus as for any module: providing the bus derives nothing (issue 774).
|
||||
for raw, want := range map[string]string{
|
||||
`{"module":"bus","version":"1","provides":[{"name":"mesh-bus"}],"upgrade":{"policy":"record","why":"its streams"}}`: PolicyRecord + " " + FromModule,
|
||||
|
||||
@@ -801,6 +801,11 @@ type Manifest struct {
|
||||
// start, and is then replaced.
|
||||
OwnSecrets OwnSecrets `json:"own-secrets,omitempty"`
|
||||
|
||||
// ToolRunner says this module is the node's tool runner (issue 774): the one program on each node that loads every
|
||||
// assigned module's tools bundle, served by its runtime process and the only module that may declare `waits`. Said by
|
||||
// the module, never by a name in mesh-sdk.
|
||||
ToolRunner bool `json:"tool-runner,omitempty"`
|
||||
|
||||
// CoreCredentials says this module's own secrets are the core's credentials (novox/hq ADR 0349 §16, issue 755): the
|
||||
// vault seals each to the machine, and the node-engine writes the vault's box into its file, never a value of its
|
||||
// own (Rendering.VaultSealed). Said by the module, never by a list in mesh-sdk (the operator, 2026-11-03: mesh-sdk names
|
||||
|
||||
+13
-3
@@ -1,8 +1,18 @@
|
||||
package catalogue
|
||||
|
||||
// RuntimeModule is the module that is the node's tool runtime. Mirrored in the broker package,
|
||||
// which composes a principal of its own for it; the agreement test there holds the two to one string.
|
||||
const RuntimeModule = "node-tools"
|
||||
// IsToolRunner says whether a manifest is the node's tool runner's, as it says itself (`tool-runner`, issue 774): the
|
||||
// one program on each node that loads every assigned module's tools bundle. Never known here by its name.
|
||||
func IsToolRunner(m Manifest) bool { return m.ToolRunner }
|
||||
|
||||
// ToolRunnerOf is the module among manifests that says it is the tool runner, and whether one does.
|
||||
func ToolRunnerOf(manifests []Manifest) (Manifest, bool) {
|
||||
for _, m := range manifests {
|
||||
if IsToolRunner(m) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return Manifest{}, false
|
||||
}
|
||||
|
||||
// BundleRoot is where a machine keeps the tools bundles the mesh delivers to it: under the mesh's
|
||||
// own directory, beside the daemons the host unpacks there, and never where a package manager also
|
||||
|
||||
@@ -24,24 +24,12 @@ var appliedThrough = map[string]string{
|
||||
"container": ContainerRuntimeSeat,
|
||||
}
|
||||
|
||||
// foundationModules are the pieces genesis lays before any module exists (novox/hq ADR 0207 §5):
|
||||
// the host and the private network. Registered as modules so they can be assigned, but what they
|
||||
// declare is the installation's, not a module's, so it is never judged. The third piece, the
|
||||
// bootstrap container runtime, is not a module at all: its package and service are in the genesis
|
||||
// bundle the host applies itself, and never pass through a resolution here.
|
||||
//
|
||||
// The private network's module is overlay.Name, written out because the overlay package composes
|
||||
// on top of this one; its resources are computed, which exempts it by the rule below as well.
|
||||
var foundationModules = map[string]bool{
|
||||
"mesh-host": true,
|
||||
"mesh-wireguard": true,
|
||||
}
|
||||
|
||||
// isFoundation is whether a module's declarations are the foundation's rather than its own. A
|
||||
// module whose resources are computed is the mesh's by construction — the private network's peer
|
||||
// list and its tools are the controller's, written per node — so it counts whatever its name.
|
||||
// isFoundation is whether a module's declarations are the foundation's rather than its own: a module whose resources are
|
||||
// computed (`computed`, the private network's peer list written per node) is the mesh's by construction. Read from the
|
||||
// manifest alone, never from its name (issue 774, the operator 2026-11-03): the node-engine's own manifest declares no
|
||||
// resource a seat applies, so it depends on none by the rules below.
|
||||
func isFoundation(m Manifest) bool {
|
||||
return foundationModules[m.Module] || m.Computed != ""
|
||||
return m.Computed != ""
|
||||
}
|
||||
|
||||
// DependsOn is every seat a module needs held on its node: derived from the resource types it
|
||||
|
||||
@@ -370,19 +370,3 @@ func uncovered(patterns []string) []string {
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// **A module keeps its data in the bus's predefined forms** (the lead and the operator, 2026-10-29; the operator's rule of
|
||||
// 2026-10-22, "data storage is done using our predefined nats bus options"): a state, a log or an object store, which the
|
||||
// runtime reads and writes for its tools and other modules read by a declared `reads`. A stream of its own is outside
|
||||
// them and refused, naming it. StreamsKeptUntilMoved are the streams declared before this check, each allowed by name
|
||||
// until its module moves it; the list only shrinks.
|
||||
var StreamsKeptUntilMoved = map[string][]string{
|
||||
"brother": {"device-asks"},
|
||||
"mesh-build-go": {"toolchains-go"},
|
||||
"mesh-build-node": {"toolchains-node"},
|
||||
"mesh-mail": {"mail", "mail-sends"},
|
||||
"only-office": {"convert-asks"},
|
||||
"mesh-delivery-assigner": {"placement"},
|
||||
"mesh-delivery-checker": {"checks"},
|
||||
"mesh-delivery-forge-bridge": {"forge"},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user