Merge pull request 'Issue 100: a secret the mesh mints cannot be the one the service it takes over already uses' (#86) from issues/100-a-minted-secret-cannot-be-the-one-the-service-already-uses into main

This commit was merged in pull request #86.
This commit is contained in:
2026-09-23 00:54:21 +00:00
@@ -0,0 +1,67 @@
---
status: open
opened: 2026-09-23
located-in: []
fixed-by:
amended-design:
---
# 100 — A secret the mesh mints cannot be the one the service it takes over already uses
## What was observed
Cutting a second service over on the control-node, 2026-09-23. The service signs its sessions with
a secret the machine has had for as long as it has been running. The mesh's module declares that
secret as one it *requires*, answered by the provision that makes secrets — so the mesh minted a
new one, and the running service was handed a value it had never seen.
The obvious thing was tried first: carry the old value in, the way the mesh already carries a value
it "did not make and cannot invent".
```
secret accept <node> <module> app-secret
→ does not declare "app-secret" as an own secret; it declares: broker
```
Carrying a value works only for a module's **own** secrets. A secret answered by the provision is
minted, and there is no way to say *this one already exists, here it is*. The two categories look
the same in a manifest and behave oppositely at a cutover.
Here it cost one re-login and nothing more. That is luck about which service went second.
**Six modules in the catalogue take a secret this way**, and the failure is different in each:
- A service whose **setup already ran** ignores the value entirely. The mesh then holds a
credential that does not work, believes it is the admin's, and nothing says otherwise until
somebody tries to use it.
- A credential **other systems were configured with** — a streaming source password, an API token —
changes under them. The service is fine; its clients are not, and they fail somewhere else.
- A secret that **keys stored data** makes that data unreadable. None of the six is known to be in
this class, and nothing in the mesh would stop one being added.
None of these is reported. The cutover succeeds, the service answers, and the damage is at a
distance.
## Why it matters beyond this instance
The mesh's model is right for a service it raises: nobody should know a password, so the mesh makes
one. Taking over a service that already exists inverts it — **the value is a fact about the machine,
and the mesh's job is to learn it, not to choose it.** Every migration is the second case, and the
mesh has a mechanism for exactly this (`secret accept`) that the provision cannot reach.
It also makes a class of module quietly un-migratable. A module is written with `requires: secret`
because that is the normal way to be given a password. The same choice, met at a cutover, means
the module can only be installed fresh.
## Open questions
- Should a provision-answered secret be acceptable too — the operator saying *this one exists* —
and does that belong to the module (it knows the secret pre-exists), the node, or the act of
taking?
- What should `take` do when a module it is cutting over has secrets the mesh minted and the
service is already initialised? Refuse, warn, or ask — and how would it know the service is
initialised?
- A module's manifest does not say whether a secret **keys stored data** or merely authenticates.
Should it, so the dangerous case can be refused rather than discovered?
- What is the reverse path: the mesh has minted one, the service ignored it, and the working value
is still on the machine. Nothing reconciles those.