Derive the terminal's settings from what a module serves and which files it trusts; a found directory is its own condition
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 superseded: a newer head of the same pull request

The third review of #170 (hq issue 339): a setting overrides any key a
provider serves, so any caller of the settings verb could move a database's
port, a registry's port or an issuer to a listener of its own and collect
what consumers present. TerminalKeys now derives from the manifest: places,
accesses, every served key and every setting a served value asks for, and
every setting a file marked `trusted` asks for. `trusted` is the catalogue's
word, taken out before the declaration; `module check` warns of a file that
asks for a setting without saying, and refuses it from 2026-10-30. The hand
list is gone. A directory used as found is now its own condition kind, the
operator's, never urgent, and the gate exempts it where it exempts a relogin.
This commit is contained in:
jochen
2026-10-09 01:23:44 +02:00
parent ec7b8bcd58
commit b9fc09c375
12 changed files with 368 additions and 58 deletions
+1
View File
@@ -913,6 +913,7 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
// such field, and the reason is for a reader of the manifest.
delete(copied, SecretsInEnvironment)
delete(copied, NamesOnPurpose)
delete(copied, TrustedField)
// **An operator's value, from the assignment** (novox/hq ADR 0112, ADR 0155): what a
// definition may not carry because it is true of one installation only. Filled from
// the same layers a mergeable file takes, and refused when no layer set it.
-21
View File
@@ -438,24 +438,3 @@ func namesOfAccessIDs(accesses map[string]string) []string {
sort.Strings(names)
return names
}
// trustAnchors are the settings a provider serves its consumers as what they trust, by module (novox/hq issue
// 339): set through a verb, any caller could point every consumer at an authority or an issuer of its own.
//
// - step-ca, the mesh's internal ACME authority: `root`, the root a consumer is handed to trust (the one
// setting that may hold lines, settingsHoldOneLine); `roots` and `path`, where a consumer fetches the roots
// and the ACME directory from, which a setting may override as it may any served fact.
// - keycloak, the identity provider: `issuer`, the issuer every OIDC consumer checks a login's token against.
//
// Named here, not in the manifests, because no manifest field says "this is trusted" yet; the catalogue was read
// for every served fact and every ${setting:…} on 2026-10-09, and these are the ones a consumer trusts.
var trustAnchors = map[string][]string{
rootModule: {rootSetting, "roots", "path"},
"keycloak": {"issuer"},
}
// TerminalKeys are the settings keys of a module that are set at the controller's terminal alone, never through
// a verb (novox/hq issue 339): places and accesses for every module, and a provider's trust anchors.
func TerminalKeys(module string) []string {
return append([]string{PlacesSetting, AccessesSetting}, trustAnchors[module]...)
}
+1
View File
@@ -90,6 +90,7 @@ func ApplySettings(resource map[string]any, layers []Layer) (map[string]any, err
out["content"] = string(rendered) + "\n"
delete(out, "merge")
delete(out, "protected")
delete(out, TrustedField)
return out, nil
}
+112
View File
@@ -0,0 +1,112 @@
package catalogue
import (
"fmt"
"sort"
"time"
)
// 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 marked `trusted` asks for**: 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. The manifest says so on the
// file (TrustedField), and `module check` names a file that asks for a setting without saying.
//
// 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
// 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"
// TrustRequiredFrom is when `module check` refuses a file that asks for a setting and does not say whether it is
// trusted. Until then it is warned and counted: the catalogue's files get the field in their own change, which
// can only land once a controller that takes the field out of the declaration runs.
var TrustRequiredFrom = time.Date(2026, 10, 30, 0, 0, 0, 0, time.UTC)
// 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, and every setting a file
// marked trusted 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 trusted, _ := r[TrustedField].(bool); !trusted || fmt.Sprint(r["type"]) != "file" {
continue
}
if content, ok := r["content"].(string); ok {
for _, asked := range settingsUsed(content) {
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.
func UnsaidTrust(m Manifest) []string {
var out []string
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "file" {
continue
}
content, _ := r["content"].(string)
if len(settingsUsed(content)) == 0 {
continue
}
if _, said := r[TrustedField]; !said {
out = append(out, fmt.Sprint(r["id"]))
}
}
sort.Strings(out)
return out
}
// 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
}
+41 -10
View File
@@ -128,17 +128,48 @@ func TestTheRuntimesDataAndAnyHomesSSHAreTheMachinesOwn(t *testing.T) {
}
}
// A trust anchor the mesh hands its consumers is the terminal's too (novox/hq issue 339): the authority's root,
// where its roots and directory are, and the identity provider's issuer.
func TestTrustAnchorsAreTerminalKeys(t *testing.T) {
for module, keys := range map[string][]string{
"step-ca": {"places", "accesses", "root", "roots", "path"},
"keycloak": {"places", "accesses", "issuer"},
"mailu": {"places", "accesses"},
// What a provider serves its consumers is the terminal's (novox/hq issue 339): any key under its `serves`, and any
// setting a served value asks for, is set at the terminal alone — a verb that could change a port could point every
// consumer at a listener of the caller's own. So is any setting a file marked `trusted` asks for.
func TestTerminalKeysAreDerived(t *testing.T) {
postgres := Manifest{Module: "postgres", Serves: map[string]map[string]any{"postgres-database": {"port": 5432.0}}}
keycloak := Manifest{Module: "keycloak", Serves: map[string]map[string]any{"oidc-client": {
"issuer": "${setting:issuer}", "token-path": "/protocol/openid-connect/token"}}}
power := Manifest{Module: "power", Resources: []map[string]any{
{"id": "logind", "type": "file", "path": "/etc/systemd/logind.conf.d/power.conf", "trusted": true,
"content": "HandleLidSwitch=${setting:handle-lid-switch}\n"},
{"id": "note", "type": "file", "path": "/var/lib/power/note", "trusted": false, "content": "${setting:greeting}\n"}}}
for _, c := range []struct {
m Manifest
want string
}{
{postgres, "places,accesses,port"},
{keycloak, "places,accesses,issuer,token-path"},
{power, "places,accesses,handle-lid-switch"},
{Manifest{Module: "plain"}, "places,accesses"},
} {
got := TerminalKeys(module)
if strings.Join(got, ",") != strings.Join(keys, ",") {
t.Errorf("%s: %v; want %v", module, got, keys)
if got := strings.Join(TerminalKeys(c.m), ","); got != c.want {
t.Errorf("%s: %s; want %s", c.m.Module, got, c.want)
}
}
}
// A file that asks for a setting says whether what it asks is trusted (novox/hq issue 339): `trusted` is a
// boolean, on a file alone, and a file asking for a setting without it is named.
func TestAFileSaysWhetherItsSettingsAreTrusted(t *testing.T) {
m := Manifest{Module: "power", Resources: []map[string]any{
{"id": "said", "type": "file", "path": "/etc/a", "trusted": true, "content": "${setting:a}"},
{"id": "unsaid", "type": "file", "path": "/etc/b", "content": "${setting:b}"},
{"id": "no-setting", "type": "file", "path": "/etc/c", "content": "plain"}}}
if got := strings.Join(UnsaidTrust(m), ","); got != "unsaid" {
t.Errorf("unsaid: %s; want unsaid", got)
}
for _, bad := range []map[string]any{
{"id": "x", "type": "file", "path": "/etc/x", "trusted": "yes", "content": "${setting:a}"},
{"id": "y", "type": "directory", "trusted": true},
} {
if problems := TrustProblems(Manifest{Module: "power", Resources: []map[string]any{bad}}); len(problems) == 0 {
t.Errorf("%v was taken", bad)
}
}
}