Files
mesh-controller/internal/catalogue/terminal_keys.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

189 lines
8.3 KiB
Go

package catalogue
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// Which settings are the controller's terminal's alone (novox/hq issue 339).
//
// A setting is the operator's word on how a module is configured, and the `settings` verb writes it for any
// caller allowed to call verbs — agents among them. Most settings change only the module itself. Some change
// what root or another module trusts, and those are said at the terminal alone, never through a verb:
//
// 1. `places` and `accesses`: where the node-engine creates and, as root, owns a module's directories, and
// which of the machine's paths are mounted into its container.
// 2. **Every key a provider serves**, and every setting a served value asks for. A setting overrides a served
// key (Settle), and what is served is what every consumer of the provision connects to and believes: a
// database's port, an object store's scheme, a registry's port, an identity provider's issuer and token
// path. Through a verb, any caller could point every consumer at a listener of its own and collect the
// credentials they present.
// 3. **Every setting a file asks for, unless the file says `"trusted": false`.** A file root or a consumer trusts —
// a logind drop-in, an env file that says which uid a container runs as, a script run as root — must not
// change through a verb, and the safe reading of a file that says nothing is that it is one of them (fail
// closed). `"trusted": false` is the opt-out, for a file nothing trusts: a person's own notifier settings.
// `module check` lists the files that say nothing, so an author can opt one out where that is true.
// **A mergeable file asks for every key its own content names** (novox/hq issue 340): a setting of that key
// lands in the file as a `${setting:…}` would, without the file ever spelling one. The agent's module keeps the
// managed settings and tool servers every Claude Code session on a node obeys in one, and through a verb an
// agent could have given every person's session a hook of its own.
//
// 4. **A module's whole layer, when it has a mergeable file not marked `"trusted": false`** (novox/hq issue 340,
// TrustedMergeable). A mergeable file takes any key a layer sets, not only those its content names, so no list
// of keys covers it: an empty runtime configuration a provider reads would take a `url` of the caller's.
// **Unless the module's files name the keys they take** (novox/hq ADR 0326, ADR 0329 §5): then a mergeable
// file takes exactly its `takes-settings` and no other key, so those keys of a trusted file are terminal keys
// and the rest of the layer is not. A key no file names is stray and refused where it is set. So betterbird's
// `tags`, in a file nothing trusts, is set through the verb, while its `accounts` and `identities` are not.
// 5. **Every setting a directory's owner asks for** (novox/hq issue 279): root chowns the directory to the number
// at the next push, so through a verb any caller could hand itself — or uid 0 — a module's folder. Whatever
// else asks for the key, a file marked `"trusted": false` included, the owner makes it the terminal's.
//
// Derived from the manifest, never listed by hand, so a provider or a trusted file added tomorrow is covered.
// TrustedField is the key a file resource carries to say whether the settings it asks for are trusted: true, or
// absent, makes each a terminal key; false says, out loud, that none changes what root or a consumer trusts. Said in the
// catalogue, never on the machine: the composer takes it out before the node-engine, which parses strictly.
const TrustedField = "trusted"
// TerminalKeys are the settings keys of a module that are set at the controller's terminal alone: places and
// accesses, every key its provisions serve and every setting a served value asks for, every setting a file
// asks for unless it says `"trusted": false`, and every setting a directory's owner asks for. Places and accesses first, then the rest sorted.
func TerminalKeys(m Manifest) []string {
keys := map[string]bool{}
for _, served := range m.Serves {
for key, value := range served {
keys[key] = true
if s, ok := value.(string); ok {
for _, asked := range settingsUsed(s) {
keys[asked] = true
}
}
}
}
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) == "directory" {
owner, _ := r["owner"].(string)
for _, asked := range settingsUsed(owner) {
keys[asked] = true
}
continue
}
if fmt.Sprint(r["type"]) != "file" {
continue
}
if trusted, said := r[TrustedField].(bool); said && !trusted {
continue
}
for _, asked := range asksFor(r) {
keys[asked] = true
}
}
delete(keys, PlacesSetting)
delete(keys, AccessesSetting)
rest := make([]string, 0, len(keys))
for k := range keys {
rest = append(rest, k)
}
sort.Strings(rest)
return append([]string{PlacesSetting, AccessesSetting}, rest...)
}
// UnsaidTrust is every file of a module that asks for a setting and does not say whether it is trusted, by id:
// each counts as trusted, and is listed so an author can opt out a file nothing trusts.
func UnsaidTrust(m Manifest) []string {
var out []string
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "file" {
continue
}
if len(asksFor(r)) == 0 {
continue
}
if _, said := r[TrustedField]; !said {
out = append(out, fmt.Sprint(r["id"]))
}
}
sort.Strings(out)
return out
}
// TrustedMergeable is every mergeable file of a module not marked `"trusted": false`, by id (novox/hq issue 340).
// A mergeable file takes any key a layer sets, not only those its content names: an empty runtime configuration a
// provider reads takes a `url` of a caller's as surely as a declared one. So a module holding one has its whole
// layer set at the controller's terminal; a verb may read it and change nothing. A module whose mergeable files
// name the keys they take (ADR 0326) has none here: each such file takes only its named keys, which TerminalKeys
// holds for a trusted one (ADR 0329 §5).
func TrustedMergeable(m Manifest) []string {
if namesItsKeys(m) {
return nil
}
var out []string
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "file" {
continue
}
if how, _ := r["merge"].(string); how == "" {
continue
}
if trusted, said := r[TrustedField].(bool); said && !trusted {
continue
}
out = append(out, fmt.Sprint(r["id"]))
}
sort.Strings(out)
return out
}
// asksFor is every setting a file resource asks for: each `${setting:…}` in its content and, for a mergeable file,
// every key at the top of the content it merges into (novox/hq issue 340). Those are the keys the file declares it
// takes: a layer's value for one of them lands in it as surely as a placeholder would be filled. A key the content
// does not name may land too, but nothing reading the file was written to read it.
func asksFor(r map[string]any) []string {
content, _ := r["content"].(string)
asked := settingsUsed(content)
if how, _ := r["merge"].(string); how != "" {
if _, names := takenKeys(r); names {
// The file takes exactly the keys it names (ADR 0326), whatever its content holds, and a renamed
// one under the file's own spelling too, which a layer said before its module moved (issue 751).
asked = append(asked, takenNames(r)...)
sort.Strings(asked)
return asked
}
}
if how, _ := r["merge"].(string); how != "" && strings.TrimSpace(content) != "" {
var base map[string]any
if json.Unmarshal([]byte(content), &base) == nil {
for key := range base {
asked = append(asked, key)
}
}
}
sort.Strings(asked)
return asked
}
// TrustProblems are the ways a manifest states `trusted` wrongly: anything but true or false, or on anything but
// a file. Refused at registration and by `module check`.
func TrustProblems(m Manifest) []string {
var out []string
for _, r := range m.Resources {
v, said := r[TrustedField]
if !said {
continue
}
if fmt.Sprint(r["type"]) != "file" {
out = append(out, fmt.Sprintf("%s: %v is a %v and says %q; only a file says whether the settings it "+
"asks for are trusted (novox/hq issue 339)", m.Module, r["id"], r["type"], TrustedField))
continue
}
if _, ok := v.(bool); !ok {
out = append(out, fmt.Sprintf("%s: %v says %q as %v; it is true or false (novox/hq issue 339)",
m.Module, r["id"], TrustedField, v))
}
}
return out
}