From c2a37ab7f826ac4a4c6d23ef817994415ce19a0c Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 1 Sep 2026 17:42:27 +0200 Subject: [PATCH] =?UTF-8?q?38=20=E2=80=94=20the=20mesh=20assigns=20the=20p?= =?UTF-8?q?ort,=20and=20a=20module=20does=20not=20care?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jochen's call, and the right one: a module cannot choose a port well, because it is written once and assigned anywhere. Any number it picks is a guess about a machine it has never seen, and two modules guessing the same number is not a mistake either of them made. Writing it up turned up something the issue had missed. The same number appears three times in every module — the rule set, what a consumer is told, and what the runtime publishes — and nothing checks that they agree. They agree today because one person wrote all three. A module whose `serves` said one thing and whose container published another would resolve, compose, apply, and hand every consumer a port that answers nothing. So the decision is one source with the other two derived, and an assignment made once and kept, as a credential is. The part that needed thought is ports that cannot move — mail on 25, submission on 587. Those become claims, which is what the mesh already has for what is singular on a machine. Two modules wanting 25 is the same shape as two wanting the seat, and gets refused by name at assignment rather than by a container runtime at apply. That makes this mostly a matter of pointing an existing mechanism at ports. Left open: whether a module should publish to the machine at all. Assignment makes publishing safe without making it necessary. --- .../0038-the-mesh-assigns-the-port.md | 82 +++++++++++++++++++ .../00-report.md | 18 +++- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 02-DECISIONS/0038-the-mesh-assigns-the-port.md diff --git a/02-DECISIONS/0038-the-mesh-assigns-the-port.md b/02-DECISIONS/0038-the-mesh-assigns-the-port.md new file mode 100644 index 0000000..4b51302 --- /dev/null +++ b/02-DECISIONS/0038-the-mesh-assigns-the-port.md @@ -0,0 +1,82 @@ +--- +topic: what runs on it +status: proposed +date: 2026-09-01 +deciders: jochen +reconstructed: false +rests-on: 02-DECISIONS/0009-modules-and-the-graph.md +--- + +# 38. The mesh assigns the port, and a module does not care + +## The problem, as met + +A database module cannot start on a machine that runs the control plane. The mesh keeps its own +store there and holds 5432; the module publishes 5432. Nothing notices until a container runtime +three layers down says `port is already allocated` +([`028`](../04-ISSUES/028-two-things-want-one-port-and-nothing-says-so/00-report.md)). + +A module cannot fix this by choosing better, because **a module cannot know what else is on the +machine.** It is written once and assigned anywhere. Any number it picks is a guess about a +machine it has never seen, and two modules guessing the same number is not a mistake either of +them made. + +## The number is written three times, and nothing makes them agree + +Every module says its port in three places: + +| where | for | example | +|---|---|---| +| `listens` | the rule set that lets traffic in | `{port: 5432, from: mesh}` | +| `serves` | what a consumer must know to connect | `{port: 5432}` | +| a container's `ports` | what the runtime publishes | `"5432:5432"` | + +They agree today because one person wrote all three. Nothing checks it. A module whose `serves` +said 5432 and whose container published 5433 would resolve, compose, apply, and hand every +consumer a port that answers nothing. + +## The decision + +**The mesh assigns the machine-side port, and the module says only what it needs.** A module +declares that a container port must be reachable and what it is for. Which number the machine uses +is the mesh's to choose, because the mesh is the only thing that knows what else is there. + +**One source, and the other two are derived.** `serves` carries the assigned port so a consumer is +told where to connect without the module having written it down; the rule set is computed from the +same assignment. Three copies become one fact. + +**An assignment is made once and kept**, exactly as a credential is. A port that moved on every +push would restart both ends each time and would hand consumers a number that was true when it was +read. + +## Some ports cannot move, and that is a claim + +Mail is 25, submission is 587, IMAP over TLS is 993. A mail system on a strange port is not a mail +system. So a module may say a port is **fixed by the protocol** rather than assigned. + +**A fixed port is exactly a claim** — the thing the mesh already has for what is singular on a +machine: one seat, one display server, one artifact store. Two modules wanting 25 on one machine is +the same shape as two wanting the seat, and gets the same answer: the second is refused, by name, +when it is assigned rather than when it is applied. + +That is why this does not need a new mechanism so much as it needs the existing one pointed at +ports. + +## What follows + +- **A module becomes portable in a way it was not.** Two databases on one machine stop being a + collision and become two assignments. +- **The substrate has to be visible.** The mesh cannot assign around its own store while it has + never heard of it. What the bundle holds must be written down somewhere the assignment can read + — which the bundle does not say today. +- **A refusal can be useful.** *25 is held by the mail system on this machine* is a sentence a + person can act on. `port is already allocated` is not. +- **`serves` stops being written by hand**, which is a small vocabulary change with a large + consequence: what a consumer is told is now derived from what actually happened. + +## What this does not settle + +**Whether a module should publish to the machine at all.** Assignment makes publishing safe; it +does not make it necessary. Consumers could instead reach a provider on the module's own network by +name, with nothing published — which would make the question moot for anything inside the mesh, and +would still leave it for anything reached from outside. diff --git a/04-ISSUES/028-two-things-want-one-port-and-nothing-says-so/00-report.md b/04-ISSUES/028-two-things-want-one-port-and-nothing-says-so/00-report.md index a4676c9..37fc251 100644 --- a/04-ISSUES/028-two-things-want-one-port-and-nothing-says-so/00-report.md +++ b/04-ISSUES/028-two-things-want-one-port-and-nothing-says-so/00-report.md @@ -3,7 +3,7 @@ status: located opened: 2026-09-01 located-in: [mesh-control] fixed-by: -amended-design: +amended-design: 02-DECISIONS/0038-the-mesh-assigns-the-port.md --- # 028 — Two things want one port, and nothing says so until the machine @@ -55,3 +55,19 @@ folklore. `listens` already says which ports a module accepts on, and filtering is computed from it. That is about what may reach a port from elsewhere. This is about two things on one machine wanting to own the same one, which `listens` does not model and could not answer. + +## Answered in principle + +[ADR 0038](../../02-DECISIONS/0038-the-mesh-assigns-the-port.md), proposed the same day: **the mesh +assigns the machine-side port and a module does not care.** A module cannot choose well, because it +is written once and assigned anywhere — any number it picks is a guess about a machine it has never +seen. + +A port fixed by its protocol — mail on 25, submission on 587 — becomes a **claim**, which is the +mechanism the mesh already has for what is singular on a machine. Two modules wanting 25 is the +same shape as two wanting the seat, and earns the same refusal at assignment rather than at apply. + +The record also names what this issue missed: the same number is written **three times** in every +module — once for the rule set, once for what a consumer is told, once for what the runtime +publishes — and nothing checks that they agree. A module whose `serves` and whose container +disagreed would hand every consumer a port that answers nothing.