Both lines of work numbered from the same point, so four decision records and one design document existed twice with different content. The trunk keeps its numbers and this branch yields — the only rule that scales, because the trunk's are already cited by what merged before them. 0117 the bus is the only broker -> 0125 0118 a module declares its own seats -> 0126 0119 amqp is a provision, not the bus -> 0127 0120 the mesh bus is required -> 0128 0123 a seat carries its role's protocol -> 0129 0124 the predecessor is ending -> 0130 design 29, what a module declares -> design 32 Applied to the code repositories too, because a stale reference is worse when numbers collide than when they dangle: the reader lands on a real record that decided something else. Two reconciliations the merge forced, both real: **0110 was marked wholly superseded and was not.** Its successor says in as many words that everything 0110 decided about what a seat *is* stands untouched — and two records that landed on the trunk rest on exactly that part. So it is accepted again, extended rather than replaced, with a note saying which of its claims moved and where. **A seat's protocol becomes columns, not fields.** The trunk moved the seat set out of compiled code into a table the controller owns. This branch had added what a role accepts, emits and serves to the Go slice. The decision is unaffected and the mechanism is better for it: giving a role a protocol is now a write rather than a rebuild, which is the trunk's own argument applied to what this branch added. One check still fails and it fails on main too: a record resting on ADR 0112 while that is still 'proposed'. Left alone — it is not this merge's to answer.
395 lines
27 KiB
Markdown
395 lines
27 KiB
Markdown
---
|
|
layer: to-be
|
|
status: proposed
|
|
code: []
|
|
updated: 2026-09-26
|
|
decisions:
|
|
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.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
|
|
- 02-DECISIONS/0126-a-module-declares-its-own-seats.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. **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 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md) (superseding [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md)),
|
|
[26 — The seats](26-the-seats.md)). Only a seat that delivers a provision can be named; naming a
|
|
foundation seat is refused, because it delivers nothing. 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](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md));
|
|
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](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
|
|
|
### Secrets: provisioning all the way down
|
|
|
|
**Two kinds of secret, one rule each** ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)):
|
|
|
|
- **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](26-the-seats.md)).
|
|
|
|
**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](../../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 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](../../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 assignment
|
|
|
|
**A module is assigned at most once to a node**, and that pair is the assignment's identity
|
|
([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md)). Its directories,
|
|
containers, login, broker account and settings are keyed by it, as today, and a login still fits the
|
|
tightest backend ([ADR 0049](../../02-DECISIONS/0049-a-consumers-identity-fits-the-tightest-backend.md)).
|
|
|
|
**A module may run on many nodes, and one assignment may hold a seat**
|
|
([ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md) (superseding [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md))). 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](21-the-installation-in-full.md)), 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](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)). 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](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)).
|
|
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 depends on how many parties hold the credential**
|
|
([ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md), on
|
|
[research 016](../../01-RESEARCH/016-how-a-credential-can-be-rotated/00-overview.md)):
|
|
|
|
- **Two parties**, a consumer and its provider, or a module or node's host and the broker: each consumer
|
|
has two credentials, both with its rights over one resource named after the consumer. For most
|
|
providers the second is a second login derived by the mesh; where a backend's user is its resource, it
|
|
is a second token. The vault drives the rotation and records each step. It makes the new value; each
|
|
applier ensures the unused credential and confirms both authenticate; only then are readers given it
|
|
and recreated; each reader confirms by authenticating with it; and only then is the old one retired.
|
|
Nobody is left without a credential that works, a rotation can be abandoned until the old one is
|
|
retired, and `status` shows who a rotation waits on.
|
|
- **One party**, a provider's administrative credential or a module's own secret: in place, staged.
|
|
An applied one is delivered beside the current value, the provisioner changes the backend with the
|
|
current one, and only then does the new value become current. One read at start is delivered, and
|
|
the host recreates the module.
|
|
|
|
**Retiring a credential never removes what it reached.** An adapter keeps *retire a credential* and
|
|
*remove the consumer* apart, and the harness keys what it applied by consumer, so a changed login is
|
|
never a removal. The resource is removed only when the consumer no longer requires it from that
|
|
provider: unassigned, the requirement dropped, or re-resolved elsewhere. Today these are one call, and
|
|
in five providers it deletes the consumer's data, so this separation comes first. An adapter that cannot
|
|
yet ensure a second credential rotates in place, with its window stated, and is listed until it can.
|
|
|
|
## 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](../../02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md)) | 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](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)) |
|
|
| 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](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
|
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 over
|
|
its two credentials, the consumer recreated by derivation, never without a working login, 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](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md). |
|
|
| 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 | The tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): an applied secret restarts nothing, and one read at start recreates its reader without a declared restart. |
|
|
| A two-party credential rotates over two credentials | The rotation tests of [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md): retiring a credential leaves the resource intact; a changed login is never a removal; readers move only after the applier confirms and confirm by authenticating; an unreachable reader keeps its old credential until it returns; rotation state survives a restart; a single-party applied secret is staged. |
|
|
| A private key is made where it is used | The per-key tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): a node's sealing key, the operator's key and the certificate authority's key never leave where they were made. |
|
|
| The controller and a node's host take the same path | 0113's tests: the controller's definition declares requirements and no own secret; a node's bus account is made by the vault and delivered sealed to that node. |
|
|
| Moving the vault or the broker is break-glass | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
|
|
| A secret that cannot be rotated says so | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
|
|
| Only the controller reads the seat placeholder | A catalogue test, from phase 3: no definition uses the seat placeholder. |
|
|
| 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 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.
|
|
|
|
## The three gaps, answered (2026-09-26, operator)
|
|
|
|
**Where the root comes from.** A node setting, fixed at installation; a node that states none
|
|
gets the default under `/var/lib`. (The adopted-machine placement stays as proposed: a
|
|
per-assignment placement for data that must sit where it already is — mssql is novox's live
|
|
case.)
|
|
|
|
**What sits beneath it — dissolved, not decided.** The question assumed the mesh's own
|
|
writes (`/var/lib/mesh/<module>`: sealed credentials, composed bindings) need a
|
|
module-visible reservation. They do not: a module *requires* a `host-path` and receives a
|
|
location; what the mesh writes for the module is the mesh's plumbing, placed where the mesh
|
|
chooses and mounted in — never part of the module's contract. One reservation per
|
|
requirement, `<root>/<module>/<name>`.
|
|
|
|
**Resolution happens in the controller, at declaration composition.** The node receives
|
|
concrete paths exactly as today — the wire format and the host's apply do not change for
|
|
this. What changes is that no *manifest* carries a path; the controller resolves
|
|
requirement → location against the node's root setting. (The host still needs issue 126's
|
|
fix — volumes in the spec comparison — or a resolved path change cannot reach a running
|
|
container.)
|
|
|
|
**Retirement order** stays per-module data migration with verification, smallest and
|
|
empties first, the mail spool and the store last — the novox session's six-module window
|
|
(mesh-catalog #97, hq 126) is the worked example, hazard included.
|
|
|