Files
hq/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md
T
jochen 942ebe350f Research 016: survey how each provider can rotate a credential
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.
2026-09-26 00:14:23 +02:00

23 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be proposed
2026-09-26
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, issue 119).

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. the holder of the seat the requirement names. A requirement may name a seat instead of leaving the provider open. It asks for the mesh's one, and the mesh answers with whichever assignment holds that seat, with nothing asked of anyone. Unheld, the requirement is refused, naming the seat (ADR 0110, 26 — The seats). A secret requirement always names mesh-vault, because that provision is reserved;
  2. a pin: the assignment names a provider, because this consumer is coupled to that provider's contents (ADR 0084);
  3. the provider on the consumer's own node;
  4. the only provider in the mesh;
  5. otherwise a person chooses, at assignment. Assigning the module lists the candidates, with the holder of a seat that delivers the provision suggested first, and the answer is recorded on the assignment as its pin. Without an answer the module is not assigned, and the refusal names the candidates. Nothing is ever guessed (ADR 0009).

Secrets: provisioning all the way down

Two kinds of secret, one rule each (ADR 0113):

  • a shared secret, a value more than one party must hold (a password, a token, an API key), is made by the vault, and by nothing else;
  • a private key, such as a node's sealing key, the operator's key or the mesh's certificate authority, is made where it is used and never leaves. A private key anyone else held would no longer be private.

Every shared secret is a secret requirement, and only the vault provides secret. The vault holds the mesh-vault seat, and that provision is reserved to it: no other module may provide it, and no pin can choose another provider (26 — The seats).

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 of its recipients, each sealed to its own node: the database's machine, which applies it by creating the login, and gitea's, which presents it;
  5. the database provider creates the login, exactly as it does today, and gitea connects.

Every other shared secret takes the same path:

  • a module's own secret;
  • every broker account's password on the mesh's bus, where the broker's own provisioner creates the account;
  • an enrolment token, which the operator receives and the controller can only verify;
  • a secret operator value, which the operator delivers to the vault;
  • a secret a backend issues itself, such as a forge's API token, which the module that received it delivers to the vault.

The controller takes the same path, because it is a module. Its store logins and bus accounts are own secrets of its definition today, and become requirements of that definition. A node's host is the one party with no definition, because it is what runs definitions. Its bus account is a requirement the mesh makes for each enrolled node, answered and carried exactly as for a module.

A provider makes resources and data. Beyond secrets, a provider's adapter may answer 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, ADR 0107).

Where a directory is on the machine is the assignment's:

  • a node's default layout, a root per node with one directory per assignment 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).

An operator's shared data is an access, as before (ADR 0051): 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 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).

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), 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 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 assignment

A module is assigned at most once to a node, and that pair is the assignment's identity (ADR 0112). Its directories, containers, login, broker account and settings are keyed by it, as today, and a login still fits the tightest backend (ADR 0049).

A module may run on many nodes, and one assignment may hold a seat (ADR 0110). The definition says which seats the module can hold; the assignment says which it does. So the store module can run on every node, one of those assignments holds mesh-store, and moving that role changes an assignment, not a definition.

What must stay singular stays so: by a seat, or by an operator value colliding, as with a public name.

Genesis

Genesis delivers, and the vault adopts. The vault cannot run first: it is built on the shared runtime base, which the installation makes only after the store, the broker and the controller exist (21 — The installation in full), and it learns what to answer from the controller over the bus. So the vault is installed as soon as that base exists, before any other module built on it, and genesis generates what is needed until then:

  • the store's superuser, and the broker's admin in the hashed form the broker needs;
  • the bus accounts of the temporary and permanent controller (its account and the broker-management login), the control-node's host, the builder, the broker's own provisioner and the vault;
  • the controller's three store logins, and the first enrolment token.

Until the broker's provisioner runs, genesis creates those bus accounts with the broker's admin, as the controller does today; the provisioner adopts them when it starts. Genesis seals everything to the control-node's key, and when the vault is installed the controller delivers the values to it, recorded as the mesh's own, with nobody present. That distinction keeps them rotatable: an operator's value is never replaced, and these are, because the vault can make their replacements.

That is the one time anything but the vault generates a shared secret, and it ends by handing them over. It is also the answer to the objection ADR 0085 had to the vault being the only maker: the vault cannot make what exists before it, so what exists before it is delivered to it.

Raising the vault or the broker again is a genesis act. Moving either seat to a new assignment, or recovering either after it is lost, delivers the values it needs the way genesis did. It is a stated break-glass procedure, and an ordinary assignment attempting it is refused.

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 requirement's contract, and the vault makes the new value (ADR 0113). An operator's external key is not rotated by the vault, which cannot make its replacement: an operator delivers a new one.

Each requirement says how its recipient takes a new value. It either applies it, through a provisioner (a provider setting a login's password, the broker's provisioner updating an account, a store's provisioner changing its own superuser), or reads it at start. Every module in the catalogue reads its secrets at start, and none watches them. The host already recreates a container when a file it read at creation changes, its env-files and files mounted into it directly (issue 103). So a reader's restart is derived, and a definition declares restart-on only for a secret reaching a process, or a file in a mounted directory. A secret a service takes only at first initialisation is applied by its provisioner or marked not rotatable by the mesh, and a rotation of it is refused rather than reported done.

How old and new change over is not settled. Until it is, rotation stays as the controller does it today: in place, both ends sent in one push, with a stated window in which a consumer cannot authenticate. Research 016 measured three mechanisms against every provider. One constraint holds whichever is chosen: retiring a credential must never remove a consumer's resource. Today's adapters remove both in one call, and in five providers that deletes the consumer's data.

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;
  • several candidates and no choice made, and which they are;
  • 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) operator requirements on an assignment
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, enrolment tokens a secret the vault makes (ADR 0113)
root secrets genesis mints and keeps apart made by genesis once, then delivered to the vault, which holds and rotates them
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 shared secret, and providers answer. The vault holds its seat and its reserved provision; resolution expands per-consumer secret requirements; genesis delivers the foundation's first secrets to the vault; the broker's provisioner creates every bus account; the mesh carries providers' data back. Issue 103 is fixed in the host already. Ends when nothing outside the vault generates a shared secret after genesis, a lab consumer of analytics receives its site id, and a database credential rotates, the vault making the value, the consumer recreated by derivation, and its data intact.
  3. Definitions move, and seats move to assignments. Every catalogue definition is rewritten, adopted and running assignments placed where their data already is, and each claim becomes a seat the module can hold, held by the assignment that holds it today. Ends when the list of definitions using an old form is empty, the old forms are removed, and the store module runs on two lab machines with one holding mesh-store.

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.
A module provider is chosen by named seat, pin, co-location, only one, a person's choice Resolution tests for each step: a requirement naming a seat served by its holder even with another provider on the consumer's node, and refused when the seat is unheld; several candidates and none local, where assignment lists them with the seat's holder first and records the choice as a pin, and refuses without one.
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 assignment asking for a public name another holds is refused, naming the holder.
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.
A seat is held by an assignment, not a module A resolution test: the store module on two nodes, one holding mesh-store; a second assignment asking to hold it is refused.
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 generates a shared secret after genesis A controller test: no code path generates one. An installer test: genesis generates exactly the foundation's first secrets and delivers them to the vault.
Only the vault provides secret The parser refuses another provider of it, and resolution refuses a pin on a secret requirement.
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 recipients.
Restarts are derived, and rotation keeps data The tests of ADR 0113: an applied secret restarts nothing, one read at start recreates its reader without a declared restart, and rotating a consumer's credential leaves its resource and data intact.
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

  • How old and new credentials change over on rotation: in place, as today, or two logins over one resource, as research 016 recommends for credentials with two parties. It is decided in its own record.

  • 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 assignment.

  • 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.