Renumber the routing record to 0066 — 0056 was already taken
0056 is 'the authority is the control plane, not a database', drafted on the in-progress record chain this branch was cut from before those three records landed. Two files would have collided at merge, which is the kind of thing that is cheap now and confusing later. The code written against it still says 0056 and is corrected separately. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
---
|
||||
topic: routing and names
|
||||
status: proposed
|
||||
date: 2026-09-09
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 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](0007-connectivity.md) and [connectivity §3](../03-DESIGN/01-to-be/08-connectivity.md)
|
||||
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](../03-DESIGN/01-to-be/08-connectivity.md) 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](../03-DESIGN/01-to-be/08-connectivity.md)). 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-domain` setting**, 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](0007-connectivity.md), which made a route a grant and named exposure,
|
||||
resolution and certificates as one context.
|
||||
- [ADR 0009 — modules and the graph](0009-modules-and-the-graph.md), the provide/require/contribute
|
||||
vocabulary a route and an authority both use.
|
||||
- [Connectivity design §2, §3, §5](../03-DESIGN/01-to-be/08-connectivity.md), amended alongside this
|
||||
record.
|
||||
Reference in New Issue
Block a user