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