Merge pull request 'ADR 0155: a definition names no installation, how that is checked, and the three ways out (group 4, step 1)' (#226) from feat/a-definition-names-no-installation into main
Reviewed-on: #226
This commit was merged in pull request #226.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-09-30
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
---
|
||||
|
||||
# 155. A definition names no installation: how that is checked, and the three ways a value that did gets out
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) decided that a module definition
|
||||
names no node, no mesh and no host path, and said how the name half is checked: *a catalogue test
|
||||
finds no domain name in any definition value*. No such test existed
|
||||
([issue 134](../04-ISSUES/134-a-definition-may-still-name-the-mesh/00-report.md)). Written and run
|
||||
over the 77 definitions on 2026-09-30, the check it describes finds **42 values**, in 15 definitions,
|
||||
and they are of four kinds that want four different answers:
|
||||
|
||||
| kind | count | example |
|
||||
|---|---|---|
|
||||
| a service told its own public name as a literal | 5 | an identity provider's `KC_HOSTNAME`, an object store's console redirect, an automation tool's webhook URL |
|
||||
| an operator's value written into the definition | 10 | a mail server's domain, site name, website, and the address it trusts a real-IP header from |
|
||||
| this mesh's forge, by URL, as a recipe's build context | 2 | the builder and the proxy, which package the controller's source |
|
||||
| an application built outside the mesh, pulled from this mesh's registry | 7 | four sites and tools whose repositories are the operator's own |
|
||||
| the world's servers, named by upstream defaults | 10 | a Matrix homeserver's trusted key server, Element's integration manager |
|
||||
| a module named after the domain it serves | 8 | one site module, with its paths and network named after it |
|
||||
|
||||
Not one was careless. Each was the value the software needs, and until today there was nowhere else
|
||||
to put it ([issue 122](../04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md)).
|
||||
Two of the answers were built before this record: a module is told the name its route composes
|
||||
(`${bound:<route>:name}`, controller PR 149, 2026-09-30), and a source may be a path on the git seat
|
||||
([ADR 0111](0111-a-build-source-is-on-the-git-seat-or-external.md)). What was missing: an operator's
|
||||
value in a file the software reads, the same for a build *context*, a way to say a name is meant, and
|
||||
the check.
|
||||
|
||||
## Considered Options
|
||||
|
||||
**1. A string search for the installation's own names.** Rejected. The controller is as
|
||||
mesh-agnostic as the definitions; it does not know which names are "this mesh's", and a check that had
|
||||
to be told would be configured per installation and pass everywhere else. What it can know is the
|
||||
*shape*: a name under a public top-level domain, a public address.
|
||||
|
||||
**2. Report every such shape.** Rejected. Eight of the 42 were `why` strings — prose the mesh never
|
||||
reads, explaining what a port is for — and a check that reports those beside `KC_HOSTNAME` teaches
|
||||
people to ignore the report. And a Matrix homeserver *must* name the federation's public key server;
|
||||
a check with no way to say so would be a check people argue with rather than obey.
|
||||
|
||||
**3. Judge what the mesh acts on; let a definition say which names it means, one by one, with a
|
||||
reason; exempt the world's services that a definition may name as a policy default.** Chosen.
|
||||
|
||||
**For an operator's value**, one option was to wait for design 27's requirement form in full. Rejected
|
||||
for the reason 0112 gave against a slow operator provider: if asking a person for a value takes more
|
||||
than a setting, module authors route around it and the literals come back. `${setting:<key>}` is the
|
||||
operator provider in its first form, on the settings a module already has.
|
||||
|
||||
## Decision
|
||||
|
||||
**The check.** Every string value of a definition that the mesh acts on is judged for a hostname under
|
||||
a public top-level domain and for a public address. Not judged: `why` and `description`, which are
|
||||
prose. Allowed where they can only mean the world: the public registries an `image` may be pulled
|
||||
from, the public resolvers a machine may forward to, and the public certificate authorities' ACME
|
||||
directories. The container runtime's alias for its own host is the runtime's. A module's own name is a
|
||||
value too. The check runs in `module check` and as a catalogue-wide test; **it does not yet refuse at
|
||||
registration**, because the list it prints is the list that shrinks, and a registration that refused a
|
||||
manifest whose only remedy is a merge elsewhere would refuse the mesh's own catalogue on the day the
|
||||
check landed. It moves to registration when the list has been empty for a release.
|
||||
|
||||
**A name a definition means is declared with its reason.** `names-on-purpose` on a resource maps each
|
||||
such name to why: *the federation's public key server, the world's*; *built outside the mesh, from the
|
||||
application's own repository, until that repository is a build source on the git seat*. A name the map
|
||||
does not cover is still reported. The host never sees the word.
|
||||
|
||||
**An operator's value reaches a file as `${setting:<key>}`**, filled from the module's settings
|
||||
layers — the mesh's, then the node's — the same layers a mergeable file and a contribution take, so
|
||||
`settings set <module>` stays the one place a person's values go. Refused, naming the key and the
|
||||
command, when nothing set it: a default for a mail domain would be the literal this removes, and a
|
||||
blank written silently would be a service that comes up wrong somewhere that names nothing.
|
||||
|
||||
**A build context may live on the git seat.** `context: {"seat": "git", "repository": "<owner>/<name>"}`
|
||||
is composed by the mesh that builds it: the request carries each seat's clone base, and a builder told
|
||||
no base for a seat a context names refuses the build by the seat's name rather than guessing a forge.
|
||||
|
||||
**A module is named for what it is.** The site module named after its domain is `website`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The catalogue names no installation, and a test says so.** The forty-two became zero the same day,
|
||||
by the four answers above; seven of them are declared on purpose and stay visible as the list to
|
||||
shrink — four applications the mesh does not build yet.
|
||||
- **An operator's values are the assignment's.** The mail module takes its domain, its site name, its
|
||||
website and the address it trusts a real-IP header from as settings; a mesh that installs it without
|
||||
them is refused at composition, by name, which is the right moment. The module's own README says
|
||||
which.
|
||||
- **What got harder:** a manifest reviewer has one more word to read, and `names-on-purpose` on an
|
||||
application's image is a debt visible in the definition until the application is built here. A
|
||||
reader of `settings set` output sees more keys than files, because a key a file asks for is a
|
||||
destination too.
|
||||
- **Not decided here:** ADR 0112's requirement form (design 27) still replaces `${setting:…}` and the
|
||||
other placeholders when it lands; this is its first case, the way [ADR 0038](0038-the-mesh-assigns-the-port.md)
|
||||
was for ports. Host paths ([issue 119](../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md))
|
||||
are the next step of the same group, and the registry's name ([issue 123](../04-ISSUES/123-the-image-registry-is-named-after-a-role/00-report.md))
|
||||
the one after.
|
||||
|
||||
## How this is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A value naming an installation is reported at its path, in the definition's words | a catalogue unit test over a definition with a hostname in an env value and a public address in a file |
|
||||
| Prose, the world's registries in an image, public resolvers, ACME directories and the runtime's own alias are not reported | the same tests |
|
||||
| A name declared on purpose is not reported; a name beside it that is not declared is | a test with a homeserver's config |
|
||||
| An image from an installation's registry needs a reason | a test without and with the word |
|
||||
| A module named after a domain is reported | a test |
|
||||
| No definition in the catalogue names an installation | `TestNoCatalogueManifestNamesAnInstallation` over the checkout, and `module check modules/` |
|
||||
| `${setting:key}` fills from the layers, node over mesh; refused by name when unset; not stray when set | three tests |
|
||||
| A context on a seat is cloned from the base the mesh sent; a seat with no base is refused by name | the builder's test |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) — extended: the check it promised, and the operator provider's first form
|
||||
- [ADR 0111](0111-a-build-source-is-on-the-git-seat-or-external.md) — a source on the seat; now a context too
|
||||
- [issue 122](../04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md), [issue 134](../04-ISSUES/134-a-definition-may-still-name-the-mesh/00-report.md) — what this closes
|
||||
- [27 — A module requires, the mesh resolves](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md) — where this sits in the larger design
|
||||
- mesh-controller PR 169, mesh-catalog PR 188 — the check, the words, and the catalogue that passes it
|
||||
@@ -258,6 +258,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0147** — [A module anchors the mesh's authority on a machine, and takes it away again](0147-a-module-anchors-the-meshs-authority.md)
|
||||
- **0150** — [A module's own code runs as supervised processes under the module's one account](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md)
|
||||
- **0152** — [The operator's surface is a module the mesh assigns: the console](0152-the-operators-surface-is-a-module-the-console.md)
|
||||
- **0155** — [A definition names no installation: how that is checked, and the three ways a value that did gets out](0155-a-definition-names-no-installation-and-how-that-is-checked.md)
|
||||
|
||||
### How it is built
|
||||
|
||||
|
||||
@@ -186,7 +186,8 @@ disagrees with it.
|
||||
| `grants` | credentials it must create for its consumers |
|
||||
| `filtering` | rules beyond its own ports |
|
||||
| `computed` | marks a module the controller generates rather than an author writing |
|
||||
| `build.artifacts` | what it produces |
|
||||
| `build.artifacts` | what it produces; an artifact's `context` may be a URL or a path on the git seat (`seat: git`), composed by the mesh that builds it |
|
||||
| `names-on-purpose` | on a resource: each name it means to name — the world's federation server, a registry that built an application the mesh does not — with its reason. A definition names no installation, and a test says so ([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)) |
|
||||
|
||||
**A container mounts only what the manifest declares**
|
||||
([ADR 0091](../../02-DECISIONS/0091-a-mount-is-declared-three-ways.md)). A bind mount the module
|
||||
@@ -234,7 +235,7 @@ counts as a copy and what as a base.
|
||||
| resource | is | a module may |
|
||||
|---|---|---|
|
||||
| `directory` | a directory with a mode and an owner | ✅ |
|
||||
| `file` | literal content, with `${bound:…}` and `${secret:…}` filled in | ✅ |
|
||||
| `file` | literal content, with `${bound:…}`, `${secret:…}`, `${dir:…}`, `${port:…}`, `${machine:…}` and `${setting:…}` filled in — the last an operator's value from the assignment's settings, refused by name when unset ([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)) | ✅ |
|
||||
| `user` | a login | ✅ |
|
||||
| `access` | a pre-existing path it may use and must not own | ✅ |
|
||||
| `archive` | files fetched by digest and unpacked | ✅ |
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
code: []
|
||||
updated: 2026-09-26
|
||||
status: in-progress
|
||||
code: [mesh-controller internal/catalogue]
|
||||
updated: 2026-09-30
|
||||
decisions:
|
||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
- 02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md
|
||||
- 02-DECISIONS/0115-one-assignment-of-a-module-per-node.md
|
||||
- 02-DECISIONS/0113-the-vault-makes-every-secret.md
|
||||
- 02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md
|
||||
@@ -171,6 +172,15 @@ make a new external key, so rotating one means an operator handing over a new va
|
||||
assignment, and the route provider answers. A public name already held by another assignment is
|
||||
refused, like any other singular thing.
|
||||
|
||||
**Built so far, 2026-09-30** ([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)):
|
||||
the operator's value in its first form — `${setting:<key>}` in a file's content, from the assignment's
|
||||
settings layers, refused by name when nothing set it; a module told the name its route composes
|
||||
(`${bound:<route>:name}`); a build context on the git seat; and the check that no definition names an
|
||||
installation, with `names-on-purpose` for the names a definition means. The host's directory in its
|
||||
first form is [`${dir:<id>}`](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md),
|
||||
a placed directory under the node's root. Each is this design's provider in the shape the existing
|
||||
placeholders have, not yet the one requirement form below; they are phase 1's first cases.
|
||||
|
||||
## How a definition reads what was resolved
|
||||
|
||||
**One form, naming a requirement and a field of its contract.** A definition that needs the database's
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
status: located
|
||||
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:
|
||||
amended-design:
|
||||
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
|
||||
@@ -114,3 +114,18 @@ particular to one installation, and also has nowhere to live but the definition.
|
||||
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)).
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
status: open
|
||||
status: resolved
|
||||
opened: 2026-09-28
|
||||
located-in: [mesh-catalog, mesh-controller internal/catalogue]
|
||||
fixed-by:
|
||||
amended-design:
|
||||
fixed-by: mesh-controller PR 169 (the check, module check, the catalogue-wide test); mesh-catalog PR 188 (the catalogue that passes it); ADR 0155
|
||||
amended-design: 03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md
|
||||
---
|
||||
|
||||
# 134 — A definition may still name the mesh, and the check that would say so does not exist
|
||||
@@ -64,3 +64,17 @@ that.
|
||||
hostnames above are the first real cases.
|
||||
- Should a build context name a repository on the git seat rather than by URL, and if so, what does
|
||||
that mean for a context in *another* mesh's forge?
|
||||
|
||||
## Resolved, 2026-09-30
|
||||
|
||||
The check exists: `InstallationProblems`, run by `module check` and by a catalogue-wide test. Run over
|
||||
the 77 definitions it found 42 values, not 15 — the by-hand count had missed a second name one
|
||||
character after the first on the same line, which is the kind of thing a check is for. The three open
|
||||
questions: **a domain in a `why` string does not break the rule**, prose is not judged, and the eight
|
||||
were rewritten anyway because this catalogue is public; **a service's public name is the name the mesh
|
||||
composes for its route**, read through the route's binding, and an operator's own value is a setting;
|
||||
**a build context names a repository on the git seat**, `seat: git` with the path, and a context in
|
||||
another mesh's forge stays a URL, which the check reports and `names-on-purpose` would declare. The
|
||||
seven values that remain are declared with their reason — four applications built outside the mesh —
|
||||
and are the list that shrinks ([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)).
|
||||
Registration does not refuse yet; it will when the list has been empty for a release.
|
||||
|
||||
Reference in New Issue
Block a user