Merge pull request 'ADR 0158: a provider with one credential shares it with every consumer, and the vault remakes it for all at once' (#246) from decision/0158-a-provider-with-one-credential-shares-it into main

This commit was merged in pull request #246.
This commit is contained in:
2026-10-01 10:18:11 +00:00
4 changed files with 134 additions and 2 deletions
@@ -0,0 +1,112 @@
---
topic: the mesh
status: accepted
date: 2026-10-01
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md
---
# 158. A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once
## Context
[ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) gave every consumer of a
provision its own credential: the mesh mints one per pair, the provider's own code creates the
login, and rotating one consumer's touches nothing else. That is right for a database, a broker, an
object store — software that can hold many logins.
The media software on the home server cannot. A download client has one web password; an indexer
has one API key; each of the library managers has one key in its configuration; the media server
holds one token issued elsewhere. There is no login per consumer to create, so
[ADR 0113](0113-the-vault-makes-every-secret.md)'s only remaining form applied: the value is
*accepted*. On 2026-10-01 the home server held forty-seven accepted own secrets and twelve accepted
pair credentials, every one rotatable only by a person changing the software by hand and accepting
the new value, and one pair credential sat *made* and wrong because nobody could accept the real one.
The operator asked for every password in the vault and rotatable, and for a library manager's
definition to receive the download client's credential and address through provisioning like
anything else (filed as the forge's issue 243 on this repository).
The address half already works: the library manager requires the download client's API provision,
the provider serves scheme, port and user name, and the binding carries them. Only the credential
half had no form.
## Considered Options
1. **Keep accepting.** Honest about what the software can do and what the mesh cannot, and it is
the state the home server was in: nothing rotates, a consumer added later needs a person, and an
unknown predecessor password stays unknown for ever.
2. **Put a login per consumer in front of the software.** A proxy that holds the one credential and
issues many. A second service per provider, with its own credential to keep, to make the mesh's
model fit software that does not share it.
3. **Let the provider say its one credential is the credential.** An offer names which of the
provider's own secrets *is* what every consumer receives. The vault keeps one record, sealed to
the provider's machine, every current consumer's machine and the operator, and because it stores
no plaintext it cannot seal an existing value to a later consumer — so it **remakes the value
for all of them at once** whenever the set of consumers changes or a rotation is asked. The
provider takes it the way an own secret is taken ([ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md),
issue 180); consumers read it at start.
## Decision
**Option 3.** A provider whose software holds one credential shares that credential, and the mesh
owns its whole lifecycle.
- **The offer says so.** `{"name": "download-client-api", "credential": {"own": "password"}}` on a
provider's `provides` entry names one of its own secrets as the credential of that provision. The
named own secret must say how it is taken (`taken: at-start` or `taken: applied`); an offer
naming an undeclared or untaken secret is refused at parse.
- **One record, many seals.** The vault keeps one value per (provider assignment, provision). It is
sealed to the provider's machine, to each consumer's machine that currently binds the provision,
and to the operator. Every consumer's binding file carries the provider's one user name and the
secret file carries the shared value; the shape a consumer reads is the pair credential's, so a
consumer's definition does not know whether its credential is shared.
- **Remade for all, together.** When a consumer binds or unbinds, or `secret rotate` is asked on the
provider's own secret, the vault makes a new value and seals it to every current holder in one
act, and the mesh sends every holding machine. The provider restarts on the new value or applies
it at start; each consumer restarts on it. There is no window between two credentials, because
there is one credential; there is the restart, stated as the cost below.
- **An accepted shared value is sealed to everyone the moment it is accepted.** `secret accept` on
the provider's own secret is the one moment the mesh holds the plaintext, and it seals copies for
every current consumer then. It is not remade afterwards ([ADR 0113](0113-the-vault-makes-every-secret.md)):
a consumer that binds later is refused until the value is accepted again, in words that say so.
- **A value the software issues itself stays accepted.** A token the media server obtains from its
vendor cannot be set by the mesh; its provision keeps the accepted form until a module can deliver a
value it did not mint to the vault, which this record does not build.
- **Nothing changes for software that holds many logins.** ADR 0048's form stays the default; this
is the form for an offer that says it has one credential.
## Consequences
- The media stack's six providers stop needing a person per consumer. A library manager binding
the download client gets a working credential the mesh made, and an unknown predecessor password
is replaced by one the mesh knows, recoverable with the operator's key.
- **Adding or removing a consumer restarts every consumer of that provision and the provider.**
That is the price of one credential, and it is paid when a definition binds, not at an hour of
nobody's choosing. It is stated in the plan's words when it happens.
- Rotation of a shared credential is [ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md)'s
single-party form across several machines: in place, all holders sent together. The staged form
for a backend that takes its credential once is still not built, and a provider whose own secret
says `applied` refuses rotation by name until it is.
- The vault can name who holds a shared value — the copies are the record — so *who has this* stays
a query, as design 13 requires.
- The accepted count on the home server becomes a list that shrinks, provider by provider, as each
one's start applies the file.
## How this is checked
| Rule | Checked by |
|---|---|
| An offer may name one of its own secrets as its credential; an undeclared or untaken secret is refused at parse | manifest tests |
| A consumer of a shared provision receives the provider's value as its pair credential, under the provider's one user name | resolver and declaration tests |
| The record is sealed to the provider, every current consumer and the operator; a consumer binding or unbinding remakes it for all | inventory tests against a raised store |
| Rotating the provider's own secret remakes every holder's copy, and an accepted value is sealed to current consumers once and not remade | inventory tests |
| Live: a library manager on the home server binds the download client with a value the mesh made, the client takes it at start, and a rotation through the console reaches both | done by hand after the media catalogue's providers apply the file at start |
## References
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) — extended: the per-consumer form stays the default; this is the form for one credential
- [ADR 0113](0113-the-vault-makes-every-secret.md), [ADR 0114](0114-a-shared-credential-rotates-over-two-credentials.md) — the accepted form and the single-party rotation this rests on
- [Issue 180](../04-ISSUES/180-a-modules-own-secret-cannot-be-rotated/00-report.md) — the `taken` word and the rotation this reuses
- [Design 24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md), [Design 13 — Credentials and their rotation](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md)
- The forge's issue 243 on this repository, where the operator's ask and the home server's count were recorded
+1
View File
@@ -171,6 +171,7 @@ python3 00-META/checks/index.py fail if stale
- **0154** — [The mesh's own verbs are the mesh-controller seat's tools, and which verbs those are](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)
- **0156** — [An artifact is what a build produces, the artifact store serves every kind, and its seat is named for its scope](0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)
- **0157** — [A build says what it does on the bus, as it happens](0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md)
- **0158** — [A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once](0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)
### Its tiers, from the bottom up
@@ -145,5 +145,7 @@ and the operator, and sends the machine, so the module starts again on it. A sec
is refused with the word to write, because a credential rotated under software that never reads it
again is the fault of issue 179 made deliberately; an applied one is refused until the staged form is
built; an accepted one is refused as ADR 0113 says. `rotate` is a verb on the controller's seat with
both shapes, so the console asks for either. *How it is checked:* the tests named in issue 180, and a
both shapes, so the console asks for either. A provider that shares its one credential with every
consumer ([ADR 0158](../../02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md))
rotates the same way, with every holder's copy remade and every holding machine sent together. *How it is checked:* the tests named in issue 180, and a
live rotation through the console of a secret a module reads at start.
+18 -1
View File
@@ -2,8 +2,9 @@
layer: to-be
status: implemented
code: [mesh-catalog, mesh-controller, mesh-host]
updated: 2026-09-21
updated: 2026-10-01
decisions:
- 02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md
- 02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md
- 02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md
- 02-DECISIONS/0085-a-secret-is-a-provision.md
@@ -127,6 +128,22 @@ credential a provider grants; the export names each entry by the node and module
the name they know it by, and says whether it is a module's own secret or a pair credential, so
recovery addresses both alike.
### A provider with one credential
*Decided 2026-10-01 ([ADR 0158](../../02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)); to be built.*
Software that holds one credential — a download client's web password, an indexer's one API key —
cannot give each consumer a login, so ADR 0048's form does not fit it and its values were accepted
by hand. An offer may now say `"credential": {"own": "<secret>"}`: the provider's own secret *is* the
credential every consumer of that provision receives, in the shape of an ordinary pair credential,
under the provider's one user name. The vault keeps one value per provider assignment and provision,
sealed to the provider's machine, each consumer's machine and the operator; because it holds no
plaintext it remakes the value for every holder at once when a consumer binds or unbinds or a
rotation is asked, and the mesh sends every holding machine together. The provider takes it as it
says it takes its own secret (`taken`, issue 180); consumers read it at start. An accepted value is
sealed to the consumers of the moment and not remade; a consumer that binds later waits for the next
acceptance. *How it is checked:* the rows of ADR 0158's table, once built.
## Beyond generate and hold
Owning a secret means owning more than its creation. The mesh being migrated onto has a working