Amend ADR 0085: the vault is a foundation module and holds the root secrets
Recorded on the record, dated, before anything shipped against the sentences that change. The vault is installed at genesis like the store and broker, one per mesh, and holds every secret a module has for itself sealed a second time to an operator key whose private half never enters the mesh — the break-glass path the first version left open, without a key one place holds. Design 24 says how; 07 and 21 say what genesis does not yet do; issue 071 names the fixed credentials the foundation is raised with today.
This commit is contained in:
@@ -246,6 +246,12 @@ host's vocabulary grows by one shape rather than by one resource type per founda
|
||||
the module that provides one.
|
||||
- **Whether one host can raise all three.** The claim under stage 2 of
|
||||
[the node host](05-the-node-host.md), never proved. If it is false, the tier boundary moves.
|
||||
- **The vault as the fourth piece.** [ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md),
|
||||
amended, makes the vault a foundation module: genesis makes the operator key before anything
|
||||
is minted, replaces the fixed credentials the store and broker are raised with, and installs
|
||||
`mesh-vault` beside the adopted store and broker ([24](24-the-secrets-vault.md)). The controller's
|
||||
half exists; the installer's does not yet, and the bundle still raises the foundation with
|
||||
well-known credentials ([issue 071](../../04-ISSUES/071-the-foundation-is-raised-with-fixed-credentials/00-report.md)).
|
||||
- **How the foundation is updated once a mesh exists.** Pinned by hand at bootstrap; afterwards
|
||||
the controller could deliver it like anything else, and nothing says whether it does.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ code:
|
||||
- mesh-host internal/bootstrap
|
||||
- mesh-host cmd/mesh-bootstrap
|
||||
- mesh-lab test/integration/one-node-mesh.test.ts
|
||||
updated: 2026-09-15
|
||||
updated: 2026-09-20
|
||||
decisions:
|
||||
- 02-DECISIONS/0067-genesis-is-a-pivot.md
|
||||
- 02-DECISIONS/0073-the-installer-carries-a-builder.md
|
||||
@@ -118,6 +118,11 @@ told anything.
|
||||
this is the lab being honest rather than the mesh being broken. What is missing is the step that
|
||||
puts the shipped unit on the machine.
|
||||
|
||||
**The foundation is raised with fixed credentials, and they stay.** The store's superuser and the
|
||||
broker's administrator are constants in the bundle, carried into the mesh by `secret accept`. No
|
||||
operator key is made at genesis, so nothing minted during installation is sealed to one
|
||||
([24](24-the-secrets-vault.md), [issue 071](../../04-ISSUES/071-the-foundation-is-raised-with-fixed-credentials/00-report.md)).
|
||||
|
||||
**Nothing asserts a mesh was installed this way.** A claim that a machine was brought up by this
|
||||
procedure cannot be contradicted by anything afterwards.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog]
|
||||
code: [mesh-catalog, mesh-controller]
|
||||
updated: 2026-09-20
|
||||
decisions:
|
||||
- 02-DECISIONS/0085-a-secret-is-a-provision.md
|
||||
@@ -57,21 +57,60 @@ This is what replaces the "generated value that nothing owns". A module's own pa
|
||||
a special kind of thing injected by the synchroniser and becomes a provision with a provider, a
|
||||
holder, and a lifecycle — the same shape as everything else the mesh grants.
|
||||
|
||||
## The vault is a module, and node-scoped
|
||||
## The vault is a foundation module, one per mesh
|
||||
|
||||
The vault is an ordinary module. A mesh that wants one runs it; a mesh whose modules require no
|
||||
secret of their own runs none — the same test identity meets
|
||||
([ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md)). It is provisioned
|
||||
the ordinary way, and it does **not** mint the mesh's delivery credentials — the controller does
|
||||
that, because the controller must mint in order to deliver any provision, the vault's own included.
|
||||
The vault mints secrets *for modules*, downstream of its own existence, never the credential that
|
||||
delivers it.
|
||||
The vault is an ordinary module — built, assigned and upgraded like any other — and it is part of
|
||||
the foundation: installed at genesis, the way the store and broker are raised first and then
|
||||
adopted as modules ([ADR 0078](../../02-DECISIONS/0078-the-store-and-broker-are-modules.md)). A
|
||||
mesh has root secrets, so a mesh has a vault; it is not something a mesh opts into
|
||||
([ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md), amended). It is provisioned the
|
||||
ordinary way, and it does **not** mint the mesh's delivery credentials — the controller does that,
|
||||
because the controller must mint in order to deliver any provision, the vault's own included.
|
||||
|
||||
Like every provider it is node-scoped
|
||||
([ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md), [23](23-choosing-a-provider.md)):
|
||||
each node may run its own vault, and a module's own secret is held by the vault on the module's
|
||||
node, selected the same way any provider is. There is no single mesh vault holding everything, for
|
||||
the same reason there is no single mesh store.
|
||||
There is **one vault per mesh**, on the control-node, reached from any node the way identity is.
|
||||
Node scoping ([ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md),
|
||||
[23](23-choosing-a-provider.md)) exists because a store holds data a consumer is coupled to; a
|
||||
vault holds nothing a consumer is coupled to, and a second one would be a second place to lose.
|
||||
|
||||
## The root secrets, and the operator key
|
||||
|
||||
The mesh has secrets of its own that no module requires from anybody: the store's superuser, the
|
||||
broker's administrator, the controller's credentials to its contexts, the sealing key of every
|
||||
node. Until now each was sealed to the node that uses it and to nothing else, so a node whose key
|
||||
was gone took them with it — and at genesis they were not even secret, the foundation being raised
|
||||
with fixed credentials that were then carried in.
|
||||
|
||||
The vault's second job is to hold these, and it does so without holding a value:
|
||||
|
||||
- **An operator sealing key.** A keypair made once, by the operator, whose private half is written
|
||||
to a file the operator keeps off the mesh and whose public half the mesh records. It is made
|
||||
before anything else is minted, so that everything is sealed to it.
|
||||
- **A second seal.** Every secret a module holds for itself — minted or accepted — is sealed to its
|
||||
node as before and, in addition, to the operator key. What the mesh stores is one more blob it
|
||||
cannot open. A secret made before the key existed has no such copy and cannot get one, the
|
||||
plaintext being gone; the mesh says which those are rather than letting the export pass for
|
||||
complete.
|
||||
- **The export.** All operator-sealed copies, the key's public half and its fingerprint, and the
|
||||
list of what is not recoverable, as one document. The vault declares that it *keeps* this, and
|
||||
the mesh writes it onto the vault's own disk as an ordinary declared file — ciphertext to the
|
||||
machine that holds it and to the bus it crossed. The same document can be written out by the
|
||||
operator to keep beside the key.
|
||||
- **Recovery.** The operator, holding the private key, opens one secret from the export or from the
|
||||
store and gets it as a file, never on a terminal unless asked for. It needs the key and a blob;
|
||||
the mesh has the blobs and no key, the operator the key and no blob until given one. Nothing in
|
||||
this reintroduces a place that can open everything, which is the property
|
||||
[ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md) was
|
||||
built to keep — so the break-glass question the first version of this design left open is
|
||||
answered by it.
|
||||
|
||||
**Genesis** makes the operator key first and mints real root secrets in place of the fixed ones the
|
||||
foundation is raised with, so the mesh is handed over with nothing well-known in it. That step is
|
||||
the installer's and is not yet built; until it is, the fixed credentials are the as-is and are
|
||||
said so in [21](21-the-installation-in-full.md).
|
||||
|
||||
What is not yet sealed to the operator: a module's vault-provided secret — the pair credential of
|
||||
[13](13-credentials-and-their-rotation.md). That is the next increment, the same column and the
|
||||
same call on the pair table.
|
||||
|
||||
## Beyond generate and hold
|
||||
|
||||
@@ -80,13 +119,10 @@ secrets subsystem whose surface names the operations a vault is responsible for
|
||||
backing it up, verifying it is what it should be, and a break-glass recovery for when the normal
|
||||
path is unavailable. These become the vault module's, specified against it rather than scattered.
|
||||
|
||||
One of them is left open on purpose. **Break-glass must not reintroduce a key that one place
|
||||
holds.** The whole point of sealing a secret to the machine that needs it, asymmetrically, is that
|
||||
no single place can open everything
|
||||
([ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md)); a
|
||||
recovery path that keeps a master key would undo exactly that. How the vault lets an operator
|
||||
recover a secret without becoming the thing the sealing was designed to prevent is a question this
|
||||
design opens and does not yet answer.
|
||||
Break-glass is answered above, and by the property it had to keep: **it does not reintroduce a
|
||||
key that one place holds.** The vault keeps blobs sealed to the operator; the operator keeps a key
|
||||
with nothing to open until handed a blob. Locating and verifying are the vault's tools, by
|
||||
fingerprint; backing up is the export.
|
||||
|
||||
## What this changes for a module
|
||||
|
||||
|
||||
Reference in New Issue
Block a user