Skip to main content

relay_knowledge/domain/code/repository_index/
mod.rs

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