Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception.
111 lines
5.7 KiB
Markdown
111 lines
5.7 KiB
Markdown
---
|
|
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.
|