5.9 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.
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.