ADR 0262: record the lenient read of stored manifests and the marked defaults layer

A stored manifest read strictly would fail the whole catalogue after a controller rollback, and a node
called default would lose its layer. Also scope the default, retire tunable, and list the new checks.
This commit is contained in:
jochen
2026-10-08 17:55:04 +02:00
parent 417db4d837
commit 0e5a5e2085
2 changed files with 28 additions and 5 deletions
+2 -1
View File
@@ -66,7 +66,7 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
**Purpose.** To say what one module is, in the one document every other domain reads.
**Recorded in** the catalogue, one manifest per module. **Upstream of** every other domain; its words
are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0040, 0126, 0174, 0188,
0207, 0236.
0207, 0236, 0262.
**Uses:** module, seat, provision (Provisioning), data class (Data), health (Health and repair).
- **manifest** — the file a module is defined by (`module.json`): what it is, what it provides and
@@ -89,6 +89,7 @@ are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0
software and only tuned per node: a font size, a width
([ADR 0262](../02-DECISIONS/0262-a-preference-has-a-default-in-its-manifest-and-the-operators-own-value-has-none.md)).
A setting with no default is the operator's own value: a domain, an identity.
*Not:* ~~tunable~~ (hq)
- **kept region** — a marked block in a managed file the mesh writes *into*, where the operator's own
lines survive every send and are given back when the module goes (ADR 0174). The other way a node
varies a module.
@@ -70,9 +70,10 @@ when nothing sets it, as ADR 0155 decided. This record narrows that refusal to e
does not change it.
**The settings override the default in a fixed order:** the manifest's default, then the mesh-wide
settings, then the node's, and the last one wins. A default fills `${setting:<key>}` wherever it
appears: a file's content, a contribution, a served fact. It is never merged into a mergeable JSON
file as a key, because that file's content already holds its own defaults, and a key laid over every
settings, then the node's, and the last one wins. A default fills `${setting:<key>}` in a file's
content, a contribution and a served fact. A module's zone name is the one served value it does not
fill: that name is the operator's own and has no default. A default is never merged into a mergeable
JSON file as a key, because that file's content already holds its own defaults, and a key laid over every
such file is the leak [issue 168](../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md)
describes.
@@ -89,6 +90,17 @@ the mesh, or the node. Where the mesh or the node overrides a default, the defau
**A default must be read by something.** A declared key that no file, contribution or served fact asks
for is refused, because a default that reaches nothing is the same mistake as a stray setting.
**The defaults are a layer of their own, marked as one.** A node's settings are named after the node,
and a node may be called anything, `default` included. The defaults are told apart by a mark on the
layer, never by its name.
**A stored manifest is read leniently; a manifest being registered is read strictly.** Registration
and the module check refuse a key the controller does not know, as before. Reading the catalogue the
store already holds sets such a key aside, says so once in the controller's log, and keeps the rest.
Without this, the first manifest to use `settings` would make every older controller fail to read the
whole catalogue, and with it every plan and every send on every node, the moment the controller was
rolled back. The same holds for every field a later record adds.
## Consequences
- The notifier's font size and width become preferences with today's values as defaults, and the
@@ -96,6 +108,11 @@ for is refused, because a default that reaches nothing is the same mistake as a
- A manifest that declares `settings` is refused by a controller older than this record, which reads
manifests strictly. The controller change is sent first, and a catalogue change that uses the field
merges only after it is live.
- **The lenient read protects rollbacks to this controller and later ones, not to earlier ones.** Once a
manifest in the store uses `settings`, rolling the controller back to a version before this record
still fails the catalogue read. Rolled back to this version or a later one, the module's defaults are
set aside with the field, so a module that relies on a default is left out of its machine's
declaration by name, and the rest of the machine is composed.
- **What got harder:** a module author now decides, for every `${setting:…}`, whether the value is a
preference or the operator's own, and says so. The check below catches the clear mistakes by the
key's name and by the value naming an installation. It cannot catch a preference default that is
@@ -112,12 +129,17 @@ for is refused, because a default that reaches nothing is the same mistake as a
| Rule | Checked by |
|---|---|
| A default is declared only for a preference, with why | The controller's manifest parser, run on every catalogue pull request as the module check: a `settings` entry is refused without `kind: preference`, without a default, with an empty or non-scalar default, or without `why` (`TestTheParserTakesAPreferenceAndRefusesTheRest`) |
| The operator's own value has no default | The same parser refuses a default for a key whose name says it is the operator's (domain, host, issuer, url, mail, address, identity, login, user, account, password, secret, token, credential), and the installation check of ADR 0155 already refuses a default naming a domain or a public address. `TestAnOperatorsValueWithoutADefaultIsStillRefused`: an unset key with no default is still refused by name |
| The operator's own value has no default | The same parser refuses a default for a key that has, as a whole word, a name of the operator's own value (among them domain, host, zone, url, ip, mail, address, user, owner, uid, password, secret, token, key, and client-id), and the installation check of ADR 0155 already refuses a default naming a domain or a public address. `TestAnOperatorsValueWithoutADefaultIsStillRefused`: an unset key with no default is still refused by name |
| The node over the mesh over the default | `TestTheNodeOverTheMeshOverTheDefault`, and `TestAComposedMachineGetsTheDefaultAndTheNodesValue` through a machine's whole composition |
| A default never becomes a key of a mergeable file | `TestADefaultIsNotMergedIntoAJSONFile` |
| A key with a default is accepted by `settings` | `TestAKeyWithADefaultIsNotStray` |
| A default reaches something | The parser refuses a declared key that nothing asks for (`TestTheParserTakesAPreferenceAndRefusesTheRest`, "read by nothing") |
| Every value says where it came from | `TestEveryValueSaysWhereItCameFrom` and `TestSettingsSayWhereEachValueComesFrom` |
| The operator's word is a whole word of the key | `TestAKeyIsTheOperatorsByAWholeWord`: `max-tokens`, `show-hostname`, `users-per-page`, `mailbox-size` are preferences; `api-key`, `oauth-client-id`, `public-ip`, `dns-zone` are not |
| A default is read by the installation check | `TestTheInstallationCheckReadsADefault`, including a setting called `description` |
| A default fills a contribution and a served fact, and never replaces a contribution's own value | `TestADefaultFillsAContributionAndAServedFactButNotALiteral` |
| A node called `default` is a node | `TestANodeCalledDefaultIsANodesLayer` |
| A stored manifest with an unknown key is read, and registration refuses it | `TestAStoredManifestWithAnUnknownKeyIsReadAndRegistrationRefusesIt` |
| Nothing changes where no machine sets a value | The notifier's manifest test fills the file with the defaults and finds today's lines; live, `mesh-controller.plan` for the workstation shows no change to the notifier's file after the catalogue change is sent |
## References