141 lines
9.2 KiB
Markdown
141 lines
9.2 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-26
|
|
located-in: [mesh-controller internal/catalogue/declaration.go, mesh-catalog modules/keycloak, mesh-catalog modules/minio, mesh-catalog modules/nextcloud]
|
|
fixed-by: mesh-controller PR 149 (a module is told the name it is served under), PR 169 (an operator's value, a context on the seat); mesh-catalog PR 188; ADR 0155
|
|
amended-design: 03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md
|
|
---
|
|
|
|
# 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](../119-a-module-definition-decides-where-its-files-live/00-report.md),
|
|
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](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md) (proposed)
|
|
makes in full, and what [issue 119](../119-a-module-definition-decides-where-its-files-live/00-report.md)
|
|
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.
|
|
|
|
## Resolved, 2026-09-30
|
|
|
|
The open questions, answered in order. **A module names what it will be reached at** through the
|
|
binding of the route it contributes: `${bound:route:name}`, or `:name-<local>` for several
|
|
contributions, is the composed public name; `:internal-name` the private one (controller PR 149). The
|
|
identity provider, the object store's console and the automation tool's webhook now read it there.
|
|
**The scheme is the module's**, because it is true of the route the proxy terminates and every instance
|
|
wrote `https://` in front of the name. **An adopted resource's name is a setting** on the assignment;
|
|
so is every other operator's value — a mail domain, a site name, the address a proxy forwards from —
|
|
as `${setting:<key>}` in the file the software reads
|
|
([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)).
|
|
**The check that notices the next one** is the shape of the value, as this record guessed it would be,
|
|
and it is more than nothing: it found forty-two, and the catalogue passes it now
|
|
([issue 134](../134-a-definition-may-still-name-the-mesh/00-report.md)).
|
|
|
|
**What the rollout cost, 2026-09-30 evening.** The site module's rename from its domain to `website`
|
|
was a new module to the mesh, and two things the old assignment carried by name were lost: the
|
|
container still named the old network, and a port setting on the old assignment had hidden that
|
|
`listens` said one port while the container published another. The site answered 502 for about
|
|
twenty minutes across two one-line fixes (mesh-catalog PRs 190, 191). A module's rename is an
|
|
unassign and an assign, and everything the assignment held — settings, ports, its directory — is the
|
|
new module's to get again; the mesh says nothing about that today. The mail module's settings turned
|
|
out to reach every fact it contributes, which is [issue 173](../173-a-modules-settings-reach-every-fact-it-contributes/00-report.md).
|