diff --git a/02-DECISIONS/0045-what-a-module-is.md b/02-DECISIONS/0045-what-a-module-is.md new file mode 100644 index 0000000..0cdebd9 --- /dev/null +++ b/02-DECISIONS/0045-what-a-module-is.md @@ -0,0 +1,98 @@ +--- +status: accepted +date: 2026-09-03 +deciders: jochen +reconstructed: false +supersedes: 0017-modules-outside-the-core-are-grouped-by-domain.md +extends: 0002-everything-is-a-module.md +--- + +# 45. What a module is + +## Context + +[ADR 0002](0002-everything-is-a-module.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. +[ADR 0017](0017-modules-outside-the-core-are-grouped-by-domain.md) 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-the-product-is-novox-mesh.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 0044](0044-what-the-sdk-holds-and-refuses.md)). +- A **control-plane context** (the mesh itself — [ADR 0015](0015-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 [ADR 0017](0017-modules-outside-the-core-are-grouped-by-domain.md).** Modules are + organised by their relationships (seats, provisions), not grouped into domain folders. +- **Refines [ADR 0002](0002-everything-is-a-module.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 0044](0044-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 0002](0002-everything-is-a-module.md) — everything is a module; this says what one is. +- [ADR 0015](0015-mesh-brokers-nodes-host-agents-think.md) — contexts are the mesh, not modules. +- [ADR 0017](0017-modules-outside-the-core-are-grouped-by-domain.md) — superseded. +- [ADR 0027](0027-the-product-is-novox-mesh.md) — protocol-not-product, generalised here. +- [ADR 0044](0044-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.