Research 016: survey how each provider can rotate a credential

Overlap as drafted in 0113 would have deleted consumer data: seven of
eight providers name the resource after the login and five drop it on
remove. Rotation is now undecided in 0113 and to-be 27, pending the
survey. Also: a requirement naming a seat resolves to its holder, a
person chooses among remaining candidates at assignment, the controller's
secrets are requirements of its definition, genesis seals to the
control-node key, and moving the vault or broker is break-glass.
This commit is contained in:
jochen
2026-09-26 00:14:23 +02:00
parent 805df3f81e
commit 942ebe350f
9 changed files with 422 additions and 199 deletions
@@ -74,19 +74,23 @@ node's settings for it, and what it serves. Holdings are not stored separately.
assignment, and a second record of the same fact would be a second thing to disagree with the first.
**Holding a seat may deliver a provision.** A seat that delivers a provision may only be held by an
assignment of a module that provides it, at the seat's scope. A requirement for that provision
resolves, in order, to:
assignment of a module that provides it, at the seat's scope.
1. a provider the consumer's node was **pinned** to, a consumer coupled to one provider's contents;
2. **the holder of the seat**, **even when another provider runs on the consumer's own node**;
3. otherwise refused, naming the unheld seat.
**A requirement may name a seat, and then the seat's holder answers it.** Naming the seat asks for
*the mesh's* one, not for whichever provider is nearest, so the holder answers **even when another
provider runs on the consumer's own node**, and nothing is asked of anyone. Unheld, the requirement is
refused, naming the seat. A builder asks for the mesh's npm registry this way, and is served by the
holder of `npm-package-registry` wherever it runs, with no pin on any machine.
**Co-location does not apply to a provision a seat delivers.** A seat exists to say *which one is the
mesh's*, and co-location answering first would let any second provider on a consumer's machine take
over silently for that consumer. That is the failure [issue 106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md)
names for the vault. This keeps [ADR 0009](0009-modules-and-the-graph.md)'s rule that a requirement with
several answers is never guessed: the seat is the choice made once, mesh-wide, by assigning the holder,
instead of once per consumer by pinning.
**A requirement that names no seat resolves as [ADR 0084](0084-which-provider-serves-a-consumer.md)
has it**: a pin, then the provider on the consumer's own node, then the only provider. Where several
remain and none is local, **a person chooses when the module is assigned**. Assignment lists the
candidates, with the holder of a seat that delivers the provision suggested first, and records the
answer on the assignment as its pin. Without an answer the module is not assigned. This keeps
[ADR 0009](0009-modules-and-the-graph.md)'s rule that a requirement with several answers is never
guessed. The choice is made either by the requirement naming the seat, or by a person at assignment,
and never silently by what happens to run nearby. That is the failure
[issue 106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md) names for the vault.
**A seat delivers a provision only where the mesh has one answer for everyone.** The artifact store,
the npm registry, git and the vault are each one per mesh by their own records, so their seats
@@ -95,13 +99,15 @@ deliver them.
**The foundation's seats deliver nothing.** `mesh-controller`, `mesh-store` and `mesh-broker` name
which assignment the mesh *itself* uses: the controller, the store holding its records, the broker
carrying its bus. The store and broker modules may run on other nodes too, and a database or `amqp`
consumer is served by co-location from whichever runs on its own node, the seat's holder included.
Were `mesh-store` to deliver, every database consumer on every node would be sent to one machine.
consumer that names no seat is served by co-location from whichever runs on its own node, the seat's
holder included. Were `mesh-store` to deliver, a consumer could name it and be sent to the store the
mesh keeps its own records in. That is not a store for consumers.
**A seat may reserve its provision.** Where a second provider would break the reason the provision
exists, only an assignment holding the seat may provide it at all: the parser refuses a definition
that provides it without being able to hold the seat, resolution refuses an assignment providing it
without holding the seat, and a pin cannot choose anyone else. `secret` is the one reserved provision.
without holding the seat, and a pin cannot choose anyone else: a requirement for it always names the seat. `secret` is the one
reserved provision.
The vault is one per mesh because a second *"would be a second place to lose"*
([ADR 0085](0085-a-secret-is-a-provision.md), as amended), and a second `secret` provider is exactly
that, whether a pin chose it or not.
@@ -150,9 +156,13 @@ On acceptance, each of these is amended by this record, not edited:
- [ADR 0009](0009-modules-and-the-graph.md): a claim in a definition says a module *can* hold a seat;
the assignment says it does.
- [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md): "a mesh runs one postgres
and one lavinmq" becomes one holder of `mesh-store` and one of `mesh-broker`. The store and broker
modules may run on other nodes.
- [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md) and
[ADR 0078](0078-the-store-and-broker-are-modules.md): "a mesh runs one postgres and one
lavinmq" becomes one holder of `mesh-store` and one of `mesh-broker`. The store and broker modules may
run on other nodes.
- [ADR 0084](0084-which-provider-serves-a-consumer.md): a requirement may name a seat, which its holder
answers; and where several providers remain and none is local, the choice is asked when the module is
assigned and recorded as a pin, rather than refused until someone pins it.
## Consequences
@@ -163,8 +173,9 @@ On acceptance, each of these is amended by this record, not edited:
- Manifest validation refuses an unknown seat, a seat named at the wrong scope, and a delivering seat
named by a module that does not provide the provision. Resolution refuses a second holder, and an
assignment holding a seat its module cannot hold.
- Resolution prefers the seat's holder for a provision it delivers, after a pin. A provider record
gains the module it came from.
- Resolution answers a requirement naming a seat with its holder. Assignment asks a person where
several providers remain, suggesting the seat's holder first, and records the answer as a pin. A
provider record gains the module it came from.
- A `seats` command lists the set with each seat's holder, derived from assignments.
- **What got harder:** a module wanting a new singular role can no longer invent a name. It needs a
record. And an assignment has one more thing to say. Both are the point.
@@ -179,8 +190,9 @@ On acceptance, each of these is amended by this record, not edited:
| A seat outside the set is refused | Manifest-validation tests for an unknown seat, the wrong scope, and a delivering seat named by a module that does not provide it. |
| Every module in use names a seat in the set | A controller test parses every catalogue manifest and fails on any refused seat. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). |
| A seat is held by one assignment, not by a module | A resolution test: the store module assigned to two nodes resolves, with one assignment holding `mesh-store`; a second assignment asking to hold it is refused. |
| The holder answers for a provision its seat delivers | Resolution tests: two providers with the seat held; a pin overriding the seat; a second provider on the consumer's own node, where the holder still answers; the seat unheld with two providers, and with **one** provider, both refused naming the seat. |
| The foundation's seats route nobody | A resolution test: with the store module on two nodes, a database consumer is served by the one on its own node, whichever holds `mesh-store`. |
| A requirement naming a seat is answered by its holder | Resolution tests: two providers with the seat held; a second provider on the consumer's own node, where the holder still answers; the seat unheld with two providers, and with **one** provider, both refused naming the seat. |
| Several providers and none local is a person's choice | An assignment test: the candidates are listed with the delivering seat's holder first; the answer is recorded as a pin; with no answer the module is not assigned. |
| The foundation's seats route nobody | A resolution test: with the store module on two nodes, a database consumer is served by the one on its own node, whichever holds `mesh-store`; a requirement naming `mesh-store` is refused, because it delivers nothing. |
| A reserved provision has no other provider | The parser refuses a definition providing `secret` that cannot hold `mesh-vault`; resolution refuses an assignment providing it without holding the seat, and a pin on a `secret` requirement. |
## References
@@ -71,8 +71,9 @@ unresolved requirement and what could answer it, all at once.
| **the operator, through the assignment** | a value a person chooses that is not secret: a public name, a greeting, a number of workers | settings, carried literals |
A module provider is chosen as [ADR 0084](0084-which-provider-serves-a-consumer.md) and
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say: a pin, then the holder of a
seat that delivers the provision; for a provision no seat delivers, co-location and then the only one.
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md) say. A requirement naming a seat is
answered by its holder. Otherwise it is a pin, then co-location, then the only provider, and where
several remain, a person chooses at assignment and the choice is recorded as a pin.
A host provider is always the module's own node, because a host path or a port means nothing on any
other. An operator value is the assignment's, or the requirement's default, or unresolved.
@@ -133,6 +134,11 @@ On acceptance, each of these is amended by a record of its own, not edited:
path moves from the definition to the assignment.
- [ADR 0091](0091-a-mount-is-declared-three-ways.md): a mount's host side is a resolved requirement,
checked as resolved rather than as a path the definition declares.
- [To-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md): the step that builds and runs a
store module as a database provider, beside the foundation's store on the same node, would run the
store module twice on one node. The adopted store module ([ADR 0078](0078-the-store-and-broker-are-modules.md))
holds `mesh-store` and serves that node's database consumers by co-location, so there is no second
one.
- The [glossary](../00-META/glossary.md): *provision* widens from "a service one module provides" to a
requirement answered by any of the four providers, and *requirement* and *contract* are added. None
of it lands while this record is only proposed, because the glossary is the authority on the words
@@ -36,9 +36,9 @@ 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](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md));
and some secrets are read only when a service first initialises, where a restart changes nothing.
a container fed by an env-file was not recreated when that file changed
([issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md),
since fixed in the host); and some secrets are read only when a service first initialises, where a restart changes nothing.
**And providers cannot answer with data.** [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md)
left *"delivering provider-generated data back to a consumer"* to a separate decision. The analytics
@@ -93,11 +93,12 @@ it first means reordering the whole installation and giving the vault a second w
The vault cannot make that value. The module that received it delivers it to the vault, which keeps
it and provides it like any other; rotating it means asking the backend again.
**Parties that are not modules take the same path.** The controller's own store login and bus account,
and each node agent's bus account, have no definition to require them. The controller asks the vault
on its own behalf, or a node's, and the vault answers the way it answers any requirement: made by the
vault, sealed to the recipient, carried by the mesh. The requirement is not written in a definition,
because the controller and a node agent are the mesh itself, but it is answered no differently.
**The controller is a module, and takes the same path.** Its store logins (inventory, identity and
licences) and its bus accounts are own secrets of its definition today, and become `secret` requirements
of that definition like any module's. **A node's host is the one party with no definition.** Its bus
account is a requirement the mesh makes for each enrolled node, answered by the vault, sealed to that
node and carried like any other. It is the only requirement not written in a definition, because the
host is what runs definitions.
**Only the vault may provide `secret`.** An assignment providing it must hold the `mesh-vault` seat.
The parser refuses a definition that provides it and cannot hold the seat, and a pin cannot route a
@@ -115,18 +116,25 @@ as that base exists**, before any other module built on it, and everything neede
generated by genesis:
- the store's superuser, and the broker's admin in the hashed form the broker needs;
- the bus accounts of the temporary and permanent controller, the control-node's agent, the builder,
the broker's own provisioner and the vault;
- the controller's store login, and the first enrolment token.
- the bus accounts of the temporary and permanent controller (its account and the broker-management
login), the control-node's host, the builder, the broker's own provisioner and the vault;
- the controller's three store logins (inventory, identity and licences), and the first enrolment
token.
Until the broker's provisioner runs, genesis creates the bus accounts it generated, with the broker's
admin, as the controller does today. Genesis seals all of it to the operator key, and when the vault is
installed it **delivers the values to the vault, recorded as the mesh's own**, not as an operator's.
admin, as the controller does today. Genesis seals all of it to the control-node's key, and when the vault
is installed the controller **delivers the values to the vault, recorded as the mesh's own**, not as an
operator's. Nobody has to be present for it.
That distinction matters: an operator's value is never replaced ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)),
and these are, because the vault can make their replacements. The broker's provisioner then adopts the
accounts genesis created. From then on the vault makes every shared secret, and genesis has made its
last one.
**Raising the vault or the broker again is a genesis act.** Moving the `mesh-vault` or `mesh-broker`
seat to a new assignment, or recovering either after it is lost, is done the way genesis did it: the
values it needs are delivered, not made by a vault that is not there. That is a break-glass procedure,
stated and checked, never an ordinary assignment.
**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
@@ -134,74 +142,44 @@ is *given*, never what it is *called* ([ADR 0049](0049-a-consumers-identity-fits
### 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.
**Who asks and who makes are decided here; the mechanism is not.** A rotation is asked of the vault,
by an operator or by the vault's policy, such as a maximum age in the requirement's contract, and the
vault makes the new value. 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 a backend
issued is rotated by the module that holds the backend asking it again and delivering the new value to
the vault.
**A secret's contract says how each recipient takes a new value:**
**Each recipient takes a new value one of two ways, marked on its requirement:**
| 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 |
| **applying** it | a provider setting a login's password; the broker's provisioner updating an account; a store's provisioner changing its own 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 recreates it, because a file it read at creation changed |
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. Where no provisioner exists to make it, such as a
module's own bootstrap password, the contract marks the secret **not rotatable by the mesh**, and a
rotation request is refused, saying why, rather than restarting a service that would carry on with
the old value. 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. A provider's
per-consumer secrets are applied, never read at start, so the host never restarts a provider for one.
The marking is on each requirement, not on the secret, because one secret has recipients of both kinds.
Every module in the catalogue reads its secrets at start, and none watches them
([research 016](../01-RESEARCH/016-how-a-credential-can-be-rotated/02-the-readers.md)). A secret a
backend takes only when it first initialises is marked applied, and its provider's provisioner makes
the change using the old value. Where no provisioner can make it, the requirement is marked **not
rotatable by the mesh**, and a rotation request is refused, saying why, rather than restarting a service
that would carry on with the old value.
**Old and new overlap: nobody is ever without a credential that works.** A credential is never
changed in place. The new one is added beside the old, every reader moves to it, and only then is the
old one removed. There is one mechanism, the same for every provider:
**How old and new change over is not decided here.** Three mechanisms were measured against every
provider's code in [research 016](../01-RESEARCH/016-how-a-credential-can-be-rotated/00-overview.md):
in place, as the controller's `rotate` does today; two secrets on one login; and two logins over one
resource. Its findings bound the choice:
1. **The vault makes the new value.**
2. **Each applier adds it beside the old.** A consumer has two logins, both derived by the mesh, and it
uses one at a time. The provider's loop creates the other with the new value, through the adapter's
existing create, and leaves the one in use untouched. It verifies that the new login works and the
old one still does, and confirms. It repeats that confirmation on every reconcile pass until the
vault acknowledges it, so a lost message costs one pass.
3. **Only then is the new login released to the readers.** A reader receives the new login and its
value together. The host restarts it, or recreates a container whose env-file carries it.
4. **Each reader confirms**, by restarting with the new login and passing its health check, where its
definition declares one.
5. **Only when every reader has confirmed is the old login retired.** Each applier removes it, through
the adapter's existing remove, and verifies that it no longer authenticates.
- every credential provider already re-applies a password in place, so today's rotation works, with a
window in which a consumer cannot authenticate;
- seven of eight name the consumer's resource after its login, and five destroy the resource when they
remove the login. **No mechanism may retire a login through today's remove**, because in those five
it deletes the consumer's data;
- only one backend holds two passwords on one login;
- every backend can give two logins the same rights over one resource, once the adapter separates the
resource from the login.
**What overlap closes:**
- a reader whose machine is offline keeps the old login, which still works, until it returns and
moves; the rotation shows as waiting on that reader, and nobody is locked out;
- a bus account's owner keeps its old account until it has confirmed the new one over the bus it still
has, so no party can lose the bus it would hear the new value on;
- a provisioner restarted mid-rotation is still delivered both values until the old is retired, so it
can verify either.
**What overlap costs.**
- **Consumer modules: nothing.** A consumer reads one login at a time and changes it when it restarts.
- **Providers: one duty.** Both of a consumer's logins must have the same rights over its data,
because the consumer's data was written under one login and is read under the other. In postgres,
both are members of one role that owns the data. That is the adapter's part, and the only place
overlap touches provider code. The alternation itself is the provider loop's, so every provider gets
it by using the harness.
- **The mesh:** it derives two logins per consumer, and both must still fit the tightest backend
([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md)).
- **Secrets with no applier**, such as a module's own secret read only by itself, have no second party
to overlap with. They are delivered and the reader restarted, where their contract allows rotation at
all.
| step | who |
|---|---|
| asks | an operator, or the vault's policy |
| makes the value | the vault |
| adds the new login beside the old | each applier's provisioner, confirming on every pass until acknowledged |
| moves each reader | the host, restarting or recreating what reads the secret |
| confirms each reader | its restart and health check |
| retires the old login | each applier's provisioner, once every reader has confirmed |
| shows progress | `status`: waiting on which applier or reader, never done until the old is retired |
The mechanism is decided in its own record, on those facts. Until then rotation stays as the
controller implements it, in place, with its window stated.
## What this changes in earlier records
@@ -218,19 +196,17 @@ On acceptance, each of these is superseded or amended by this record, not edited
so 0092's rule that an operator's value is never replaced does not apply to them.
- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) is amended: a broker
account is created by the broker's provisioner, not the controller. Its scoping stands.
- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md) is amended: the mesh derives two
logins per consumer, and both fit the tightest backend.
- [To-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md),
[to-be 21](../03-DESIGN/01-to-be/21-the-installation-in-full.md) and
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: rotation overlaps old and new
instead of being all-or-nothing, with restarts derived and each step confirmed; the vault is
installed as soon as the shared runtime base exists, and genesis delivers its secrets to it; the vault
is the only maker.
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: the vault makes a rotated value and each
requirement says whether its recipient applies it or reads it at start, while the changeover
mechanism stays as to-be 13 describes until its own record; the vault is installed as soon as the
shared runtime base exists, and genesis delivers its secrets to it; the vault is the only maker.
- The [glossary](../00-META/glossary.md) gains *shared secret*, *recipient*, *applies* and *reads at
start*, and *reserved provision*, once this record is accepted.
- [Issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
becomes a prerequisite: rotation cannot be trusted while a changed env-file leaves a container on
its old value.
is a prerequisite, and its fix is in the host: a container is recreated when a file it read at
creation changes. The issue is to be recorded as fixed, and derived restarts rest on it.
## Consequences
@@ -238,43 +214,37 @@ On acceptance, each of these is superseded or amended by this record, not edited
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 the alternation of two logins per consumer and repeated confirmation
of each step. A credential provider's adapter gains one duty, giving both logins the same rights over
the consumer's data. A data provider's adapter gains a return value. No consumer module changes.
- A data provider's adapter gains a return value. What a credential provider's adapter must change for
rotation is decided with the mechanism ([research 016](../01-RESEARCH/016-how-a-credential-can-be-rotated/03-the-options.md)).
- 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. Six provider clients export a password
generator nothing uses any more; it is removed, so no module can quietly start minting again.
- The installation changes order: the vault is installed as soon as the shared runtime base exists,
before any other module built on it.
- **What got harder:** a rotation lasts until its slowest reader has moved, so a reader offline for a
week keeps the old login valid for a week. That is shown, and it is the price of never locking anyone
out. A provider briefly holds two logins per consumer. A secret some services read only at first
start can no longer be "rotated" by a restart that quietly changes nothing; it is refused instead.
- **What got harder:** a secret some services read only at first start can no longer be "rotated" by
a restart that quietly changes nothing; it is refused instead, or applied by its provisioner. And
moving the vault or the broker is a procedure, not an assignment.
## 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. A catalogue test: no module's code generates one, found by scanning for generation calls, with none exempt. An installer test: genesis generates exactly the list above and delivers it to the vault, recorded as the mesh's own. |
| Only the vault generates a shared secret after genesis | A controller test: no code path generates a shared secret. A catalogue test: no module's code generates one, found by scanning for generation calls. Exempt are the vault itself, and randomness that is not a secret any other party holds, such as a password hash's salt, each named in a declared list. An installer test: genesis generates exactly the list above and delivers it to the vault, recorded as the mesh's own. |
| A private key is made where it is used | A test per key: a node's sealing key never leaves the node, the operator's private key never enters the mesh, and the certificate authority's private key never leaves the controller's identity store. |
| Only the vault provides `secret` | The parser refuses a definition providing `secret` that cannot hold `mesh-vault`, and resolution refuses a pin on a `secret` requirement. |
| Parties that are not modules take the same path | Controller tests: its own store login, its bus account and a node agent's bus account are each made by the vault and delivered sealed; an enrolment token reaches the controller only as what verifies it. |
| The controller and each node's host take the same path | A catalogue test: the controller's definition declares no own secret, only requirements. A controller test: a node's bus account is made by the vault and delivered sealed to that node; an enrolment token reaches the controller only as what verifies it. |
| Genesis's values reach the vault unattended | An installer test: genesis's values are sealed to the control-node's key and delivered by the controller when the vault is installed, with no operator step. |
| Moving the vault or broker is a procedure | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
| A backend-issued secret enters through the vault | A vault test: a value delivered as issued is provided like any other, and rotating it is refused as the vault's act. |
| A secret with no provisioner to apply it is not rotated by restart | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
| A secret with no provisioner to apply it is not rotated by restart | A vault test: rotating a secret whose requirement is marked not rotatable by the mesh is refused, naming why. |
| A rotation never destroys a consumer's data | A provider test per credential provider: rotating a consumer's credential leaves its resource and data intact. It fails today for no provider, because rotation is in place; it guards whichever mechanism replaces it. |
| 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 operator's value is never rotated by the vault, and genesis's values are | Vault tests: a rotation request on an operator's external key is refused, naming the operator; the same request on a value genesis delivered makes a replacement. |
| Old and new overlap | A rotation test: after an applier adds the new login, both authenticate; readers are released only after it confirms; the old login is removed only after every reader confirms, and then no longer authenticates. |
| An offline reader is never locked out | A rotation test with one reader's node offline: it keeps authenticating with the old login throughout, the rotation shows waiting on it, and completes when it returns. |
| A bus account's owner keeps the bus | A rotation test on a node agent's bus account: the agent stays connected on the old account until it has confirmed the new one. |
| A restarted provisioner can still verify | A rotation test restarting the applier's provisioner mid-rotation: it is delivered both values and confirms. |
| Both logins have the same rights | A provider test per credential provider: data written under one of a consumer's logins is read and changed under the other. |
| Both logins fit the tightest backend | A controller test: the two derived logins for the longest node and module names fit the limit ADR 0049 sets. |
| 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: the rotation shows unconfirmed until every reader has restarted with the new login and passed its health check, and the old login is retired. |
| Restarts are derived from how a secret is read | A host test: a secret read at start recreates the container that read it at creation, through an env-file or a direct mount; an applied secret restarts nothing. A catalogue test: a secret that reaches a process, or a file in a mounted directory, has `restart-on` naming it. |
| 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