Design 24 flips to in-progress with mesh-catalog as its owner (playbook 04). Starting the build surfaced two gaps the decision did not settle: a module requiring `secret` receives exactly one value (069), and no command can accept an operator's value into a consumer↔vault pair (070). Both opened as issues rather than improvised around. Also fills fixed-by on 067 and 068, which the cycle check refused as resolved with no reference.
5.9 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | ||||
|---|---|---|---|---|---|---|---|---|
| to-be | in-progress |
|
2026-09-20 |
|
24 — The secrets vault
Identity runs on the mesh, not of it — a module other modules require, not a part of the
controller (ADR 0031). The
store and the broker are ordinary modules too
(ADR 0078). Secrets are the piece
that never got the same treatment: nothing owns a secret. This document describes the module that
does — a vault that provides a secret provision — and, as importantly, the boundary of what
it owns and what it deliberately does not.
Three kinds of secret, and which the vault owns
A mesh handles three species of secret, and confusing them is how the current arrangement went wrong.
- A provisioned credential is the login one module uses against another — a database password, a broker account. The mesh mints it per consumer↔provider pair and seals it to the machines that must hold it (ADR 0048), and its provider makes it true. This is not the vault's, and the vault does not change it. It already has an owner and a rotation (13); disturbing it would buy nothing.
- A module's own secret is a value a single module needs for itself — the password of a store it runs privately, an internal signing token. Today the mesh generates this as a value nothing owns, and so it cannot be rotated. This is the vault's.
- An operator-delivered secret is a value only a person can supply — a credential for something outside the mesh. Today it is sealed in and then held, un-audited and un-rotatable. The vault holds this, and can hand it out and audit it, though it cannot generate it.
The vault, then, is the provider a module turns to for a secret that is not the byproduct of some other provision. It is the answer to "this module needs a password, and there is no provider whose job it is to give it one."
A secret as a provision
A module that needs a secret for its own use requires a secret provision, exactly as it requires
a database from the store. The vault generates the value — or takes custody of one an operator
delivered — and the credential belongs to the consumer↔vault pair. Because it is an ordinary pair
credential, everything already built for pair credentials applies to it unchanged: it rotates
with the one command that discards a credential and delivers both ends together, it is one secret
per holder so rotating one touches nothing else, and who holds this is a query rather than an
assumption (13). The rotation a module's own secret lacks
today is not new machinery; it is the machinery that already moves a database password, pointed at
a secret the vault provides.
This is what replaces the "generated value that nothing owns". A module's own password stops being a special kind of thing injected by the synchroniser and becomes a provision with a provider, a holder, and a lifecycle — the same shape as everything else the mesh grants.
The vault is a module, and node-scoped
The vault is an ordinary module. A mesh that wants one runs it; a mesh whose modules require no secret of their own runs none — the same test identity meets (ADR 0031). It is provisioned the ordinary way, and it does not mint the mesh's delivery credentials — the controller does that, because the controller must mint in order to deliver any provision, the vault's own included. The vault mints secrets for modules, downstream of its own existence, never the credential that delivers it.
Like every provider it is node-scoped (ADR 0084, 23): each node may run its own vault, and a module's own secret is held by the vault on the module's node, selected the same way any provider is. There is no single mesh vault holding everything, for the same reason there is no single mesh store.
Beyond generate and hold
Owning a secret means owning more than its creation. The mesh being migrated onto has a working secrets subsystem whose surface names the operations a vault is responsible for — locating a secret, backing it up, verifying it is what it should be, and a break-glass recovery for when the normal path is unavailable. These become the vault module's, specified against it rather than scattered.
One of them is left open on purpose. Break-glass must not reintroduce a key that one place holds. The whole point of sealing a secret to the machine that needs it, asymmetrically, is that no single place can open everything (ADR 0048); a recovery path that keeps a master key would undo exactly that. How the vault lets an operator recover a secret without becoming the thing the sealing was designed to prevent is a question this design opens and does not yet answer.
What this changes for a module
A module that today writes a password into its own composition — an embedded store's login, an internal token — instead requires it from the vault and reads it where the mesh puts it. The change is taken module by module, not as a flag day, and it is the same change in each: a value the module authored becomes a value the vault provides. When it is done, the guarantee the as-is design already makes for generated secrets — nothing in the repository contains a credential — holds for a module's own secrets not by convention but because there is a provider whose job it is to keep them.