Both carried a topic outside the six the index knows, so neither had a place to be read in — 0067 had none at all. Both are 'the tiers', beside 0036 (bootstrap ends at a usable mesh) and 0007 (connectivity), which is what they extend. 0067 cited 0041 for tier 0's property; on this trunk 0041 is events, and the record it meant is 0005. A citation that resolves to the wrong record reads as corroboration, which is worse than a dead link. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
7.2 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| the tiers | proposed | 2026-09-09 | jochen | false | 0007-connectivity.md |
66. Public routing is name-agnostic, its names are resolved inside the mesh, and an internal authority can certify them
Context
ADR 0007 and connectivity §3 made a public route a grant: a workload that must be reachable requires a route, the proxy provides it, the consumer contributes the name it wants and the port it listens on. What was never pinned is what that name is — and building a whole mesh in the lab showed the gap costs more than it looks.
The catalogue shipped each route as a full domain. A module that needed a public name carried
that name, in full, as a literal in its manifest. Running the same catalogue against a different
domain — a lab standing in for production, or a second operator's mesh — meant overriding that
literal on every routed module, per node. The mesh was, in effect, carrying a map of names to
services: the one thing it should never hold, because a name is the operator's choice (one runs
the forge at git, another at code) and the domain is the node's, and neither is the mesh's to
know.
And a second gap surfaced the moment an internal issuer tried to certify those names.
Connectivity §5 already states the issuer must be
configurable and that the lab runs its own ACME authority. With that authority wired to the proxy,
issuance still could not complete: the authority accepted the order and offered a challenge, then
could not connect to the validation target. Nothing inside the mesh resolved the public route
name. The mesh publishes each <node>.internal name into every container, but not the public names
the proxy serves — so a validator living in the mesh had no address to reach, and a name the mesh
cannot resolve is a name it cannot have certified.
The two are one problem. A name the mesh can compose from parts it is given, and propagate to whoever needs to resolve it, is exactly a name it can also have certified — and the reverse: without the composition and the propagation, neither the routing nor the certificate is the operator's to move between meshes.
Considered Options
1. Keep the full domain in the manifest, override per node. The status quo. It works, and it is wrong in the specific way this repository cares about: the catalogue holds a domain map, lab and production differ by an override on every routed module rather than one fact, and a module manifest names something — the public domain — that belongs to the node, not the module. An unowned name in the wrong place is the shape of a leak.
2. A module declares a label; the node declares its public domain; the mesh composes. The route
contribution carries a subdomain the operator chose, the node carries its public domain as
node-level configuration, and the mesh joins <label>.<public-domain> and grants exactly that. The
mesh interprets nothing. Lab-versus-production becomes one node setting. Chosen.
3. For certification, issue only from a publicly reachable node against a public authority. This is already true for public meshes and stays true. It is not an option for a lab or an internal-only mesh: there is no public authority to answer, and no public reachability to validate against. An internal issuer is required there — and an internal issuer must be able to validate, which it cannot do unless the routed name resolves and is reachable inside the mesh. So the resolution gap is not optional to close; it is what makes an internal authority possible at all.
Decision
The mesh core holds no map of hostnames, subdomains or domains. A route contribution carries a
label (the subdomain) chosen by the module's operator. A node contributes its public domain
as node-level configuration. The mesh composes <label>.<public-domain>, grants exactly that name,
and never interprets what it means. One operator's forge at git.example.tld and another's at
code.other.example are the same module with two facts supplied around it.
When the proxy is granted a name, the mesh publishes that name → the node that serves it into
internal resolution, mesh-wide — the same mechanism, and the same "given by the mesh, not chosen
by a module," that already writes <node>.internal into every declared container
(connectivity §2). The mesh propagates the names it was
told to serve. It still knows nothing about what any of them mean.
An internal authority certifies those names by the same path a public one would. The proxy is pointed at whichever issuer the mesh names — a public ACME authority, or an internal one — and trusts that issuer's root; nothing else about issuance changes. The internal authority validates by reaching the routed name, which the clause above has just made resolvable inside the mesh. So the three are one decision: compose the name, propagate it, certify it — each is meaningless without the one before it.
Consequences
- Lab-versus-production is one node-level
public-domainsetting, not an override on every routed module. The same catalogue runs against any domain. - The manifest layer needs composition it does not yet have. Today a route name is stored as a literal, with no interpolation of a node's domain into a module's label. Until that exists, the composed name is produced by a per-node settings override — a stopgap that reproduces option 1's per-module cost and is explicitly not the design.
- An internal issuer depends on route-name resolution. Its challenge validates against the routed name; without that name in internal resolution, issuance for it cannot complete inside the mesh. The lab found this as a live failure, not a theory.
- Nothing about the public path changes. A publicly reachable node issuing a public name from a public authority is untouched; this widens the same shape to names and meshes that are not public.
How each is checked — an unenforced rule is indistinguishable from a wrong one:
- Name-agnostic: the same catalogue resolves against two different public domains by changing one node setting and nothing else; and no module manifest contains a full public domain. A manifest that pins an FQDN is the smell the check looks for.
- Resolution: a request to a routed name, made from inside the mesh, reaches the workload that serves it — and, the sharper check, issuance for that name against the internal authority completes, which it cannot unless the validator resolved and reached the target.
- Internal authority: a TLS handshake to a routed name verifies against the internal root and nothing else, the same shape §5 already uses for internal node-to-node names.
References
- ADR 0007 — connectivity, which made a route a grant and named exposure, resolution and certificates as one context.
- ADR 0009 — modules and the graph, the provide/require/contribute vocabulary a route and an authority both use.
- Connectivity design §2, §3, §5, amended alongside this record.