1use crate::{
4 api::{
5 ApiError, ApiMetadata, CodeRepositoryFeatureFlagsResponse,
6 CodeRepositoryFrameworkGraphResponse, CodeRepositoryQueryResponse, RequestContext,
7 },
8 domain::{
9 CodeFeatureFlagRequest, CodeRetrievalRequest, FrameworkGraph, FrameworkGraphRequest,
10 FreshnessPolicy,
11 },
12};
13
14use crate::application::service::RelayKnowledgeService;
15
16use super::{
17 errors::storage_api_error,
18 freshness::{
19 CodeFeatureFlagFreshnessContext, CodeQueryFreshnessContext, FrameworkGraphFreshnessContext,
20 code_feature_flag_freshness_diagnostics, code_query_freshness_diagnostics,
21 framework_graph_freshness_diagnostics,
22 },
23 repository::{
24 annotate_query_result_staleness, ensure_worktree_overlay_matches_current_worktree,
25 required_code_repository,
26 },
27 scope::{
28 active_index_matches_request, feature_flag_request_at_indexed_ref,
29 framework_graph_request_at_indexed_ref, indexed_source_scope,
30 latest_compatible_code_scope_status, missing_indexed_source_scope_error,
31 resolved_code_scope_status, retrieval_request_at_indexed_ref,
32 },
33 source_fallback::apply_code_grep_fallback,
34};
35
36impl RelayKnowledgeService {
37 pub async fn query_code_repository(
39 &self,
40 request: CodeRetrievalRequest,
41 context: RequestContext,
42 ) -> Result<CodeRepositoryQueryResponse, ApiError> {
43 let store = self.store().await.map_err(storage_api_error)?;
44 let status =
45 required_code_repository(store.as_ref(), &request.repository.repository).await?;
46 if request.freshness_policy == FreshnessPolicy::GraphOnly {
47 let graph_version = store
48 .current_graph_version()
49 .await
50 .map_err(storage_api_error)?;
51 let degraded_reason = "graph_only freshness policy selected".to_owned();
52 return Ok(CodeRepositoryQueryResponse {
53 metadata: ApiMetadata::graph_only(&context, graph_version),
54 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
55 &status,
56 &request.repository,
57 request.repository.ref_selector.clone(),
58 ),
59 freshness: crate::api::CodeRepositoryFreshnessDiagnostics::graph_only(
60 graph_version.get(),
61 request.freshness_policy,
62 indexed_source_scope(&status),
63 request.repository.ref_selector.clone(),
64 degraded_reason.clone(),
65 ),
66 request,
67 results: Vec::new(),
68 degraded_reason: Some(degraded_reason),
69 });
70 }
71 let requested_ref = request.repository.ref_selector.clone();
72 let mut request = retrieval_request_at_indexed_ref(request, &status).await?;
73 if requested_ref == "worktree" {
74 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
75 .await?;
76 }
77 let requested_resolved_ref = request.repository.ref_selector.clone();
78 let freshness_target = request.repository.clone();
79 let mut served_stale_scope = false;
80 let mut stale_reason = None;
81 let scoped_status = match resolved_code_scope_status(&store, &status, &request.repository)
82 .await
83 {
84 Ok(scoped_status) => scoped_status,
85 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
86 if !active_index_matches_request(&store, &status, &request.repository).await? {
87 return Err(error);
88 }
89 let Some(stale_status) =
90 latest_compatible_code_scope_status(&store, &request.repository).await?
91 else {
92 return Err(error);
93 };
94 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
95 return Err(error);
96 };
97 request.repository.ref_selector = last_indexed_commit;
98 served_stale_scope = true;
99 stale_reason = Some(
100 "requested ref is not indexed yet; served last completed code index".to_owned(),
101 );
102 stale_status
103 }
104 Err(error) => return Err(error),
105 };
106 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh && scoped_status.stale {
107 return Err(ApiError::invalid_argument(format!(
108 "code repository '{}' scope '{}' is stale; run repo index or repo update before querying with wait_until_fresh",
109 scoped_status.alias,
110 scoped_status
111 .last_indexed_scope_id
112 .as_deref()
113 .unwrap_or("unscoped")
114 )));
115 }
116 let graph_version = store
117 .current_graph_version()
118 .await
119 .map_err(storage_api_error)?;
120 let source_scope = indexed_source_scope(&scoped_status)
121 .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
122 let mut results = store
123 .search_code_scope(source_scope, request.clone())
124 .await
125 .map_err(storage_api_error)?;
126 let fallback_degraded_reason =
127 apply_code_grep_fallback(&store, &status, &scoped_status, &request, &mut results)
128 .await?;
129 let degraded_reason = results
130 .iter()
131 .find_map(|hit| hit.degraded_reason.clone())
132 .or(fallback_degraded_reason)
133 .or_else(|| scoped_status.degraded_reason.clone())
134 .or_else(|| stale_reason.clone());
135 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
136 &scoped_status,
137 &request.repository,
138 requested_ref.clone(),
139 );
140 if served_stale_scope {
141 scope.stale = true;
142 }
143 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
144 if served_stale_scope {
145 metadata.stale = true;
146 }
147 let freshness = code_query_freshness_diagnostics(
148 &store,
149 CodeQueryFreshnessContext {
150 base_status: &status,
151 scoped_status: &scoped_status,
152 request: &request,
153 requested_ref,
154 requested_resolved_ref,
155 freshness_target,
156 stale_reason,
157 degraded_reason: degraded_reason.clone(),
158 results: &results,
159 graph_version: graph_version.get(),
160 },
161 )
162 .await?;
163 annotate_query_result_staleness(&mut results, &freshness);
164
165 Ok(CodeRepositoryQueryResponse {
166 metadata,
167 scope,
168 freshness,
169 request,
170 results,
171 degraded_reason,
172 })
173 }
174
175 pub async fn query_code_repository_feature_flags(
177 &self,
178 request: CodeFeatureFlagRequest,
179 context: RequestContext,
180 ) -> Result<CodeRepositoryFeatureFlagsResponse, ApiError> {
181 let store = self.store().await.map_err(storage_api_error)?;
182 let status =
183 required_code_repository(store.as_ref(), &request.repository.repository).await?;
184 if request.freshness_policy == FreshnessPolicy::GraphOnly {
185 let graph_version = store
186 .current_graph_version()
187 .await
188 .map_err(storage_api_error)?;
189 let degraded_reason = "graph_only freshness policy selected".to_owned();
190 return Ok(CodeRepositoryFeatureFlagsResponse {
191 metadata: ApiMetadata::graph_only(&context, graph_version),
192 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
193 &status,
194 &request.repository,
195 request.repository.ref_selector.clone(),
196 ),
197 freshness: crate::api::CodeRepositoryFreshnessDiagnostics::graph_only(
198 graph_version.get(),
199 request.freshness_policy,
200 indexed_source_scope(&status),
201 request.repository.ref_selector.clone(),
202 degraded_reason.clone(),
203 ),
204 request,
205 flags: Vec::new(),
206 degraded_reason: Some(degraded_reason),
207 });
208 }
209 let requested_ref = request.repository.ref_selector.clone();
210 let mut request = feature_flag_request_at_indexed_ref(request, &status).await?;
211 if requested_ref == "worktree" {
212 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
213 .await?;
214 }
215 let requested_resolved_ref = request.repository.ref_selector.clone();
216 let freshness_target = request.repository.clone();
217 let mut served_stale_scope = false;
218 let mut stale_reason = None;
219 let scoped_status = match resolved_code_scope_status(&store, &status, &request.repository)
220 .await
221 {
222 Ok(scoped_status) => scoped_status,
223 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
224 if !active_index_matches_request(&store, &status, &request.repository).await? {
225 return Err(error);
226 }
227 let Some(stale_status) =
228 latest_compatible_code_scope_status(&store, &request.repository).await?
229 else {
230 return Err(error);
231 };
232 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
233 return Err(error);
234 };
235 request.repository.ref_selector = last_indexed_commit;
236 served_stale_scope = true;
237 stale_reason = Some(
238 "requested ref is not indexed yet; served last completed code index".to_owned(),
239 );
240 stale_status
241 }
242 Err(error) => return Err(error),
243 };
244 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh && scoped_status.stale {
245 return Err(ApiError::invalid_argument(format!(
246 "code repository '{}' scope '{}' is stale; run repo index or repo update before querying feature flags with wait_until_fresh",
247 scoped_status.alias,
248 scoped_status
249 .last_indexed_scope_id
250 .as_deref()
251 .unwrap_or("unscoped")
252 )));
253 }
254 let graph_version = store
255 .current_graph_version()
256 .await
257 .map_err(storage_api_error)?;
258 let source_scope = indexed_source_scope(&scoped_status)
259 .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
260 let flags = store
261 .search_code_feature_flags_scope(source_scope, request.clone())
262 .await
263 .map_err(storage_api_error)?;
264 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
265 &scoped_status,
266 &request.repository,
267 requested_ref.clone(),
268 );
269 if served_stale_scope {
270 scope.stale = true;
271 }
272 let degraded_reason = scoped_status
273 .degraded_reason
274 .clone()
275 .or_else(|| stale_reason.clone());
276 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
277 if served_stale_scope {
278 metadata.stale = true;
279 }
280 let freshness = code_feature_flag_freshness_diagnostics(
281 &store,
282 CodeFeatureFlagFreshnessContext {
283 base_status: &status,
284 scoped_status: &scoped_status,
285 request: &request,
286 requested_ref,
287 requested_resolved_ref,
288 freshness_target,
289 stale_reason,
290 degraded_reason: degraded_reason.clone(),
291 flags: &flags,
292 graph_version: graph_version.get(),
293 },
294 )
295 .await?;
296
297 Ok(CodeRepositoryFeatureFlagsResponse {
298 metadata,
299 scope,
300 freshness,
301 request,
302 flags,
303 degraded_reason,
304 })
305 }
306
307 pub async fn query_code_repository_framework_graph(
309 &self,
310 request: FrameworkGraphRequest,
311 context: RequestContext,
312 ) -> Result<CodeRepositoryFrameworkGraphResponse, ApiError> {
313 let store = self.store().await.map_err(storage_api_error)?;
314 let status =
315 required_code_repository(store.as_ref(), &request.repository.repository).await?;
316 if request.freshness_policy == FreshnessPolicy::GraphOnly {
317 let graph_version = store
318 .current_graph_version()
319 .await
320 .map_err(storage_api_error)?;
321 let degraded_reason = "graph_only freshness policy selected".to_owned();
322 return Ok(CodeRepositoryFrameworkGraphResponse {
323 metadata: ApiMetadata::graph_only(&context, graph_version),
324 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
325 &status,
326 &request.repository,
327 request.repository.ref_selector.clone(),
328 ),
329 freshness: crate::api::CodeRepositoryFreshnessDiagnostics::graph_only(
330 graph_version.get(),
331 request.freshness_policy,
332 indexed_source_scope(&status),
333 request.repository.ref_selector.clone(),
334 degraded_reason.clone(),
335 ),
336 request,
337 graph: FrameworkGraph {
338 nodes: Vec::new(),
339 edges: Vec::new(),
340 truncated: false,
341 },
342 degraded_reason: Some(degraded_reason),
343 });
344 }
345 let requested_ref = request.repository.ref_selector.clone();
346 let mut request = framework_graph_request_at_indexed_ref(request, &status).await?;
347 if requested_ref == "worktree" {
348 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
349 .await?;
350 }
351 let requested_resolved_ref = request.repository.ref_selector.clone();
352 let freshness_target = request.repository.clone();
353 let mut served_stale_scope = false;
354 let mut stale_reason = None;
355 let scoped_status = match resolved_code_scope_status(&store, &status, &request.repository)
356 .await
357 {
358 Ok(scoped_status) => scoped_status,
359 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
360 if !active_index_matches_request(&store, &status, &request.repository).await? {
361 return Err(error);
362 }
363 let Some(stale_status) =
364 latest_compatible_code_scope_status(&store, &request.repository).await?
365 else {
366 return Err(error);
367 };
368 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
369 return Err(error);
370 };
371 request.repository.ref_selector = last_indexed_commit;
372 served_stale_scope = true;
373 stale_reason = Some(
374 "requested ref is not indexed yet; served last completed code index".to_owned(),
375 );
376 stale_status
377 }
378 Err(error) => return Err(error),
379 };
380 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh && scoped_status.stale {
381 return Err(ApiError::invalid_argument(format!(
382 "code repository '{}' scope '{}' is stale; run repo index or repo update before querying framework facts with wait_until_fresh",
383 scoped_status.alias,
384 scoped_status
385 .last_indexed_scope_id
386 .as_deref()
387 .unwrap_or("unscoped")
388 )));
389 }
390 let graph_version = store
391 .current_graph_version()
392 .await
393 .map_err(storage_api_error)?;
394 let source_scope = indexed_source_scope(&scoped_status)
395 .ok_or_else(|| missing_indexed_source_scope_error(&scoped_status))?;
396 let graph = store
397 .search_framework_graph_scope(source_scope, request.clone())
398 .await
399 .map_err(storage_api_error)?;
400 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
401 &scoped_status,
402 &request.repository,
403 requested_ref.clone(),
404 );
405 if served_stale_scope {
406 scope.stale = true;
407 }
408 let degraded_reason = scoped_status
409 .degraded_reason
410 .clone()
411 .or_else(|| stale_reason.clone());
412 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
413 if served_stale_scope {
414 metadata.stale = true;
415 }
416 let freshness = framework_graph_freshness_diagnostics(
417 &store,
418 FrameworkGraphFreshnessContext {
419 base_status: &status,
420 scoped_status: &scoped_status,
421 request: &request,
422 requested_ref,
423 requested_resolved_ref,
424 freshness_target,
425 stale_reason,
426 degraded_reason: degraded_reason.clone(),
427 graph: &graph,
428 graph_version: graph_version.get(),
429 },
430 )
431 .await?;
432
433 Ok(CodeRepositoryFrameworkGraphResponse {
434 metadata,
435 scope,
436 freshness,
437 request,
438 graph,
439 degraded_reason,
440 })
441 }
442}