Files
hq/02-DECISIONS/0085-a-secret-is-a-provision.md
T
jschoubben fc4ab370d6 Graduate issues 067 and 068 to decisions and to-be designs
ADR 0084 (extends 0027) — a provision is served by a node-scoped provider the
consumer selects, defaulting to co-location; a module may instead carry a private
embedded instance that is not a provision. Design: 01-to-be/23-choosing-a-provider.

ADR 0085 (extends 0031) — a secret is a provision and the vault is the module that
provides it; a module's own local secret becomes an ordinary pair credential that
rotates through the existing machinery, while the controller's provisioning-credential
mint (0048) is unchanged. Design: 01-to-be/24-the-secrets-vault; doc 13 amended to
cross-link the non-pair secret.

Issues 067/068 marked resolved with amended-design set. ADR index regenerated;
records and index checks pass.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-20 21:27:29 +02:00

6.6 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 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

  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); 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 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.

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's secret_locate / secret_backup / secret_verify / secret_breakglass subsystem is the prior art it draws on.