To-be 27 (proposed): a module requires, the mesh resolves — with ADRs 0109–0114, research 016 and issue 119 #113
@@ -82,8 +82,9 @@ like an external API key, is still the operator's: the vault is where it is *kep
|
||||
operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)), not who
|
||||
provides it.
|
||||
|
||||
**What a provider answers with is its contract's fields**, made by the provider and carried back to
|
||||
the consumer by the mesh ([ADR 0113](0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)).
|
||||
**Every secret is made by the vault, and a provider answers with resources and data**
|
||||
([ADR 0113](0113-the-vault-makes-every-secret.md)). A provider that needs a secret for a consumer
|
||||
requires it from the vault, like any consumer. The mesh carries every answer back.
|
||||
|
||||
**A directory is a host provision.** Its contract is the owner and mode the module needs, including
|
||||
the owner its image expects ([ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)).
|
||||
@@ -111,8 +112,9 @@ carries, and a provider's identity. So **one module may be assigned to one node
|
||||
must stay singular stays so by a claim, or by an operator value colliding: a public name already taken
|
||||
is refused like any other singular thing.
|
||||
|
||||
**Genesis is the one exception**, as [ADR 0113](0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)
|
||||
states: the foundation's requirements are answered by genesis itself, before any provider exists.
|
||||
**Genesis is not an exception.** It raises the vault first and asks it for the foundation's secrets,
|
||||
so the foundation's requirements are answered the same way as everything else
|
||||
([ADR 0113](0113-the-vault-makes-every-secret.md)).
|
||||
|
||||
## What this changes in earlier records
|
||||
|
||||
@@ -177,7 +179,7 @@ On acceptance, each of these is amended by a record of its own, not edited:
|
||||
- [ADR 0038](0038-the-mesh-assigns-the-port.md), [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md),
|
||||
[ADR 0084](0084-which-provider-serves-a-consumer.md): the parts already unified
|
||||
- [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md): which module provider answers
|
||||
- [ADR 0113](0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md): who makes an answer, and how it travels
|
||||
- [ADR 0113](0113-the-vault-makes-every-secret.md): the vault makes every secret, and how answers travel
|
||||
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md): the operator as a provider
|
||||
- [ADR 0051](0051-shared-data-is-the-operators.md), [ADR 0107](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md),
|
||||
[ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md): what a directory's contract carries, and what it must not
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: proposed
|
||||
date: 2026-09-25
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 113. A provider makes what it provides, and the mesh carries it back to the consumer
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) decided that a provider is
|
||||
handed the credential and makes none: the controller mints one per consumer and provider pair, seals
|
||||
it to both nodes, and the provider creates the login under it. It fixed a real fault. The provider
|
||||
harness of the time generated its own password and sealed it with a symmetric key nobody held, so a
|
||||
consumer could never receive what the provider made. Controller-minting worked because it needed no
|
||||
way back from provider to consumer.
|
||||
|
||||
**It left that way back undecided, deliberately.** 0048 says so: *"delivering provider-generated data
|
||||
back to a consumer is a return path the mesh does not have and this decision does not build — a
|
||||
separate shape, left to a separate decision."* Since then, the missing return path has come up
|
||||
repeatedly:
|
||||
|
||||
- **Data provisions have nothing to answer with.** The analytics provider assigns a site id the
|
||||
consumer needs. The DNS provider registers a name the consumer should be told. Both have no path
|
||||
back, and say so in their code.
|
||||
- **Some contracts need a value the controller cannot make.** The broker needs its admin password in
|
||||
a hashed form the mesh's plain secret delivery cannot produce, so a module-specific bootstrap step
|
||||
was written to derive it. That admin is a foundation credential, which genesis makes, so this
|
||||
record does not remove that step. It is the clearest instance of the general problem, though: a
|
||||
contract can require a form only the party that understands the software can produce.
|
||||
- **The vault is a ledger.** A module's own secret is "a `secret` provision the controller mints and
|
||||
the vault records" ([ADR 0085](0085-a-secret-is-a-provision.md), as amended). The one module whose
|
||||
job is secrets generates none, and cannot apply a policy (length, form, lifetime) because it never
|
||||
makes one. Every other provider creates what it provides. The vault is the exception.
|
||||
|
||||
[ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) proposes that everything a module
|
||||
needs is a requirement answered by a provider against a contract. Under that, "the controller mints
|
||||
this one kind of answer on the provider's behalf" is a special case the model would carry forever.
|
||||
|
||||
## Considered Options
|
||||
|
||||
**1. Keep 0048: the controller mints credentials, and data provisions stay without a way back.**
|
||||
Rejected. The vault stays a ledger, contracts needing a derived form keep needing bespoke steps, and
|
||||
a data provision stays unable to answer at all.
|
||||
|
||||
**2. The provider makes the value and hands it to the consumer itself.** Rejected. The two may be on
|
||||
different machines with no path between them the mesh has agreed to, and a provider reaching
|
||||
consumers directly is a second delivery system beside the mesh's. 0048's objection, a symmetric key
|
||||
both ends hold, is not what rules this out: node keys are asymmetric, and a provider can seal to a
|
||||
node's public key without sharing anything.
|
||||
|
||||
**3. The provider makes the value and gives it to the controller in plaintext, which seals and
|
||||
delivers it.** Rejected. It works, and it puts every secret in the controller's memory and on the
|
||||
broker in the clear, a surface today's design does not have for provider-side values.
|
||||
|
||||
**4. The provider makes the value, seals each secret field to the consumer's node itself, and the
|
||||
mesh carries the sealed answer.** Chosen. The mesh already tells a provider who each consumer is and
|
||||
where; it also hands it the consumer node's public key. The provider seals to it, and answers over
|
||||
its own scoped account. The controller delivers the sealed fields as it delivers everything else,
|
||||
and can open none of them.
|
||||
|
||||
## Decision
|
||||
|
||||
**A provider makes what it provides.** Given a consumer, it creates the resource and answers with
|
||||
the fields its contract names: a password, an access key, a site id, a registered name, a hashed
|
||||
admin secret. The vault generates the secrets it provides, to their contract, and rotates them.
|
||||
|
||||
**The provider seals, and the mesh carries.** With each consumer, the mesh hands the provider that
|
||||
consumer node's public key. The provider seals every field its contract marks secret to it, and
|
||||
hands the answer to the controller over its own scoped broker account. The controller delivers the
|
||||
answer as the consumer's resolved values, and can open none of the sealed fields. A provider never
|
||||
reaches a consumer directly. A secret's plaintext exists where it is made, on the provider's machine,
|
||||
and where it is used, on the consumer's, and nowhere between. That is stricter than today, where the
|
||||
controller holds every minted credential in the clear when it makes it.
|
||||
|
||||
**Who a consumer is stays the mesh's.** The login a consumer presents is the mesh's derivation, which
|
||||
both ends agree on by construction ([ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md),
|
||||
issue 023). A provider makes what a consumer is *given*, never what it is *called*.
|
||||
|
||||
**Rotation is the provider's act.** Asked to rotate, a provider makes a new value and answers again,
|
||||
and the mesh redelivers it. An operator-delivered value ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md))
|
||||
is still never replaced by the mesh: its provider is the operator.
|
||||
|
||||
**The foundation is the one exception.** The foundation's own credentials, the vault's own access and
|
||||
the mesh's root secrets exist before any provider can answer. The controller mints those: at genesis,
|
||||
and when an operator rotates a root secret ([ADR 0085](0085-a-secret-is-a-provision.md)). It seals
|
||||
them to the operator key as today, including any form the foundation's software needs, such as the
|
||||
broker admin's hash. Nothing else is minted by the controller.
|
||||
|
||||
## What this changes in earlier records
|
||||
|
||||
On acceptance, each of these is superseded or amended by this record, not edited:
|
||||
|
||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md) is superseded: a provider no
|
||||
longer receives a minted credential. Its refusal of provider-held symmetric keys stands, and is why
|
||||
option 2 is rejected here.
|
||||
- [ADR 0085](0085-a-secret-is-a-provision.md) is amended: the vault generates a module's own secret
|
||||
rather than recording one the controller minted. "The vault stores no plaintext, ever" stands. It
|
||||
makes a value, seals it, hands it to the mesh and keeps only what it keeps today.
|
||||
- [To-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) and
|
||||
[to-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) are amended: both describe
|
||||
the controller minting a module's credentials and rotating them. After this, the controller mints
|
||||
only the foundation's, and a provider rotates by answering again.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **A consumer waits for its provider.** Its requirement is not resolved until the provider has
|
||||
answered, so resolution gains a state, *waiting on a provider*, that is shown rather than silent.
|
||||
Today a consumer can receive a credential before the resource behind it exists. After this it
|
||||
cannot.
|
||||
- The provider harness in the SDK changes: an adapter's create answers with its contract's fields
|
||||
instead of returning nothing, and every provider in the catalogue moves to it. This is one
|
||||
migration per provider, the cost 0048 named for changing the contract, and it is paid once.
|
||||
- The controller gains the return path: receiving a sealed answer on a provider's account and
|
||||
delivering it. Each contribution gains the consumer node's public key. Data provisions gain the same
|
||||
path, so the analytics and DNS providers can finally answer.
|
||||
- A contract needing a derived form is met by its provider. The broker admin's hash stays with the
|
||||
controller, because that admin is a foundation credential.
|
||||
- **What got harder:** a provider that is down cannot hand out credentials, where today the controller
|
||||
could mint one in its absence. That is honest, because a credential for a resource that does not
|
||||
exist yet was never usable, but it moves a failure from later and silent to earlier and visible.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A provider's secret fields are sealed before they leave it | An SDK test: an answer whose contract marks a field secret cannot be handed over unsealed. A controller test: an unsealed secret field in an answer is refused, not delivered. |
|
||||
| A provider's answer reaches the consumer sealed to its node | A controller test delivering a provider's answer: each secret field opens with the consumer node's key and with no other, the controller's included. |
|
||||
| A consumer's identity is still the mesh's | A resolution test: the login a consumer presents is the mesh's derivation, whatever the provider answers. |
|
||||
| A consumer waits for its provider | A resolution test with no answer yet: the requirement shows as waiting on a provider, and nothing is delivered. |
|
||||
| The controller mints only the foundation's credentials | A controller test: the minting function is reachable only from genesis and from root-secret rotation, and never for a module's provision. |
|
||||
| The vault generates and rotates | A vault test: a requested secret is generated to its contract, and a rotation answers with a new value. |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md): the decision this supersedes,
|
||||
and the return path it left open
|
||||
- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md): identity stays the mesh's
|
||||
- [ADR 0085](0085-a-secret-is-a-provision.md), [ADR 0092](0092-an-operator-delivers-a-pair-credential.md):
|
||||
the vault, and the operator as a provider
|
||||
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md): everything a module needs is a requirement
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: proposed
|
||||
date: 2026-09-25
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 113. The vault makes every secret, a provider makes resources and data, and the mesh carries both
|
||||
|
||||
## Context
|
||||
|
||||
**A secret comes into being seven 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 broker account | the controller, but only when a person runs a separate command; otherwise the random value above, which cannot work ([issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md)) | 49 modules |
|
||||
| a `secret` from the vault | the controller mints it, and the vault only records it ([ADR 0085](0085-a-secret-is-a-provision.md), as amended) | 6 modules |
|
||||
| a value an operator accepts | a person ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md)) | where accepted |
|
||||
| a licence for model access | a separate controller context with its own store | model consumers |
|
||||
| the mesh'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. The same has happened to the broker
|
||||
account, a special case of the second row that fails silently when the separate command is forgotten.
|
||||
|
||||
**Rotation has its own gaps.** To-be 13 makes rotation one command, all-or-nothing, and states the
|
||||
window in which a consumer cannot authenticate: the provider has taken the new password, and the
|
||||
consumer has not yet restarted with it. A consumer restarts only if its definition remembered to say
|
||||
so, and a container fed by an env-file is not recreated when that file changes, so it keeps the old
|
||||
value ([issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)).
|
||||
Nothing confirms that the new secret works.
|
||||
|
||||
**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
|
||||
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 seven paths.** Rejected. The paths are the problem:
|
||||
each is made, kept, rotated and audited differently, and tidying keeps all seven.
|
||||
|
||||
**2. Every provider mints its own secrets, with one shared function in the SDK.** Rejected. It makes
|
||||
generation uniform and leaves custody scattered: every provider's machine holds secrets the vault
|
||||
never sees, so rotation, audit and the operator's break-glass copies cover only some of them. The
|
||||
function would also need a conforming implementation in every language a provider is written in.
|
||||
|
||||
**3. The vault makes every secret, and a provider that needs one requires it, like any consumer.**
|
||||
Chosen. A provider serving a consumer requires a secret for that consumer from the vault. Secrets
|
||||
become provisioning all the way down, with one maker at the bottom.
|
||||
|
||||
## Decision
|
||||
|
||||
**The vault makes every secret in the mesh.** Generation, to a secret's contract, exists in the vault
|
||||
and nowhere else. The controller mints nothing.
|
||||
|
||||
**A provider that needs a secret for a consumer requires it from the vault.** A provision's contract
|
||||
declares it: *for each consumer, one secret*. Resolution expands that into one requirement per
|
||||
consumer, named for the consumer. So gitea requiring a database makes the database's provider require
|
||||
a secret named for gitea, and the vault answers it. The provider's own code does not change. It is
|
||||
handed a login and a password, as it is today.
|
||||
|
||||
**A secret has holders, and the vault delivers to each.** The database credential has two: the
|
||||
provider, which creates the login with it, and the consumer, which presents it. The vault hands it to
|
||||
the mesh, which delivers it to each holder sealed to that holder's node. Plaintext exists in the
|
||||
vault while it is made and on each holder's machine, and nowhere else. The controller carries sealed
|
||||
values it cannot open.
|
||||
|
||||
**Every other secret takes the same path:**
|
||||
|
||||
- a module's **own secret** is a `secret` requirement the vault answers. `own-secrets` is retired;
|
||||
- a **broker account** is the module's identity on the bus. Its name is the mesh's, its password is a
|
||||
secret the vault makes, and the controller creates the account with it, as it creates accounts
|
||||
today. There is no separate command to forget;
|
||||
- an **operator's value** that is secret is handed to the vault, which provides it like any other
|
||||
secret. It is never replaced by rotation ([ADR 0092](0092-an-operator-delivers-a-pair-credential.md));
|
||||
- a **licence's** credential is an operator's value the vault keeps. What the licences context adds,
|
||||
refreshing a token and a manager holding the refresh credential, is provider behaviour, decided in
|
||||
its own record.
|
||||
|
||||
**Genesis is the vault's first answer, not an exception.** Genesis raises the vault before anything
|
||||
else and asks it for the foundation's secrets: the store's superuser, the broker's admin and its hashed
|
||||
form, and the vault's own broker account. The vault answers with the same code it always uses, before
|
||||
the bus exists. Genesis mints nothing itself.
|
||||
|
||||
**A provider makes resources and data, and the mesh carries data back.** A provider answers 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](0049-a-consumers-identity-fits-the-tightest-backend.md)).
|
||||
|
||||
### Rotation
|
||||
|
||||
**It is asked of the vault**, by an operator or by the vault's own policy, such as the maximum age a
|
||||
secret's contract sets.
|
||||
|
||||
**It is provider-first.** The vault delivers the new value first to the holders that *accept* it,
|
||||
such as the database, and waits for each to confirm it has applied it. Only then does it release the
|
||||
value to the holders that *present* it, such as gitea. A consumer is never sent a value its provider
|
||||
has not accepted, so the window shrinks to the consumer's own restart. A provider that does not
|
||||
confirm holds the rotation: the consumer keeps the old value, which still works, and `status` shows the
|
||||
rotation as waiting on that provider. It is never half-done and never silently abandoned.
|
||||
|
||||
**Restarts are derived, not declared.** The mesh knows which process reads which secret, because the
|
||||
definition reads it through its requirement ([to-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md)).
|
||||
When a secret changes, the host restarts every process that reads it, and recreates a container whose
|
||||
env-file carries it. No definition has to remember `restart-on` for a secret.
|
||||
|
||||
**It is confirmed.** A rotated secret is shown as unconfirmed until each consumer has restarted with
|
||||
it and, where its definition declares a health check, passed it. Delivered is not the same as working,
|
||||
and the mesh says which one it knows.
|
||||
|
||||
| step | who |
|
||||
|---|---|
|
||||
| asks | an operator, or the vault's policy |
|
||||
| makes the value | the vault |
|
||||
| carries it | the controller, sealed, provider first |
|
||||
| applies it on the provider | the provider's provisioner, which confirms |
|
||||
| applies it on the consumer | the host, restarting or recreating what reads it |
|
||||
| confirms it works | the consumer's restart and health check, 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](0048-a-provider-creates-the-credential-the-mesh-minted.md) 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](0085-a-secret-is-a-provision.md) is amended: the vault makes a module's own secret rather
|
||||
than recording one the controller minted, own secrets are retired, and genesis asks the vault for
|
||||
the root secrets. "The vault stores no plaintext, ever" stands.
|
||||
- [ADR 0092](0092-an-operator-delivers-a-pair-credential.md) is amended: an operator delivers a secret
|
||||
to the vault.
|
||||
- [To-be 13](../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md) and
|
||||
[to-be 24](../03-DESIGN/01-to-be/24-the-secrets-vault.md) are amended: rotation is provider-first,
|
||||
derived and confirmed, and the vault is the only maker.
|
||||
- [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.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The vault is on the path of every new or rotated secret.** Today the controller is, and both run
|
||||
on the control-node, so no new single point of failure appears. It is stated rather than implied.
|
||||
- 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 one thing: confirming that a rotation was applied. Adapters are
|
||||
unchanged.
|
||||
- The mesh gains the way back from provider to consumer, for confirmations and for data.
|
||||
- 54 modules move from own secrets to vault requirements, and the broker account stops needing a
|
||||
separate command.
|
||||
- **What got harder:** a secret can no longer be made when the vault is down, where today the
|
||||
controller makes one regardless. And a rotation waits for its provider. Both move failures from
|
||||
late and silent to early and visible, which is the trade this record makes throughout.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| Only the vault makes secrets | A controller test: no code path mints a secret. A vault test: the one generation function is the only one, and genesis reaches it through the vault. |
|
||||
| A provider's per-consumer secret comes from the vault | A resolution test: a consumer requiring a database expands to a secret requirement named for it, answered by the vault. |
|
||||
| A secret reaches each holder sealed to its node | A controller test: each holder's copy opens with that holder's node key and with no other, the controller's included. |
|
||||
| Own secrets are retired | A catalogue test: no definition declares an own secret, with a declared list of exceptions that shrinks to empty. |
|
||||
| A broker account needs no separate command | A resolution test: assigning a module that speaks on the bus yields its account. |
|
||||
| Rotation is provider-first | A rotation test: the consumer is not sent the new value until the provider confirms; a provider that never confirms leaves the consumer on the old value, shown as waiting. |
|
||||
| Restarts are derived | A host test: changing a secret restarts every process that reads it, and recreates a container whose env-file carries it, with no `restart-on` declared. |
|
||||
| Rotation is confirmed | A rotation test: the secret shows as unconfirmed until the consumer restarted and passed its health check. |
|
||||
| A provider answers data back | A resolution test: the analytics provider's site id reaches its consumer as a resolved value. |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0048](0048-a-provider-creates-the-credential-the-mesh-minted.md): the decision this supersedes,
|
||||
and the return path it left open
|
||||
- [ADR 0085](0085-a-secret-is-a-provision.md), [ADR 0092](0092-an-operator-delivers-a-pair-credential.md),
|
||||
[ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md): the vault, the operator, and identity
|
||||
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [to-be 27](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md):
|
||||
everything a module needs is a requirement
|
||||
- [Issue 095](../04-ISSUES/095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md),
|
||||
[issue 103](../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md): what fails today
|
||||
@@ -158,7 +158,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0099** — [A step that runs once names what it reads, and runs again when it changed](0099-a-step-that-runs-once-names-what-it-reads.md)
|
||||
- **0110** — [A seat is a module assignment from a closed set, and it may deliver a provision](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
||||
- **0112** — [A module definition names no node, no mesh and no path: everything it needs is a requirement the mesh resolves](0112-a-module-definition-names-no-node-mesh-or-path.md) *(proposed)*
|
||||
- **0113** — [A provider makes what it provides, and the mesh carries it back to the consumer](0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md) *(proposed)*
|
||||
- **0113** — [The vault makes every secret, a provider makes resources and data, and the mesh carries both](0113-the-vault-makes-every-secret.md) *(proposed)*
|
||||
|
||||
### How it is built
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ code: []
|
||||
updated: 2026-09-25
|
||||
decisions:
|
||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
- 02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md
|
||||
- 02-DECISIONS/0113-the-vault-makes-every-secret.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
|
||||
@@ -69,11 +69,29 @@ Which module answers, in order:
|
||||
5. otherwise **refused**: naming the unheld seat, for a provision a seat delivers, even when exactly
|
||||
one provider exists; naming the candidates otherwise.
|
||||
|
||||
The provider makes what it provides and answers with its contract's fields. The mesh carries the
|
||||
answer back to the consumer, sealing every secret field to the consumer's node
|
||||
([ADR 0113](../../02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)). The vault
|
||||
is a module provider like any other: it holds the `mesh-vault` seat and generates the secrets it
|
||||
provides.
|
||||
### Secrets: provisioning all the way down
|
||||
|
||||
**The vault makes every secret, and it is the only thing that does**
|
||||
([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)). It holds the `mesh-vault` seat,
|
||||
so every `secret` requirement resolves to it.
|
||||
|
||||
**A provider that needs a secret for a consumer requires one, like any consumer.** A provision's
|
||||
contract declares it: *for each consumer, one secret*. Resolution expands that into one requirement
|
||||
per consumer, named for that consumer:
|
||||
|
||||
1. gitea requires `postgres-database`;
|
||||
2. the database provider, to serve gitea, requires a `secret` named for gitea;
|
||||
3. the vault makes it and hands it to the mesh;
|
||||
4. the mesh delivers it to both holders, each sealed to its own node: the database's machine, to
|
||||
create the login, and gitea's, to present it;
|
||||
5. the database provider creates the login, exactly as it does today, and gitea connects.
|
||||
|
||||
A module's own secret, a broker account's password and a secret operator value take the same path.
|
||||
Nothing in the mesh makes a secret except the vault.
|
||||
|
||||
**A provider makes resources and data.** Beyond secrets, a provider answers with its contract's
|
||||
non-secret fields: an analytics site id, a registered public name. The mesh carries them back to the
|
||||
consumer as resolved values.
|
||||
|
||||
### The node's host
|
||||
|
||||
@@ -116,11 +134,11 @@ A value a person chooses: a public name for an endpoint, a greeting, how many wo
|
||||
needs no provider module, no grant and no credential. If asking a person for a value took more than
|
||||
that, module authors would route around it, and the literals this replaces would come back.
|
||||
|
||||
**A secret operator value**, such as an external API key, is still an operator requirement: its
|
||||
provider is the operator. What differs is where it is kept. The vault holds it as an
|
||||
operator-delivered value ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)),
|
||||
never as a setting, because anything secret belongs in one place that can seal and audit it. The
|
||||
vault is its custodian, not its provider.
|
||||
**A secret operator value**, such as an external API key, follows the one rule for secrets: the
|
||||
vault provides it. The operator hands the value to the vault, once
|
||||
([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)), and the module requires
|
||||
a `secret` like any other. The only difference is that rotation never replaces it: the vault cannot
|
||||
make a new external key, so rotating one means an operator handing over a new value.
|
||||
|
||||
**An endpoint** is an operator value inside a route requirement: the public name is chosen on the
|
||||
assignment, and the route provider answers. A public name already held by another assignment is
|
||||
@@ -162,10 +180,36 @@ slug, under the same rules as a slug. This has to be settled before a second ins
|
||||
|
||||
## Genesis
|
||||
|
||||
The one exception. Before any provider exists, genesis answers the foundation's own requirements
|
||||
itself: the store's and broker's credentials, the vault's own access, and the root secrets. It seals
|
||||
them to the operator key as it does now ([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md)).
|
||||
After genesis, nothing is answered except by a provider.
|
||||
**Genesis is the vault's first answer, not an exception.** It raises the vault before anything else
|
||||
and asks it for the foundation's secrets: the store's superuser, the broker's admin in the hashed form
|
||||
the broker needs, and the vault's own broker account. The vault answers with the same code it always
|
||||
uses, before the bus exists, and seals the root secrets to the operator key as today
|
||||
([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md)). Genesis makes no secret itself, so
|
||||
there is one way a secret is made, from the first one onwards.
|
||||
|
||||
The vault can sit at the bottom because it requires nothing but a broker account: it keeps its data
|
||||
on its own disk, not in the store. So the waterfall ends at the vault.
|
||||
|
||||
## Rotation
|
||||
|
||||
Rotating a secret is asked of the vault, by an operator or by the vault's own policy, such as a
|
||||
maximum age in the secret's contract ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)).
|
||||
|
||||
1. **The vault makes the new value.**
|
||||
2. **It is delivered to the holders that accept it first.** For a database credential that is the
|
||||
database's provider, whose provisioner applies it and confirms it did.
|
||||
3. **Only then is it released to the holders that present it**, such as gitea. A consumer is never
|
||||
sent a value its provider has not accepted, so the window in which it cannot log in shrinks to its
|
||||
own restart. A provider that does not confirm holds the rotation: the consumer keeps the old value,
|
||||
which still works, and the rotation shows as waiting on that provider.
|
||||
4. **The host restarts every process that reads the secret**, and recreates a container whose
|
||||
env-file carries it. It knows which, because a definition reads a secret only through its
|
||||
requirement. No definition declares a restart for a secret. This needs
|
||||
[issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)
|
||||
fixed, or a container fed by an env-file keeps the old value.
|
||||
5. **It is confirmed.** The rotation shows as unconfirmed until each consumer has restarted with the
|
||||
new value and passed its health check, where its definition declares one. Delivered and working
|
||||
are shown as different things.
|
||||
|
||||
## Refusing
|
||||
|
||||
@@ -175,10 +219,10 @@ requirement it names what is missing and what would answer it:
|
||||
- an unheld seat, and which modules could hold it;
|
||||
- no provider, and which modules could provide it;
|
||||
- an operator value with no default, and that the assignment must give it;
|
||||
- a provider that has not answered yet, and which one.
|
||||
- a provider, or the vault, that has not answered yet, and which one.
|
||||
|
||||
The last one is a state, not a failure. A consumer waiting for its provider is shown as waiting, and
|
||||
nothing is delivered until the answer arrives ([ADR 0113](../../02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)).
|
||||
The last one is a state, not a failure. A consumer waiting for its provider or for the vault is shown
|
||||
as waiting, and nothing is delivered until the answer arrives.
|
||||
|
||||
## What this retires
|
||||
|
||||
@@ -188,7 +232,9 @@ nothing is delivered until the answer arrives ([ADR 0113](../../02-DECISIONS/011
|
||||
| settings on an assignment ([ADR 0046](../../02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md)) | operator requirements, addressed to an instance |
|
||||
| a port the mesh assigns | a host requirement |
|
||||
| machine facts and machine placeholders | host requirements |
|
||||
| a secret the controller mints | a secret the vault makes ([ADR 0113](../../02-DECISIONS/0113-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md)) |
|
||||
| every secret the controller mints: provider credentials, own secrets, broker passwords, root secrets | a secret the vault makes ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)) |
|
||||
| a separate command issuing a broker account | a requirement resolved on assignment |
|
||||
| `restart-on` naming a secret's file | a restart the host derives |
|
||||
| paths in resources, mounts, bindings, secrets and received files | host directory requirements, placed by the assignment |
|
||||
| literals carried in a definition | operator requirements with defaults |
|
||||
|
||||
@@ -204,10 +250,12 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
the controller. The controller resolves requirements from the four providers, refuses as above,
|
||||
and fills the one form. Old mechanisms keep working beside it. *Ends when* a definition written
|
||||
entirely in the new form installs on a lab machine.
|
||||
2. **Providers answer.** The SDK harness answers with contract fields and seals the secret ones, the
|
||||
controller carries answers back, and the vault holds its seat and generates. *Ends when* the
|
||||
analytics and DNS providers answer their consumers, and a module's own secret is generated by the
|
||||
vault and rotated by it.
|
||||
2. **The vault makes every secret, and providers answer.** The vault holds its seat and is the only
|
||||
maker; resolution expands per-consumer secret requirements; genesis asks the vault first; the 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 first. *Ends when* no code path outside the vault makes a secret, the analytics and DNS
|
||||
providers answer their consumers, and a database credential rotates provider-first, with the
|
||||
consumer restarted by derivation and the rotation confirmed.
|
||||
3. **Definitions move.** Every catalogue definition is rewritten, adopted and running assignments
|
||||
placed where their data already is. *Ends when* the list of definitions using an old form is
|
||||
empty, and the old forms are removed.
|
||||
@@ -229,6 +277,9 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
| A public name already held is refused | A resolution test: a second instance asking for a public name the first holds is refused, naming the first. |
|
||||
| Everything keyed by a module is keyed by its instance | A resolution test: two instances of one module on one node get two directories, two containers, two logins and separate settings. |
|
||||
| A consumer waits for its provider | A resolution test with a provider that has not answered: shown as waiting, and nothing delivered. |
|
||||
| Only the vault makes secrets | A controller test: no code path mints a secret, genesis included. |
|
||||
| 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 holders. |
|
||||
| Rotation is provider-first, derived and confirmed | The rotation tests of [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md): the consumer waits for the provider's confirmation, is restarted without a declared restart, and shows unconfirmed until healthy. |
|
||||
| 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. |
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`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-a-provider-makes-what-it-provides-and-the-mesh-carries-it.md), [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.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) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user