From af20831bb726f24bce9714a4a98c60bedff8fecc Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Tue, 25 Aug 2026 00:45:03 -0700 Subject: [PATCH] fix(agents): query shared instructions exactly Issue one publisher-and-slug filter per assigned coordinate so unrelated cross-product notes cannot exhaust limited relay results. Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho --- crates/buzz-acp/src/relay_skills.rs | 66 +++++++++++---- .../src/managed_agents/relay_skills.rs | 81 +++++++++++++------ 2 files changed, 107 insertions(+), 40 deletions(-) diff --git a/crates/buzz-acp/src/relay_skills.rs b/crates/buzz-acp/src/relay_skills.rs index cdff25eb86..2f5f1e6479 100644 --- a/crates/buzz-acp/src/relay_skills.rs +++ b/crates/buzz-acp/src/relay_skills.rs @@ -1,6 +1,6 @@ -//! Progressive-disclosure metadata for owner-assigned relay skills. +//! Progressive-disclosure metadata for owner-assigned shared instructions. -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use nostr::{Event, Filter, Kind, PublicKey, SingleLetterTag}; @@ -46,23 +46,11 @@ pub(crate) async fn fetch_assigned_skill_covers( if coordinates.is_empty() { return Ok(None); } - let authors = coordinates - .iter() - .map(|coordinate| coordinate.publisher) - .collect::>(); - let slugs = coordinates - .iter() - .map(|coordinate| coordinate.slug.clone()) - .collect::>(); - let filter = Filter::new() - .kind(Kind::Custom(SKILL_KIND)) - .authors(authors) - .custom_tags(SingleLetterTag::lowercase(nostr::Alphabet::D), slugs) - .limit(coordinates.len()); + let filters = exact_coordinate_filters(&coordinates); let value = rest - .query(&[filter]) + .query(&filters) .await - .map_err(|error| format!("relay skill query failed: {error}"))?; + .map_err(|error| format!("shared instruction query failed: {error}"))?; let events = value .as_array() .ok_or_else(|| "relay skill query returned non-array".to_string())? @@ -72,6 +60,20 @@ pub(crate) async fn fetch_assigned_skill_covers( Ok(render_assigned_skill_covers(&coordinates, events)) } +fn exact_coordinate_filters(coordinates: &[Coordinate]) -> Vec { + let d_tag = SingleLetterTag::lowercase(nostr::Alphabet::D); + coordinates + .iter() + .map(|coordinate| { + Filter::new() + .kind(Kind::Custom(SKILL_KIND)) + .author(coordinate.publisher) + .custom_tags(d_tag, [coordinate.slug.as_str()]) + .limit(1) + }) + .collect() +} + fn render_assigned_skill_covers(coordinates: &[Coordinate], events: Vec) -> Option { let requested = coordinates .iter() @@ -152,6 +154,36 @@ mod tests { use super::*; use nostr::{EventBuilder, Keys, Tag, Timestamp}; + #[test] + fn exact_query_filters_do_not_admit_author_slug_cross_products() { + let alpha = Keys::generate(); + let beta = Keys::generate(); + let coordinate = + |keys: &Keys, slug: &str| format!("30023:{}:{slug}", keys.public_key().to_hex()); + let coordinates = [coordinate(&alpha, "alpha"), coordinate(&beta, "beta")] + .iter() + .filter_map(|value| parse_coordinate(value)) + .collect::>(); + + let filters = exact_coordinate_filters(&coordinates) + .into_iter() + .map(|filter| serde_json::to_value(filter).unwrap()) + .collect::>(); + + assert_eq!(filters.len(), 2); + assert_eq!( + filters[0]["authors"], + serde_json::json!([alpha.public_key().to_hex()]) + ); + assert_eq!(filters[0]["#d"], serde_json::json!(["alpha"])); + assert_eq!(filters[0]["limit"], serde_json::json!(1)); + assert_eq!( + filters[1]["authors"], + serde_json::json!([beta.public_key().to_hex()]) + ); + assert_eq!(filters[1]["#d"], serde_json::json!(["beta"])); + } + #[test] fn renders_only_verified_exact_assigned_covers_in_assignment_order() { let first = Keys::generate(); diff --git a/desktop/src-tauri/src/managed_agents/relay_skills.rs b/desktop/src-tauri/src/managed_agents/relay_skills.rs index 2780df041f..3ded719b37 100644 --- a/desktop/src-tauri/src/managed_agents/relay_skills.rs +++ b/desktop/src-tauri/src/managed_agents/relay_skills.rs @@ -5,7 +5,7 @@ //! malformed publisher identities from becoming executable configuration. use std::{ - collections::{BTreeSet, HashMap, HashSet}, + collections::{HashMap, HashSet}, time::Duration, }; @@ -504,12 +504,11 @@ pub(crate) struct ResolvedRelaySkill { pub editable: bool, } -/// Fetch current NIP-23 heads for exact assigned coordinates in one relay REQ. +/// Fetch current NIP-23 heads for exact assigned coordinates. /// -/// The filter narrows by every requested author and slug, then the projection -/// checks each event against the requested `(publisher, d)` pair again. This -/// second check is the authorization boundary: a relay returning an event for -/// an unrequested cross-product coordinate cannot make it executable. +/// Each filter names one `(publisher, d)` pair. Keeping those constraints in +/// the same filter avoids the author × slug cross-product that can otherwise +/// let unrelated newer notes consume a limited result page. pub(crate) async fn resolve_assigned_relay_skills( relay_client: &NativeRelayClient, relay_url: String, @@ -525,25 +524,19 @@ pub(crate) async fn resolve_assigned_relay_skills( .iter() .map(|coordinate| parse_relay_skill_coordinate(coordinate)) .collect::, _>>()?; - let authors = parsed - .iter() - .map(|coordinate| coordinate.publisher.to_hex()) - .collect::>(); - let slugs = parsed - .iter() - .map(|coordinate| coordinate.slug.clone()) - .collect::>(); - let filter = serde_json::json!({ - "kinds": [RELAY_SKILL_KIND], - "authors": authors, - "#d": slugs, - "limit": requested.len(), - }); + let filters = exact_coordinate_filters(&parsed); let session = relay_client.session(relay_url, keys).await; - let events = session - .fetch_events(filter, RELAY_SKILL_FETCH_TIMEOUT) - .await?; + let pages = futures_util::future::try_join_all(filters.into_iter().map(|filter| { + let session = &session; + async move { + session + .fetch_events(filter, RELAY_SKILL_FETCH_TIMEOUT) + .await + } + })) + .await?; + let events = pages.into_iter().flatten().collect(); let heads = tauri::async_runtime::spawn_blocking(move || { resolved_heads_from_events(&requested, events) }) @@ -552,6 +545,20 @@ pub(crate) async fn resolve_assigned_relay_skills( Ok(heads) } +fn exact_coordinate_filters(coordinates: &[RelaySkillCoordinate]) -> Vec { + coordinates + .iter() + .map(|coordinate| { + serde_json::json!({ + "kinds": [RELAY_SKILL_KIND], + "authors": [coordinate.publisher.to_hex()], + "#d": [&coordinate.slug], + "limit": 1, + }) + }) + .collect() +} + fn resolved_heads_from_events( coordinates: &[String], events: Vec, @@ -753,6 +760,34 @@ mod tests { assert_eq!(reasons[0].code, RelaySkillIncompatibilityCode::EmptyBody); } + #[test] + fn exact_query_filters_do_not_admit_author_slug_cross_products() { + let alpha = Keys::generate(); + let beta = Keys::generate(); + let coordinates = [ + format!("30023:{}:alpha", alpha.public_key().to_hex()), + format!("30023:{}:beta", beta.public_key().to_hex()), + ] + .iter() + .map(|value| parse_relay_skill_coordinate(value).unwrap()) + .collect::>(); + + let filters = exact_coordinate_filters(&coordinates); + + assert_eq!(filters.len(), 2); + assert_eq!( + filters[0]["authors"], + serde_json::json!([alpha.public_key().to_hex()]) + ); + assert_eq!(filters[0]["#d"], serde_json::json!(["alpha"])); + assert_eq!(filters[0]["limit"], serde_json::json!(1)); + assert_eq!( + filters[1]["authors"], + serde_json::json!([beta.public_key().to_hex()]) + ); + assert_eq!(filters[1]["#d"], serde_json::json!(["beta"])); + } + #[test] fn resolves_only_exact_requested_signer_and_slug() { let alpha = Keys::generate();