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.
81 lines
5.2 KiB
Markdown
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.
|