Files
hq/02-DECISIONS/0094-a-module-may-hold-several-secrets-from-one-provider.md
T

3.2 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the tiers accepted 2026-09-21 jochen false 02-DECISIONS/0085-a-secret-is-a-provision.md

94. A module may hold several secrets from one provider, each a pair of its own

Context

ADR 0085 makes a module's own secret a provision: the module requires secret from the vault and reads the pair credential minted for that consumer↔vault pair. A pair has one credential, a module requires a provision once, and so a module received one value. Read against the catalogue, nine modules hold two or more secrets besides their broker account; seven of them hold genuinely independent values with independent lifetimes — a root certificate, its key and that key's password; an admin password beside an API token. None derives from another, so "one value, derivation the module's business" answers nothing (issue 069).

Considered Options

  1. One value per module; the module derives the rest. Rejected: the values are independent.
  2. Require the provision several times. Rejected: requires is a list of names, and a requirement is matched by name everywhere.
  3. The secrets map names several files under local names, and each local name is a pair credential of its own. Adopted.

Decision

A module's secrets entry for a requirement may be a path, as before, or an object of local names to paths. Each local name is its own pair credential, keyed on it beside the provision, the consumer node, the consumer module and the provider; its own file on the consumer, referred to as ${secret:<local name>}; its own holder at the provider, named the consumer's identity with the local name after it; and rotated apart from the others. A local name may not be one of the module's own secrets or something it requires, so what a placeholder means is never ambiguous. The plain shape is unchanged, and every credential that exists is the one it was.

The holder's suffix is not a login any backend checks — a secret is not a login — so the identity limit that binds a database role or an access key does not apply to it.

Consequences

The ten modules that could not move onto the vault can. What got harder: rotate secret for a consumer rotates every local name it holds from that provider; rotating one of several is a finer command than the mesh has, and waits for a case that needs it.

How it is checked

Manifest tests read both shapes, write them back, and refuse a colliding or unusable local name. A resolver test asserts two local names are two needs, two files with two credentials, and two holders at the provider. An inventory test asserts two local names are two rows, that rotating one leaves the other, and that the provider is told both. The vault bed installs a consumer that keeps two secrets and asserts two values delivered, two holders in the vault's ledger, and both rotated by one command.

References