Files
hq/02-DECISIONS/0045-what-a-module-is.md
T
jschoubben b2cb481681 ADR 0045 — what a module is
A module is one self-contained piece of software the mesh installs and
manages; the software is its identity, and capabilities/seats/provisions
are the relationships between modules, not what a module is. Records the
three relationships (shared seat, exclusive seat, provide/require), that
interfaces are mesh-owned and providers adapt to them, and the naming
rule: draw the interface at the consumer's real coupling — neutral where
the coupling is thin (analytics), protocol-scoped where the consumer
speaks a protocol (postgres/mssql/mongodb), never false genericity.

Supersedes 0017 (domain grouping — wrong axis), refines 0002, generalises
0027's protocol-not-product rule.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-03 22:50:52 +02:00

5.7 KiB

status, date, deciders, reconstructed, supersedes, extends
status date deciders reconstructed supersedes extends
accepted 2026-09-03 jochen false 0017-modules-outside-the-core-are-grouped-by-domain.md 0002-everything-is-a-module.md

45. What a module is

Context

ADR 0002 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. ADR 0017 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).

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 0044).
  • A control-plane context (the mesh itself — ADR 0015).

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 ADR 0017. Modules are organised by their relationships (seats, provisions), not grouped into domain folders.
  • Refines ADR 0002. 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 0044) and shipping its provisioner as an adapter to a mesh interface — a de-coupling, not just a move.

References

  • ADR 0002 — everything is a module; this says what one is.
  • ADR 0015 — contexts are the mesh, not modules.
  • ADR 0017 — superseded.
  • ADR 0027 — protocol-not-product, generalised here.
  • ADR 0044 — per-module code lives in the module.
  • research 011 — the graph of these relationships.