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