Every configurable thing on a node is a module, the home included, and a module is whatever it declares (0173, extending 0040). A node varies a module only through a setting rendered into the file or a kept region, never an edit (0174, extending 0011; issue 168 first). One tool runtime per node serves every module's tools on the host side, never in a container; the console is its serving mode, renamed node-tools (0175, extending 0150; 0047/0150/0152 carry dated notes). The login shell is a node seat held by one shell module with `execute` as its contract (0176). A unit may be user-scoped and the service manager is a node seat held by systemd (0177). To-be 37 is handed off in-progress to mesh-host, mesh-controller, mesh-tools and mesh-catalog, with the build in order: the account on every node, the runtime, zsh, systemd, then the graphical stack. To-be 29 keeps ~/.ssh and points at 37; 33 §6 and 34 are amended; the glossary gains node tools, bundle, kept region, installed/holding, and retires flavor.
6.5 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| what runs on it | accepted | 2026-09-03 | jochen | false | 0009-modules-and-the-graph.md |
40. What a module is
Reconciliation note (2026-09-05): supersedes the earlier "grouped by domain" decision, which the consolidation folded into how-we-build.md; no standalone record remains to point at.
Context
ADR 0009 settled that everything is a module, but never said what a module is beyond "a directory the mesh processes." That gap let the catalogue's breadth read as a smell: a module can carry a container, a built image, tools, a provisioner, migrations, health, config, seat claims, requires and provides — so much that the unit seemed ill-defined. The earlier "grouped by domain" decision (folded in consolidation; see the reconciliation note above) tried to organise modules by domain, which is the wrong axis. This record states what a module is, drawn from the cases that stress-tested it: the shell, i3-vs-sway, umami, and "database."
Decision
A module is one self-contained piece of software the mesh installs and manages — everything needed to make that one thing real and integrable: what runs, the seats it claims, what it provides to other modules, what it requires from them, and what operates it.
The software is the module's identity. Capabilities, seats and provisioned resources are the
relationships between modules, not what a module is — and that is what binds a module into one
thing. umami is bound by being umami: its container runs umami, its provisioner creates umami sites,
its tools query umami, its requires gets umami a database. Every feature serves the one software.
The three relationships
- Shared seat — several modules fulfil a capability and coexist; one may be default. bash, zsh
and fish all join
shell. - Exclusive seat — modules contend for a single slot; one holds it. i3 (needs x11) and sway
(needs wayland) contend for
display-session. - Provide / require — a provider ships the provisioner that creates instances of the resource it offers and returns sealed credentials; a consumer requires it and the mesh wires the credential in. Symmetric: umami requires a database and provides analytics.
Interfaces are mesh-owned; providers adapt to them
The mesh defines the interface for a capability — the provider-neutral contract of what a consumer receives and how it integrates. Both sides conform: a provider's provisioner adapts its software's real API to the mesh contract; a consumer depends on the interface, never on a provider. Swap one provider for another and the consumer does not change.
The naming rule — draw the interface at the consumer's real coupling
Name a provides/requires at the widest boundary across which the consumer genuinely does not
care which implementation serves it:
- Where the consumer's coupling is thin — an analytics embed snippet and dashboard, opaque to it —
the mesh defines a neutral interface (
analytics) and providers (umami, amumi) adapt. Swappable across vendors. - Where the consumer speaks a protocol — a database's wire protocol and query dialect — the
interface is the protocol:
postgres-database,mssql-database,mongodb-database. Swappable only among protocol-compatible implementations, never across, because the application cannot cross it either. "database" is not a capability; the protocol is. - Never false genericity. A name must not promise a swap the contract cannot deliver (research 005).
This is ADR 0027's rule made general — "names the protocol, not the product; a database names the engine because the app targets it" — with the reason stated: the contract sits where the coupling is.
What is not a module
- A library (built against, never deployed — ADR 0039).
- A control-plane context (the mesh itself — ADR 0001).
A swappable machine mechanism (a firewall — ufw, nftables) is a module implementing a capability. The host hardcodes no firewall, supervisor, package manager or runtime; it owns only the generic apply primitives and platform detection, so it runs where none of those exist — an Android phone has no ufw, systemd, pacman or Docker.
The mechanism changed — 2026-10-02, by ADR 0176. The shell example above — bash, zsh and fish all join
shell; one may be default — is read as installed is not holding: the three may all be installed, and thelogin-shellseat is node-scoped and held by exactly one. The decision — what a module is, and the three relationships — stands; ADR 0173 applies it to the operator's whole machine.
Consequences
- Supersedes the earlier "grouped by domain" decision (folded in consolidation; see the reconciliation note above). Modules are organised by their relationships (seats, provisions), not grouped into domain folders.
- Refines ADR 0009. Everything the mesh runs and integrates is a module — but a module is defined by the software it delivers, not by being a bucket of features.
- The target is a self-fulfilling mesh: declared wants bound to swappable modules, provisioners wiring credentials, nothing hardcoded. The control plane's whole job is the binding.
- Converting a module from the old system includes pulling its per-module code out of the shared SDK (ADR 0039) and shipping its provisioner as an adapter to a mesh interface — a de-coupling, not just a move.
References
- ADR 0009 — everything is a module; this says what one is.
- ADR 0001 — contexts are the mesh, not modules.
- The earlier "grouped by domain" decision — superseded (folded in consolidation; see the note above).
- ADR 0027 — protocol-not-product, generalised here.
- ADR 0039 — per-module code lives in the module.
- research 011 — the graph of these relationships.