1use serde::{Deserialize, Serialize};
4
5use super::{
6 CodeFrameworkEdgeRecord, CodeFrameworkNodeRecord,
7 dependencies::CodeDependencyRecord,
8 error::DomainError,
9 repository::{
10 CodeCallRecord, CodeFeatureFlagRecord, CodeFileDiagnostic, CodeImportRecord, CodeIndexMode,
11 CodePathTombstone, CodeRouteRecord, RepositoryCodeChunkRecord, RepositoryCodeFileRecord,
12 RepositoryCodeReferenceRecord, RepositoryCodeSymbolRecord,
13 },
14 workspace::CodeMonorepoWorkspace,
15};
16
17mod incremental_clone;
18mod reference_resolution;
19mod software_projection;
20
21pub(crate) use self::incremental_clone::{
22 CodeIncrementalClonePhase, code_incremental_clone, code_incremental_clone_state,
23};
24pub(crate) use self::reference_resolution::{
25 CodeReferenceResolution, CodeReferenceResolutionQueryIndexRepair, CodeReferenceResolutionStage,
26 code_reference_resolution, code_reference_resolution_cursor_digest,
27 code_reference_resolution_query_index_repair,
28 code_reference_resolution_query_index_repair_state, code_reference_resolution_state,
29};
30pub(crate) use self::software_projection::{
31 CodeSoftwareProjectionPhase, SOFTWARE_PROJECTION_CHECKPOINT, code_software_projection_phase,
32};
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct CodeIndexSnapshot {
37 pub repository_id: String,
38 pub source_scope: String,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub base_resolved_commit_sha: Option<String>,
41 pub resolved_commit_sha: String,
42 pub tree_hash: String,
43 pub path_filters: Vec<String>,
44 pub language_filters: Vec<String>,
45 pub full_replace: bool,
46 pub changed_path_count: usize,
47 pub skipped_unchanged_count: usize,
48 pub deleted_paths: Vec<String>,
49 pub tombstones: Vec<CodePathTombstone>,
50 pub files: Vec<RepositoryCodeFileRecord>,
51 pub symbols: Vec<RepositoryCodeSymbolRecord>,
52 pub references: Vec<RepositoryCodeReferenceRecord>,
53 pub imports: Vec<CodeImportRecord>,
54 pub calls: Vec<CodeCallRecord>,
55 pub dependencies: Vec<CodeDependencyRecord>,
56 pub feature_flags: Vec<CodeFeatureFlagRecord>,
57 #[serde(default)]
58 pub framework_nodes: Vec<CodeFrameworkNodeRecord>,
59 #[serde(default)]
60 pub framework_edges: Vec<CodeFrameworkEdgeRecord>,
61 pub routes: Vec<CodeRouteRecord>,
62 pub chunks: Vec<RepositoryCodeChunkRecord>,
63 #[serde(default)]
64 pub workspaces: Vec<CodeMonorepoWorkspace>,
65 pub diagnostics: Vec<CodeFileDiagnostic>,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70pub struct CodeIndexResourceBudget {
71 pub max_files_per_batch: usize,
72 pub max_bytes_per_batch: usize,
73 pub max_rows_per_batch: usize,
74}
75
76impl CodeIndexResourceBudget {
77 pub const DEFAULT_MAX_FILES_PER_BATCH: usize = 512;
78 pub const DEFAULT_MAX_BYTES_PER_BATCH: usize = 16 * 1024 * 1024;
79 pub const DEFAULT_MAX_ROWS_PER_BATCH: usize = 150_000;
80
81 pub fn new(
83 max_files_per_batch: usize,
84 max_bytes_per_batch: usize,
85 max_rows_per_batch: usize,
86 ) -> Result<Self, DomainError> {
87 if max_files_per_batch == 0 {
88 return Err(DomainError::invalid(
89 "max_files_per_batch",
90 "must be greater than zero",
91 ));
92 }
93 if max_bytes_per_batch == 0 {
94 return Err(DomainError::invalid(
95 "max_bytes_per_batch",
96 "must be greater than zero",
97 ));
98 }
99 if max_rows_per_batch == 0 {
100 return Err(DomainError::invalid(
101 "max_rows_per_batch",
102 "must be greater than zero",
103 ));
104 }
105
106 Ok(Self {
107 max_files_per_batch,
108 max_bytes_per_batch,
109 max_rows_per_batch,
110 })
111 }
112}
113
114impl Default for CodeIndexResourceBudget {
115 fn default() -> Self {
116 Self {
117 max_files_per_batch: Self::DEFAULT_MAX_FILES_PER_BATCH,
118 max_bytes_per_batch: Self::DEFAULT_MAX_BYTES_PER_BATCH,
119 max_rows_per_batch: Self::DEFAULT_MAX_ROWS_PER_BATCH,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub struct CodeIndexSession {
127 pub repository_id: String,
128 pub source_scope: String,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub base_resolved_commit_sha: Option<String>,
131 pub resolved_commit_sha: String,
132 pub tree_hash: String,
133 pub path_filters: Vec<String>,
134 pub language_filters: Vec<String>,
135 pub full_replace: bool,
136 pub total_path_count: usize,
137 pub changed_path_count: usize,
138 pub skipped_unchanged_count: usize,
139 pub deleted_paths: Vec<String>,
140 #[serde(default)]
145 pub changed_paths: Vec<String>,
146 pub tombstones: Vec<CodePathTombstone>,
147 #[serde(default)]
148 pub workspaces: Vec<CodeMonorepoWorkspace>,
149 pub resource_budget: CodeIndexResourceBudget,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct CodeIndexBatch {
155 pub repository_id: String,
156 pub source_scope: String,
157 pub batch_index: usize,
158 pub parsed_byte_count: usize,
159 pub files: Vec<RepositoryCodeFileRecord>,
160 pub symbols: Vec<RepositoryCodeSymbolRecord>,
161 pub references: Vec<RepositoryCodeReferenceRecord>,
162 pub imports: Vec<CodeImportRecord>,
163 pub dependencies: Vec<CodeDependencyRecord>,
164 pub feature_flags: Vec<CodeFeatureFlagRecord>,
165 #[serde(default)]
166 pub framework_nodes: Vec<CodeFrameworkNodeRecord>,
167 #[serde(default)]
168 pub framework_edges: Vec<CodeFrameworkEdgeRecord>,
169 pub routes: Vec<CodeRouteRecord>,
170 pub chunks: Vec<RepositoryCodeChunkRecord>,
171 pub diagnostics: Vec<CodeFileDiagnostic>,
172}
173
174impl CodeIndexBatch {
175 pub fn row_count(&self) -> usize {
176 self.files
177 .len()
178 .saturating_add(self.symbols.len())
179 .saturating_add(self.references.len())
180 .saturating_add(self.imports.len())
181 .saturating_add(self.dependencies.len())
182 .saturating_add(self.feature_flags.len())
183 .saturating_add(self.framework_nodes.len())
184 .saturating_add(self.framework_edges.len())
185 .saturating_add(self.routes.len())
186 .saturating_add(self.chunks.len())
187 .saturating_add(self.diagnostics.len())
188 }
189}
190
191pub(crate) const CODE_QUERY_INDEX_PLAN_VERSION: u32 = 3;
197
198pub(crate) const CODE_QUERY_INDEX_PLAN_UNIT_COUNT: usize = 17;
200
201const LEGACY_CODE_QUERY_INDEX_PLAN_V1: u32 = 1;
202const LEGACY_CODE_QUERY_INDEX_PLAN_V1_UNIT_COUNT: usize = 16;
203const LEGACY_CODE_QUERY_INDEX_PLAN_V2: u32 = 2;
204const LEGACY_CODE_QUERY_INDEX_PLAN_V2_UNIT_COUNT: usize = 17;
205
206const CODE_QUERY_INDEX_SUBPHASE_PREFIX: &str = "finalizing:build_query_indexes";
207const CODE_QUERY_INDEX_REPAIR_PREFIX: &str = "finalizing:query_index_repair";
208const CODE_REFERENCE_SEARCH_REBUILD_PREFIX: &str = "finalizing:rebuild_reference_search";
209const CODE_REFERENCE_SEARCH_REBUILD_VERSION: u32 = 2;
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub(crate) enum CodeReferenceSearchRebuildStage {
214 Cleanup,
215 Discover,
216 Build,
217}
218
219impl CodeReferenceSearchRebuildStage {
220 const fn code(self) -> &'static str {
221 match self {
222 Self::Cleanup => "cleanup",
223 Self::Discover => "discover",
224 Self::Build => "build",
225 }
226 }
227
228 fn parse(code: &str) -> Option<Self> {
229 match code {
230 "cleanup" => Some(Self::Cleanup),
231 "discover" => Some(Self::Discover),
232 "build" => Some(Self::Build),
233 _ => None,
234 }
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub(crate) struct CodeReferenceSearchRebuild {
241 pub(crate) protocol_version: u32,
242 pub(crate) stage: CodeReferenceSearchRebuildStage,
243 pub(crate) completed_page_ordinal: usize,
244}
245
246impl CodeReferenceSearchRebuild {
247 pub(crate) fn checkpoint_state(self) -> Option<String> {
253 (matches!(
254 self.protocol_version,
255 1 | CODE_REFERENCE_SEARCH_REBUILD_VERSION
256 ) && !(self.protocol_version == 1
257 && self.stage == CodeReferenceSearchRebuildStage::Discover))
258 .then(|| {
259 format!(
260 "{CODE_REFERENCE_SEARCH_REBUILD_PREFIX}:v{}:{}:{}",
261 self.protocol_version,
262 self.stage.code(),
263 self.completed_page_ordinal
264 )
265 })
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub(crate) struct CodeReferenceSearchQueryIndexRepair {
273 pub(crate) plan_version: u32,
274 pub(crate) completed_unit: usize,
275 pub(crate) reference_search: CodeReferenceSearchRebuild,
276}
277
278impl CodeReferenceSearchQueryIndexRepair {
279 pub(crate) const fn requires_legacy_retired_prefix(self) -> bool {
280 self.plan_version == LEGACY_CODE_QUERY_INDEX_PLAN_V2
281 }
282
283 pub(crate) fn next_state(self, completed_unit: usize) -> Option<String> {
286 code_reference_search_query_index_repair_state_for_version(
287 self.plan_version,
288 completed_unit,
289 self.reference_search,
290 )
291 }
292}
293
294pub(crate) fn code_reference_search_rebuild_state(
295 stage: CodeReferenceSearchRebuildStage,
296 completed_page_ordinal: usize,
297) -> String {
298 format!(
299 "{CODE_REFERENCE_SEARCH_REBUILD_PREFIX}:v{CODE_REFERENCE_SEARCH_REBUILD_VERSION}:{}:{completed_page_ordinal}",
300 stage.code()
301 )
302}
303
304pub(crate) fn code_reference_search_rebuild(state: &str) -> Option<CodeReferenceSearchRebuild> {
307 let suffix = state.strip_prefix(&format!("{CODE_REFERENCE_SEARCH_REBUILD_PREFIX}:v"))?;
308 let mut parts = suffix.split(':');
309 let version = parts.next()?.parse::<u32>().ok()?;
310 let stage = CodeReferenceSearchRebuildStage::parse(parts.next()?)?;
311 let completed_page_ordinal = parts.next()?.parse::<usize>().ok()?;
312 if !matches!(version, 1 | CODE_REFERENCE_SEARCH_REBUILD_VERSION)
313 || (version == 1 && stage == CodeReferenceSearchRebuildStage::Discover)
314 || parts.next().is_some()
315 {
316 return None;
317 }
318 let canonical = format!(
319 "{CODE_REFERENCE_SEARCH_REBUILD_PREFIX}:v{version}:{}:{completed_page_ordinal}",
320 stage.code()
321 );
322 (canonical == state).then_some(CodeReferenceSearchRebuild {
323 protocol_version: version,
324 stage,
325 completed_page_ordinal,
326 })
327}
328
329pub(crate) fn code_reference_search_query_index_repair_state(
330 unit: usize,
331 reference_search: CodeReferenceSearchRebuild,
332) -> Option<String> {
333 code_reference_search_query_index_repair_state_for_version(
334 CODE_QUERY_INDEX_PLAN_VERSION,
335 unit,
336 reference_search,
337 )
338}
339
340fn code_reference_search_query_index_repair_state_for_version(
341 plan_version: u32,
342 unit: usize,
343 reference_search: CodeReferenceSearchRebuild,
344) -> Option<String> {
345 (matches!(
346 plan_version,
347 LEGACY_CODE_QUERY_INDEX_PLAN_V2 | CODE_QUERY_INDEX_PLAN_VERSION
348 ) && unit < CODE_QUERY_INDEX_PLAN_UNIT_COUNT
349 && matches!(
350 reference_search.protocol_version,
351 1 | CODE_REFERENCE_SEARCH_REBUILD_VERSION
352 )
353 && !(reference_search.protocol_version == 1
354 && reference_search.stage == CodeReferenceSearchRebuildStage::Discover))
355 .then(|| {
356 format!(
357 "{CODE_QUERY_INDEX_REPAIR_PREFIX}:v{plan_version}:{unit}:resume:reference_search:v{}:{}:{}",
358 reference_search.protocol_version,
359 reference_search.stage.code(), reference_search.completed_page_ordinal
360 )
361 })
362}
363
364pub(crate) fn code_reference_search_query_index_repair(
365 state: &str,
366) -> Option<CodeReferenceSearchQueryIndexRepair> {
367 let suffix = state.strip_prefix(&format!("{CODE_QUERY_INDEX_REPAIR_PREFIX}:v"))?;
368 let (version_and_unit, reference) = suffix.split_once(":resume:reference_search:v")?;
369 let (version, unit) = version_and_unit.split_once(':')?;
370 let version = version.parse::<u32>().ok()?;
371 let unit = unit.parse::<usize>().ok()?;
372 let mut reference = reference.split(':');
373 let reference_version = reference.next()?.parse::<u32>().ok()?;
374 let stage = CodeReferenceSearchRebuildStage::parse(reference.next()?)?;
375 let completed_page_ordinal = reference.next()?.parse::<usize>().ok()?;
376 if !matches!(
377 version,
378 CODE_QUERY_INDEX_PLAN_VERSION | LEGACY_CODE_QUERY_INDEX_PLAN_V2
379 ) || unit >= CODE_QUERY_INDEX_PLAN_UNIT_COUNT
380 || !matches!(reference_version, 1 | CODE_REFERENCE_SEARCH_REBUILD_VERSION)
381 || (reference_version == 1 && stage == CodeReferenceSearchRebuildStage::Discover)
382 || reference.next().is_some()
383 {
384 return None;
385 }
386 let reference_search = CodeReferenceSearchRebuild {
387 protocol_version: reference_version,
388 stage,
389 completed_page_ordinal,
390 };
391 let canonical = format!(
392 "{CODE_QUERY_INDEX_REPAIR_PREFIX}:v{version}:{unit}:resume:reference_search:v{reference_version}:{}:{}",
393 reference_search.stage.code(),
394 reference_search.completed_page_ordinal
395 );
396 (canonical == state).then_some(CodeReferenceSearchQueryIndexRepair {
397 plan_version: version,
398 completed_unit: unit,
399 reference_search,
400 })
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
409#[repr(u8)]
410pub(crate) enum CodeQueryIndexRepairResumePhase {
411 BuildQueryIndexes = 0,
412 ResolveReferences = 1,
413 ResolveImports = 2,
414 ResolveCallTargets = 3,
415 RefreshDependencies = 4,
416 RebuildReferenceSearch = 5,
417 RebuildCalls = 6,
418 PublishScope = 7,
419 ResolveWorkspaceImports = 8,
420 SoftwareProjection = 9,
421 PartitionedPublish = 10,
422}
423
424impl CodeQueryIndexRepairResumePhase {
425 pub(crate) const ALL: [Self; 11] = [
426 Self::BuildQueryIndexes,
427 Self::ResolveReferences,
428 Self::ResolveImports,
429 Self::ResolveCallTargets,
430 Self::RefreshDependencies,
431 Self::RebuildReferenceSearch,
432 Self::RebuildCalls,
433 Self::PublishScope,
434 Self::ResolveWorkspaceImports,
435 Self::SoftwareProjection,
436 Self::PartitionedPublish,
437 ];
438
439 pub(crate) const fn checkpoint_state(self) -> &'static str {
440 match self {
441 Self::BuildQueryIndexes => "finalizing:build_query_indexes",
442 Self::ResolveReferences => "finalizing:resolve_references",
443 Self::ResolveImports => "finalizing:resolve_imports",
444 Self::ResolveCallTargets => "finalizing:resolve_call_targets",
445 Self::RefreshDependencies => "finalizing:refresh_dependencies",
446 Self::RebuildReferenceSearch => "finalizing:rebuild_reference_search",
447 Self::RebuildCalls => "finalizing:rebuild_calls",
448 Self::PublishScope => "finalizing:publish_scope",
449 Self::ResolveWorkspaceImports => "finalizing:resolve_workspace_imports",
450 Self::SoftwareProjection => "finalizing:software_projection",
451 Self::PartitionedPublish => "finalizing:partitioned_publish",
452 }
453 }
454
455 pub(crate) fn from_checkpoint_state(state: &str) -> Option<Self> {
456 Self::ALL
457 .into_iter()
458 .find(|phase| phase.checkpoint_state() == state)
459 }
460
461 const fn code(self) -> u8 {
462 self as u8
463 }
464
465 fn from_code(code: u8) -> Option<Self> {
466 Self::ALL.into_iter().find(|phase| phase.code() == code)
467 }
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq)]
472pub(crate) struct CodeQueryIndexRepair {
473 pub(crate) plan_version: u32,
474 pub(crate) completed_unit: usize,
475 pub(crate) resume_phase: CodeQueryIndexRepairResumePhase,
476}
477
478impl CodeQueryIndexRepair {
479 pub(crate) const fn requires_legacy_retired_prefix(self) -> bool {
480 self.plan_version == LEGACY_CODE_QUERY_INDEX_PLAN_V2
481 }
482
483 pub(crate) fn next_state(self, completed_unit: usize) -> Option<String> {
486 code_query_index_repair_state_for_version(
487 self.plan_version,
488 completed_unit,
489 self.resume_phase,
490 )
491 }
492}
493
494#[derive(Debug, Clone, Copy, PartialEq, Eq)]
496pub(crate) struct CodeQueryIndexSubphase {
497 pub(crate) plan_version: u32,
498 pub(crate) completed_unit: usize,
499}
500
501impl CodeQueryIndexSubphase {
502 pub(crate) const fn requires_legacy_retired_prefix(self) -> bool {
503 matches!(
504 self.plan_version,
505 LEGACY_CODE_QUERY_INDEX_PLAN_V1 | LEGACY_CODE_QUERY_INDEX_PLAN_V2
506 )
507 }
508
509 pub(crate) fn next_state(self, completed_unit: usize) -> Option<String> {
512 code_query_index_subphase_state_for_version(self.plan_version, completed_unit)
513 }
514}
515
516pub(crate) fn code_query_index_subphase_state(unit: usize) -> Option<String> {
518 code_query_index_subphase_state_for_version(CODE_QUERY_INDEX_PLAN_VERSION, unit)
519}
520
521fn code_query_index_subphase_state_for_version(plan_version: u32, unit: usize) -> Option<String> {
522 let unit_count = match plan_version {
523 CODE_QUERY_INDEX_PLAN_VERSION => CODE_QUERY_INDEX_PLAN_UNIT_COUNT,
524 LEGACY_CODE_QUERY_INDEX_PLAN_V2 => LEGACY_CODE_QUERY_INDEX_PLAN_V2_UNIT_COUNT,
525 LEGACY_CODE_QUERY_INDEX_PLAN_V1 => LEGACY_CODE_QUERY_INDEX_PLAN_V1_UNIT_COUNT,
526 _ => return None,
527 };
528 (unit < unit_count)
529 .then(|| format!("{CODE_QUERY_INDEX_SUBPHASE_PREFIX}:v{plan_version}:{unit}"))
530}
531
532pub(crate) fn code_query_index_subphase(state: &str) -> Option<CodeQueryIndexSubphase> {
538 let suffix = state.strip_prefix(&format!("{CODE_QUERY_INDEX_SUBPHASE_PREFIX}:v"))?;
539 let (version, unit) = suffix.split_once(':')?;
540 let version = version.parse::<u32>().ok()?;
541 let unit = unit.parse::<usize>().ok()?;
542 let unit_count = match version {
543 CODE_QUERY_INDEX_PLAN_VERSION => CODE_QUERY_INDEX_PLAN_UNIT_COUNT,
544 LEGACY_CODE_QUERY_INDEX_PLAN_V2 => LEGACY_CODE_QUERY_INDEX_PLAN_V2_UNIT_COUNT,
545 LEGACY_CODE_QUERY_INDEX_PLAN_V1 => LEGACY_CODE_QUERY_INDEX_PLAN_V1_UNIT_COUNT,
546 _ => return None,
547 };
548 if unit >= unit_count {
549 return None;
550 }
551 let canonical = format!("{CODE_QUERY_INDEX_SUBPHASE_PREFIX}:v{version}:{unit}");
552 (canonical == state).then_some(CodeQueryIndexSubphase {
553 plan_version: version,
554 completed_unit: unit,
555 })
556}
557
558pub(crate) fn code_query_index_repair_state(
561 unit: usize,
562 resume_phase: CodeQueryIndexRepairResumePhase,
563) -> Option<String> {
564 code_query_index_repair_state_for_version(CODE_QUERY_INDEX_PLAN_VERSION, unit, resume_phase)
565}
566
567fn code_query_index_repair_state_for_version(
568 plan_version: u32,
569 unit: usize,
570 resume_phase: CodeQueryIndexRepairResumePhase,
571) -> Option<String> {
572 (matches!(
573 plan_version,
574 LEGACY_CODE_QUERY_INDEX_PLAN_V2 | CODE_QUERY_INDEX_PLAN_VERSION
575 ) && unit < CODE_QUERY_INDEX_PLAN_UNIT_COUNT)
576 .then(|| {
577 format!(
578 "{CODE_QUERY_INDEX_REPAIR_PREFIX}:v{plan_version}:{unit}:resume:{}",
579 resume_phase.code()
580 )
581 })
582}
583
584pub(crate) fn code_query_index_repair(state: &str) -> Option<CodeQueryIndexRepair> {
586 let suffix = state.strip_prefix(&format!("{CODE_QUERY_INDEX_REPAIR_PREFIX}:v"))?;
587 let (version_and_unit, resume_code) = suffix.split_once(":resume:")?;
588 let (version, unit) = version_and_unit.split_once(':')?;
589 let version = version.parse::<u32>().ok()?;
590 let unit = unit.parse::<usize>().ok()?;
591 let resume_code = resume_code.parse::<u8>().ok()?;
592 if !matches!(
593 version,
594 CODE_QUERY_INDEX_PLAN_VERSION | LEGACY_CODE_QUERY_INDEX_PLAN_V2
595 ) || unit >= CODE_QUERY_INDEX_PLAN_UNIT_COUNT
596 {
597 return None;
598 }
599 let resume_phase = CodeQueryIndexRepairResumePhase::from_code(resume_code)?;
600 let canonical = format!(
601 "{CODE_QUERY_INDEX_REPAIR_PREFIX}:v{version}:{unit}:resume:{}",
602 resume_phase.code()
603 );
604 (canonical == state).then_some(CodeQueryIndexRepair {
605 plan_version: version,
606 completed_unit: unit,
607 resume_phase,
608 })
609}
610
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
613pub struct CodeIncrementalSummaryReceipt {
614 pub task_id: String,
615 pub base_resolved_commit_sha: String,
616 pub changed_path_count: usize,
617 pub skipped_unchanged_count: usize,
618 pub deleted_path_count: usize,
619 pub affected_path_count: usize,
620 pub blob_read_count: usize,
621 pub parsed_file_count: usize,
622 pub sqlite_write_count: usize,
623 pub degraded_file_count: usize,
624 pub batch_count: usize,
625}
626
627impl CodeIncrementalSummaryReceipt {
628 pub(crate) fn validate(&self) -> Result<(), DomainError> {
629 let affected_surface = self
630 .parsed_file_count
631 .checked_add(self.deleted_path_count)
632 .ok_or_else(|| {
633 DomainError::invalid(
634 "incremental_summary",
635 "affected path surface exceeds platform capacity",
636 )
637 })?;
638 let minimum_sqlite_writes = self
639 .parsed_file_count
640 .checked_add(self.degraded_file_count)
641 .ok_or_else(|| {
642 DomainError::invalid(
643 "incremental_summary",
644 "minimum SQLite write count exceeds platform capacity",
645 )
646 })?;
647 if self.task_id.trim().is_empty()
648 || self.task_id.len() > 1_024
649 || self.base_resolved_commit_sha.trim().is_empty()
650 || self.base_resolved_commit_sha.len() > 1_024
651 || self.blob_read_count != self.parsed_file_count
652 || self.parsed_file_count > self.affected_path_count
653 || self.deleted_path_count > self.affected_path_count
654 || self.affected_path_count > affected_surface
655 || self.degraded_file_count > self.parsed_file_count
656 || self.sqlite_write_count < minimum_sqlite_writes
657 || self.batch_count != 1
658 {
659 return Err(DomainError::invalid(
660 "incremental_summary",
661 "durable incremental metrics are inconsistent",
662 ));
663 }
664 Ok(())
665 }
666}
667
668#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
670pub struct CodeIndexCheckpoint {
671 pub repository_id: String,
672 pub source_scope: String,
673 #[serde(default)]
674 pub resolved_commit_sha: String,
675 #[serde(default)]
676 pub tree_hash: String,
677 #[serde(default)]
678 pub path_filters: Vec<String>,
679 #[serde(default)]
680 pub language_filters: Vec<String>,
681 pub state: String,
682 pub total_path_count: usize,
683 pub parsed_file_count: usize,
684 pub committed_file_count: usize,
685 pub committed_symbol_count: usize,
686 pub committed_reference_count: usize,
687 pub committed_chunk_count: usize,
688 #[serde(default)]
689 pub committed_fact_row_count: usize,
690 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub incremental_summary: Option<CodeIncrementalSummaryReceipt>,
692 pub batch_count: usize,
693 #[serde(skip_serializing_if = "Option::is_none")]
694 pub last_path: Option<String>,
695 pub resource_budget: CodeIndexResourceBudget,
696 pub updated_at_ms: u64,
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
701#[serde(rename_all = "snake_case")]
702pub enum CodeIndexTaskState {
703 Queued,
704 Running,
705 Succeeded,
706 Retrying,
707 Failed,
708 DeadLetter,
709 Cancelled,
710}
711
712impl CodeIndexTaskState {
713 pub const fn as_str(self) -> &'static str {
715 match self {
716 Self::Queued => "queued",
717 Self::Running => "running",
718 Self::Succeeded => "succeeded",
719 Self::Retrying => "retrying",
720 Self::Failed => "failed",
721 Self::DeadLetter => "dead_letter",
722 Self::Cancelled => "cancelled",
723 }
724 }
725
726 pub fn parse(value: &str) -> Result<Self, DomainError> {
728 match value {
729 "queued" => Ok(Self::Queued),
730 "running" => Ok(Self::Running),
731 "succeeded" => Ok(Self::Succeeded),
732 "retrying" => Ok(Self::Retrying),
733 "failed" => Ok(Self::Failed),
734 "dead_letter" => Ok(Self::DeadLetter),
735 "cancelled" => Ok(Self::Cancelled),
736 _ => Err(DomainError::invalid(
737 "code_index_task_state",
738 "unknown code index task state",
739 )),
740 }
741 }
742
743 pub const fn is_unfinished(self) -> bool {
745 matches!(self, Self::Queued | Self::Running | Self::Retrying)
746 }
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
751pub struct CodeIndexTaskRecord {
752 pub task_id: String,
753 pub repository_id: String,
754 pub alias: String,
755 pub ref_selector: String,
756 pub resolved_commit_sha: String,
757 pub tree_hash: String,
758 pub source_scope: String,
759 pub path_filters: Vec<String>,
760 pub language_filters: Vec<String>,
761 pub mode: CodeIndexMode,
762 pub state: CodeIndexTaskState,
763 #[serde(skip_serializing_if = "Option::is_none")]
764 pub lease_owner: Option<String>,
765 #[serde(skip_serializing_if = "Option::is_none")]
766 pub lease_expires_at_ms: Option<u64>,
767 pub attempt_count: u32,
768 #[serde(default)]
770 pub publication_generation: u64,
771 pub next_retry_at_ms: u64,
772 pub input_fingerprint: String,
773 pub resource_budget: CodeIndexResourceBudget,
774 pub payload_json: String,
775 #[serde(skip_serializing_if = "Option::is_none")]
776 pub last_error_kind: Option<String>,
777 #[serde(skip_serializing_if = "Option::is_none")]
778 pub last_error_message: Option<String>,
779 pub created_at_ms: u64,
780 pub updated_at_ms: u64,
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
785pub struct CodeIndexPublicationFence {
786 pub repository_id: String,
787 pub task_id: String,
788 pub lease_owner: String,
789 pub attempt_count: u32,
790 pub generation: u64,
791}
792
793#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
795pub struct CodeIndexTaskQueueStatus {
796 pub queued_task_count: usize,
797 pub running_task_count: usize,
798 pub retrying_task_count: usize,
799 pub dead_letter_task_count: usize,
800 pub running_lease_count: usize,
801 #[serde(skip_serializing_if = "Option::is_none")]
802 pub last_error: Option<String>,
803}
804
805#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
807pub struct CodeScopeRetentionSummary {
808 pub repository_id: String,
809 pub retained_scope_count: usize,
810 pub prunable_scope_count: usize,
811 pub pruned_scope_count: usize,
812 #[serde(default)]
813 pub scope_listing_truncated: bool,
814 #[serde(default)]
815 pub retiring_job_count: usize,
816 #[serde(default)]
817 pub maintenance_pending: bool,
818 pub retained_scopes: Vec<String>,
819 pub prunable_scopes: Vec<String>,
820 pub pruned_scopes: Vec<String>,
821 #[serde(default)]
822 pub retiring_jobs: Vec<CodeScopeRetirementJobStatus>,
823 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub repository_retention_job: Option<CodeRepositoryRetentionJobStatus>,
825}
826
827#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
829pub struct CodeRepositoryRetentionJobStatus {
830 pub repository_id: String,
831 pub initial_scope: String,
832 pub cutoff_ms: u64,
833 #[serde(default)]
834 pub cutoff_publication_generation: u64,
835 pub phase: String,
836 pub created_at_ms: u64,
837 pub updated_at_ms: u64,
838 #[serde(default, skip_serializing_if = "Option::is_none")]
839 pub last_error: Option<String>,
840}
841
842#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
844pub struct CodeScopeRetirementJobStatus {
845 pub repository_id: String,
846 pub source_scope: String,
847 pub phase: String,
848 pub deleted_rows: usize,
849 pub updated_at_ms: u64,
850 #[serde(default, skip_serializing_if = "Option::is_none")]
851 pub last_error: Option<String>,
852}
853
854#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
856pub struct CodeIndexProgressSummary {
857 pub git_file_count: usize,
858 pub blob_read_count: usize,
859 pub parsed_file_count: usize,
860 pub sqlite_write_count: usize,
861 pub skipped_file_count: usize,
862 pub degraded_file_count: usize,
863 pub batch_count: usize,
864 pub checkpoint_file_count: usize,
865 pub resource_budget: CodeIndexResourceBudget,
866}
867
868#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
870pub struct CodeIndexSummary {
871 pub repository_id: String,
872 pub source_scope: String,
873 #[serde(default, skip_serializing_if = "Option::is_none")]
874 pub base_resolved_commit_sha: Option<String>,
875 pub resolved_commit_sha: String,
876 pub tree_hash: String,
877 pub indexed_file_count: usize,
878 pub changed_path_count: usize,
879 pub skipped_unchanged_count: usize,
880 pub deleted_path_count: usize,
881 pub symbol_count: usize,
882 #[serde(default)]
883 pub handwritten_symbol_count: usize,
884 #[serde(default)]
885 pub generated_symbol_count: usize,
886 pub reference_count: usize,
887 pub chunk_count: usize,
888 pub degraded_file_count: usize,
889 pub progress: CodeIndexProgressSummary,
890}
891
892#[cfg(test)]
893#[path = "mod_tests.rs"]
894mod mod_tests;