From 35860aff9dfd4ea396bde3224bd6312bab99e9fb Mon Sep 17 00:00:00 2001 From: udev Date: Mon, 17 Aug 2026 05:24:31 +0000 Subject: [PATCH] fix: route annotation queue score lookups to queriers (#13801) ## Summary - route Discovery and annotation queue endpoints to queriers so score lookups, item hydration, and review reads do not fall through to ingesters - make Discovery source_stream required at the API response boundary - cover Discovery, queue item detail, and review routing with regression tests The QueueItem remains reference-only. Workbench and enqueue validation derive source_stream from timestamp-bounded _llm_scores queries in the enterprise implementation. Depends on https://github.com/openobserve/o2-enterprise/pull/2423 ## Testing - 5 querier routing tests - Discovery response serialization test with the enterprise feature - 16 enterprise annotation queue tests - 14 enterprise Discovery tests --- src/api/management/src/models/discovery.rs | 4 ++-- src/config/src/router.rs | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/api/management/src/models/discovery.rs b/src/api/management/src/models/discovery.rs index 01fd71e01e..1d220bb868 100644 --- a/src/api/management/src/models/discovery.rs +++ b/src/api/management/src/models/discovery.rs @@ -69,7 +69,7 @@ pub struct DiscoveryItemResponseBody { pub trace_id: Option, pub session_id: Option, pub ref_timestamp: i64, - pub source_stream: Option, + pub source_stream: String, /// Derived quality: `issue` for one unhealthy dimension and `multiple` for /// two or more unhealthy dimensions. pub quality: String, @@ -217,7 +217,7 @@ mod tests { trace_id: Some("trace-1".to_string()), session_id: None, ref_timestamp: 123, - source_stream: Some("traces".to_string()), + source_stream: "traces".to_string(), quality: DiscoveryQuality::Issue, issue_count: 1, context: Some(serde_json::json!({ diff --git a/src/config/src/router.rs b/src/config/src/router.rs index 5d3e6c0fab..24e6c966c0 100644 --- a/src/config/src/router.rs +++ b/src/config/src/router.rs @@ -14,7 +14,7 @@ // along with this program. If not, see . /// usize indicates the number of parts to skip based on their actual paths. -const QUERIER_ROUTES: [(&str, usize); 32] = [ +const QUERIER_ROUTES: [(&str, usize); 34] = [ ("config", 0), // /config ("summary", 2), // /api/{org_id}/summary ("organizations", 1), // /api/organizations @@ -48,8 +48,10 @@ const QUERIER_ROUTES: [(&str, usize); 32] = [ ("chat_stream", 3), /* /api/{org_id}/ai/chat_stream * {label_name}/ * values */ - ("service_streams", 2), // /api/{org_id}/service_streams/... - ("node/list", 2), // /api/_meta/node/list + ("discovery", 2), // /api/{org_id}/discovery + ("annotation_queues", 2), // /api/{org_id}/annotation_queues/... + ("service_streams", 2), // /api/{org_id}/service_streams/... + ("node/list", 2), // /api/_meta/node/list ]; const QUERIER_ROUTES_BY_BODY: [&str; 9] = [ "/_search", @@ -161,6 +163,15 @@ mod tests { assert!(is_querier_route("/api/org1/service_streams/_correlate")); assert!(is_querier_route("/api/org1/service_streams/_grouped")); + // AI evaluation reads execute searches and must never fall through to + // the default ingester route. + assert!(is_querier_route("/api/org1/discovery")); + assert!(is_querier_route( + "/api/org1/annotation_queues/queue-1/items/item-1" + )); + assert!(is_querier_route( + "/api/org1/annotation_queues/queue-1/items/item-1/reviews" + )); // Test trace detail routes assert!(is_querier_route( "/api/org1/default/traces/trace-id/details"