Recorded on the record, dated, before anything shipped against the sentences that change. The vault is installed at genesis like the store and broker, one per mesh, and holds every secret a module has for itself sealed a second time to an operator key whose private half never enters the mesh — the break-glass path the first version left open, without a key one place holds. Design 24 says how; 07 and 21 say what genesis does not yet do; issue 071 names the fixed credentials the foundation is raised with today.
9.9 KiB
topic, status, date, amended, deciders, reconstructed, extends
| topic | status | date | amended | deciders | reconstructed | extends |
|---|---|---|---|---|---|---|
| what runs on it | accepted | 2026-09-20 | 2026-09-20 | jochen | false | 02-DECISIONS/0031-the-control-plane-authenticates-nobody.md |
85. A secret is a provision, and the vault is the module that provides it
Context
ADR 0031 decided that identity runs on the mesh, not of it — a module other modules require, rather than a privileged part of the controller. ADR 0078 did the same for the store and the broker: the twelve-module floor has no specialty left in it. Secrets are the exception that survived. No module owns a secret.
Secret handling is smeared across three built-in parts of the runtime:
- the controller mints one credential per consumer↔provider pair and seals it to both node keys (ADR 0048);
- the mesh generates local secrets — "generated secrets are the mesh's, never authored"
(as-is,
06-configuration-and-secrets.md) — for a value a single module needs for its own use; - the synchroniser injects both into a node's generated files.
Nothing is the owner of "a secret" the way the store module is the owner of "a database", and the
cost is recorded rather than hypothetical. The as-is design names the weakness in its own words:
"Rotation is not a mesh operation… there is no mechanism that rotates one and informs everything
holding it." A rotate command has since been built and proven, but it reaches only the
provisioned pairs; a secret a module generates for its own fully-local use — the password of a
version-pinned embedded store (ADR 0084), an internal
token — is minted by the mesh and then has no operation that can remake it. Three species of
secret, and only the first has an owner:
| species | minted by | rotates? |
|---|---|---|
| a provisioned credential (a database login) | controller, sealed to nodes (0048) | yes — rotate, per pair |
| a module's own local secret | the mesh, as a generated value | no owner, no rotation |
| an operator-delivered secret (an external key) | a person, sealed in (secret accept) |
no rotation, no audit |
Considered Options
- Leave it a property of the controller. Rejected: it is the smear above — no owner, local secrets that cannot be rotated, operator secrets that cannot be audited — and it is exactly the specialty 0078 removed for the store and broker, kept here for no reason anyone recorded.
- A dedicated vault built into the foundation, not a module. Rejected: it reintroduces a privileged built-in, the thing 0031 and 0078 went out of their way to remove, and a mesh that wants none would still carry it.
- Fold all minting, the controller's provisioning credentials included, into the vault. Rejected: the controller must mint in order to deliver any provision — the vault's own credential among them — so making the vault mint the credential of its own delivery is the store/broker chicken-and-egg for no gain. The provisioned-pair credential already has an owner and a rotation (13); this record does not disturb it.
- A secret is a provision; the vault is an ordinary module that provides it. Adopted.
Decision
Secret-holding is a module, parallel to identity. A module that needs a secret for its own
use — a local service's password, an internal token, an external key it was handed — requires a
secret provision from a vault provider, exactly as it requires a database from the store. The
vault generates the value (or holds one it was given), and because the credential belongs to the
consumer↔vault pair it rotates, backs up and is audited through the same per-pair machinery
13 already defines. That is what
gives the second and third species the rotation and audit they lack. A module's own secret stops
being a generated value that nothing owns and becomes an ordinary provision with a provider.
The controller's minting of provisioning-pair credentials is unchanged (ADR 0048): it is how every provision, the vault's own included, is delivered. The vault does not mint the mesh's delivery credentials; it provides secrets to modules, and is itself provisioned the ordinary way.
The vault is node-scoped like every provider (ADR 0084): each node its own, selected the same way, so a module's own secret is held by the vault on the module's node. A mesh that wants no vault runs none — a module requiring no secret needs nothing, which is the same test 0031 applied to identity.
Consequences
The gap that opened this — a generated local secret with no rotation — closes without new machinery: rotating such a secret is the vault's provisioner remaking a pair credential, the operation 13 already specifies. Backup, audit and break-glass gain an owner — the vault module — and become things a design specifies rather than absences. The as-is sentence "rotation is not a mesh operation" is already false for provisioned pairs and, once the vault ships, for local secrets too; the as-is document is updated when it does, not before.
What got harder: a break-glass path — recovering a secret when the sealed delivery path is unavailable — must not reintroduce a key that one place holds, which is the property ADR 0048 was built to preserve; how the vault offers recovery without it is left to the design as an open question. And a module that today bakes a password into its own composition must instead require it from the vault — a migration taken module by module, not a flag day.
Amendment — 2026-09-20, before anything shipped
Recorded on the record itself rather than as a supersession, by its decider, on the day it was accepted and before any code was merged against the sentences that change. The original text above is left as written; this section says what it got wrong and what stands instead.
What it got wrong. The decision treated the vault as a provider like the store — optional, and one per node — and left the mesh's own root secrets outside it: the store's superuser, the broker's administrator, the controller's contexts, sealed to a node key and nothing else. Those are the secrets with no rotation and no recovery, and they are the ones a vault exists for. At genesis they are not even secret: the foundation raises its store and broker with fixed, well-known credentials and carries those into the mesh. Leaving that floor in place gave module secrets an owner and the root secrets none.
What stands instead.
- The vault is a foundation module. It is installed at genesis as part of the foundation (ADR 0078 is the precedent: a foundation piece is still an ordinary module), not assigned later by a mesh that happens to want one. "A mesh that wants no vault runs none" is withdrawn. A mesh has root secrets, so a mesh has a vault.
- One per mesh, on the control-node. "The vault is node-scoped like every provider" is withdrawn. Node scoping (ADR 0084) exists because a store holds data a consumer is coupled to; the vault holds nothing a consumer is coupled to, and a second one would be a second place to lose. Consumers on other nodes reach it as they reach identity.
- The vault holds the mesh's root secrets under an operator-held key. The controller mints and delivers exactly as before; in addition, every secret a module holds for itself is sealed a second time, to an operator sealing key whose private half never enters the mesh. The vault keeps those operator-sealed copies on its own disk, outside the store, and can hand them out — they are ciphertext to everything but the operator. This is the break-glass path the original text left open, and it does not reintroduce a key one place holds: the mesh holds blobs it cannot open, and the operator holds a key with nothing to open until given a blob. Recovery needs both.
- Genesis mints real root secrets and seals them to the operator key first, so the fixed credentials the foundation is raised with are replaced before the mesh is handed over.
Unchanged. A module's own secret is a secret provision the controller mints and the vault
records; the provisioned-pair path of ADR 0048
is untouched; the vault stores no plaintext, ever.
What it costs. An operator key is a thing a person must keep, and a mesh whose operator key is lost has root secrets that can be rotated but not recovered — the same standing as today, stated. Sealing every own secret twice is a column and a call. Genesis grows a step. A module's vault-provided secret (a pair credential) is not yet sealed to the operator key; that is the next increment, not this one.
References
- ADR 0031 — identity is a module; this is the same move for secrets.
- ADR 0048 — the provisioning-credential path, left unchanged.
- ADR 0078 — the de-specialisation this completes.
- ADR 0084 — the node-scoping the vault obeys.
- issue 068 — the gap.
03-DESIGN/01-to-be/24-the-secrets-vault.md— the design. The source mesh'ssecret_locate/secret_backup/secret_verify/secret_breakglasssubsystem is the prior art it draws on.