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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//! EU-5b RED tests — lock-flip of the default embedder identity to bge-small.
//!
//! Per `dev/plans/prompts/0.7.1-EMBEDDER-UNDEFER-HANDOFF.md` §EU-5b. These
//! tests assert:
//!
//! 1. `DEFAULT_EMBEDDER_*` constants point at fathomdb-bge-small-en-v1.5 /
//! 5c38ec7c... / 384.
//! 2. `EmbedderChoice::Default` materialises a real BGE embedder, the open
//! succeeds, and the reported identity is the bge-small one. (Feature-gated
//! behind `default-embedder`; cold-open downloads ~133 MB from HuggingFace.)
//! 3. `OpenReport.embedder_mean_centering_required` is true for Default.
//! 4. `OpenReport.embedder_download_ms` is `Some(>0)` on a cold cache and
//! `None` on a warm cache.
//! 5. `OpenReport.embedder_events` is populated on Default opens.
//! 6. The `MeanAccumulator` pins the per-workspace mean at the threshold
//! crossing and emits `MeanVecPinned` in the same commit.
//! 7. The §0.5 at-pin re-quantize pass actually UPDATEs pre-pin sign-bit
//! rows.
//! 8. CLI `fathomdb doctor warm-cache` lives at the CLI level
//! (see `tests/cli_warm_cache.rs`).
use std::sync::Arc;
use fathomdb_embedder::EmbedderEvent;
use fathomdb_embedder_api::{Embedder, EmbedderError, EmbedderIdentity, Vector};
use fathomdb_engine::{EmbedderChoice, Engine, MEAN_VEC_PIN_THRESHOLD};
use rusqlite::Connection;
use tempfile::TempDir;
// EU-5c — env-gate the network-hitting tests below. CI sets
// `FATHOMDB_SKIP_NETWORK_TESTS=1` only when the cache-warm step fails
// (HF unreachable, etc.); the default path is "tests run against warm
// cache". Local dev with internet runs them normally. cargo has no
// first-class "skipped" status — early-return with a log line is the
// idiomatic pattern.
#[allow(unused_macros)]
macro_rules! skip_if_no_network {
() => {
if std::env::var("FATHOMDB_SKIP_NETWORK_TESTS").is_ok() {
eprintln!("[skip] FATHOMDB_SKIP_NETWORK_TESTS set; skipping test");
return;
}
};
}
fn fixture_path(name: &str) -> (TempDir, std::path::PathBuf) {
let dir = TempDir::new().unwrap();
let path = dir.path().join(format!("{name}.sqlite"));
(dir, path)
}
// ---------------------------------------------------------------------------
// Test 1 — constant flip
// ---------------------------------------------------------------------------
#[test]
fn default_embedder_constants_flipped_to_bge_small() {
// The engine exposes the default identity via `Engine::open` ->
// `OpenReport.default_embedder`. Use that as the observable witness.
let (_dir, path) = fixture_path("eu5b_constants");
let opened =
Engine::open(&path).expect("open should succeed even pre-fetch (no Default choice)");
assert_eq!(opened.report.default_embedder.name, "fathomdb-bge-small-en-v1.5");
assert_eq!(opened.report.default_embedder.revision, "5c38ec7c405ec4b44b94cc5a9bb96e735b38267a");
assert_eq!(opened.report.default_embedder.dimension, 384);
}
// ---------------------------------------------------------------------------
// Tests 2 / 3 / 5 — EmbedderChoice::Default round-trip (cold open).
//
// These run only with the `default-embedder` feature enabled (and require
// network access for the HuggingFace fetch).
// ---------------------------------------------------------------------------
#[cfg(feature = "default-embedder")]
#[test]
fn embedder_choice_default_succeeds_with_bge_identity() {
skip_if_no_network!();
let (_dir, path) = fixture_path("eu5b_default_succeeds");
let opened = Engine::open_with_choice(&path, EmbedderChoice::Default)
.expect("EmbedderChoice::Default must succeed after EU-5b");
assert_eq!(opened.report.default_embedder.name, "fathomdb-bge-small-en-v1.5");
}
#[cfg(feature = "default-embedder")]
#[test]
fn open_report_embedder_mean_centering_required_true_for_default() {
skip_if_no_network!();
let (_dir, path) = fixture_path("eu5b_mc_required");
let opened = Engine::open_with_choice(&path, EmbedderChoice::Default)
.expect("EmbedderChoice::Default must succeed after EU-5b");
assert!(
opened.report.embedder_mean_centering_required,
"Default identity must report MC-required after the EU-5b flip"
);
}
#[cfg(feature = "default-embedder")]
#[test]
fn open_report_embedder_events_populated_for_default() {
skip_if_no_network!();
let (_dir, path) = fixture_path("eu5b_events");
let opened = Engine::open_with_choice(&path, EmbedderChoice::Default)
.expect("EmbedderChoice::Default must succeed");
assert!(
!opened.report.embedder_events.is_empty(),
"Default open must surface loader events (downloads or cache hits)"
);
}
#[cfg(feature = "default-embedder")]
#[test]
fn open_report_embedder_download_ms_some_on_cold_open() {
skip_if_no_network!();
// Cold open: download_ms must be Some(>0) when any file was fetched.
// (We can't force a cold cache portably without overriding the loader
// cache root; this test asserts only the non-`None` shape on first
// Default open in the process, which is the documented contract.)
let (_dir, path) = fixture_path("eu5b_download_ms");
let opened = Engine::open_with_choice(&path, EmbedderChoice::Default)
.expect("EmbedderChoice::Default must succeed");
// Either Some(>0) on cold, or None on warm — but the field must be
// surfaced (no longer hard-coded `None` regardless of state).
if let Some(ms) = opened.report.embedder_download_ms {
assert!(ms > 0, "download_ms must be > 0 on cold open");
}
// If the cache was already warm before this test, the field reads None
// and the events stream is all cache hits — still a valid post-EU-5b
// shape. The "non-`None` on cold open" half is exercised by
// `cli_doctor_warm_cache` against a fresh cache root.
}
// ---------------------------------------------------------------------------
// Test 6 — DefaultEmbedderNotWired removed.
// Asserts the variant is gone from EmbedderError. Compile-time check: if
// any code still references the variant after GREEN, the engine + bindings
// won't build.
// ---------------------------------------------------------------------------
#[test]
fn default_embedder_not_wired_error_variant_removed() {
// Exhaustive-match witness: if `DefaultEmbedderNotWired` were re-added
// to `EmbedderError`, the compiler would force an arm here and we'd
// notice immediately. The two production variants are the only legal
// inhabitants after EU-5b.
fn _exhaustive_witness(err: EmbedderError) -> &'static str {
match err {
EmbedderError::Failed { .. } => "failed",
EmbedderError::Timeout => "timeout",
}
}
// Touch the witness so the compiler keeps it. No runtime assertion.
let _ = _exhaustive_witness;
}
// ---------------------------------------------------------------------------
// Tests 7 / 8 — MeanAccumulator pinning + at-pin re-quantize.
//
// We use a SimulatedBgeEmbedder that reports the bge-small identity name
// (so the engine treats it as MC-required) but produces deterministic
// f32 output without touching the network or candle.
// ---------------------------------------------------------------------------
#[derive(Clone, Debug)]
struct SimulatedBgeEmbedder {
identity: EmbedderIdentity,
}
impl Default for SimulatedBgeEmbedder {
fn default() -> Self {
Self {
identity: EmbedderIdentity::new(
"fathomdb-bge-small-en-v1.5",
"5c38ec7c405ec4b44b94cc5a9bb96e735b38267a",
384,
),
}
}
}
impl Embedder for SimulatedBgeEmbedder {
fn identity(&self) -> EmbedderIdentity {
self.identity.clone()
}
fn embed(&self, input: &str) -> Result<Vector, EmbedderError> {
// Deterministic, varying-by-input vector so the streaming mean
// accumulator sees non-trivial input.
let mut v = vec![0.0f32; 384];
let mut seed: u64 = 0;
for b in input.bytes() {
seed = seed.wrapping_mul(131).wrapping_add(u64::from(b));
}
for (i, slot) in v.iter_mut().enumerate() {
let mixed = seed.wrapping_add(i as u64).wrapping_mul(2654435761);
*slot = ((mixed >> 8) as u32 as f32) / (u32::MAX as f32) - 0.5;
}
Ok(v)
}
}
fn open_with_simulated_bge(
name: &str,
) -> (TempDir, std::path::PathBuf, fathomdb_engine::OpenedEngine) {
let (dir, path) = fixture_path(name);
let opened = Engine::open_with_choice(
&path,
EmbedderChoice::Caller(Arc::new(SimulatedBgeEmbedder::default())),
)
.expect("open with simulated bge");
(dir, path, opened)
}
fn write_docs(engine: &Engine, count: u64) {
engine.configure_vector_kind_for_test("doc").expect("configure vector kind");
for i in 0..count {
let txt = format!("doc-{i}");
engine
.write_vector_for_test("doc", &txt)
.unwrap_or_else(|err| panic!("write_vector_for_test failed at i={i}: {err:?}"));
}
}
#[test]
fn mean_accumulator_pins_at_threshold_with_real_embedder() {
let (_dir, path, opened) = open_with_simulated_bge("eu5b_pin_threshold");
write_docs(&opened.engine, MEAN_VEC_PIN_THRESHOLD);
drop(opened);
// The mean_vec must now be populated.
let connection = Connection::open(&path).expect("reopen");
let mean_blob: Option<Vec<u8>> = connection
.query_row(
"SELECT mean_vec FROM _fathomdb_embedder_profiles WHERE profile = 'default'",
[],
|row| row.get::<_, Option<Vec<u8>>>(0),
)
.expect("mean_vec query");
let mean_blob = mean_blob.expect("mean_vec must be NOT NULL after threshold crossing");
assert_eq!(mean_blob.len(), 384 * 4, "mean_vec byte length must be 4 * dim");
}
#[test]
fn mean_accumulator_emits_mean_vec_pinned_event() {
let (_dir, _path, opened) = open_with_simulated_bge("eu5b_pin_event");
// Subscribe to lifecycle events that carry MeanVecPinned. The engine's
// existing surface for accumulator events is to surface them through
// the same `OpenReport.embedder_events` channel on the *next* open —
// but EU-5b also expects the projection-worker pin commit to emit a
// MeanVecPinned variant retrievable via the test seam.
write_docs(&opened.engine, MEAN_VEC_PIN_THRESHOLD);
let events =
opened.engine.drain_mean_centering_events_for_test().expect("drain mean-centering events");
let mut saw_pin = false;
for ev in &events {
if let EmbedderEvent::MeanVecPinned { dim, doc_count } = ev.clone() {
assert_eq!(dim, 384u32);
assert!(doc_count >= 1u64, "doc_count must be >= 1 at pin commit");
saw_pin = true;
}
}
assert!(saw_pin, "MeanVecPinned event must be emitted at pin commit");
}
#[test]
fn requantize_pass_updates_prepin_rows() {
let (_dir, path, opened) = open_with_simulated_bge("eu5b_requantize");
// Write enough docs to cross the pin threshold.
write_docs(&opened.engine, MEAN_VEC_PIN_THRESHOLD);
drop(opened);
// The pre-pin rows' sign-bit columns must reflect a non-zero mean
// having been applied. We sample one row and assert at least one
// bit differs from the un-centered sign-bit of its raw vector. (The
// simulated embedder produces a varying signed distribution, so the
// chance of a coincidence equality is astronomically small.)
let connection = Connection::open(&path).expect("reopen");
let any_row: i64 = connection
.query_row("SELECT rowid FROM vector_default ORDER BY rowid LIMIT 1", [], |row| row.get(0))
.expect("at least one row exists");
let embedding: Vec<u8> = connection
.query_row("SELECT embedding FROM vector_default WHERE rowid = ?1", [any_row], |row| {
row.get::<_, Vec<u8>>(0)
})
.expect("read embedding f32");
let embedding_bin: Vec<u8> = connection
.query_row("SELECT embedding_bin FROM vector_default WHERE rowid = ?1", [any_row], |row| {
row.get::<_, Vec<u8>>(0)
})
.expect("read embedding_bin");
// Recompute the un-centered sign-bit pattern host-side and compare.
// sqlite-vec packs little-endian, MSB-first per byte. We don't need
// to mirror the exact encoding — we just need at least one byte to
// disagree to prove the re-quantize touched the row.
let mut uncentered_bits = Vec::with_capacity(embedding.len() / 4 / 8 + 1);
let mut acc: u8 = 0;
let mut count = 0;
for chunk in embedding.chunks_exact(4) {
let f = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
acc = (acc << 1) | (if f >= 0.0 { 1 } else { 0 });
count += 1;
if count == 8 {
uncentered_bits.push(acc);
acc = 0;
count = 0;
}
}
if count > 0 {
uncentered_bits.push(acc << (8 - count));
}
// Both buffers should be the same length (1 bit per dim, packed).
assert_eq!(
uncentered_bits.len(),
embedding_bin.len(),
"bit-packed length mismatch: uncentered={} stored={}",
uncentered_bits.len(),
embedding_bin.len()
);
assert!(
uncentered_bits != embedding_bin,
"stored sign-bits must differ from un-centered sign-bits after re-quantize"
);
}