Skip to main content

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