Files
hq/02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
T
jochen fa2c09a2c5 ADR 0113 and to-be 27: the vault makes every secret — provisioning all the way down
A secret comes into being seven ways today: provider credentials, own secrets (54 modules), broker
accounts through a command that is easy to forget, a vault that only records what the controller
mints (6 modules), operator values, licences, and root secrets. The vault was built to end own secrets
and did not; the old path was never retired.

0113 is rewritten as a waterfall. The vault makes every secret and nothing else does. A provider that
needs a secret for a consumer requires it from the vault, declared once in its provision's contract
and expanded per consumer by resolution; the vault delivers it to both holders, each sealed to its own
node, so a provider's code is unchanged. Own secrets, broker passwords, operator values and licence
credentials take the same path. Genesis is not an exception: it raises the vault first and asks it,
so there is one way a secret is made from the first one on. The vault can sit at the bottom because it
requires nothing but a broker account.

One shared mint function in the SDK was considered and rejected: generation becomes uniform but custody
stays spread over every provider's machine, and each SDK language needs its own implementation.

Rotation is asked of the vault and is provider-first: the value goes to the holder that accepts it,
which confirms, before the holder that presents it gets it, so the lockout window shrinks to the
consumer's own restart, and an unconfirmed provider holds the rotation rather than half-doing it. The
host derives which processes to restart or recreate from the requirement a definition reads, so no
definition declares restart-on for a secret. A rotation shows unconfirmed until each consumer restarted
and passed its health check. Issue 103 becomes a prerequisite.

The file is renamed to match what it now decides. 0112 follows.
2026-09-25 23:10:52 +02:00

186 lines
13 KiB
Markdown

---
topic: what runs on it
status: proposed
date: 2026-09-25
deciders: jochen
reconstructed: false
extends: 0046-a-module-configuration-is-its-assignments-not-its-manifest.md
---
# 112. A module definition names no node, no mesh and no path: everything it needs is a requirement the mesh resolves
## Context
[Issue 119](../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md) found
**789 host-path strings in 70 of the catalogue's 71 module definitions.** Each definition chooses
where on the machine its directories, mounts, bindings, secrets, env-files and received files live,
and often repeats that path in an environment variable or in code. Mounts are checked against what
the definition declares ([ADR 0091](0091-a-mount-is-declared-three-ways.md)); nothing checks the
copies. The issue records what that has already allowed:
- a provider that would provision nobody without a word;
- a contributions file that carries host paths into containers, so every provider must mount its
grants directory at the identical path;
- defaults in code that disagree with their own manifests;
- no way to assign one module to one node twice, because every identity is keyed by the module's name.
**Paths are one case of a wider pattern.** A module gets what it needs through at least six separate
mechanisms today, each with its own syntax and its own failure modes:
- provisions, read through bindings;
- settings on the assignment ([ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md));
- ports the mesh assigns ([ADR 0038](0038-the-mesh-assigns-the-port.md));
- machine facts a manifest asks for;
- secrets, either minted or accepted from an operator;
- literals carried in the definition itself.
The mesh has already unified parts of this. Ports became the mesh's rather than the module's (0038),
configuration became the assignment's (0046), and which provider serves a consumer became the
assignment's choice ([ADR 0084](0084-which-provider-serves-a-consumer.md)). What remains is the
concept that joins them.
## Considered Options
**1. Keep host paths in definitions, and check that every copy agrees.** Rejected. It checks the
agreement of something that should not be there. A definition still could not follow its data to
another disk, be adopted onto a machine whose data is already somewhere, or run twice on one node.
**2. Keep the separate mechanisms, and add directories as a seventh.** Rejected. It fixes paths and
keeps the pattern that produced them: each mechanism is resolved, validated and refused differently,
so a module author learns six systems and a reviewer checks six kinds of gap.
**3. One concept: a module requires, and the mesh resolves every requirement against a contract.**
Chosen.
## Decision
**A module definition is node-agnostic and mesh-agnostic.** It names no node, no mesh and no host
path. **Everything a module needs is a requirement**: a name, a contract saying what the module may
read from it, and which kind of provider answers it.
**Installing a module on a node resolves every requirement, or refuses.** A refusal names each
unresolved requirement and what could answer it, all at once.
**There are four kinds of provider, and the set is closed:**
| provider | answers | today's mechanism it replaces |
|---|---|---|
| **another module** | a database, a bucket, a vhost, a secret, a route | provisions and bindings |
| **the node's host** | a directory, a port, facts about the machine | resource paths, `${port:}`, `${machine:}`, facts |
| **the mesh** | the module's identity and names, and the delivery of every answer | derived logins and generated names; the controller's delivery |
| **the operator, through the assignment** | a value a person chooses: a public name, a greeting, an external key | settings, carried literals |
A module provider is chosen as [ADR 0084](0084-which-provider-serves-a-consumer.md) and
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say: a pin, then the holder of a
seat that delivers the provision; for a provision no seat delivers, co-location and then the only one.
A host provider is always the module's own node, because a host path or a port means nothing on any
other. An operator value is the assignment's, or the requirement's default, or unresolved.
**A person's value stays cheap.** An operator requirement's contract is a type and, optionally, a
default. It needs no provider module, no grant and no credential. An operator value that is secret,
like an external API key, is still the operator's: the vault is where it is *kept*, as an
operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)), not who
provides it.
**Every secret is made by the vault, and a provider answers with resources and data**
([ADR 0113](0113-the-vault-makes-every-secret.md)). A provider that needs a secret for a consumer
requires it from the vault, like any consumer. The mesh carries every answer back.
**A directory is a host provision.** Its contract is the owner and mode the module needs, including
the owner its image expects ([ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)).
It carries no persistence flag. A directory is kept while it holds anything
([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md), which refused a `keep` flag for good
reason), and data that is disposable is not a directory but a named volume (0107). *Where* it is on
the machine is the assignment's. A node has a default layout, and an assignment may place a directory
elsewhere: on a second disk, or where an adopted machine's data already is
([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)).
**An operator's shared data stays an `access`** ([ADR 0051](0051-shared-data-is-the-operators.md)),
not a directory. 0051 rejected giving a directory an operator owner, because the mesh must never
create, chown or remove such data, and that stands. Only where its location is written changes: the
module requires read or read-write access, and the assignment says where the data is.
**Inside a container, a module sees its own paths.** The definition says where the image expects each
directory. The mesh mounts the assignment's location there. No host path is ever a value a process
reads, and the mesh's own files (answers, contributions) name nothing by host path, so a provider needs
no mount at a machine-identical path.
**An assignment has an identity of its own: an instance name**, defaulting to the module's name.
Everything keyed by the module's name today is keyed by the instance instead: directories, container
names, the login a consumer presents, broker accounts, a claim's holder, the settings an assignment
carries, and a provider's identity. So **one module may be assigned to one node more than once.** What
must stay singular stays so by a claim, or by an operator value colliding: a public name already taken
is refused like any other singular thing.
**Genesis is not an exception.** It raises the vault first and asks it for the foundation's secrets,
so the foundation's requirements are answered the same way as everything else
([ADR 0113](0113-the-vault-makes-every-secret.md)).
## What this changes in earlier records
On acceptance, each of these is amended by a record of its own, not edited:
- [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md): settings become
operator requirements, addressed to an instance rather than to a module on a node.
- [ADR 0038](0038-the-mesh-assigns-the-port.md): a port becomes a host requirement. What 0038 decided
is unchanged; it is the first case of this rule.
- [ADR 0051](0051-shared-data-is-the-operators.md): an access keeps its shape and its semantics; its
path moves from the definition to the assignment.
- [ADR 0091](0091-a-mount-is-declared-three-ways.md): a mount's host side is a resolved requirement,
checked as resolved rather than as a path the definition declares.
- [ADR 0084](0084-which-provider-serves-a-consumer.md): a provider is a (node, instance) pair, not a
(node, module) pair, so a consumer can name one of two instances on one node.
- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md): a login is built from the
instance, which gets a short form under the same rules as a slug, so it still fits the tightest
backend.
- [To-be 26](../03-DESIGN/01-to-be/26-the-seats.md): a seat's holder is an instance.
- The [glossary](../00-META/glossary.md): *provision* widens from "a service one module provides" to a
requirement answered by any of the four providers, and *instance* is added. Neither lands while this
record is only proposed, because the glossary is the authority on the words in use, not on words
under review.
## Consequences
- **Every definition changes.** 70 of 71 name host paths today, and most use at least three of the
mechanisms this replaces. The change is mechanical for most. The design has to say how existing
modules move without their data moving: an adopted or already-running assignment is placed where
its data already is.
- The controller resolves every requirement at assignment and refuses unresolved ones. The host
answers directories and ports. The settings, placeholders, facts and bindings that exist today
are retired as separate mechanisms, once nothing uses them.
- Identity moves from the module to the instance, which touches logins, broker accounts, settings,
provider selection and every resource name. A login already has a 20-character limit
([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)), which a node and an instance
name will strain. The design must answer that before a second instance is possible.
- **What got harder:** a definition no longer says where a module's data is on a machine, or what a
setting's value is. The assignment does, and `plan` shows it. That is the point, and it is also a
real loss of at-a-glance legibility, which the overview has to give back.
- **Not decided here:** the syntax a definition reads a requirement's fields with; a node's default
layout; the order in which the mechanisms are retired. [To-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md)
proposes all three.
## How it is checked
| Rule | Checked by |
|---|---|
| A definition names no host path | A catalogue test: the host side of every mount, and every resource location, is a requirement rather than an absolute path. A declared list of exceptions shrinks to empty as definitions move. |
| No host path is a value a process reads | A catalogue test: every absolute path in a container's environment or env-files lies on the container side of one of its mounts, or is declared the image's own. A second test finds literal paths in module code used as fallbacks for an environment variable. |
| A definition names no node and no mesh | The parser has no field that names a node; a node is named only in an assignment. A catalogue test finds no domain name in any definition value. |
| Every requirement has one of the four providers | The parser refuses a requirement whose provider kind is not one of the four. |
| Installation resolves every requirement | A resolution test with one requirement unanswered: refused, naming it and what could answer it. |
| A host requirement is answered on its module's own node | A resolution test: an assignment placing a directory or a port on another node is refused. |
| A module can run twice on one node | A resolution test assigning one module twice under two instance names: two directories, two logins, two containers, separate settings, no collision. |
| A public name already taken is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. |
| An adopted assignment is placed where its data is | An adoption test: the directory resolves to the data's existing location, and nothing is moved. |
## References
- [Issue 119](../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md): the evidence
- [ADR 0038](0038-the-mesh-assigns-the-port.md), [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md),
[ADR 0084](0084-which-provider-serves-a-consumer.md): the parts already unified
- [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md): which module provider answers
- [ADR 0113](0113-the-vault-makes-every-secret.md): the vault makes every secret, and how answers travel
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md): the operator as a provider
- [ADR 0051](0051-shared-data-is-the-operators.md), [ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md),
[ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md): what a directory's contract carries, and what it must not