Found by fixing 027 and pushing again. The declaration is now accepted and the database container still cannot start: the mesh's own store holds 5432 on that machine, and the module publishes 5432. Nothing catches it because the substrate is not a module. It arrives from the bundle before there is a mesh to ask, so the control plane has never heard of the store and does not know it holds a port. Resolution can compare modules with each other and cannot compare one against what the mesh is built on. Nor does it compare modules with each other. A port is exclusive on a machine in exactly the way a claim is, and the mesh has a mechanism for that which ports do not use. It has been met before: the end-to-end test that exercises a real database publishes 5433 rather than 5432, inline, with nothing saying why. That is how a constraint becomes folklore. The open question is bigger than the bug. Whether a module should publish to the machine at all decides how a consumer reaches it, and changes what `serves` means.
58 lines
2.6 KiB
Markdown
58 lines
2.6 KiB
Markdown
---
|
|
status: located
|
|
opened: 2026-09-01
|
|
located-in: [mesh-control]
|
|
fixed-by:
|
|
amended-design:
|
|
---
|
|
|
|
# 028 — Two things want one port, and nothing says so until the machine
|
|
|
|
## Symptom
|
|
|
|
The database module cannot start on a machine that runs the control plane:
|
|
|
|
```
|
|
Bind for 127.0.0.1:5432 failed: port is already allocated
|
|
```
|
|
|
|
The mesh keeps its own store on that machine, from the bundle, and it holds 5432. The module
|
|
publishes 5432 too. Everything up to the machine is content: it resolves, it composes, it is
|
|
pushed, and it is applied — the container is simply the one resource that fails.
|
|
|
|
## Why nothing catches it
|
|
|
|
**The substrate is not a module.** It arrives from the bundle a host carries, before there is a
|
|
mesh to ask. So the control plane has never heard of `mesh-store` and does not know it holds a
|
|
port. Resolution can compare modules against each other and cannot compare a module against the
|
|
thing the mesh is built on.
|
|
|
|
**And nothing compares modules against each other either.** A port is exclusive on a machine in
|
|
exactly the way a claim is — one seat, one display server, one artifact store — and the mesh has a
|
|
mechanism for that, which ports do not use. Two modules both publishing 5432 would meet the same
|
|
wall, one machine later.
|
|
|
|
## It has been met before, and worked around
|
|
|
|
The end-to-end test that exercises a real database publishes `5433:5432` rather than `5432:5432`.
|
|
The workaround is right there, inline, with no note saying why — which is how a constraint becomes
|
|
folklore.
|
|
|
|
## What a fix has to settle
|
|
|
|
- **Whether a module should publish to the machine at all.** Consumers reach a provider by the
|
|
machine's address and the port it *serves*, so publishing is what makes that true. An alternative
|
|
is that they reach it on the module's own network by name, and nothing is published — which
|
|
changes what `serves` means and is a larger decision than it looks.
|
|
- **Where the substrate's ports are written down.** Whatever compares them needs to know what the
|
|
bundle holds. The bundle is a list of pinned references; what those containers bind is not in it.
|
|
- **What a refusal should say.** *5432 is held by the mesh's own store on this machine* is a useful
|
|
sentence. *Port is already allocated*, arriving from a container runtime three layers down, is
|
|
not.
|
|
|
|
## Not the same as a firewall rule
|
|
|
|
`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.
|