Files
hq/04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md
T
jochen f709e8e0fb Issues 119 and 122: what a host path is, and what a node's layout would replace
Not every host path names this machine. A system file the mesh owns is at that path on every machine
of the kind — the path is the fact. The operator's shared data is already answered as an access. A
path inside a container is the software's contract. What is left, and what a node's default layout
would replace, is 514: a module's own data, and what the mesh writes for that module.

Documents the reservation model as the records already have it — a root per node, one directory per
assignment, a placement for adopted data — and the three things nothing states: where the root comes
from, what sits beneath it, and the order the 514 are retired in.

Stops there deliberately. Changing where a definition looks without moving the data does not fail: the
mesh creates the directory, the container starts, the service comes up empty. Retiring these is a data
migration with a verification step, module by module, and belongs with whoever can see the machine.
2026-09-26 17:21:14 +02:00

7.1 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
located 2026-09-26
mesh-controller internal/catalogue/declaration.go
mesh-catalog modules/keycloak
mesh-catalog modules/minio
mesh-catalog modules/nextcloud

122 — A module cannot ask for its own public name, so three manifests wrote this mesh's names into the catalogue

What was observed

Reviewing the open module changes on 2026-09-26, three of them independently put a name belonging to one particular mesh into a module definition, each for a different piece of software and each as the only way to make the software work:

  • The identity provider's manifest gains KC_HOSTNAME: https://<label>.<public-domain> as a literal, because the software generates absolute URLs and, behind a proxy, cannot derive them.
  • The object store's manifest gains MINIO_BROWSER_REDIRECT_URL: https://<label>.<public-domain> as a literal, for the same reason — its console redirects to an absolute URL.
  • The file-sync module's S3 bucket is renamed from nextcloud to a name carrying this mesh's own name, so the module matches a bucket that already exists here.

No reviewer introduced these carelessly: each is the value the software needs, and there is nowhere else to put it.

And they are not the first. Asked whether merging them would set a precedent, the catalogue answered no: five modules already on the main branch carry one. The clearest is the workflow automation module, whose environment file states its host, its protocol and a full absolute webhook URL as literals; two more — the proxy and the builder — carry a complete clone URL for a repository on this mesh's own forge, scheme, host and port included.

Counted properly, 2026-09-26. The five were what a first look found. A sweep of all 71 definitions, for values that could only be true of one installation:

What is named Occurrences Modules
A public domain, or a name under one 49 26
The node's own name 58 19
A routable IP address 12 1
A host path for a module's own files — the node's to decide 514 70
An email address 0 0

The path row is issue 119, counted again — and counted differently, which is the correction worth keeping. A path inside a container is not a fact about the machine. /run/secrets/… and the directory a server keeps its data in are the software's own contract, true in any mesh that runs it; only the host side of a mount names where it landed. The first sweep matched path-shaped strings and so counted both halves of every mount and every in-container location a value mentioned. Counted by role instead — directory and file resources, the host side of mounts, accesses, and the targets of binds, grants, receives and secrets — it is 698 across 70 definitions — of which 514 are the category at issue. The rest are a system file the mesh owns (/etc/…, where the path is the fact and is the same on every machine) and the operator's shared data, which ADR 0051 already answers as an access. Issue 119 carries that breakdown and what a node's default layout would replace. Issue 119's own figure is role-counted already and close to this; it additionally counts paths written into environment values, a few of which are container-side.

The rest of the table is this issue. Thirty of the seventy-one definitions name this installation in one of the first three ways, and the worst single case is not a domain at all: a mail module states the node's own public IPv4 as the address it trusts a real-IP header from, so a node that moves, or gains a second address, silently stops attributing mail to the right sender. A private subnet, the mail domain, the site name and the administrator's domain sit in the same block of values.

Two upstream resolvers are excluded from the count deliberately: naming a public DNS service is a policy default, true of any mesh that wants it, not a fact about this one.

So the three under review are the visible edge of a pattern the catalogue already follows, which changes what this issue is for. It is not a matter of refusing three changes; there is no version of this catalogue today that does not name the mesh it was written in, and a mechanism is the only thing that removes any of them.

What the mesh offers instead, and why none of it answers

A manifest may interpolate ${secret:…}, ${seat:…}, ${bound:…}, ${port:…} and ${machine:…}. The machine form resolves at, name and address — the machine's identity on the private network. None of them yields a public name.

The mesh does compose public names: a route contribution's label is joined to the node's public domain, <label>.<public-domain>, and the mesh interprets neither half. That happens inside the controller when a declaration is built, and the result reaches the proxy that serves the route. It does not reach the module that asked for the route, so a module that must tell its own software what it will be reached at cannot read what the mesh already worked out.

So the workaround is the only expressible option: write the answer down, in the definition, for the one mesh it is true of.

Why it matters beyond these three

A module definition is meant to hold what is true of the module everywhere, with everything particular to an installation resolved at assignment — the argument ADR 0112 (proposed) makes in full, and what issue 119 found for paths. A hostname is the same kind of fact as a path, and arriving by the same route: not because anyone disagreed with the principle, but because the mechanism that would honour it does not exist yet.

The cost is concrete. A second mesh installing the identity provider from this catalogue gets the first mesh's hostname, and its login flow breaks in a way that looks like a proxy fault. Nothing checks for it: a literal hostname in an env value is a well-formed string, and no rule distinguishes it from a version number.

The bucket case is the same shape with a different subject — an adopted resource's real name is also particular to one installation, and also has nowhere to live but the definition.

Open questions

  • Should a module be able to name what it will be reached at — a route-scoped interpolation resolving to the composed public name of a route it contributes, so the answer the mesh already computes is the one the software is told? That is the smallest fix and it stays within the existing vocabulary.
  • Does the scheme belong to it? Every instance here wrote https://, which is true of a route the proxy terminates and not of the module's own port.
  • For an adopted resource such as an existing bucket: is that a setting on the assignment (where ADR 0112 would put it), and if so what reads it — the provisioner, or the module's own values?
  • What check would notice the next one? A definition naming a public domain is detectable in the shape of the value, which is more than nothing, and less than a rule.