--- topic: what runs on it status: accepted date: 2026-08-28 deciders: jochen reconstructed: false --- # 9. Modules and the graph *Consolidated 2026-08-28 from six records.* ## Everything is a module One kind of thing, one manifest describing all of them. A database, a web application, a window manager and a firewall rule set are all modules — not because they are alike, but because **anything else means a second kind of thing with its own rules, and then a third.** **A module is the unit of delivery**: assignable to a node, versionable, replaceable on its own. ## There are no domain modules An earlier decision grouped modules by domain — four things constituting *how a node is reachable* becoming one `networking` module. **That was wrong, and the correction is worth keeping** because the observation behind it was right. The measurement holds: reachability is the **only** place in the catalogue where modules genuinely change together under one intent. What did not hold is the conclusion. Tight coupling means they share an **authority** — one place that decides for all of them — and not that they should be one artifact. `wireguard` and the proxy are deployed to different sets of nodes, so a module containing both would be assigned where half of it is unwanted. > **Coherence is a context. Delivery is a module.** **Folders assert relationships; edges record them.** What grouping was for — finding things, seeing what belongs together — is a tag and a query, neither of which anybody has to keep true by hand. ## Three edges | edge | means | declared? | satisfied | |---|---|---|---| | **presence** | that thing must exist and be reachable here | yes | at provisioning | | **instantiation** | that thing makes something for me and hands back credentials — a database, a bucket, a route | yes | at provisioning, and again whenever it must be | | **build** | I was compiled against that artifact | **no — read from imports** | **at build, once** | **Instantiation implies presence; presence does not imply instantiation.** **A route is an instantiation edge**, and it is worth noticing because the direction is the mirror of a database: the consumer supplies a target and receives a *name*, rather than supplying nothing and receiving credentials. Same edge. **Provider stops being a category.** Any hosted thing can be a factory — an identity provider grants clients, a mail server grants mailboxes. It is a facet, not a kind. **A module may also declare exclusion**, because some things cannot coexist on one machine and that is a fact about the module rather than about a particular node. ### Why the build edge is a different kind It is fixed inside an artifact rather than negotiated when something runs, and **its only remedy is a rebuild** — nothing can re-provision it. It is also **derived rather than declared**, and the asymmetry is deliberate: a runtime edge is an *intention* somebody has about how the mesh should be wired, and only a person can state it. A build edge is a *fact about code that already exists*, and a declared list of dependencies drifts from the imports it describes. **An artifact is out of date when its source moved, or when anything it was built against moved.** So what is recorded is a commit *and the identity of every artifact it was built against*, which is what makes the rebuild set computable and *is this current?* answerable without building. **The graph measures design quality, not just build order.** A module with many inbound build edges is one whose every change is expensive — and that is readable before anything is built. The current shared library is exactly that, and nobody could see it because nothing drew the edges. ## Provisioning is declared, never configured by hand A module declares what it **provides** and what it **requires**. The mesh satisfies it: a provisioner belonging to the provider creates the resource and its credential, records the grant, and the values are derived onto the consumer. **Neither the credential nor the topology is ever written by hand.** A requirement may name a provider on another node, so cross-node wiring is the same declaration. ## The core library is the mesh's domain One module everything may depend on. It holds **what is true of the mesh regardless of which context you are in**: a module, a node, an assignment. The test: *would this still mean the same thing in a context that had never heard of the one it came from?* A node would. A pipeline stage would not — that is delivery's. **Types ship with the module that owns them**, not here. A consumer needing `inventory`'s types depends on `inventory` — one narrow, visible edge — rather than everything depending on a hub where the relationship cannot be seen. **A library everything depends on is expensive to change whether it holds types or code; the fan-in is what makes it expensive**, which is why *types, not behaviour* was the wrong guard. **It stays small on its own.** A domain model changes when what the mesh *is* changes, which is rare. A drawer labelled *shared* changes whenever anybody writes something reusable, which is constantly — and *who else might want this* always answers yes, which is how the current one grew. ## Consequences - **Fewer things will be shared, and some code will be written twice.** That is the trade: the current library exists because sharing felt free. Two similar functions in two modules is often the better answer. - **The check is a measurement rather than a prohibition.** Inbound build edges say when something is becoming a hub, while it is happening rather than after. - **Reading build edges needs a language-aware tool per language**, which is the real cost and the reason declaring them looks tempting. It is still wrong.