Skip to main content

relay_knowledge/domain/code/
repository_index.rs

1use serde::{Deserialize, Serialize};
2
3use super::{
4    code_dependency::CodeDependencyRecord,
5    code_repository::{
6        CodeCallRecord, CodeFeatureFlagRecord, CodeFileDiagnostic, CodeImportRecord, CodeIndexMode,
7        CodePathTombstone, CodeRouteRecord, RepositoryCodeChunkRecord, RepositoryCodeFileRecord,
8        RepositoryCodeReferenceRecord, RepositoryCodeSymbolRecord,
9    },
10    code_workspace::CodeMonorepoWorkspace,
11    error::DomainError,
12};
13
14/// Parsed index changes ready to commit into storage.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct CodeIndexSnapshot {
17    pub repository_id: String,
18    pub source_scope: String,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub base_resolved_commit_sha: Option<String>,
21    pub resolved_commit_sha: String,
22    pub tree_hash: String,
23    pub path_filters: Vec<String>,
24    pub language_filters: Vec<String>,
25    pub full_replace: bool,
26    pub changed_path_count: usize,
27    pub skipped_unchanged_count: usize,
28    pub deleted_paths: Vec<String>,
29    pub tombstones: Vec<CodePathTombstone>,
30    pub files: Vec<RepositoryCodeFileRecord>,
31    pub symbols: Vec<RepositoryCodeSymbolRecord>,
32    pub references: Vec<RepositoryCodeReferenceRecord>,
33    pub imports: Vec<CodeImportRecord>,
34    pub calls: Vec<CodeCallRecord>,
35    pub dependencies: Vec<CodeDependencyRecord>,
36    pub feature_flags: Vec<CodeFeatureFlagRecord>,
37    pub routes: Vec<CodeRouteRecord>,
38    pub chunks: Vec<RepositoryCodeChunkRecord>,
39    #[serde(default)]
40    pub workspaces: Vec<CodeMonorepoWorkspace>,
41    pub diagnostics: Vec<CodeFileDiagnostic>,
42}
43
44/// Resource budget used to partition repository indexing into bounded batches.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46pub struct CodeIndexResourceBudget {
47    pub max_files_per_batch: usize,
48    pub max_bytes_per_batch: usize,
49    pub max_rows_per_batch: usize,
50}
51
52impl CodeIndexResourceBudget {
53    pub const DEFAULT_MAX_FILES_PER_BATCH: usize = 512;
54    pub const DEFAULT_MAX_BYTES_PER_BATCH: usize = 16 * 1024 * 1024;
55    pub const DEFAULT_MAX_ROWS_PER_BATCH: usize = 150_000;
56
57    /// Creates a non-zero resource budget for batch parsing and SQLite writes.
58    pub fn new(
59        max_files_per_batch: usize,
60        max_bytes_per_batch: usize,
61        max_rows_per_batch: usize,
62    ) -> Result<Self, DomainError> {
63        if max_files_per_batch == 0 {
64            return Err(DomainError::invalid(
65                "max_files_per_batch",
66                "must be greater than zero",
67            ));
68        }
69        if max_bytes_per_batch == 0 {
70            return Err(DomainError::invalid(
71                "max_bytes_per_batch",
72                "must be greater than zero",
73            ));
74        }
75        if max_rows_per_batch == 0 {
76            return Err(DomainError::invalid(
77                "max_rows_per_batch",
78                "must be greater than zero",
79            ));
80        }
81
82        Ok(Self {
83            max_files_per_batch,
84            max_bytes_per_batch,
85            max_rows_per_batch,
86        })
87    }
88}
89
90impl Default for CodeIndexResourceBudget {
91    fn default() -> Self {
92        Self {
93            max_files_per_batch: Self::DEFAULT_MAX_FILES_PER_BATCH,
94            max_bytes_per_batch: Self::DEFAULT_MAX_BYTES_PER_BATCH,
95            max_rows_per_batch: Self::DEFAULT_MAX_ROWS_PER_BATCH,
96        }
97    }
98}
99
100/// Stable metadata for one resumable repository indexing session.
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102pub struct CodeIndexSession {
103    pub repository_id: String,
104    pub source_scope: String,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub base_resolved_commit_sha: Option<String>,
107    pub resolved_commit_sha: String,
108    pub tree_hash: String,
109    pub path_filters: Vec<String>,
110    pub language_filters: Vec<String>,
111    pub full_replace: bool,
112    pub total_path_count: usize,
113    pub changed_path_count: usize,
114    pub skipped_unchanged_count: usize,
115    pub deleted_paths: Vec<String>,
116    pub tombstones: Vec<CodePathTombstone>,
117    #[serde(default)]
118    pub workspaces: Vec<CodeMonorepoWorkspace>,
119    pub resource_budget: CodeIndexResourceBudget,
120}
121
122/// One bounded parse result committed under a checkpointed index session.
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct CodeIndexBatch {
125    pub repository_id: String,
126    pub source_scope: String,
127    pub batch_index: usize,
128    pub parsed_byte_count: usize,
129    pub files: Vec<RepositoryCodeFileRecord>,
130    pub symbols: Vec<RepositoryCodeSymbolRecord>,
131    pub references: Vec<RepositoryCodeReferenceRecord>,
132    pub imports: Vec<CodeImportRecord>,
133    pub dependencies: Vec<CodeDependencyRecord>,
134    pub feature_flags: Vec<CodeFeatureFlagRecord>,
135    pub routes: Vec<CodeRouteRecord>,
136    pub chunks: Vec<RepositoryCodeChunkRecord>,
137    pub diagnostics: Vec<CodeFileDiagnostic>,
138}
139
140impl CodeIndexBatch {
141    pub fn row_count(&self) -> usize {
142        self.files
143            .len()
144            .saturating_add(self.symbols.len())
145            .saturating_add(self.references.len())
146            .saturating_add(self.imports.len())
147            .saturating_add(self.dependencies.len())
148            .saturating_add(self.feature_flags.len())
149            .saturating_add(self.routes.len())
150            .saturating_add(self.chunks.len())
151            .saturating_add(self.diagnostics.len())
152    }
153}
154
155/// Durable progress checkpoint for a repository indexing session.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157pub struct CodeIndexCheckpoint {
158    pub repository_id: String,
159    pub source_scope: String,
160    pub state: String,
161    pub total_path_count: usize,
162    pub parsed_file_count: usize,
163    pub committed_file_count: usize,
164    pub committed_symbol_count: usize,
165    pub committed_reference_count: usize,
166    pub committed_chunk_count: usize,
167    pub batch_count: usize,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub last_path: Option<String>,
170    pub resource_budget: CodeIndexResourceBudget,
171    pub updated_at_ms: u64,
172}
173
174/// Persistent lifecycle for background code repository index tasks.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "snake_case")]
177pub enum CodeIndexTaskState {
178    Queued,
179    Running,
180    Succeeded,
181    Retrying,
182    Failed,
183    DeadLetter,
184    Cancelled,
185}
186
187impl CodeIndexTaskState {
188    /// Stable storage and API representation.
189    pub const fn as_str(self) -> &'static str {
190        match self {
191            Self::Queued => "queued",
192            Self::Running => "running",
193            Self::Succeeded => "succeeded",
194            Self::Retrying => "retrying",
195            Self::Failed => "failed",
196            Self::DeadLetter => "dead_letter",
197            Self::Cancelled => "cancelled",
198        }
199    }
200
201    /// Parses the stable storage and API representation.
202    pub fn parse(value: &str) -> Result<Self, DomainError> {
203        match value {
204            "queued" => Ok(Self::Queued),
205            "running" => Ok(Self::Running),
206            "succeeded" => Ok(Self::Succeeded),
207            "retrying" => Ok(Self::Retrying),
208            "failed" => Ok(Self::Failed),
209            "dead_letter" => Ok(Self::DeadLetter),
210            "cancelled" => Ok(Self::Cancelled),
211            _ => Err(DomainError::invalid(
212                "code_index_task_state",
213                "unknown code index task state",
214            )),
215        }
216    }
217
218    /// Returns whether the task can still consume executor capacity.
219    pub const fn is_unfinished(self) -> bool {
220        matches!(self, Self::Queued | Self::Running | Self::Retrying)
221    }
222}
223
224/// Durable background task for one code repository index request.
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226pub struct CodeIndexTaskRecord {
227    pub task_id: String,
228    pub repository_id: String,
229    pub alias: String,
230    pub ref_selector: String,
231    pub resolved_commit_sha: String,
232    pub tree_hash: String,
233    pub source_scope: String,
234    pub path_filters: Vec<String>,
235    pub language_filters: Vec<String>,
236    pub mode: CodeIndexMode,
237    pub state: CodeIndexTaskState,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub lease_owner: Option<String>,
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub lease_expires_at_ms: Option<u64>,
242    pub attempt_count: u32,
243    pub next_retry_at_ms: u64,
244    pub input_fingerprint: String,
245    pub resource_budget: CodeIndexResourceBudget,
246    pub payload_json: String,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub last_error_kind: Option<String>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub last_error_message: Option<String>,
251    pub created_at_ms: u64,
252    pub updated_at_ms: u64,
253}
254
255/// Aggregated durable queue state for background code-index tasks.
256#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
257pub struct CodeIndexTaskQueueStatus {
258    pub queued_task_count: usize,
259    pub running_task_count: usize,
260    pub retrying_task_count: usize,
261    pub dead_letter_task_count: usize,
262    pub running_lease_count: usize,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub last_error: Option<String>,
265}
266
267/// Scope retention result after pruning old repository snapshots.
268#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
269pub struct CodeScopeRetentionSummary {
270    pub repository_id: String,
271    pub retained_scope_count: usize,
272    pub prunable_scope_count: usize,
273    pub pruned_scope_count: usize,
274    pub retained_scopes: Vec<String>,
275    pub prunable_scopes: Vec<String>,
276    pub pruned_scopes: Vec<String>,
277}
278
279/// Coarse phase timing and counts reported by repository indexing.
280#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
281pub struct CodeIndexProgressSummary {
282    pub git_file_count: usize,
283    pub blob_read_count: usize,
284    pub parsed_file_count: usize,
285    pub sqlite_write_count: usize,
286    pub skipped_file_count: usize,
287    pub degraded_file_count: usize,
288    pub batch_count: usize,
289    pub checkpoint_file_count: usize,
290    pub resource_budget: CodeIndexResourceBudget,
291}
292
293/// Result of applying a code index snapshot.
294#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct CodeIndexSummary {
296    pub repository_id: String,
297    pub source_scope: String,
298    pub resolved_commit_sha: String,
299    pub tree_hash: String,
300    pub indexed_file_count: usize,
301    pub changed_path_count: usize,
302    pub skipped_unchanged_count: usize,
303    pub deleted_path_count: usize,
304    pub symbol_count: usize,
305    #[serde(default)]
306    pub handwritten_symbol_count: usize,
307    #[serde(default)]
308    pub generated_symbol_count: usize,
309    pub reference_count: usize,
310    pub chunk_count: usize,
311    pub degraded_file_count: usize,
312    pub progress: CodeIndexProgressSummary,
313}