Skip to main content

relay_knowledge/application/code_repository/query/
mod.rs

1//! Coordinates repository retrieval and feature-flag query workflows.
2
3use 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    /// Queries indexed symbols, references, imports, calls, and code chunks.
33    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    /// Lists configuration-driven feature flags and their code graph relationships.
170    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}