1use std::{path::Path, sync::Arc};
2
3mod catalog;
4mod control_plane;
5mod diagnostics;
6mod framework;
7mod indexing;
8mod repository;
9mod repository_set_store;
10mod routing;
11mod status;
12mod totals;
13
14use crate::{
15 clock::system_now_millis_or_zero as now_millis,
16 domain::{
17 CodeFeatureFlagGraph, CodeFeatureFlagRequest, CodeIndexBatch, CodeIndexCheckpoint,
18 CodeIndexPublicationFence, CodeIndexSession, CodeIndexSnapshot, CodeIndexSummary,
19 CodeRepositoryRegistration, CodeRepositoryRemovalSummary, CodeRepositoryReport,
20 CodeRepositoryStatus, CodeRepositoryTotals, CodeRetrievalHit, CodeRetrievalRequest,
21 CodeSymbolGenerationCounts, SoftwareGlobalProjection, SoftwareGlobalRequest,
22 },
23 paths::RuntimePaths,
24 storage::{
25 BusinessKnowledgeStore, CodeImpactChanges, CodeIndexPublicationStore,
26 CodeIndexPublicationTarget, CodeIndexSourceStore, CodeIndexTaskClaimRequest,
27 CodeIndexTaskCompletion, CodeIndexTaskFailure, CodeIndexTaskLeaseRecord,
28 CodeIndexTaskLeaseRecovery, CodeIndexTaskLeaseRenewal, CodeIndexTaskStore,
29 CodeQueryReadStore, CodeScopeRetentionRequest, CodeScopeRetentionStore,
30 RepositoryCatalogStore, SoftwareProjectionStore, SqliteGraphStore, StorageError,
31 StorageFuture,
32 },
33};
34
35use catalog::{SqliteShardCatalog, initialize_catalog_schema};
36use routing::{report_matches_active_control, repository_store_for_report, source_scope_store};
37
38#[derive(Clone)]
41pub struct PartitionedSqliteKnowledgeStore {
42 control: Arc<SqliteGraphStore>,
43 catalog: Arc<SqliteShardCatalog>,
44}
45
46impl PartitionedSqliteKnowledgeStore {
47 pub fn open(control_path: impl AsRef<Path>, paths: RuntimePaths) -> Result<Self, StorageError> {
48 let control_path = control_path.as_ref().to_path_buf();
49 let control = Arc::new(SqliteGraphStore::open(&control_path)?);
50 initialize_catalog_schema(&control_path)?;
51
52 Ok(Self {
53 control,
54 catalog: Arc::new(SqliteShardCatalog::new(control_path, paths)),
55 })
56 }
57}
58
59impl RepositoryCatalogStore for PartitionedSqliteKnowledgeStore {
60 fn upsert_code_repository(
61 &self,
62 registration: CodeRepositoryRegistration,
63 ) -> StorageFuture<'_, CodeRepositoryStatus> {
64 repository::upsert(self, registration)
65 }
66
67 fn code_repository_status(
68 &self,
69 repository: String,
70 ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
71 repository::status(self, repository)
72 }
73
74 fn list_code_repositories(&self) -> StorageFuture<'_, Vec<CodeRepositoryStatus>> {
75 control_plane::list_code_repositories(self)
76 }
77
78 fn remove_code_repository(
79 &self,
80 repository: String,
81 now_ms: u64,
82 ) -> StorageFuture<'_, Option<CodeRepositoryRemovalSummary>> {
83 repository::remove(self, repository, now_ms)
84 }
85
86 fn code_repository_scope_status(
87 &self,
88 repository: String,
89 resolved_commit_sha: String,
90 path_filters: Vec<String>,
91 language_filters: Vec<String>,
92 ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
93 repository::scope_status(
94 self,
95 repository,
96 resolved_commit_sha,
97 path_filters,
98 language_filters,
99 )
100 }
101
102 fn latest_code_repository_scope_status(
103 &self,
104 repository: String,
105 path_filters: Vec<String>,
106 language_filters: Vec<String>,
107 ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
108 repository::latest_scope_status(self, repository, path_filters, language_filters)
109 }
110}
111
112impl CodeIndexTaskStore for PartitionedSqliteKnowledgeStore {
113 fn queue_code_index_task(
114 &self,
115 task: crate::storage::CodeIndexTaskSeed,
116 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
117 let control = Arc::clone(&self.control);
118 Box::pin(async move { control.queue_code_index_task(task).await })
119 }
120
121 fn claim_code_index_task(
122 &self,
123 request: CodeIndexTaskClaimRequest,
124 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
125 self.control.claim_code_index_task(request)
126 }
127
128 fn recover_code_index_task_leases(
129 &self,
130 now_ms: u64,
131 max_attempts: u32,
132 ) -> StorageFuture<'_, ()> {
133 self.control
134 .recover_code_index_task_leases(now_ms, max_attempts)
135 }
136
137 fn running_code_index_task_leases(&self) -> StorageFuture<'_, Vec<CodeIndexTaskLeaseRecord>> {
138 self.control.running_code_index_task_leases()
139 }
140
141 fn recover_code_index_task_leases_by_task(
142 &self,
143 request: CodeIndexTaskLeaseRecovery,
144 ) -> StorageFuture<'_, usize> {
145 self.control.recover_code_index_task_leases_by_task(request)
146 }
147
148 fn reset_code_index_tasks(
149 &self,
150 repository_id: String,
151 now_ms: u64,
152 ) -> StorageFuture<'_, Vec<crate::domain::CodeIndexTaskRecord>> {
153 self.control.reset_code_index_tasks(repository_id, now_ms)
154 }
155
156 fn renew_code_index_task_lease(
157 &self,
158 request: CodeIndexTaskLeaseRenewal,
159 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
160 self.control.renew_code_index_task_lease(request)
161 }
162
163 fn complete_code_index_task(
164 &self,
165 request: CodeIndexTaskCompletion,
166 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
167 self.control.complete_code_index_task(request)
168 }
169
170 fn run_code_index_post_maintenance(
171 &self,
172 repository_id: String,
173 source_scope: String,
174 ) -> StorageFuture<'_, ()> {
175 let this = self.clone();
176 Box::pin(async move {
177 let active = this
178 .catalog
179 .active_repository_for_scope(source_scope.clone())
180 .await?
181 .as_deref()
182 == Some(repository_id.as_str());
183 let shard = if active {
184 this.catalog
185 .existing_repository_store(repository_id.clone())
186 .await?
187 } else {
188 this.catalog
189 .checkpoint_repository_store(repository_id.clone())
190 .await?
191 }
192 .ok_or_else(|| {
193 StorageError::InvalidInput(format!(
194 "repository shard for '{repository_id}' is unavailable for post-index maintenance"
195 ))
196 })?;
197 shard
198 .run_code_index_post_maintenance(repository_id, source_scope)
199 .await
200 })
201 }
202
203 fn code_index_publication_receipt(
204 &self,
205 task_id: String,
206 repository_id: String,
207 source_scope: String,
208 now_ms: u64,
209 ) -> StorageFuture<'_, bool> {
210 self.control
211 .code_index_publication_receipt(task_id, repository_id, source_scope, now_ms)
212 }
213
214 fn reconcile_code_index_publication_with_fence(
215 &self,
216 target: CodeIndexPublicationTarget,
217 fence: CodeIndexPublicationFence,
218 ) -> StorageFuture<'_, bool> {
219 indexing::publication::reconcile(self, target, fence)
220 }
221
222 fn fail_code_index_task(
223 &self,
224 request: CodeIndexTaskFailure,
225 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskRecord> {
226 self.control.fail_code_index_task(request)
227 }
228
229 fn code_index_task(
230 &self,
231 task_id: String,
232 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
233 self.control.code_index_task(task_id)
234 }
235
236 fn active_code_index_task(
237 &self,
238 repository_id: String,
239 ) -> StorageFuture<'_, Option<crate::domain::CodeIndexTaskRecord>> {
240 self.control.active_code_index_task(repository_id)
241 }
242
243 fn code_index_task_queue_status(
244 &self,
245 ) -> StorageFuture<'_, crate::domain::CodeIndexTaskQueueStatus> {
246 self.control.code_index_task_queue_status()
247 }
248}
249
250impl CodeScopeRetentionStore for PartitionedSqliteKnowledgeStore {
251 fn code_scope_retention(
252 &self,
253 repository_id: String,
254 ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
255 indexing::retention::status(self, repository_id)
256 }
257
258 fn prune_code_repository_scopes(
259 &self,
260 request: CodeScopeRetentionRequest,
261 ) -> StorageFuture<'_, crate::domain::CodeScopeRetentionSummary> {
262 indexing::retention::prune(self, request)
263 }
264
265 fn schedule_code_repository_retention(
266 &self,
267 max_indexed_repositories: usize,
268 now_ms: u64,
269 ) -> StorageFuture<'_, Option<String>> {
270 self.control
271 .schedule_code_repository_retention(max_indexed_repositories, now_ms)
272 }
273
274 fn code_repository_retention_scan_pending(&self) -> StorageFuture<'_, bool> {
275 self.control.code_repository_retention_scan_pending()
276 }
277}
278
279impl CodeIndexSourceStore for PartitionedSqliteKnowledgeStore {
280 fn code_file_fingerprints(
281 &self,
282 repository_id: String,
283 ) -> StorageFuture<'_, Vec<crate::domain::CodeFileFingerprint>> {
284 indexing::file_index::fingerprints(self, repository_id)
285 }
286
287 fn code_file_fingerprints_for_scope(
288 &self,
289 source_scope: String,
290 ) -> StorageFuture<'_, Vec<crate::domain::CodeFileFingerprint>> {
291 indexing::file_index::fingerprints_for_scope(self, source_scope)
292 }
293
294 fn code_file_candidate_paths_for_scope(
295 &self,
296 source_scope: String,
297 path_filters: Vec<String>,
298 language_filters: Vec<String>,
299 exclude_generated: bool,
300 limit: usize,
301 ) -> StorageFuture<'_, Vec<String>> {
302 indexing::file_index::candidate_paths_for_scope(
303 self,
304 source_scope,
305 path_filters,
306 language_filters,
307 exclude_generated,
308 limit,
309 )
310 }
311
312 fn code_file_candidate_paths_for_query_scope(
313 &self,
314 source_scope: String,
315 query: String,
316 path_filters: Vec<String>,
317 language_filters: Vec<String>,
318 exclude_generated: bool,
319 limit: usize,
320 ) -> StorageFuture<'_, Vec<String>> {
321 indexing::file_index::candidate_paths_for_query_scope(
322 self,
323 source_scope,
324 query,
325 path_filters,
326 language_filters,
327 exclude_generated,
328 limit,
329 )
330 }
331
332 fn repository_documents_for_scope(
333 &self,
334 source_scope: String,
335 path_filters: Vec<String>,
336 max_files: usize,
337 max_bytes: usize,
338 ) -> StorageFuture<'_, Vec<crate::domain::IndexedRepositoryDocument>> {
339 routing::repository_documents_for_scope(
340 self.clone(),
341 source_scope,
342 path_filters,
343 max_files,
344 max_bytes,
345 )
346 }
347}
348
349impl CodeIndexPublicationStore for PartitionedSqliteKnowledgeStore {
350 fn code_index_checkpoint(
351 &self,
352 source_scope: String,
353 ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>> {
354 indexing::checkpoint::by_scope(self, source_scope)
355 }
356
357 fn latest_code_index_checkpoint(
358 &self,
359 repository_id: String,
360 ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>> {
361 indexing::checkpoint::latest(self, repository_id)
362 }
363
364 fn apply_code_index_snapshot(
365 &self,
366 snapshot: CodeIndexSnapshot,
367 ) -> StorageFuture<'_, CodeIndexSummary> {
368 indexing::lifecycle::apply_snapshot(self, snapshot)
369 }
370
371 fn apply_code_index_snapshot_with_fence(
372 &self,
373 snapshot: CodeIndexSnapshot,
374 fence: CodeIndexPublicationFence,
375 ) -> StorageFuture<'_, CodeIndexSummary> {
376 indexing::lifecycle::apply_snapshot_with_fence(self, snapshot, fence)
377 }
378
379 fn clear_code_workspace_state(
380 &self,
381 repository_id: String,
382 source_scope: String,
383 ) -> StorageFuture<'_, ()> {
384 indexing::lifecycle::clear_workspace(self, repository_id, source_scope)
385 }
386
387 fn code_repository_auto_workspace_state_exists(
388 &self,
389 repository_id: String,
390 ) -> StorageFuture<'_, bool> {
391 indexing::lifecycle::auto_workspace_state_exists(self, repository_id)
392 }
393
394 fn clear_code_workspace_state_with_fence(
395 &self,
396 repository_id: String,
397 source_scope: String,
398 fence: CodeIndexPublicationFence,
399 ) -> StorageFuture<'_, ()> {
400 indexing::lifecycle::clear_workspace_with_fence(self, repository_id, source_scope, fence)
401 }
402 fn begin_code_index_session(
403 &self,
404 session: CodeIndexSession,
405 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
406 indexing::lifecycle::begin_session(self, session)
407 }
408
409 fn begin_code_index_session_with_fence(
410 &self,
411 session: CodeIndexSession,
412 fence: CodeIndexPublicationFence,
413 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
414 indexing::lifecycle::begin_session_with_fence(self, session, fence)
415 }
416
417 fn begin_code_index_session_at_checkpoint(
418 &self,
419 session: CodeIndexSession,
420 expected_checkpoint: Option<CodeIndexCheckpoint>,
421 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
422 indexing::lifecycle::begin_session_at_checkpoint(self, session, expected_checkpoint)
423 }
424
425 fn begin_code_index_session_at_checkpoint_with_fence(
426 &self,
427 session: CodeIndexSession,
428 expected_checkpoint: Option<CodeIndexCheckpoint>,
429 fence: CodeIndexPublicationFence,
430 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
431 indexing::lifecycle::begin_session_at_checkpoint_with_fence(
432 self,
433 session,
434 expected_checkpoint,
435 fence,
436 )
437 }
438
439 fn apply_code_index_batch(
440 &self,
441 batch: CodeIndexBatch,
442 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
443 indexing::lifecycle::apply_batch(self, batch)
444 }
445
446 fn apply_code_index_batch_with_fence(
447 &self,
448 batch: CodeIndexBatch,
449 fence: CodeIndexPublicationFence,
450 ) -> StorageFuture<'_, CodeIndexCheckpoint> {
451 indexing::lifecycle::apply_batch_with_fence(self, batch, fence)
452 }
453
454 fn finalize_code_index_session(
455 &self,
456 session: CodeIndexSession,
457 ) -> StorageFuture<'_, CodeIndexSummary> {
458 indexing::lifecycle::finalize_session(self, session)
459 }
460
461 fn finalize_code_index_session_with_fence(
462 &self,
463 session: CodeIndexSession,
464 fence: CodeIndexPublicationFence,
465 ) -> StorageFuture<'_, CodeIndexSummary> {
466 indexing::lifecycle::finalize_session_with_fence(self, session, fence)
467 }
468
469 fn advance_code_index_session_with_fence(
470 &self,
471 session: CodeIndexSession,
472 fence: CodeIndexPublicationFence,
473 ) -> StorageFuture<'_, crate::storage::CodeIndexFinalizationStep> {
474 indexing::lifecycle::advance_session_with_fence(self, session, fence)
475 }
476}
477
478impl CodeQueryReadStore for PartitionedSqliteKnowledgeStore {
479 fn search_code(
480 &self,
481 request: CodeRetrievalRequest,
482 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
483 let this = self.clone();
484 Box::pin(async move {
485 if let Some(shard) = routing::repository_store_for_selector(
486 &this.control,
487 &this.catalog,
488 request.repository.clone(),
489 )
490 .await?
491 {
492 return match shard.search_code(request.clone()).await {
493 Ok(hits) => Ok(hits),
494 Err(error) if routing::is_missing_code_scope_error(&error) => {
495 this.control.search_code(request).await
496 }
497 Err(error) => Err(error),
498 };
499 }
500 this.control.search_code(request).await
501 })
502 }
503
504 fn search_code_feature_flags(
505 &self,
506 request: CodeFeatureFlagRequest,
507 ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
508 let this = self.clone();
509 Box::pin(async move {
510 if let Some(shard) = routing::repository_store_for_selector(
511 &this.control,
512 &this.catalog,
513 request.repository.clone(),
514 )
515 .await?
516 {
517 return match shard.search_code_feature_flags(request.clone()).await {
518 Ok(flags) => Ok(flags),
519 Err(error) if routing::is_missing_code_scope_error(&error) => {
520 this.control.search_code_feature_flags(request).await
521 }
522 Err(error) => Err(error),
523 };
524 }
525 this.control.search_code_feature_flags(request).await
526 })
527 }
528
529 fn search_code_feature_flags_scope(
530 &self,
531 source_scope: String,
532 request: CodeFeatureFlagRequest,
533 ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
534 routing::search_code_feature_flags_scope(self.clone(), source_scope, request)
535 }
536
537 fn search_code_scope(
538 &self,
539 source_scope: String,
540 request: CodeRetrievalRequest,
541 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
542 routing::search_code_scope(self.clone(), source_scope, request)
543 }
544
545 fn analyze_code_impact(
546 &self,
547 request: crate::domain::CodeImpactRequest,
548 changes: CodeImpactChanges,
549 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
550 let this = self.clone();
551 Box::pin(async move {
552 if let Some(shard) = routing::repository_store_for_selector(
553 &this.control,
554 &this.catalog,
555 request.repository.clone(),
556 )
557 .await?
558 {
559 return match shard
560 .analyze_code_impact(request.clone(), changes.clone())
561 .await
562 {
563 Ok(hits) => Ok(hits),
564 Err(error) if routing::is_missing_code_scope_error(&error) => {
565 this.control.analyze_code_impact(request, changes).await
566 }
567 Err(error) => Err(error),
568 };
569 }
570 this.control.analyze_code_impact(request, changes).await
571 })
572 }
573
574 fn analyze_code_impact_scope(
575 &self,
576 source_scope: String,
577 request: crate::domain::CodeImpactRequest,
578 changes: CodeImpactChanges,
579 ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
580 routing::analyze_code_impact_scope(self.clone(), source_scope, request, changes)
581 }
582
583 fn codebase_view_snapshot(
584 &self,
585 source_scope: String,
586 request: crate::domain::CodebaseViewRequest,
587 row_limit: usize,
588 ) -> StorageFuture<'_, crate::domain::CodebaseViewSnapshot> {
589 routing::codebase_view_snapshot(self.clone(), source_scope, request, row_limit)
590 }
591
592 fn code_repository_totals(&self) -> StorageFuture<'_, CodeRepositoryTotals> {
593 let this = self.clone();
594 Box::pin(async move { totals::code_repository_totals(this.control, this.catalog).await })
595 }
596
597 fn code_repository_report(
598 &self,
599 repository: String,
600 ) -> StorageFuture<'_, CodeRepositoryReport> {
601 let this = self.clone();
602 Box::pin(async move {
603 if let Some(shard) =
604 repository_store_for_report(&this.control, &this.catalog, repository.clone())
605 .await?
606 {
607 let report = shard.code_repository_report(repository.clone()).await?;
608 if report_matches_active_control(
609 &this.control,
610 &this.catalog,
611 repository.clone(),
612 &report,
613 )
614 .await?
615 {
616 return Ok(report);
617 }
618 }
619 this.control.code_repository_report(repository).await
620 })
621 }
622
623 fn code_repository_scope_symbol_generation_counts(
624 &self,
625 source_scope: String,
626 ) -> StorageFuture<'_, CodeSymbolGenerationCounts> {
627 let this = self.clone();
628 Box::pin(async move {
629 totals::scope_symbol_generation_counts(this.control, this.catalog, source_scope).await
630 })
631 }
632}
633
634impl SoftwareProjectionStore for PartitionedSqliteKnowledgeStore {
635 fn refresh_software_global_projection(
636 &self,
637 source_scope: String,
638 ) -> StorageFuture<'_, SoftwareGlobalProjection> {
639 let this = self.clone();
640 Box::pin(async move {
641 if let Some(shard) = source_scope_store(&this.catalog, source_scope.clone()).await? {
642 return shard.refresh_software_global_projection(source_scope).await;
643 }
644 this.control
645 .refresh_software_global_projection(source_scope)
646 .await
647 })
648 }
649
650 fn refresh_software_global_projection_with_fence(
651 &self,
652 source_scope: String,
653 fence: CodeIndexPublicationFence,
654 ) -> StorageFuture<'_, SoftwareGlobalProjection> {
655 let this = self.clone();
656 Box::pin(async move {
657 let shard = this
658 .catalog
659 .checkpoint_repository_store(fence.repository_id.clone())
660 .await?
661 .ok_or_else(|| {
662 StorageError::InvalidInput(format!(
663 "repository shard for fenced software projection '{}' is missing",
664 fence.repository_id
665 ))
666 })?;
667 let projection = shard
668 .refresh_software_global_projection_with_fence(source_scope.clone(), fence.clone())
669 .await?;
670 let status = shard
671 .code_repository_status(projection.status.repository_id.clone())
672 .await?
673 .ok_or_else(|| {
674 StorageError::InvalidInput(
675 "sharded code repository status is missing after software publication"
676 .to_owned(),
677 )
678 })?;
679 this.catalog
680 .publish_scope_status_with_fence(
681 projection.status.repository_id.clone(),
682 source_scope,
683 status,
684 fence,
685 )
686 .await?;
687 Ok(projection)
688 })
689 }
690
691 fn software_global_projection(
692 &self,
693 request: SoftwareGlobalRequest,
694 ) -> StorageFuture<'_, SoftwareGlobalProjection> {
695 let this = self.clone();
696 Box::pin(async move {
697 if let Some(shard) = routing::repository_store_for_selector(
698 &this.control,
699 &this.catalog,
700 request.repository.clone(),
701 )
702 .await?
703 {
704 return match shard.software_global_projection(request.clone()).await {
705 Ok(projection) => Ok(projection),
706 Err(error) if routing::is_missing_code_scope_error(&error) => {
707 this.control.software_global_projection(request).await
708 }
709 Err(error) => Err(error),
710 };
711 }
712 this.control.software_global_projection(request).await
713 })
714 }
715
716 fn software_global_projection_for_scope(
717 &self,
718 source_scope: String,
719 request: SoftwareGlobalRequest,
720 ) -> StorageFuture<'_, SoftwareGlobalProjection> {
721 let this = self.clone();
722 Box::pin(async move {
723 if let Some(shard) = source_scope_store(&this.catalog, source_scope.clone()).await? {
724 return shard
725 .software_global_projection_for_scope(source_scope, request)
726 .await;
727 }
728 this.control
729 .software_global_projection_for_scope(source_scope, request)
730 .await
731 })
732 }
733}
734
735impl BusinessKnowledgeStore for PartitionedSqliteKnowledgeStore {
736 fn replace_business_knowledge_projection(
737 &self,
738 input: crate::domain::BusinessKnowledgeProjectionInput,
739 ) -> StorageFuture<'_, crate::domain::BusinessKnowledgeStatus> {
740 let this = self.clone();
741 Box::pin(async move {
742 if let Some(shard) =
743 source_scope_store(&this.catalog, input.source_scope.clone()).await?
744 {
745 return shard.replace_business_knowledge_projection(input).await;
746 }
747 this.control
748 .replace_business_knowledge_projection(input)
749 .await
750 })
751 }
752
753 fn replace_business_knowledge_projection_with_fence(
754 &self,
755 input: crate::domain::BusinessKnowledgeProjectionInput,
756 fence: CodeIndexPublicationFence,
757 ) -> StorageFuture<'_, crate::domain::BusinessKnowledgeStatus> {
758 let this = self.clone();
759 Box::pin(async move {
760 let shard = this
761 .catalog
762 .checkpoint_repository_store(fence.repository_id.clone())
763 .await?
764 .ok_or_else(|| {
765 StorageError::InvalidInput(format!(
766 "repository shard for fenced business projection '{}' is missing",
767 fence.repository_id
768 ))
769 })?;
770 shard
771 .replace_business_knowledge_projection_with_fence(input, fence)
772 .await
773 })
774 }
775
776 fn business_knowledge_projection_for_scope(
777 &self,
778 source_scope: String,
779 request: crate::domain::BusinessKnowledgeQueryRequest,
780 ) -> StorageFuture<'_, crate::domain::BusinessKnowledgeProjection> {
781 let this = self.clone();
782 Box::pin(async move {
783 if let Some(shard) = source_scope_store(&this.catalog, source_scope.clone()).await? {
784 return shard
785 .business_knowledge_projection_for_scope(source_scope, request)
786 .await;
787 }
788 this.control
789 .business_knowledge_projection_for_scope(source_scope, request)
790 .await
791 })
792 }
793
794 fn business_knowledge_status(
795 &self,
796 source_scope: String,
797 ) -> StorageFuture<'_, Option<crate::domain::BusinessKnowledgeStatus>> {
798 let this = self.clone();
799 Box::pin(async move {
800 if let Some(shard) = source_scope_store(&this.catalog, source_scope.clone()).await? {
801 return shard.business_knowledge_status(source_scope).await;
802 }
803 this.control.business_knowledge_status(source_scope).await
804 })
805 }
806}
807
808#[cfg(test)]
809#[path = "mod_tests.rs"]
810mod tests;
811
812#[cfg(test)]
813#[path = "post_maintenance_tests.rs"]
814mod post_maintenance_tests;