Apply review: two credentials, staged admin rotation, a ninth provider
The fact-check found mailu, whose user is its mailbox, so 0114 rotates over two credentials rather than two logins, the adapter choosing what a credential is. Also: minio keeps non-empty buckets; five backends take their admin credential only at first init, so single-party rotation is staged; postgres ownership moves to a non-login role; the harness keys by consumer; rotation state lives with the vault. Consistency fixes across 0110-0113, 26 and 27; issue 103 resolved by mesh-host PR #22.
This commit is contained in:
@@ -152,3 +152,19 @@ With the seat unheld, a build from the seat is refused and says why. External bu
|
||||
**Not yet designed:** a credential for cloning a private repository. The mesh's own repositories are
|
||||
public. The natural place for a clone credential is a `secret` from the vault, and that is a decision
|
||||
still to take.
|
||||
|
||||
## How it is checked
|
||||
|
||||
The rules here are [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md)'s
|
||||
and [ADR 0111](../../02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md)'s, and each is
|
||||
checked as their tables say:
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| The set is closed, and every entry names its decision | 0110: a unit test on the set's size and decisions; manifest tests refusing an unknown seat or the wrong scope. |
|
||||
| A seat is held by one assignment, and only by one whose module can hold it | 0110: resolution tests for a second holder and for a seat the definition does not name. |
|
||||
| A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0110: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. |
|
||||
| Several providers and none local is a person's choice | 0110: an assignment test listing candidates with the seat's holder first and recording the pin. |
|
||||
| `secret` is reserved | 0110: the parser and resolution refusals for another provider and a pin. |
|
||||
| Holdings are derived, and the overview lists every seat | 0110: the `seats` command test, including an unheld seat. |
|
||||
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
|
||||
|
||||
@@ -6,7 +6,7 @@ updated: 2026-09-26
|
||||
decisions:
|
||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
- 02-DECISIONS/0113-the-vault-makes-every-secret.md
|
||||
- 02-DECISIONS/0114-a-shared-credential-rotates-over-two-logins.md
|
||||
- 02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md
|
||||
- 02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md
|
||||
- 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
|
||||
- 02-DECISIONS/0084-which-provider-serves-a-consumer.md
|
||||
@@ -64,8 +64,9 @@ Which module answers, in order:
|
||||
the provider open. It asks for *the mesh's* one, and the mesh answers with whichever assignment
|
||||
holds that seat, with nothing asked of anyone. Unheld, the requirement is refused, naming the seat
|
||||
([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md),
|
||||
[26 — The seats](26-the-seats.md)). A `secret` requirement always names `mesh-vault`, because
|
||||
that provision is reserved;
|
||||
[26 — The seats](26-the-seats.md)). Only a seat that delivers a provision can be named; naming a
|
||||
foundation seat is refused, because it delivers nothing. A `secret` requirement always names
|
||||
`mesh-vault`, because that provision is reserved;
|
||||
2. **a pin**: the assignment names a provider, because this consumer is coupled to that provider's
|
||||
contents ([ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md));
|
||||
3. **the provider on the consumer's own node**;
|
||||
@@ -250,22 +251,28 @@ applied by its provisioner or marked not rotatable by the mesh, and a rotation o
|
||||
than reported done.
|
||||
|
||||
**How old and new change over depends on how many parties hold the credential**
|
||||
([ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-logins.md), on
|
||||
([ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md), on
|
||||
[research 016](../../01-RESEARCH/016-how-a-credential-can-be-rotated/00-overview.md)):
|
||||
|
||||
- **Two parties**, a consumer and its provider, or a module and the broker: each consumer has two logins
|
||||
derived by the mesh, both with its rights over one resource, named after the consumer. The vault makes
|
||||
the new value; each applier ensures the unused login with it and confirms both authenticate; only then
|
||||
are readers given it and recreated; once every reader has confirmed, the old login is retired. Nobody
|
||||
is left without a credential that works, and `status` shows who a rotation waits on.
|
||||
- **One party**, a provider's administrative credential or a module's own secret: in place. Its own
|
||||
provisioner applies it with the old value, or the host recreates it.
|
||||
- **Two parties**, a consumer and its provider, or a module or node's host and the broker: each consumer
|
||||
has two credentials, both with its rights over one resource named after the consumer. For most
|
||||
providers the second is a second login derived by the mesh; where a backend's user is its resource, it
|
||||
is a second token. The vault drives the rotation and records each step. It makes the new value; each
|
||||
applier ensures the unused credential and confirms both authenticate; only then are readers given it
|
||||
and recreated; each reader confirms by authenticating with it; and only then is the old one retired.
|
||||
Nobody is left without a credential that works, a rotation can be abandoned until the old one is
|
||||
retired, and `status` shows who a rotation waits on.
|
||||
- **One party**, a provider's administrative credential or a module's own secret: in place, staged.
|
||||
An applied one is delivered beside the current value, the provisioner changes the backend with the
|
||||
current one, and only then does the new value become current. One read at start is delivered, and
|
||||
the host recreates the module.
|
||||
|
||||
**Retiring a login never removes what it reached.** An adapter keeps *retire a login* and *remove the
|
||||
consumer* apart. Only unassigning removes the resource, and never a rotation. Today the two are one
|
||||
call, and in five providers it deletes the consumer's data, so this separation comes first. An adapter
|
||||
that cannot yet ensure a second login rotates in place, with its window stated, and is listed until it
|
||||
can.
|
||||
**Retiring a credential never removes what it reached.** An adapter keeps *retire a credential* and
|
||||
*remove the consumer* apart, and the harness keys what it applied by consumer, so a changed login is
|
||||
never a removal. The resource is removed only when the consumer no longer requires it from that
|
||||
provider: unassigned, the requirement dropped, or re-resolved elsewhere. Today these are one call, and
|
||||
in five providers it deletes the consumer's data, so this separation comes first. An adapter that cannot
|
||||
yet ensure a second credential rotates in place, with its window stated, and is listed until it can.
|
||||
|
||||
## Refusing
|
||||
|
||||
@@ -314,8 +321,8 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
mesh carries providers' data back.
|
||||
[Issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||
is fixed in the host already. *Ends when* nothing outside the vault generates a shared secret after
|
||||
genesis, a lab consumer of analytics receives its site id, and a database credential rotates over its two
|
||||
logins, the consumer recreated by derivation, never without a working login, and its data intact.
|
||||
genesis, a lab consumer of analytics receives its site id, and a database credential rotates over
|
||||
its two credentials, the consumer recreated by derivation, never without a working login, and its data intact.
|
||||
3. **Definitions move, and seats move to assignments.** Every catalogue definition is rewritten, adopted
|
||||
and running assignments placed where their data already is, and each claim becomes a seat the
|
||||
module can hold, held by the assignment that holds it today. *Ends when* the list of definitions
|
||||
@@ -342,7 +349,12 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
| Only the vault provides `secret` | The parser refuses another provider of it, and resolution refuses a pin on a `secret` requirement. |
|
||||
| A provider's per-consumer secret comes from the vault | A resolution test: requiring a database expands to a secret requirement named for the consumer, answered by the vault and delivered to both recipients. |
|
||||
| Restarts are derived | The tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): an applied secret restarts nothing, and one read at start recreates its reader without a declared restart. |
|
||||
| A two-party credential rotates over two logins | The rotation tests of [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-logins.md): retiring a login leaves the resource intact; readers move only after the applier confirms; an unreachable reader keeps its old login until it returns; a single-party secret rotates in place. |
|
||||
| A two-party credential rotates over two credentials | The rotation tests of [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md): retiring a credential leaves the resource intact; a changed login is never a removal; readers move only after the applier confirms and confirm by authenticating; an unreachable reader keeps its old credential until it returns; rotation state survives a restart; a single-party applied secret is staged. |
|
||||
| A private key is made where it is used | The per-key tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): a node's sealing key, the operator's key and the certificate authority's key never leave where they were made. |
|
||||
| The controller and a node's host take the same path | 0113's tests: the controller's definition declares requirements and no own secret; a node's bus account is made by the vault and delivered sealed to that node. |
|
||||
| Moving the vault or the broker is break-glass | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
|
||||
| A secret that cannot be rotated says so | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
|
||||
| Only the controller reads the seat placeholder | A catalogue test, from phase 3: no definition uses the seat placeholder. |
|
||||
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
|
||||
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`22-the-work-ahead.md`](22-the-work-ahead.md) | Everything decided and not yet built, in dependency order, each phase ending at a run | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0075](../../02-DECISIONS/0075-two-stores-and-which-provides-what.md), [ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md) |
|
||||
| [`23-choosing-a-provider.md`](23-choosing-a-provider.md) | Which of several providers of a kind serves a consumer, and when a module carries its own instead | [ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md), [ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md) |
|
||||
| [`24-the-secrets-vault.md`](24-the-secrets-vault.md) | The module that owns a secret — a `secret` provision, and the boundary of what it owns | [ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md), [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md), [ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md) |
|
||||
| [`26-the-seats.md`](26-the-seats.md) | What a mesh can have one of, who fills each, and a seat's holder answering for the provision it delivers — including the `git` seat a build's source can live on | [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [ADR 0111](../../02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md), [ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md) |
|
||||
| [`27-a-module-requires-the-mesh-resolves.md`](27-a-module-requires-the-mesh-resolves.md) | **Proposed.** One concept for everything a module needs: a requirement with a contract, answered by one of four kinds of provider, resolved at assignment or refused. Retires settings, placeholders, facts and paths in definitions | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md), [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md) |
|
||||
| [`26-the-seats.md`](26-the-seats.md) | **Proposed.** What a mesh can have one of, who fills each, and a seat's holder answering for the provision it delivers — including the `git` seat a build's source can live on | [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [ADR 0111](../../02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md), [ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md) |
|
||||
| [`27-a-module-requires-the-mesh-resolves.md`](27-a-module-requires-the-mesh-resolves.md) | **Proposed.** One concept for everything a module needs: a requirement with a contract, answered by one of four kinds of provider, resolved at assignment or refused. Retires settings, placeholders, facts and paths in definitions | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md), [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md), [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user