From 90ecfe6a01fb931934cbbe23292e312c0cc15ae1 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 29 Aug 2026 23:36:23 +0200 Subject: [PATCH] An edge has two directions, and only one of them is built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0009 already said a consumer supplies a target and receives a name. What it did not say is that those are two separate mechanisms. Contribution — publish me at this name, on this port — now exists. Binding — and hand me back a credential — does not, and is the larger half: a secret has to exist, be stored, reach one node and not the others, and rotate with every holder informed. That is the invariant set found violated three ways at once, so it is not something to add in passing. The absence had a measured cost. Exactly two modules opened a direct connection to the control plane's database, and they are the reason every node permanently holds a credential to it. Both were doing by hand what this edge is for. Neither needed a new kind of thing. --- 02-DECISIONS/0009-modules-and-the-graph.md | 37 ++++++++++++++++++++++ 03-DESIGN/01-to-be/08-connectivity.md | 12 +++++++ 2 files changed, 49 insertions(+) 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