--- topic: what runs on it status: accepted date: 2026-09-20 amended: 2026-09-20 deciders: jochen reconstructed: false extends: 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](0031-the-control-plane-authenticates-nobody.md) 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](0078-the-store-and-broker-are-modules.md) 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](0048-a-provider-creates-the-credential-the-mesh-minted.md)); - 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](0084-which-provider-serves-a-consumer.md)), 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 1. **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. 2. **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. 3. **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](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md)); this record does not disturb it. 4. **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](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) 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](0048-a-provider-creates-the-credential-the-mesh-minted.md)): 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](0084-which-provider-serves-a-consumer.md)): 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](0048-a-provider-creates-the-credential-the-mesh-minted.md) 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](0078-the-store-and-broker-are-modules.md) 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](0084-which-provider-serves-a-consumer.md)) 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](0048-a-provider-creates-the-credential-the-mesh-minted.md) 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](0031-the-control-plane-authenticates-nobody.md) — identity is a module; this is the same move for secrets. - [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) — the provisioning-credential path, left unchanged. - [ADR 0078](0078-the-store-and-broker-are-modules.md) — the de-specialisation this completes. - [ADR 0084](0084-which-provider-serves-a-consumer.md) — the node-scoping the vault obeys. - [issue 068](../04-ISSUES/068-secrets-have-no-owning-module/00-report.md) — the gap. - [`03-DESIGN/01-to-be/24-the-secrets-vault.md`](../03-DESIGN/01-to-be/24-the-secrets-vault.md) — the design. The source mesh's `secret_locate` / `secret_backup` / `secret_verify` / `secret_breakglass` subsystem is the prior art it draws on.