136 lines
9.8 KiB
Markdown
136 lines
9.8 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: proposed
|
|
date: 2026-10-01
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md
|
|
---
|
|
|
|
# 164. A setting is declared with its default, its meaning and what changing it costs
|
|
|
|
## Context
|
|
|
|
The operator asked for one thing for every module, with the container runtime as the first case: **one
|
|
consistent default configuration for every machine, overridable per assignment, and easy to change
|
|
later.** The four machines' runtime configurations were each written by hand and differ — one keeps
|
|
running containers through a daemon restart and one does not, their log rotation differs, and each
|
|
names its resolver and its trusted registries in its own words.
|
|
|
|
Most of this was already decided.
|
|
[ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md) said the definition is
|
|
identity and **defaults**, the assignment's settings are the configuration, *unset is the default*, and
|
|
*an unknown setting is refused*. [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) made
|
|
a setting an operator requirement whose contract is "a type and, optionally, a default", answered by
|
|
"the assignment's, or the requirement's default, or unresolved". An assignment is a module on a node,
|
|
so the node layer of a module's settings already *is* the per-assignment override, and the mesh-wide
|
|
layer is the one consistent default a person changes once.
|
|
|
|
What was built is narrower than what was decided, measured in the controller on the day of deciding:
|
|
|
|
- **Nothing declares which keys are settable.** A mergeable file's content is its defaults, and every
|
|
key of it — and every key not in it — is accepted. Nothing tells a person, or the console, what can be
|
|
set, of what type, or what it means.
|
|
- **The refusal of an unknown setting is not there for most modules.** The stray-setting report returns
|
|
nothing at all for a module with any mergeable file, because such a file "takes any key"
|
|
([issue 173](../04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md) left
|
|
files that way on purpose). It reports rather than refuses where it does run.
|
|
- **A value in a file that is not JSON can have no default.** `${setting:<key>}`
|
|
([ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md)) is refused when no
|
|
layer sets it — right for a mail domain, where a default is the very literal 0155 removes, and wrong
|
|
for a tunable like the resolver's upstreams, which the resolver module therefore carries as literals
|
|
in its file.
|
|
- **What a change costs is said per file, not per key.** A service names the files it is reloaded or
|
|
restarted on. The runtime re-reads its trusted registries on a reload and its `dns` key only when it
|
|
starts; the resolver module declared a reload, so on two machines the key was written, reloaded,
|
|
and never read, and every container got a public resolver for weeks while everything read as
|
|
current ([issue 110](../04-ISSUES/110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/01-resolution.md)).
|
|
|
|
## Considered Options
|
|
|
|
1. **Leave settings implicit; document each module's keys in its README.** Rejected: a key the mesh
|
|
does not know cannot be refused, typed, listed by the console or costed, and a README is a rule
|
|
enforced by nothing.
|
|
2. **A second mechanism for tunables beside settings** — defaults in a new block, settings untouched.
|
|
Rejected: two ways to state one person's value, and design 27 already retires six mechanisms
|
|
that grew that way.
|
|
3. **Settings declared in the definition, as 0112's operator requirement: a key, a type, a meaning,
|
|
optionally a default, and what a change costs.** Adopted.
|
|
|
|
## Decision
|
|
|
|
**A module declares every setting it takes.** Each declared setting has a name, a type, one sentence
|
|
of meaning, optionally a default, and what a change to it costs. The spelling is design 27's to settle
|
|
with the rest of the requirement form; this record decides the content.
|
|
|
|
**A setting with a default is a tunable; a setting without one is the operator's.** A tunable resolves
|
|
to its default when no layer sets it — wherever it is read, a mergeable file or `${setting:<key>}` in a
|
|
file of any format. A setting with no default is refused by name when nothing sets it, as 0155 decided;
|
|
0155's refusal is narrowed to exactly that case, not changed for it. Whether a value has a default is a
|
|
fact about the software (a log size does, a mail domain does not), and the definition states it once.
|
|
|
|
**The layers stay as they are, and every value says where it came from.** The definition's default,
|
|
then the mesh-wide layer, then the node's — later wins, objects merge, lists replace. One consistent
|
|
configuration for every machine is the default plus the mesh-wide layer; one machine that differs says
|
|
so in its own layer and nothing else. Asked for a module's configuration on a machine, the mesh lists
|
|
every declared setting with its effective value and its source: *default*, *mesh*, or *node*.
|
|
|
|
**Changing later is changing one of three places, and the plan shows its reach before anything moves.**
|
|
A new default ships with the module's next version and reaches every assignment that does not override
|
|
it; a mesh-wide setting reaches every assignment of the module; a node's reaches one. The plan of a
|
|
change names each assignment whose effective value moves.
|
|
|
|
**A declared setting is the only kind accepted.** Setting a key the module does not declare is refused
|
|
when it is set, naming the declared keys, rather than reported when the machine is planned. The mesh's
|
|
own words — where a port, a directory or an operator's data is placed, how far an endpoint reaches —
|
|
are the mesh's to validate as they are today, and no module declares them. A module
|
|
that declares no settings keeps today's behaviour until it does; a catalogue test lists those modules,
|
|
and the list shrinks to empty before the implicit form is removed — design 27's rule for every retired
|
|
mechanism.
|
|
|
|
**A setting says what it costs: nothing, a reload, or a restart.** When a file changes, the host
|
|
applies the strongest cost among the settings whose values moved in it, so a key the software reads
|
|
only at start can no longer be written and never read. A setting that reaches a container's environment
|
|
costs that container being recreated, which the host already does when a container's specification
|
|
changes; it needs no declaration. A service's `reload-on` and `restart-on` keep
|
|
naming the files that are not settings — a generated roster, a credential.
|
|
|
|
**The container runtime is the first module to declare its settings** and the model for the rest:
|
|
its log rotation, keeping containers through a daemon restart, and its resolver are tunables, and
|
|
its trusted registries are what the mesh tells it.
|
|
|
|
## Consequences
|
|
|
|
- The console can show a module's settings as a form: what can be set, of what type, its default,
|
|
and where the current value came from. That is the surface the operator wants for changing a
|
|
default later.
|
|
- `settings set` can refuse an unknown key, so ADR 0046's rule is enforced where it was only stated.
|
|
- The resolver's upstreams, the runtime's log rotation, and other literals a definition carries
|
|
because it could not give them a default become declared tunables.
|
|
- **What got harder:** every module that takes settings must list them, and a mergeable file no
|
|
longer silently accepts a key its author did not foresee. A person who needs one adds it to the
|
|
definition, which is a new module version, not a setting.
|
|
- Issue 173's open question — a consumer checks nothing against a contract — is unchanged; this record
|
|
is the operator half of design 27's contract, not the provider half.
|
|
- Not decided here: the spelling (design 27); whether a node's layer may be narrowed to a single key
|
|
rather than replaced whole, as `settings set` does today.
|
|
|
|
## How this is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| Every setting a module takes is declared | A parser test refusing a setting declaration without a type or meaning; a catalogue test listing modules with mergeable files or `${setting:}` and no declarations, which must be empty before the implicit form is removed |
|
|
| A tunable resolves to its default; an operator value without one is refused | Resolution tests: an unset tunable in a JSON file and in a text file both take the default; an unset setting with no default is refused naming it (0155's existing test) |
|
|
| An undeclared key is refused when set | A controller test: `settings set` with an undeclared key fails naming the declared keys, and nothing is stored |
|
|
| Every effective value names its source | A test listing a module's configuration on a node with one key from each of default, mesh and node |
|
|
| A change's reach is shown before it moves | A plan test: changing a mesh-wide setting names every assignment whose effective value moves and no other |
|
|
| The strongest cost applies | A host test: a file where a reload-cost key and a restart-cost key both moved restarts; a file where only reload-cost keys moved reloads |
|
|
| Live | The container runtime's module lists its settings with their sources on every machine, and a mesh-wide change to its log rotation reaches all four at the next push |
|
|
|
|
## References
|
|
|
|
- [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md), [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)
|
|
- [Design 27 — a module requires, the mesh resolves](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md)
|
|
- Issues [110](../04-ISSUES/110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/00-report.md), [173](../04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md)
|
|
- mesh-controller `internal/catalogue/settings.go` (`settle`, `UnusedSettings`), `internal/catalogue/setting_into.go`
|