From 230c18d8b111f3ad6653eaff689650901c447ebe Mon Sep 17 00:00:00 2001 From: Hanwen Cheng Date: Sun, 24 May 2026 00:27:56 +0800 Subject: [PATCH] broker: remove dead IdempotencyStore (post-issue-#72) Issue #72 / PR #96 deleted POST /v1/mint-aws-creds and the crates/agentkeys-broker-server/src/handlers/mint.rs handler, which was the only production consumer of IdempotencyStore. The store remained wired through boot.rs -> AppState but no live code path read or wrote through it. Removed: - crates/agentkeys-broker-server/src/storage/idempotency.rs (the store + tests). - pub mod / pub use lines in storage/mod.rs. - idempotency_store field on AppState (state.rs). - idempotency_store field on BootArtifacts + open() block + idempotency_path() helper in boot.rs. - Assignment in main.rs AppState constructor. - agentkeys_broker_idempotency_hits / _conflicts AtomicU64 counters and their /metrics array entries (no live path bumped them); test assertion for help/type-line count updated from 10 to 8. - IdempotencyStore::open_in_memory() boilerplate in six integration tests. - Idempotency-Key sub-section + bullet in docs/operator-runbook-stage7.md and docs/stage7-demo-and-verification.md (only the parts that documented the removed metric counters + dedup feature; other /v1/mint-aws-creds doc residue from PR #96 stays for a separate doc-cleanup PR). cargo build + cargo test -p agentkeys-broker-server + cargo clippy -p agentkeys-broker-server -- -D warnings all exit 0. --- crates/agentkeys-broker-server/src/boot.rs | 24 +- crates/agentkeys-broker-server/src/main.rs | 1 - crates/agentkeys-broker-server/src/metrics.rs | 16 +- crates/agentkeys-broker-server/src/state.rs | 9 +- .../src/storage/idempotency.rs | 249 ------------------ .../src/storage/mod.rs | 2 - .../tests/auth_wallet_flow.rs | 3 +- .../tests/email_flow.rs | 5 +- .../tests/grant_flow.rs | 3 +- .../tests/oauth2_flow.rs | 5 +- .../tests/oidc_flow.rs | 3 +- .../tests/wallet_flow.rs | 3 +- docs/operator-runbook-stage7.md | 1 - docs/stage7-demo-and-verification.md | 5 +- 14 files changed, 14 insertions(+), 315 deletions(-) delete mode 100644 crates/agentkeys-broker-server/src/storage/idempotency.rs diff --git a/crates/agentkeys-broker-server/src/boot.rs b/crates/agentkeys-broker-server/src/boot.rs index 0b78f56..363d9d8 100644 --- a/crates/agentkeys-broker-server/src/boot.rs +++ b/crates/agentkeys-broker-server/src/boot.rs @@ -29,9 +29,7 @@ use crate::jwt::SessionKeypair; use crate::oidc::OidcKeypair; use crate::plugins::audit::{AuditAnchor, AuditPolicy}; use crate::plugins::PluginRegistry; -use crate::storage::{ - AuthNonceStore, GrantStore, IdempotencyStore, IdentityLinkStore, WalletStore, -}; +use crate::storage::{AuthNonceStore, GrantStore, IdentityLinkStore, WalletStore}; /// Outcome of the synchronous Tier-1 boot phase. pub struct BootArtifacts { @@ -43,7 +41,6 @@ pub struct BootArtifacts { pub nonce_store: Arc, pub grant_store: Arc, pub identity_link_store: Arc, - pub idempotency_store: Arc, /// Concrete EmailLink plugin handle (Phase A.1, US-018). Populated /// when `email_link` is in `BROKER_AUTH_METHODS` AND the /// `auth-email-link` feature is compiled in. The registry's auth @@ -186,16 +183,6 @@ pub fn run_tier1(config: &BrokerConfig) -> anyhow::Result { ) })?, ); - let idempotency_store = Arc::new(IdempotencyStore::open(&idempotency_path(config)).map_err( - |e| { - boot_fail( - env::BROKER_AUDIT_DB_PATH, - &config.audit_db_path.display().to_string(), - format!("IdempotencyStore: {}", e), - "idempotency-db", - ) - }, - )?); // 5. Validate + parse plugin selection env vars. Every name in each // list must resolve at compile time (i.e. the corresponding @@ -238,7 +225,6 @@ pub fn run_tier1(config: &BrokerConfig) -> anyhow::Result { nonce_store, grant_store, identity_link_store, - idempotency_store, #[cfg(feature = "auth-email-link")] email_link: built.email_link, #[cfg(feature = "auth-oauth2")] @@ -314,14 +300,6 @@ fn identity_links_path(config: &BrokerConfig) -> std::path::PathBuf { .unwrap_or_else(|| std::path::PathBuf::from("identity_links.sqlite")) } -fn idempotency_path(config: &BrokerConfig) -> std::path::PathBuf { - config - .audit_db_path - .parent() - .map(|p| p.join("idempotency.sqlite")) - .unwrap_or_else(|| std::path::PathBuf::from("idempotency.sqlite")) -} - #[cfg(feature = "audit-sqlite")] fn open_sqlite_anchor(config: &BrokerConfig) -> Result, anyhow::Error> { use crate::plugins::audit::sqlite::SqliteAnchor; diff --git a/crates/agentkeys-broker-server/src/main.rs b/crates/agentkeys-broker-server/src/main.rs index fc2e2fd..212a4c3 100644 --- a/crates/agentkeys-broker-server/src/main.rs +++ b/crates/agentkeys-broker-server/src/main.rs @@ -177,7 +177,6 @@ async fn main() -> anyhow::Result<()> { nonce_store: boot_artifacts.nonce_store, grant_store: boot_artifacts.grant_store, identity_link_store: boot_artifacts.identity_link_store, - idempotency_store: boot_artifacts.idempotency_store, metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::clone(&tier2), #[cfg(feature = "auth-email-link")] diff --git a/crates/agentkeys-broker-server/src/metrics.rs b/crates/agentkeys-broker-server/src/metrics.rs index c7cb382..e24bc4f 100644 --- a/crates/agentkeys-broker-server/src/metrics.rs +++ b/crates/agentkeys-broker-server/src/metrics.rs @@ -22,8 +22,6 @@ pub struct Metrics { pub auth_failed_unauthorized: AtomicU64, pub auth_failed_rate_limited: AtomicU64, pub auth_failed_other: AtomicU64, - pub idempotency_hits: AtomicU64, - pub idempotency_conflicts: AtomicU64, } impl Metrics { @@ -74,16 +72,6 @@ impl Metrics { &self.auth_failed_other, "Auth attempts that failed with any other 4xx/5xx.", ), - ( - "agentkeys_broker_idempotency_hits_total", - &self.idempotency_hits, - "Idempotency-Key replays served from cache.", - ), - ( - "agentkeys_broker_idempotency_conflicts_total", - &self.idempotency_conflicts, - "Idempotency-Key requests with mismatched body hash (422).", - ), ]; for (name, counter, help) in pairs { use std::fmt::Write as _; @@ -123,8 +111,8 @@ mod tests { let s = m.render_prometheus(); let help_count = s.matches("# HELP").count(); let type_count = s.matches("# TYPE").count(); - assert_eq!(help_count, 10); - assert_eq!(type_count, 10); + assert_eq!(help_count, 8); + assert_eq!(type_count, 8); } #[test] diff --git a/crates/agentkeys-broker-server/src/state.rs b/crates/agentkeys-broker-server/src/state.rs index 66931aa..878d6e8 100644 --- a/crates/agentkeys-broker-server/src/state.rs +++ b/crates/agentkeys-broker-server/src/state.rs @@ -7,9 +7,7 @@ use crate::metrics::Metrics; use crate::oidc::OidcKeypair; use crate::plugins::audit::AuditPolicy; use crate::plugins::PluginRegistry; -use crate::storage::{ - AuthNonceStore, GrantStore, IdempotencyStore, IdentityLinkStore, WalletStore, -}; +use crate::storage::{AuthNonceStore, GrantStore, IdentityLinkStore, WalletStore}; use crate::sts::StsClient; /// Tier-2 reachability state shared with the /readyz handler. @@ -50,11 +48,6 @@ pub struct AppState { /// OmniAccount. Recovery flow consults this to find which master /// should sign the recovery grant. pub identity_link_store: Arc, - /// Idempotency-Key dedup (Phase D-rest, US-037). Originally consumed - /// by mint_v2; after PR #96 (issue #72) the only consumer is gone, - /// so this field is currently unread by any live handler. Slated for - /// removal — see follow-up task "Remove dead IdempotencyStore code". - pub idempotency_store: Arc, /// Atomic counters surfaced via /metrics (Phase D-rest, US-036). pub metrics: Arc, pub tier2: Arc, diff --git a/crates/agentkeys-broker-server/src/storage/idempotency.rs b/crates/agentkeys-broker-server/src/storage/idempotency.rs deleted file mode 100644 index ab147aa..0000000 --- a/crates/agentkeys-broker-server/src/storage/idempotency.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! `IdempotencyStore` — Idempotency-Key dedup (Phase D-rest, US-037). -//! -//! Per plan §Phase D-rest: clients send `Idempotency-Key: ` on -//! mint endpoints. The broker: -//! 1. Hashes the request body to a deterministic fingerprint. -//! 2. Looks up the key — if present + body_hash matches, returns the -//! cached response (no re-mint, no STS quota). -//! 3. If present + body_hash differs → 422 (caller bug). -//! 4. If absent → mint normally, store the response on success. -//! -//! Window default 5 minutes. - -use std::path::Path; -use std::sync::{Mutex, MutexGuard}; - -use rusqlite::{params, Connection, OptionalExtension}; -use sha2::{Digest, Sha256}; - -use crate::plugins::auth::AuthError; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum IdempotencyOutcome { - /// Key never seen; caller proceeds with normal mint flow. - NotSeen, - /// Key + body_hash match → caller returns the cached response body. - Replay { response_body: String }, - /// Key matches but body_hash differs → caller returns 422. - Conflict, -} - -pub struct IdempotencyStore { - conn: Mutex, -} - -impl IdempotencyStore { - pub fn open(path: &Path) -> Result { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent) - .map_err(|e| AuthError::Internal(format!("create idempotency dir: {}", e)))?; - } - let conn = Connection::open(path) - .map_err(|e| AuthError::Internal(format!("open idempotency db: {}", e)))?; - let store = Self { - conn: Mutex::new(conn), - }; - store.init_schema()?; - Ok(store) - } - - pub fn open_in_memory() -> Result { - let conn = Connection::open_in_memory() - .map_err(|e| AuthError::Internal(format!("open in-memory idempotency db: {}", e)))?; - let store = Self { - conn: Mutex::new(conn), - }; - store.init_schema()?; - Ok(store) - } - - fn lock(&self) -> Result, AuthError> { - self.conn - .lock() - .map_err(|e| AuthError::Internal(format!("idempotency mutex poisoned: {}", e))) - } - - fn init_schema(&self) -> Result<(), AuthError> { - let conn = self.lock()?; - conn.execute_batch( - "PRAGMA journal_mode=WAL; - PRAGMA synchronous=NORMAL; - CREATE TABLE IF NOT EXISTS idempotency_keys ( - key TEXT PRIMARY KEY, - body_hash TEXT NOT NULL, - response_body TEXT NOT NULL, - stored_at INTEGER NOT NULL, - expires_at INTEGER NOT NULL - ); - CREATE INDEX IF NOT EXISTS idx_idempotency_expires - ON idempotency_keys(expires_at);", - ) - .map_err(|e| AuthError::Internal(format!("init idempotency schema: {}", e)))?; - Ok(()) - } - - /// Hash a request body to a deterministic fingerprint. Used as the - /// idempotency dedup key alongside the Idempotency-Key header. - pub fn body_hash(body: &[u8]) -> String { - let mut h = Sha256::new(); - h.update(body); - hex::encode(h.finalize()) - } - - /// Look up a (key, body_hash) pair. Returns: - /// - NotSeen → key absent or expired (caller proceeds with mint). - /// - Replay → key + body_hash match (return cached response). - /// - Conflict → key matches but body_hash differs (caller bug). - pub fn check( - &self, - key: &str, - body_hash: &str, - now: i64, - ) -> Result { - let conn = self.lock()?; - let row: Option<(String, String, i64)> = conn - .query_row( - "SELECT body_hash, response_body, expires_at FROM idempotency_keys WHERE key = ?1", - params![key], - |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)), - ) - .optional() - .map_err(|e| AuthError::Internal(format!("idempotency check: {}", e)))?; - match row { - None => Ok(IdempotencyOutcome::NotSeen), - Some((stored_hash, _, expires_at)) if expires_at <= now => { - let _ = stored_hash; - Ok(IdempotencyOutcome::NotSeen) - } - Some((stored_hash, response_body, _)) if stored_hash == body_hash => { - Ok(IdempotencyOutcome::Replay { response_body }) - } - Some(_) => Ok(IdempotencyOutcome::Conflict), - } - } - - /// Store a successful response keyed by (key, body_hash). Idempotent — - /// re-storing under the same key is a no-op (caller raced and lost). - pub fn store( - &self, - key: &str, - body_hash: &str, - response_body: &str, - stored_at: i64, - expires_at: i64, - ) -> Result<(), AuthError> { - let conn = self.lock()?; - conn.execute( - "INSERT OR IGNORE INTO idempotency_keys - (key, body_hash, response_body, stored_at, expires_at) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![key, body_hash, response_body, stored_at, expires_at], - ) - .map_err(|e| AuthError::Internal(format!("idempotency store: {}", e)))?; - Ok(()) - } - - /// Janitor — drop expired rows. - pub fn purge_expired(&self, now: i64) -> Result { - let conn = self.lock()?; - let n = conn - .execute( - "DELETE FROM idempotency_keys WHERE expires_at <= ?1", - params![now], - ) - .map_err(|e| AuthError::Internal(format!("idempotency purge: {}", e)))?; - Ok(n) - } - - pub fn writable(&self) -> bool { - let Ok(conn) = self.conn.lock() else { - return false; - }; - conn.execute( - "CREATE TABLE IF NOT EXISTS _readyz_probe (id INTEGER PRIMARY KEY)", - [], - ) - .is_ok() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn store() -> IdempotencyStore { - IdempotencyStore::open_in_memory().unwrap() - } - - #[test] - fn body_hash_is_sha256_hex() { - let h = IdempotencyStore::body_hash(b"hello"); - assert_eq!(h.len(), 64); - assert_eq!(h, IdempotencyStore::body_hash(b"hello")); - assert_ne!(h, IdempotencyStore::body_hash(b"world")); - } - - #[test] - fn check_not_seen_for_unknown_key() { - let s = store(); - let r = s.check("k1", "abc", 100).unwrap(); - assert_eq!(r, IdempotencyOutcome::NotSeen); - } - - #[test] - fn store_then_check_returns_replay() { - let s = store(); - s.store("k1", "abc", r#"{"creds":"..."}"#, 100, 1000) - .unwrap(); - let r = s.check("k1", "abc", 200).unwrap(); - match r { - IdempotencyOutcome::Replay { response_body } => { - assert!(response_body.contains("creds")); - } - other => panic!("expected Replay, got {:?}", other), - } - } - - #[test] - fn check_returns_conflict_when_body_hash_differs() { - let s = store(); - s.store("k1", "abc", "body1", 100, 1000).unwrap(); - let r = s.check("k1", "xyz", 200).unwrap(); - assert_eq!(r, IdempotencyOutcome::Conflict); - } - - #[test] - fn expired_key_treated_as_not_seen() { - let s = store(); - s.store("k1", "abc", "body", 100, 200).unwrap(); - let r = s.check("k1", "abc", 9999).unwrap(); - assert_eq!(r, IdempotencyOutcome::NotSeen); - } - - #[test] - fn store_is_idempotent_under_race() { - let s = store(); - s.store("k1", "abc", "body1", 100, 1000).unwrap(); - // Concurrent caller stores under same key — INSERT OR IGNORE. - s.store("k1", "abc", "body2", 100, 1000).unwrap(); - let r = s.check("k1", "abc", 200).unwrap(); - match r { - IdempotencyOutcome::Replay { response_body } => { - // First write wins. - assert_eq!(response_body, "body1"); - } - other => panic!("expected Replay, got {:?}", other), - } - } - - #[test] - fn purge_drops_expired_rows() { - let s = store(); - s.store("old", "h1", "body1", 100, 200).unwrap(); - s.store("fresh", "h2", "body2", 100, 9999).unwrap(); - let n = s.purge_expired(500).unwrap(); - assert_eq!(n, 1); - let r = s.check("fresh", "h2", 600).unwrap(); - assert!(matches!(r, IdempotencyOutcome::Replay { .. })); - } -} diff --git a/crates/agentkeys-broker-server/src/storage/mod.rs b/crates/agentkeys-broker-server/src/storage/mod.rs index 414f271..4d2087f 100644 --- a/crates/agentkeys-broker-server/src/storage/mod.rs +++ b/crates/agentkeys-broker-server/src/storage/mod.rs @@ -15,7 +15,6 @@ pub mod email_rate_limits; #[cfg(feature = "auth-email-link")] pub mod email_tokens; pub mod grants; -pub mod idempotency; pub mod identity_links; #[cfg(feature = "auth-oauth2")] pub mod oauth_pending; @@ -29,7 +28,6 @@ pub use email_rate_limits::{EmailRateLimitStore, RateLimitOutcome}; #[cfg(feature = "auth-email-link")] pub use email_tokens::{EmailConsumeOutcome, EmailRequestStatus, EmailTokenStore}; pub use grants::{Grant, GrantConsumeOutcome, GrantStore}; -pub use idempotency::{IdempotencyOutcome, IdempotencyStore}; pub use identity_links::{IdentityLink, IdentityLinkStore}; #[cfg(feature = "auth-oauth2")] pub use oauth_pending::{OAuth2PendingConsume, OAuth2PendingStatus, OAuth2PendingStore}; diff --git a/crates/agentkeys-broker-server/tests/auth_wallet_flow.rs b/crates/agentkeys-broker-server/tests/auth_wallet_flow.rs index 0122082..fe33f6f 100644 --- a/crates/agentkeys-broker-server/tests/auth_wallet_flow.rs +++ b/crates/agentkeys-broker-server/tests/auth_wallet_flow.rs @@ -25,7 +25,7 @@ use agentkeys_broker_server::{ plugins::wallet::keystore::ClientSideKeystoreProvisioner, plugins::PluginRegistry, state::{AppState, Tier2State}, - storage::{AuthNonceStore, GrantStore, IdempotencyStore, IdentityLinkStore, WalletStore}, + storage::{AuthNonceStore, GrantStore, IdentityLinkStore, WalletStore}, sts::{AssumedCredentials, StsClient, StubStsClient}, }; use k256::ecdsa::SigningKey; @@ -108,7 +108,6 @@ async fn spawn_broker_with_wallet_sig() -> (String, Arc) { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::new(Tier2State::default()), #[cfg(feature = "auth-email-link")] diff --git a/crates/agentkeys-broker-server/tests/email_flow.rs b/crates/agentkeys-broker-server/tests/email_flow.rs index 0699f48..4b98232 100644 --- a/crates/agentkeys-broker-server/tests/email_flow.rs +++ b/crates/agentkeys-broker-server/tests/email_flow.rs @@ -32,8 +32,8 @@ use agentkeys_broker_server::{ }, state::{AppState, Tier2State}, storage::{ - AuthNonceStore, EmailRateLimitStore, EmailTokenStore, GrantStore, IdempotencyStore, - IdentityLinkStore, WalletStore, + AuthNonceStore, EmailRateLimitStore, EmailTokenStore, GrantStore, IdentityLinkStore, + WalletStore, }, sts::{AssumedCredentials, StsClient, StubStsClient}, }; @@ -125,7 +125,6 @@ async fn spawn_broker() -> (String, Arc, Arc) { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::new(Tier2State::default()), email_link: Some(plugin.clone()), diff --git a/crates/agentkeys-broker-server/tests/grant_flow.rs b/crates/agentkeys-broker-server/tests/grant_flow.rs index 5e84952..007eaf3 100644 --- a/crates/agentkeys-broker-server/tests/grant_flow.rs +++ b/crates/agentkeys-broker-server/tests/grant_flow.rs @@ -32,7 +32,7 @@ use agentkeys_broker_server::{ PluginRegistry, }, state::{AppState, Tier2State}, - storage::{AuthNonceStore, GrantStore, IdempotencyStore, IdentityLinkStore, WalletStore}, + storage::{AuthNonceStore, GrantStore, IdentityLinkStore, WalletStore}, sts::{AssumedCredentials, StsClient, StubStsClient}, }; use serde_json::Value; @@ -107,7 +107,6 @@ async fn spawn_broker() -> Harness { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::new(Tier2State::default()), #[cfg(feature = "auth-email-link")] diff --git a/crates/agentkeys-broker-server/tests/oauth2_flow.rs b/crates/agentkeys-broker-server/tests/oauth2_flow.rs index 1e5cef7..09707cc 100644 --- a/crates/agentkeys-broker-server/tests/oauth2_flow.rs +++ b/crates/agentkeys-broker-server/tests/oauth2_flow.rs @@ -35,8 +35,8 @@ use agentkeys_broker_server::{ }, state::{AppState, Tier2State}, storage::{ - AuthNonceStore, EmailRateLimitStore, GrantStore, IdempotencyStore, IdentityLinkStore, - OAuth2PendingStore, WalletStore, + AuthNonceStore, EmailRateLimitStore, GrantStore, IdentityLinkStore, OAuth2PendingStore, + WalletStore, }, sts::{AssumedCredentials, StsClient, StubStsClient}, }; @@ -132,7 +132,6 @@ async fn spawn_broker() -> (String, Arc, Arc) { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::new(Tier2State::default()), #[cfg(feature = "auth-email-link")] diff --git a/crates/agentkeys-broker-server/tests/oidc_flow.rs b/crates/agentkeys-broker-server/tests/oidc_flow.rs index bedd946..3ad980a 100644 --- a/crates/agentkeys-broker-server/tests/oidc_flow.rs +++ b/crates/agentkeys-broker-server/tests/oidc_flow.rs @@ -6,7 +6,7 @@ //! 2. fetch JWKS → confirm ES256 P-256 public key + kid //! 3. mint a JWT for a real session → verify ES256 signature with the JWKS -use agentkeys_broker_server::storage::{GrantStore, IdempotencyStore, IdentityLinkStore}; +use agentkeys_broker_server::storage::{GrantStore, IdentityLinkStore}; use std::path::PathBuf; use std::sync::Arc; @@ -96,7 +96,6 @@ async fn spawn_broker() -> (String, Arc) { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: std::sync::Arc::new(agentkeys_broker_server::state::Tier2State::default()), #[cfg(feature = "auth-email-link")] diff --git a/crates/agentkeys-broker-server/tests/wallet_flow.rs b/crates/agentkeys-broker-server/tests/wallet_flow.rs index 56d30e3..7c9c360 100644 --- a/crates/agentkeys-broker-server/tests/wallet_flow.rs +++ b/crates/agentkeys-broker-server/tests/wallet_flow.rs @@ -25,7 +25,7 @@ use agentkeys_broker_server::{ PluginRegistry, }, state::{AppState, Tier2State}, - storage::{AuthNonceStore, GrantStore, IdempotencyStore, IdentityLinkStore, WalletStore}, + storage::{AuthNonceStore, GrantStore, IdentityLinkStore, WalletStore}, sts::{AssumedCredentials, StsClient, StubStsClient}, }; use serde_json::Value; @@ -100,7 +100,6 @@ async fn spawn_broker() -> Harness { nonce_store, grant_store: Arc::new(GrantStore::open_in_memory().unwrap()), identity_link_store: Arc::new(IdentityLinkStore::open_in_memory().unwrap()), - idempotency_store: Arc::new(IdempotencyStore::open_in_memory().unwrap()), metrics: Arc::new(agentkeys_broker_server::metrics::Metrics::new()), tier2: Arc::new(Tier2State::default()), #[cfg(feature = "auth-email-link")] diff --git a/docs/operator-runbook-stage7.md b/docs/operator-runbook-stage7.md index 9862854..5459cfb 100644 --- a/docs/operator-runbook-stage7.md +++ b/docs/operator-runbook-stage7.md @@ -686,7 +686,6 @@ standard exposition format. Counters available: - `agentkeys_broker_audit_writes_total` / `_failed_total` - `agentkeys_broker_auth_attempts_total` - `agentkeys_broker_auth_failed_unauthorized_total` / `_rate_limited_total` / `_other_total` -- `agentkeys_broker_idempotency_hits_total` / `_conflicts_total` When `BROKER_METRICS_ENABLED` is unset or `false`, `/metrics` returns 404 — operators who don't run a Prometheus scraper should leave it diff --git a/docs/stage7-demo-and-verification.md b/docs/stage7-demo-and-verification.md index c9fe3b8..54818dd 100644 --- a/docs/stage7-demo-and-verification.md +++ b/docs/stage7-demo-and-verification.md @@ -22,7 +22,7 @@ When you finish this guide you will have: 5. **Proven cloud-enforced per-user isolation** — `omni_A`'s derived wallet reads its own prefix; `omni_B`'s derived wallet returns `AccessDenied` from S3 itself, not from app code. -6. Inspected the audit log + metrics + idempotency cache. +6. Inspected the audit log + metrics. 7. Exercised capability grants and wallet recovery. The guide assumes the build deployed includes: @@ -1917,7 +1917,7 @@ exercise this end-to-end against the stub. --- -## 12. Metrics + idempotency (Phase D-rest) +## 12. Metrics (Phase D-rest) ### 12.1 Prometheus metrics @@ -1934,7 +1934,6 @@ curl -sS --fail-with-body https://broker.litentry.org/metrics | head -30 # agentkeys_broker_mints_failed_total 0 # agentkeys_broker_audit_writes_total 14 # agentkeys_broker_auth_attempts_total 23 -# agentkeys_broker_idempotency_hits_total 3 # … ```