Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: sweep agents' legacy hyperhive-* forge tokens at start

Before b5d07d4d, hive-c0re minted a new `hyperhive-<unix-seconds>` token
for an agent on every spawn and rebuild and never revoked one, so every
live agent's forge user carries a pile of write-scoped tokens nothing
holds. Nothing in the tree lists or deletes them.

On each start, swarm-controller now walks the store's hive-agent-*
roster (the one the swarm-agent mint pass walks), and for every agent
whose swarm-agent token that pass would keep, deletes each token named
exactly `hyperhive-<digits>`. It logs the count per agent and a total.

- `core` is refused by name in both the roster filter and the per-user
  delete: hive-c0re still names core's live admin token
  `hyperhive-<unix-seconds>`.
- An agent whose swarm-agent token is not current is skipped, because
  consumers fall back to `<state>/forge-token`, the last hyperhive-*
  token, until the swarm token is fetched.
- A failed list or delete is logged and skipped; the sweep does not
  retry and never blocks startup. A second start deletes nothing.

Tokens on forge users of agents no longer on the store roster (already
destroyed) are not reached.

Closes #4644
This commit is contained in:
atlas 2026-09-26 15:44:38 +02:00 • committed by mara
commit 23e0c313b8
4 changed files with 368 additions and 5 deletions

View file

@ -27,7 +27,7 @@ use super::Client;
/// The forge's name for every agent token this module mints.
///
/// Deliberately not `hyperhive-…`: that prefix is what `hive-c0re` named each
/// of its re-mints, and a later sweep of those must never match this one.
/// of its re-mints, and [`super::legacy_tokens`] deletes those.
pub const AGENT_TOKEN_NAME: &str = "swarm-agent";
/// The scopes an agent token carries. Byte-identical to the `TOKEN_SCOPES`
@ -167,7 +167,7 @@ pub(super) fn is_not_found(e: &ForgejoError) -> bool {
impl Client {
/// Every access token the forge lists for `agent`, or `None` when the
/// forge has no such user.
async fn list_agent_tokens(&self, agent: &str) -> Result<Option<Vec<AccessToken>>> {
pub(super) async fn list_agent_tokens(&self, agent: &str) -> Result<Option<Vec<AccessToken>>> {
match self.api.admin_list_user_access_tokens(agent).all().await {
Ok(tokens) => Ok(Some(tokens)),
Err(e) if is_not_found(&e) => Ok(None),
@ -270,7 +270,7 @@ impl Client {
}
/// What the forge and the store say about `agent`'s token.
async fn observe_agent(
pub(super) async fn observe_agent(
&self,
store: &swarm_secret_client::SecretStore,
agent: &str,