diff --git a/02-DECISIONS/0009-modules-and-the-graph.md b/02-DECISIONS/0009-modules-and-the-graph.md index 24615df..aca46f5 100644 --- a/02-DECISIONS/0009-modules-and-the-graph.md +++ b/02-DECISIONS/0009-modules-and-the-graph.md @@ -173,11 +173,55 @@ address of its database on another, as a file, and reaches it by a name the mesh stated absence looks like a boundary, and somebody wiring this up should not spend an afternoon looking for a password that was never going to be there. -**What remains is the secret itself**, and it is the larger half: it must exist, be stored, reach -one node and not the others, and rotate with every holder informed — which is -[the invariant set](0001-mesh-brokers-nodes-host-agents-think.md) that was found violated three -ways at once, and the reason it is not something to add in passing. What is built is the shape it -will arrive in. +### And the secret, which is delivered without ever being held + +*Written 2026-08-30, after looking at how the existing mesh does it. The design here is a reaction +to a measurement, not a preference.* + +**The obvious arrangement is a credentials column, encrypted at rest.** It exists, and its own +tooling records what it bought: + +| | | +|---|---| +| the tool for finding a secret matches **by value**, not by name | because one password is in the provisions table, the environment table, each node's environment file in plain text, and **inside every connection string composed from it** — copies its documentation calls *"often the only copies actually in use"* | +| a query against the encrypted column **returns zero rows and proves nothing** | so auditing moved to the decrypted copies on the machines | + +**Two faults, and encryption at rest addresses neither.** The control plane can read what it +stores, so a copy of its database is a copy of every credential in the mesh. And one secret has +many homes with nothing tracking them — **composition is what mints the untracked ones**, because +building a connection string centrally creates a new secret-bearing value no rotation path knows +about. + +**So the value is sealed to the node that will use it before it is stored.** With a key that node +generated and whose private half the mesh has never seen — a third key beside the identity and the +overlay, for the same reason those are two rather than one. What is stored is unusable by whoever +holds it, the mesh included, and the broker relays a blob it cannot read. This is what makes +[ADR 0004](0004-a-node-and-how-it-joins.md)'s *compromise of a node is compromise of that node* +true of secrets rather than true of identity and quietly false of everything that matters. + +**And nothing is composed centrally.** A connection string is assembled on the machine that needs +one, if at all. The mesh delivers parts. + +**What it costs, stated because it is real:** the mesh cannot audit by value. That is the right +trade rather than an oversight — a query over an encrypted column could not either, so the audit +was never real. What *is* answerable is which node holds what, which is the question rotation +actually asks. + +**A consequence that shapes the mechanism.** The mesh discarded the plaintext, so it cannot +compose a file containing it. The credential is therefore **its own file**, holding the value and +nothing else, beside the readable one. That is better than the alternative it was forced into: +the readable half stays readable in the declaration, and the secret half changes only when the +secret does, so a service reloading on it reloads for a real reason. + +**Rotation is generating a new one**, because reading the old one back is not possible. Both ends +are re-sealed and reach their machines in the same push — which removes the window where half the +mesh holds a dead credential, the failure +[recorded in ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) as consumers on three nodes +holding one for two days. + +**What remains is a provisioner** — something that creates the database user the credential is +for. The mesh now generates the secret, tells the provider to create it and the consumer to use +it; nothing yet acts on the telling. **One check that only became possible now.** Two machines wired together across no private network is a mesh that reports itself configured and does not work, and the failure surfaces as a