Skip to main content

tinyquant_core/corpus/
events.rs

1//! Domain events emitted by the [`Corpus`](super::Corpus) aggregate.
2//!
3//! Exactly **four** event variants exist, matching `tinyquant_cpu.corpus.events`
4//! one-for-one.  No new variants are added in Phase 18; see the plan for the
5//! parity table and rationale.
6//!
7//! ## Events NOT raised (parity with Python)
8//!
9//! - **No** `VectorsRemoved` — `Corpus::remove` is a silent mutation.
10//! - **No** `VectorDecompressed` (singular) — only `decompress_all` emits.
11//! - **No** `CorpusCleared` — not a method on the Python aggregate.
12//! - **No** `PolicyChanged` — `CompressionPolicy` is immutable after the
13//!   first insert.
14
15use alloc::sync::Arc;
16
17use crate::codec::CodecConfig;
18use crate::corpus::compression_policy::CompressionPolicy;
19use crate::types::{CorpusId, Timestamp, VectorId};
20
21/// A domain event emitted by the [`Corpus`](super::Corpus) aggregate.
22///
23/// Events are buffered in `pending_events` and drained atomically by
24/// [`Corpus::drain_events`](super::Corpus::drain_events).
25#[derive(Clone, Debug)]
26pub enum CorpusEvent {
27    /// Emitted once when a new `Corpus` is successfully constructed.
28    ///
29    /// Python parity: `CorpusCreated`.
30    Created {
31        /// The id of the newly created corpus.
32        corpus_id: CorpusId,
33        /// The codec configuration active at construction time.
34        codec_config: CodecConfig,
35        /// The compression policy set at construction time.
36        compression_policy: CompressionPolicy,
37        /// Nanosecond Unix timestamp of construction.
38        timestamp: Timestamp,
39    },
40
41    /// Emitted after one or more vectors are successfully inserted.
42    ///
43    /// A single `VectorsInserted` event covers the entire batch on
44    /// `insert_batch` success; `insert` emits exactly one event per call.
45    ///
46    /// Python parity: `VectorsInserted`.
47    VectorsInserted {
48        /// The id of the corpus that received the vectors.
49        corpus_id: CorpusId,
50        /// Ids of the vectors that were inserted, in insertion order.
51        vector_ids: Arc<[VectorId]>,
52        /// Number of vectors inserted (equals `vector_ids.len()`).
53        count: u32,
54        /// Nanosecond Unix timestamp of the insertion.
55        timestamp: Timestamp,
56    },
57
58    /// Emitted after [`decompress_all`](super::Corpus::decompress_all) succeeds.
59    ///
60    /// Single-vector `decompress` does **not** emit an event (Python parity).
61    ///
62    /// Python parity: `CorpusDecompressed`.
63    Decompressed {
64        /// The id of the corpus whose vectors were decompressed.
65        corpus_id: CorpusId,
66        /// Total number of vectors decompressed.
67        vector_count: u32,
68        /// Nanosecond Unix timestamp of the operation.
69        timestamp: Timestamp,
70    },
71
72    /// Emitted when a policy or dimension violation is detected but the
73    /// operation is not immediately fatal (e.g., logging path).
74    ///
75    /// Python parity: `CompressionPolicyViolationDetected`.
76    PolicyViolationDetected {
77        /// The id of the corpus where the violation was detected.
78        corpus_id: CorpusId,
79        /// The category of the violation.
80        kind: ViolationKind,
81        /// Human-readable detail string, suitable for logging.
82        detail: Arc<str>,
83        /// Nanosecond Unix timestamp of detection.
84        timestamp: Timestamp,
85    },
86}
87
88/// Category of a policy or configuration violation detected by the corpus.
89///
90/// The `as_python_tag` strings must match Python's `violation_type` field
91/// exactly for cross-language parity.
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
93pub enum ViolationKind {
94    /// The supplied `CodecConfig` hash does not match the corpus's config.
95    ConfigMismatch,
96    /// An operation conflicts with the corpus's compression policy.
97    PolicyConflict,
98    /// A vector with the same id already exists in the corpus.
99    DuplicateId,
100    /// The supplied vector's dimension does not match the corpus's dimension.
101    DimensionMismatch,
102}
103
104impl ViolationKind {
105    /// Returns the canonical Python `violation_type` string for this kind.
106    ///
107    /// These strings are part of the public API contract; do **not** change
108    /// them without updating the Python counterpart.
109    #[must_use]
110    pub const fn as_python_tag(self) -> &'static str {
111        match self {
112            Self::ConfigMismatch => "config_mismatch",
113            Self::PolicyConflict => "policy_conflict",
114            Self::DuplicateId => "duplicate_id",
115            Self::DimensionMismatch => "dimension_mismatch",
116        }
117    }
118}