Skip to main content

relay_knowledge/api/
operations.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    domain::{
7        CodeFeatureFlagGraph, CodeFeatureFlagRequest, CodeImpactPathGroups, CodeImpactRequest,
8        CodeIndexCheckpoint, CodeIndexSummary, CodeIndexTaskQueueStatus, CodeIndexTaskRecord,
9        CodeRepositoryRegistration, CodeRepositoryRemovalSummary, CodeRepositoryReport,
10        CodeRepositoryScopePreview, CodeRepositorySelector, CodeRepositorySet,
11        CodeRepositorySetAddMemberRequest, CodeRepositorySetCreateRequest, CodeRepositorySetMember,
12        CodeRepositorySetQueryHit, CodeRepositorySetQueryRequest, CodeRepositorySetRefreshSummary,
13        CodeRepositorySetRefreshTaskRecord, CodeRepositorySetRemoveMemberRequest,
14        CodeRepositorySetStatus, CodeRepositoryStatus, CodeRepositoryTotals, CodeRetrievalHit,
15        CodeRetrievalRequest, CodeScopeRetentionSummary, CommitReceipt, ConfidenceScore,
16        EvidenceExtractionMetadata, EvidenceModality, EvidenceSpan, ExtractionDiagnostic,
17        FactStatus, FreshnessPolicy, FusionDiagnostics, GraphVersionRange, IndexKind, IndexStatus,
18        LayoutRegion, ProposalConflictRecord, ProposalRecord, ProposalState, RerankDiagnostics,
19        RetrievalBackendStatus, RetrievalBudgetUsed, RetrievalHit, RetrievalMode,
20        RetrievedContextPack, ServiceDefinitionPlan, ServiceManagerAction, ServiceOperatorStatus,
21        SoftwareBuildTarget, SoftwareComponent, SoftwareDependencyUsage, SoftwareDesignElement,
22        SoftwareFile, SoftwareGlobalRequest, SoftwareGlobalStatus, SoftwareIacResource,
23        SoftwareRelationship, SoftwareSdkUsage, SoftwareTopic, WorkerKind, WorkerStatus,
24        WorkerTaskRecord,
25    },
26    storage::{GraphInspection, IndexCursor, IndexRefreshDiagnostics},
27};
28
29use super::{
30    AgentProtocolStatus, ApiMetadata, CodeRepositoryFreshnessDiagnostics,
31    FileIndexFreshnessDiagnostics, RuntimeStatus,
32};
33
34pub const GRAPH_CANVAS_DEFAULT_LIMIT: usize = 250;
35pub const GRAPH_CANVAS_MAX_LIMIT: usize = 1000;
36
37/// Graph canvas view selected by the Web workspace.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40pub enum GraphCanvasKind {
41    Knowledge,
42    Code,
43    Mixed,
44}
45
46impl GraphCanvasKind {
47    /// Parses the stable Web query representation.
48    pub fn parse(value: &str) -> Result<Self, String> {
49        match value {
50            "knowledge" => Ok(Self::Knowledge),
51            "code" => Ok(Self::Code),
52            "mixed" => Ok(Self::Mixed),
53            _ => Err(format!("unsupported graph canvas kind '{value}'")),
54        }
55    }
56
57    /// Returns the stable Web query representation.
58    pub const fn as_str(self) -> &'static str {
59        match self {
60            Self::Knowledge => "knowledge",
61            Self::Code => "code",
62            Self::Mixed => "mixed",
63        }
64    }
65}
66
67/// Bounded graph canvas request for same-origin Web exploration.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct GraphCanvasRequest {
70    pub kind: GraphCanvasKind,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub source_scope: Option<String>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub query: Option<String>,
75    pub limit: usize,
76}
77
78/// Node rendered in the Web graph canvas.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct GraphCanvasNode {
81    pub id: String,
82    pub kind: String,
83    pub label: String,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub subtitle: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub source_scope: Option<String>,
88    pub graph_version: u64,
89    pub weight: u32,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub status: Option<String>,
92    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
93    pub details: BTreeMap<String, String>,
94}
95
96/// Edge rendered in the Web graph canvas.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct GraphCanvasEdge {
99    pub id: String,
100    pub kind: String,
101    pub source: String,
102    pub target: String,
103    pub label: String,
104    pub graph_version: u64,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub confidence_basis_points: Option<u16>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub evidence_count: Option<usize>,
109    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
110    pub details: BTreeMap<String, String>,
111}
112
113/// Bounded graph canvas summary for truncation and legend hints.
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub struct GraphCanvasSummary {
116    pub kind: GraphCanvasKind,
117    pub node_count: usize,
118    pub edge_count: usize,
119    pub truncated: bool,
120    pub available_kinds: Vec<String>,
121}
122
123/// Same-origin graph canvas response.
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct GraphCanvasResponse {
126    pub metadata: ApiMetadata,
127    pub nodes: Vec<GraphCanvasNode>,
128    pub edges: Vec<GraphCanvasEdge>,
129    pub summary: GraphCanvasSummary,
130}
131
132/// Evidence item supplied to the ingest API.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct IngestEvidence {
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub id: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub source_path: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub span: Option<EvidenceSpan>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub confidence: Option<ConfidenceScore>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub status: Option<FactStatus>,
145    pub content: String,
146    #[serde(default)]
147    pub entity_labels: Vec<String>,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub extraction: Option<IngestEvidenceExtraction>,
150}
151
152/// Optional multimodal extraction metadata supplied with an evidence item.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct IngestEvidenceExtraction {
155    pub modality: EvidenceModality,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub source_uri: Option<String>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub source_hash: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub media_hash: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub extractor: Option<String>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub extractor_version: Option<String>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub observed_at: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub parent_evidence_id: Option<String>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub layout_region: Option<LayoutRegion>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub embedding_model: Option<String>,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub embedding_dimension: Option<u16>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub diagnostic: Option<ExtractionDiagnostic>,
178}
179
180impl IngestEvidenceExtraction {
181    /// Converts API metadata into the domain extraction contract.
182    pub fn into_domain_metadata(self) -> EvidenceExtractionMetadata {
183        EvidenceExtractionMetadata {
184            modality: self.modality,
185            source_uri: self.source_uri,
186            source_hash: self.source_hash,
187            media_hash: self.media_hash,
188            extractor: self.extractor,
189            extractor_version: self.extractor_version,
190            observed_at: self.observed_at,
191            parent_evidence_id: self.parent_evidence_id,
192            layout_region: self.layout_region,
193            embedding_model: self.embedding_model,
194            embedding_dimension: self.embedding_dimension,
195            diagnostic: self
196                .diagnostic
197                .unwrap_or_else(|| EvidenceExtractionMetadata::text_span().diagnostic),
198        }
199    }
200}
201
202/// Structured relation supplied to the ingest API.
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204pub struct IngestRelation {
205    pub id: String,
206    pub source_entity_label: String,
207    pub relation_type: String,
208    pub target_entity_label: String,
209    #[serde(default)]
210    pub evidence_ids: Vec<String>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub confidence: Option<ConfidenceScore>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub status: Option<FactStatus>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub version_range: Option<GraphVersionRange>,
217}
218
219/// Structured claim supplied to the ingest API.
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
221pub struct IngestClaim {
222    pub id: String,
223    pub subject_entity_label: String,
224    pub predicate: String,
225    pub object: String,
226    #[serde(default)]
227    pub evidence_ids: Vec<String>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub confidence: Option<ConfidenceScore>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub status: Option<FactStatus>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub version_range: Option<GraphVersionRange>,
234}
235
236/// Structured event supplied to the ingest API.
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238pub struct IngestEvent {
239    pub id: String,
240    pub event_type: String,
241    #[serde(default)]
242    pub entity_labels: Vec<String>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub occurred_at: Option<String>,
245    #[serde(default)]
246    pub evidence_ids: Vec<String>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub confidence: Option<ConfidenceScore>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub status: Option<FactStatus>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub version_range: Option<GraphVersionRange>,
253}
254
255/// Ingest request shared by CLI, Web, HTTP, and future agent adapters.
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257pub struct IngestRequest {
258    pub source_scope: String,
259    #[serde(default)]
260    pub evidence: Vec<IngestEvidence>,
261    #[serde(default)]
262    pub relations: Vec<IngestRelation>,
263    #[serde(default)]
264    pub claims: Vec<IngestClaim>,
265    #[serde(default)]
266    pub events: Vec<IngestEvent>,
267}
268
269/// Ingest response with committed graph and refreshed index versions.
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271pub struct IngestResponse {
272    pub metadata: ApiMetadata,
273    pub receipt: CommitReceipt,
274    pub indexes: Vec<IndexStatus>,
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub index_refresh_error: Option<String>,
277}
278
279/// Maintenance-worker output for derived OCR, caption, table, layout, or image embeddings.
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281pub struct MultimodalExtractionRequest {
282    pub source_scope: String,
283    pub parent_evidence_id: String,
284    pub derived_evidence: Vec<IngestEvidence>,
285}
286
287/// Commit result for a bounded multimodal maintenance batch.
288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
289pub struct MultimodalExtractionResponse {
290    pub metadata: ApiMetadata,
291    pub parent_evidence_id: String,
292    pub derived_evidence_count: usize,
293    pub receipt: CommitReceipt,
294    pub indexes: Vec<IndexStatus>,
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub index_refresh_error: Option<String>,
297}
298
299/// Hybrid retrieval request over graph facts and derived indexes.
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301pub struct HybridRetrievalRequest {
302    pub query: String,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub source_scope: Option<String>,
305    pub limit: usize,
306    pub freshness: FreshnessPolicy,
307}
308
309impl HybridRetrievalRequest {
310    /// Creates a bounded default retrieval request for human-facing interfaces.
311    pub fn new(query: impl Into<String>) -> Self {
312        Self {
313            query: query.into(),
314            source_scope: None,
315            limit: 10,
316            freshness: FreshnessPolicy::default(),
317        }
318    }
319}
320
321/// Retrieval response with freshness and degradation information.
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct HybridRetrievalResponse {
324    pub metadata: ApiMetadata,
325    pub context_pack: RetrievedContextPack,
326    pub retrieval_mode: RetrievalMode,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub source_scope: Option<String>,
329    pub freshness: FreshnessPolicy,
330    pub results: Vec<RetrievalHit>,
331    pub fusion: FusionDiagnostics,
332    pub rerank: RerankDiagnostics,
333    #[serde(default, skip_serializing_if = "Vec::is_empty")]
334    pub backend_statuses: Vec<RetrievalBackendStatus>,
335    pub truncated: bool,
336    pub budget_used: RetrievalBudgetUsed,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub degraded_reason: Option<String>,
339    pub indexes: Vec<IndexStatus>,
340    #[serde(default)]
341    pub index_cursors: Vec<IndexCursor>,
342    #[serde(default)]
343    pub index_refresh: IndexRefreshDiagnostics,
344}
345
346/// Graph inspection request with optional scope filtering reserved for adapters.
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
348pub struct GraphInspectionRequest {
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub source_scope: Option<String>,
351}
352
353/// Graph inspection response for diagnostics and agent adapters.
354#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
355pub struct GraphInspectionResponse {
356    pub metadata: ApiMetadata,
357    pub graph: GraphInspection,
358    pub repository_code_totals: CodeRepositoryTotals,
359}
360
361/// Index refresh request. Empty `kinds` means all v1 index families.
362#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363pub struct IndexRefreshRequest {
364    #[serde(default)]
365    pub kinds: Vec<IndexKind>,
366}
367
368/// Index refresh response after metadata is updated.
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
370pub struct IndexRefreshResponse {
371    pub metadata: ApiMetadata,
372    pub indexes: Vec<IndexStatus>,
373    pub index_cursors: Vec<IndexCursor>,
374    pub diagnostics: IndexRefreshDiagnostics,
375}
376
377/// Bounded local file indexing request.
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
379pub struct FileIndexRequest {
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub source_scope: Option<String>,
382    #[serde(default)]
383    pub roots: Vec<String>,
384}
385
386/// Local file indexing response.
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
388pub struct FileIndexResponse {
389    pub metadata: ApiMetadata,
390    pub summary: crate::storage::FileIndexScanSummary,
391}
392
393/// Bounded local file-location query request.
394#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
395pub struct FileQueryRequest {
396    pub query: String,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub source_scope: Option<String>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub root_id: Option<String>,
401    pub limit: usize,
402    #[serde(default)]
403    pub freshness_policy: FreshnessPolicy,
404}
405
406/// Local file-location query response.
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct FileQueryResponse {
409    pub metadata: ApiMetadata,
410    pub query: String,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub source_scope: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub root_id: Option<String>,
415    #[serde(default = "FileIndexFreshnessDiagnostics::legacy_unknown")]
416    pub freshness: FileIndexFreshnessDiagnostics,
417    pub results: Vec<crate::storage::FileSearchHit>,
418    pub truncated: bool,
419    pub duration_ms: u64,
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub degraded_reason: Option<String>,
422}
423
424/// Service manager status surfaced without exposing platform-specific handles.
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
426pub struct ServiceStatusResponse {
427    pub metadata: ApiMetadata,
428    pub service_name: String,
429    pub mode: String,
430    pub background_enabled: bool,
431    pub silent_updates_enabled: bool,
432    pub service_definition_path: String,
433    pub storage: StorageTopologyDiagnostics,
434    pub index_refresh: IndexRefreshDiagnostics,
435    pub file_index: crate::storage::FileIndexDiagnostics,
436    pub agent_protocols: AgentProtocolStatus,
437    pub operator: ServiceOperatorStatus,
438    pub workers: Vec<WorkerStatus>,
439    pub code_index_workers: CodeIndexWorkerStatus,
440    pub proposal_backlog: usize,
441    pub audit_sink: AuditSinkStatus,
442}
443
444/// Storage topology and shard-catalog diagnostics surfaced by the control plane.
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
446pub struct StorageTopologyDiagnostics {
447    pub topology: String,
448    pub control_database_path: String,
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub repository_shards_dir: Option<String>,
451    pub shard_catalog_active: bool,
452    pub active_shard_count: usize,
453    pub staged_shard_count: usize,
454    pub missing_shard_count: usize,
455    pub runtime_state_paths: Vec<String>,
456    pub shards: Vec<StorageShardDiagnostics>,
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub degraded_reason: Option<String>,
459}
460
461/// One partitioned SQLite shard reported through storage diagnostics.
462#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
463pub struct StorageShardDiagnostics {
464    pub repository_id: String,
465    pub state: String,
466    pub shard_locator: String,
467    pub resolved_path: String,
468    pub source_scope_count: usize,
469    pub exists: bool,
470    pub updated_at_ms: u64,
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub degraded_reason: Option<String>,
473}
474
475/// Control-plane storage topology response.
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477pub struct StorageTopologyResponse {
478    pub metadata: ApiMetadata,
479    pub storage: StorageTopologyDiagnostics,
480}
481
482/// Master-worker diagnostics for repository code indexing.
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484pub struct CodeIndexWorkerStatus {
485    pub configured_worker_count: usize,
486    pub active_worker_slots: usize,
487    pub queue_depth: usize,
488    pub queued_task_count: usize,
489    pub running_task_count: usize,
490    pub retrying_task_count: usize,
491    pub dead_letter_task_count: usize,
492    pub running_lease_count: usize,
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub last_error: Option<String>,
495}
496
497impl CodeIndexWorkerStatus {
498    /// Overlays resident master runtime configuration onto durable task queue state.
499    pub fn from_queue(configured_worker_count: usize, queue: CodeIndexTaskQueueStatus) -> Self {
500        let queue_depth = queue
501            .queued_task_count
502            .saturating_add(queue.retrying_task_count);
503
504        Self {
505            configured_worker_count,
506            active_worker_slots: configured_worker_count.saturating_sub(queue.running_task_count),
507            queue_depth,
508            queued_task_count: queue.queued_task_count,
509            running_task_count: queue.running_task_count,
510            retrying_task_count: queue.retrying_task_count,
511            dead_letter_task_count: queue.dead_letter_task_count,
512            running_lease_count: queue.running_lease_count,
513            last_error: queue.last_error,
514        }
515    }
516}
517
518/// Durable audit sink health surfaced in service diagnostics.
519#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
520pub struct AuditSinkStatus {
521    pub durable: bool,
522    pub event_count: usize,
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub last_error: Option<String>,
525}
526
527/// Worker status filter. Missing kind means all worker families.
528#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
529pub struct WorkerStatusRequest {
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub kind: Option<WorkerKind>,
532}
533
534/// Worker status response.
535#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
536pub struct WorkerStatusResponse {
537    pub metadata: ApiMetadata,
538    pub workers: Vec<WorkerStatus>,
539}
540
541/// Bounded foreground worker run request.
542#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
543pub struct WorkerRunRequest {
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub kind: Option<WorkerKind>,
546}
547
548/// Bounded foreground worker run response.
549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
550pub struct WorkerRunResponse {
551    pub metadata: ApiMetadata,
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub task: Option<WorkerTaskRecord>,
554    #[serde(default)]
555    pub proposals: Vec<ProposalRecord>,
556    pub workers: Vec<WorkerStatus>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub degraded_reason: Option<String>,
559}
560
561/// Preview split-worker request for one durable code-index task.
562#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
563pub struct CodeIndexWorkerRunRequest {
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub task_id: Option<String>,
566}
567
568/// Preview split-worker result for one durable code-index task.
569#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
570pub struct CodeIndexWorkerRunResponse {
571    pub metadata: ApiMetadata,
572    pub worker_kind: String,
573    pub claimed: bool,
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub task: Option<CodeIndexTaskRecord>,
576}
577
578/// Proposal list filter.
579#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
580pub struct ProposalListApiRequest {
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub state: Option<ProposalState>,
583    pub limit: usize,
584}
585
586/// Proposal list response.
587#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
588pub struct ProposalListResponse {
589    pub metadata: ApiMetadata,
590    pub proposals: Vec<ProposalRecord>,
591}
592
593/// Proposal detail response with conflict lifecycle.
594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
595pub struct ProposalShowResponse {
596    pub metadata: ApiMetadata,
597    pub proposal: ProposalRecord,
598    pub conflicts: Vec<ProposalConflictRecord>,
599    pub payload: serde_json::Value,
600}
601
602/// Manual proposal decision request.
603#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
604pub struct ProposalDecisionApiRequest {
605    pub actor: String,
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub reason: Option<String>,
608}
609
610/// Manual proposal decision response.
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
612pub struct ProposalDecisionResponse {
613    pub metadata: ApiMetadata,
614    pub proposal: ProposalRecord,
615    #[serde(skip_serializing_if = "Option::is_none")]
616    pub receipt: Option<CommitReceipt>,
617    #[serde(skip_serializing_if = "Option::is_none")]
618    pub index_refresh_error: Option<String>,
619}
620
621/// Durable audit query request.
622#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
623pub struct AuditQueryApiRequest {
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub operation: Option<String>,
626    pub limit: usize,
627}
628
629/// Durable audit query response.
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
631pub struct AuditQueryResponse {
632    pub metadata: ApiMetadata,
633    pub events: Vec<crate::domain::AuditEventRecord>,
634}
635
636/// Service manager plan request.
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
638pub struct ServicePlanRequest {
639    pub action: ServiceManagerAction,
640}
641
642/// Service manager plan response.
643#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
644pub struct ServicePlanResponse {
645    pub metadata: ApiMetadata,
646    pub plan: ServiceDefinitionPlan,
647}
648
649/// Service definition write response.
650#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
651pub struct ServiceDefinitionWriteResponse {
652    pub metadata: ApiMetadata,
653    pub plan: ServiceDefinitionPlan,
654    pub written: bool,
655}
656
657/// Service silent-update operator response.
658#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
659pub struct ServiceOperatorResponse {
660    pub metadata: ApiMetadata,
661    pub operator: ServiceOperatorStatus,
662}
663
664/// Startup recovery report for resident service mode.
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666pub struct ServiceRecoveryReport {
667    pub metadata: ApiMetadata,
668    pub graph_version: u64,
669    pub stale_index_kinds: Vec<IndexKind>,
670    pub refreshed_index_kinds: Vec<IndexKind>,
671    pub index_lag_max: u64,
672    pub task_queue_depth: usize,
673    pub dead_letter_count: usize,
674    pub heartbeat_state: String,
675}
676
677/// Aggregated health response for CLI/Web/service diagnostics.
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679pub struct HealthResponse {
680    pub metadata: ApiMetadata,
681    pub healthy: bool,
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub degraded_reason: Option<String>,
684    pub storage: StorageTopologyDiagnostics,
685    pub graph: GraphInspection,
686    pub repository_code_totals: CodeRepositoryTotals,
687    pub indexes: Vec<IndexStatus>,
688    pub index_cursors: Vec<IndexCursor>,
689    pub index_refresh: IndexRefreshDiagnostics,
690    pub file_index: crate::storage::FileIndexDiagnostics,
691    pub runtime: RuntimeStatus,
692}
693
694/// Remote embedding provider probe response with secret-free diagnostics.
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
696pub struct EmbeddingProviderProbeResponse {
697    pub metadata: ApiMetadata,
698    pub ok: bool,
699    pub provider: Option<String>,
700    pub model: String,
701    pub dimension: u32,
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub latency_ms: Option<u64>,
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub error_code: Option<String>,
706    #[serde(skip_serializing_if = "Option::is_none")]
707    pub error_message: Option<String>,
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub retryable: Option<bool>,
710}
711
712/// Code repository registration request.
713#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
714pub struct CodeRepositoryRegisterRequest {
715    pub root_path: String,
716    #[serde(default)]
717    pub alias: String,
718    #[serde(default)]
719    pub path_filters: Vec<String>,
720    #[serde(default)]
721    pub language_filters: Vec<String>,
722}
723
724/// Code repository registration response.
725#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
726pub struct CodeRepositoryRegisterResponse {
727    pub metadata: ApiMetadata,
728    pub registration: CodeRepositoryRegistration,
729    pub status: CodeRepositoryStatus,
730}
731
732/// Code repository removal response.
733#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
734pub struct CodeRepositoryRemoveResponse {
735    pub metadata: ApiMetadata,
736    pub removed_status: CodeRepositoryStatus,
737    pub summary: CodeRepositoryRemovalSummary,
738}
739
740/// Code repository index response.
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
742pub struct CodeRepositoryIndexResponse {
743    pub metadata: ApiMetadata,
744    pub scope: CodeRepositoryScopeMetadata,
745    pub summary: CodeIndexSummary,
746    pub status: CodeRepositoryStatus,
747}
748
749/// Code repository index start response for queued or no-op index requests.
750#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
751pub struct CodeRepositoryIndexStartResponse {
752    pub metadata: ApiMetadata,
753    pub scope: CodeRepositoryScopeMetadata,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    pub summary: Option<CodeIndexSummary>,
756    pub status: CodeRepositoryStatus,
757    #[serde(skip_serializing_if = "Option::is_none")]
758    pub task: Option<CodeIndexTaskRecord>,
759    #[serde(skip_serializing_if = "Option::is_none")]
760    pub checkpoint: Option<CodeIndexCheckpoint>,
761}
762
763/// Code repository index task reset response.
764#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
765pub struct CodeRepositoryIndexResetResponse {
766    pub metadata: ApiMetadata,
767    pub status: CodeRepositoryStatus,
768    pub reset_task_count: usize,
769    pub reset_tasks: Vec<CodeIndexTaskRecord>,
770    #[serde(skip_serializing_if = "Option::is_none")]
771    pub active_task: Option<CodeIndexTaskRecord>,
772    #[serde(skip_serializing_if = "Option::is_none")]
773    pub checkpoint: Option<CodeIndexCheckpoint>,
774    pub retention: CodeScopeRetentionSummary,
775}
776
777/// Code repository scope preview response.
778#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct CodeRepositoryScopePreviewResponse {
780    pub metadata: ApiMetadata,
781    pub scope: CodeRepositoryScopeMetadata,
782    pub preview: CodeRepositoryScopePreview,
783}
784
785/// Code repository scope and index metadata attached to code responses.
786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
787pub struct CodeRepositoryScopeMetadata {
788    pub scope_id: String,
789    pub repository_id: String,
790    pub alias: String,
791    pub requested_ref: String,
792    pub resolved_commit_sha: String,
793    pub tree_hash: String,
794    pub path_filters: Vec<String>,
795    pub language_filters: Vec<String>,
796    pub index_versions: Vec<String>,
797    pub stale: bool,
798}
799
800impl CodeRepositoryScopeMetadata {
801    /// Builds stable scope metadata from the selected repository snapshot.
802    pub fn from_status(
803        status: &CodeRepositoryStatus,
804        selector: &CodeRepositorySelector,
805        requested_ref: impl Into<String>,
806    ) -> Self {
807        Self {
808            scope_id: status.last_indexed_scope_id.clone().unwrap_or_default(),
809            repository_id: status.repository_id.clone(),
810            alias: status.alias.clone(),
811            requested_ref: requested_ref.into(),
812            resolved_commit_sha: status.last_indexed_commit.clone().unwrap_or_default(),
813            tree_hash: status.tree_hash.clone().unwrap_or_default(),
814            path_filters: merged_filters(&status.path_filters, &selector.path_filters),
815            language_filters: merged_filters(&status.language_filters, &selector.language_filters),
816            index_versions: vec![format!(
817                "code:{}:{}",
818                status
819                    .last_indexed_scope_id
820                    .as_deref()
821                    .unwrap_or("unscoped"),
822                status.tree_hash.as_deref().unwrap_or("unindexed")
823            )],
824            stale: status.stale,
825        }
826    }
827
828    /// Builds scope metadata for a queued or running index task.
829    pub fn from_index_task(task: &CodeIndexTaskRecord, requested_ref: impl Into<String>) -> Self {
830        Self {
831            scope_id: task.source_scope.clone(),
832            repository_id: task.repository_id.clone(),
833            alias: task.alias.clone(),
834            requested_ref: requested_ref.into(),
835            resolved_commit_sha: task.resolved_commit_sha.clone(),
836            tree_hash: task.tree_hash.clone(),
837            path_filters: task.path_filters.clone(),
838            language_filters: task.language_filters.clone(),
839            index_versions: vec![format!("code:{}:{}", task.source_scope, task.tree_hash)],
840            stale: true,
841        }
842    }
843}
844
845/// Code repository retrieval response.
846#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
847pub struct CodeRepositoryQueryResponse {
848    pub metadata: ApiMetadata,
849    pub scope: CodeRepositoryScopeMetadata,
850    #[serde(default = "CodeRepositoryFreshnessDiagnostics::legacy_unknown")]
851    pub freshness: CodeRepositoryFreshnessDiagnostics,
852    pub request: CodeRetrievalRequest,
853    pub results: Vec<CodeRetrievalHit>,
854    #[serde(skip_serializing_if = "Option::is_none")]
855    pub degraded_reason: Option<String>,
856}
857
858/// Code repository feature-flag graph response.
859#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
860pub struct CodeRepositoryFeatureFlagsResponse {
861    pub metadata: ApiMetadata,
862    pub scope: CodeRepositoryScopeMetadata,
863    #[serde(default = "CodeRepositoryFreshnessDiagnostics::legacy_unknown")]
864    pub freshness: CodeRepositoryFreshnessDiagnostics,
865    pub request: CodeFeatureFlagRequest,
866    pub flags: Vec<CodeFeatureFlagGraph>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub degraded_reason: Option<String>,
869}
870
871/// Code repository impact response.
872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
873pub struct CodeRepositoryImpactResponse {
874    pub metadata: ApiMetadata,
875    pub scope: CodeRepositoryScopeMetadata,
876    pub request: CodeImpactRequest,
877    pub path_groups: CodeImpactPathGroups,
878    pub results: Vec<CodeRetrievalHit>,
879}
880
881/// Code repository status response.
882#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
883pub struct CodeRepositoryStatusResponse {
884    pub metadata: ApiMetadata,
885    pub status: CodeRepositoryStatus,
886    #[serde(skip_serializing_if = "Option::is_none")]
887    pub active_task: Option<CodeIndexTaskRecord>,
888    #[serde(skip_serializing_if = "Option::is_none")]
889    pub checkpoint: Option<CodeIndexCheckpoint>,
890    pub retention: CodeScopeRetentionSummary,
891}
892
893/// Code repository operations report response.
894#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
895pub struct CodeRepositoryReportResponse {
896    pub metadata: ApiMetadata,
897    pub scope: CodeRepositoryScopeMetadata,
898    pub report: CodeRepositoryReport,
899}
900
901/// Repository-scoped software global model projection response.
902#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
903pub struct SoftwareGlobalResponse {
904    pub metadata: ApiMetadata,
905    pub scope: CodeRepositoryScopeMetadata,
906    pub request: SoftwareGlobalRequest,
907    pub status: SoftwareGlobalStatus,
908    pub components: Vec<SoftwareComponent>,
909    pub dependency_usages: Vec<SoftwareDependencyUsage>,
910    pub sdk_usages: Vec<SoftwareSdkUsage>,
911    pub files: Vec<SoftwareFile>,
912    pub topics: Vec<SoftwareTopic>,
913    pub relationships: Vec<SoftwareRelationship>,
914    pub build_targets: Vec<SoftwareBuildTarget>,
915    pub iac_resources: Vec<SoftwareIacResource>,
916    pub design_elements: Vec<SoftwareDesignElement>,
917}
918
919/// Repository-set creation response.
920#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
921pub struct CodeRepositorySetCreateResponse {
922    pub metadata: ApiMetadata,
923    pub request: CodeRepositorySetCreateRequest,
924    pub repository_set: CodeRepositorySet,
925}
926
927/// Repository-set member addition response.
928#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
929pub struct CodeRepositorySetAddResponse {
930    pub metadata: ApiMetadata,
931    pub request: CodeRepositorySetAddMemberRequest,
932    pub member: CodeRepositorySetMember,
933    pub status: CodeRepositorySetStatus,
934}
935
936/// Repository-set member removal response.
937#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
938pub struct CodeRepositorySetRemoveResponse {
939    pub metadata: ApiMetadata,
940    pub request: CodeRepositorySetRemoveMemberRequest,
941    pub member: CodeRepositorySetMember,
942    pub status: CodeRepositorySetStatus,
943}
944
945/// Repository-set query response.
946#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
947pub struct CodeRepositorySetQueryResponse {
948    pub metadata: ApiMetadata,
949    pub request: CodeRepositorySetQueryRequest,
950    pub status: CodeRepositorySetStatus,
951    pub results: Vec<CodeRepositorySetQueryHit>,
952    pub truncated: bool,
953    #[serde(skip_serializing_if = "Option::is_none")]
954    pub degraded_reason: Option<String>,
955}
956
957/// Repository-set status response.
958#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
959pub struct CodeRepositorySetStatusResponse {
960    pub metadata: ApiMetadata,
961    pub status: CodeRepositorySetStatus,
962}
963
964/// Repository-set overlay refresh response.
965#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
966pub struct CodeRepositorySetRefreshResponse {
967    pub metadata: ApiMetadata,
968    pub status: CodeRepositorySetStatus,
969    #[serde(skip_serializing_if = "Option::is_none")]
970    pub summary: Option<CodeRepositorySetRefreshSummary>,
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub task: Option<CodeRepositorySetRefreshTaskRecord>,
973}
974
975fn merged_filters(base: &[String], request: &[String]) -> Vec<String> {
976    let mut merged = Vec::new();
977    for value in base.iter().chain(request.iter()) {
978        if !merged.contains(value) {
979            merged.push(value.clone());
980        }
981    }
982
983    merged
984}