relay_knowledge/application/code_repository/query/
mod.rs1use crate::{
4 api::{
5 ApiError, ApiMetadata, CodeRepositoryFeatureFlagsResponse, CodeRepositoryQueryResponse,
6 RequestContext,
7 },
8 domain::{CodeFeatureFlagRequest, CodeRetrievalRequest, FreshnessPolicy},
9};
10
11use crate::application::service::RelayKnowledgeService;
12
13use super::{
14 errors::storage_api_error,
15 freshness::{
16 CodeFeatureFlagFreshnessContext, CodeQueryFreshnessContext,
17 code_feature_flag_freshness_diagnostics, code_query_freshness_diagnostics,
18 },
19 repository::{
20 annotate_query_result_staleness, ensure_worktree_overlay_matches_current_worktree,
21 required_code_repository,
22 },
23 scope::{
24 active_index_matches_request, feature_flag_request_at_indexed_ref, indexed_source_scope,
25 latest_compatible_code_scope_status, missing_indexed_source_scope_error,
26 resolved_code_scope_status, retrieval_request_at_indexed_ref,
27 },
28 source_fallback::apply_code_grep_fallback,
29};
30
31impl RelayKnowledgeService {
32 pub async fn query_code_repository(
34 &self,
35 request: CodeRetrievalRequest,
36 context: RequestContext,
37 ) -> Result<CodeRepositoryQueryResponse, ApiError> {
38 let store = self.store().await.map_err(storage_api_error)?;
39 let status = required_code_repository(&store, &request.repository.repository).await?;
40 if request.freshness_policy == FreshnessPolicy::GraphOnly {
41 let graph_version = store
42 .current_graph_version()
43 .await
44 .map_err(storage_api_error)?;
45 let degraded_reason = "graph_only freshness policy selected".to_owned();
46 return Ok(CodeRepositoryQueryResponse {
47 metadata: ApiMetadata::graph_only(&context, graph_version),
48 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
49 &status,
50 &request.repository,
51 request.repository.ref_selector.clone(),
52 ),
53 freshness: crate::api::CodeRepositoryFreshnessDiagnostics::graph_only(
54 graph_version.get(),
55 request.freshness_policy,
56 indexed_source_scope(&status),
57 request.repository.ref_selector.clone(),
58 degraded_reason.clone(),
59 ),
60 request,
61 results: Vec::new(),
62 degraded_reason: Some(degraded_reason),
63 });
64 }
65 let requested_ref = request.repository.ref_selector.clone();
66 let mut request = retrieval_request_at_indexed_ref(request, &status).await?;
67 if requested_ref == "worktree" {
68 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
69 .await?;
70 }
71 let requested_resolved_ref = request.repository.ref_selector.clone();
72 let freshness_target = request.repository.clone();
73 let mut served_stale_scope = false;
74 let mut stale_reason = None;
75 let scoped_status = match resolved_code_scope_status(&store, &status, &request.repository)
76 .await
77 {
78 Ok(scoped_status) => scoped_status,
79 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
80 if !active_index_matches_request(&store, &status, &request.repository).await? {
81 return Err(error);
82 }
83 let Some(stale_status) =
84 latest_compatible_code_scope_status(&store, &request.repository).await?
85 else {
86 return Err(error);
87 };
88 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
89 return Err(error);
90 };
91 request.repository.ref_selector = last_indexed_commit;
92 served_stale_scope = true;
93 stale_reason = Some(
94 "requested ref is not indexed yet; served last completed code index".to_owned(),
95 );
96 stale_status
97 }
98 Err(error) => return Err(error),
99 };
100 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh && scoped_status.stale {
101 return Err(ApiError::invalid_argument(format!(
102 "code repository '{}' scope '{}' is stale; run repo index or repo update before querying with wait_until_fresh",
103 scoped_status.alias,
104 scoped_status
105 .last_indexed_scope_id
106 .as_deref()
107 .unwrap_or("unscoped")
108 )));
109 }
110 let graph_version = store
111 .current_graph_version()
112 .await
113 .map_err(storage_api_error)?;
114 let source_scope = indexed_source_scope(&scoped_status)
115 .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
116 let mut results = store
117 .search_code_scope(source_scope, request.clone())
118 .await
119 .map_err(storage_api_error)?;
120 let fallback_degraded_reason =
121 apply_code_grep_fallback(&store, &status, &scoped_status, &request, &mut results)
122 .await?;
123 let degraded_reason = results
124 .iter()
125 .find_map(|hit| hit.degraded_reason.clone())
126 .or(fallback_degraded_reason)
127 .or_else(|| scoped_status.degraded_reason.clone())
128 .or_else(|| stale_reason.clone());
129 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
130 &scoped_status,
131 &request.repository,
132 requested_ref.clone(),
133 );
134 if served_stale_scope {
135 scope.stale = true;
136 }
137 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
138 if served_stale_scope {
139 metadata.stale = true;
140 }
141 let freshness = code_query_freshness_diagnostics(
142 &store,
143 CodeQueryFreshnessContext {
144 base_status: &status,
145 scoped_status: &scoped_status,
146 request: &request,
147 requested_ref,
148 requested_resolved_ref,
149 freshness_target,
150 stale_reason,
151 degraded_reason: degraded_reason.clone(),
152 results: &results,
153 graph_version: graph_version.get(),
154 },
155 )
156 .await?;
157 annotate_query_result_staleness(&mut results, &freshness);
158
159 Ok(CodeRepositoryQueryResponse {
160 metadata,
161 scope,
162 freshness,
163 request,
164 results,
165 degraded_reason,
166 })
167 }
168
169 pub async fn query_code_repository_feature_flags(
171 &self,
172 request: CodeFeatureFlagRequest,
173 context: RequestContext,
174 ) -> Result<CodeRepositoryFeatureFlagsResponse, ApiError> {
175 let store = self.store().await.map_err(storage_api_error)?;
176 let status = required_code_repository(&store, &request.repository.repository).await?;
177 if request.freshness_policy == FreshnessPolicy::GraphOnly {
178 let graph_version = store
179 .current_graph_version()
180 .await
181 .map_err(storage_api_error)?;
182 let degraded_reason = "graph_only freshness policy selected".to_owned();
183 return Ok(CodeRepositoryFeatureFlagsResponse {
184 metadata: ApiMetadata::graph_only(&context, graph_version),
185 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
186 &status,
187 &request.repository,
188 request.repository.ref_selector.clone(),
189 ),
190 freshness: crate::api::CodeRepositoryFreshnessDiagnostics::graph_only(
191 graph_version.get(),
192 request.freshness_policy,
193 indexed_source_scope(&status),
194 request.repository.ref_selector.clone(),
195 degraded_reason.clone(),
196 ),
197 request,
198 flags: Vec::new(),
199 degraded_reason: Some(degraded_reason),
200 });
201 }
202 let requested_ref = request.repository.ref_selector.clone();
203 let mut request = feature_flag_request_at_indexed_ref(request, &status).await?;
204 if requested_ref == "worktree" {
205 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
206 .await?;
207 }
208 let requested_resolved_ref = request.repository.ref_selector.clone();
209 let freshness_target = request.repository.clone();
210 let mut served_stale_scope = false;
211 let mut stale_reason = None;
212 let scoped_status = match resolved_code_scope_status(&store, &status, &request.repository)
213 .await
214 {
215 Ok(scoped_status) => scoped_status,
216 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
217 if !active_index_matches_request(&store, &status, &request.repository).await? {
218 return Err(error);
219 }
220 let Some(stale_status) =
221 latest_compatible_code_scope_status(&store, &request.repository).await?
222 else {
223 return Err(error);
224 };
225 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
226 return Err(error);
227 };
228 request.repository.ref_selector = last_indexed_commit;
229 served_stale_scope = true;
230 stale_reason = Some(
231 "requested ref is not indexed yet; served last completed code index".to_owned(),
232 );
233 stale_status
234 }
235 Err(error) => return Err(error),
236 };
237 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh && scoped_status.stale {
238 return Err(ApiError::invalid_argument(format!(
239 "code repository '{}' scope '{}' is stale; run repo index or repo update before querying feature flags with wait_until_fresh",
240 scoped_status.alias,
241 scoped_status
242 .last_indexed_scope_id
243 .as_deref()
244 .unwrap_or("unscoped")
245 )));
246 }
247 let graph_version = store
248 .current_graph_version()
249 .await
250 .map_err(storage_api_error)?;
251 let source_scope = indexed_source_scope(&scoped_status)
252 .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
253 let flags = store
254 .search_code_feature_flags_scope(source_scope, request.clone())
255 .await
256 .map_err(storage_api_error)?;
257 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
258 &scoped_status,
259 &request.repository,
260 requested_ref.clone(),
261 );
262 if served_stale_scope {
263 scope.stale = true;
264 }
265 let degraded_reason = scoped_status
266 .degraded_reason
267 .clone()
268 .or_else(|| stale_reason.clone());
269 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
270 if served_stale_scope {
271 metadata.stale = true;
272 }
273 let freshness = code_feature_flag_freshness_diagnostics(
274 &store,
275 CodeFeatureFlagFreshnessContext {
276 base_status: &status,
277 scoped_status: &scoped_status,
278 request: &request,
279 requested_ref,
280 requested_resolved_ref,
281 freshness_target,
282 stale_reason,
283 degraded_reason: degraded_reason.clone(),
284 flags: &flags,
285 graph_version: graph_version.get(),
286 },
287 )
288 .await?;
289
290 Ok(CodeRepositoryFeatureFlagsResponse {
291 metadata,
292 scope,
293 freshness,
294 request,
295 flags,
296 degraded_reason,
297 })
298 }
299}