Files
hq/04-ISSUES/180-a-modules-own-secret-cannot-be-rotated/00-report.md
T

4.7 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
resolved 2026-10-01
mesh-controller cmd/mesh-controller/secret.go (accept and now rotate)
mesh-controller internal/inventory/secrets.go (a module's own secret)
mesh-controller internal/catalogue/manifest.go (how an own secret is taken)
the controller's seat (rotate was not a verb)
mesh-controller PR 183 (`secret rotate`, the `taken` word on an own secret, `rotate` on the controller's seat with two shapes); the staged form for an applied secret stays open below
03-DESIGN/01-to-be/13-credentials-and-their-rotation.md
02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md

180 — A module's own secret cannot be rotated, and nothing rotates from the console

What was observed

Filed first in the forge's tracker on this repository (its issue 231, 2026-09-30), after a module's API token was printed by accident on the home server and the only way to change it was to generate a value by hand and secret accept it. The report said there was no rotation at all. That was half right: rotate <provision> has existed for a pair credential since design 13, pushing both ends together; what did not exist was any rotation of a module's own secret — a token, an application secret, an administrator — and any way to ask for either through the console (ADR 0152).

Why this is here

ADR 0114 decided how a single party's credential rotates: in place, in one of two forms. Read at start — the vault delivers the new value as current and the party is started again. Applied — the party's own code applies the value to a backend that takes it once, so the new value must be staged beside the current one until the party confirms it. Neither form was built, and nothing said which form a given secret needed. That last gap is the dangerous one: issue 179 is what a value looks like when the mesh believes it was taken and the software never read it. A rotation that made that happen on purpose would be worse than no rotation.

Resolved, 2026-10-01 — the read-at-start form, and the verb

An own secret says how it is taken. In a definition, "own-secrets": {"api-token": {"path": …, "taken": "at-start"}} says the module reads the file when it starts; "taken": "applied" says its own code applies the value to a backend; a path alone says neither. A definition that says neither is not rotated by the mesh, and the refusal names the word to write.

secret rotate <node> <module> <name> makes the secret anew the way the first mint did, seals it to the machine and to the operator, and sends the machine, so the module starts again on the new value, under the same restart-on that any changed file triggers. Said in the log with who asked and when, never the value. An applied secret is refused by name, until the staged form exists. A value given to the mesh rather than made by it is refused as ADR 0113 says, with the way out: change it where it lives, then accept the new value.

rotate is a verb on the controller's seat (ADR 0154), with the two shapes the mesh has: a pair credential by provision and consuming machine, or an own secret by machine, module and name. The console can ask for either.

Nothing in the catalogue says taken yet: the word ships one release ahead of its first use, and the first definitions to say it follow once this controller runs.

How it is checked: the manifest form and its refusals; the rotation against a raised store — rotates a secret taken at start, refuses an applied one, an undeclared one, an accepted one and an unknown name, each in its own words; the verb's two shapes; and, live, a secret rotated through the console on a module that reads it at start, the module restarted, and the module working.

Open — the applied form

The staged rotation ADR 0114 decided for an applied secret is not built: the vault delivering the new value beside the current one, the module's own code switching the backend and confirming, and only then the new value current. It needs a word in the module's protocol for confirm, and it is the form the identity provider's administrator and every database's superuser need. The request's second half — re-issuing a pair credential through the provider's own code rather than by re-minting and pushing — is the same shape from the provider's side, and sits with it.