diff --git a/04-ISSUES/100-a-minted-secret-cannot-be-the-one-the-service-already-uses/00-report.md b/04-ISSUES/100-a-minted-secret-cannot-be-the-one-the-service-already-uses/00-report.md new file mode 100644 index 0000000..9df2872 --- /dev/null +++ b/04-ISSUES/100-a-minted-secret-cannot-be-the-one-the-service-already-uses/00-report.md @@ -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 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.