From 884ba4b2e9ccdb7ac541ce479140fda33c2f4604 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Mon, 27 Jul 2026 14:07:05 -0400 Subject: [PATCH] Revert "fix(cli,relay): resolve agents by verified owner (#2615)" This reverts commit c3084b36d975259f2dfeee8edc9131b40a8bce83. --- crates/buzz-cli/README.md | 1 - crates/buzz-cli/src/commands/users.rs | 101 +----- crates/buzz-cli/src/lib.rs | 3 - crates/buzz-db/src/lib.rs | 18 - crates/buzz-db/src/migration.rs | 2 +- crates/buzz-db/src/user.rs | 121 ------- crates/buzz-relay/src/api/bridge.rs | 328 +------------------ migrations/0025_users_agent_owner_lookup.sql | 3 - 8 files changed, 9 insertions(+), 568 deletions(-) delete mode 100644 migrations/0025_users_agent_owner_lookup.sql diff --git a/crates/buzz-cli/README.md b/crates/buzz-cli/README.md index f5ab0135c3..a8c668cf06 100644 --- a/crates/buzz-cli/README.md +++ b/crates/buzz-cli/README.md @@ -56,7 +56,6 @@ buzz reactions get --event buzz users get # your own profile buzz users get --pubkey # single user buzz users get --pubkey --pubkey # batch (max 200) -buzz users get --name Honey --owner me # exact-name lookup scoped to your verified owner buzz users set-presence --status online # DMs diff --git a/crates/buzz-cli/src/commands/users.rs b/crates/buzz-cli/src/commands/users.rs index 2d36929b8d..3f8325b4b9 100644 --- a/crates/buzz-cli/src/commands/users.rs +++ b/crates/buzz-cli/src/commands/users.rs @@ -1,5 +1,3 @@ -use nostr::PublicKey; - use crate::client::{normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::validate_hex64; @@ -15,7 +13,6 @@ pub async fn cmd_get_users( client: &BuzzClient, pubkeys: &[String], name: Option<&str>, - owner: Option<&str>, format: &crate::OutputFormat, ) -> Result<(), CliError> { if let Some(query) = name { @@ -24,7 +21,7 @@ pub async fn cmd_get_users( "--name and --pubkey are mutually exclusive".into(), )); } - return search_by_name(client, query, owner, format).await; + return search_by_name(client, query, format).await; } for pk in pubkeys { @@ -41,18 +38,12 @@ pub async fn cmd_get_users( pubkeys.iter().map(|s| s.as_str()).collect() }; - let owner = resolve_owner(client, owner)?; let filter = serde_json::json!({ "kinds": [0], "authors": authors, - "limit": authors.len(), - "include_agent_owner": true, - "agent_owner": owner, + "limit": authors.len() }); let resp = client.query(&filter).await?; - let my_owner_pubkey = client - .auth_tag_owner_hex() - .unwrap_or_else(|| client.keys().public_key().to_hex()); let events: Vec = serde_json::from_str(&resp).unwrap_or_default(); let profiles: Vec = events .iter() @@ -64,7 +55,6 @@ pub async fn cmd_get_users( "pubkey".to_string(), serde_json::json!(e.get("pubkey").and_then(|v| v.as_str()).unwrap_or("")), ); - copy_owner_fields(e, obj, &my_owner_pubkey); } Some(profile) }) @@ -76,9 +66,6 @@ pub async fn cmd_get_users( .map(|p| serde_json::json!({ "pubkey": p.get("pubkey").cloned().unwrap_or_default(), "display_name": p.get("display_name").or_else(|| p.get("name")).cloned().unwrap_or_default(), - "owner_pubkey": p.get("owner_pubkey").cloned().unwrap_or_default(), - "owner_display_name": p.get("owner_display_name").cloned().unwrap_or_default(), - "owned_by_me": p.get("owned_by_me").cloned().unwrap_or_default(), })) .collect(); serde_json::to_string(&compact).unwrap_or_default() @@ -89,70 +76,23 @@ pub async fn cmd_get_users( Ok(()) } -fn copy_owner_fields( - event: &serde_json::Value, - profile: &mut serde_json::Map, - my_owner_pubkey: &str, -) { - if let Some(value) = event.get("agent_owner_pubkey") { - profile.insert("owner_pubkey".to_string(), value.clone()); - } - if let Some(value) = event.get("agent_owner_display_name") { - profile.insert("owner_display_name".to_string(), value.clone()); - } - if let Some(owner) = event - .get("agent_owner_pubkey") - .and_then(|value| value.as_str()) - { - profile.insert( - "owned_by_me".to_string(), - serde_json::json!(owner == my_owner_pubkey), - ); - } -} - -fn resolve_owner(client: &BuzzClient, owner: Option<&str>) -> Result, CliError> { - owner - .map(|owner| { - if owner == "me" { - Ok(client - .auth_tag_owner_hex() - .unwrap_or_else(|| client.keys().public_key().to_hex())) - } else { - PublicKey::parse(owner) - .map(|pubkey| pubkey.to_hex()) - .map_err(|e| { - CliError::Usage(format!("--owner must be `me`, a pubkey, or npub: {e}")) - }) - } - }) - .transpose() -} - /// Search for users by display name via NIP-50 full-text search on kind:0 profiles. /// Returns [] if the relay does not implement NIP-50 search. async fn search_by_name( client: &BuzzClient, query: &str, - owner: Option<&str>, format: &crate::OutputFormat, ) -> Result<(), CliError> { if query.trim().is_empty() { return Err(CliError::Usage("--name cannot be empty".into())); } - let owner = resolve_owner(client, owner)?; let filter = serde_json::json!({ "kinds": [0], "search": query, - "limit": 100, - "include_agent_owner": true, - "agent_owner": owner, + "limit": 100 }); let raw = client.query(&filter).await?; - let my_owner_pubkey = client - .auth_tag_owner_hex() - .unwrap_or_else(|| client.keys().public_key().to_hex()); // Parse and filter client-side for case-insensitive substring match // on display_name or name fields (NIP-50 may return broader matches). @@ -186,7 +126,6 @@ async fn search_by_name( "pubkey".to_string(), serde_json::json!(event.get("pubkey").and_then(|v| v.as_str()).unwrap_or("")), ); - copy_owner_fields(event, obj, &my_owner_pubkey); } Some(profile) }) @@ -198,9 +137,6 @@ async fn search_by_name( .map(|p| serde_json::json!({ "pubkey": p.get("pubkey").cloned().unwrap_or_default(), "display_name": p.get("display_name").or_else(|| p.get("name")).cloned().unwrap_or_default(), - "owner_pubkey": p.get("owner_pubkey").cloned().unwrap_or_default(), - "owner_display_name": p.get("owner_display_name").cloned().unwrap_or_default(), - "owned_by_me": p.get("owned_by_me").cloned().unwrap_or_default(), })) .collect(); serde_json::to_string(&compact).unwrap_or_default() @@ -375,11 +311,9 @@ pub async fn dispatch( ) -> Result<(), CliError> { use crate::UsersCmd; match cmd { - UsersCmd::Get { - pubkeys, - name, - owner, - } => cmd_get_users(client, &pubkeys, name.as_deref(), owner.as_deref(), format).await, + UsersCmd::Get { pubkeys, name } => { + cmd_get_users(client, &pubkeys, name.as_deref(), format).await + } UsersCmd::SetProfile { name, avatar, @@ -402,30 +336,9 @@ pub async fn dispatch( #[cfg(test)] mod tests { - use super::{copy_owner_fields, presence_subject}; + use super::presence_subject; use serde_json::json; - #[test] - fn copy_owner_fields_marks_authenticated_owner() { - let owner = "a".repeat(64); - let event = json!({ - "agent_owner_pubkey": owner, - "agent_owner_display_name": "John", - }); - let mut profile = serde_json::Map::new(); - copy_owner_fields(&event, &mut profile, &"a".repeat(64)); - assert_eq!(profile["owner_display_name"], "John"); - assert_eq!(profile["owned_by_me"], true); - } - - #[test] - fn copy_owner_fields_marks_other_owner() { - let event = json!({"agent_owner_pubkey": "a".repeat(64)}); - let mut profile = serde_json::Map::new(); - copy_owner_fields(&event, &mut profile, &"b".repeat(64)); - assert_eq!(profile["owned_by_me"], false); - } - #[test] fn presence_subject_uses_p_tag() { let event = json!({"pubkey": "relay", "tags": [["p", "user"]]}); diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 1571d74e28..6ab81a082d 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -808,9 +808,6 @@ pub enum UsersCmd { /// Search by display name (case-insensitive substring match) #[arg(long = "name")] name: Option, - /// Filter agents by verified owner (`me`, 64-char hex, or npub) - #[arg(long = "owner")] - owner: Option, }, /// Update the current identity's profile #[command(name = "set-profile")] diff --git a/crates/buzz-db/src/lib.rs b/crates/buzz-db/src/lib.rs index 1df6045032..9c63b2e8ab 100644 --- a/crates/buzz-db/src/lib.rs +++ b/crates/buzz-db/src/lib.rs @@ -1843,24 +1843,6 @@ impl Db { user::search_users(&self.pool, community_id, query, limit).await } - /// Return agent pubkeys whose verified owner matches `owner_pubkey`. - pub async fn list_agent_pubkeys_by_owner( - &self, - community_id: CommunityId, - owner_pubkey: &[u8], - ) -> Result>> { - user::list_agent_pubkeys_by_owner(&self.pool, community_id, owner_pubkey).await - } - - /// Fetch verified ownership metadata for the requested agent pubkeys. - pub async fn get_agent_owners( - &self, - community_id: CommunityId, - agent_pubkeys: &[Vec], - ) -> Result> { - user::get_agent_owners(&self.pool, community_id, agent_pubkeys).await - } - /// Atomically set agent owner — only if no owner is currently assigned. /// Returns Ok(true) if set, Ok(false) if an owner already exists. pub async fn set_agent_owner( diff --git a/crates/buzz-db/src/migration.rs b/crates/buzz-db/src/migration.rs index 3605ea98dc..1674b0ec4d 100644 --- a/crates/buzz-db/src/migration.rs +++ b/crates/buzz-db/src/migration.rs @@ -560,7 +560,7 @@ mod tests { let mut migrations: Vec<_> = MIGRATOR.iter().collect(); migrations.sort_by_key(|migration| migration.version); - assert_eq!(migrations.len(), 25); + assert_eq!(migrations.len(), 24); assert_eq!(migrations[0].version, 1); assert_eq!(&*migrations[0].description, "initial schema"); assert!(migrations[0] diff --git a/crates/buzz-db/src/user.rs b/crates/buzz-db/src/user.rs index f477c78f76..066fb5f5c0 100644 --- a/crates/buzz-db/src/user.rs +++ b/crates/buzz-db/src/user.rs @@ -5,17 +5,6 @@ use buzz_core::CommunityId; use sqlx::PgPool; use sqlx::Row; -/// Ownership metadata for an agent profile. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AgentOwner { - /// Raw agent public key. - pub agent_pubkey: Vec, - /// Raw owner public key, verified when the agent authenticated via NIP-OA. - pub owner_pubkey: Vec, - /// Owner's current display name, when available. - pub owner_display_name: Option, -} - /// A user's profile fields. #[derive(Debug, Clone)] pub struct UserProfile { @@ -291,65 +280,6 @@ pub async fn search_users( .collect()) } -/// Return agent pubkeys whose verified owner matches `owner_pubkey`. -pub async fn list_agent_pubkeys_by_owner( - pool: &PgPool, - community_id: CommunityId, - owner_pubkey: &[u8], -) -> Result>> { - let rows = sqlx::query_scalar::<_, Vec>( - r#" - SELECT pubkey - FROM users - WHERE community_id = $1 AND agent_owner_pubkey = $2 - "#, - ) - .bind(community_id.as_uuid()) - .bind(owner_pubkey) - .fetch_all(pool) - .await?; - Ok(rows) -} - -/// Fetch verified ownership metadata for the requested agent pubkeys. -pub async fn get_agent_owners( - pool: &PgPool, - community_id: CommunityId, - agent_pubkeys: &[Vec], -) -> Result> { - if agent_pubkeys.is_empty() { - return Ok(Vec::new()); - } - - let rows = sqlx::query_as::<_, (Vec, Vec, Option)>( - r#" - SELECT agent.pubkey, agent.agent_owner_pubkey, owner.display_name - FROM users agent - LEFT JOIN users owner - ON owner.community_id = agent.community_id - AND owner.pubkey = agent.agent_owner_pubkey - WHERE agent.community_id = $1 - AND agent.pubkey = ANY($2) - AND agent.agent_owner_pubkey IS NOT NULL - "#, - ) - .bind(community_id.as_uuid()) - .bind(agent_pubkeys) - .fetch_all(pool) - .await?; - - Ok(rows - .into_iter() - .map( - |(agent_pubkey, owner_pubkey, owner_display_name)| AgentOwner { - agent_pubkey, - owner_pubkey, - owner_display_name, - }, - ) - .collect()) -} - /// Set the owner pubkey for an agent user. /// The owner pubkey must already exist in the users table (FK constraint). /// Returns an error if the agent pubkey is not found (rows_affected == 0). @@ -534,57 +464,6 @@ mod tests { ); } - #[tokio::test] - #[ignore = "requires Postgres"] - async fn test_agent_owner_queries_are_scoped_and_include_display_name() { - let db = setup_db().await; - let community = make_community(&db.pool).await; - let agent_pk = random_pubkey(); - let other_agent_pk = random_pubkey(); - let owner_pk = random_pubkey(); - let other_owner_pk = random_pubkey(); - for pubkey in [&agent_pk, &other_agent_pk, &owner_pk, &other_owner_pk] { - ensure_user(&db.pool, community, pubkey).await.unwrap(); - } - update_user_profile( - &db.pool, - community, - &owner_pk, - Some("Owner Name"), - None, - None, - None, - ) - .await - .unwrap(); - set_agent_owner(&db.pool, community, &agent_pk, &owner_pk) - .await - .unwrap(); - set_agent_owner(&db.pool, community, &other_agent_pk, &other_owner_pk) - .await - .unwrap(); - - assert_eq!( - list_agent_pubkeys_by_owner(&db.pool, community, &owner_pk) - .await - .unwrap(), - vec![agent_pk.clone()] - ); - assert_eq!( - get_agent_owners(&db.pool, community, &[agent_pk.clone(), other_agent_pk],) - .await - .unwrap() - .into_iter() - .find(|owner| owner.agent_pubkey == agent_pk) - .unwrap(), - AgentOwner { - agent_pubkey: agent_pk, - owner_pubkey: owner_pk, - owner_display_name: Some("Owner Name".into()), - } - ); - } - /// set_channel_add_policy should persist each of the three valid policies. #[tokio::test] #[ignore = "requires Postgres"] diff --git a/crates/buzz-relay/src/api/bridge.rs b/crates/buzz-relay/src/api/bridge.rs index 9cfa878d46..c8ec0cdbc9 100644 --- a/crates/buzz-relay/src/api/bridge.rs +++ b/crates/buzz-relay/src/api/bridge.rs @@ -280,112 +280,6 @@ fn extension_flag(raw: &Value, key: &str) -> bool { raw.get(key).and_then(Value::as_bool).unwrap_or(false) } -fn extract_agent_owner(raw: &Value) -> Result>, (StatusCode, Json)> { - let Some(value) = raw.get("agent_owner") else { - return Ok(None); - }; - if value.is_null() { - return Ok(None); - } - let owner = value.as_str().ok_or_else(|| { - api_error( - StatusCode::BAD_REQUEST, - "agent_owner must be a 64-char hex pubkey", - ) - })?; - let bytes = hex::decode(owner) - .ok() - .filter(|bytes| bytes.len() == 32) - .ok_or_else(|| { - api_error( - StatusCode::BAD_REQUEST, - "agent_owner must be a 64-char hex pubkey", - ) - })?; - Ok(Some(bytes)) -} - -fn include_agent_owner(raw_filters: &[Value]) -> bool { - raw_filters - .iter() - .any(|raw| extension_flag(raw, "include_agent_owner")) -} - -fn apply_agent_owner_authors(raw: &mut Value, owned_pubkeys: &[Vec]) { - let mut owned_hex: std::collections::HashSet = - owned_pubkeys.iter().map(hex::encode).collect(); - if let Some(requested) = raw.get("authors").and_then(Value::as_array) { - let requested: std::collections::HashSet<&str> = - requested.iter().filter_map(Value::as_str).collect(); - owned_hex.retain(|pubkey| requested.contains(pubkey.as_str())); - } - raw["authors"] = Value::Array(owned_hex.into_iter().map(Value::String).collect()); -} - -fn reject_unsupported_agent_owner_filter(raw: &Value) -> Result<(), (StatusCode, Json)> { - if raw.get("agent_owner").is_some() - && (extension_flag(raw, "top_level") - || raw.get("feed_types").is_some() - || raw.get("depth_limit").is_some()) - { - return Err(api_error( - StatusCode::BAD_REQUEST, - "agent_owner is not supported with channel-window, feed, or thread filters", - )); - } - Ok(()) -} - -async fn enrich_agent_owners( - state: &AppState, - tenant: &buzz_core::tenant::TenantContext, - events: &mut [Value], -) -> Result<(), (StatusCode, Json)> { - let pubkeys: Vec> = events - .iter() - .filter_map(|event| event.get("pubkey").and_then(Value::as_str)) - .filter_map(|pubkey| hex::decode(pubkey).ok()) - .collect(); - let owners = state - .db - .get_agent_owners(tenant.community(), &pubkeys) - .await - .map_err(|e| internal_error(&format!("agent owner lookup error: {e}")))?; - let owners: std::collections::HashMap, buzz_db::user::AgentOwner> = owners - .into_iter() - .map(|owner| (owner.agent_pubkey.clone(), owner)) - .collect(); - - for event in events { - let Some(agent_pubkey) = event - .get("pubkey") - .and_then(Value::as_str) - .and_then(|pubkey| hex::decode(pubkey).ok()) - else { - continue; - }; - let Some(owner) = owners.get(&agent_pubkey) else { - continue; - }; - let Some(object) = event.as_object_mut() else { - continue; - }; - object.insert( - "agent_owner_pubkey".into(), - Value::String(hex::encode(&owner.owner_pubkey)), - ); - object.insert( - "agent_owner_display_name".into(), - owner - .owner_display_name - .clone() - .map(Value::String) - .unwrap_or(Value::Null), - ); - } - Ok(()) -} - fn extract_depth_limit(raw: &Value) -> Option { raw.get("depth_limit")? .as_u64() @@ -1073,20 +967,8 @@ async fn query_events_authed( // Two-pass parse: preserve raw JSON for custom extension fields (before_id, // depth_limit, feed_types) that nostr::Filter silently drops. - let mut raw_filters: Vec = serde_json::from_slice(body) + let raw_filters: Vec = serde_json::from_slice(body) .map_err(|e| api_error(StatusCode::BAD_REQUEST, &format!("invalid filters: {e}")))?; - for raw in &mut raw_filters { - reject_unsupported_agent_owner_filter(raw)?; - let Some(owner_pubkey) = extract_agent_owner(raw)? else { - continue; - }; - let owned_pubkeys = state - .db - .list_agent_pubkeys_by_owner(tenant.community(), &owner_pubkey) - .await - .map_err(|e| internal_error(&format!("agent owner filter error: {e}")))?; - apply_agent_owner_authors(raw, &owned_pubkeys); - } let filters: Vec = raw_filters .iter() .map(|v| serde_json::from_value(v.clone())) @@ -1323,14 +1205,6 @@ async fn query_events_authed( if handled.contains(&idx) { continue; } - if raw.get("agent_owner").is_some() - && raw - .get("authors") - .and_then(Value::as_array) - .is_some_and(Vec::is_empty) - { - continue; - } if let Some(ch_id) = extract_channel_from_filter(filter) { if !accessible_channels.contains(&ch_id) { @@ -1432,9 +1306,6 @@ async fn query_events_authed( } } - if include_agent_owner(&raw_filters) { - enrich_agent_owners(state, tenant, &mut events).await?; - } Ok(Json(Value::Array(events))) } @@ -1779,14 +1650,6 @@ async fn handle_bridge_search( for (raw, filter) in raw_filters.iter().zip(filters) { let search_mode = extract_search_mode(raw); let search_page = extract_search_page(raw); - if raw.get("agent_owner").is_some() - && raw - .get("authors") - .and_then(Value::as_array) - .is_some_and(Vec::is_empty) - { - continue; - } let search_text = match &filter.search { Some(s) if !s.is_empty() => s.clone(), _ => continue, @@ -1900,9 +1763,6 @@ async fn handle_bridge_search( } } - if include_agent_owner(raw_filters) { - enrich_agent_owners(state, tenant, &mut events).await?; - } Ok(Json(Value::Array(events))) } @@ -2928,66 +2788,6 @@ mod tests { ); } - #[test] - fn extract_agent_owner_accepts_hex_and_rejects_invalid_values() { - let owner = "a".repeat(64); - assert_eq!( - extract_agent_owner(&serde_json::json!({"agent_owner": owner})) - .unwrap() - .unwrap(), - vec![0xaa; 32] - ); - assert!(extract_agent_owner(&serde_json::json!({"agent_owner": "bad"})).is_err()); - assert!(extract_agent_owner(&serde_json::json!({"agent_owner": true})).is_err()); - assert!(extract_agent_owner(&serde_json::json!({"agent_owner": 42})).is_err()); - assert_eq!( - extract_agent_owner(&serde_json::json!({"agent_owner": null})).unwrap(), - None - ); - assert_eq!(extract_agent_owner(&serde_json::json!({})).unwrap(), None); - } - - #[test] - fn agent_owner_authors_fail_closed_and_intersect_requested_authors() { - let agent_a = vec![0xaa; 32]; - let agent_b = vec![0xbb; 32]; - let owned = vec![agent_a.clone(), agent_b]; - - let mut unscoped = serde_json::json!({}); - apply_agent_owner_authors(&mut unscoped, &owned); - let authors = unscoped["authors"].as_array().unwrap(); - assert_eq!(authors.len(), 2); - assert!(authors.contains(&serde_json::json!(hex::encode(&agent_a)))); - - let mut intersected = serde_json::json!({ - "authors": [hex::encode(&agent_a), "cc".repeat(32)] - }); - apply_agent_owner_authors(&mut intersected, &owned); - assert_eq!( - intersected["authors"], - serde_json::json!([hex::encode(agent_a)]) - ); - - let mut empty = serde_json::json!({"authors": ["cc".repeat(32)]}); - apply_agent_owner_authors(&mut empty, &owned); - assert_eq!(empty["authors"], serde_json::json!([])); - } - - #[test] - fn agent_owner_rejects_specialized_filters_that_cannot_enforce_authors() { - for raw in [ - serde_json::json!({"agent_owner": "aa".repeat(32), "top_level": true}), - serde_json::json!({"agent_owner": "aa".repeat(32), "feed_types": ["activity"]}), - serde_json::json!({"agent_owner": "aa".repeat(32), "depth_limit": 1}), - ] { - assert!(reject_unsupported_agent_owner_filter(&raw).is_err()); - } - assert!(reject_unsupported_agent_owner_filter( - &serde_json::json!({"agent_owner": "aa".repeat(32), "kinds": [0]}) - ) - .is_ok()); - } - /// `nip42_expected_relay_url` derives scheme from `config_relay_url`'s /// prefix: `wss://` → `wss`, everything else → `ws`. Deployments that run /// `ws://` in dev/test must produce a `ws://` URL that matches what @@ -3555,36 +3355,6 @@ mod tests { /// Drive a single POST /events request through the router and return the /// HTTP status code. - async fn post_query( - state: Arc, - host: &str, - pubkey_hex: &str, - body: &[u8], - ) -> (axum::http::StatusCode, Value) { - use axum::body::{to_bytes, Body}; - use axum::http::{header, Request}; - use tower::ServiceExt; - - let response = crate::router::build_router(state) - .oneshot( - Request::builder() - .method("POST") - .uri("/query") - .header(header::HOST, host) - .header("x-pubkey", pubkey_hex) - .body(Body::from(body.to_vec())) - .expect("build request"), - ) - .await - .expect("router oneshot"); - let status = response.status(); - let bytes = to_bytes(response.into_body(), usize::MAX) - .await - .expect("read response body"); - let body = serde_json::from_slice(&bytes).expect("parse response JSON"); - (status, body) - } - async fn post_events( state: Arc, host: &str, @@ -3610,102 +3380,6 @@ mod tests { .status() } - #[tokio::test] - #[ignore = "requires Postgres and Redis"] - async fn query_agent_owner_returns_only_verified_owner_matches() { - let state = bridge_handler_test_state() - .await - .expect("local Postgres and Redis required"); - let host = format!("bridge-owner-{}.local", uuid::Uuid::new_v4().simple()); - let community = state - .db - .ensure_configured_community(&host) - .await - .expect("ensure community") - .id; - let requester = Keys::generate(); - let owner_a = Keys::generate(); - let owner_b = Keys::generate(); - let agent_a = Keys::generate(); - let agent_b = Keys::generate(); - - for keys in [&requester, &owner_a, &owner_b, &agent_a, &agent_b] { - state - .db - .ensure_user(community, &keys.public_key().to_bytes()) - .await - .expect("ensure user"); - } - state - .db - .set_agent_owner( - community, - &agent_a.public_key().to_bytes(), - &owner_a.public_key().to_bytes(), - ) - .await - .expect("set first owner"); - state - .db - .set_agent_owner( - community, - &agent_b.public_key().to_bytes(), - &owner_b.public_key().to_bytes(), - ) - .await - .expect("set second owner"); - - for keys in [&agent_a, &agent_b] { - let event = EventBuilder::new(Kind::Metadata, r#"{"display_name":"Honey"}"#) - .sign_with_keys(keys) - .expect("sign profile"); - state - .db - .insert_event(community, &event, None) - .await - .expect("insert profile"); - } - - let filter = serde_json::json!([{ - "kinds": [0], - "search": "Honey", - "agent_owner": owner_a.public_key().to_hex(), - "include_agent_owner": true, - "limit": 100 - }]); - let (status, body) = post_query( - state.clone(), - &host, - &requester.public_key().to_hex(), - filter.to_string().as_bytes(), - ) - .await; - assert_eq!(status, StatusCode::OK, "query response: {body}"); - let results = body.as_array().expect("array response"); - assert_eq!(results.len(), 1); - assert_eq!(results[0]["pubkey"], agent_a.public_key().to_hex()); - assert_eq!( - results[0]["agent_owner_pubkey"], - owner_a.public_key().to_hex() - ); - - let no_match = serde_json::json!([{ - "kinds": [0], - "search": "Honey", - "agent_owner": requester.public_key().to_hex(), - "limit": 100 - }]); - let (status, body) = post_query( - state, - &host, - &requester.public_key().to_hex(), - no_match.to_string().as_bytes(), - ) - .await; - assert_eq!(status, StatusCode::OK, "query response: {body}"); - assert_eq!(body, serde_json::json!([])); - } - /// Collect buzz_events_rejected_total with (transport, reason) labels from /// a DebuggingRecorder snapshot. fn http_reject_counts( diff --git a/migrations/0025_users_agent_owner_lookup.sql b/migrations/0025_users_agent_owner_lookup.sql deleted file mode 100644 index 1a55e49e03..0000000000 --- a/migrations/0025_users_agent_owner_lookup.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE INDEX idx_users_agent_owner - ON users (community_id, agent_owner_pubkey) - WHERE agent_owner_pubkey IS NOT NULL;