//! The review classifier agreement: where its inference keys, its queue //! client secret and its forge token live in the store, and what each object //! holds. //! //! The operator writes the keys by hand in the store's UI; the classifier //! reads them and the client secret under its own certificate, a key once per //! run. Its read stanza in `swarm-bao.nix` names exactly these three paths. The forge token is `swarm-controller`'s alone: it mints it, and //! posts the classifier's reviews with it. use serde::{Deserialize, Serialize}; use crate::{ Error, path::{Kind, principal_prefix}, }; /// The classifier's name segment under [`Kind::Service`]. const SERVICE: &str = "review-classifier"; /// The path holding the Anthropic API key the classifier runs `claude` with. /// /// # Errors /// Never in practice: the name segment is a constant. The `Result` is /// [`principal_prefix`]'s. pub fn api_key_path() -> Result { let prefix = principal_prefix(Kind::Service, SERVICE)?; Ok(format!("{prefix}/anthropic-api-key")) } /// The path holding the provider key the classifier runs an ACP agent with, /// as a [`crate::acp::ProviderKey`]. /// /// # Errors /// Never in practice: the name segment is a constant. The `Result` is /// [`principal_prefix`]'s. pub fn acp_provider_key_path() -> Result { let prefix = principal_prefix(Kind::Service, SERVICE)?; Ok(format!("{prefix}/acp-provider")) } /// What [`api_key_path`] holds. /// /// No `Debug`: `api_key` is a live provider credential. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ApiKey { /// The key itself, under the field name [`crate::acp::ProviderKey`] uses. /// The operator enters this field name in the store's UI. pub api_key: String, } /// The path holding the token of the classifier's forge account, as a /// [`crate::forge::Credential`]. /// /// # Errors /// Never in practice: the name segment is a constant. The `Result` is /// [`principal_prefix`]'s. pub fn forge_token_path() -> Result { let prefix = principal_prefix(Kind::Service, SERVICE)?; Ok(format!("{prefix}/forge/token")) } /// The path holding the queue client secret of the classifier's OIDC client /// `client_id`, where `swarm-secret-publisher.nix` puts every swarm service's. /// /// # Errors /// [`Error::PathSegment`] when `client_id` is not a single path segment. pub fn client_secret_path(client_id: &str) -> Result { let prefix = principal_prefix(Kind::Service, client_id)?; Ok(format!("{prefix}/oidc/client")) } /// What [`client_secret_path`] holds. /// /// No `Debug`: `value` authenticates the classifier at the queue. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ClientSecret { /// The secret, under the field name the publisher writes. pub value: String, } #[cfg(test)] mod tests { use super::*; #[test] fn the_paths_are_the_ones_the_store_grants() { // Spelled out: the operator types the first into the store's UI, and // `swarm-bao.nix` grants each literal. assert_eq!( api_key_path().expect("a constant segment is legal"), "swarm/services/review-classifier/anthropic-api-key" ); assert_eq!( acp_provider_key_path().expect("a constant segment is legal"), "swarm/services/review-classifier/acp-provider" ); assert_eq!( forge_token_path().expect("a constant segment is legal"), "swarm/services/review-classifier/forge/token" ); assert_eq!( client_secret_path("swarm-review-classifier").expect("a plain id is legal"), "swarm/services/swarm-review-classifier/oidc/client" ); } #[test] fn the_stored_key_field_is_api_key() { let stored: ApiKey = serde_json::from_str(r#"{"api_key":"k"}"#).expect("the documented shape decodes"); assert_eq!(stored.api_key, "k"); } }