Files
hq/02-DECISIONS/0113-the-vault-makes-every-secret.md
T
jochen 1b5f2c2c1a ADR 0113 and to-be 27: address the review of the vault rework
Two decisions taken with the author:
- Genesis delivers and the vault adopts. The vault cannot run first — it is built on the runtime base
  the installation makes after the store, broker and controller, and it learns its work over the bus.
  Genesis generates the foundation's first shared secrets, seals them to the operator key, and
  delivers them to the vault through the path an operator's value takes; from then on the vault holds
  and rotates them. This answers ADR 0085's own reason for rejecting vault-only minting, which 0113
  now names instead of stepping around.
- Rotation re-confirms on every pass. An applier repeats its confirmation until acknowledged, so a lost
  message costs one pass; an applier that stops after applying locks readers out until its supervised
  restart, and that window is stated and shown, not claimed away.

Fixes:
- Scope: a shared secret is made by the vault; a private key (node sealing keys, the operator's key,
  the certificate authority) is made where it is used. The inventory adds the makers the first version
  missed: node and builder broker passwords, and enrolment tokens.
- Broker accounts are created by the broker's provisioner, not the controller, so the controller never
  holds their plaintext; mesh-broker delivers amqp again — one broker per mesh — and only mesh-store
  delivers nothing.
- secret is a reserved provision: only the mesh-vault holder may provide it, and no pin routes around it.
- A secret's contract says whether a recipient applies it or reads it at start; appliers are never
  restarted for it, init-only secrets are applied, and confirmation is to-be 13's standard.
- Operator secrets are one rule everywhere: a secret requirement answered by the vault (0112 no longer
  says otherwise). A data provider's adapter may return fields; the data-return check names a lab consumer.
- 'Holder' now means a seat's holder only; a secret has recipients.
2026-09-25 23:27:58 +02:00

16 KiB

topic, status, date, deciders, reconstructed
topic status date deciders reconstructed
what runs on it proposed 2026-09-25 jochen false

113. The vault makes every shared secret, a provider makes resources and data, and the mesh carries both

Context

A shared secret comes into being many different ways today, counted across the catalogue and the controller on 2026-09-25:

kind made by used by
a credential between a consumer and a provider the controller 19 modules
a module's own secret (own-secrets) the controller, as a random value nothing owns 54 modules
a module's broker account the controller, but only when a person runs a separate command; otherwise the random value above, which cannot work (issue 095) 49 modules
a node's and the builder's broker accounts the controller, each in its own code path every node, the builder
an enrolment token the controller every node joining
a secret from the vault the controller mints it, and the vault only records it (ADR 0085, as amended) 6 modules
a value an operator accepts a person (ADR 0092) where accepted
a licence for model access a separate controller context with its own store model consumers
the foundation's root secrets genesis, sealed to the operator key the foundation

The vault was built to end the second row, and did not. ADR 0085 says a module's own secret "stops being a generated value that nothing owns". 54 modules still use one, and 6 use the vault. The replacement was added and the old path was never retired.

ADR 0085 considered and rejected making the vault the only maker, because "the controller must mint in order to deliver any provision — the vault's own credential among them": the vault cannot make the credentials that exist before it does. That objection is real, and this record has to answer it rather than step around it.

Rotation has gaps. To-be 13 makes rotation one command, all-or-nothing, with a stated window in which a consumer cannot authenticate. A consumer restarts only if its definition remembered to say so; a container fed by an env-file is not recreated when that file changes (issue 103); and some secrets are read only when a service first initialises, where a restart changes nothing.

And providers cannot answer with data. ADR 0048 left "delivering provider-generated data back to a consumer" to a separate decision. The analytics provider's site id and the DNS provider's record have no way back, and say so in their code.

Considered Options

1. Keep the controller minting, and tidy the paths. Rejected. The paths are the problem: each is made, kept, rotated and audited differently, and tidying keeps them all.

2. Every provider mints its own secrets, with one shared function in the SDK. Rejected. Generation becomes uniform, but custody stays spread over every provider's machine, so rotation, audit and the operator's break-glass copies cover only some secrets. Each SDK language needs its own implementation.

3. Raise the vault first at genesis, so it makes even the first secrets. Rejected. The vault is built on the shared runtime base, which the installation makes only after the store, the broker and the controller exist, and the vault learns what to answer from the controller over the bus. Running it first means reordering the whole installation and giving the vault a second way of being asked.

4. The vault makes every shared secret; genesis delivers the first ones to it. Chosen. It answers 0085's objection with a mechanism the mesh already has: a value delivered to the vault.

Decision

There are two kinds of secret, and each has one rule.

  • A shared secret is a value more than one party must hold: a password, a token, an API key. The vault makes every one. Nothing else in the mesh generates a shared secret.
  • A private key is made where it is used and never leaves: a node's sealing key, the operator's key, the mesh's certificate authority. This is not a second way of making secrets. A private key any other party ever held would no longer be private.

Every shared secret is a secret requirement, answered by the vault:

  • a credential between a consumer and a provider. A provision's contract declares for each consumer, one secret, and resolution expands it into one requirement per consumer. So gitea requiring a database makes the database's provider require a secret for gitea, and the vault answers it. The provider's own code does not change: it is handed a login and a password, as today;
  • a module's own secret. own-secrets is retired;
  • every broker account: a module's, a node's, the builder's. The broker delivers amqp through its seat (ADR 0110), and the broker's own provisioner creates each account from the vault's secret, like any provider. The controller no longer creates accounts, and there is no separate command to forget;
  • an enrolment token;
  • a secret operator value, such as an external API key, which the operator delivers to the vault (ADR 0092). A licence's credential is one of these. What the licences context adds, refreshing a token, is provider behaviour, decided in its own record.

Only the vault may provide secret. A module providing it must hold the mesh-vault seat, and the parser refuses one that does not. A pin cannot route a secret requirement anywhere else, because there is nowhere else.

A secret has recipients, and the vault delivers to each. The database credential has two: the provider, which applies it by creating the login, and the consumer, which presents it. The vault hands the value to the mesh sealed to each recipient's node. The controller and the broker carry sealed values they cannot open.

Genesis delivers, and the vault adopts. The foundation's first shared secrets exist before the vault can run: the store's superuser, the broker's admin in the hashed form the broker needs, the bus accounts of the temporary controller and of the vault itself, and the first enrolment token. Genesis generates these, seals them to the operator key as today, and delivers them to the vault when the vault is installed, through the same path an operator's value takes. From then on the vault holds, audits and rotates them. Unlike an operator's external key, the vault can make their replacements, so they are delivered but replaceable. Genesis is the only thing besides the vault that ever generates a shared secret, once, before the vault exists, and it hands them over.

A provider makes resources and data, and the mesh carries data back. A provider's adapter may answer with its contract's non-secret fields: a site id, a registered name. The mesh delivers them to the consumer as resolved values. Who a consumer is stays the mesh's: a provider makes what a consumer is given, never what it is called (ADR 0049).

Rotation

It is asked of the vault, by an operator or by the vault's policy, such as a maximum age in the secret's contract. A delivered value the vault cannot replace, such as an external API key, is not rotated by the vault: rotating it means an operator delivering a new one.

A secret's contract says how each recipient takes a new value:

recipient takes it by example what happens on rotation
applying it a provider creating the login; the broker's provisioner updating an account; the store's own provisioner changing its superuser its provisioner applies the new value; it is never restarted for it
reading it at start a consumer reading its password when it starts the host restarts it, or recreates a container whose env-file carries it

A secret read only when a service first initialises cannot be rotated by a restart. Its contract marks it applied, and a provisioner makes the change. The host derives which recipients read a secret at start from the requirement their definition reads it through, so no definition declares a restart for a secret.

It is applier-first. The vault delivers the new value first to the recipients that apply it. Each applies it, verifies that the new value authenticates and the old one no longer does, and confirms. It repeats that confirmation on every reconcile pass until the vault acknowledges it, so a lost message costs one pass. Only after every applier has confirmed does the vault release the value to the recipients that read it at start.

The remaining window is stated. If an applier applies the new value and its provisioner stops before confirming, the recipients that present the secret are locked out until the provisioner runs again, because the old value no longer works and they have not been sent the new one. A provisioner is supervised and restarted when it exits, so the window is bounded by that restart. The mesh shows the rotation as waiting on that applier for as long as it lasts, never as done.

It is confirmed. A rotation is shown as unconfirmed until every applier has confirmed, and every recipient that reads at start has restarted with the new value and passed its health check, where its definition declares one.

step who
asks an operator, or the vault's policy
makes the value the vault
carries it the controller, sealed, appliers first
applies it each applier's provisioner, which verifies and confirms on every pass until acknowledged
takes it at start the host, restarting or recreating what reads it
confirms it applier confirmations, then restarts and health checks, shown in status

What this changes in earlier records

On acceptance, each of these is superseded or amended by this record, not edited:

  • ADR 0048 is superseded: the controller no longer mints a provider's credential; the vault makes it. That a provider is handed its credential and seals nothing stands.
  • ADR 0085 is amended: the vault makes every shared secret, own secrets are retired, and its rejection of vault-only minting is answered by genesis delivering the first secrets. "The vault stores no plaintext, ever" stands.
  • ADR 0092 is amended: an operator delivers a secret to the vault, and genesis delivers the foundation's first secrets the same way.
  • ADR 0043 is amended: a broker account is created by the broker's provisioner, not the controller. Its scoping stands.
  • To-be 13, to-be 21 and to-be 24 are amended: rotation is applier-first, derived and confirmed; genesis delivers its secrets to the vault; the vault is the only maker.
  • Issue 103 becomes a prerequisite: rotation cannot be trusted while a changed env-file leaves a container on its old value.

Consequences

  • The vault is on the path of every new or rotated shared secret. Today the controller holds that place, on the same node. A secret can no longer be made while the vault is down.
  • Resolution expands per-consumer requirements from a provision's contract. The contract declares them, never the provider's code, so what a provider requires stays predictable from the catalogue.
  • The SDK's provider loop gains repeated confirmation of an applied rotation. A credential provider's adapter is unchanged. A data provider's adapter gains a return value.
  • The broker's provisioner gains every bus account, and the controller loses five separate places it generates a secret today.
  • 54 modules move from own secrets to vault requirements.
  • What got harder: a rotation waits for its appliers, and an applier that stops mid-rotation locks presenters out until it restarts. Both are shown, not hidden, and the second is bounded by a supervised restart rather than by someone noticing.

How it is checked

Rule Checked by
Only the vault generates a shared secret after genesis A controller test: no code path generates a shared secret. An installer test: genesis generates exactly the foundation's first secrets and delivers them to the vault.
A private key is made where it is used A test per key: a node's sealing key never leaves the node, and the operator's private key never enters the mesh.
Only the vault provides secret The parser refuses a module providing secret without holding mesh-vault, and resolution refuses a pin on a secret requirement.
A provider's per-consumer secret comes from the vault A resolution test: a consumer requiring a database expands to a secret requirement for it, answered by the vault and delivered to both recipients.
Values are carried sealed A controller test: each recipient's copy opens with that recipient's node key and no other; neither the controller nor a message on the broker can open one.
Own secrets are retired A catalogue test: no definition declares an own secret, with a declared list of exceptions that shrinks to empty.
Bus accounts come from the broker's provisioner A resolution test: assigning a module that speaks on the bus yields its account, created by the broker's provisioner with no separate command.
An irreplaceable delivered value is not rotated by the vault A vault test: a rotation request on an operator's external key is refused, naming the operator as its source.
Rotation is applier-first and re-confirmed A rotation test: presenters are not sent the new value until every applier confirms, and a confirmation lost in transit is repeated on the next pass.
Restarts are derived from how a secret is read A host test: a secret read at start restarts its reader, and recreates a container whose env-file carries it; an applied secret restarts nothing.
Rotation is confirmed A rotation test: an applier confirms only after the new value authenticates and the old one does not, and the rotation shows unconfirmed until every reader restarted and passed its health check.
A provider answers data back A lab test with a consumer requiring analytics: the provider's site id reaches it as a resolved value.

References