Files
hq/02-DECISIONS/0051-a-module-configuration-is-its-assignments-not-its-manifest.md
T
jschoubben bfa08742f8 Consolidate hq: ADRs 0044-0052 merged in, and 0017/0049-0052 accepted
Brings the independent ADR branches (0044-0052) onto one branch so hq lands as a
single MR, and ratifies the five that were still proposed — 0017, and 0049-0052,
which are implemented and green in the lab. With 0053/0054 already accepted here, the
whole ADR chain 0044-0054 is accepted on this branch.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-05 03:01:06 +02:00

5.1 KiB

status, date, deciders, reconstructed, extends
status date deciders reconstructed extends
accepted 2026-09-04 jochen false 0005-capabilities-are-provisioned-on-declaration.md

51. A module's configuration is its assignment's, not its manifest's

Context

A module is assigned to a node — assign <node> <module>, always to a machine; there is no assignment to the mesh. "Mesh" is a scope, not a place: a provides or a claim scoped mesh reaches the whole mesh, but the module still runs on a node. So the two kinds of thing a module can carry are the manifest (what the module is) and, separately, what it should do here — which differs by deployment and by node.

The mesh already has the second: settings. settings set <module> [--node <node>] — with a node it is that machine's, without it the whole mesh's — layered over what the module declares and applied at resolution, changeable without editing the module and without a rebuild. That is the surface a meshboard would edit.

But settings today reach only a module's config-file content (a mergeable file the module owns). Configuration that is not a file has been landing in the manifest instead, statically — a registrar's zone and domain, the address public names point at, and, most sharply, listens.from. That last one is the tell: whether a port is open to the private overlay or to the public internet is a per-node deployment choice — the same database internal on one machine and public on another — and a value fixed in the manifest is one value for every machine, so it cannot be. Static configuration in the manifest is configuration in the wrong place: it cannot vary per node, and it cannot change without a new module version.

Decision

The manifest is identity and defaults; the assignment's settings are the configuration

A module's manifest declares what it is — what it provides, requires and claims, the shape of its resources — and, for anything configurable, a default. The values that make a running instance this instance are settings, carried by the assignment: per-node, or mesh-wide when no node is named, applied over the defaults at resolution. Change one and the next reconcile carries it; nothing is edited on a machine and nothing is rebuilt.

Settings drive the configurable fields the manifest marks, not only file content

Settings extend beyond a config file's content to the manifest fields a module declares settable — foremost:

  • listens.from: a module declares its safe default (from: mesh), and a per-node setting raises or lowers it. postgres declares listens: [{ port: 5432, from: mesh }]; on the machine that should expose it, a setting makes that port from: anywhere. Same module, different exposure, and the firewall (ADR 0050) is computed from the effective value, so the packet filter follows the setting.
  • A provider's own configuration: a registrar's zone, domain and the ingress its names point at (ADR 0049) are mesh-wide settings, not manifest constants — one mesh's Cloudflare zone is not another's, and the module description is the same for both.

Unset is the default, and an unknown setting is refused

A field with no setting keeps the manifest's default, so a module runs correctly configured by nobody. A setting that matches no settable field — like a config value that reaches no file today — is named, not silently dropped, so a misspelled setting is found rather than believed (the discipline of UnusedSettings, and of ADR 0048: a declaration is enforced or it is a comment).

Consequences

  • The postgres case works: one module, from: mesh by default, from: anywhere where a setting says so — internal on ace, public on novox, changeable live.
  • Provider modules stop carrying a mesh's specifics: cloudflare-dns describes a Cloudflare registrar, and which zone and ingress is a setting, so the same module serves every mesh.
  • Configuration becomes a thing a meshboard manages — set per node or mesh-wide, applied on the next reconcile — rather than a manifest edit and a rebuild (ADR 0004: the way you change a managed thing is not by editing it).
  • What a manifest may not do is grow a value that differs per machine; if it differs per machine it is a setting, and the manifest holds only the default.

References

  • ADR 0005 — what is provisioned on declaration; its per-instance values are the assignment's.
  • ADR 0004 — a managed thing is changed through the mesh, not by editing it; settings are that, for configuration.
  • ADR 0050 — the firewall follows the effective listens.from, so making from a setting makes exposure a setting.
  • ADR 0049 — the provider whose zone and ingress are settings, not manifest constants.