1use serde::{Deserialize, Serialize};
2
3use super::{
4 CodeDependencyRecord, CodeParseStatus, CodeParseStatusCounts, DomainError, FreshnessPolicy,
5 error::required_text,
6};
7
8const CODE_SNAPSHOT_FACT_VERSION: &str =
9 "code-facts-js-ts-import-edges-v1-sbom-dependencies-v2-python-type-refs-v1-scope-compat-v1";
10
11pub fn code_snapshot_scope_id(
13 repository_id: &str,
14 tree_hash: &str,
15 path_filters: &[String],
16 language_filters: &[String],
17) -> String {
18 let mut input = Vec::new();
19 append_hash_part(&mut input, "git_snapshot");
20 append_hash_part(&mut input, repository_id);
21 append_hash_part(&mut input, tree_hash);
22 append_hash_list(&mut input, path_filters);
23 append_hash_list(&mut input, language_filters);
24 append_hash_part(&mut input, CODE_SNAPSHOT_FACT_VERSION);
25
26 format!("git_snapshot:{:016x}", stable_hash64(&input))
27}
28
29pub fn code_snapshot_expected_scope_id(
30 repository_id: &str,
31 tree_hash: &str,
32 path_filters: &[String],
33 language_filters: &[String],
34) -> Option<String> {
35 Some(code_snapshot_scope_id(
36 repository_id,
37 tree_hash,
38 path_filters,
39 language_filters,
40 ))
41}
42
43pub fn code_snapshot_scope_is_fact_versioned(source_scope: &str) -> bool {
44 let Some(scope_hash) = source_scope.strip_prefix("git_snapshot:") else {
45 return false;
46 };
47 scope_hash.len() == 16
48 && scope_hash
49 .chars()
50 .all(|character| character.is_ascii_hexdigit())
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct RepositoryCodeRange {
56 pub start: u32,
57 pub end: u32,
58}
59
60impl RepositoryCodeRange {
61 pub fn new(field: &'static str, start: usize, end: usize) -> Result<Self, DomainError> {
63 if end < start {
64 return Err(DomainError::invalid(
65 field,
66 "end must be greater than or equal to start",
67 ));
68 }
69
70 Ok(Self {
71 start: checked_u32(field, start)?,
72 end: checked_u32(field, end)?,
73 })
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct CodeRepositoryRegistration {
80 pub repository_id: String,
81 pub alias: String,
82 pub root_path: String,
83 pub path_filters: Vec<String>,
84 pub language_filters: Vec<String>,
85}
86
87impl CodeRepositoryRegistration {
88 pub fn new(
90 repository_id: impl Into<String>,
91 alias: impl Into<String>,
92 root_path: impl Into<String>,
93 path_filters: Vec<String>,
94 language_filters: Vec<String>,
95 ) -> Result<Self, DomainError> {
96 Ok(Self {
97 repository_id: required_text("repository_id", repository_id)?,
98 alias: required_text("alias", alias)?,
99 root_path: required_text("root_path", root_path)?,
100 path_filters: normalize_filter_list("path_filter", path_filters)?,
101 language_filters: normalize_filter_list("language_filter", language_filters)?,
102 })
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct CodeRepositorySelector {
109 pub repository: String,
110 pub ref_selector: String,
111 pub path_filters: Vec<String>,
112 pub language_filters: Vec<String>,
113}
114
115impl CodeRepositorySelector {
116 pub fn new(
118 repository: impl Into<String>,
119 ref_selector: impl Into<String>,
120 path_filters: Vec<String>,
121 language_filters: Vec<String>,
122 ) -> Result<Self, DomainError> {
123 Ok(Self {
124 repository: required_text("repository", repository)?,
125 ref_selector: required_text("ref_selector", ref_selector)?,
126 path_filters: normalize_filter_list("path_filter", path_filters)?,
127 language_filters: normalize_filter_list("language_filter", language_filters)?,
128 })
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case")]
135pub enum CodeIndexMode {
136 Full,
137 Incremental { base_ref: String, head_ref: String },
138 WorktreeOverlay,
139}
140
141impl CodeIndexMode {
142 pub fn incremental(
144 base_ref: impl Into<String>,
145 head_ref: impl Into<String>,
146 ) -> Result<Self, DomainError> {
147 Ok(Self::Incremental {
148 base_ref: required_text("base_ref", base_ref)?,
149 head_ref: required_text("head_ref", head_ref)?,
150 })
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct CodeIndexRequest {
157 pub repository: CodeRepositorySelector,
158 pub mode: CodeIndexMode,
159 pub freshness_policy: FreshnessPolicy,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum CodeQueryKind {
166 Hybrid,
167 Symbol,
168 Definition,
169 References,
170 Callers,
171 Callees,
172 Imports,
173 Sbom,
174 Impact,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179pub struct CodeRetrievalRequest {
180 pub query: String,
181 pub repository: CodeRepositorySelector,
182 pub code_query_kind: CodeQueryKind,
183 pub limit: usize,
184 pub freshness_policy: FreshnessPolicy,
185}
186
187impl CodeRetrievalRequest {
188 pub fn new(
190 query: impl Into<String>,
191 repository: CodeRepositorySelector,
192 code_query_kind: CodeQueryKind,
193 limit: usize,
194 freshness_policy: FreshnessPolicy,
195 ) -> Result<Self, DomainError> {
196 let limit = match limit {
197 1..=50 => limit,
198 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
199 _ => return Err(DomainError::invalid("limit", "must be 50 or less")),
200 };
201
202 Ok(Self {
203 query: required_text("query", query)?,
204 repository,
205 code_query_kind,
206 limit,
207 freshness_policy,
208 })
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct CodeFeatureFlagRequest {
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub query: Option<String>,
217 pub repository: CodeRepositorySelector,
218 pub limit: usize,
219 pub freshness_policy: FreshnessPolicy,
220}
221
222impl CodeFeatureFlagRequest {
223 pub fn new(
225 query: Option<String>,
226 repository: CodeRepositorySelector,
227 limit: usize,
228 freshness_policy: FreshnessPolicy,
229 ) -> Result<Self, DomainError> {
230 let limit = match limit {
231 1..=100 => limit,
232 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
233 _ => return Err(DomainError::invalid("limit", "must be 100 or less")),
234 };
235 let query = query
236 .map(|value| required_text("query", value))
237 .transpose()?;
238
239 Ok(Self {
240 query,
241 repository,
242 limit,
243 freshness_policy,
244 })
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250pub struct CodeImpactRequest {
251 pub repository: CodeRepositorySelector,
252 pub base_ref: String,
253 pub head_ref: String,
254 pub limit: usize,
255}
256
257impl CodeImpactRequest {
258 pub fn new(
260 repository: CodeRepositorySelector,
261 base_ref: impl Into<String>,
262 head_ref: impl Into<String>,
263 limit: usize,
264 ) -> Result<Self, DomainError> {
265 let limit = match limit {
266 1..=100 => limit,
267 0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
268 _ => return Err(DomainError::invalid("limit", "must be 100 or less")),
269 };
270
271 Ok(Self {
272 repository,
273 base_ref: required_text("base_ref", base_ref)?,
274 head_ref: required_text("head_ref", head_ref)?,
275 limit,
276 })
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum CodeRetrievalLayer {
284 Lexical,
285 Symbol,
286 Definition,
287 Reference,
288 CallGraph,
289 ImportGraph,
290 Sbom,
291 Impact,
292 TextFallback,
293}
294
295impl CodeRetrievalLayer {
296 pub const fn as_str(self) -> &'static str {
298 match self {
299 Self::Lexical => "lexical",
300 Self::Symbol => "symbol",
301 Self::Definition => "definition",
302 Self::Reference => "reference",
303 Self::CallGraph => "call_graph",
304 Self::ImportGraph => "import_graph",
305 Self::Sbom => "sbom",
306 Self::Impact => "impact",
307 Self::TextFallback => "text_fallback",
308 }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
314pub struct CodeRepositoryStatus {
315 pub repository_id: String,
316 pub alias: String,
317 pub root_path: String,
318 pub path_filters: Vec<String>,
319 pub language_filters: Vec<String>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub last_indexed_scope_id: Option<String>,
322 pub last_indexed_commit: Option<String>,
323 pub tree_hash: Option<String>,
324 pub state: String,
325 pub indexed_file_count: usize,
326 pub symbol_count: usize,
327 pub reference_count: usize,
328 pub chunk_count: usize,
329 pub stale: bool,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub degraded_reason: Option<String>,
332}
333
334#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
336pub struct CodeRepositoryRemovalSummary {
337 pub repository_id: String,
338 pub aliases_removed: Vec<String>,
339 pub removed_scope_count: usize,
340 pub removed_index_task_count: usize,
341 pub removed_repository_set_member_count: usize,
342 pub invalidated_repository_set_count: usize,
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
347pub struct RepositoryCodeFileRecord {
348 pub repository_id: String,
349 pub source_scope: String,
350 pub file_id: String,
351 pub path: String,
352 pub language_id: String,
353 pub blob_hash: String,
354 pub byte_len: usize,
355 pub line_count: usize,
356 pub parse_status: CodeParseStatus,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub degraded_reason: Option<String>,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363pub struct CodeFileFingerprint {
364 pub path: String,
365 pub blob_hash: String,
366}
367
368#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
370pub struct RepositoryCodeSymbolRecord {
371 pub repository_id: String,
372 pub source_scope: String,
373 pub symbol_snapshot_id: String,
374 pub canonical_symbol_id: String,
375 pub file_id: String,
376 pub path: String,
377 pub language_id: String,
378 pub name: String,
379 pub qualified_name: String,
380 pub kind: String,
381 pub signature: String,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub doc_comment: Option<String>,
384 pub byte_range: RepositoryCodeRange,
385 pub line_range: RepositoryCodeRange,
386}
387
388#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
390pub struct RepositoryCodeReferenceRecord {
391 pub repository_id: String,
392 pub source_scope: String,
393 pub reference_id: String,
394 pub file_id: String,
395 pub path: String,
396 pub name: String,
397 pub kind: String,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub target_symbol_snapshot_id: Option<String>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub target_hint: Option<String>,
402 pub resolution_state: String,
403 pub confidence_basis_points: u16,
404 pub confidence_tier: String,
405 pub byte_range: RepositoryCodeRange,
406 pub line_range: RepositoryCodeRange,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
411pub struct CodeImportRecord {
412 pub repository_id: String,
413 pub source_scope: String,
414 pub import_id: String,
415 pub file_id: String,
416 pub path: String,
417 pub module: String,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub target_hint: Option<String>,
420 pub resolution_state: String,
421 pub confidence_basis_points: u16,
422 pub confidence_tier: String,
423 pub line_range: RepositoryCodeRange,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
428pub struct CodeCallRecord {
429 pub repository_id: String,
430 pub source_scope: String,
431 pub call_id: String,
432 pub file_id: String,
433 pub path: String,
434 pub caller_symbol_snapshot_id: Option<String>,
435 pub caller_name: Option<String>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub callee_symbol_snapshot_id: Option<String>,
438 pub callee_name: String,
439 #[serde(skip_serializing_if = "Option::is_none")]
440 pub target_hint: Option<String>,
441 pub resolution_state: String,
442 pub confidence_basis_points: u16,
443 pub confidence_tier: String,
444 pub line_range: RepositoryCodeRange,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct CodeFeatureFlagRecord {
450 pub repository_id: String,
451 pub source_scope: String,
452 pub feature_flag_id: String,
453 pub usage_id: String,
454 pub file_id: String,
455 pub path: String,
456 pub language_id: String,
457 pub name: String,
458 pub source_kind: String,
459 pub source_key: String,
460 pub edge_kind: String,
461 pub confidence_basis_points: u16,
462 pub confidence_tier: String,
463 pub byte_range: RepositoryCodeRange,
464 pub line_range: RepositoryCodeRange,
465 pub excerpt: String,
466}
467
468#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
470pub struct RepositoryCodeChunkRecord {
471 pub repository_id: String,
472 pub source_scope: String,
473 pub chunk_id: String,
474 pub file_id: String,
475 pub path: String,
476 pub language_id: String,
477 pub content: String,
478 pub byte_range: RepositoryCodeRange,
479 pub line_range: RepositoryCodeRange,
480 pub symbol_snapshot_id: Option<String>,
481}
482
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485pub struct CodeFileDiagnostic {
486 pub repository_id: String,
487 pub source_scope: String,
488 pub path: String,
489 pub parse_status: CodeParseStatus,
490 pub message: String,
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495pub struct CodePathTombstone {
496 pub repository_id: String,
497 pub source_scope: String,
498 pub old_path: String,
499 pub new_path: Option<String>,
500 pub base_ref: String,
501 pub head_ref: String,
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
506pub struct CodeIndexSnapshot {
507 pub repository_id: String,
508 pub source_scope: String,
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub base_resolved_commit_sha: Option<String>,
511 pub resolved_commit_sha: String,
512 pub tree_hash: String,
513 pub path_filters: Vec<String>,
514 pub language_filters: Vec<String>,
515 pub full_replace: bool,
516 pub changed_path_count: usize,
517 pub skipped_unchanged_count: usize,
518 pub deleted_paths: Vec<String>,
519 pub tombstones: Vec<CodePathTombstone>,
520 pub files: Vec<RepositoryCodeFileRecord>,
521 pub symbols: Vec<RepositoryCodeSymbolRecord>,
522 pub references: Vec<RepositoryCodeReferenceRecord>,
523 pub imports: Vec<CodeImportRecord>,
524 pub calls: Vec<CodeCallRecord>,
525 pub dependencies: Vec<CodeDependencyRecord>,
526 pub feature_flags: Vec<CodeFeatureFlagRecord>,
527 pub chunks: Vec<RepositoryCodeChunkRecord>,
528 pub diagnostics: Vec<CodeFileDiagnostic>,
529}
530
531#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
533pub struct CodeIndexResourceBudget {
534 pub max_files_per_batch: usize,
535 pub max_bytes_per_batch: usize,
536 pub max_rows_per_batch: usize,
537}
538
539impl CodeIndexResourceBudget {
540 pub const DEFAULT_MAX_FILES_PER_BATCH: usize = 512;
541 pub const DEFAULT_MAX_BYTES_PER_BATCH: usize = 16 * 1024 * 1024;
542 pub const DEFAULT_MAX_ROWS_PER_BATCH: usize = 150_000;
543
544 pub fn new(
546 max_files_per_batch: usize,
547 max_bytes_per_batch: usize,
548 max_rows_per_batch: usize,
549 ) -> Result<Self, DomainError> {
550 if max_files_per_batch == 0 {
551 return Err(DomainError::invalid(
552 "max_files_per_batch",
553 "must be greater than zero",
554 ));
555 }
556 if max_bytes_per_batch == 0 {
557 return Err(DomainError::invalid(
558 "max_bytes_per_batch",
559 "must be greater than zero",
560 ));
561 }
562 if max_rows_per_batch == 0 {
563 return Err(DomainError::invalid(
564 "max_rows_per_batch",
565 "must be greater than zero",
566 ));
567 }
568
569 Ok(Self {
570 max_files_per_batch,
571 max_bytes_per_batch,
572 max_rows_per_batch,
573 })
574 }
575}
576
577impl Default for CodeIndexResourceBudget {
578 fn default() -> Self {
579 Self {
580 max_files_per_batch: Self::DEFAULT_MAX_FILES_PER_BATCH,
581 max_bytes_per_batch: Self::DEFAULT_MAX_BYTES_PER_BATCH,
582 max_rows_per_batch: Self::DEFAULT_MAX_ROWS_PER_BATCH,
583 }
584 }
585}
586
587#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
589pub struct CodeIndexSession {
590 pub repository_id: String,
591 pub source_scope: String,
592 #[serde(skip_serializing_if = "Option::is_none")]
593 pub base_resolved_commit_sha: Option<String>,
594 pub resolved_commit_sha: String,
595 pub tree_hash: String,
596 pub path_filters: Vec<String>,
597 pub language_filters: Vec<String>,
598 pub full_replace: bool,
599 pub total_path_count: usize,
600 pub changed_path_count: usize,
601 pub skipped_unchanged_count: usize,
602 pub deleted_paths: Vec<String>,
603 pub tombstones: Vec<CodePathTombstone>,
604 pub resource_budget: CodeIndexResourceBudget,
605}
606
607#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
609pub struct CodeIndexBatch {
610 pub repository_id: String,
611 pub source_scope: String,
612 pub batch_index: usize,
613 pub parsed_byte_count: usize,
614 pub files: Vec<RepositoryCodeFileRecord>,
615 pub symbols: Vec<RepositoryCodeSymbolRecord>,
616 pub references: Vec<RepositoryCodeReferenceRecord>,
617 pub imports: Vec<CodeImportRecord>,
618 pub dependencies: Vec<CodeDependencyRecord>,
619 pub feature_flags: Vec<CodeFeatureFlagRecord>,
620 pub chunks: Vec<RepositoryCodeChunkRecord>,
621 pub diagnostics: Vec<CodeFileDiagnostic>,
622}
623
624impl CodeIndexBatch {
625 pub fn row_count(&self) -> usize {
627 self.files
628 .len()
629 .saturating_add(self.symbols.len())
630 .saturating_add(self.references.len())
631 .saturating_add(self.imports.len())
632 .saturating_add(self.dependencies.len())
633 .saturating_add(self.feature_flags.len())
634 .saturating_add(self.chunks.len())
635 .saturating_add(self.diagnostics.len())
636 }
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
641pub struct CodeIndexCheckpoint {
642 pub repository_id: String,
643 pub source_scope: String,
644 pub state: String,
645 pub total_path_count: usize,
646 pub parsed_file_count: usize,
647 pub committed_file_count: usize,
648 pub committed_symbol_count: usize,
649 pub committed_reference_count: usize,
650 pub committed_chunk_count: usize,
651 pub batch_count: usize,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 pub last_path: Option<String>,
654 pub resource_budget: CodeIndexResourceBudget,
655 pub updated_at_ms: u64,
656}
657
658#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
660#[serde(rename_all = "snake_case")]
661pub enum CodeIndexTaskState {
662 Queued,
663 Running,
664 Succeeded,
665 Retrying,
666 Failed,
667 DeadLetter,
668 Cancelled,
669}
670
671impl CodeIndexTaskState {
672 pub const fn as_str(self) -> &'static str {
674 match self {
675 Self::Queued => "queued",
676 Self::Running => "running",
677 Self::Succeeded => "succeeded",
678 Self::Retrying => "retrying",
679 Self::Failed => "failed",
680 Self::DeadLetter => "dead_letter",
681 Self::Cancelled => "cancelled",
682 }
683 }
684
685 pub fn parse(value: &str) -> Result<Self, DomainError> {
687 match value {
688 "queued" => Ok(Self::Queued),
689 "running" => Ok(Self::Running),
690 "succeeded" => Ok(Self::Succeeded),
691 "retrying" => Ok(Self::Retrying),
692 "failed" => Ok(Self::Failed),
693 "dead_letter" => Ok(Self::DeadLetter),
694 "cancelled" => Ok(Self::Cancelled),
695 _ => Err(DomainError::invalid(
696 "code_index_task_state",
697 "unknown code index task state",
698 )),
699 }
700 }
701
702 pub const fn is_unfinished(self) -> bool {
704 matches!(self, Self::Queued | Self::Running | Self::Retrying)
705 }
706}
707
708#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
710pub struct CodeIndexTaskRecord {
711 pub task_id: String,
712 pub repository_id: String,
713 pub alias: String,
714 pub ref_selector: String,
715 pub resolved_commit_sha: String,
716 pub tree_hash: String,
717 pub source_scope: String,
718 pub path_filters: Vec<String>,
719 pub language_filters: Vec<String>,
720 pub mode: CodeIndexMode,
721 pub state: CodeIndexTaskState,
722 #[serde(skip_serializing_if = "Option::is_none")]
723 pub lease_owner: Option<String>,
724 #[serde(skip_serializing_if = "Option::is_none")]
725 pub lease_expires_at_ms: Option<u64>,
726 pub attempt_count: u32,
727 pub next_retry_at_ms: u64,
728 pub input_fingerprint: String,
729 pub resource_budget: CodeIndexResourceBudget,
730 pub payload_json: String,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub last_error_kind: Option<String>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub last_error_message: Option<String>,
735 pub created_at_ms: u64,
736 pub updated_at_ms: u64,
737}
738
739#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
741pub struct CodeIndexTaskQueueStatus {
742 pub queued_task_count: usize,
743 pub running_task_count: usize,
744 pub retrying_task_count: usize,
745 pub dead_letter_task_count: usize,
746 pub running_lease_count: usize,
747 #[serde(skip_serializing_if = "Option::is_none")]
748 pub last_error: Option<String>,
749}
750
751#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
753pub struct CodeScopeRetentionSummary {
754 pub repository_id: String,
755 pub retained_scope_count: usize,
756 pub prunable_scope_count: usize,
757 pub pruned_scope_count: usize,
758 pub retained_scopes: Vec<String>,
759 pub prunable_scopes: Vec<String>,
760 pub pruned_scopes: Vec<String>,
761}
762
763#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
765pub struct CodeIndexProgressSummary {
766 pub git_file_count: usize,
767 pub blob_read_count: usize,
768 pub parsed_file_count: usize,
769 pub sqlite_write_count: usize,
770 pub skipped_file_count: usize,
771 pub degraded_file_count: usize,
772 pub batch_count: usize,
773 pub checkpoint_file_count: usize,
774 pub resource_budget: CodeIndexResourceBudget,
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct CodeIndexSummary {
780 pub repository_id: String,
781 pub source_scope: String,
782 pub resolved_commit_sha: String,
783 pub tree_hash: String,
784 pub indexed_file_count: usize,
785 pub changed_path_count: usize,
786 pub skipped_unchanged_count: usize,
787 pub deleted_path_count: usize,
788 pub symbol_count: usize,
789 pub reference_count: usize,
790 pub chunk_count: usize,
791 pub degraded_file_count: usize,
792 pub progress: CodeIndexProgressSummary,
793}
794
795#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
797pub struct CodeRepositoryLanguagePreview {
798 pub language_id: String,
799 pub file_count: usize,
800 pub byte_count: usize,
801}
802
803#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
805pub struct CodeRepositoryLargestFile {
806 pub path: String,
807 pub byte_count: usize,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
812pub struct CodeRepositoryExcludedPath {
813 pub path: String,
814 pub reason: String,
815}
816
817#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
819pub struct CodeRepositoryScopePreview {
820 pub repository_id: String,
821 pub alias: String,
822 pub requested_ref: String,
823 pub resolved_commit_sha: String,
824 pub tree_hash: String,
825 pub selected_file_count: usize,
826 pub selected_byte_count: usize,
827 pub unsupported_file_count: usize,
828 pub generated_or_heavy_file_count: usize,
829 pub expected_degraded_file_count: usize,
830 pub language_distribution: Vec<CodeRepositoryLanguagePreview>,
831 pub largest_files: Vec<CodeRepositoryLargestFile>,
832 pub excluded_paths: Vec<CodeRepositoryExcludedPath>,
833}
834
835#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
837pub struct CodeRepositoryTotals {
838 pub repository_count: usize,
839 pub indexed_file_count: usize,
840 pub symbol_count: usize,
841 pub reference_count: usize,
842 pub chunk_count: usize,
843 pub degraded_file_count: usize,
844 pub parse_status_counts: CodeParseStatusCounts,
845}
846
847#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
849pub struct CodeRepositoryLatencySample {
850 pub query: String,
851 pub kind: CodeQueryKind,
852 pub result_count: usize,
853 pub duration_ms: u64,
854}
855
856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
858pub struct CodeRepositoryReport {
859 pub repository_id: String,
860 pub alias: String,
861 pub root_path: String,
862 pub path_filters: Vec<String>,
863 pub language_filters: Vec<String>,
864 pub resolved_commit_sha: Option<String>,
865 pub tree_hash: Option<String>,
866 pub indexed_file_count: usize,
867 pub symbol_count: usize,
868 pub reference_count: usize,
869 pub chunk_count: usize,
870 pub degraded_file_count: usize,
871 pub resolved_edge_count: usize,
872 pub ambiguous_edge_count: usize,
873 pub unresolved_edge_count: usize,
874 pub degradation_summary: Vec<String>,
875 pub representative_queries: Vec<String>,
876 pub latency_samples: Vec<CodeRepositoryLatencySample>,
877 pub freshness_state: String,
878}
879
880#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
882pub struct CodeImpactPathGroups {
883 pub in_scope_changed_paths: Vec<String>,
884 pub out_of_scope_changed_paths: Vec<String>,
885}
886
887#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
889pub struct CodeRetrievalHit {
890 pub repository_id: String,
891 pub scope_id: String,
892 pub resolved_commit_sha: String,
893 pub tree_hash: String,
894 pub path: String,
895 pub language_id: String,
896 pub byte_range: RepositoryCodeRange,
897 pub line_range: RepositoryCodeRange,
898 pub symbol_snapshot_id: Option<String>,
899 #[serde(skip_serializing_if = "Option::is_none")]
900 pub canonical_symbol_id: Option<String>,
901 pub file_id: Option<String>,
902 pub retrieval_layers: Vec<CodeRetrievalLayer>,
903 pub index_versions: Vec<String>,
904 pub stale: bool,
905 #[serde(skip_serializing_if = "Option::is_none")]
906 pub degraded_reason: Option<String>,
907 #[serde(skip_serializing_if = "Option::is_none")]
908 pub edge_kind: Option<String>,
909 #[serde(skip_serializing_if = "Option::is_none")]
910 pub edge_resolution_state: Option<String>,
911 #[serde(skip_serializing_if = "Option::is_none")]
912 pub edge_target_hint: Option<String>,
913 #[serde(skip_serializing_if = "Option::is_none")]
914 pub edge_confidence_basis_points: Option<u16>,
915 #[serde(skip_serializing_if = "Option::is_none")]
916 pub edge_confidence_tier: Option<String>,
917 pub score: f64,
918 pub excerpt: String,
919}
920
921#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
923pub struct CodeFeatureFlagUsage {
924 pub usage_id: String,
925 pub path: String,
926 pub language_id: String,
927 pub file_id: String,
928 pub byte_range: RepositoryCodeRange,
929 pub line_range: RepositoryCodeRange,
930 pub edge_kind: String,
931 #[serde(skip_serializing_if = "Option::is_none")]
932 pub related_symbol_snapshot_id: Option<String>,
933 #[serde(skip_serializing_if = "Option::is_none")]
934 pub related_symbol_name: Option<String>,
935 pub confidence_basis_points: u16,
936 pub confidence_tier: String,
937 pub excerpt: String,
938}
939
940#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
942pub struct CodeFeatureFlagGraph {
943 pub feature_flag_id: String,
944 pub name: String,
945 pub source_kind: String,
946 pub source_key: String,
947 pub score: f64,
948 pub usages: Vec<CodeFeatureFlagUsage>,
949}
950
951fn normalize_filter_list(
952 field: &'static str,
953 values: Vec<String>,
954) -> Result<Vec<String>, DomainError> {
955 let mut normalized = Vec::new();
956 for value in values {
957 let value = required_text(field, value)?;
958 if !normalized.contains(&value) {
959 normalized.push(value);
960 }
961 }
962
963 Ok(normalized)
964}
965
966fn checked_u32(field: &'static str, value: usize) -> Result<u32, DomainError> {
967 u32::try_from(value).map_err(|_| DomainError::invalid(field, "must fit in u32"))
968}
969
970fn append_hash_list(input: &mut Vec<u8>, values: &[String]) {
971 input.extend_from_slice(&(values.len() as u64).to_le_bytes());
972 for value in values {
973 append_hash_part(input, value);
974 }
975}
976
977fn append_hash_part(input: &mut Vec<u8>, value: &str) {
978 input.extend_from_slice(&(value.len() as u64).to_le_bytes());
979 input.extend_from_slice(value.as_bytes());
980}
981
982fn stable_hash64(bytes: &[u8]) -> u64 {
983 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
984 const FNV_PRIME: u64 = 0x100000001b3;
985
986 let mut hash = FNV_OFFSET_BASIS;
987 for byte in bytes {
988 hash ^= u64::from(*byte);
989 hash = hash.wrapping_mul(FNV_PRIME);
990 }
991
992 hash
993}