a3s_memory/vector/index.rs
1use super::{
2 VectorIndexChangeToken, VectorIndexDescriptor, VectorIndexError, VectorIndexObservation,
3 VectorIndexStatus, VectorMutationConsistency, VectorRecord, VectorResult, VectorRevision,
4 VectorSearchRequest, VectorSearchResult,
5};
6
7/// A bounded vector index whose content and lifecycle are owned by its caller.
8///
9/// Partitions are the atomic mutation unit. Implementations must make a
10/// successful replacement visible in one revision and must not expose a
11/// partially constructed partition to concurrent searches.
12#[async_trait::async_trait]
13pub trait VectorIndex: Send + Sync {
14 /// Return the immutable shape and resource limits of this index.
15 fn descriptor(&self) -> &VectorIndexDescriptor;
16
17 /// Return the latest locally observed status without waiting for I/O.
18 ///
19 /// This compatibility accessor can be stale for durable or remote
20 /// backends. Use [`Self::observe`] when the result guards correctness.
21 fn status(&self) -> VectorIndexStatus;
22
23 /// Return exact evidence for the current revision of one index history.
24 ///
25 /// The default preserves source compatibility but provides no continuity
26 /// proof. A backend may return `Some` only when every content mutation
27 /// advances the revision and its history identity changes whenever storage
28 /// is independently recreated or restored onto a divergent history.
29 fn change_token(&self) -> Option<VectorIndexChangeToken> {
30 None
31 }
32
33 /// Observe one self-consistent published revision.
34 ///
35 /// The conservative default exposes only the status compatibility view.
36 /// Backends must override this method to expose an exact history token;
37 /// doing so asserts that the status and token were read atomically.
38 async fn observe(&self) -> VectorResult<VectorIndexObservation> {
39 let observation = VectorIndexObservation {
40 status: self.status(),
41 change_token: None,
42 };
43 observation.verify()?;
44 Ok(observation)
45 }
46
47 /// Return the strongest partition-mutation ordering contract implemented
48 /// by this backend.
49 fn mutation_consistency(&self) -> VectorMutationConsistency {
50 VectorMutationConsistency::PartitionAtomic
51 }
52
53 /// Atomically replace every record in `partition`.
54 ///
55 /// Replacing an existing partition with an empty record list removes it.
56 /// Replacing a missing partition with an empty list is a no-op.
57 async fn replace_partition(
58 &self,
59 partition: &str,
60 records: Vec<VectorRecord>,
61 ) -> VectorResult<VectorIndexStatus>;
62
63 /// Atomically replace one partition only when the complete index still has
64 /// `expected_revision`.
65 ///
66 /// Implementations advertising `IndexRevisionCas` must compare and mutate
67 /// at one linearization point. The default fails closed so a custom backend
68 /// cannot accidentally claim cross-writer ordering from a check-then-write.
69 async fn replace_partition_if_revision(
70 &self,
71 _partition: &str,
72 _expected_revision: VectorRevision,
73 _records: Vec<VectorRecord>,
74 ) -> VectorResult<VectorIndexStatus> {
75 Err(VectorIndexError::ConditionalMutationUnsupported)
76 }
77
78 /// Atomically remove one partition. Missing partitions are a no-op.
79 async fn remove_partition(&self, partition: &str) -> VectorResult<VectorIndexStatus>;
80
81 /// Atomically remove one partition only when the complete index still has
82 /// `expected_revision`.
83 async fn remove_partition_if_revision(
84 &self,
85 _partition: &str,
86 _expected_revision: VectorRevision,
87 ) -> VectorResult<VectorIndexStatus> {
88 Err(VectorIndexError::ConditionalMutationUnsupported)
89 }
90
91 /// Search one immutable index revision.
92 async fn search(&self, request: VectorSearchRequest) -> VectorResult<VectorSearchResult>;
93
94 /// Remove every partition. Clearing an empty index is a no-op.
95 async fn clear(&self) -> VectorResult<VectorIndexStatus>;
96}