Skip to main content

relay_knowledge/application/code_repository/
business_knowledge.rs

1//! Coordinates immutable repository-scoped business knowledge reads.
2
3use crate::{
4    api::{ApiError, ApiMetadata, BusinessKnowledgeQueryResponse, RequestContext},
5    application::service::RelayKnowledgeService,
6    domain::{
7        BusinessKnowledgeQueryRequest, BusinessKnowledgeResolution, BusinessKnowledgeStatus,
8        FreshnessPolicy, GraphVersion,
9    },
10};
11
12use super::{
13    errors::storage_api_error,
14    repository::{ensure_worktree_overlay_matches_current_worktree, required_code_repository},
15    scope::{
16        active_index_matches_request, indexed_commit_for_selector,
17        latest_compatible_code_scope_status, resolved_code_scope_status,
18    },
19};
20
21impl RelayKnowledgeService {
22    /// Reads business terms and declared technical mappings from the indexed graph only.
23    pub async fn business_knowledge_query(
24        &self,
25        request: BusinessKnowledgeQueryRequest,
26        context: RequestContext,
27    ) -> Result<BusinessKnowledgeQueryResponse, ApiError> {
28        let store = self.store().await.map_err(storage_api_error)?;
29        let repository_status =
30            required_code_repository(store.as_ref(), &request.repository.repository).await?;
31        if request.freshness_policy == FreshnessPolicy::GraphOnly {
32            let graph_version = store
33                .current_graph_version()
34                .await
35                .map_err(storage_api_error)?;
36            let scope = crate::api::CodeRepositoryScopeMetadata::from_status(
37                &repository_status,
38                &request.repository,
39                request.repository.ref_selector.clone(),
40            );
41            return Ok(BusinessKnowledgeQueryResponse {
42                metadata: ApiMetadata::graph_only(&context, graph_version),
43                status: BusinessKnowledgeStatus {
44                    repository_id: repository_status.repository_id,
45                    source_scope: scope.scope_id.clone(),
46                    resolved_commit_sha: scope.resolved_commit_sha.clone(),
47                    projected_graph_version: GraphVersion::ZERO,
48                    stale: true,
49                    source_count: 0,
50                    domain_count: 0,
51                    term_count: 0,
52                    mapping_count: 0,
53                    last_error: Some("graph_only freshness policy selected".to_owned()),
54                },
55                scope,
56                request,
57                resolution: BusinessKnowledgeResolution::NotFound,
58                domains: Vec::new(),
59                terms: Vec::new(),
60            });
61        }
62
63        let requested_ref = request.repository.ref_selector.clone();
64        let mut request = request_at_indexed_ref(request, &repository_status).await?;
65        if requested_ref == "worktree" {
66            ensure_worktree_overlay_matches_current_worktree(
67                &store,
68                &repository_status,
69                &request.repository,
70            )
71            .await?;
72        }
73        let mut served_stale_scope = false;
74        let scoped_status =
75            match resolved_code_scope_status(&store, &repository_status, &request.repository).await
76            {
77                Ok(status) => status,
78                Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
79                    if !active_index_matches_request(
80                        &store,
81                        &repository_status,
82                        &request.repository,
83                    )
84                    .await?
85                    {
86                        return Err(error);
87                    }
88                    let Some(stale) =
89                        latest_compatible_code_scope_status(&store, &request.repository).await?
90                    else {
91                        return Err(error);
92                    };
93                    let Some(commit) = stale.last_indexed_commit.clone() else {
94                        return Err(error);
95                    };
96                    request.repository.ref_selector = commit;
97                    served_stale_scope = true;
98                    stale
99                }
100                Err(error) => return Err(error),
101            };
102        let source_scope = scoped_status.last_indexed_scope_id.clone().ok_or_else(|| {
103            ApiError::invalid_argument(format!(
104                "code repository '{}' does not have an indexed source scope",
105                scoped_status.alias
106            ))
107        })?;
108        request.repository.repository = repository_status.repository_id.clone();
109        if let Some(commit) = scoped_status.last_indexed_commit.clone() {
110            request.repository.ref_selector = commit;
111        }
112        let projection = store
113            .business_knowledge_projection_for_scope(source_scope, request.clone())
114            .await
115            .map_err(storage_api_error)?;
116        if request.freshness_policy == FreshnessPolicy::WaitUntilFresh
117            && (projection.status.stale || scoped_status.stale)
118        {
119            return Err(ApiError::invalid_argument(format!(
120                "business knowledge projection for repository '{}' scope '{}' is stale; run repo index before querying with wait_until_fresh",
121                repository_status.alias, projection.status.source_scope
122            )));
123        }
124        let graph_version = store
125            .current_graph_version()
126            .await
127            .map_err(storage_api_error)?;
128        let mut metadata = ApiMetadata::graph_only(&context, graph_version);
129        if projection.status.stale || scoped_status.stale || served_stale_scope {
130            metadata.stale = true;
131        }
132        let mut selector = request.repository.clone();
133        selector.path_filters = scoped_status.path_filters.clone();
134        selector.language_filters = scoped_status.language_filters.clone();
135        let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
136            &scoped_status,
137            &selector,
138            requested_ref,
139        );
140        if served_stale_scope {
141            scope.stale = true;
142        }
143        let mut status = projection.status;
144        if scoped_status.stale || served_stale_scope {
145            status.stale = true;
146        }
147        Ok(BusinessKnowledgeQueryResponse {
148            metadata,
149            scope,
150            request,
151            status,
152            resolution: projection.resolution,
153            domains: projection.domains,
154            terms: projection.terms,
155        })
156    }
157}
158
159async fn request_at_indexed_ref(
160    mut request: BusinessKnowledgeQueryRequest,
161    status: &crate::domain::CodeRepositoryStatus,
162) -> Result<BusinessKnowledgeQueryRequest, ApiError> {
163    request.repository.ref_selector = indexed_commit_for_selector(
164        status,
165        &request.repository,
166        request.repository.ref_selector.clone(),
167    )
168    .await?;
169    Ok(request)
170}