--- topic: what runs on it status: accepted date: 2026-09-03 deciders: jochen reconstructed: false extends: 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](0009-modules-and-the-graph.md) 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 1. **Shared seat** — several modules fulfil a capability and coexist; one may be default. bash, zsh and fish all join `shell`. 2. **Exclusive seat** — modules contend for a single slot; one holds it. i3 (needs x11) and sway (needs wayland) contend for `display-session`. 3. **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](../01-RESEARCH/005-domain-grouping/analysis.md)). This is [ADR 0027](0027-a-provision-names-what-the-consumer-is-coupled-to.md)'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](0039-what-the-sdk-holds-and-refuses.md)). - A **control-plane context** (the mesh itself — [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md)). 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](0009-modules-and-the-graph.md).** 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](0039-what-the-sdk-holds-and-refuses.md)) and shipping its provisioner as an adapter to a mesh interface — a de-coupling, not just a move. ## References - [ADR 0009](0009-modules-and-the-graph.md) — everything is a module; this says what one is. - [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) — contexts are the mesh, not modules. - The earlier "grouped by domain" decision — superseded (folded in consolidation; see the note above). - [ADR 0027](0027-a-provision-names-what-the-consumer-is-coupled-to.md) — protocol-not-product, generalised here. - [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) — per-module code lives in the module. - [research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) — the graph of these relationships.