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