--- layer: to-be status: proposed code: [] updated: 2026-09-25 decisions: - 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md - 02-DECISIONS/0113-the-vault-makes-every-secret.md - 02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md - 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md - 02-DECISIONS/0084-which-provider-serves-a-consumer.md - 02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md - 02-DECISIONS/0038-the-mesh-assigns-the-port.md --- # 27 — A module requires, the mesh resolves **One concept for everything a module needs.** A module definition states what it requires. Each requirement has a contract and a kind of provider. Installing the module on a node resolves every requirement, or refuses and says why. Nothing else reaches a module: no path it chose, no setting beside the model, no literal it carries. This replaces six mechanisms that grew separately: provisions read through bindings, settings, assigned ports, machine facts, minted or accepted secrets, and literals in the definition. Each resolved, validated and failed in its own way ([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md)). ## A requirement A requirement has three parts: | part | is | |---|---| | name | what the module calls it, unique within the module | | contract | the fields the module may read, and what each promises: a type, whether it is secret, and anything the provider must honour | | provider kind | which of the four kinds of provider answers it | **A contract is shared, not per module.** A database's contract is the database's, whoever requires it. **The controller holds every contract**, one per provision name, declared where the provision is defined in the catalogue. Today contracts are implicit in each provider's served fields; the first phase below makes them explicit, because nothing can be checked against a contract that is not written down. A provider is checked against the contract it claims to answer. A module's own specification may narrow a contract (a password of at least this length, a directory owned by this user) and never widen it. ## The four kinds of provider The set is closed, like the seats. A fifth kind is a decision, because each kind is a place an answer can come from and a reviewer has to know every one. | provider | answers | resolved by | replaces | |---|---|---|---| | **a module** | a database, a bucket, a vhost, a route, a secret | the rule below | provisions and bindings | | **the node's host** | a directory, a port, a fact about the machine | always the module's own node | resource paths, assigned ports, machine placeholders, facts | | **the mesh** | the module's identity and names, and the delivery of every answer | the controller | derived logins and generated names; the controller's delivery | | **the operator** | a value a person chooses | the assignment, else the requirement's default | settings, carried literals | ### A module provider Which module answers, in order: 1. **a pin**: the assignment names a provider, because this consumer is coupled to that provider's contents ([ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md)); 2. **the holder of a seat** that delivers the provision, where one does. Co-location does not apply to these: the seat is the mesh's one answer for everyone ([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [26 — The seats](26-the-seats.md)); 3. for a provision no seat delivers, **the provider on the consumer's own node**; 4. for a provision no seat delivers, **the only provider** in the mesh; 5. otherwise **refused**: naming the unheld seat, for a provision a seat delivers, even when exactly one provider exists; naming the candidates otherwise. ### Secrets: provisioning all the way down **The vault makes every secret, and it is the only thing that does** ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)). It holds the `mesh-vault` seat, so every `secret` requirement resolves to it. **A provider that needs a secret for a consumer requires one, like any consumer.** A provision's contract declares it: *for each consumer, one secret*. Resolution expands that into one requirement per consumer, named for that consumer: 1. gitea requires `postgres-database`; 2. the database provider, to serve gitea, requires a `secret` named for gitea; 3. the vault makes it and hands it to the mesh; 4. the mesh delivers it to both holders, each sealed to its own node: the database's machine, to create the login, and gitea's, to present it; 5. the database provider creates the login, exactly as it does today, and gitea connects. A module's own secret, a broker account's password and a secret operator value take the same path. Nothing in the mesh makes a secret except the vault. **A provider makes resources and data.** Beyond secrets, a provider answers with its contract's non-secret fields: an analytics site id, a registered public name. The mesh carries them back to the consumer as resolved values. ### The node's host The host answers what only a machine can: where a directory is, which port is free, what the machine is. It is always the module's own node, because none of these means anything elsewhere. **A directory.** The contract is an owner and a mode, and the owner the image expects where it has one. There is no persistence flag. A directory is kept while it holds anything, and data that may be lost is a named volume, not a directory ([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md), [ADR 0107](../../02-DECISIONS/0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)). *Where* a directory is on the machine is the assignment's: - **a node's default layout**, a root per node with one directory per instance beneath it, used when the assignment says nothing; - **a placement**, where the assignment puts one directory elsewhere: on a second disk, or where an adopted machine's data already is ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)). **An operator's shared data** is an access, as before ([ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md)): never created, owned or removed by the mesh. The module requires read or read-write access. Where the data is, is an operator value on the assignment. **A port** is what [ADR 0038](../../02-DECISIONS/0038-the-mesh-assigns-the-port.md) already decided: the module says which port its software uses, and the host answers with where the machine put it. **A fact** is something the machine knows: its name on the private network, the names of the mesh's machines. Each fact has a contract like anything else. ### The mesh The mesh answers who the module is: its login, its broker account, the names it is known by. These are derived by the mesh so every party agrees by construction, and no provider may make them ([ADR 0049](../../02-DECISIONS/0049-a-consumers-identity-fits-the-tightest-backend.md)). ### The operator A value a person chooses: a public name for an endpoint, a greeting, how many workers to run. **It must stay cheap.** An operator requirement's contract is a type and, optionally, a default. It needs no provider module, no grant and no credential. If asking a person for a value took more than that, module authors would route around it, and the literals this replaces would come back. **A secret operator value**, such as an external API key, follows the one rule for secrets: the vault provides it. The operator hands the value to the vault, once ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)), and the module requires a `secret` like any other. The only difference is that rotation never replaces it: the vault cannot make a new external key, so rotating one means an operator handing over a new value. **An endpoint** is an operator value inside a route requirement: the public name is chosen on the assignment, and the route provider answers. A public name already held by another assignment is refused, like any other singular thing. ## 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 host in an environment variable, the directory's location on the host side of a mount, or the public name in a configuration file writes the same thing: the requirement's name and the field. The controller fills it at resolution. This one form replaces the placeholders that exist today, one per mechanism: bound values, secrets, ports and machine facts. **The seat placeholder stays, for the controller alone.** The controller composes its own declaration and reaches the store and broker it made before any module existed, so it cannot be their consumer. One module reads the placeholder today: the store module, to find its own server's port. That is its own port, so it becomes a host port requirement in phase 3, and after that no module uses the seat placeholder. **A secret field reaches a process as a file**, as [ADR 0086](../../02-DECISIONS/0086-a-secret-reaches-a-process-as-a-file.md) decided. The one exception 0086 allows is a declared env-file with its reason; a secret field as a value in a container's environment is refused when the definition is parsed, with no exception. ## An instance An assignment has an identity: an **instance name**, which defaults to the module's name. Everything keyed by the module's name today is keyed by the instance: directories, containers, the login it presents, its broker account, the seats it holds, its settings and its identity as a provider. So a module may run twice on one node, under two instance names. What must stay singular stays so: by a seat, or by an operator value colliding, as with a public name. **A login still has to fit the tightest backend**, which is twenty characters today ([ADR 0049](../../02-DECISIONS/0049-a-consumers-identity-fits-the-tightest-backend.md)). A node name and an instance name will not fit in full. The instance therefore gets a short form alongside the module's slug, under the same rules as a slug. This has to be settled before a second instance is allowed. ## Genesis **Genesis is the vault's first answer, not an exception.** It raises the vault before anything else and asks it for the foundation's secrets: the store's superuser, the broker's admin in the hashed form the broker needs, and the vault's own broker account. The vault answers with the same code it always uses, before the bus exists, and seals the root secrets to the operator key as today ([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md)). Genesis makes no secret itself, so there is one way a secret is made, from the first one onwards. The vault can sit at the bottom because it requires nothing but a broker account: it keeps its data on its own disk, not in the store. So the waterfall ends at the vault. ## Rotation Rotating a secret is asked of the vault, by an operator or by the vault's own policy, such as a maximum age in the secret's contract ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)). 1. **The vault makes the new value.** 2. **It is delivered to the holders that accept it first.** For a database credential that is the database's provider, whose provisioner applies it and confirms it did. 3. **Only then is it released to the holders that present it**, such as gitea. A consumer is never sent a value its provider has not accepted, so the window in which it cannot log in shrinks to its own restart. A provider that does not confirm holds the rotation: the consumer keeps the old value, which still works, and the rotation shows as waiting on that provider. 4. **The host restarts every process that reads the secret**, and recreates a container whose env-file carries it. It knows which, because a definition reads a secret only through its requirement. No definition declares a restart for a secret. This needs [issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md) fixed, or a container fed by an env-file keeps the old value. 5. **It is confirmed.** The rotation shows as unconfirmed until each consumer has restarted with the new value and passed its health check, where its definition declares one. Delivered and working are shown as different things. ## Refusing Installation refuses when any requirement is unresolved, and **says everything at once**. For each requirement it names what is missing and what would answer it: - an unheld seat, and which modules could hold it; - no provider, and which modules could provide it; - an operator value with no default, and that the assignment must give it; - a provider, or the vault, that has not answered yet, and which one. The last one is a state, not a failure. A consumer waiting for its provider or for the vault is shown as waiting, and nothing is delivered until the answer arrives. ## What this retires | mechanism | becomes | |---|---| | provisions read through bindings | a module requirement; its answer is the contract's fields | | settings on an assignment ([ADR 0046](../../02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md)) | operator requirements, addressed to an instance | | a port the mesh assigns | a host requirement | | machine facts and machine placeholders | host requirements | | every secret the controller mints: provider credentials, own secrets, broker passwords, root secrets | a secret the vault makes ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)) | | a separate command issuing a broker account | a requirement resolved on assignment | | `restart-on` naming a secret's file | a restart the host derives | | paths in resources, mounts, bindings, secrets and received files | host directory requirements, placed by the assignment | | literals carried in a definition | operator requirements with defaults | Each is retired only once nothing uses it. Until then both are accepted, and a catalogue test lists the definitions still using the old form. That list shrinks to empty, and then the old form is removed from the parser. ## Phases Each phase ends at a check that holds, so none of them leaves a mechanism half-replaced. 1. **Contracts, resolution and the new form.** Every provision's contract is written down and held by the controller. The controller resolves requirements from the four providers, refuses as above, and fills the one form. Old mechanisms keep working beside it. *Ends when* a definition written entirely in the new form installs on a lab machine. 2. **The vault makes every secret, and providers answer.** The vault holds its seat and is the only maker; resolution expands per-consumer secret requirements; genesis asks the vault first; the mesh carries providers' data back. [Issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md) is fixed first. *Ends when* no code path outside the vault makes a secret, the analytics and DNS providers answer their consumers, and a database credential rotates provider-first, with the consumer restarted by derivation and the rotation confirmed. 3. **Definitions move.** Every catalogue definition is rewritten, adopted and running assignments placed where their data already is. *Ends when* the list of definitions using an old form is empty, and the old forms are removed. 4. **Instances.** The instance name and its short form, and everything keyed by it. *Ends when* one module runs twice on one lab machine with two public names. ## How it is checked | Rule | Checked by | |---|---| | Every requirement has one of the four provider kinds | The parser refuses any other. | | A definition names no host path, node or mesh | The catalogue tests of [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md). | | A module provider is chosen by pin, seat, co-location, only one, refusal | Resolution tests for each step; for a second provider on a consumer's own machine when a seat delivers the provision; and for an unheld seat with exactly one provider, refused. | | A provider answers within its contract | A controller test: an answer carrying a field its contract does not name, or missing one it does, is refused and not delivered. | | A module narrows a contract and never widens it | The parser refuses a module specification that loosens a contract's field. | | A host requirement is answered on its own node | A resolution test placing one elsewhere: refused. | | An operator value needs no provider module | A resolution test: a requirement with a default resolves with no module assigned anywhere. | | A secret field reaches a process as a file | The parser refuses a secret field as a container environment value, and accepts it in a file or a declared env-file with its reason. | | A public name already held is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. | | Everything keyed by a module is keyed by its instance | A resolution test: two instances of one module on one node get two directories, two containers, two logins and separate settings. | | A consumer waits for its provider | A resolution test with a provider that has not answered: shown as waiting, and nothing delivered. | | Only the vault makes secrets | A controller test: no code path mints a secret, genesis included. | | A provider's per-consumer secret comes from the vault | A resolution test: requiring a database expands to a secret requirement named for the consumer, answered by the vault and delivered to both holders. | | Rotation is provider-first, derived and confirmed | The rotation tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): the consumer waits for the provider's confirmation, is restarted without a declared restart, and shows unconfirmed until healthy. | | Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. | | The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. | ## Not settled here - The exact spelling of the one form. It must name a requirement and a field and nothing else. - The layout a node's default root uses beneath it, beyond one directory per instance. - Whether a module provider's answer can change without the provider being asked, for example a provider moving. The rule so far is that it cannot, and moving is re-resolving. - **Which seats a module holds.** Today a claim is part of the definition, so moving a seat is a definition change ([26 — The seats](26-the-seats.md)). By this document's own logic it belongs to the assignment: a definition says which seats a module *can* hold, and the assignment says which it *does*. That changes [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), and is its own decision.