diff --git a/02-DECISIONS/0009-modules-and-the-graph.md b/02-DECISIONS/0009-modules-and-the-graph.md index da422f4..a7b8db8 100644 --- a/02-DECISIONS/0009-modules-and-the-graph.md +++ b/02-DECISIONS/0009-modules-and-the-graph.md @@ -96,6 +96,43 @@ grants clients, a mail server grants mailboxes. It is a facet, not a kind. **A module may also declare what it claims**, because some things cannot coexist and that is a fact about the module rather than about a particular node. What that means precisely is below. +### An edge has two directions, and only one of them is built + +*Written 2026-08-29, from building it. The row above already says a consumer **supplies a target +and receives a name**; what it did not say is that those are two separate mechanisms, and that +having one without the other is what forced two modules outside the system entirely.* + +| direction | the consumer says | who needs it | +|---|---|---| +| **contribution** | *publish me at this name, on this port* | the proxy, the DNS server, a firewall | +| **binding** | *and give me back a credential to it* | the database, the object store, the identity provider | + +**Contribution is built.** A module declares what it contributes to a requirement; the control +plane collects every contribution on a node and writes them to a path the provider named, as a +file, in the mesh's own shape. **Contributing to something is requiring it** — asking to be +published means a publisher must exist, and a module that had to say both would eventually say +one, with the failure appearing as a machine where nothing serves the route. + +**The control plane does not know what a reverse proxy is**, and does not write one's +configuration. It delivers the facts; the module turns them into whatever it runs. That boundary +is what makes swapping the proxy cost nothing in any module that publishes through it, and it is +[the same separation](0001-mesh-brokers-nodes-host-agents-think.md) that keeps third-party +software *on* the mesh rather than *of* it. It also costs the host nothing: a received file is a +file, which was checked by putting the control plane's output through the host's own parser rather +than by asserting it. + +**Binding is not built**, and it is the larger half. It needs a secret to exist, be stored, be +delivered to one node and not the others, and be rotated with every holder informed — which is +[the invariant set](0001-mesh-brokers-nodes-host-agents-think.md) that was found violated three +ways at once, and the reason it is not something to add in passing. + +**What the absence cost, measured.** Exactly two modules opened a direct connection to the control +plane's database — the proxy and the VPN — and they are the reason every node permanently holds a +credential to it. Both were doing by hand what this edge is for. The VPN's half is closed by being +[a module whose files are computed](../03-DESIGN/01-to-be/08-connectivity.md); the proxy's is +closed by contribution. **Neither needed a new kind of thing, and both had been outside the model +for as long as there was one.** + ### Why the build edge is a different kind It is fixed inside an artifact rather than negotiated when something runs, and **its only remedy diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index 3f58828..7f8e596 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -99,6 +99,18 @@ back in by the names, which needed addresses only WireGuard hands out — and no Running two VPNs is not always wrong; being *the* one the mesh runs over is singular. So it is a claim, and the collision is refused by name. +**And the proxy's half, which was the other module reaching into the database.** A web application +requiring a reverse proxy has to say *which name, which port*, and there was nowhere to put it — +`requires` says a thing must exist and never said what to do with it. A module now contributes to +a requirement, the control plane collects every contribution on a node, and the provider is given +them as a file at a path it named. It reloads when that file changes, by the same `restart-on` the +private network needed when a peer list changed under a running interface. + +**The proxy's configuration is not written by the mesh.** It is given the facts and turns them +into whatever it runs, which is why swapping Traefik for something else touches nothing that +publishes through it. See [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) for the +other direction — handing a credential *back* — which is the larger half and is not built. + **What is still not a module, and why that is correct.** The host needs none of this. It has an address and a route before the mesh exists — that is the machine's own networking — and the broker's address is carried in the token rather than resolved