Files
mesh-controller/internal/catalogue/setting_defaults.go
T
jochen 193168e086
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Place a left-out module's backup lines best effort, and refuse more identity keys (hq ADR 0262 review)
An unplaceable line of a left-out module, such as an access nobody placed, failed the whole machine's
declaration. Say it among what could not be placed instead, never copy the definition's path past a
placement that does not read, and accept a removal only when the decoder is past it.
2026-10-08 18:27:50 +02:00

235 lines
9.4 KiB
Go

package catalogue
import (
"fmt"
"sort"
"strings"
)
// A preference has a default; the operator's own value has none (novox/hq ADR 0262, extending ADR
// 0112 and taking the default half of ADR 0164).
//
// `${setting:<key>}` was refused whenever no layer set the key, and `settings set` refused a key no
// file asked for yet. Together they meant a running module could never gain a setting: the file that
// asks for it fails to compose until somebody sets it, and nobody can set it until the file asks.
// A definition may now give a key a default, with why, when the value is a **preference** — a font
// size, a width, a number of workers: something true of the software that a machine may tune. A
// value that is inherently the operator's — a domain, a public name, an identity, a secret — gets no
// default and stays refused by name until a layer sets it, which is what ADR 0112 and ADR 0155 were
// written for.
//
// The default is the lowest layer. The mesh's layer, then the node's, override it. It fills only
// `${setting:<key>}`: a mergeable file's content already is its defaults, and laying a default over
// it would reach every mergeable file of the module (novox/hq issue 168).
// SettingDeclaration is one key's default, as the definition gives it.
type SettingDeclaration struct {
// Kind says what sort of value this is. `preference` is the only kind with a default; it is
// stated rather than assumed so a reviewer sees the claim being made.
Kind string `json:"kind"`
// Default is the value when no layer sets the key: a string, a number or a boolean.
Default any `json:"default"`
// Why this default: one sentence for whoever wonders whether to change it.
Why string `json:"why"`
}
// KindPreference is the kind of a setting that may have a default.
const KindPreference = "preference"
// DefaultLayer is what the layer of a module's own defaults is called where a value's source is said.
// Only said: the layer is recognised by Layer.Default, never by this name, which a node may also have.
const DefaultLayer = "default"
// meshWords are the settings keys the mesh reads itself; a module declares none of them.
var meshWords = map[string]bool{
PortsSetting: true, ExposeSetting: true, ReachSetting: true, EndpointsSetting: true,
PlacesSetting: true, AccessesSetting: true, NetworksSetting: true,
}
// operatorsOwn are the words that, as what a key's name is about, say its value is the operator's and
// never a preference: a default for one would be the literal ADR 0112 removed from definitions.
var operatorsOwn = map[string]bool{
"domain": true, "host": true, "hostname": true, "servername": true, "fqdn": true, "zone": true,
"realm": true, "tenant": true, "site": true, "timezone": true,
"issuer": true, "url": true, "uri": true, "webhook": true, "origin": true, "dsn": true,
"ip": true, "ipv4": true, "ipv6": true,
"email": true, "mail": true, "phone": true, "address": true,
"identity": true, "login": true, "user": true, "username": true, "account": true, "owner": true,
"uid": true, "gid": true, "puid": true, "pgid": true,
"password": true, "pass": true, "passwd": true, "passphrase": true, "secret": true, "token": true,
"key": true, "apikey": true, "bearer": true, "cert": true, "certificate": true, "credential": true,
"nameserver": true, "gateway": true, "subnet": true, "sender": true, "recipient": true, "contact": true,
"trusted": true, "whitelist": true, "peer": true, "bind": true, "listen": true, "upstream": true,
"proxy": true, "admin": true, "mac": true,
}
// operatorsCompounds are names of two words that are the operator's though neither word alone says so
// at the end of a key: a client's identifier, and a name the world knows a site or server by.
var operatorsCompounds = map[string]bool{
"client-id": true, "site-name": true, "server-name": true, "public-name": true, "smtp-relay": true,
"host-name": true, "user-name": true, "domain-name": true, "dns-server": true,
// Whom a rule lets in or keeps out: a list of addresses or networks.
"allow-from": true, "deny-from": true, "allow-list": true,
}
// aboutAnAmount are first words that make a key about how many or whether, never about whom:
// `max-tokens` is a number, `show-hostname` a switch. Not `allow` or `use`: `allow-from` and
// `use-host` name whom.
var aboutAnAmount = map[string]bool{
"max": true, "min": true, "num": true, "count": true, "show": true, "hide": true, "enable": true,
"disable": true,
}
// operatorsWord is what in a key's name says its value is the operator's, or "". A key is about its
// last word — `url-timeout` is a timeout, `user-agent` an agent, `mail-domain` a domain — or its last two
// as one of operatorsCompounds. A plural is read as its singular.
func operatorsWord(key string) string {
words := strings.FieldsFunc(key, func(r rune) bool { return r == '-' || r == '_' || r == '.' })
if len(words) == 0 || (len(words) > 1 && aboutAnAmount[words[0]]) {
return ""
}
for i, w := range words {
switch {
case operatorsOwn[w]:
case strings.HasSuffix(w, "ies") && operatorsOwn[strings.TrimSuffix(w, "ies")+"y"]:
words[i] = strings.TrimSuffix(w, "ies") + "y"
case strings.HasSuffix(w, "s") && operatorsOwn[strings.TrimSuffix(w, "s")]:
words[i] = strings.TrimSuffix(w, "s")
}
}
if n := len(words); n > 1 {
// Where a secret or an identity is kept is the operator's too: `password-file`, `token-path`.
if (words[n-1] == "file" || words[n-1] == "path") && operatorsOwn[words[n-2]] {
return words[n-2] + "-" + words[n-1]
}
for _, last := range []string{words[n-1], strings.TrimSuffix(words[n-1], "s")} {
if pair := words[n-2] + "-" + last; operatorsCompounds[pair] {
return pair
}
}
}
if last := words[len(words)-1]; operatorsOwn[last] {
return last
}
return ""
}
// SettingProblems is every way a definition's setting defaults are wrong, in its own words.
func SettingProblems(m Manifest) []string {
if len(m.Settings) == 0 {
return nil
}
used := settingKeysUsedBy(m)
var problems []string
for _, key := range sortedSettingKeys(m.Settings) {
d := m.Settings[key]
say := func(format string, args ...any) {
problems = append(problems, fmt.Sprintf("%s's setting %q: ", m.Module, key)+fmt.Sprintf(format, args...))
}
if !settingRef.MatchString("${setting:" + key + "}") {
say("not a usable key: lower-case letters, digits, dots, dashes and underscores")
continue
}
if meshWords[key] {
say("the mesh reads %q itself, and a module gives it no default", key)
continue
}
if d.Kind != KindPreference {
say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", d.Kind, KindPreference)
}
if word := operatorsWord(key); word != "" {
say("a key naming %q is the operator's value, and has no default — it is the "+
"assignment's, never the definition's (novox/hq ADR 0112, ADR 0262)", word)
}
switch v := d.Default.(type) {
case string:
if strings.TrimSpace(v) == "" {
say("an empty default is no default; give the value, or declare nothing")
}
case float64, bool:
case nil:
say("no default: a key without one is the operator's, and is not declared here")
default:
say("a default is a string, a number or a boolean, and this is %T", d.Default)
}
if strings.TrimSpace(d.Why) == "" {
say("no why: say in one sentence why this default")
}
if !used[key] {
say("nothing asks for ${setting:%s}, so the default reaches nothing", key)
}
}
return problems
}
// Defaults is the layer a module's own defaults make, or nothing when it gives none.
func Defaults(m Manifest) (Layer, bool) {
if len(m.Settings) == 0 {
return Layer{}, false
}
values := map[string]any{}
for key, d := range m.Settings {
if d.Default != nil {
values[key] = d.Default
}
}
return Layer{From: DefaultLayer, Values: values, Default: true}, len(values) > 0
}
// WithDefaults is a module's layers with its defaults under them, for filling `${setting:<key>}`.
func WithDefaults(m Manifest, layers []Layer) []Layer {
d, has := Defaults(m)
if !has {
return layers
}
return append([]Layer{d}, layers...)
}
// SettingSource is one key's effective value and the layer it came from.
type SettingSource struct {
Key string
Value any
// From is DefaultLayer, MeshWideLayer or the node's name.
From string
// FromDefault is whether the value is the module's default, whatever From reads.
FromDefault bool
// Default is the module's default, when it gives one.
Default any
HasDefault bool
}
// Effective is every key a module gives a default or a layer sets, with its value and where it came
// from: the default, then the mesh's layer, then the node's — later wins.
func Effective(m Manifest, layers []Layer) []SettingSource {
byKey := map[string]*SettingSource{}
for key, d := range m.Settings {
byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, FromDefault: true,
Default: d.Default, HasDefault: true}
}
for _, layer := range layers {
for key, v := range layer.Values {
s, ok := byKey[key]
if !ok {
s = &SettingSource{Key: key}
byKey[key] = s
}
s.Value, s.From, s.FromDefault = v, layer.From, layer.Default
}
}
out := make([]SettingSource, 0, len(byKey))
for _, s := range byKey {
out = append(out, *s)
}
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
return out
}
func sortedSettingKeys(in map[string]SettingDeclaration) []string {
keys := make([]string, 0, len(in))
for k := range in {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}