Skip to main content

vectorizer_sdk/
models.rs

1//! Data models for the Vectorizer SDK
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8// Re-export hybrid search models
9pub mod hybrid_search;
10pub use hybrid_search::*;
11
12// Re-export graph models
13pub mod graph;
14pub use graph::*;
15
16// Re-export file upload models
17pub mod file_upload;
18pub use file_upload::*;
19
20// Typed Qdrant-compatible filter builder (phase23).
21// Exposed as `vectorizer_sdk::models::filter` so callers do not need
22// to depend on the server crate directly.
23pub mod filter;
24pub use filter::{
25    QdrantCondition, QdrantFilter, QdrantGeoBoundingBox, QdrantGeoPoint, QdrantGeoRadius,
26    QdrantMatchValue, QdrantRange, QdrantValuesCount,
27};
28
29// ===== TIER-CONTROL REPORTS (phase13) =====
30
31/// Aggregate outcome of a `delete_by_filter` call against
32/// `POST /collections/{name}/vectors/delete_by_filter`.
33///
34/// Server contract: `{scanned, matched, deleted, results}`.
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36pub struct DeleteByFilterReport {
37    /// Total vectors scanned by the filter pass.
38    pub scanned: usize,
39    /// Vectors that matched the filter predicate.
40    pub matched: usize,
41    /// Vectors successfully deleted.
42    pub deleted: usize,
43    /// Per-id outcomes for matched vectors.
44    #[serde(default)]
45    pub results: Vec<serde_json::Value>,
46}
47
48/// Aggregate outcome of a `bulk_update_metadata` call against
49/// `POST /collections/{name}/vectors/bulk_update_metadata`.
50///
51/// Server contract: `{scanned, matched, updated, results}`.
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct BulkUpdateReport {
54    /// Total vectors scanned by the filter pass.
55    pub scanned: usize,
56    /// Vectors that matched the filter predicate.
57    pub matched: usize,
58    /// Vectors whose metadata was successfully updated.
59    pub updated: usize,
60    /// Per-id outcomes for matched vectors.
61    #[serde(default)]
62    pub results: Vec<serde_json::Value>,
63}
64
65/// Aggregate outcome of a `copy_vectors` call against
66/// `POST /collections/{src}/vectors/copy`.
67///
68/// Server contract: `{src, dst, requested, copied, failed, results}`.
69/// Per-id status: `ok | missing_in_src | dst_insert_failed`.
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71pub struct CopyReport {
72    /// Source collection name, echoed by the server.
73    pub src: String,
74    /// Destination collection name, echoed by the server.
75    pub dst: String,
76    /// Total ids the request asked to copy.
77    pub requested: usize,
78    /// Successfully copied ids.
79    pub copied: usize,
80    /// Ids that failed at any step.
81    pub failed: usize,
82    /// Per-id outcomes, in request order.
83    pub results: Vec<VectorOpResult>,
84}
85
86/// Job descriptor returned by `reencode_collection` against
87/// `POST /collections/{name}/reencode`.
88///
89/// Server contract: `{job_id, collection, state, target_encoding, progress}`.
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct ReencodeJob {
92    /// Opaque job id generated by the server.
93    pub job_id: String,
94    /// Collection being re-encoded.
95    pub collection: String,
96    /// Job state string (e.g. `"completed"`).
97    pub state: String,
98    /// Target encoding requested (e.g. `"sq8"`, `"binary"`, `"fp32"`).
99    pub target_encoding: String,
100    /// Progress fraction in `[0.0, 1.0]`.
101    pub progress: f64,
102}
103
104// ===== TIER-DEMOTION REPORTS (issue #265) =====
105
106/// Per-vector outcome for a `delete_vectors` or `move_to_collection`
107/// call. The `status` string is one of:
108///
109/// - `ok` — vector was deleted (delete) or moved (move) successfully.
110/// - `missing_in_src` — id was not present in the source collection.
111/// - `dst_insert_failed` — destination rejected the insert (move only;
112///   typically a dim/encoding mismatch).
113/// - `src_delete_failed` — destination accepted the insert but the
114///   source delete failed (move only; the vector now exists in BOTH
115///   collections — recoverable on retry).
116/// - `error` — generic per-id failure (delete only).
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct VectorOpResult {
119    /// Vector id this row refers to. May be missing when the request
120    /// payload contained a non-string entry that the server rejected.
121    #[serde(default)]
122    pub id: Option<String>,
123
124    /// One of `ok | missing_in_src | dst_insert_failed |
125    /// src_delete_failed | error` — see [`VectorOpResult`] doc.
126    pub status: String,
127
128    /// Server-side error message, populated when `status != "ok"`.
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub error: Option<String>,
131
132    /// Index of this entry in the request's `ids` array (delete only).
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub index: Option<usize>,
135}
136
137/// Aggregate outcome of a `delete_vectors` call against
138/// `POST /batch_delete`. Mirrors the server contract:
139/// `{collection, count, deleted, failed, results}`.
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct DeleteReport {
142    /// Source collection name, echoed by the server.
143    pub collection: String,
144    /// Total ids the request asked to delete.
145    #[serde(default)]
146    pub count: usize,
147    /// Successfully deleted ids.
148    pub deleted: usize,
149    /// Ids that failed (missing or backend error).
150    pub failed: usize,
151    /// Per-id outcomes, in request order.
152    pub results: Vec<VectorOpResult>,
153}
154
155/// Aggregate outcome of a `move_to_collection` call against
156/// `POST /collections/{src}/vectors/move` (issue #265).
157///
158/// Server invariant: vectors are inserted into `dst` BEFORE being
159/// deleted from `src`, so a mid-batch failure leaves a recoverable
160/// duplicate (never data loss). Per-id failures populate `results`
161/// without aborting the batch — operators chasing tier-demotion sweeps
162/// want partial progress, not an abort-on-first-error contract.
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
164pub struct MoveReport {
165    /// Source collection name, echoed by the server.
166    pub src: String,
167    /// Destination collection name, echoed by the server.
168    pub dst: String,
169    /// Total ids the request asked to move.
170    pub requested: usize,
171    /// Successfully moved ids (insert + delete both succeeded).
172    pub moved: usize,
173    /// Ids that failed at any step.
174    pub failed: usize,
175    /// Per-id outcomes, in request order.
176    pub results: Vec<VectorOpResult>,
177}
178
179// ===== CLIENT-SIDE REPLICATION CONFIGURATION =====
180
181/// Read preference for routing read operations.
182/// Similar to MongoDB's read preferences.
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
184#[serde(rename_all = "snake_case")]
185pub enum ReadPreference {
186    /// Route all reads to master
187    Master,
188    /// Route reads to replicas (round-robin)
189    #[default]
190    Replica,
191    /// Route to the node with lowest latency
192    Nearest,
193}
194
195/// Host configuration for master/replica topology.
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct HostConfig {
198    /// Master node URL (receives all write operations)
199    pub master: String,
200    /// Replica node URLs (receive read operations based on read_preference)
201    pub replicas: Vec<String>,
202}
203
204/// Options that can be passed to read operations for per-operation override.
205#[derive(Debug, Clone, Default)]
206pub struct ReadOptions {
207    /// Override the default read preference for this operation
208    pub read_preference: Option<ReadPreference>,
209}
210
211/// Vector similarity metrics
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
213#[serde(rename_all = "snake_case")]
214pub enum SimilarityMetric {
215    /// Cosine similarity
216    #[default]
217    Cosine,
218    /// Euclidean distance
219    Euclidean,
220    /// Dot product
221    DotProduct,
222}
223
224/// Vector representation
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct Vector {
227    /// Unique identifier for the vector
228    pub id: String,
229    /// Vector data as an array of numbers
230    pub data: Vec<f32>,
231    /// Optional metadata associated with the vector
232    pub metadata: Option<HashMap<String, serde_json::Value>>,
233    /// Optional ECC public key for payload encryption (PEM, base64, or hex format)
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub public_key: Option<String>,
236}
237
238/// Collection representation
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct Collection {
241    /// Collection name
242    pub name: String,
243    /// Vector dimension
244    pub dimension: usize,
245    /// Similarity metric used for search (API may return as 'metric')
246    #[serde(alias = "similarity_metric")]
247    pub metric: Option<String>,
248    /// Optional description
249    #[serde(default)]
250    pub description: Option<String>,
251    /// Creation timestamp
252    #[serde(default)]
253    pub created_at: Option<String>,
254    /// Last update timestamp
255    #[serde(default)]
256    pub updated_at: Option<String>,
257    /// Vector count
258    #[serde(default)]
259    pub vector_count: usize,
260    /// Document count
261    #[serde(default)]
262    pub document_count: usize,
263    /// Embedding provider
264    #[serde(default)]
265    pub embedding_provider: Option<String>,
266    /// Indexing status
267    #[serde(default)]
268    pub indexing_status: Option<serde_json::Value>,
269    /// Normalization config
270    #[serde(default)]
271    pub normalization: Option<serde_json::Value>,
272    /// Quantization config
273    #[serde(default)]
274    pub quantization: Option<serde_json::Value>,
275    /// Size info
276    #[serde(default)]
277    pub size: Option<serde_json::Value>,
278    /// Per-collection vector-count ring buffer (phase25 §6). At most
279    /// 60 samples, one per minute, sampled lazily on
280    /// `GET /collections/{name}` requests. Empty array on older
281    /// servers or for collections that have never been read.
282    #[serde(default)]
283    pub vector_count_history: Vec<VectorCountSample>,
284}
285
286/// One sample in the per-collection vector-count history (phase25 §6).
287#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
288pub struct VectorCountSample {
289    /// Sample timestamp in unix seconds.
290    pub at: u64,
291    /// Vector count at the time of the sample.
292    pub count: usize,
293}
294
295/// Collection information.
296///
297/// The v3.0.0 REST surface returns `metric` in Rust-Debug form
298/// (e.g. `"Cosine"`), plus new top-level blocks (`size`, `quantization`,
299/// `normalization`, `status`). Every field beyond `name` + `dimension`
300/// carries `#[serde(default)]` so the model tolerates pre-v3 servers
301/// and future additions (request models keep the strict posture; this
302/// is a response-only struct).
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct CollectionInfo {
305    /// Collection name
306    pub name: String,
307    /// Vector dimension
308    pub dimension: usize,
309    /// Similarity metric used for search. The v3 server emits this in
310    /// Rust-Debug form (`"Cosine"` / `"Euclidean"` / `"DotProduct"`);
311    /// callers that compare against `"cosine"` etc. should go through
312    /// `.to_lowercase()`.
313    #[serde(default, alias = "similarity_metric")]
314    pub metric: String,
315    /// Number of vectors in the collection
316    #[serde(default)]
317    pub vector_count: usize,
318    /// Number of documents in the collection
319    #[serde(default)]
320    pub document_count: usize,
321    /// Creation timestamp (RFC3339). Optional — pre-v3 servers may omit.
322    #[serde(default)]
323    pub created_at: String,
324    /// Last update timestamp (RFC3339). Optional — pre-v3 servers may omit.
325    #[serde(default)]
326    pub updated_at: String,
327    /// Indexing status. Absent on the v3 server; some legacy servers send it.
328    #[serde(default)]
329    pub indexing_status: Option<IndexingStatus>,
330    /// Size block emitted by v3 (`{total, total_bytes, index, index_bytes,
331    /// payload, payload_bytes}`).
332    #[serde(default)]
333    pub size: Option<serde_json::Value>,
334    /// Quantization block emitted by v3 (`{enabled, type, bits}`).
335    #[serde(default)]
336    pub quantization: Option<serde_json::Value>,
337    /// Normalization block emitted by v3.
338    #[serde(default)]
339    pub normalization: Option<serde_json::Value>,
340    /// Ready/indexing/error state emitted by v3.
341    #[serde(default)]
342    pub status: Option<String>,
343}
344
345/// Indexing status.
346///
347/// Every field carries `#[serde(default)]` to match the tolerant
348/// posture of the parent [`CollectionInfo`] — the v3 server emits a
349/// subset of this shape (`status`/`progress`/`total_documents`/
350/// `processed_documents` plus some extra keys this struct doesn't
351/// model) and omits `vector_count` and `last_updated`, which used to
352/// make `serde_json::from_str::<CollectionInfo>` fail on a
353/// `Collection → JSON → CollectionInfo` round-trip through
354/// `Collection::indexing_status: Option<serde_json::Value>` that
355/// preserves the server's partial shape.
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct IndexingStatus {
358    /// Status
359    #[serde(default)]
360    pub status: String,
361    /// Progress percentage
362    #[serde(default)]
363    pub progress: f32,
364    /// Total documents
365    #[serde(default)]
366    pub total_documents: usize,
367    /// Processed documents
368    #[serde(default)]
369    pub processed_documents: usize,
370    /// Vector count
371    #[serde(default)]
372    pub vector_count: usize,
373    /// Estimated time remaining
374    #[serde(default)]
375    pub estimated_time_remaining: Option<String>,
376    /// Last updated timestamp
377    #[serde(default)]
378    pub last_updated: String,
379}
380
381/// Search result
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct SearchResult {
384    /// Vector ID
385    pub id: String,
386    /// Similarity score
387    pub score: f32,
388    /// Vector content (if available)
389    pub content: Option<String>,
390    /// Optional metadata
391    pub metadata: Option<HashMap<String, serde_json::Value>>,
392}
393
394/// Search response.
395///
396/// `query_time_ms` defaults to `0.0` because the v3.0.x server's text
397/// search handler doesn't emit it — callers that need elapsed timing
398/// should measure client-side. Same tolerance applies to the
399/// additional diagnostic fields the server may add in later versions.
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct SearchResponse {
402    /// Search results.
403    #[serde(default)]
404    pub results: Vec<SearchResult>,
405    /// Query time in milliseconds (server-reported; 0.0 when the
406    /// server omits it).
407    #[serde(default)]
408    pub query_time_ms: f64,
409    /// Echo of the original query string, if the server returned one.
410    #[serde(default)]
411    pub query: Option<String>,
412    /// Echo of the requested result limit, if the server returned one.
413    #[serde(default)]
414    pub limit: Option<usize>,
415    /// Echo of the collection name, if the server returned one.
416    #[serde(default)]
417    pub collection: Option<String>,
418}
419
420/// Embedding request
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct EmbeddingRequest {
423    /// Text to embed
424    pub text: String,
425    /// Optional model to use for embedding
426    pub model: Option<String>,
427    /// Optional parameters for embedding generation
428    pub parameters: Option<EmbeddingParameters>,
429}
430
431/// Embedding parameters
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct EmbeddingParameters {
434    /// Maximum sequence length
435    pub max_length: Option<usize>,
436    /// Whether to normalize the embedding
437    pub normalize: Option<bool>,
438    /// Optional prefix for the text
439    pub prefix: Option<String>,
440}
441
442/// Embedding response
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct EmbeddingResponse {
445    /// Generated embedding vector
446    pub embedding: Vec<f32>,
447    /// Model used for embedding
448    pub model: String,
449    /// Text that was embedded
450    pub text: String,
451    /// Embedding dimension
452    pub dimension: usize,
453    /// Provider used
454    pub provider: String,
455}
456
457/// Health status
458#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct HealthStatus {
460    /// Service status
461    pub status: String,
462    /// Service version
463    pub version: String,
464    /// Timestamp
465    pub timestamp: String,
466    /// Uptime in seconds
467    pub uptime: Option<u64>,
468    /// Number of collections
469    pub collections: Option<usize>,
470    /// Total number of vectors
471    pub total_vectors: Option<usize>,
472}
473
474/// Collections list response
475#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct CollectionsResponse {
477    /// List of collections
478    pub collections: Vec<Collection>,
479}
480
481/// Create collection response
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct CreateCollectionResponse {
484    /// Success message
485    pub message: String,
486    /// Collection name
487    pub collection: String,
488}
489
490/// Database statistics
491#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct DatabaseStats {
493    /// Total number of collections
494    pub total_collections: usize,
495    /// Total number of vectors
496    pub total_vectors: usize,
497    /// Total memory estimate in bytes
498    pub total_memory_estimate_bytes: usize,
499    /// Collections information
500    pub collections: Vec<CollectionStats>,
501}
502
503/// Collection statistics
504#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct CollectionStats {
506    /// Collection name
507    pub name: String,
508    /// Number of vectors
509    pub vector_count: usize,
510    /// Vector dimension
511    pub dimension: usize,
512    /// Memory estimate in bytes
513    pub memory_estimate_bytes: usize,
514}
515
516/// Batch text request
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct BatchTextRequest {
519    /// Text ID
520    pub id: String,
521    /// Text content
522    pub text: String,
523    /// Optional metadata
524    pub metadata: Option<HashMap<String, String>>,
525}
526
527/// Batch configuration
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct BatchConfig {
530    /// Maximum batch size
531    pub max_batch_size: Option<usize>,
532    /// Number of parallel workers
533    pub parallel_workers: Option<usize>,
534    /// Whether operations should be atomic
535    pub atomic: Option<bool>,
536}
537
538/// Batch insert request
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct BatchInsertRequest {
541    /// Texts to insert
542    pub texts: Vec<BatchTextRequest>,
543    /// Batch configuration
544    pub config: Option<BatchConfig>,
545}
546
547/// Batch response.
548///
549/// Tolerant of both the old (pre-v3) `{success, operation, total_operations,
550/// successful_operations, failed_operations, duration_ms, errors}` shape
551/// and the v3.0.x server's `/insert_texts` response
552/// `{collection, count, inserted, failed, results}`. All fields default to
553/// empty/zero/false when absent so callers can match on whichever pair the
554/// running server emits without branching on version.
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct BatchResponse {
557    /// Whether the operation was successful (pre-v3 shape; v3 emits
558    /// `inserted`/`failed` instead — left `false` and the caller
559    /// should inspect `successful_operations > 0 && failed_operations == 0`).
560    #[serde(default)]
561    pub success: bool,
562    /// Collection name (both shapes emit this).
563    #[serde(default)]
564    pub collection: String,
565    /// Operation type (pre-v3 only; v3 omits).
566    #[serde(default)]
567    pub operation: String,
568    /// Total number of operations (pre-v3 shape). v3 emits `count` —
569    /// normalised into this field via the alias.
570    #[serde(default, alias = "count")]
571    pub total_operations: usize,
572    /// Number of successful operations (pre-v3 shape). v3 emits
573    /// `inserted` — aliased so either maps onto this field.
574    #[serde(default, alias = "inserted")]
575    pub successful_operations: usize,
576    /// Number of failed operations. Same field name in both shapes.
577    #[serde(default, alias = "failed")]
578    pub failed_operations: usize,
579    /// Duration in milliseconds (pre-v3 only).
580    #[serde(default)]
581    pub duration_ms: u64,
582    /// Error messages (pre-v3 shape).
583    #[serde(default)]
584    pub errors: Vec<String>,
585    /// Per-entry result records emitted by v3 `/insert_texts`. Each
586    /// record carries the client-sent id (`client_id`) and the
587    /// server-assigned UUIDs under `vector_ids`; use this when the
588    /// server reassigns ids on insert.
589    #[serde(default)]
590    pub results: Vec<BatchResultEntry>,
591}
592
593/// One entry in `BatchResponse::results` as emitted by the v3
594/// `/insert_texts` handler. Carries the client-provided id alongside
595/// the server-assigned vector UUID(s) so callers can round-trip the
596/// mapping when they need idempotency by client id.
597#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct BatchResultEntry {
599    /// Original `id` the caller sent in `BatchTextRequest`.
600    #[serde(default)]
601    pub client_id: String,
602    /// Zero-based index of the entry in the original batch.
603    #[serde(default)]
604    pub index: usize,
605    /// `"ok"` or `"error"`.
606    #[serde(default)]
607    pub status: String,
608    /// Whether the server chunked the input (long text → multiple
609    /// vectors).
610    #[serde(default)]
611    pub chunked: bool,
612    /// Server-assigned UUID(s) — one element unless `chunked` is true.
613    #[serde(default)]
614    pub vector_ids: Vec<String>,
615    /// Count of vectors created for this entry (≥1 if `chunked`).
616    #[serde(default)]
617    pub vectors_created: usize,
618    /// Populated only on `status == "error"`.
619    #[serde(default)]
620    pub error: Option<String>,
621}
622
623/// Batch search request
624#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct BatchSearchRequest {
626    /// Search queries
627    pub queries: Vec<BatchSearchQuery>,
628    /// Batch configuration
629    pub config: Option<BatchConfig>,
630}
631
632/// Batch search response
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct BatchSearchResponse {
635    /// Whether the operation was successful
636    pub success: bool,
637    /// Collection name
638    pub collection: String,
639    /// Total number of queries
640    pub total_queries: usize,
641    /// Number of successful queries
642    pub successful_queries: usize,
643    /// Number of failed queries
644    pub failed_queries: usize,
645    /// Duration in milliseconds
646    pub duration_ms: u64,
647    /// Search results
648    pub results: Vec<Vec<SearchResult>>,
649    /// Error messages
650    pub errors: Vec<String>,
651}
652
653/// Batch vector update
654#[derive(Debug, Clone, Serialize, Deserialize)]
655pub struct BatchVectorUpdate {
656    /// Vector ID
657    pub id: String,
658    /// New vector data (optional)
659    pub data: Option<Vec<f32>>,
660    /// New metadata (optional)
661    pub metadata: Option<HashMap<String, serde_json::Value>>,
662}
663
664/// Batch update request
665#[derive(Debug, Clone, Serialize, Deserialize)]
666pub struct BatchUpdateRequest {
667    /// Vector updates
668    pub updates: Vec<BatchVectorUpdate>,
669    /// Batch configuration
670    pub config: Option<BatchConfig>,
671}
672
673/// Batch delete request
674#[derive(Debug, Clone, Serialize, Deserialize)]
675pub struct BatchDeleteRequest {
676    /// Vector IDs to delete
677    pub vector_ids: Vec<String>,
678    /// Batch configuration
679    pub config: Option<BatchConfig>,
680}
681
682/// Summarization methods
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
684#[serde(rename_all = "snake_case")]
685pub enum SummarizationMethod {
686    /// Extractive summarization
687    #[default]
688    Extractive,
689    /// Keyword summarization
690    Keyword,
691    /// Sentence summarization
692    Sentence,
693    /// Abstractive summarization
694    Abstractive,
695}
696
697/// Summarize text request
698#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct SummarizeTextRequest {
700    /// Text to summarize
701    pub text: String,
702    /// Summarization method
703    pub method: Option<SummarizationMethod>,
704    /// Maximum summary length
705    pub max_length: Option<usize>,
706    /// Compression ratio
707    pub compression_ratio: Option<f32>,
708    /// Language code
709    pub language: Option<String>,
710}
711
712/// Summarize text response
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct SummarizeTextResponse {
715    /// Summary ID
716    pub summary_id: String,
717    /// Original text
718    pub original_text: String,
719    /// Generated summary
720    pub summary: String,
721    /// Method used
722    pub method: String,
723    /// Original text length
724    pub original_length: usize,
725    /// Summary length
726    pub summary_length: usize,
727    /// Compression ratio
728    pub compression_ratio: f32,
729    /// Language
730    pub language: String,
731    /// Status
732    pub status: String,
733    /// Message
734    pub message: String,
735    /// Metadata
736    pub metadata: HashMap<String, String>,
737}
738
739/// Summarize context request
740#[derive(Debug, Clone, Serialize, Deserialize)]
741pub struct SummarizeContextRequest {
742    /// Context to summarize
743    pub context: String,
744    /// Summarization method
745    pub method: Option<SummarizationMethod>,
746    /// Maximum summary length
747    pub max_length: Option<usize>,
748    /// Compression ratio
749    pub compression_ratio: Option<f32>,
750    /// Language code
751    pub language: Option<String>,
752}
753
754/// Summarize context response
755#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct SummarizeContextResponse {
757    /// Summary ID
758    pub summary_id: String,
759    /// Original context
760    pub original_context: String,
761    /// Generated summary
762    pub summary: String,
763    /// Method used
764    pub method: String,
765    /// Original context length
766    pub original_length: usize,
767    /// Summary length
768    pub summary_length: usize,
769    /// Compression ratio
770    pub compression_ratio: f32,
771    /// Language
772    pub language: String,
773    /// Status
774    pub status: String,
775    /// Message
776    pub message: String,
777    /// Metadata
778    pub metadata: HashMap<String, String>,
779}
780
781/// Get summary response
782#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct GetSummaryResponse {
784    /// Summary ID
785    pub summary_id: String,
786    /// Original text
787    pub original_text: String,
788    /// Generated summary
789    pub summary: String,
790    /// Method used
791    pub method: String,
792    /// Original text length
793    pub original_length: usize,
794    /// Summary length
795    pub summary_length: usize,
796    /// Compression ratio
797    pub compression_ratio: f32,
798    /// Language
799    pub language: String,
800    /// Creation timestamp
801    pub created_at: String,
802    /// Metadata
803    pub metadata: HashMap<String, String>,
804    /// Status
805    pub status: String,
806}
807
808/// Summary info
809#[derive(Debug, Clone, Serialize, Deserialize)]
810pub struct SummaryInfo {
811    /// Summary ID
812    pub summary_id: String,
813    /// Method used
814    pub method: String,
815    /// Language
816    pub language: String,
817    /// Original text length
818    pub original_length: usize,
819    /// Summary length
820    pub summary_length: usize,
821    /// Compression ratio
822    pub compression_ratio: f32,
823    /// Creation timestamp
824    pub created_at: String,
825    /// Metadata
826    pub metadata: HashMap<String, String>,
827}
828
829/// List summaries response
830#[derive(Debug, Clone, Serialize, Deserialize)]
831pub struct ListSummariesResponse {
832    /// List of summaries
833    pub summaries: Vec<SummaryInfo>,
834    /// Total count
835    pub total_count: usize,
836    /// Status
837    pub status: String,
838}
839
840/// Indexing progress
841#[derive(Debug, Clone, Serialize, Deserialize)]
842pub struct IndexingProgress {
843    /// Whether indexing is in progress
844    pub is_indexing: bool,
845    /// Overall status
846    pub overall_status: String,
847    /// Collections being indexed
848    pub collections: Vec<CollectionProgress>,
849}
850
851/// Collection progress
852#[derive(Debug, Clone, Serialize, Deserialize)]
853pub struct CollectionProgress {
854    /// Collection name
855    pub collection_name: String,
856    /// Status
857    pub status: String,
858    /// Progress percentage
859    pub progress: f32,
860    /// Vector count
861    pub vector_count: usize,
862    /// Error message if any
863    pub error_message: Option<String>,
864    /// Last updated timestamp
865    pub last_updated: String,
866}
867
868// ===== INTELLIGENT SEARCH MODELS =====
869
870/// Intelligent search request
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct IntelligentSearchRequest {
873    /// Search query
874    pub query: String,
875    /// Collections to search (optional - searches all if not specified)
876    pub collections: Option<Vec<String>>,
877    /// Maximum number of results
878    pub max_results: Option<usize>,
879    /// Enable domain expansion
880    pub domain_expansion: Option<bool>,
881    /// Enable technical focus
882    pub technical_focus: Option<bool>,
883    /// Enable MMR diversification
884    pub mmr_enabled: Option<bool>,
885    /// MMR balance parameter (0.0-1.0)
886    pub mmr_lambda: Option<f32>,
887}
888
889/// Semantic search request
890#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct SemanticSearchRequest {
892    /// Search query
893    pub query: String,
894    /// Collection to search
895    pub collection: String,
896    /// Maximum number of results
897    pub max_results: Option<usize>,
898    /// Enable semantic reranking
899    pub semantic_reranking: Option<bool>,
900    /// Enable cross-encoder reranking
901    pub cross_encoder_reranking: Option<bool>,
902    /// Minimum similarity threshold
903    pub similarity_threshold: Option<f32>,
904}
905
906/// Contextual search request
907#[derive(Debug, Clone, Serialize, Deserialize)]
908pub struct ContextualSearchRequest {
909    /// Search query
910    pub query: String,
911    /// Collection to search
912    pub collection: String,
913    /// Metadata-based context filters
914    pub context_filters: Option<HashMap<String, serde_json::Value>>,
915    /// Maximum number of results
916    pub max_results: Option<usize>,
917    /// Enable context-aware reranking
918    pub context_reranking: Option<bool>,
919    /// Weight of context factors (0.0-1.0)
920    pub context_weight: Option<f32>,
921}
922
923/// Multi-collection search request
924#[derive(Debug, Clone, Serialize, Deserialize)]
925pub struct MultiCollectionSearchRequest {
926    /// Search query
927    pub query: String,
928    /// Collections to search
929    pub collections: Vec<String>,
930    /// Maximum results per collection
931    pub max_per_collection: Option<usize>,
932    /// Maximum total results
933    pub max_total_results: Option<usize>,
934    /// Enable cross-collection reranking
935    pub cross_collection_reranking: Option<bool>,
936}
937
938/// Intelligent search result
939#[derive(Debug, Clone, Serialize, Deserialize)]
940pub struct IntelligentSearchResult {
941    /// Result ID
942    pub id: String,
943    /// Similarity score
944    pub score: f32,
945    /// Result content
946    pub content: String,
947    /// Metadata
948    pub metadata: Option<HashMap<String, serde_json::Value>>,
949    /// Collection name
950    pub collection: Option<String>,
951    /// Query used for this result
952    pub query_used: Option<String>,
953}
954
955/// Intelligent search response
956#[derive(Debug, Clone, Serialize, Deserialize)]
957pub struct IntelligentSearchResponse {
958    /// Search results
959    pub results: Vec<IntelligentSearchResult>,
960    /// Total number of results found
961    pub total_results: usize,
962    /// Search duration in milliseconds
963    pub duration_ms: u64,
964    /// Queries generated
965    pub queries_generated: Option<Vec<String>>,
966    /// Collections searched
967    pub collections_searched: Option<Vec<String>>,
968    /// Search metadata
969    pub metadata: Option<HashMap<String, serde_json::Value>>,
970}
971
972/// Semantic search response
973#[derive(Debug, Clone, Serialize, Deserialize)]
974pub struct SemanticSearchResponse {
975    /// Search results
976    pub results: Vec<IntelligentSearchResult>,
977    /// Total number of results found
978    pub total_results: usize,
979    /// Search duration in milliseconds
980    pub duration_ms: u64,
981    /// Collection searched
982    pub collection: String,
983    /// Search metadata
984    pub metadata: Option<HashMap<String, serde_json::Value>>,
985}
986
987/// Contextual search response
988#[derive(Debug, Clone, Serialize, Deserialize)]
989pub struct ContextualSearchResponse {
990    /// Search results
991    pub results: Vec<IntelligentSearchResult>,
992    /// Total number of results found
993    pub total_results: usize,
994    /// Search duration in milliseconds
995    pub duration_ms: u64,
996    /// Collection searched
997    pub collection: String,
998    /// Context filters applied
999    pub context_filters: Option<HashMap<String, serde_json::Value>>,
1000    /// Search metadata
1001    pub metadata: Option<HashMap<String, serde_json::Value>>,
1002}
1003
1004/// Multi-collection search response
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1006pub struct MultiCollectionSearchResponse {
1007    /// Search results
1008    pub results: Vec<IntelligentSearchResult>,
1009    /// Total number of results found
1010    pub total_results: usize,
1011    /// Search duration in milliseconds
1012    pub duration_ms: u64,
1013    /// Collections searched
1014    pub collections_searched: Vec<String>,
1015    /// Results per collection
1016    pub results_per_collection: Option<HashMap<String, usize>>,
1017    /// Search metadata
1018    pub metadata: Option<HashMap<String, serde_json::Value>>,
1019}
1020
1021// ==================== REPLICATION MODELS ====================
1022
1023/// Status of a replica node
1024#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1025#[serde(rename_all = "PascalCase")]
1026pub enum ReplicaStatus {
1027    /// Replica is connected and healthy
1028    Connected,
1029    /// Replica is syncing data
1030    Syncing,
1031    /// Replica is lagging behind master
1032    Lagging,
1033    /// Replica is disconnected
1034    Disconnected,
1035}
1036
1037/// Information about a replica node
1038#[derive(Debug, Clone, Serialize, Deserialize)]
1039pub struct ReplicaInfo {
1040    /// Unique identifier for the replica
1041    pub replica_id: String,
1042    /// Hostname or IP address of the replica
1043    pub host: String,
1044    /// Port number of the replica
1045    pub port: u16,
1046    /// Current status of the replica
1047    pub status: String,
1048    /// Timestamp of last heartbeat
1049    pub last_heartbeat: DateTime<Utc>,
1050    /// Number of operations successfully synced
1051    pub operations_synced: u64,
1052
1053    // Legacy fields (backwards compatible)
1054    /// Legacy: Current offset on replica (deprecated, use operations_synced)
1055    #[serde(skip_serializing_if = "Option::is_none")]
1056    pub offset: Option<u64>,
1057    /// Legacy: Lag in operations (deprecated, use status)
1058    #[serde(skip_serializing_if = "Option::is_none")]
1059    pub lag: Option<u64>,
1060}
1061
1062/// Statistics for replication status
1063#[derive(Debug, Clone, Serialize, Deserialize)]
1064pub struct ReplicationStats {
1065    // New fields (v1.2.0+)
1066    /// Role of the node: Master or Replica
1067    #[serde(skip_serializing_if = "Option::is_none")]
1068    pub role: Option<String>,
1069    /// Total bytes sent to replicas (Master only)
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    pub bytes_sent: Option<u64>,
1072    /// Total bytes received from master (Replica only)
1073    #[serde(skip_serializing_if = "Option::is_none")]
1074    pub bytes_received: Option<u64>,
1075    /// Timestamp of last synchronization
1076    #[serde(skip_serializing_if = "Option::is_none")]
1077    pub last_sync: Option<DateTime<Utc>>,
1078    /// Number of operations pending replication
1079    #[serde(skip_serializing_if = "Option::is_none")]
1080    pub operations_pending: Option<usize>,
1081    /// Size of snapshot data in bytes
1082    #[serde(skip_serializing_if = "Option::is_none")]
1083    pub snapshot_size: Option<usize>,
1084    /// Number of connected replicas (Master only)
1085    #[serde(skip_serializing_if = "Option::is_none")]
1086    pub connected_replicas: Option<usize>,
1087
1088    // Legacy fields (backwards compatible - always present)
1089    /// Current offset on master node
1090    pub master_offset: u64,
1091    /// Current offset on replica node
1092    pub replica_offset: u64,
1093    /// Number of operations behind
1094    pub lag_operations: u64,
1095    /// Total operations replicated
1096    pub total_replicated: u64,
1097}
1098
1099/// Response for replication status endpoint
1100#[derive(Debug, Clone, Serialize, Deserialize)]
1101pub struct ReplicationStatusResponse {
1102    /// Overall status message
1103    pub status: String,
1104    /// Detailed replication statistics
1105    pub stats: ReplicationStats,
1106    /// Optional message with additional information
1107    #[serde(skip_serializing_if = "Option::is_none")]
1108    pub message: Option<String>,
1109}
1110
1111/// Response for listing replicas
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113pub struct ReplicaListResponse {
1114    /// List of replica nodes
1115    pub replicas: Vec<ReplicaInfo>,
1116    /// Total count of replicas
1117    pub count: usize,
1118    /// Status message
1119    pub message: String,
1120}
1121
1122// ===== VECTOR OPERATIONS — NEW METHODS (phase12) =====
1123
1124/// Paginated vector listing returned by `GET /collections/{name}/vectors`.
1125#[derive(Debug, Clone, Serialize, Deserialize)]
1126pub struct VectorPage {
1127    /// Vectors on this page — each entry carries `id`, `vector`, and `payload`.
1128    #[serde(default)]
1129    pub vectors: Vec<serde_json::Value>,
1130    /// Total vector count in the collection (unfiltered).
1131    #[serde(default)]
1132    pub total: usize,
1133    /// The effective `limit` applied by the server.
1134    #[serde(default)]
1135    pub limit: usize,
1136    /// The byte-offset applied by the server.
1137    ///
1138    /// Note: the task spec calls this `page` but the server handler
1139    /// (`GET /collections/{name}/vectors`) uses an `offset` query
1140    /// parameter internally and echoes it back as `offset`.
1141    #[serde(default)]
1142    pub offset: usize,
1143    /// Optional human-readable pagination hint from the server.
1144    #[serde(default)]
1145    pub message: Option<String>,
1146}
1147
1148/// Request body for `update_vector` (`POST /update`).
1149///
1150/// Server reads `id` and `collection` from the top-level body.
1151/// All other keys are treated as payload updates.
1152#[derive(Debug, Clone, Serialize, Deserialize)]
1153pub struct UpdateVectorRequest {
1154    /// Vector id to update.
1155    pub id: String,
1156    /// New metadata / payload to merge (free-form JSON).
1157    #[serde(default, skip_serializing_if = "Option::is_none")]
1158    pub metadata: Option<serde_json::Value>,
1159}
1160
1161/// One item in a `batch_insert_texts` call (`POST /batch_insert`).
1162#[derive(Debug, Clone, Serialize, Deserialize)]
1163pub struct BatchInsertItem {
1164    /// Optional client-supplied id.
1165    #[serde(default, skip_serializing_if = "Option::is_none")]
1166    pub id: Option<String>,
1167    /// Text content to embed and insert.
1168    pub text: String,
1169    /// Optional metadata to attach.
1170    #[serde(default, skip_serializing_if = "Option::is_none")]
1171    pub metadata: Option<serde_json::Value>,
1172}
1173
1174/// Aggregate outcome of a `batch_insert_texts` or `insert_vectors` call.
1175/// Server response shape: `{collection, inserted, failed, count, results}`.
1176#[derive(Debug, Clone, Serialize, Deserialize)]
1177pub struct BatchInsertReport {
1178    /// Collection the vectors were inserted into.
1179    pub collection: String,
1180    /// Vectors successfully inserted.
1181    #[serde(alias = "inserted")]
1182    pub successful: usize,
1183    /// Vectors that failed.
1184    pub failed: usize,
1185    /// Total entries submitted.
1186    #[serde(default, alias = "count")]
1187    pub total: usize,
1188    /// Per-entry results.
1189    #[serde(default)]
1190    pub results: Vec<BatchResultEntry>,
1191}
1192
1193/// One entry in a `batch_update_vectors` call (`POST /batch_update`).
1194#[derive(Debug, Clone, Serialize, Deserialize)]
1195pub struct VectorUpdate {
1196    /// Vector id to update.
1197    pub id: String,
1198    /// Optional new dense vector data.
1199    #[serde(default, skip_serializing_if = "Option::is_none")]
1200    pub vector: Option<Vec<f32>>,
1201    /// Optional new payload.
1202    #[serde(default, skip_serializing_if = "Option::is_none")]
1203    pub payload: Option<serde_json::Value>,
1204}
1205
1206/// Aggregate outcome of a `batch_update_vectors` call.
1207/// Server response: `{collection, count, updated, failed, results}`.
1208#[derive(Debug, Clone, Serialize, Deserialize)]
1209pub struct BatchUpdateReport {
1210    /// Collection the update ran against.
1211    pub collection: String,
1212    /// Total entries submitted.
1213    #[serde(default, alias = "count")]
1214    pub total: usize,
1215    /// Successfully updated entries.
1216    #[serde(alias = "updated")]
1217    pub successful: usize,
1218    /// Failed entries.
1219    pub failed: usize,
1220    /// Per-entry results.
1221    #[serde(default)]
1222    pub results: Vec<serde_json::Value>,
1223}
1224
1225/// One vector entry in an `insert_vectors` call (`POST /insert_vectors`).
1226#[derive(Debug, Clone, Serialize, Deserialize)]
1227pub struct RawVectorInsert {
1228    /// Optional client-supplied id.
1229    #[serde(default, skip_serializing_if = "Option::is_none")]
1230    pub id: Option<String>,
1231    /// Pre-computed embedding (length must match collection dimension).
1232    pub embedding: Vec<f32>,
1233    /// Optional payload (takes precedence over `metadata`).
1234    #[serde(default, skip_serializing_if = "Option::is_none")]
1235    pub payload: Option<serde_json::Value>,
1236    /// Optional metadata (string–string map; used when `payload` absent).
1237    #[serde(default, skip_serializing_if = "Option::is_none")]
1238    pub metadata: Option<HashMap<String, String>>,
1239}
1240
1241/// One query in a `batch_search` call (`POST /batch_search`).
1242#[derive(Debug, Clone, Serialize, Deserialize)]
1243pub struct BatchSearchQuery {
1244    /// Text query (embedded server-side) or raw vector (send via `vector`).
1245    #[serde(default, skip_serializing_if = "Option::is_none")]
1246    pub query: Option<String>,
1247    /// Raw vector query (skips embedding).
1248    #[serde(default, skip_serializing_if = "Option::is_none")]
1249    pub vector: Option<Vec<f32>>,
1250    /// Max results for this query.
1251    #[serde(default, skip_serializing_if = "Option::is_none")]
1252    pub limit: Option<usize>,
1253    /// Score threshold.
1254    #[serde(default, skip_serializing_if = "Option::is_none")]
1255    pub threshold: Option<f64>,
1256}
1257
1258/// Request for `search_by_file` (`POST /collections/{name}/search/file`).
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260pub struct SearchByFileRequest {
1261    /// File path to search within.
1262    pub file_path: String,
1263    /// Max results.
1264    #[serde(default, skip_serializing_if = "Option::is_none")]
1265    pub limit: Option<usize>,
1266}
1267
1268// ===== DISCOVERY PIPELINE TYPES (phase12) =====
1269
1270/// Request for `broad_discovery` (`POST /discovery/broad_discovery`).
1271///
1272/// Server reads `queries` (array of strings) and optional `k`.
1273#[derive(Debug, Clone, Serialize, Deserialize)]
1274pub struct BroadDiscoveryRequest {
1275    /// Expanded query strings to search across all collections.
1276    pub queries: Vec<String>,
1277    /// Number of top chunks to retrieve per query (default 50).
1278    #[serde(default, skip_serializing_if = "Option::is_none")]
1279    pub k: Option<usize>,
1280}
1281
1282/// Response from `broad_discovery`.
1283/// Server: `{chunks: [{collection, score, content_preview}], count}`.
1284#[derive(Debug, Clone, Serialize, Deserialize)]
1285pub struct BroadDiscoveryResponse {
1286    /// Retrieved chunk summaries.
1287    #[serde(default)]
1288    pub chunks: Vec<serde_json::Value>,
1289    /// Total chunks returned.
1290    #[serde(default)]
1291    pub count: usize,
1292}
1293
1294/// Request for `semantic_focus` (`POST /discovery/semantic_focus`).
1295#[derive(Debug, Clone, Serialize, Deserialize)]
1296pub struct SemanticFocusRequest {
1297    /// Target collection name.
1298    pub collection: String,
1299    /// Queries to focus within the collection.
1300    pub queries: Vec<String>,
1301    /// Number of top results per query (default 15).
1302    #[serde(default, skip_serializing_if = "Option::is_none")]
1303    pub k: Option<usize>,
1304}
1305
1306/// Response from `semantic_focus`.
1307/// Server: `{chunks: [...], count}`.
1308#[derive(Debug, Clone, Serialize, Deserialize)]
1309pub struct SemanticFocusResponse {
1310    /// Retrieved chunk summaries.
1311    #[serde(default)]
1312    pub chunks: Vec<serde_json::Value>,
1313    /// Total chunks returned.
1314    #[serde(default)]
1315    pub count: usize,
1316}
1317
1318/// Request for `promote_readme` (`POST /discovery/promote_readme`).
1319/// `chunks` is an array of `ScoredChunk`-compatible objects.
1320#[derive(Debug, Clone, Serialize, Deserialize)]
1321pub struct PromoteReadmeRequest {
1322    /// Chunks to evaluate for README promotion.
1323    pub chunks: Vec<serde_json::Value>,
1324}
1325
1326/// Response from `promote_readme`.
1327/// Server: `{promoted_chunks: [...], count}`.
1328#[derive(Debug, Clone, Serialize, Deserialize)]
1329pub struct PromoteReadmeResponse {
1330    /// Chunks identified as README-quality.
1331    #[serde(default)]
1332    pub promoted_chunks: Vec<serde_json::Value>,
1333    /// Count of promoted chunks.
1334    #[serde(default)]
1335    pub count: usize,
1336}
1337
1338/// Request for `compress_evidence` (`POST /discovery/compress_evidence`).
1339#[derive(Debug, Clone, Serialize, Deserialize)]
1340pub struct CompressEvidenceRequest {
1341    /// Input chunks to compress into bullets.
1342    pub chunks: Vec<serde_json::Value>,
1343    /// Maximum number of bullets to emit (default 20).
1344    #[serde(default, skip_serializing_if = "Option::is_none")]
1345    pub max_bullets: Option<usize>,
1346    /// Maximum bullets per source document (default 3).
1347    #[serde(default, skip_serializing_if = "Option::is_none")]
1348    pub max_per_doc: Option<usize>,
1349}
1350
1351/// Response from `compress_evidence`.
1352/// Server: `{bullets: [{text, source_id, category, score}], count}`.
1353#[derive(Debug, Clone, Serialize, Deserialize)]
1354pub struct CompressEvidenceResponse {
1355    /// Compressed bullet points.
1356    #[serde(default)]
1357    pub bullets: Vec<serde_json::Value>,
1358    /// Count of bullets.
1359    #[serde(default)]
1360    pub count: usize,
1361}
1362
1363/// Request for `build_answer_plan` (`POST /discovery/build_answer_plan`).
1364#[derive(Debug, Clone, Serialize, Deserialize)]
1365pub struct AnswerPlanRequest {
1366    /// Bullets to organize into a plan.
1367    pub bullets: Vec<serde_json::Value>,
1368}
1369
1370/// Response from `build_answer_plan`.
1371/// Server: `{sections: [...], total_bullets, sources}`.
1372#[derive(Debug, Clone, Serialize, Deserialize)]
1373pub struct AnswerPlan {
1374    /// Organized sections.
1375    #[serde(default)]
1376    pub sections: Vec<serde_json::Value>,
1377    /// Total bullet count across all sections.
1378    #[serde(default)]
1379    pub total_bullets: usize,
1380    /// Source collection names referenced.
1381    #[serde(default)]
1382    pub sources: Vec<String>,
1383}
1384
1385/// Request for `render_llm_prompt` (`POST /discovery/render_llm_prompt`).
1386#[derive(Debug, Clone, Serialize, Deserialize)]
1387pub struct RenderPromptRequest {
1388    /// The answer plan to render.
1389    pub plan: AnswerPlan,
1390}
1391
1392/// Response from `render_llm_prompt`.
1393/// Server: `{prompt, length, estimated_tokens}`.
1394#[derive(Debug, Clone, Serialize, Deserialize)]
1395pub struct LlmPrompt {
1396    /// The rendered prompt string.
1397    pub prompt: String,
1398    /// Byte length of the prompt.
1399    #[serde(default)]
1400    pub length: usize,
1401    /// Rough token estimate (length / 4).
1402    #[serde(default)]
1403    pub estimated_tokens: usize,
1404}
1405
1406// ===== ADMIN / OBSERVABILITY TYPES (phase12) =====
1407
1408/// Server statistics returned by `GET /stats`.
1409///
1410/// Server: `{collections, total_vectors, uptime_seconds, version,
1411/// default_quantization, compression_ratio}`. The last two are
1412/// phase25 §5 additions and default to `("none", 1.0)` on older
1413/// servers that do not emit them.
1414#[derive(Debug, Clone, Serialize, Deserialize)]
1415pub struct Stats {
1416    /// Number of collections.
1417    #[serde(default)]
1418    pub collections: usize,
1419    /// Total vectors across all collections.
1420    #[serde(default)]
1421    pub total_vectors: usize,
1422    /// Server uptime in seconds.
1423    #[serde(default)]
1424    pub uptime_seconds: u64,
1425    /// Server version string.
1426    #[serde(default)]
1427    pub version: String,
1428    /// Most-common quantization label across active collections
1429    /// (`none`, `binary`, `sq-4bit`, `sq-8bit`, `sq-16bit`, `sq`, or
1430    /// `pq`). `none` when the store is empty or the server is older
1431    /// than phase25 §5.
1432    #[serde(default = "default_quantization_label")]
1433    pub default_quantization: String,
1434    /// Mean compression ratio (uncompressed_bytes / compressed_bytes)
1435    /// across the collections sharing `default_quantization`. `1.0`
1436    /// when no collections are present or on older servers.
1437    #[serde(default = "default_compression_ratio")]
1438    pub compression_ratio: f32,
1439}
1440
1441fn default_quantization_label() -> String {
1442    "none".to_string()
1443}
1444
1445fn default_compression_ratio() -> f32 {
1446    1.0
1447}
1448
1449/// Runtime metrics snapshot returned by `GET /metrics/runtime`
1450/// (phase25). Single JSON object refreshed once per second on the
1451/// server. Every field defaults so the SDK tolerates older servers
1452/// that do not emit the route.
1453#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1454pub struct RuntimeMetrics {
1455    /// CPU usage of the server process, 0–100 %.
1456    #[serde(default)]
1457    pub cpu_percent: f64,
1458    /// Resident-set size of the server process in bytes.
1459    #[serde(default)]
1460    pub memory_rss_bytes: u64,
1461    /// Total physical memory of the host in bytes.
1462    #[serde(default)]
1463    pub memory_total_bytes: u64,
1464    /// RSS as a fraction of total memory, 0–100 %.
1465    #[serde(default)]
1466    pub memory_percent: f64,
1467    /// Active HTTP connections at the moment of sampling.
1468    #[serde(default)]
1469    pub active_connections: usize,
1470    /// Seconds since the server process started.
1471    #[serde(default)]
1472    pub uptime_seconds: u64,
1473    /// Rolling 60-second queries-per-second across all routes.
1474    #[serde(default)]
1475    pub qps_window_60s: f64,
1476    /// Fraction of requests in the last 60 s with HTTP 5xx status,
1477    /// 0–1.
1478    #[serde(default)]
1479    pub error_rate_5xx_60s: f64,
1480    /// Per-route latency / throughput. Sorted descending by QPS.
1481    #[serde(default)]
1482    pub throughput_by_route: Vec<RouteStats>,
1483    /// WAL state. Zero-initialised on standalone servers without
1484    /// replication.
1485    #[serde(default)]
1486    pub wal: WalSnapshot,
1487}
1488
1489/// Per-route latency + QPS line in `RuntimeMetrics.throughput_by_route`.
1490#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1491pub struct RouteStats {
1492    /// Route path (raw URI; templated routes are normalised by the server).
1493    #[serde(default)]
1494    pub route: String,
1495    /// Queries per second for this route over the last 60 s.
1496    #[serde(default)]
1497    pub qps: f64,
1498    /// 50th-percentile latency in milliseconds.
1499    #[serde(default)]
1500    pub p50_ms: f64,
1501    /// 99th-percentile latency in milliseconds.
1502    #[serde(default)]
1503    pub p99_ms: f64,
1504}
1505
1506/// WAL state surfaced inside `RuntimeMetrics.wal` (phase25 §3).
1507#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1508pub struct WalSnapshot {
1509    /// Latest offset appended to the WAL.
1510    #[serde(default)]
1511    pub current_seq: u64,
1512    /// On-disk WAL file size in bytes (0 in memory-only mode).
1513    #[serde(default)]
1514    pub size_bytes: u64,
1515    /// Unix timestamp (seconds) at which `last_checkpoint_seq` last
1516    /// advanced. 0 when no replica has confirmed an offset.
1517    #[serde(default)]
1518    pub last_checkpoint_at: u64,
1519    /// Lowest offset that has been confirmed by all replicas.
1520    #[serde(default)]
1521    pub last_checkpoint_seq: u64,
1522}
1523
1524/// Server status returned by `GET /status`.
1525/// Server: `{online, version, uptime_seconds, collections_count}`.
1526#[derive(Debug, Clone, Serialize, Deserialize)]
1527pub struct ServerStatus {
1528    /// Whether the server is online.
1529    #[serde(default)]
1530    pub online: bool,
1531    /// Version string.
1532    #[serde(default)]
1533    pub version: String,
1534    /// Uptime in seconds.
1535    #[serde(default)]
1536    pub uptime_seconds: u64,
1537    /// Number of collections.
1538    #[serde(default)]
1539    pub collections_count: usize,
1540}
1541
1542/// Query parameters for `get_logs` (`GET /logs`).
1543#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1544pub struct LogsQuery {
1545    /// Number of log lines to return (default 100).
1546    #[serde(default, skip_serializing_if = "Option::is_none")]
1547    pub lines: Option<usize>,
1548    /// Optional level filter (`INFO`, `WARN`, `ERROR`, `DEBUG`).
1549    #[serde(default, skip_serializing_if = "Option::is_none")]
1550    pub level: Option<String>,
1551}
1552
1553/// One log entry returned by `GET /logs`.
1554#[derive(Debug, Clone, Serialize, Deserialize)]
1555pub struct LogEntry {
1556    /// ISO-8601 timestamp.
1557    #[serde(default)]
1558    pub timestamp: String,
1559    /// Log level.
1560    #[serde(default)]
1561    pub level: String,
1562    /// Log message.
1563    #[serde(default)]
1564    pub message: String,
1565    /// Source component.
1566    #[serde(default)]
1567    pub source: String,
1568}
1569
1570/// Report returned by `cleanup_empty_collections` (`DELETE /collections/cleanup`).
1571///
1572/// The server shape is inferred; the handler returns a free-form JSON
1573/// `Value` so we use `serde_json::Value` fields with defaults.
1574#[derive(Debug, Clone, Serialize, Deserialize)]
1575pub struct CleanupReport {
1576    /// Whether the cleanup succeeded.
1577    #[serde(default)]
1578    pub success: bool,
1579    /// Number of collections removed.
1580    #[serde(default)]
1581    pub removed: usize,
1582    /// Names of the removed collections.
1583    #[serde(default)]
1584    pub collections: Vec<String>,
1585    /// Any message from the server.
1586    #[serde(default)]
1587    pub message: Option<String>,
1588}
1589
1590/// Server configuration snapshot returned by `GET /config` and `POST /config`.
1591///
1592/// The config is a free-form YAML object loaded from `config.yml`; the
1593/// SDK surfaces it as `serde_json::Value` to avoid coupling to the
1594/// server's internal `Config` struct. Wrap in a newtype for ergonomics.
1595#[derive(Debug, Clone, Serialize, Deserialize)]
1596pub struct ConfigSnapshot(pub serde_json::Value);
1597
1598/// Patch applied to the server configuration by `POST /config`.
1599/// The server accepts the full config as a JSON object and writes it to
1600/// `config.yml`.
1601#[derive(Debug, Clone, Serialize, Deserialize)]
1602pub struct ConfigPatch(pub serde_json::Value);
1603
1604/// Metadata for one server-side backup file returned by `GET /backups`.
1605/// Server shape: `{id, name, date, size, collections}`.
1606#[derive(Debug, Clone, Serialize, Deserialize)]
1607pub struct BackupInfo {
1608    /// UUID of the backup.
1609    pub id: String,
1610    /// Human-readable name.
1611    pub name: String,
1612    /// Creation timestamp (RFC3339).
1613    pub date: String,
1614    /// File size in bytes (estimate).
1615    #[serde(default)]
1616    pub size: u64,
1617    /// Collection names included in the backup.
1618    #[serde(default)]
1619    pub collections: Vec<String>,
1620}
1621
1622/// Request body for `create_backup` (`POST /backups/create`).
1623#[derive(Debug, Clone, Serialize, Deserialize)]
1624pub struct CreateBackupRequest {
1625    /// Backup name.
1626    pub name: String,
1627    /// Collection names to include (empty = all).
1628    #[serde(default)]
1629    pub collections: Vec<String>,
1630}
1631
1632/// Request body for `restore_backup` (`POST /backups/restore`).
1633#[derive(Debug, Clone, Serialize, Deserialize)]
1634pub struct RestoreBackupRequest {
1635    /// ID of the backup to restore.
1636    pub backup_id: String,
1637}
1638
1639/// Workspace config entry returned by `GET /workspace/config` and
1640/// `GET /workspace/list`.
1641///
1642/// The config is a free-form YAML object; surfaced as `serde_json::Value`.
1643#[derive(Debug, Clone, Serialize, Deserialize)]
1644pub struct WorkspaceConfig(pub serde_json::Value);
1645
1646/// Request body for `add_workspace` (`POST /workspace/add`).
1647/// Server reads `path` and `collection_name`.
1648#[derive(Debug, Clone, Serialize, Deserialize)]
1649pub struct AddWorkspaceRequest {
1650    /// File-system path to watch.
1651    pub path: String,
1652    /// Collection name to index files into.
1653    pub collection_name: String,
1654}
1655
1656// ===== AUTH TYPES (phase12) =====
1657
1658/// User record returned by auth endpoints.
1659/// Server shape: `{user_id, username, roles}`.
1660#[derive(Debug, Clone, Serialize, Deserialize)]
1661pub struct User {
1662    /// Opaque user identifier.
1663    pub user_id: String,
1664    /// Username string.
1665    pub username: String,
1666    /// Role names.
1667    #[serde(default)]
1668    pub roles: Vec<String>,
1669}
1670
1671/// JWT token returned by `POST /auth/refresh`.
1672/// Server shape: `{access_token, token_type, expires_in}`.
1673#[derive(Debug, Clone, Serialize, Deserialize)]
1674pub struct JwtToken {
1675    /// Bearer access token.
1676    pub access_token: String,
1677    /// Token type (always `"Bearer"`).
1678    pub token_type: String,
1679    /// Lifetime in seconds.
1680    pub expires_in: u64,
1681}
1682
1683/// Password policy report returned by `POST /auth/validate-password`.
1684/// Server shape: `{valid, errors, strength, strength_label}`.
1685#[derive(Debug, Clone, Serialize, Deserialize)]
1686pub struct PasswordPolicyReport {
1687    /// Whether the password satisfies all policy rules.
1688    pub valid: bool,
1689    /// Validation errors (empty when valid).
1690    #[serde(default)]
1691    pub errors: Vec<String>,
1692    /// Strength score 0–100.
1693    #[serde(default)]
1694    pub strength: u8,
1695    /// Human-readable strength label.
1696    #[serde(default)]
1697    pub strength_label: String,
1698}
1699
1700/// Request body for `create_api_key` (`POST /auth/keys`).
1701#[derive(Debug, Clone, Serialize, Deserialize)]
1702pub struct CreateApiKeyRequest {
1703    /// Key name / description.
1704    pub name: String,
1705    /// Permissions (optional; defaults to Read).
1706    #[serde(default)]
1707    pub permissions: Vec<String>,
1708    /// TTL in seconds from now (None = never expires).
1709    #[serde(default, skip_serializing_if = "Option::is_none")]
1710    pub expires_in: Option<u64>,
1711}
1712
1713/// API key returned by `POST /auth/keys`.
1714///
1715/// The actual key string is only returned once at creation time.
1716/// `GET /auth/keys` returns entries with `api_key` omitted.
1717#[derive(Debug, Clone, Serialize, Deserialize)]
1718pub struct ApiKey {
1719    /// Key UUID.
1720    pub id: String,
1721    /// Key name.
1722    pub name: String,
1723    /// Permissions.
1724    #[serde(default)]
1725    pub permissions: Vec<String>,
1726    /// The raw API key value (only present at creation time).
1727    #[serde(default, skip_serializing_if = "Option::is_none")]
1728    pub api_key: Option<String>,
1729    /// Creation timestamp (Unix epoch seconds).
1730    #[serde(default)]
1731    pub created_at: u64,
1732    /// Last-used timestamp.
1733    #[serde(default, skip_serializing_if = "Option::is_none")]
1734    pub last_used: Option<u64>,
1735    /// Expiry timestamp (None = never expires).
1736    #[serde(default, skip_serializing_if = "Option::is_none")]
1737    pub expires_at: Option<u64>,
1738    /// Whether the key is currently active.
1739    #[serde(default)]
1740    pub active: bool,
1741    /// One-time warning message (present at creation).
1742    #[serde(default, skip_serializing_if = "Option::is_none")]
1743    pub warning: Option<String>,
1744    /// Total successful credential validations recorded against this
1745    /// key. Defaults to 0 for keys that have never been used (or for
1746    /// servers that don't yet emit the field).
1747    #[serde(default)]
1748    pub usage_count: u64,
1749}
1750
1751/// Per-collection scope attached to an API key.
1752#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1753pub struct ApiKeyScope {
1754    /// Collection this scope applies to.
1755    pub collection: String,
1756    /// Permissions granted on that collection.
1757    #[serde(default)]
1758    pub permissions: Vec<String>,
1759}
1760
1761/// Request body for `PUT /auth/keys/{id}/permissions`.
1762#[derive(Debug, Clone, Serialize, Deserialize)]
1763pub struct UpdateApiKeyPermissionsRequest {
1764    /// New permission list. Server rejects an empty list with 400.
1765    pub permissions: Vec<String>,
1766    /// `None` leaves existing scopes untouched. `Some(vec![])` clears
1767    /// scopes (default-deny on scope-aware routes).
1768    #[serde(default, skip_serializing_if = "Option::is_none")]
1769    pub scopes: Option<Vec<ApiKeyScope>>,
1770}
1771
1772/// Flattened key view returned by `update_api_key_permissions` and
1773/// `get_api_key_usage`. Mirrors the server's `ApiKeyView`.
1774#[derive(Debug, Clone, Serialize, Deserialize)]
1775pub struct ApiKeyView {
1776    pub id: String,
1777    pub name: String,
1778    pub user_id: String,
1779    #[serde(default)]
1780    pub permissions: Vec<String>,
1781    #[serde(default)]
1782    pub scopes: Vec<ApiKeyScope>,
1783    pub created_at: u64,
1784    #[serde(default, skip_serializing_if = "Option::is_none")]
1785    pub last_used: Option<u64>,
1786    #[serde(default, skip_serializing_if = "Option::is_none")]
1787    pub expires_at: Option<u64>,
1788    #[serde(default)]
1789    pub active: bool,
1790    #[serde(default)]
1791    pub usage_count: u64,
1792}
1793
1794/// One day's usage bucket from `GET /auth/keys/{id}/usage`.
1795#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1796pub struct ApiKeyUsageBucket {
1797    /// ISO-8601 date (UTC), e.g. `"2026-05-03"`.
1798    pub date: String,
1799    /// Successful validations recorded for that day.
1800    pub count: u64,
1801}
1802
1803/// Response body for `GET /auth/keys/{id}/usage`.
1804#[derive(Debug, Clone, Serialize, Deserialize)]
1805pub struct ApiKeyUsageReport {
1806    /// Live key view with up-to-date `usage_count`.
1807    pub key: ApiKeyView,
1808    /// Daily counter buckets, oldest first. Days with zero validations
1809    /// are still present so the caller can render a continuous
1810    /// sparkline without gap-fill logic.
1811    pub buckets: Vec<ApiKeyUsageBucket>,
1812    /// Sum of `buckets[*].count`.
1813    pub window_total: u64,
1814}
1815
1816/// Request body for `create_user` (`POST /auth/users`).
1817#[derive(Debug, Clone, Serialize, Deserialize)]
1818pub struct CreateUserRequest {
1819    /// Username.
1820    pub username: String,
1821    /// Initial password.
1822    pub password: String,
1823    /// Roles to assign (optional; defaults to `["User"]`).
1824    #[serde(default)]
1825    pub roles: Vec<String>,
1826}
1827
1828// ===== REPLICATION SDK TYPES (phase12) =====
1829
1830/// Replication status returned by `GET /replication/status`.
1831/// Server: `{role, enabled, stats?, replicas?}`.
1832#[derive(Debug, Clone, Serialize, Deserialize)]
1833pub struct ReplicationStatus {
1834    /// Node role (`"Master"`, `"Replica"`, or `"Standalone"`).
1835    pub role: String,
1836    /// Whether replication is enabled on this node.
1837    pub enabled: bool,
1838    /// Replication stats (master or replica depending on role).
1839    #[serde(default, skip_serializing_if = "Option::is_none")]
1840    pub stats: Option<ReplicationStats>,
1841    /// Connected replicas (master only).
1842    #[serde(default, skip_serializing_if = "Option::is_none")]
1843    pub replicas: Option<Vec<ReplicaInfo>>,
1844}
1845
1846/// Request body for `configure_replication` (`POST /replication/configure`).
1847#[derive(Debug, Clone, Serialize, Deserialize)]
1848pub struct ReplicationConfig {
1849    /// Target role: `"master"`, `"replica"`, or `"standalone"`.
1850    pub role: String,
1851    /// Bind address for master nodes.
1852    #[serde(default, skip_serializing_if = "Option::is_none")]
1853    pub bind_address: Option<String>,
1854    /// Master address for replica nodes.
1855    #[serde(default, skip_serializing_if = "Option::is_none")]
1856    pub master_address: Option<String>,
1857    /// Heartbeat interval in milliseconds.
1858    #[serde(default, skip_serializing_if = "Option::is_none")]
1859    pub heartbeat_interval: Option<u64>,
1860    /// Replication log size.
1861    #[serde(default, skip_serializing_if = "Option::is_none")]
1862    pub log_size: Option<usize>,
1863}
1864
1865// ===== HUB TYPES (phase12) =====
1866
1867/// A user-scoped backup entry returned by `GET /hub/backups`.
1868/// Mirrors `vectorizer::hub::backup::UserBackupInfo`.
1869#[derive(Debug, Clone, Serialize, Deserialize)]
1870pub struct UserBackup {
1871    /// Backup UUID.
1872    pub id: String,
1873    /// User UUID (owner).
1874    pub user_id: String,
1875    /// Human-readable backup name.
1876    pub name: String,
1877    /// Optional description.
1878    #[serde(default, skip_serializing_if = "Option::is_none")]
1879    pub description: Option<String>,
1880    /// Collections included.
1881    #[serde(default)]
1882    pub collections: Vec<String>,
1883    /// Creation timestamp (RFC3339).
1884    pub created_at: String,
1885    /// Backup size in bytes.
1886    #[serde(default)]
1887    pub size: u64,
1888    /// Status string (`"active"`, `"creating"`, etc.).
1889    #[serde(default)]
1890    pub status: String,
1891}
1892
1893/// Request for `create_user_backup` (`POST /hub/backups`).
1894#[derive(Debug, Clone, Serialize, Deserialize)]
1895pub struct CreateUserBackupRequest {
1896    /// User UUID who owns the backup.
1897    pub user_id: String,
1898    /// Backup name.
1899    pub name: String,
1900    /// Optional description.
1901    #[serde(default, skip_serializing_if = "Option::is_none")]
1902    pub description: Option<String>,
1903    /// Collections to include (None = all user collections).
1904    #[serde(default, skip_serializing_if = "Option::is_none")]
1905    pub collections: Option<Vec<String>>,
1906}
1907
1908/// Request for `restore_user_backup` (`POST /hub/backups/restore`).
1909#[derive(Debug, Clone, Serialize, Deserialize)]
1910pub struct RestoreUserBackupRequest {
1911    /// User UUID.
1912    pub user_id: String,
1913    /// Backup UUID to restore.
1914    pub backup_id: String,
1915    /// Whether to overwrite existing collections.
1916    #[serde(default)]
1917    pub overwrite: bool,
1918}
1919
1920/// Request for `upload_user_backup` (`POST /hub/backups/upload`).
1921///
1922/// The server actually accepts raw bytes via query params; the SDK
1923/// wraps the upload parameters here. The actual byte payload is passed
1924/// separately by the method.
1925#[derive(Debug, Clone, Serialize, Deserialize)]
1926pub struct UploadUserBackupRequest {
1927    /// User UUID.
1928    pub user_id: String,
1929    /// Optional backup name override.
1930    #[serde(default, skip_serializing_if = "Option::is_none")]
1931    pub name: Option<String>,
1932    /// Binary backup data.
1933    #[serde(skip)]
1934    pub data: Vec<u8>,
1935}
1936
1937/// Usage statistics returned by `GET /hub/usage/statistics`.
1938#[derive(Debug, Clone, Serialize, Deserialize)]
1939pub struct UsageStatistics {
1940    /// Whether the call succeeded.
1941    #[serde(default)]
1942    pub success: bool,
1943    /// Human-readable message.
1944    #[serde(default)]
1945    pub message: String,
1946    /// The statistics payload (free-form per server).
1947    #[serde(default)]
1948    pub stats: Option<serde_json::Value>,
1949}
1950
1951/// Quota information returned by `GET /hub/usage/quota`.
1952#[derive(Debug, Clone, Serialize, Deserialize)]
1953pub struct QuotaInfo {
1954    /// Whether the call succeeded.
1955    #[serde(default)]
1956    pub success: bool,
1957    /// Human-readable message.
1958    #[serde(default)]
1959    pub message: String,
1960    /// The quota payload (free-form per server).
1961    #[serde(default)]
1962    pub quota: Option<serde_json::Value>,
1963}
1964
1965// ===== SCHEMA-EVOLUTION + OBSERVABILITY TYPES (phase14) =====
1966
1967/// Parameters for `reindex_collection` (`POST /collections/{name}/reindex`).
1968///
1969/// All fields carry defaults matching the server handler: `m=16`,
1970/// `ef_construction=200`, `ef_search=100`.
1971#[derive(Debug, Clone, Serialize, Deserialize)]
1972pub struct ReindexParams {
1973    /// HNSW `M` parameter (number of bi-directional links).
1974    pub m: u32,
1975    /// HNSW `ef_construction` (size of the dynamic candidate list during build).
1976    pub ef_construction: u32,
1977    /// HNSW `ef_search` (size of the dynamic candidate list at query time).
1978    pub ef_search: u32,
1979}
1980
1981/// Job descriptor returned by `reindex_collection`
1982/// (`POST /collections/{name}/reindex`).
1983///
1984/// Server contract: `{job_id, collection, state, params, progress}`.
1985#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1986pub struct ReindexJob {
1987    /// Opaque job id generated by the server.
1988    pub job_id: String,
1989    /// Collection that was re-indexed.
1990    pub collection: String,
1991    /// Job state string (`"completed"` on success).
1992    pub state: String,
1993    /// HNSW parameters that were applied.
1994    pub params: serde_json::Value,
1995    /// Progress fraction in `[0.0, 1.0]`.
1996    pub progress: f64,
1997}
1998
1999/// Native snapshot metadata returned by `create_native_snapshot` and
2000/// each entry in `list_native_snapshots`.
2001///
2002/// Server contract: `{id, collection, created_at, size_bytes}`.
2003#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2004pub struct NativeSnapshotInfo {
2005    /// Opaque snapshot id (UUID string).
2006    pub id: String,
2007    /// Collection the snapshot was taken from.
2008    pub collection: String,
2009    /// Creation timestamp in RFC-3339 format.
2010    pub created_at: String,
2011    /// Compressed snapshot size in bytes.
2012    pub size_bytes: u64,
2013}
2014
2015/// Request body for `explain_search`
2016/// (`POST /collections/{name}/explain`).
2017#[derive(Debug, Clone, Serialize, Deserialize)]
2018pub struct ExplainRequest {
2019    /// Query vector (must match the collection's dimension).
2020    pub vector: Vec<f32>,
2021    /// Number of nearest neighbours to retrieve (default 10).
2022    #[serde(default, skip_serializing_if = "Option::is_none")]
2023    pub k: Option<u64>,
2024}
2025
2026/// Execution trace attached to an `ExplainResponse`.
2027///
2028/// Server contract: `{visited_nodes, ef_search, hnsw_search_ms,
2029/// payload_filter_evals, quantization_score_ms, total_ms}`.
2030#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2031pub struct ExplainTrace {
2032    /// Number of HNSW graph nodes visited during the search.
2033    pub visited_nodes: usize,
2034    /// Effective `ef_search` value used.
2035    pub ef_search: usize,
2036    /// Wall-clock time spent inside HNSW traversal (milliseconds).
2037    pub hnsw_search_ms: f64,
2038    /// Number of payload-filter predicate evaluations.
2039    pub payload_filter_evals: usize,
2040    /// Wall-clock time spent on quantized distance scoring (milliseconds).
2041    pub quantization_score_ms: f64,
2042    /// Total wall-clock time for the explain call (milliseconds).
2043    pub total_ms: f64,
2044}
2045
2046/// Response from `explain_search`
2047/// (`POST /collections/{name}/explain`).
2048///
2049/// Server contract: `{collection, k, results, trace}`.
2050#[derive(Debug, Clone, Serialize, Deserialize)]
2051pub struct ExplainResponse {
2052    /// Collection the search ran against.
2053    pub collection: String,
2054    /// Number of neighbours requested.
2055    pub k: usize,
2056    /// Ranked search hits (same shape as regular search results).
2057    #[serde(default)]
2058    pub results: Vec<serde_json::Value>,
2059    /// Full execution trace.
2060    pub trace: ExplainTrace,
2061}
2062
2063/// One entry in the slow-query ring buffer returned by `GET /slow_queries`.
2064///
2065/// Server contract: `{timestamp, collection, k, duration_ms}`.
2066#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2067pub struct SlowQueryEntry {
2068    /// ISO-8601 / RFC-3339 timestamp when the slow query was recorded.
2069    pub timestamp: String,
2070    /// Collection the query ran against.
2071    pub collection: String,
2072    /// Number of neighbours requested (`k`).
2073    pub k: usize,
2074    /// Observed query duration in milliseconds.
2075    pub duration_ms: f64,
2076}
2077
2078/// Slow-query ring-buffer configuration returned by
2079/// `POST /slow_queries/config`.
2080///
2081/// Server contract: `{threshold_ms, capacity, status}`.
2082#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2083pub struct SlowQueryConfig {
2084    /// Minimum duration (ms) for a query to be recorded.
2085    pub threshold_ms: u64,
2086    /// Maximum number of entries retained in the ring buffer.
2087    pub capacity: usize,
2088}
2089
2090/// Validation result returned by `POST /hub/validate-key`.
2091#[derive(Debug, Clone, Serialize, Deserialize)]
2092pub struct HubApiKeyValidation {
2093    /// Whether the key is valid.
2094    pub valid: bool,
2095    /// Tenant id the key belongs to.
2096    #[serde(default)]
2097    pub tenant_id: String,
2098    /// Tenant name.
2099    #[serde(default)]
2100    pub tenant_name: String,
2101    /// Permissions granted by the key.
2102    #[serde(default)]
2103    pub permissions: Vec<String>,
2104    /// Validation timestamp (RFC3339).
2105    #[serde(default)]
2106    pub validated_at: String,
2107}
2108
2109// ===== CLUSTER + AUTH ADMIN TYPES (phase15) =====
2110
2111/// Report returned by `POST /cluster/failover`.
2112///
2113/// Server contract: `{promoted_replica_id, master_offset_at_promotion,
2114/// replica_offset_at_promotion, residual_lag_operations}`.
2115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2116pub struct FailoverReport {
2117    /// ID of the replica that was promoted to primary.
2118    pub promoted_replica_id: String,
2119    /// Master WAL offset at the time of promotion.
2120    pub master_offset_at_promotion: u64,
2121    /// Replica's confirmed offset at the time of promotion.
2122    pub replica_offset_at_promotion: u64,
2123    /// Remaining lag in WAL operations.
2124    pub residual_lag_operations: u64,
2125}
2126
2127/// Report returned by `POST /cluster/replicas/{id}/resync`.
2128///
2129/// Server contract: `{replica_id, snapshot_offset, full_snapshot}`.
2130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2131pub struct ResyncJob {
2132    /// ID of the replica being resynced.
2133    pub replica_id: String,
2134    /// Master WAL offset used as the snapshot baseline.
2135    pub snapshot_offset: u64,
2136    /// Whether a full snapshot transfer was initiated.
2137    pub full_snapshot: bool,
2138}
2139
2140/// Information about a newly added cluster peer.
2141///
2142/// Returned by `POST /cluster/peers`.
2143/// Server contract: `{node_id, address, role}`.
2144#[derive(Debug, Clone, Serialize, Deserialize)]
2145pub struct PeerInfo {
2146    /// Opaque node id assigned by the server.
2147    pub node_id: String,
2148    /// Address of the peer (host:port).
2149    pub address: String,
2150    /// Role string: `"member"` or `"observer"`.
2151    pub role: String,
2152}
2153
2154/// Request body for `POST /cluster/peers`.
2155#[derive(Debug, Clone, Serialize, Deserialize)]
2156pub struct AddPeerRequest {
2157    /// Address of the new peer (host:port).
2158    pub address: String,
2159    /// Role: `"member"` (default) or `"observer"`.
2160    #[serde(default)]
2161    pub role: String,
2162}
2163
2164/// Job descriptor returned by `POST /cluster/rebalance`.
2165///
2166/// Server contract: `{job_id, status, shards_to_move, shards_moved,
2167/// last_checkpoint_node, message}`.
2168#[derive(Debug, Clone, Serialize, Deserialize)]
2169pub struct RebalanceJob {
2170    /// Opaque job id.
2171    pub job_id: String,
2172    /// Lifecycle state: `"running"`, `"paused"`, `"completed"`, `"failed"`.
2173    pub status: String,
2174    /// Total shards that need to move.
2175    pub shards_to_move: usize,
2176    /// Shards moved so far.
2177    pub shards_moved: usize,
2178    /// Node-id of the last checkpoint.
2179    #[serde(default, skip_serializing_if = "Option::is_none")]
2180    pub last_checkpoint_node: Option<String>,
2181    /// Human-readable status message.
2182    pub message: String,
2183}
2184
2185/// Response from `POST /auth/keys/{id}/rotate`.
2186///
2187/// Server contract: `{old_key_id, new_key_id, new_token, grace_until}`.
2188#[derive(Debug, Clone, Serialize, Deserialize)]
2189pub struct RotatedKey {
2190    /// The old key id (still valid until `grace_until`).
2191    pub old_key_id: String,
2192    /// The new key id.
2193    pub new_key_id: String,
2194    /// The new key token value — store it securely.
2195    pub new_token: String,
2196    /// Unix timestamp until which the OLD key is still accepted.
2197    pub grace_until: u64,
2198}
2199
2200/// Request body for `POST /auth/keys` — extended with optional per-collection scopes.
2201#[derive(Debug, Clone, Serialize, Deserialize)]
2202pub struct CreateScopedApiKeyRequest {
2203    /// Key name / description.
2204    pub name: String,
2205    /// Global permissions (optional; defaults to `["Read"]`).
2206    #[serde(default)]
2207    pub permissions: Vec<String>,
2208    /// TTL in seconds from now (`None` = never expires).
2209    #[serde(default, skip_serializing_if = "Option::is_none")]
2210    pub expires_in: Option<u64>,
2211    /// Per-collection scopes. Empty = default-deny on scope-enforced routes.
2212    #[serde(default)]
2213    pub scopes: Vec<TokenScope>,
2214}
2215
2216/// Per-collection permission scope sent in [`CreateScopedApiKeyRequest`].
2217#[derive(Debug, Clone, Serialize, Deserialize)]
2218pub struct TokenScope {
2219    /// Collection name this scope applies to.
2220    pub collection: String,
2221    /// Permissions granted on that collection (e.g. `["read", "write"]`).
2222    #[serde(default)]
2223    pub permissions: Vec<String>,
2224}
2225
2226/// RFC 7662 token introspection response from `POST /auth/introspect`.
2227///
2228/// Server contract: `{active, scope?, sub?, exp?, username?}`.
2229#[derive(Debug, Clone, Serialize, Deserialize)]
2230pub struct TokenIntrospection {
2231    /// Whether the token is currently active.
2232    pub active: bool,
2233    /// Space-separated scope string.
2234    #[serde(default, skip_serializing_if = "Option::is_none")]
2235    pub scope: Option<String>,
2236    /// Subject (user_id or key_id).
2237    #[serde(default, skip_serializing_if = "Option::is_none")]
2238    pub sub: Option<String>,
2239    /// Expiry (Unix timestamp).
2240    #[serde(default, skip_serializing_if = "Option::is_none")]
2241    pub exp: Option<u64>,
2242    /// Username (non-standard extension; omitted for inactive tokens).
2243    #[serde(default, skip_serializing_if = "Option::is_none")]
2244    pub username: Option<String>,
2245}
2246
2247/// One entry in the admin audit log returned by `GET /auth/audit`.
2248///
2249/// Server contract: `{actor, action, target, at, correlation_id}`.
2250#[derive(Debug, Clone, Serialize, Deserialize)]
2251pub struct AuditEntry {
2252    /// Username or key-id of the actor.
2253    pub actor: String,
2254    /// Canonical action name, e.g. `"create_api_key"`.
2255    pub action: String,
2256    /// Target resource.
2257    pub target: String,
2258    /// UTC timestamp (RFC-3339).
2259    pub at: String,
2260    /// Correlation-ID propagated from the request middleware.
2261    #[serde(default, skip_serializing_if = "Option::is_none")]
2262    pub correlation_id: Option<String>,
2263}
2264
2265/// Query parameters for `GET /auth/audit`.
2266#[derive(Debug, Clone, Default, Serialize)]
2267pub struct AuditQuery {
2268    /// Filter by actor.
2269    #[serde(skip_serializing_if = "Option::is_none")]
2270    pub actor: Option<String>,
2271    /// Filter by action name.
2272    #[serde(skip_serializing_if = "Option::is_none")]
2273    pub action: Option<String>,
2274    /// Entries at or after this RFC-3339 timestamp.
2275    #[serde(skip_serializing_if = "Option::is_none")]
2276    pub since: Option<String>,
2277    /// Entries at or before this RFC-3339 timestamp.
2278    #[serde(skip_serializing_if = "Option::is_none")]
2279    pub until: Option<String>,
2280    /// Maximum entries to return (server default 200).
2281    #[serde(skip_serializing_if = "Option::is_none")]
2282    pub limit: Option<usize>,
2283}