diff --git a/02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md b/02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md new file mode 100644 index 0000000..0916793 --- /dev/null +++ b/02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md @@ -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::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:}` 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:}`**, 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 ` 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": "/"}` +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 diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 97d1482..737d7cc 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -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 diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index faaf840..59a3019 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -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 | ✅ | diff --git a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md index f557ed6..8ac3cde 100644 --- a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md +++ b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md @@ -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:}` 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::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:}`](../../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 diff --git a/04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md b/04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md index 9986fcc..2e7eec4 100644 --- a/04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md +++ b/04-ISSUES/122-a-module-cannot-ask-for-its-own-public-name/00-report.md @@ -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-` 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:}` 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)). diff --git a/04-ISSUES/134-a-definition-may-still-name-the-mesh/00-report.md b/04-ISSUES/134-a-definition-may-still-name-the-mesh/00-report.md index f5471d2..11138b4 100644 --- a/04-ISSUES/134-a-definition-may-still-name-the-mesh/00-report.md +++ b/04-ISSUES/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.