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
//! ADR-017 §R-F7 — cache-side telemetry seam.
//!
//! Defines the trait + label-constant set that the Phase A.2 substrate
//! (`block_store::DiskBlockStore`, `recovery::*`) calls into when a
//! quarantine or eviction event fires. The concrete sink lives in
//! `serve::api::state::KvSpillCounters` (bin-side), but the substrate
//! deliberately depends only on this trait so the narrow `kv_persist`
//! lib facade (`src/lib.rs`) can compile without pulling the bin-side
//! `serve::api` module graph.
//!
//! ## Why a trait (not a direct struct ref)
//!
//! `src/lib.rs` re-exports `kv_persist::block_store` + `kv_persist::recovery`
//! into a narrow lib target so `tests/kv_persist_writer_kill_minus_9.rs`
//! can drive the real production types from a forked child. That lib
//! target intentionally OMITS `serve::api` (and its long tail of
//! `multi_model` + `intelligence::hardware` deps). Importing
//! `KvSpillCounters` directly inside `block_store.rs` / `recovery.rs`
//! would break the lib build.
//!
//! Threading the seam as `Option<&Arc<dyn KvCacheMetricsSink>>`
//! decouples the substrate from the concrete sink: the lib build
//! doesn't see `KvSpillCounters` at all (nothing in the lib's import
//! graph mentions it); the bin build wires the production
//! `KvSpillCounters` instance through `Arc<dyn KvCacheMetricsSink>`.
//! Tests that don't care about telemetry pass `None`.
//!
//! ## Why the label-constant arrays live here too
//!
//! `KV_QUARANTINE_REASONS` and `KV_EVICTION_TRIGGERS` are the closed
//! enums of `/metrics` label values. Both are referenced by
//! (a) the bump sites here in `kv_persist`, and
//! (b) the `/metrics` handler in `serve::api::handlers`, which emits
//! them as the cardinality preamble.
//! Hosting them in `kv_persist::metrics` keeps the two consumers in
//! lockstep — one place to amend if a label is added, one compile-time
//! match for both sides.
use Arc;
/// ADR-017 §R-F7: closed enum of `hf2q_kv_quarantined_total{reason="..."}`
/// label values. Order is load-bearing — index lookups in
/// `KvCacheMetricsSink::record_quarantine` use the corresponding
/// `KvQuarantineReason` variant index, and the `/metrics` emit walks
/// this array in order so successive scrapes stay diff-stable.
///
/// Adding a new variant MUST append (not insert mid-array). The
/// constant length ([`KV_QUARANTINE_REASON_COUNT`]) is what
/// `KvSpillCounters` uses to size its `[AtomicU64; N]` storage.
pub const KV_QUARANTINE_REASONS: & = &;
/// Cardinality of [`KV_QUARANTINE_REASONS`]. Used by `KvSpillCounters`
/// as the storage-array length.
pub const KV_QUARANTINE_REASON_COUNT: usize = 4;
/// ADR-017 §R-F7: closed enum of `hf2q_kv_cache_evictions_total{trigger="..."}`
/// label values. Today only `"budget_overflow"` fires (the only path
/// through `evict_lru_until_under_budget`). Future triggers (e.g.
/// `"manual"` for an operator-driven cache flush) MUST append to the
/// end of this array to preserve scrape ordering.
pub const KV_EVICTION_TRIGGERS: & = &;
/// Cardinality of [`KV_EVICTION_TRIGGERS`]. Sized for the single
/// trigger today.
pub const KV_EVICTION_TRIGGER_COUNT: usize = 1;
/// ADR-017 §R-F7 quarantine-reason mirror enum, decoupled from
/// `kv_persist::recovery::QuarantineReason` so the metrics seam doesn't
/// pull `recovery` into modules that only need to bump telemetry.
/// `From<recovery::QuarantineReason>` in `recovery.rs` keeps the two
/// enums in 1:1 correspondence (exhaustive match — adding a variant to
/// either side without the other is a compile error).
///
/// Variant order matches [`KV_QUARANTINE_REASONS`] index ordering:
/// 0. `TruncatedHeader` → `"trunc"`
/// 1. `VersionMismatch` → `"verbump"`
/// 2. `BodyHashMismatch` → `"bodyhash"`
/// 3. `ParityFail` → `"parity"`
/// ADR-017 §R-F7 telemetry sink — implemented by
/// `serve::api::state::KvSpillCounters` (bin-side). The Phase A.2
/// substrate (`block_store`, `recovery`) takes
/// `Option<&Arc<dyn KvCacheMetricsSink>>` at every call site so:
///
/// * The narrow `kv_persist` lib facade compiles without pulling
/// `serve::api` into its module graph (see `src/lib.rs`).
/// * Tests that don't care about telemetry pass `None` and the bump
/// becomes a no-op.
/// * The production wiring (`cmd_serve`) hands the same Arc the
/// `/metrics` handler reads, so bump and scrape see one shared
/// table without an extra hop through global state.
///
/// `Send + Sync` because the manager's eviction path may run from a
/// concurrent tokio task; `KvSpillCounters` is already Send + Sync so
/// the bound is a no-cost discipline check at trait-impl time.
/// Convenience alias used by trigger sites: the optional reference the
/// bump-site receives. `None` ⇒ no-op (tests, lib build).
pub type MetricsSinkRef<'a> = ;