Files
hq/04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md
T
jschoubben 36454d7e4a Issues 173 and 174 resolved; the installation check refuses at registration (ADR 0155)
A setting overrides a key a contribution or served fact declares and adds none; a provider that must
tell its consumers an operator's value declares it as ${setting:…} (173). The mesh's own files for a
module are a placed directory, `place: "mesh"`, and forty-eight definitions name no host path for
them (174). Registration refuses a definition naming an installation, the day the list emptied
rather than a release later (0155, progressive insight; 134). Designs 27 and 18 carry the rules.
2026-09-30 22:36:20 +02:00

4.6 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
resolved 2026-09-30
mesh-controller internal/catalogue/settings.go (settle)
mesh-controller internal/catalogue/declaration.go (composed
ownNames)
mesh-controller PR 175 (a setting overrides a declared key and adds none; `${setting:…}` in a served or contributed value); mesh-catalog PR 196 (mail declares its domain, the identity provider its issuer) 03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md

173 — A module's settings reach every fact it contributes, not only the file that asked

What was observed

Setting the mail module's operator values — domain, sitename, website, proxy-address — so that its environment file could read them as ${setting:…} (ADR 0155), and then planning the control node, showed the four keys in places nothing asked for them:

  • in every route the module contributes to the proxy, beside label, endpoint and port;
  • in the database it contributes to the store's provider, beside the database's name;
  • in the smtp facts every consumer of its mail provision is bound to.

Nothing broke: a provider ignores a key it does not read. But a proxy now receives a mail server's proxy-address and website as if they were route facts, a consumer of mail is told the site's name, and a reader of plan cannot tell which of a contribution's keys the module meant and which leaked in.

Why this is here

Settings are one flat map per module, laid over every mergeable file, every contribution and every served fact alike (settle). That was the right generality when a setting was a contribution's override — a route's label is the example the code gives. It stops being right the day a setting is an operator's value for one file, which ADR 0155 made ordinary. The design permits a value to travel where nobody sent it, silently, and every consumer of a provision reads a map that grows with the provider's unrelated settings.

ADR 0112 and design 27 already say where this ends: a requirement has a contract, and a value goes to the requirement that asked for it. Until that form exists, this is the cost of the placeholder being the first case of it.

Open questions

  • Should a key a file asks for with ${setting:<key>} be withheld from contributions and served facts, or should a contribution's overrides live under their own key (contributes, serves)? The second is the shape design 27 draws; the first is the smaller change and keeps the leak from widening while it is drawn.
  • What does a consumer do with a served key it did not expect? Today: nothing, silently. A served map is not checked against what the provision's contract says it carries, because there is no such contract yet.

Resolved, 2026-09-30

A setting overrides a key a contribution or a served fact declares, and adds none. A file keeps taking any key, because a configuration file is where an operator adds things; a contribution and a served fact are a contract the other side reads, and a setting made for one of the module's files is no part of it. A key that lands nowhere — no mergeable file, no ${setting:…} asking for it, no contribution or served fact declaring it — is named as stray when the node is planned, rather than dropped.

The first open question is answered the smaller way, and it turned out to be the right one: the two keys consumers actually read through the leak — the mail provider's domain, the identity provider's issuer — are now declared by the provider in what it serves, as the operator's value (${setting:domain}, ${setting:issuer}), filled from the same setting that used to leak and refused by name when nothing sets it. So the contract says what travels, which is the shape design 27 draws, without a second key for overrides. The second question stands: a consumer still checks nothing against a contract, because there is none yet; what it is told is now only what the provider declared.

How it was checked: the plans of all four nodes, under the running controller and the one with the rule, compared resource by resource — every key that disappears from a contribution is a leaked file setting or one of the mesh's own words (expose, endpoints), and nothing a provider reads goes away; the two served keys were declared before the controller rolled. Unit tests: a setting a route never declared does not reach the proxy; a served value nothing sets is refused by name; the setting a served fact asks for is not stray.