Declare preferences with a type and a predefined key, and lay mesh and node config between default and node (hq issue 630)

A preference may fall back to one reviewed mesh-wide or node-wide value instead of a module layer for the whole mesh; like a default, config fills ${setting:} only.
This commit is contained in:
2026-10-13 13:08:37 +00:00
parent 6f1a05366c
commit 3d16515771
6 changed files with 320 additions and 27 deletions
+35 -21
View File
@@ -114,15 +114,23 @@ func operatorsWord(key string) string {
return ""
}
// SettingProblems is every way a definition's setting defaults are wrong, in its own words.
// SettingProblems is every way a definition's setting defaults and preferences are wrong, in its own
// words. The list of predefined keys is not needed here; PredefinedProblems judges against it.
func SettingProblems(m Manifest) []string {
if len(m.Settings) == 0 {
declared := PreferencesOf(m)
if len(declared) == 0 {
return nil
}
used := settingKeysUsedBy(m)
var problems []string
for _, key := range sortedSettingKeys(m.Settings) {
d := m.Settings[key]
problems := preferenceProblems(m)
keys := make([]string, 0, len(declared))
for k := range declared {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
d := declared[key]
_, old := 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...))
}
@@ -134,23 +142,27 @@ func SettingProblems(m Manifest) []string {
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 old && m.Settings[key].Kind != KindPreference {
say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", m.Settings[key].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")
if old {
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)
}
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)
} else if v, isString := d.Default.(string); isString && strings.TrimSpace(v) == "" {
say("an empty default is no default; give the value, or declare nothing")
}
if strings.TrimSpace(d.Why) == "" {
say("no why: say in one sentence why this default")
@@ -164,11 +176,12 @@ func SettingProblems(m Manifest) []string {
// 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 {
declared := PreferencesOf(m)
if len(declared) == 0 {
return Layer{}, false
}
values := map[string]any{}
for key, d := range m.Settings {
for key, d := range declared {
if d.Default != nil {
values[key] = d.Default
}
@@ -189,7 +202,7 @@ func WithDefaults(m Manifest, layers []Layer) []Layer {
type SettingSource struct {
Key string
Value any
// From is DefaultLayer, MeshWideLayer or the node's name.
// From is DefaultLayer, MeshConfigLayer, NodeConfigLayer(node), MeshWideLayer or the node's name.
From string
// FromDefault is whether the value is the module's default, whatever From reads.
FromDefault bool
@@ -199,10 +212,11 @@ type SettingSource struct {
}
// 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.
// from: the default, then mesh config, then node config, then the module's own layer on the node —
// later wins (novox/hq issue 630).
func Effective(m Manifest, layers []Layer) []SettingSource {
byKey := map[string]*SettingSource{}
for key, d := range m.Settings {
for key, d := range PreferencesOf(m) {
byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, FromDefault: true,
Default: d.Default, HasDefault: true}
}