--- status: resolved opened: 2026-10-01 located-in: [mesh-controller examples/route-proxy/main.go (routesFrom requires a route's public `name` and treats `internal-name` only as an alias of it; the handler serves every routed name to any source), mesh-controller internal/broker/membership.go (a membership says nothing of what its module receives or who the mesh is)] fixed-by: mesh-controller PR 207 (the membership carries what a module receives and who the mesh is; the proxy follows it and serves internal names to the mesh only), mesh-catalog PR 211 (the proxy's bus account), mesh-controller PR 208 (the issue verb that delivers it), live 2026-10-02 amended-design: [03-DESIGN/01-to-be/08-connectivity.md, 03-DESIGN/01-to-be/25-the-bus-on-nats.md] --- # 191 — A route with only an internal name is dropped as naming nothing ## What was observed A module whose endpoint reaches only the private network could not be reached by its internal name. The module ran and answered on its own port. Its route's internal name resolved to the serving node. The request failed during the TLS handshake: ``` http: TLS handshake error from …: no public route for "unifi.home-server.internal" in this mesh, so no certificate is asked for ``` The proxy's own log said why, every time it re-read its routes: ``` unifi on home-server asked for a route and named nothing; skipped ``` The route it skipped was not empty. The mesh had given it an endpoint, a port, a scheme and an internal name, and no public name: | route | `name` | `internal-name` | served | |---|---|---|---| | home-assistant | a public name | `home-assistant.home-server.internal` | under both | | unifi | — | `unifi.home-server.internal` | under neither | Three other modules on the same node were skipped with the same line on the same pass. ## Why it matters **Reach is decided in one place, and the proxy reads the old shape of the decision.** [ADR 0138](../../02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md) made an endpoint's reach decide which names exist. The controller composes the public name, the internal name, or both, and composes no name that nobody asked for ([issue 140](../140-an-endpoints-reach-is-not-declared/01-resolution.md)). An endpoint that reaches only the private network is the ordinary case for anything that should not face the internet. It is exactly the case the proxy drops. **The failure is quiet and points the wrong way.** Nothing marks the module unhealthy. The handshake error says *no public route*, which reads as a certificate fault on the reader's side. The line that gives the real cause is one of four identical lines repeated every few seconds in a log nobody reads until they already suspect the proxy. **The opposite move is not a workaround.** Giving the endpoint public reach makes the proxy serve it. It also publishes an administration interface to the internet to get a name on the private network. ## Open questions - Should the proxy refuse a route it cannot serve in a way the controller or an operator sees, not only in its own log? The same silent skip covers a route with no usable port or an unknown scheme. - What checks that what the controller composes and what the proxy serves stay the same shape? ADR 0138 changed one side and nothing failed on the other. ## Resolved (2026-10-02) Built as [ADR 0167](../../02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md) decided, and live on both machines that run the proxy. Each logs that its routes now come from its membership, and serves internal names to the four machines the mesh names. Checked by hand: - the internal-only route answers through the proxy from the serving machine and from two other machines of the mesh, over a certificate from the mesh's own authority that each verifies; - the same name asked from an address outside the mesh is answered as a name never routed, over plain HTTP, and refused in the TLS handshake; the list of served names it is shown leaves out every internal name. Two things the rollout found are their own records: the proxy's bus account could be issued only from the controller's command line, until mesh-controller PR 208 added the `issue` verb, and the status line counting every module as a bus user without a credential is [issue 195](../195-every-assigned-module-is-counted-as-a-bus-user-without-a-credential/00-report.md). The serving machine also lacked the certificate-trust module, so it could not verify the mesh's own certificates until it was assigned there.