--- topic: the tiers status: accepted date: 2026-09-25 deciders: jochen reconstructed: false extends: 02-DECISIONS/0007-connectivity.md --- # 108. A route carries the policy applied to a request, and names a secret rather than holding one ## Context [ADR 0007](0007-connectivity.md) settled that **a route is a grant**: a module contributes the name it wants and the port it listens on, and the proxy hands back the public name. That governs the **grant**. It says nothing about what a request arriving at the name is permitted to do, and the mesh's proxy currently permits everything: its request path is a host lookup and a forward, with no authentication, no source check, no redirect handling and no middleware anywhere in it. The standing requirement is that the mesh does **at minimum** what the system it replaces already does. Measured against the predecessor's live configuration and its module catalogue ([issue 116](../04-ISSUES/116-route-proxy-has-no-auth-or-ip-restriction/00-report.md)), four capabilities are relied on and absent: | Capability | Dependents, counted | |---|---| | Authentication | **three** modules, each gating a credential-less admin surface — a key-value browser UI, a **database web UI**, and the replaced ingress's own dashboard | | Refusal scoped to a path | **one**, and it is a live **incident mitigation** closing a write primitive that was abused | | Path-scoped routing with priority | **two** — the refusal above, and a certificate-challenge path on a host that otherwise routes elsewhere | | Redirect | **two** live routes canonicalising a `www` name onto its apex | Counting needed two sources and neither alone is complete: the catalogue cannot see what was hand-written on a node, and a node's configuration directory cannot see what modules declare as container labels. An earlier count read one source and undercounted authentication by two. **The third row is a prerequisite, not a sibling.** The proxy's table maps a host to exactly one target, so a host cannot be routed two ways. Adding authentication and a source filter would not make the refusal rule expressible. ## Considered Options 1. **Leave policy out of the mesh; keep the affected routes on the adopted ingress.** *Rejected.* The mesh would run two reverse proxies indefinitely with no principled division between them, and one of the routes held back is a live mitigation — leaving it on a component being decommissioned means its removal date is whenever somebody forgets. 2. **A separate, proxy-side settings layer keyed by route name.** *Rejected.* It keeps the grant literally clean, but answering *"what protects this route"* then requires reading two files that nothing keeps in step. A route's protection is part of what a route is. 3. **The grant carries a reference; the detail lives in a second layer.** *Rejected.* Both costs of option 2, plus a naming indirection to maintain. 4. **Carry the password hash in the declaration.** *Rejected.* It would be the first credential value in a declaration, and a precedent is easier to set than to withdraw. A hash is not a plaintext password, but it is sufficient to pass the gate it protects. 5. **An open middleware surface the proxy applies.** *Rejected.* It recreates the thing being replaced, makes the proxy's behaviour unbounded, and an open surface is far harder to narrow later than a closed one is to widen. ## Decision **A route contribution carries the policy applied to requests arriving at its name**, alongside the name, the port and the location it already carries. **The set is closed, and it is these four:** authentication; refusal scoped to a path; path-scoped routing with priority; redirect. A fifth is an amendment to this record, deliberately — each addition should be earned by a dependent that exists. **Where policy needs a credential, the declaration names a secret the mesh mints and holds. It never carries the value.** This keeps the existing secret machinery as the only thing that holds credentials, and keeps hashes out of anything regenerated, synced or committed. **Consequently the routing table is keyed by host and path, with priority** — not by host alone. This follows from the decision rather than being a separate one: two of the four capabilities need a single host routed more than one way. ## Consequences **What this makes possible.** The affected routes can leave the adopted ingress, and "at minimum" becomes a satisfiable claim rather than a standing exception. The incident mitigation gets a durable home in the mesh, which its own note already asked for. **What got harder.** The contribution shape grows, and every provider of `route` must understand more of it. The table is no longer a flat map, and priority introduces ordering that has to be deterministic rather than incidental — equal priorities must resolve the same way every time or the proxy becomes non-reproducible. A closed set means a new need is a decision, not a patch. **What does not change.** The proxy remains a reference implementation: the contract is the file the mesh writes, not the program that reads it. Another proxy may implement the same file. **How this is checked.** A rule states how it is verified, so this one does. A lab bed must show, against a mesh that declared them: a route with authentication refusing an unauthenticated request and admitting an authenticated one; a path-scoped refusal shadowing an ordinary route on the same host while that ordinary route still serves every other path; a redirect answering with the redirect; and — the negative case, which is the one that rots quietly — **a declaration carrying a credential value rather than a reference being refused**, so option 4 cannot return by accident. ## References - [Issue 116](../04-ISSUES/116-route-proxy-has-no-auth-or-ip-restriction/00-report.md) — the count, the evidence, and the structural finding about the table. - [ADR 0007](0007-connectivity.md) — a route is a grant. This record extends it to the request. - [ADR 0009](0009-modules-and-the-graph.md) — the provision vocabulary a contribution belongs to. - [To-be 08 §3](../03-DESIGN/01-to-be/08-connectivity.md) — where exposure is specified.