mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
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 <taylorkmho@gmail.com>
This commit is contained in:
@@ -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::<HashSet<_>>();
|
||||
let slugs = coordinates
|
||||
.iter()
|
||||
.map(|coordinate| coordinate.slug.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
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<Filter> {
|
||||
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<Event>) -> Option<String> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
let filters = exact_coordinate_filters(&coordinates)
|
||||
.into_iter()
|
||||
.map(|filter| serde_json::to_value(filter).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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();
|
||||
|
||||
@@ -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::<Result<Vec<_>, _>>()?;
|
||||
let authors = parsed
|
||||
.iter()
|
||||
.map(|coordinate| coordinate.publisher.to_hex())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let slugs = parsed
|
||||
.iter()
|
||||
.map(|coordinate| coordinate.slug.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
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<serde_json::Value> {
|
||||
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<Event>,
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user