Overlap as drafted in 0113 would have deleted consumer data: seven of eight providers name the resource after the login and five drop it on remove. Rotation is now undecided in 0113 and to-be 27, pending the survey. Also: a requirement naming a seat resolves to its holder, a person chooses among remaining candidates at assignment, the controller's secrets are requirements of its definition, genesis seals to the control-node key, and moving the vault or broker is break-glass.
191 lines
13 KiB
Markdown
191 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, or be adopted onto a machine whose data is already somewhere.
|
|
|
|
**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 that is not secret: a public name, a greeting, a number of workers | 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 requirement naming a seat is
|
|
answered by its holder. Otherwise it is a pin, then co-location, then the only provider, and where
|
|
several remain, a person chooses at assignment and the choice is recorded as a pin.
|
|
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.
|
|
|
|
**Every secret is a `secret` requirement, answered by the vault**, with no exception by kind
|
|
([ADR 0113](0113-the-vault-makes-every-secret.md)). An external API key an operator chooses is no
|
|
different: the operator delivers it to the vault ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)),
|
|
and the module requires a `secret` like any other. A provider that needs a secret for a consumer
|
|
requires it from the vault, like any consumer, and answers with resources and data. 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.
|
|
|
|
**A module is assigned at most once to a node.** An assignment is a module on a node, and that pair is
|
|
its identity: its directories, containers, login, broker account and settings are keyed by it, as
|
|
they are today. A module may run on many nodes, and one of those assignments may hold a seat
|
|
([ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)). Running the same module twice
|
|
on one machine is not supported. The cases that seemed to need it, such as two stores of one engine or
|
|
two stages of one application, are different modules, or the same module on different machines. The
|
|
line is drawn because every identity in the mesh is already a module on a node, and a second
|
|
instance would have to rename all of them.
|
|
|
|
**What must stay singular stays so** by a seat, or by an operator value colliding: a public name
|
|
already held by another assignment is refused like any other singular thing.
|
|
|
|
**The foundation's first secrets are delivered, then adopted.** Genesis generates them before the vault
|
|
can run and hands them to the vault once it is installed, and from then on they are answered the same
|
|
way as every other secret ([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 on an assignment.
|
|
- [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.
|
|
- [To-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md): the step that builds and runs a
|
|
store module as a database provider, beside the foundation's store on the same node, would run the
|
|
store module twice on one node. The adopted store module ([ADR 0078](0078-the-store-and-broker-are-modules.md))
|
|
holds `mesh-store` and serves that node's database consumers by co-location, so there is no second
|
|
one.
|
|
- 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 *requirement* and *contract* are added. None
|
|
of it 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 stays a module on a node. Nothing is renamed, and a login still fits the tightest backend
|
|
as [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md) arranges.
|
|
- **What got harder:** one module cannot run twice on one machine; a second stage or a second store
|
|
of one engine is a different module or a different machine. And 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 is assigned at most once to a node | A resolution test: assigning a module to a node that already runs it is refused, naming the existing assignment. |
|
|
| A public name already taken is refused | A resolution test: a second assignment asking for a public name another holds is refused, naming the holder. |
|
|
| 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
|