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}