Files
hq/04-ISSUES/094-a-port-published-as-the-machine-side-cannot-be-moved/01-diagnosis.md
T
jschoubben 668ce3ad62 Issue 094 resolved: a given port names either end and is answered once
The first pass answered under both ends, which review showed is the same fault seen
from the other side where two mappings share a number. Verified on the machine: the
forge is back on the port its own configuration has always advertised.
2026-09-23 02:37:07 +02:00

81 lines
5.2 KiB
Markdown

# Diagnosis — 2026-09-23
## One cause, two symptoms
Both halves of the report come from the same blind spot, in two different places.
**The check that refused the setting** collected, for each entry a container publishes, only its
**last** segment. A short form publishes one number, which is the container's port and the
machine's at once, so reading the last segment is right. A long form — a machine port mapped to a
different port inside the container — has two, and the last segment is the container's. So the
machine side, the number the module names in `listens`, in `serves` and in every port
substitution, was not in the set of ports the module was considered to publish, and giving it one
was refused as naming nothing.
**The lookup that made naming the other half useless** reads the same map under two different
keys. Everything derived from what the module *declares* — the filter, the openings, the guard,
what a consumer is told, a port substituted into a file — looks the machine side up. The one
reader that rewrites the mapping handed to the container runtime looks up the container's port.
For a short form those are the same number and no one notices. For a long form they differ, so
exactly one reader ever found the entry: keyed the only way the check allowed, the container's
mapping moved and nothing else did, leaving a firewall, a set of openings and a consumer all
pointing at a port the software had left.
That is also the third symptom in the report, seen from the other end. No opening was derived for
the moved port because the opening is derived from the declared port, which still read as the old
number; and a per-node `expose` could not rescue it, because `expose` keys on the same declared
port — it widens the opening on the port nothing is on any more, and naming the real machine port
is refused as a port the module does not listen on.
## What was ruled out
The openings derivation itself. Composed with no setting at all, a module publishing a long-form
mapping on an adopted node does get its opening, on the machine side, forwarded to the container's
port. A regression test now records that, deliberately passing before the fix as well as after, so
the next reader does not go looking there.
## Answering the first open question
**Either end names the mapping, and both answer.** A module publishing `"2222:22"` may reasonably
say it listens on the port its software uses or on the port the machine serves; the mesh accepts
whichever the setting names and returns the machine port under both, so every reader finds the
same number under the key it happens to hold. This keeps working what already worked — the
container's end was the only key the old check accepted — and makes it mean the same thing.
Ambiguity is refused where it is real: one number naming two **different** mappings, and the two
ends of **one** mapping given two different machine ports. Naming both ends of one mapping with
the same number was already refused, by the rule that a machine port has one holder.
## What this does not close
The second and third open questions stand, and they are the push-blocking half: the setting is
still **stored** without a manifest in view, so a key that names nothing a module publishes is
accepted where it is set and refused at composition — where it stops the node being told anything
at all, rather than stopping that module. The fix removes one reason a key could be wrong; it does
not remove the shape of the failure. Carried to [issue 096](../096-a-setting-that-cannot-work-is-stored-and-stops-the-node/00-report.md).
## Resolved — 2026-09-23
A given port names either end of a mapping and is answered **once**, under the end the module
names in its own `listens` — the number the plan, the filter, the openings, the guard and the
consumer all ask for. A number that names two of a module's mappings is refused, whether the
setting used it or the answer would be filed under it.
The first pass filed the answer under both ends. Review found that this is wrong wherever two
mappings share a number: the second key lands where another mapping's reader looks, the later
write wins, and a module publishing `8080:80` beside `9090:8080` had an explicit setting silently
replaced — the container moving to one number while the filter, the opening and the consumer kept
another. Reintroducing the fault, inside the fix for it. Two mappings onto one machine port came
with it, where before the setting had been safely refused. Neither was reachable against the
catalogue as it stands; both were silent, which is worse than reachable.
**Verified on the machine**, not only in tests: the forge was given `{"2222": 222}`, its container
now publishes `222:22`, the opening derived for it is `tcp-222-forwarded` to 22 from anywhere —
the node's `expose` for that port carried across the move — and nothing is opened at the number it
left. Git over ssh answers from outside again, on the port the forge's own configuration has
advertised in every clone URL all along.
Checked from the other side as well, and it matters: `SSH_PORT` in the forge's configuration says
222. While the port could not be moved, the mesh published 2222 and the forge told everyone 222.
A service can be wrong about itself without anything failing.