1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! [`PunnuEvent`] — the event type broadcast over the
//! [`crate::punnu::Punnu`] event stream.
//!
//! Events are **lossy by design**: when a subscriber falls behind the
//! configured channel capacity ([`crate::punnu::PunnuConfig::event_channel_capacity`],
//! default 256), the backing
//! [`tokio::sync::broadcast`] channel drops the oldest events for that
//! subscriber and surfaces a `RecvError::Lagged` on the next receive.
//! The producer (the `Punnu` itself) never blocks and never errors
//! when receivers lag — slow subscribers degrade to skipped events,
//! the cache itself stays healthy.
//!
//! The same lossy contract holds when there are zero subscribers: a
//! `send` with no receivers returns `Err(SendError)` which the Punnu
//! ignores. Events are best-effort observability, not a durable log.
use crateCacheable;
use Arc;
/// A single observable event from a [`crate::punnu::Punnu`].
///
/// Generic over the cached type `T` so subscribers can match on the
/// payload without boxing. Carries `Arc<T>` for `Insert` / `Update` so
/// consumers can hold a reference to the entry without copying it.
///
/// `PunnuEvent<T>` does **not** require `T: Debug` — `T` is often a
/// payload type that doesn't derive `Debug`. Subscribers that want
/// debug output should match on the variant and format their own
/// fields.
///
/// # Reason taxonomy: [`EventReason`] vs. [`InvalidationReason`]
///
/// [`PunnuEvent::Invalidate`] carries an [`EventReason`] — the full
/// taxonomy including system-internal reasons ([`EventReason::LruEvict`],
/// [`EventReason::TtlExpired`], [`EventReason::BackendInvalidation`])
/// that the runtime constructs but callers cannot synthesise.
/// [`crate::punnu::Punnu::invalidate`] takes the narrower
/// [`InvalidationReason`] enum, which is the public subset
/// (`Manual` / `OnSave` / `OnDelete`). The split keeps the call-side
/// API honest — callers can't pass `LruEvict` to `invalidate` — while
/// subscribers continue to see one unified reason discriminator on the
/// event stream.
// `Clone` is required by `tokio::sync::broadcast` for the message
// type. We implement it manually rather than deriving so the bound
// remains `T: Cacheable` (without forcing `T: Clone` — `Arc<T>` is
// already `Clone` regardless of `T`).
// Manual `Debug` so the event type is debuggable without requiring
// `T: Debug` (id is always Debug-renderable via the trait bound below;
// we use the type name for the value placeholder rather than the
// payload itself, which matches the spec's "type_name as a metrics
// label" pattern).
/// Caller-supplied invalidation reason — the public subset accepted by
/// [`crate::punnu::Punnu::invalidate`].
///
/// Externally-initiated invalidation only. System-internal reasons
/// (LRU eviction, TTL expiry, backend-driven invalidation) live in the
/// sibling [`EventReason`] enum; callers cannot synthesise those.
///
/// These variants are local event labels. Backend invalidation streams carry
/// backend messages such as "this id changed" or "this keyspace changed"; Sassi
/// maps those messages to [`EventReason::BackendInvalidation`] on the local
/// event stream.
/// Full invalidation taxonomy emitted on [`PunnuEvent::Invalidate`].
///
/// Includes both the public reasons callers can pass to
/// [`crate::punnu::Punnu::invalidate`] (lifted in via the
/// [`From<InvalidationReason>`] conversion) and the system-internal
/// reasons sassi constructs itself ([`EventReason::LruEvict`],
/// [`EventReason::TtlExpired`], [`EventReason::BackendInvalidation`]).
/// Subscribers see this enum on the event stream; only the
/// [`InvalidationReason`] subset is reachable through the public
/// `invalidate` call path.
///
/// The system-internal variants are individually marked
/// `#[non_exhaustive]`. Per the Rust reference, this prevents external
/// crates from constructing them via the unit syntax — sassi can
/// still write `EventReason::LruEvict` internally, but external code
/// cannot. Pattern-matching from outside requires the `{ .. }` rest
/// pattern (e.g. `EventReason::LruEvict { .. }`), which keeps the
/// invariant enforceable through Rust's safe surface: external code
/// can observe internal reasons but has no safe construction path to
/// synthesise them. (Arbitrary downstream `unsafe` can transmute its
/// way around any privacy boundary in Rust; this seal is a safe-code
/// guarantee, not a hard impossibility.)
///
/// These variants are local event labels. They are not the backend wire format.
/// Backends publish [`crate::backend::BackendInvalidation`] messages, and Sassi
/// translates those messages into this taxonomy for subscribers.