Skip to main content

relay_knowledge/storage/
code.rs

1//! Storage contracts for code repository indexes.
2
3use crate::domain::{
4    CodeFeatureFlagGraph, CodeFeatureFlagRequest, CodeFileFingerprint, CodeImpactRequest,
5    CodeIndexBatch, CodeIndexCheckpoint, CodeIndexSession, CodeIndexSnapshot, CodeIndexSummary,
6    CodeIndexTaskQueueStatus, CodeIndexTaskRecord, CodeRepositoryCrossEdge,
7    CodeRepositoryRegistration, CodeRepositoryRemovalSummary, CodeRepositoryReport,
8    CodeRepositorySet, CodeRepositorySetMember, CodeRepositorySetRefreshSummary,
9    CodeRepositorySetRefreshTaskRecord, CodeRepositorySetStatus, CodeRepositoryStatus,
10    CodeRepositoryTotals, CodeRetrievalHit, CodeRetrievalRequest, CodeScopeRetentionSummary,
11    CodeSymbolGenerationCounts, CodebaseViewRequest, CodebaseViewSnapshot,
12    SoftwareGlobalProjection, SoftwareGlobalRequest,
13};
14
15use super::{StorageError, StorageFuture};
16
17/// Default error text for stores that do not support code task lease recovery.
18pub const CODE_INDEX_TASK_LEASE_RECOVERY_UNAVAILABLE: &str =
19    "code index task lease recovery is unavailable";
20
21/// Default error text for stores that do not support code task lease renewal.
22pub const CODE_INDEX_TASK_LEASE_RENEWAL_UNAVAILABLE: &str =
23    "code index task lease renewal is unavailable";
24
25/// Diff-derived inputs used to seed code impact expansion.
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct CodeImpactChanges {
28    pub paths: Vec<String>,
29    pub deleted_symbol_names: Vec<String>,
30}
31
32/// New background code index task to persist or deduplicate.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct CodeIndexTaskSeed {
35    pub repository_id: String,
36    pub alias: String,
37    pub ref_selector: String,
38    pub resolved_commit_sha: String,
39    pub tree_hash: String,
40    pub source_scope: String,
41    pub path_filters: Vec<String>,
42    pub language_filters: Vec<String>,
43    pub mode: crate::domain::CodeIndexMode,
44    pub input_fingerprint: String,
45    pub resource_budget: crate::domain::CodeIndexResourceBudget,
46    pub payload_json: String,
47    pub now_ms: u64,
48}
49
50/// Lease acquisition request for one background code index task.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct CodeIndexTaskClaimRequest {
53    pub task_id: Option<String>,
54    pub lease_owner: String,
55    pub lease_duration_ms: u64,
56    pub max_attempts: u32,
57    pub now_ms: u64,
58}
59
60/// Lease renewal request for an actively running code index task.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct CodeIndexTaskLeaseRenewal {
63    pub task_id: String,
64    pub lease_owner: String,
65    pub attempt_count: u32,
66    pub lease_duration_ms: u64,
67    pub now_ms: u64,
68}
69
70/// Active code-index task lease used by service startup recovery.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct CodeIndexTaskLeaseRecord {
73    pub task_id: String,
74    pub lease_owner: String,
75    pub lease_expires_at_ms: Option<u64>,
76    pub attempt_count: u32,
77}
78
79/// Recovery request for running task leases known to be orphaned.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct CodeIndexTaskLeaseRecovery {
82    pub task_ids: Vec<String>,
83    pub now_ms: u64,
84    pub max_attempts: u32,
85    pub error_kind: String,
86    pub error_message: String,
87}
88
89/// Completion report guarded by task lease and attempt token.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct CodeIndexTaskCompletion {
92    pub task_id: String,
93    pub lease_owner: String,
94    pub attempt_count: u32,
95    pub now_ms: u64,
96}
97
98/// Failure report for retry and dead-letter handling.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct CodeIndexTaskFailure {
101    pub task_id: String,
102    pub lease_owner: String,
103    pub attempt_count: u32,
104    pub error_kind: String,
105    pub error_message: String,
106    pub retry_backoff_ms: u64,
107    pub max_attempts: u32,
108    pub now_ms: u64,
109}
110
111/// Scope retention request after a repository index completes.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct CodeScopeRetentionRequest {
114    pub repository_id: String,
115    pub active_scope: String,
116    pub retain_recent_successful_scopes: usize,
117}
118
119/// New repository set metadata to persist.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct CodeRepositorySetSeed {
122    pub alias: String,
123    pub description: Option<String>,
124    pub default_ref_policy_json: String,
125    pub now_ms: u64,
126}
127
128/// New or replaced repository-set member pointer.
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct CodeRepositorySetMemberSeed {
131    pub set_alias: String,
132    pub repository_id: String,
133    pub repository_alias: String,
134    pub ref_selector: String,
135    pub resolved_commit_sha: String,
136    pub source_scope: String,
137    pub path_filters: Vec<String>,
138    pub language_filters: Vec<String>,
139    pub priority: i32,
140}
141
142/// Repository-set overlay refresh task to persist or deduplicate.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct CodeRepositorySetRefreshTaskSeed {
145    pub set_id: String,
146    pub set_alias: String,
147    pub input_fingerprint: String,
148    pub now_ms: u64,
149}
150
151/// Lease acquisition request for one repository-set overlay task.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct CodeRepositorySetRefreshTaskClaimRequest {
154    pub task_id: Option<String>,
155    pub lease_owner: String,
156    pub lease_duration_ms: u64,
157    pub max_attempts: u32,
158    pub now_ms: u64,
159}
160
161/// Completion report guarded by task lease and attempt token.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct CodeRepositorySetRefreshTaskCompletion {
164    pub task_id: String,
165    pub lease_owner: String,
166    pub attempt_count: u32,
167    pub now_ms: u64,
168}
169
170/// Failure report for retry and dead-letter handling.
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct CodeRepositorySetRefreshTaskFailure {
173    pub task_id: String,
174    pub lease_owner: String,
175    pub attempt_count: u32,
176    pub error_kind: String,
177    pub error_message: String,
178    pub retry_backoff_ms: u64,
179    pub max_attempts: u32,
180    pub now_ms: u64,
181}
182
183/// Persisted code repository graph and retrieval contract.
184pub trait CodeRepositoryStore: Send + Sync {
185    fn upsert_code_repository(
186        &self,
187        registration: CodeRepositoryRegistration,
188    ) -> StorageFuture<'_, CodeRepositoryStatus>;
189
190    fn code_repository_status(
191        &self,
192        repository: String,
193    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>>;
194
195    fn list_code_repositories(&self) -> StorageFuture<'_, Vec<CodeRepositoryStatus>> {
196        Box::pin(async { Ok(Vec::new()) })
197    }
198
199    fn remove_code_repository(
200        &self,
201        repository: String,
202        now_ms: u64,
203    ) -> StorageFuture<'_, Option<CodeRepositoryRemovalSummary>> {
204        Box::pin(async move {
205            Err(StorageError::InvalidInput(format!(
206                "code repository removal for '{repository}' at {now_ms} is unavailable"
207            )))
208        })
209    }
210
211    fn code_repository_scope_status(
212        &self,
213        repository: String,
214        resolved_commit_sha: String,
215        path_filters: Vec<String>,
216        language_filters: Vec<String>,
217    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>>;
218
219    fn latest_code_repository_scope_status(
220        &self,
221        _repository: String,
222        _path_filters: Vec<String>,
223        _language_filters: Vec<String>,
224    ) -> StorageFuture<'_, Option<CodeRepositoryStatus>> {
225        Box::pin(async { Ok(None) })
226    }
227
228    fn queue_code_index_task(
229        &self,
230        task: CodeIndexTaskSeed,
231    ) -> StorageFuture<'_, CodeIndexTaskRecord>;
232
233    fn claim_code_index_task(
234        &self,
235        request: CodeIndexTaskClaimRequest,
236    ) -> StorageFuture<'_, Option<CodeIndexTaskRecord>>;
237
238    fn recover_code_index_task_leases(
239        &self,
240        _now_ms: u64,
241        _max_attempts: u32,
242    ) -> StorageFuture<'_, ()> {
243        Box::pin(async {
244            Err(StorageError::InvalidInput(
245                CODE_INDEX_TASK_LEASE_RECOVERY_UNAVAILABLE.to_owned(),
246            ))
247        })
248    }
249
250    fn running_code_index_task_leases(&self) -> StorageFuture<'_, Vec<CodeIndexTaskLeaseRecord>> {
251        Box::pin(async { Ok(Vec::new()) })
252    }
253
254    fn recover_code_index_task_leases_by_task(
255        &self,
256        _request: CodeIndexTaskLeaseRecovery,
257    ) -> StorageFuture<'_, usize> {
258        Box::pin(async {
259            Err(StorageError::InvalidInput(
260                CODE_INDEX_TASK_LEASE_RECOVERY_UNAVAILABLE.to_owned(),
261            ))
262        })
263    }
264
265    fn reset_code_index_tasks(
266        &self,
267        _repository_id: String,
268        _now_ms: u64,
269    ) -> StorageFuture<'_, Vec<CodeIndexTaskRecord>> {
270        Box::pin(async {
271            Err(StorageError::InvalidInput(
272                "code index task reset is unavailable".to_owned(),
273            ))
274        })
275    }
276
277    fn renew_code_index_task_lease(
278        &self,
279        _request: CodeIndexTaskLeaseRenewal,
280    ) -> StorageFuture<'_, CodeIndexTaskRecord> {
281        Box::pin(async {
282            Err(StorageError::InvalidInput(
283                CODE_INDEX_TASK_LEASE_RENEWAL_UNAVAILABLE.to_owned(),
284            ))
285        })
286    }
287
288    fn complete_code_index_task(
289        &self,
290        request: CodeIndexTaskCompletion,
291    ) -> StorageFuture<'_, CodeIndexTaskRecord>;
292
293    fn fail_code_index_task(
294        &self,
295        request: CodeIndexTaskFailure,
296    ) -> StorageFuture<'_, CodeIndexTaskRecord>;
297
298    fn code_index_task(&self, task_id: String) -> StorageFuture<'_, Option<CodeIndexTaskRecord>>;
299
300    fn active_code_index_task(
301        &self,
302        repository_id: String,
303    ) -> StorageFuture<'_, Option<CodeIndexTaskRecord>>;
304
305    fn code_index_task_queue_status(&self) -> StorageFuture<'_, CodeIndexTaskQueueStatus> {
306        Box::pin(async { Ok(CodeIndexTaskQueueStatus::default()) })
307    }
308
309    fn code_index_checkpoint(
310        &self,
311        source_scope: String,
312    ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>>;
313
314    fn latest_code_index_checkpoint(
315        &self,
316        _repository_id: String,
317    ) -> StorageFuture<'_, Option<CodeIndexCheckpoint>> {
318        Box::pin(async { Ok(None) })
319    }
320
321    fn code_scope_retention(
322        &self,
323        repository_id: String,
324    ) -> StorageFuture<'_, CodeScopeRetentionSummary>;
325
326    fn prune_code_repository_scopes(
327        &self,
328        request: CodeScopeRetentionRequest,
329    ) -> StorageFuture<'_, CodeScopeRetentionSummary>;
330
331    fn code_file_fingerprints(
332        &self,
333        repository_id: String,
334    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>>;
335
336    fn code_file_fingerprints_for_scope(
337        &self,
338        source_scope: String,
339    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
340        Box::pin(async move {
341            Err(StorageError::InvalidInput(format!(
342                "code file fingerprints for scope '{source_scope}' are unavailable"
343            )))
344        })
345    }
346
347    fn code_file_fingerprints_for_paths(
348        &self,
349        source_scope: String,
350        paths: Vec<String>,
351    ) -> StorageFuture<'_, Vec<CodeFileFingerprint>> {
352        Box::pin(async move {
353            let mut fingerprints = self.code_file_fingerprints_for_scope(source_scope).await?;
354            fingerprints.retain(|fingerprint| paths.iter().any(|path| path == &fingerprint.path));
355            Ok(fingerprints)
356        })
357    }
358
359    fn code_file_candidate_paths_for_scope(
360        &self,
361        source_scope: String,
362        _path_filters: Vec<String>,
363        _language_filters: Vec<String>,
364        _exclude_generated: bool,
365        _limit: usize,
366    ) -> StorageFuture<'_, Vec<String>> {
367        Box::pin(async move {
368            Err(StorageError::InvalidInput(format!(
369                "bounded code file candidate paths for scope '{source_scope}' are unavailable"
370            )))
371        })
372    }
373
374    fn code_file_candidate_paths_for_query_scope(
375        &self,
376        source_scope: String,
377        _query: String,
378        path_filters: Vec<String>,
379        language_filters: Vec<String>,
380        exclude_generated: bool,
381        limit: usize,
382    ) -> StorageFuture<'_, Vec<String>> {
383        self.code_file_candidate_paths_for_scope(
384            source_scope,
385            path_filters,
386            language_filters,
387            exclude_generated,
388            limit,
389        )
390    }
391
392    fn apply_code_index_snapshot(
393        &self,
394        snapshot: CodeIndexSnapshot,
395    ) -> StorageFuture<'_, CodeIndexSummary>;
396
397    fn clear_code_workspace_state(
398        &self,
399        _repository_id: String,
400        _source_scope: String,
401    ) -> StorageFuture<'_, ()> {
402        Box::pin(async { Ok(()) })
403    }
404
405    fn begin_code_index_session(
406        &self,
407        session: CodeIndexSession,
408    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
409        Box::pin(async move {
410            Err(StorageError::InvalidInput(format!(
411                "checkpointed code index sessions for scope '{}' are unavailable",
412                session.source_scope
413            )))
414        })
415    }
416
417    fn apply_code_index_batch(
418        &self,
419        batch: CodeIndexBatch,
420    ) -> StorageFuture<'_, CodeIndexCheckpoint> {
421        Box::pin(async move {
422            Err(StorageError::InvalidInput(format!(
423                "checkpointed code index batches for scope '{}' are unavailable",
424                batch.source_scope
425            )))
426        })
427    }
428
429    fn finalize_code_index_session(
430        &self,
431        session: CodeIndexSession,
432    ) -> StorageFuture<'_, CodeIndexSummary> {
433        Box::pin(async move {
434            Err(StorageError::InvalidInput(format!(
435                "checkpointed code index finalization for scope '{}' is unavailable",
436                session.source_scope
437            )))
438        })
439    }
440
441    fn search_code(
442        &self,
443        request: CodeRetrievalRequest,
444    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>>;
445
446    fn search_code_feature_flags(
447        &self,
448        request: CodeFeatureFlagRequest,
449    ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
450        Box::pin(async move {
451            Err(StorageError::InvalidInput(format!(
452                "code feature flag search for repository '{}' is unavailable",
453                request.repository.repository
454            )))
455        })
456    }
457
458    fn search_code_feature_flags_scope(
459        &self,
460        source_scope: String,
461        _request: CodeFeatureFlagRequest,
462    ) -> StorageFuture<'_, Vec<CodeFeatureFlagGraph>> {
463        Box::pin(async move {
464            Err(StorageError::InvalidInput(format!(
465                "code feature flag search for source scope '{source_scope}' is unavailable"
466            )))
467        })
468    }
469
470    fn search_code_scope(
471        &self,
472        source_scope: String,
473        _request: CodeRetrievalRequest,
474    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
475        Box::pin(async move {
476            Err(StorageError::InvalidInput(format!(
477                "code search for source scope '{source_scope}' is unavailable"
478            )))
479        })
480    }
481
482    fn analyze_code_impact(
483        &self,
484        request: CodeImpactRequest,
485        changes: CodeImpactChanges,
486    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>>;
487
488    fn analyze_code_impact_scope(
489        &self,
490        source_scope: String,
491        _request: CodeImpactRequest,
492        _changes: CodeImpactChanges,
493    ) -> StorageFuture<'_, Vec<CodeRetrievalHit>> {
494        Box::pin(async move {
495            Err(StorageError::InvalidInput(format!(
496                "code impact analysis for source scope '{source_scope}' is unavailable"
497            )))
498        })
499    }
500
501    fn codebase_view_snapshot(
502        &self,
503        source_scope: String,
504        _request: CodebaseViewRequest,
505        _row_limit: usize,
506    ) -> StorageFuture<'_, CodebaseViewSnapshot> {
507        Box::pin(async move {
508            Err(StorageError::InvalidInput(format!(
509                "codebase view snapshot for source scope '{source_scope}' is unavailable"
510            )))
511        })
512    }
513
514    fn code_repository_totals(&self) -> StorageFuture<'_, CodeRepositoryTotals> {
515        Box::pin(async { Ok(CodeRepositoryTotals::default()) })
516    }
517
518    fn code_repository_report(
519        &self,
520        repository: String,
521    ) -> StorageFuture<'_, CodeRepositoryReport> {
522        Box::pin(async move {
523            Err(StorageError::InvalidInput(format!(
524                "code repository report for '{repository}' is unavailable"
525            )))
526        })
527    }
528
529    fn code_repository_scope_symbol_generation_counts(
530        &self,
531        source_scope: String,
532    ) -> StorageFuture<'_, CodeSymbolGenerationCounts> {
533        Box::pin(async move {
534            Err(StorageError::InvalidInput(format!(
535                "code symbol generation counts for source scope '{source_scope}' are unavailable"
536            )))
537        })
538    }
539
540    fn refresh_software_global_projection(
541        &self,
542        source_scope: String,
543    ) -> StorageFuture<'_, SoftwareGlobalProjection> {
544        Box::pin(async move {
545            Err(StorageError::InvalidInput(format!(
546                "software global projection for source scope '{source_scope}' is unavailable"
547            )))
548        })
549    }
550
551    fn software_global_projection(
552        &self,
553        request: SoftwareGlobalRequest,
554    ) -> StorageFuture<'_, SoftwareGlobalProjection> {
555        Box::pin(async move {
556            Err(StorageError::InvalidInput(format!(
557                "software global projection for repository '{}' is unavailable",
558                request.repository.repository
559            )))
560        })
561    }
562
563    fn software_global_projection_for_scope(
564        &self,
565        source_scope: String,
566        _request: SoftwareGlobalRequest,
567    ) -> StorageFuture<'_, SoftwareGlobalProjection> {
568        Box::pin(async move {
569            Err(StorageError::InvalidInput(format!(
570                "software global projection for source scope '{source_scope}' is unavailable"
571            )))
572        })
573    }
574
575    fn create_code_repository_set(
576        &self,
577        _seed: CodeRepositorySetSeed,
578    ) -> StorageFuture<'_, CodeRepositorySet> {
579        Box::pin(async {
580            Err(StorageError::InvalidInput(
581                "repository set storage is unavailable".to_owned(),
582            ))
583        })
584    }
585
586    fn add_code_repository_set_member(
587        &self,
588        _seed: CodeRepositorySetMemberSeed,
589    ) -> StorageFuture<'_, CodeRepositorySetMember> {
590        Box::pin(async {
591            Err(StorageError::InvalidInput(
592                "repository set member storage is unavailable".to_owned(),
593            ))
594        })
595    }
596
597    fn remove_code_repository_set_member(
598        &self,
599        _set_alias: String,
600        _repository_alias: String,
601    ) -> StorageFuture<'_, CodeRepositorySetMember> {
602        Box::pin(async {
603            Err(StorageError::InvalidInput(
604                "repository set member storage is unavailable".to_owned(),
605            ))
606        })
607    }
608
609    fn code_repository_set(
610        &self,
611        _set_alias: String,
612    ) -> StorageFuture<'_, Option<CodeRepositorySet>> {
613        Box::pin(async { Ok(None) })
614    }
615
616    fn code_repository_set_status(
617        &self,
618        _set_alias: String,
619    ) -> StorageFuture<'_, Option<CodeRepositorySetStatus>> {
620        Box::pin(async { Ok(None) })
621    }
622
623    fn refresh_code_repository_set_overlay(
624        &self,
625        _set_alias: String,
626        _now_ms: u64,
627    ) -> StorageFuture<'_, CodeRepositorySetRefreshSummary> {
628        Box::pin(async {
629            Err(StorageError::InvalidInput(
630                "repository set overlay refresh is unavailable".to_owned(),
631            ))
632        })
633    }
634
635    fn code_repository_set_cross_edges(
636        &self,
637        _set_id: String,
638    ) -> StorageFuture<'_, Vec<CodeRepositoryCrossEdge>> {
639        Box::pin(async { Ok(Vec::new()) })
640    }
641
642    fn queue_code_repository_set_refresh_task(
643        &self,
644        _task: CodeRepositorySetRefreshTaskSeed,
645    ) -> StorageFuture<'_, CodeRepositorySetRefreshTaskRecord> {
646        Box::pin(async {
647            Err(StorageError::InvalidInput(
648                "repository set refresh task storage is unavailable".to_owned(),
649            ))
650        })
651    }
652
653    fn claim_code_repository_set_refresh_task(
654        &self,
655        _request: CodeRepositorySetRefreshTaskClaimRequest,
656    ) -> StorageFuture<'_, Option<CodeRepositorySetRefreshTaskRecord>> {
657        Box::pin(async { Ok(None) })
658    }
659
660    fn complete_code_repository_set_refresh_task(
661        &self,
662        _request: CodeRepositorySetRefreshTaskCompletion,
663    ) -> StorageFuture<'_, CodeRepositorySetRefreshTaskRecord> {
664        Box::pin(async {
665            Err(StorageError::InvalidInput(
666                "repository set refresh task storage is unavailable".to_owned(),
667            ))
668        })
669    }
670
671    fn fail_code_repository_set_refresh_task(
672        &self,
673        _request: CodeRepositorySetRefreshTaskFailure,
674    ) -> StorageFuture<'_, CodeRepositorySetRefreshTaskRecord> {
675        Box::pin(async {
676            Err(StorageError::InvalidInput(
677                "repository set refresh task storage is unavailable".to_owned(),
678            ))
679        })
680    }
681}