package main import ( "context" "encoding/json" "errors" "flag" "fmt" "net" "os" "path/filepath" "sort" "strings" "time" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" "github.com/novox/mesh-controller/internal/overlay" ) // the catalogue: what exists, what is assigned, and how it is configured. // // Split out of main.go, which had reached 2,769 lines because appending was always the // cheapest next step. That is how novox/hq ADR 0001 records `hal/sdk` reaching 34,636: // nothing in it was wrong, and no one edit was the one that should have been a new file. // provided is what comes with the control plane rather than from a repository. // // WireGuard, the names, and the domain module over both. The first two are here because the code // that works out their files is here: // a peer list is derived from every machine at once, so it cannot be written in a manifest, and // whatever computes it has to live wherever the whole picture is. // // **It is a module in every other respect** — assigned, unassigned, resolved, settled, and absent // from a machine nobody gave it to. func providedModules() []catalogue.Manifest { var out []catalogue.Manifest for _, raw := range []map[string]any{ // **Two used to be here and are gone**: one wrote the mesh's names into a hosts file, the // other wrote the same machines as wildcards for a resolver to read. Neither ran software // and neither could be swapped for anything, which is the test of whether a thing is a // module at all (novox/hq ADR 0040). They existed because computed output needed somewhere // to live, and now a module says where it wants it — `facts` in its own manifest. // // **And the bundle that required it is gone** (novox/hq ADR 0226): `networking` named this // module's requirement and nothing else, so every machine carried two modules for one // network. A machine is assigned the private network itself; what a release stops shipping // is retired at the next start (stores.go, Inventory.RetireUnshipped). overlay.Manifest(), } { var m catalogue.Manifest b, _ := json.Marshal(raw) _ = json.Unmarshal(b, &m) out = append(out, m) } return out } var provided = providedModules() func moduleCommand(ctx context.Context, args []string) error { if len(args) == 0 { return errors.New("module add , module check ..., module list, or module forget ") } // `check` needs no mesh, and must not: it is what somebody runs in their own repository before // there is a mesh in reach (novox/hq issue 148). A directory expands to every manifest under it. if args[0] == "check" { set := flag.NewFlagSet("module check", flag.ContinueOnError) // The longest machine name an identity must fit on (novox/hq ADR 0225): a mesh passes its own. longest := set.Int("longest-machine-name", catalogue.DefaultLongestMachine, "judge each module's identity on a machine name this many characters long") given, err := parseAround(set, args[1:]) if err != nil { return err } if *longest < 1 { return errors.New("--longest-machine-name is a length, at least 1") } var paths []string for _, a := range given { if info, err := os.Stat(a); err == nil && info.IsDir() { under, err := manifestsUnder(a) if err != nil { return err } paths = append(paths, under...) continue } paths = append(paths, a) } return moduleCheckFor(paths, *longest, os.Stdout) } open, err := openStores(ctx) if err != nil { return err } defer open.Close() inv := open.inventory switch args[0] { case "add": set := flag.NewFlagSet("module add", flag.ContinueOnError) repo := set.String("source", "", "where this module comes from") ref := set.String("ref", "", "the branch followed there") commit := set.String("commit", "", "the commit this manifest was read at") // **Where inside the repository the module is** (novox/hq ADR 0069). A module is a // repository *and* a directory, and a record that carries only the repository names a // module.json at its root — so every later build of it looks in the wrong place and fails // with "no module.json at its root". Nine modules on this mesh were registered that way // and none of them could be rebuilt (2026-09-28). path := set.String("path", "", "the module's directory inside that repository") self := set.Bool("self", false, "the source is a path on the forge holding the git seat") positionals, err := parseAround(set, args[1:]) if err != nil { return err } if len(positionals) != 1 { return errors.New("module add [--source [--self] [--path P] " + "--ref --commit ]") } raw, err := os.ReadFile(positionals[0]) if err != nil { return err } m, err := catalogue.ParseManifest(raw) if err != nil { return err } // A manifest handed over by hand takes each template's text from the tree beside it (novox/hq ADR 0349 §2). if m, err = catalogue.WithTemplatesFrom(m, filepath.Dir(positionals[0])); err != nil { return err } from, err := whereItComesFrom(*repo, *ref, *commit, *path, *self) if err != nil { return err } if err := namesNoInstallation(m); err != nil { return err } if err := inv.RegisterModule(ctx, m, from); err != nil { return err } fmt.Printf("%s registered", m.Module) if *commit != "" { fmt.Printf(" from %s", short(*commit)) } if *repo != "" && *path == "" { // Said, not refused: a module really at the root is the ordinary case for a repository // of its own. But a repository holding many modules and a record naming none of them is // a module nothing can rebuild, and the person adding it is the one who knows which. fmt.Printf("\n no directory inside %s, so it is built from that repository's root — "+ "`--path` if the module lives in a directory there", *repo) } if len(m.Provides) > 0 { fmt.Printf(", providing %s", describeOffers(m.Provides)) } fmt.Println() for _, c := range m.Claims { fmt.Printf(" claims %s, one per %s\n", c.Name, c.At()) } return nil case "list": // The catalogue: what exists, where it came from, whether it is current, and who runs it. // The provenance was recorded from the first build and nothing showed it, which made // "is this current?" a question you could only answer by reading the database. entries, err := inv.Catalogued(ctx) if err != nil { return err } // **The same list, for something other than a person** (novox/hq ADR 0195): what each module // is, where it runs, whether it is current, and what it says of itself. if len(args) > 1 && args[1] == "--json" { type listed struct { Module string `json:"module"` Version string `json:"version"` Built string `json:"built,omitempty"` Head string `json:"head,omitempty"` Current bool `json:"current"` Provided bool `json:"provided,omitempty"` // Tools says whether the module answers tools anywhere it runs: a list of its own, // a bundle the runtime serves, or a seat's verbs it claims (novox/hq ADR 0197) — // what the console checks the bus's answers against. Tools bool `json:"tools"` On []string `json:"on"` Provides []string `json:"provides,omitempty"` Requires []string `json:"requires,omitempty"` Claims []string `json:"claims,omitempty"` Capabilities []string `json:"capabilities,omitempty"` // Replaces is what each of its own tools replaces (novox/hq ADR 0245), for the console's // search and the agent's instructions. Replaces map[string][]string `json:"replaces,omitempty"` } out := make([]listed, 0, len(entries)) for _, e := range entries { m := e.Manifest l := listed{Module: m.Module, Version: m.Version, Built: e.Source.BuiltFrom, Head: e.Source.Head, Current: e.Provided || e.Source.Repository == "" || e.Source.Current(), Provided: e.Provided, On: append([]string{}, e.On...), Provides: m.Offers(), Requires: m.Requires, Capabilities: m.Capabilities, Tools: declaresTools(m), Replaces: m.Replaces} for _, c := range m.Claims { l.Claims = append(l.Claims, c.At()+"/"+c.Name) } out = append(out, l) } return printJSON(out) } if len(entries) == 0 { fmt.Println("this mesh knows about no modules yet") return nil } var stale int for _, e := range entries { m := e.Manifest fmt.Printf("%-18s %-8s", m.Module, m.Version) switch { case e.Provided: fmt.Printf(" %-22s", "with the control plane") case e.Source.Repository == "": // Handed over by hand. Legitimate — it is how a module is fixed in a hurry — and // worth saying, because nothing can rebuild it. fmt.Printf(" %-22s", "handed over") case !e.Source.Current(): stale++ fmt.Printf(" %-22s", "behind "+short(e.Source.BuiltFrom)+" < "+short(e.Source.Head)) default: fmt.Printf(" %-22s", "built "+short(e.Source.BuiltFrom)) } if len(e.On) > 0 { fmt.Printf(" on %s", strings.Join(e.On, ", ")) } else { fmt.Printf(" on nothing") } fmt.Println() var says []string if len(m.Provides) > 0 { says = append(says, "provides "+describeOffers(m.Provides)) } if len(m.Requires) > 0 { // An optional requirement says so (novox/hq ADR 0330): it is not refused when absent. required := make([]string, 0, len(m.Requires)) for _, r := range m.Requires { if m.IsOptional(r) { r += " (optional)" } required = append(required, r) } says = append(says, "requires "+strings.Join(required, ", ")) } for _, c := range m.Claims { says = append(says, "claims "+c.At()+"/"+c.Name) } if len(m.Capabilities) > 0 { says = append(says, "needs "+strings.Join(m.Capabilities, ", ")) } if len(says) > 0 { fmt.Printf(" %s\n", strings.Join(says, " · ")) } } if stale > 0 { fmt.Printf("\n%d module(s) behind their source — `build --behind` to catch up\n", stale) } return nil case "moved": if len(args) != 3 { return errors.New("module moved — the source has a newer commit") } if err := inv.SourceMoved(ctx, args[1], args[2]); err != nil { return err } from, err := inv.SourceOf(ctx, args[1]) if err != nil { return err } if from.Current() { fmt.Printf("%s is current at %s\n", args[1], short(from.Head)) return nil } fmt.Printf("%s is behind: the mesh holds %s and the source has %s\n", args[1], short(from.BuiltFrom), short(from.Head)) fmt.Printf(" run `build %s` to catch up\n", from.Repository) return nil case "forget": // **What goes with it is said before it goes** (novox/hq 04-ISSUES/017). The settings, the // module's own secrets and the ports the mesh chose all cascade off the module row, so // `forget` used to destroy them and report "forgotten" — an action succeeding into a state // its own verify would reject, and a sealed secret is not recoverable afterwards. set := flag.NewFlagSet("module forget", flag.ContinueOnError) andHeld := set.Bool("and-what-it-holds", false, "discard its settings, its own secrets and its ports along with it") positionals, err := parseAround(set, args[1:]) if err != nil { return err } if len(positionals) != 1 { return errors.New("module forget [--and-what-it-holds]") } if !*andHeld { if err := inv.ForgetModule(ctx, positionals[0]); err != nil { return err } fmt.Printf("%s forgotten\n", positionals[0]) return nil } held, err := inv.DiscardModule(ctx, positionals[0]) if err != nil { return err } fmt.Printf("%s forgotten\n", positionals[0]) for _, line := range held.Lines() { // Said after the fact as well as before it: this is the only record that these // existed, and the next person to ask why the module came back empty reads it here. fmt.Printf(" discarded%s\n", strings.TrimPrefix(line, " ")) } return nil case "issue": // A module's broker account, scoped by its emits and consumes (novox/hq ADR 0043) and // sealed to the machine that will run it — the generic case the builder was the first of. set := flag.NewFlagSet("module issue", flag.ContinueOnError) forNode := set.String("node", "", "the machine that will run it, so the credential is delivered instead of printed") positionals, err := parseAround(set, args[1:]) if err != nil { return err } if len(positionals) != 1 { return errors.New("module issue --node ") } module := positionals[0] if *forNode == "" { return errors.New("module issue needs --node: a module's account is sealed to the " + "machine that runs it, and the mesh cannot read it back to print") } shelf, err := inv.Catalogue(ctx) if err != nil { return err } m, ok := shelf[module] if !ok { return fmt.Errorf("this mesh knows no module %q; `module add` it first", module) } if err := mayIssue(m); err != nil { return err } // Which bus this mesh is on. A module gets a credential for exactly one, and the two are // made in entirely different ways: on the bus the mesh runs on today an account is a // management call, and on the bus being built it is a row the next composition writes into // the server's user list (novox/hq design 25 §4). busAddress, err := broker.BusAddress() if err != nil { return err } return issueOnTheNewBus(ctx, inv, m, *forNode, busAddress) default: return fmt.Errorf("module has no %q; it has add, check, list, moved, forget and issue", args[0]) } } func assignCommand(ctx context.Context, verb string, args []string) error { // Several modules in one act (novox/hq ADR 0207): holders that depend on each other — the // service manager and the package manager — can only go on, or come off, together. set := flag.NewFlagSet(verb, flag.ContinueOnError) // A module known and not built: ask for its build, and keep the assignment pending on it (novox/hq // issue 325). Only assign reads it. build := set.Bool("build", false, "for a module known and not built: ask for its build, and assign it when it registers") args, err := parseAround(set, args) if err != nil { return err } if len(args) < 2 { return fmt.Errorf("%s […]", verb) } if *build && verb == "unassign" { return errors.New("unassign takes no --build") } open, err := openStores(ctx) if err != nil { return err } defer open.Close() // The act itself is in acts.go, so the command API refuses exactly what this refuses // (novox/hq ADR 0035). What differs between the surfaces is how the answer is printed. act := func(ctx context.Context, open *stores, node string, modules ...string) (string, error) { return assignWith(ctx, open, node, assignOptions{Build: *build}, modules...) } if verb == "unassign" { act = unassign } said, err := act(ctx, open, args[0], args[1:]...) if said != "" { fmt.Println(said) } if err != nil { fmt.Println() return err } return nil } func settingsCommand(ctx context.Context, args []string) error { if len(args) == 0 { return errors.New("settings show [--node ] [--history], settings set " + "[--node ] [--replace], settings clear [--node ], settings preferences " + "[] [--node ], settings propose [--node ] [--replace], " + "or settings proposals []") } open, err := openStores(ctx) if err != nil { return err } defer open.Close() inv := open.inventory set := flag.NewFlagSet("settings", flag.ContinueOnError) node := set.String("node", "", "one machine, rather than the whole mesh") // **What a set removes is refused unless meant** (novox/hq ADR 0217). A layer is replaced whole, // and on 2026-10-05 setting one placement dropped a machine's whole layer for a module without a // word (novox/hq issue 304). Adding and changing keys needs nothing; removing one needs this. replace := set.Bool("replace", false, "for set and propose: remove the keys the new layer does not name") history := set.Bool("history", false, "for show: the layers this one replaced, the latest first") clear := set.Bool("clear", false, "for propose: propose that the layer be removed") positionals, err := parseAround(set, args[1:]) if err != nil { return err } switch args[0] { case "propose": // A trusted setting, proposed by anyone and set only on the operator's warrant (novox/hq ADR 0277): // the stores are opened there, so the proposal and the verb's refusals below never meet. if len(positionals) < 1 || len(positionals) > 2 { return errors.New("settings propose [--node ] [--replace], or settings propose --clear [--node ]") } values := "" if len(positionals) == 2 { values = positionals[1] } // Once its config values are mesh-config's, they are proposed there (issue 664). if forwarded, err := forwardPropose(ctx, inv, positionals[0], *node, values, *clear); forwarded || err != nil { return err } return proposeCommand(ctx, positionals[0], *node, values, *clear, *replace) case "proposals": if len(positionals) > 1 || *node != "" || *clear || *replace || *history { return errors.New("settings proposals []") } id := "" if len(positionals) == 1 { id = positionals[0] } return proposalsCommand(ctx, id) } if *clear { return errors.New("--clear is for settings propose; a layer is cleared with settings clear") } where := "the whole mesh" if *node != "" { where = *node } // Once the move of config has run, a module in the config form is read and set through mesh-config (issue 664). if (args[0] == "set" && len(positionals) == 2) || ((args[0] == "show" && !*history) || args[0] == "clear") && len(positionals) == 1 { if forwarded, err := forwardSettings(ctx, inv, args[0], positionals[0], *node, positionals, *replace); forwarded || err != nil { return err } } switch args[0] { case "set": if len(positionals) != 2 { return errors.New("settings set [--node ]") } // A file, or the values themselves when they begin with `{` — which is how the mesh's own // `settings` tool passes them, having no file to hand over (novox/hq issue 198). var raw []byte if strings.HasPrefix(strings.TrimSpace(positionals[1]), "{") { raw = []byte(positionals[1]) } else if raw, err = os.ReadFile(positionals[1]); err != nil { return err } var values map[string]any if err := json.Unmarshal(raw, &values); err != nil { return fmt.Errorf("%s is not a settings file: %w", positionals[1], err) } before, _, err := inv.Layer(ctx, *node, positionals[0]) if err != nil { return err } if err := refuseTerminalSettingsThroughAVerb(ctx, inv, before, values, positionals[0], where); err != nil { return err } added, changed, removed := settingsChange(before, values) if len(removed) > 0 && !*replace { return fmt.Errorf("%s on %s: this layer would no longer set %s. A layer is replaced whole; "+ "read it with `settings show %s%s` and include what should stay, or add --replace if the "+ "removal is meant (novox/hq ADR 0217). Nothing was changed", positionals[0], where, strings.Join(removed, ", "), positionals[0], nodeFlag(*node)) } if err := inv.SetSettingsBy(ctx, *node, positionals[0], values, setByWords()); err != nil { return err } fmt.Printf("%s on %s:\n", positionals[0], where) if len(added)+len(changed)+len(removed) == 0 { fmt.Println(" nothing changed") } for _, p := range added { fmt.Printf(" + %s\n", p) } for _, p := range changed { fmt.Printf(" ~ %s\n", p) } for _, p := range removed { fmt.Printf(" - %s\n", p) } if before != nil { fmt.Printf(" the layer it replaced is kept: settings show %s%s --history\n", positionals[0], nodeFlag(*node)) } if *node != "" { fmt.Printf(" run `plan %s --diff` to see what it changes, `push %s` to send it\n", *node, *node) } else { fmt.Println(" run `push --behind` to send it to the machines running it") } return nil case "show": if len(positionals) != 1 { return errors.New("settings show [--node ] [--history]") } if *history { past, err := inv.SettingsHistory(ctx, *node, positionals[0]) if err != nil { return err } if len(past) == 0 { fmt.Printf("%s on %s: no layer has been replaced\n", positionals[0], where) return nil } for _, p := range past { shown, _ := json.MarshalIndent(p.Values, " ", " ") by := "" if p.SetBy != "" { by = "; " + p.SetBy } if p.Why != "" { // A layer whose placement moved out of it (migration 0097): still the layer, as it stands. by += "; " + p.Why } fmt.Printf("%s on %s, until %s (%s%s):\n %s\n", positionals[0], where, p.ReplacedAt.Local().Format("2006-01-02 15:04:05"), p.ReplacedBy, by, shown) } return nil } values, has, err := inv.Layer(ctx, *node, positionals[0]) if err != nil { return err } if !has { fmt.Printf("%s on %s: no layer — the module's definition says\n", positionals[0], where) } else { shown, err := json.MarshalIndent(values, "", " ") if err != nil { return err } fmt.Println(string(shown)) // And who set it (novox/hq ADR 0277): a layer the operator approved on their phone says so. if setBy, setAt, has, err := inv.LayerOrigin(ctx, *node, positionals[0]); err != nil { return err } else if has && setBy != "" { fmt.Printf(" %s\n", setBy) } else if has { fmt.Printf(" set at %s; who set it was not kept\n", setAt.Local().Format("2006-01-02 15:04")) } } // Every value the module gives a default or a layer sets, and where it came from (novox/hq // ADR 0262): the default, the mesh's layer, or this node's. Said after the layer, which stays // the first thing printed because a caller reads it before replacing it (ADR 0217). known, err := inv.Catalogue(ctx) if err != nil { return err } m, ok := known[positionals[0]] if !ok || len(m.Settings) == 0 { return nil } var layers []catalogue.Layer if *node != "" { if mesh, has, err := inv.Layer(ctx, "", positionals[0]); err != nil { return err } else if has { layers = append(layers, catalogue.Layer{From: catalogue.MeshWideLayer, Values: mesh}) } if has { layers = append(layers, catalogue.Layer{From: *node, Values: values}) } } else if has { layers = append(layers, catalogue.Layer{From: catalogue.MeshWideLayer, Values: values}) } fmt.Print(describeEffective(positionals[0], where, catalogue.Effective(m, layers))) return nil case "preferences": // Every module's preferences, and each machine's value with its source (novox/hq ADR 0262). if len(positionals) > 1 { return errors.New("settings preferences [] [--node ]") } only := "" if len(positionals) == 1 { only = positionals[0] } if *node != "" { // A machine the mesh does not know is refused, never answered with an empty listing. if _, err := inv.NodeByName(ctx, *node); err != nil { return err } } entries, err := inv.Catalogued(ctx) if err != nil { return err } var listed []preferencesOf for _, e := range entries { m := e.Manifest if len(m.Settings) == 0 || (only != "" && m.Module != only) { continue } if *node != "" && !containsString(e.On, *node) { // Asked for one machine: a module not on it has no value there to say. continue } p := preferencesOf{Manifest: m, On: map[string][]catalogue.SettingSource{}} for _, n := range e.On { if *node != "" && n != *node { continue } p.Nodes = append(p.Nodes, n) layers, err := inv.SettingsFor(ctx, n, m.Module) if err != nil { return err } p.On[n] = catalogue.Effective(m, layers) } listed = append(listed, p) } if only != "" && len(listed) == 0 { fmt.Printf("%s declares no preferences%s\n", only, onNode(*node)) return nil } if len(listed) == 0 && *node != "" { fmt.Printf("no module on %s declares a preference\n", *node) return nil } fmt.Print(describePreferences(listed)) return nil case "clear": if len(positionals) != 1 { return errors.New("settings clear [--node ]") } before, _, err := inv.Layer(ctx, *node, positionals[0]) if err != nil { return err } if err := refuseTerminalSettingsThroughAVerb(ctx, inv, before, nil, positionals[0], where); err != nil { return err } if err := inv.ClearSettingsBy(ctx, *node, positionals[0], setByWords()); err != nil { return err } fmt.Printf("%s on %s is back to what the module says\n", positionals[0], where) return nil default: return fmt.Errorf("settings has no %q; it has show, set, clear, preferences, propose and proposals", args[0]) } } // setByWords is who sets a layer from this process, as the layer keeps it (novox/hq ADR 0277): the caller at the // controller's terminal, or through which verb. func setByWords() string { if verb, through := throughAVerb(); through { return "set by " + link.Caller() + " through " + verb + " at " + time.Now().Local().Format("2006-01-02 15:04") } return "set at the controller's terminal by " + link.Caller() + " at " + time.Now().Local().Format("2006-01-02 15:04") } // describeEffective says each setting's value on a machine or the whole mesh, where it came from, and // the module's default when a layer overrides it. func describeEffective(module, where string, values []catalogue.SettingSource) string { var b strings.Builder fmt.Fprintf(&b, "%s on %s, every value and where it comes from:\n", module, where) for _, v := range values { value, _ := json.Marshal(v.Value) fmt.Fprintf(&b, " %s = %s (%s", v.Key, value, v.From) if v.HasDefault && !v.FromDefault { d, _ := json.Marshal(v.Default) fmt.Fprintf(&b, "; the default is %s", d) } b.WriteString(")\n") } return b.String() } // onNode is ` on ` for one machine, nothing for the whole mesh. func onNode(node string) string { if node == "" { return "" } return " on " + node } // preferencesOf is one module's preferences and its value on each machine it is assigned to. type preferencesOf struct { Manifest catalogue.Manifest Nodes []string On map[string][]catalogue.SettingSource } // describePreferences lists each module's preferences — key, default and why — and, per machine it is // assigned to, the value and where it comes from (novox/hq ADR 0262). func describePreferences(modules []preferencesOf) string { if len(modules) == 0 { return "no module declares a preference\n" } var b strings.Builder for i, p := range modules { if i > 0 { b.WriteString("\n") } on := "assigned nowhere" if len(p.Nodes) > 0 { on = "on " + strings.Join(p.Nodes, ", ") } fmt.Fprintf(&b, "%s (%s)\n", p.Manifest.Module, on) keys := make([]string, 0, len(p.Manifest.Settings)) for k := range p.Manifest.Settings { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { d := p.Manifest.Settings[k] def, _ := json.Marshal(d.Default) fmt.Fprintf(&b, " %s, default %s: %s\n", k, def, d.Why) for _, n := range p.Nodes { for _, s := range p.On[n] { if s.Key != k { continue } v, _ := json.Marshal(s.Value) from := s.From if s.FromDefault { from = catalogue.DefaultLayer } else if s.From != catalogue.MeshWideLayer { from = "the node" } fmt.Fprintf(&b, " %s: %s (%s)\n", n, v, from) } } } } return b.String() } // nodeFlag is ` --node ` for a machine's layer, nothing for the whole mesh's. func nodeFlag(node string) string { if node == "" { return "" } return " --node " + node } // describeOffers says what a module provides, and marks the ones answered from anywhere in the // mesh — because "provides a database" and "provides a shell" are read the same way and mean // entirely different things about where the answer has to be. func describeOffers(offers []catalogue.Offer) string { var out []string for _, o := range offers { if o.At() == catalogue.ScopeMesh { out = append(out, o.Name+" (from anywhere in the mesh)") continue } out = append(out, o.Name) } return strings.Join(out, ", ") } // pinCommand says which node a machine gets a provision from. // // Needed only when more than one could answer, and recordable before that -- a mesh with one // database should not change where an existing machine gets its data the day a second one // arrives. func pinCommand(ctx context.Context, args []string, setting bool) error { if setting && len(args) != 4 { return errors.New("pin ") } if !setting && len(args) != 2 { return errors.New("unpin ") } open, err := openStores(ctx) if err != nil { return err } defer open.Close() inv := open.inventory if !setting { if err := inv.UnpinProvision(ctx, args[0], args[1]); err != nil { return err } fmt.Printf("%s is no longer told where to get %s from\n", args[0], args[1]) return nil } // The provider's node may be this same machine: two modules beside the consumer can both // answer a provision, and then the module is the whole question (novox/hq #258). if err := inv.PinProvision(ctx, args[0], args[1], args[2], args[3]); err != nil { return err } fmt.Printf("%s gets %s from %s/%s\n", args[0], args[1], args[2], args[3]) fmt.Printf(" run `push %s` to send it\n", args[0]) return nil } // theBrokerSeat is the mesh-scoped seat the broker module claims (novox/hq ADR 0079: a // foundation seat is named after the server it guards). const theBrokerSeat = "mesh-broker" // brokerReachableAt is the broker's address as the given node can reach it. // // The genesis address (MESH_BROKER_ADDRESS) is the broker's public endpoint — right for a machine // that can dial it, wrong across the mesh, where the path is the overlay. A node that is ON the // overlay is given the overlay name of the node HOLDING the broker — the one assigned a module // claiming the mesh-broker seat — because that is where the broker is, whatever else the topology // says. Only when nothing holds the seat yet (genesis raised the broker as plumbing and no module // has adopted it) does the hub stand in, which is where the foundation is by convention. // // "On the overlay" is what `whereEveryoneIs` answers — a machine that RESOLVED the private network // — not "has an address", which is true of every placed machine and says nothing about // whether anything can reach it (novox/hq issue 059). A node not on the overlay — at genesis, // before any `overlay place`, which is when the builder's account is issued — keeps the genesis // address, so nothing about bring-up changes. This is issue 055, corrected by 059. func brokerReachableAt(ctx context.Context, inv *inventory.Inventory, known broker.Broker, node string) (string, error) { shelf, err := inv.Catalogue(ctx) if err != nil { return "", err } onNetwork, err := whereEveryoneIs(ctx, inv, shelf) if err != nil { // No catalogue yet is genesis, and at genesis the genesis address is the right one. return known.Address, nil } if onNetwork[node] == "" { return known.Address, nil } // Where the broker is: the node whose assigned set includes a module claiming the seat. holders := map[string]bool{} for name, m := range shelf { for _, c := range m.Claims { if c.Name == theBrokerSeat && c.At() == catalogue.ScopeMesh { holders[name] = true } } } brokerAt := "" if len(holders) > 0 { overlays, err := inv.Overlays(ctx) if err != nil { return "", err } for _, o := range overlays { assigned, err := inv.Assigned(ctx, o.Name) if err != nil { continue } for _, a := range assigned { if holders[a] && onNetwork[o.Name] != "" { brokerAt = onNetwork[o.Name] } } } } if brokerAt == "" { // Nothing holds the seat (or its node is not on the overlay): the hub, where the // foundation is by convention — and only a hub that is itself on the overlay, or the // name we hand out routes nowhere. overlays, err := inv.Overlays(ctx) if err != nil { return "", err } for _, o := range overlays { if o.Hub && onNetwork[o.Name] != "" { brokerAt = onNetwork[o.Name] } } } if brokerAt == "" { return known.Address, nil } // A portless genesis address is a working configuration (amqps defaults to 5671), and // silently keeping the public address would disable this whole path — so the port defaults // rather than the fix dissolving (novox/hq issue 059). _, port, err := net.SplitHostPort(known.Address) if err != nil { port = "5671" } return net.JoinHostPort(brokerAt, port), nil } // mayIssue says whether a module can be given a broker account. The account is delivered as the // module's own secret named broker; a module that declares none has nothing to read it with, and // an account nothing reads is an orphan on the bus (novox/hq 04-ISSUES/078). Said before the // account is made, not after. func mayIssue(m catalogue.Manifest) error { if _, reads := m.OwnSecrets["broker"]; !reads { return fmt.Errorf("%s declares no own secret named broker, so there is nowhere to deliver "+ "an account; a module that speaks on the bus declares \"own-secrets\": {\"broker\": }", m.Module) } return nil } // issueOnTheNewBus gives an assigned module its credential on the bus being built. // // **Three things differ from a management call, and each is the point of the move.** The credential // is minted into the mesh's records and becomes usable at the next composition, so there is no // server to be reachable for this to work. The password travels beside the address rather than inside // it, because the runtime's contract already separates them and a credential embedded in a URL is one // that leaks into every log line that prints a connection. And the module's durable consumer is // derived from what it declared rather than declared by name, so a module cannot ask for delivery of // something it did not say it consumes. func issueOnTheNewBus(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node, busAddress string) error { user := broker.Principal{Kind: broker.KindModule, Node: node, Module: m.Module}.Username() password, err := inv.MintBusPassword(ctx, inventory.BusUser{ Username: user, Kind: busKindOf(m.Module), Node: node, Module: m.Module, }) if err != nil { return err } // Where the module is told to find the bus, and what certificate it must present. The same pair // a node is told, for the same reason: a mesh's bus presents its own certificate, in no public // trust store, so an address alone fails at TLS. known, err := broker.FromEnvironment() if err != nil { return fmt.Errorf("cannot deliver a credential without knowing where the bus is: %w", err) } reachable, err := brokerReachableAt(ctx, inv, known, node) if err != nil { return err } if err := issueWith(ctx, inv, m, node, busAddress, known, reachable, user, password); err != nil { return err } if !runsTheProvisionerClient(m) { return nil } return issueProvisionerClient(ctx, inv, m, node, known, reachable) } // runsTheProvisionerClient says a module runs its machine's provisioner client, which reaches the bus on an // account of its own (novox/hq ADR 0349 clause 11, design 57 P3): it claims the seat and declares the own secret // that account is delivered in. The same test the bus's user list makes (inventory's declaredFor). func runsTheProvisionerClient(m catalogue.Manifest) bool { _, reads := m.OwnSecrets[broker.ProvisionerSecret] return reads && m.ClaimsSeat(broker.NodeProvisionerSeat) } // issueProvisionerClient gives a machine's provisioner client its account: minted under the client's own name, // never the module's or the tool runner's, and sealed to the machine as the module's own secret // broker.ProvisionerSecret, which the client's service reads (novox/hq ADR 0349 clause 11, design 57 P3). No // claims and no consumer: the client holds no seat on this account and is delivered nothing but its answers. func issueProvisionerClient(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node string, known broker.Broker, reachable string) error { user := broker.Principal{Kind: broker.KindProvisioner, Node: node, Module: m.Module}.Username() password, err := inv.MintBusPassword(ctx, inventory.BusUser{ Username: user, Kind: inventory.BusProvisioner, Node: node, Module: m.Module, }) if err != nil { return err } held, err := json.Marshal(struct { URL string `json:"url"` Fingerprint string `json:"fingerprint,omitempty"` Node string `json:"node"` User string `json:"user"` Password string `json:"password"` }{URL: "nats://" + reachable, Fingerprint: known.Fingerprint, Node: node, User: user, Password: password}) if err != nil { return err } if err := inv.AcceptSecretForModule(ctx, node, m.Module, broker.ProvisionerSecret, string(held)); err != nil { return err } if err := handIssuedToVault(ctx, inv, m, node, broker.ProvisionerSecret, string(held)); err != nil { return err } fmt.Printf("bus user %s minted for %s's provisioner client, scoped to %s's keys, its reports and the vault\n", user, m.Module, node) return nil } // busKindOf is what a module's bus user is recorded as: the node's tool runtime where the module is // the runtime (novox/hq ADR 0175), a module otherwise. The username is the same either way — the // runtime is issued through this same path — and the kind is what a reader of the records sees. func busKindOf(module string) string { if module == catalogue.RuntimeModule { return inventory.BusNodeTools } return inventory.BusModule } // issueWith is the delivery half: the minted password sealed to the machine as the module's broker // secret, and the module's consumer created where the bus can be reached. Split from the minting // so the move can issue every module against a bus whose address it worked out itself // (`rollout mint`, design 28 task 5.2) rather than the one in this process's environment. func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node, busAddress string, known broker.Broker, reachable, user, password string) error { // The seats this module claims, with the verbs each promises (novox/hq ADR 0159): the runtime // serves a claimed seat's verbs with its tools of the same name, and the bus admits only the // holder's subscription — so the runtime tries each claim and the grant decides. Written here // because this file is the one thing the mesh writes that the runtime reads before it speaks. claims, err := claimsFor(ctx, inv, m) if err != nil { return err } held, err := json.Marshal(struct { URL string `json:"url"` Fingerprint string `json:"fingerprint,omitempty"` Node string `json:"node"` Module string `json:"module"` User string `json:"user"` Password string `json:"password"` Claims []seatClaimed `json:"claims,omitempty"` }{ URL: "nats://" + reachable, Fingerprint: known.Fingerprint, Node: node, Module: m.Module, User: user, Password: password, Claims: claims, }) if err != nil { return err } if err := inv.AcceptSecretForModule(ctx, node, m.Module, "broker", string(held)); err != nil { return err } if err := handIssuedToVault(ctx, inv, m, node, "broker", string(held)); err != nil { return err } // And how it hears what it consumes. Derived from its declaration, and only when it declared // something: a module that consumes nothing needs no consumer, and creating one would be a // durable subscription nobody reads. if consumer, needed := broker.ConsumerFor(broker.Principal{ Kind: broker.KindModule, Node: node, Module: m.Module, Emits: m.EmitsAll(), Consumes: m.Consumes, Serves: m.Tools, }); needed { if busAddress == "" { fmt.Printf(" %s consumes; its consumer is created when the bus is reachable (`push --behind`, then "+ "`rollout mint` again is harmless)\n", m.Module) } else { js, err := broker.Dial(busAddress) if err != nil { return fmt.Errorf("the credential is minted and the mesh cannot reach the bus to create "+ "how %s hears what it consumes: %w", m.Module, err) } defer js.Close() if err := js.EnsureConsumer(consumer); err != nil { return err } } } fmt.Printf("bus user %s minted for %s, scoped to what it emits and consumes\n", user, m.Module) fmt.Printf(" sealed to %s. It arrives with the next push — `push %s` to send it\n", node, node) fmt.Printf(" and it works once the bus has been told: the user list is composed into the " + "machine holding mesh-broker\n") return nil } // whereItComesFrom is the provenance a module handed over by hand records, and what a record must // say to be worth anything later. // // **A module is a repository and a directory inside it** (novox/hq ADR 0069). A record carrying only // the repository names a module.json at its root, so every later build of it looks in the wrong // place — nine modules on this mesh were registered that way and none of them could be rebuilt // (2026-09-28). The directory cannot be checked from here, because the control plane does not clone; // what can be checked is that the record is whole. func whereItComesFrom(repository, ref, commit, path string, self bool) (inventory.Source, error) { // Provenance together or not at all. A source with no commit cannot be compared against // anything, so it would record where the module came from and still never be able to say the // mesh is behind it — which is the one thing recording it is for. if (repository == "") != (commit == "") { return inventory.Source{}, errors.New("--source and --commit go together: a source with " + "no commit cannot be compared against anything, and a commit with no source has " + "nothing to be compared with") } // A directory or a forge with no repository is half a location, and the half it keeps is the // half nothing can be found with. if repository == "" && (path != "" || self) { return inventory.Source{}, errors.New("--path and --self say where inside a source and " + "which forge holds it, so they need --source: without one there is nothing for them " + "to be part of") } from := inventory.Source{Repository: repository, Ref: ref, BuiltFrom: commit, Path: path} if self { if err := onASeat(repository); err != nil { return inventory.Source{}, err } from.Seat = gitSeat } return from, nil } // namesNoInstallation is the mesh refusing a definition that names an installation, at the moment // it would enter the catalogue (novox/hq ADR 0112, ADR 0155). `module check` says the same thing // earlier, where the author is; this is the last moment the mesh can still say no, and a // definition that got past the check — written elsewhere, or checked by nobody — is refused here // in the same words. A name meant on purpose is declared with its reason and passes. func namesNoInstallation(m catalogue.Manifest) error { if problems := catalogue.TrustProblems(m); len(problems) > 0 { return fmt.Errorf("%s", strings.Join(problems, "; ")) } // And a module that declares a setting names the keys of its mergeable files (novox/hq ADR 0326 // §3, issue 574): judged where a definition is written, never where a stored build is read again. if problems := catalogue.WrittenSettingsProblems(m); len(problems) > 0 { return fmt.Errorf("%s", strings.Join(problems, "; ")) } named := catalogue.InstallationProblems(m) if len(named) == 0 { return nil } return fmt.Errorf("%s names an installation, and a definition names none — declare a name meant "+ "on purpose under %s with its reason, or take it out:\n - %s", m.Module, catalogue.NamesOnPurpose, strings.Join(named, "\n - ")) } // seatClaimed is one seat a module claims, as its runtime needs it: the name, the scope (a // node-scoped seat's verb carries the machine, design 33 §4) and the verbs the seat promises. type seatClaimed struct { Seat string `json:"seat"` Scope string `json:"scope"` Serves []string `json:"serves,omitempty"` } // claimsFor joins a module's claims with the seats' protocols from the mesh's records. func claimsFor(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest) ([]seatClaimed, error) { if len(m.Claims) == 0 { return nil, nil } seats, err := inv.Seats(ctx) if err != nil { return nil, err } byName := map[string]catalogue.Seat{} for _, s := range seats { byName[s.Name] = s } var out []seatClaimed for _, c := range m.Claims { claimed := seatClaimed{Seat: c.Name, Scope: c.At()} if s, known := byName[c.Name]; known { claimed.Scope = s.Scope // The verbs the runtime serves for the seat: the claim's own when it names them // (ADR 0160), else every verb the seat promises, which its tools then answer. claimed.Serves = c.ServesFor(catalogue.Manifest{Tools: catalogue.VerbNames(s.Serves)}) } out = append(out, claimed) } return out, nil } // declaresTools is whether a module answers tools wherever it runs (novox/hq ADR 0197): it names // tools of its own, its build delivers a bundle the node's runtime serves, or it claims a seat // whose verbs it serves. A module with none is never expected to announce anything. func declaresTools(m catalogue.Manifest) bool { if len(m.Tools) > 0 { return true } for _, b := range m.Bundles { if len(b.Loads) > 0 { return true } } for _, c := range m.Claims { if len(c.Serves) > 0 { return true } } return false } // The keys no verb may change are catalogue.TerminalKeys (novox/hq issue 339). `places` says where the // node-engine creates and, as root, owns a module's directories, with an owner the setting names; `accesses` says // which of the machine's paths are mounted into a module's container; a provider's trust anchors say what every // consumer trusts. Set through a verb, any of them lets any caller of the mesh's verbs — an agent among them — // have root hand it a directory, mount one of the machine's into a container it reaches, or have the mesh trust // an authority of its own. They are the operator's, typed at the controller's terminal. // throughAVerb says whether this process runs a seat verb's command line: the serving controller names the // verb in the environment of every command it runs for one (runVerb), and a person at the terminal runs none. // Every verb route reaches a command through runVerb — the named verbs and the generic `command` alike — so // this is the one place that knows, whatever line the verb composed. func throughAVerb() (string, bool) { verb := os.Getenv(verbVar) return verb, verb != "" } // sameLayer says whether two layers hold the same values, an absent layer and an empty one alike. func sameLayer(a, b map[string]any) bool { if len(a) == 0 && len(b) == 0 { return true } ra, _ := json.Marshal(a) rb, _ := json.Marshal(b) return string(ra) == string(rb) } // refuseTerminalSettingsThroughAVerb refuses a layer change through a verb that would add, change or remove // places or accesses; a change that leaves both as they were is not refused. func refuseTerminalSettingsThroughAVerb(ctx context.Context, inv *inventory.Inventory, before, after map[string]any, module, where string) error { verb, through := throughAVerb() if !through { return nil } // Judged against the module's definition as the catalogue holds it: what it serves and which of its files // are trusted. A catalogue that cannot be read refuses rather than judging against nothing. shelf, err := inv.Catalogue(ctx) if err != nil { return fmt.Errorf("which settings of %s are the terminal's cannot be read, so nothing was changed: %w", module, err) } // A trusted mergeable file takes any key, so its module's whole layer is the terminal's (novox/hq issue 340). if files := catalogue.TrustedMergeable(shelf[module]); len(files) > 0 && !sameLayer(before, after) { return fmt.Errorf("the settings of %s on %s are set at the controller's terminal (`mesh-cli` on the control-node) or on "+ "the operator's warrant, never through a verb alone (this line came through %q): %s merges whatever key a "+ "layer sets into a file root or a consumer trusts, so any key could point the module at a listener of the "+ "caller's, and whoever may call a verb includes agents (novox/hq issue 340; a file nothing trusts says "+ "\"trusted\": false). Propose it instead: the settings verb with propose puts the exact values to the "+ "operator on a channel that proves who answers, and the layer is set on their Approve (novox/hq ADR 0277). "+ "Nothing was changed", module, where, verb, strings.Join(files, ", ")) } for _, key := range catalogue.TerminalKeys(shelf[module]) { was, _ := json.Marshal(before[key]) now, _ := json.Marshal(after[key]) if string(was) == string(now) { continue } return fmt.Errorf("%s of %s on %s is set at the controller's terminal (`mesh-cli` on the control-node) or on the "+ "operator's warrant, never through a verb alone (this line came through %q): it says where root creates and "+ "owns a module's directories, which of the machine's paths are mounted into its container, what the mesh's "+ "consumers trust, or what a file root or a person's session obeys takes, and whoever may call a verb "+ "includes agents (novox/hq issue 339; issue 340 for a mergeable file's own keys). Propose it instead: the "+ "settings verb with propose puts the exact values to the operator on a channel that proves who answers, and "+ "the layer is set on their Approve (novox/hq ADR 0277). Nothing was changed", key, module, where, verb) } return nil }