From 87b1f94ac673de099a6e6d93b995df3492cebcd7 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 18 Oct 2026 12:18:35 +0200 Subject: [PATCH] move agent_token from swarm-queue-client to swarm-agent-verify per #5471, verifiers stop depending on the queue client. the module is store-free and needs only thiserror, so swarm-agent-verify gains one dependency and loses its swarm-queue-client edge. consumers import it from swarm_agent_verify; swarm-logs and swarm-metrics, whose only use of swarm-queue-client was this module, drop that dependency, as does swarm-logs-auth. --- Cargo.lock | 9 +++++---- hive-agent/Cargo.toml | 2 ++ hive-agent/src/swarm_queue.rs | 2 +- hive-subagent-mcp/Cargo.toml | 2 ++ hive-subagent-mcp/src/swarm_term.rs | 2 +- swarm-agent-verify/Cargo.toml | 3 +-- .../src/agent_token.rs | 8 +++----- swarm-agent-verify/src/lib.rs | 11 ++++++++--- swarm-logs-auth/Cargo.toml | 2 -- swarm-logs-auth/src/main.rs | 2 +- swarm-logs/Cargo.toml | 2 +- swarm-logs/README.md | 2 +- swarm-logs/src/auth.rs | 4 ++-- swarm-metrics/Cargo.toml | 2 +- swarm-metrics/src/auth.rs | 4 ++-- swarm-nats-auth/src/agent_token.rs | 4 ++-- swarm-queue-client/src/lib.rs | 4 ---- swarm-secret-client/src/queue.rs | 2 +- 18 files changed, 34 insertions(+), 33 deletions(-) rename {swarm-queue-client => swarm-agent-verify}/src/agent_token.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 18b3cb77..53903c57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,6 +1689,7 @@ dependencies = [ "serde", "serde_json", "sha1", + "swarm-agent-verify", "swarm-queue-client", "swarm-secret-client", "tempfile", @@ -2056,6 +2057,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "swarm-agent-verify", "swarm-queue-client", "swarm-secret-client", "tokio", @@ -4769,8 +4771,8 @@ version = "0.1.0" dependencies = [ "anyhow", "subtle", - "swarm-queue-client", "swarm-secret-client", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -4923,7 +4925,7 @@ dependencies = [ "clap-markdown", "reqwest", "serde_json", - "swarm-queue-client", + "swarm-agent-verify", "swarm-secret-client", "tokio", ] @@ -4937,7 +4939,6 @@ dependencies = [ "clap", "sha2 0.11.0", "swarm-agent-verify", - "swarm-queue-client", "swarm-secret-client", "tokio", "tracing", @@ -4976,7 +4977,7 @@ dependencies = [ "clap", "clap-markdown", "reqwest", - "swarm-queue-client", + "swarm-agent-verify", "swarm-secret-client", "tokio", ] diff --git a/hive-agent/Cargo.toml b/hive-agent/Cargo.toml index 37133008..f8e5d498 100644 --- a/hive-agent/Cargo.toml +++ b/hive-agent/Cargo.toml @@ -52,6 +52,8 @@ swarm-queue-client = { workspace = true, features = [ "agents-md-initial", "operator-profiles", ] } +# The spelling of the token the queue connection presents. +swarm-agent-verify.workspace = true # This agent's own queue secret, read from the store under its own certificate. swarm-secret-client.workspace = true # The directory an AGENTS.md edit is applied in, and the file it is written diff --git a/hive-agent/src/swarm_queue.rs b/hive-agent/src/swarm_queue.rs index 8a9da69d..b2652e23 100644 --- a/hive-agent/src/swarm_queue.rs +++ b/hive-agent/src/swarm_queue.rs @@ -386,7 +386,7 @@ async fn connect_as_agent( // `Sync` as async-nats's auth callback requires; the store read is not. let attempt = tokio::spawn(async move { let value = secret.for_attempt().await.map_err(|e| format!("{e:#}"))?; - swarm_queue_client::agent_token::format_agent_token(&name, &value) + swarm_agent_verify::agent_token::format_agent_token(&name, &value) .map_err(|e| e.to_string()) }); async move { attempt.await.map_err(|e| e.to_string())? } diff --git a/hive-subagent-mcp/Cargo.toml b/hive-subagent-mcp/Cargo.toml index bd5c8e46..b20e8d4f 100644 --- a/hive-subagent-mcp/Cargo.toml +++ b/hive-subagent-mcp/Cargo.toml @@ -34,6 +34,8 @@ swarm-queue-client = { workspace = true, features = [ "agent-inference", "agent-permissions", ] } +# The spelling of the token the queue connection presents. +swarm-agent-verify.workspace = true # The agent's own queue credential, read under its store identity. swarm-secret-client.workspace = true tokio.workspace = true diff --git a/hive-subagent-mcp/src/swarm_term.rs b/hive-subagent-mcp/src/swarm_term.rs index 1d17f479..428aea3f 100644 --- a/hive-subagent-mcp/src/swarm_term.rs +++ b/hive-subagent-mcp/src/swarm_term.rs @@ -237,7 +237,7 @@ pub(crate) async fn connect(target: &Arc) -> anyhow::Result/queue` in the swarm's //! secret store (`swarm_secret_client::queue`). This module knows nothing of -//! the store, so an agent formats its token without linking a store client. +//! the store. /// Marks an `auth_token` as an agent's own credential. /// diff --git a/swarm-agent-verify/src/lib.rs b/swarm-agent-verify/src/lib.rs index 24c6ad9c..7d52d316 100644 --- a/swarm-agent-verify/src/lib.rs +++ b/swarm-agent-verify/src/lib.rs @@ -1,22 +1,27 @@ //! Checking an agent's own credential against the swarm secret store. //! -//! The token is `swarm_queue_client::agent_token`'s spelling: a prefix, the +//! The token is [`agent_token`]'s spelling: a prefix, the //! agent's name, and its secret. The name is only a claim. It becomes an //! identity when the secret equals the one stored at //! `swarm/agents//queue`, and [`verify`] grants nothing on the name //! alone: every path that does not reach that comparison, and every lookup //! that fails, is a denial. //! +//! [`agent_token`] also holds the spelling the agents, `swarm-logs` and +//! `swarm-metrics` format their token in. +//! //! Two verifiers use this: the queue's auth callout (`swarm-nats-auth`) and //! the log store's `auth_request` responder (`swarm-logs-auth`). Each reads //! the store under its own certificate. +pub mod agent_token; + use std::future::Future; use std::time::Duration; +use crate::agent_token::AgentToken; use anyhow::Context; use subtle::ConstantTimeEq; -use swarm_queue_client::agent_token::AgentToken; use swarm_secret_client::client::{DEFAULT_CERT_MOUNT, Settings}; use swarm_secret_client::queue::{self, AgentCredential}; @@ -118,7 +123,7 @@ pub async fn verify( #[cfg(test)] mod tests { use super::*; - use swarm_queue_client::agent_token::parse_agent_token; + use crate::agent_token::parse_agent_token; /// A store holding at most one credential, or failing outright. enum Fake { diff --git a/swarm-logs-auth/Cargo.toml b/swarm-logs-auth/Cargo.toml index 2e5fca46..ac742a90 100644 --- a/swarm-logs-auth/Cargo.toml +++ b/swarm-logs-auth/Cargo.toml @@ -14,8 +14,6 @@ clap.workspace = true # The verdict cache's key: a digest of the bearer, never the bearer. sha2.workspace = true swarm-agent-verify.workspace = true -# The agent-token spelling. -swarm-queue-client.workspace = true swarm-secret-client.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/swarm-logs-auth/src/main.rs b/swarm-logs-auth/src/main.rs index b57484a8..9ebbd56e 100644 --- a/swarm-logs-auth/src/main.rs +++ b/swarm-logs-auth/src/main.rs @@ -28,7 +28,7 @@ use axum::extract::State; use axum::http::{HeaderMap, StatusCode, header}; use clap::Parser; use swarm_agent_verify::CredentialSource; -use swarm_queue_client::agent_token::parse_agent_token; +use swarm_agent_verify::agent_token::parse_agent_token; use verdicts::Verdicts; /// How long one store lookup may take before the request is denied. Well diff --git a/swarm-logs/Cargo.toml b/swarm-logs/Cargo.toml index 647d8505..90ec1b51 100644 --- a/swarm-logs/Cargo.toml +++ b/swarm-logs/Cargo.toml @@ -13,7 +13,7 @@ anyhow.workspace = true clap.workspace = true clap-markdown = "0.1" # The agent-token spelling the gateway's responder parses. -swarm-queue-client.workspace = true +swarm-agent-verify.workspace = true # The read of this agent's own queue secret. swarm-secret-client.workspace = true # Only to drive that one async read; the query itself is blocking. diff --git a/swarm-logs/README.md b/swarm-logs/README.md index 3a9d6643..71cdf219 100644 --- a/swarm-logs/README.md +++ b/swarm-logs/README.md @@ -25,7 +25,7 @@ As the agent itself. On each query it reads the agent's own queue secret, store certificate (`nix/agent-modules/bao.nix`), the same read `hive-agent` makes for its queue connection. It sends the secret as `Authorization: Bearer swarm-agent..`, spelled by -`swarm_queue_client::agent_token`. +`swarm_agent_verify::agent_token`. At the gateway, nginx hands a bearer of that shape to `swarm-logs-auth`, which compares it with the stored secret (`swarm-agent-verify`, the check the queue's diff --git a/swarm-logs/src/auth.rs b/swarm-logs/src/auth.rs index e19b6376..6f82c017 100644 --- a/swarm-logs/src/auth.rs +++ b/swarm-logs/src/auth.rs @@ -5,7 +5,7 @@ //! The read is `hive-agent`'s `swarm_queue` one: the same variables, the same //! path (`swarm/agents//queue`) and the same cert-auth role. The secret //! goes out as `Bearer swarm-agent..`, spelled by -//! `swarm_queue_client::agent_token`; the gateway's `swarm-logs-auth` +//! `swarm_agent_verify::agent_token`; the gateway's `swarm-logs-auth` //! responder checks it against the store. //! //! 🩸 The secret is never logged and never rendered into an error. @@ -87,7 +87,7 @@ impl Config { .with_context(|| format!("reading {}", self.path)) })?; let stored = stored.with_context(|| format!("nothing is stored at {}", self.path))?; - swarm_queue_client::agent_token::format_agent_token(&self.agent, &stored.value) + swarm_agent_verify::agent_token::format_agent_token(&self.agent, &stored.value) .with_context(|| format!("spelling the credential stored at {}", self.path)) } } diff --git a/swarm-metrics/Cargo.toml b/swarm-metrics/Cargo.toml index efae1dd2..fcb2153d 100644 --- a/swarm-metrics/Cargo.toml +++ b/swarm-metrics/Cargo.toml @@ -13,7 +13,7 @@ anyhow.workspace = true clap.workspace = true clap-markdown = "0.1" # The agent-token spelling the gateway's responder parses. -swarm-queue-client.workspace = true +swarm-agent-verify.workspace = true # The read of this agent's own queue secret. swarm-secret-client.workspace = true # Only to drive that one async read; the query itself is blocking. diff --git a/swarm-metrics/src/auth.rs b/swarm-metrics/src/auth.rs index e34f20c7..46d3ac51 100644 --- a/swarm-metrics/src/auth.rs +++ b/swarm-metrics/src/auth.rs @@ -5,7 +5,7 @@ //! The same read and the same bearer as `swarm-logs`' `auth` module: path //! `swarm/agents//queue`, the agent's cert-auth role, and //! `Bearer swarm-agent..` spelled by -//! `swarm_queue_client::agent_token`. On the metrics store's host a +//! `swarm_agent_verify::agent_token`. On the metrics store's host a //! `swarm-logs-auth` instance checks it against the store. //! //! 🩸 The secret is never logged and never rendered into an error. @@ -86,7 +86,7 @@ impl Config { .with_context(|| format!("reading {}", self.path)) })?; let stored = stored.with_context(|| format!("nothing is stored at {}", self.path))?; - swarm_queue_client::agent_token::format_agent_token(&self.agent, &stored.value) + swarm_agent_verify::agent_token::format_agent_token(&self.agent, &stored.value) .with_context(|| format!("spelling the credential stored at {}", self.path)) } } diff --git a/swarm-nats-auth/src/agent_token.rs b/swarm-nats-auth/src/agent_token.rs index 3f2cdfc1..c48869ce 100644 --- a/swarm-nats-auth/src/agent_token.rs +++ b/swarm-nats-auth/src/agent_token.rs @@ -1,6 +1,6 @@ //! Classifying a presented `auth_token`, and the grant for an agent's own. //! -//! The spelling is `swarm_queue_client::agent_token`'s, shared with the agent +//! The spelling is `swarm_agent_verify::agent_token`'s, shared with the agent //! that presents it. Whether its secret is the stored one is //! [`swarm_agent_verify::verify`]'s answer; nothing here grants on the name //! alone. @@ -9,7 +9,7 @@ //! unchanged. use swarm_agent_verify::CredentialSource; -use swarm_queue_client::agent_token::{AgentToken, Malformed, parse_agent_token}; +use swarm_agent_verify::agent_token::{AgentToken, Malformed, parse_agent_token}; use crate::policy::{Permissions, Policy}; diff --git a/swarm-queue-client/src/lib.rs b/swarm-queue-client/src/lib.rs index 018d29fe..ffde7829 100644 --- a/swarm-queue-client/src/lib.rs +++ b/swarm-queue-client/src/lib.rs @@ -200,10 +200,6 @@ pub mod wanted; /// inside it. See the module doc for why the two must not merge. pub mod agent_status; -/// The token an agent presents its own queue secret in. Store-free, so an agent -/// formats it without linking the secret-store client. -pub mod agent_token; - /// The bucket agent icons are published into — one key per agent, with no /// hive in it, unlike [`agent_status`]. See the module doc for why the /// placement stays out of the key, and for who may write it. diff --git a/swarm-secret-client/src/queue.rs b/swarm-secret-client/src/queue.rs index 845762b1..5164d610 100644 --- a/swarm-secret-client/src/queue.rs +++ b/swarm-secret-client/src/queue.rs @@ -24,7 +24,7 @@ //! renewing the other. //! //! The token an agent presents that secret in at the queue is spelled by -//! `swarm_queue_client::agent_token`, which needs no store client. +//! `swarm_agent_verify::agent_token`. use serde::{Deserialize, Serialize};