The forge mints its own API token with the admin account the vault delivers

The runtime beside the forge served 0 tools: GiteaClient.fromEnv required a token
(settings or MESH_GITEA_TOKEN), nobody had one to give — the mesh raised the forge —
and putting one in settings would store a secret in plaintext in the inventory. So
the fifteen tools registered nothing and the watcher logged "not watching".

What the mesh does deliver is the admin account: a login the manifest names and a
password the vault minted and the host unsealed into a file (ADR 0086). That is
enough to mint a token, so the module does (hq issue 100, the forge's tools):
POST /users/{admin}/tokens over basic auth, scoped to write:repository and
write:issue — the least the tools and the repo watcher need — kept at 0600 in the
module's own state (/var/lib/mesh/gitea/state, a new directory resource the runtime
mounts writable), read back on the next start, and minted afresh when the forge
answers 401 to it or the kept file is gone. A forge whose data came from the
predecessor has no mesh-admin: that is reported in plain words on every poll until
it clears, once per reason, not crash-looped. A configured token still wins and is
never minted over.

The mint happens on the first call, not at registration: a contributor is
synchronous, and a forge not yet answering must not keep the runtime from serving.
One source per kept file in a process, or the watcher and the tools would each
renew on a 401 and drop the other's token by name.
This commit is contained in:
2026-09-23 23:48:36 +02:00
parent 9f2c678355
commit ed094031fd
9 changed files with 625 additions and 28 deletions
+6 -3
View File
@@ -295,12 +295,15 @@ export function getGiteaTools(gitea: GiteaClient): ToolDefinition[] {
];
}
// The tools exist only when a token can be found; without one, gitea contributes none rather than
// failing the whole runtime.
// The tools exist when the client has a way to a token: one configured, or the admin account to mint
// one with (token.ts). The mint itself happens on the first call, not here — a contributor is
// synchronous, and a forge not yet answering must not keep the runtime from serving. Without either
// way, gitea contributes none rather than failing the whole runtime, and says why.
registerModuleTools("gitea", (env) => {
try {
return getGiteaTools(GiteaClient.fromEnv(env));
} catch {
} catch (err) {
console.log(`[gitea] no tools — ${err instanceof Error ? err.message : String(err)}`);
return [];
}
});