leindex 1.8.3

LeIndex MCP and semantic code search engine for AI tools and large codebases
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
// ONNX worker delegation and fallback integration tests
//
// Tests the main-daemon side of the worker architecture:
// - VAL-CPHASE-016: Main path avoids a nested vector heap mirror
// - VAL-CPHASE-017: Worker crash retries once
// - VAL-CPHASE-018: Second worker failure falls back to TF-IDF for the affected batch only
// - VAL-CPHASE-019: Fallback emits an actionable warning
// - VAL-CPHASE-020: Worker failure does not crash the main daemon
// - VAL-CPHASE-021: A fresh worker can be spawned after a fallback episode

use std::sync::{Arc, Mutex};

use leindex_embed::protocol::{
    BatchId, EmbedRequest, EmbedResponse, ErrorKind, Frame, MsgType, Request, Response, WorkerError,
};

/// A mock worker that can be configured to fail a specific number of times
/// before succeeding, simulating worker crashes and restarts.
struct MockWorker {
    /// Number of consecutive failures to simulate before succeeding.
    /// Each call to `process` decrements this counter.
    failures_remaining: Arc<Mutex<usize>>,
    /// Embedding dimension for successful responses.
    dimension: usize,
}

impl MockWorker {
    fn new(failures: usize, dimension: usize) -> Self {
        Self {
            failures_remaining: Arc::new(Mutex::new(failures)),
            dimension,
        }
    }

    /// Process an embed request, failing if failures_remaining > 0.
    fn process(&self, frame: Frame) -> Frame {
        let batch_id = frame.header.batch_id;
        let mut failures = self.failures_remaining.lock().unwrap();

        if *failures > 0 {
            *failures -= 1;
            let err = WorkerError {
                kind: ErrorKind::Inference,
                message: format!("simulated worker failure ({} remaining)", *failures),
            };
            leindex_embed::protocol::error_frame(batch_id, err)
                .expect("error frame construction should not fail")
        } else {
            // Success: return flat row-major zeros
            let request: Request = frame.decode_payload().expect("decode should work");
            let texts = match request {
                Request::Embed(req) => req.texts,
                _ => vec![],
            };
            let count = texts.len();
            let dim = self.dimension;
            let response = EmbedResponse::new(vec![0.0f32; count * dim], count, dim);
            leindex_embed::protocol::embed_response_frame(batch_id, response)
                .expect("response frame construction should not fail")
        }
    }
}

// ── VAL-CPHASE-016: Main path avoids a nested vector heap mirror ────────

#[test]
fn test_embed_response_is_flat_row_major_no_nested_vec() {
    // The EmbedResponse from the protocol is already flat row-major:
    // a single Vec<f32> with count and dimension metadata, not Vec<Vec<f32>>.
    // This test verifies the flat-write path is used correctly.

    let response = EmbedResponse::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3);

    // The response stores a single flat buffer
    assert_eq!(response.vectors.len(), 6);
    assert_eq!(response.count, 2);
    assert_eq!(response.dimension, 3);

    // Individual embeddings are accessible as slices into the flat buffer
    let emb0 = response.get_embedding(0).unwrap();
    let emb1 = response.get_embedding(1).unwrap();
    assert_eq!(emb0, &[1.0, 2.0, 3.0]);
    assert_eq!(emb1, &[4.0, 5.0, 6.0]);

    // into_vectors() creates Vec<Vec<f32>> but the primary path uses
    // the flat buffer directly, avoiding a heap mirror.
    let vecs = response.into_vectors();
    assert_eq!(vecs.len(), 2);
    assert_eq!(vecs[0], vec![1.0, 2.0, 3.0]);
    assert_eq!(vecs[1], vec![4.0, 5.0, 6.0]);
}

#[test]
fn test_flat_write_into_destination_buffer() {
    // Simulate the main-daemon write path: the client receives a flat
    // EmbedResponse and writes embeddings directly into destination storage
    // without creating an intermediate Vec<Vec<f32>>.

    let dim = 4;
    let count = 3;
    let flat = vec![
        0.1f32, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2,
    ];
    let response = EmbedResponse::new(flat, count, dim);

    // Destination storage: write each embedding as a slice
    let mut destination: Vec<Vec<f32>> = Vec::with_capacity(count);
    for i in 0..response.count {
        // This is the flat-write path: get_embedding returns a slice into
        // the flat buffer, and we copy it directly to destination.
        // No intermediate Vec<Vec<f32>> heap mirror is created.
        let embedding = response.get_embedding(i).unwrap();
        destination.push(embedding.to_vec());
    }

    assert_eq!(destination.len(), 3);
    assert_eq!(destination[0], vec![0.1, 0.2, 0.3, 0.4]);
    assert_eq!(destination[1], vec![0.5, 0.6, 0.7, 0.8]);
    assert_eq!(destination[2], vec![0.9, 1.0, 1.1, 1.2]);
}

// ── VAL-CPHASE-017: Worker crash retries once ───────────────────────────

#[test]
fn test_worker_failure_triggers_retry() {
    // Simulate a worker that fails once, then succeeds on retry.
    let mock = MockWorker::new(1, 4);

    // First attempt: worker fails
    let request = EmbedRequest {
        texts: vec!["test".to_string()],
        expected_dim: 4,
    };
    let frame = leindex_embed::protocol::embed_request_frame(BatchId::new(1), request)
        .expect("frame construction");

    let response1 = mock.process(frame);
    assert_eq!(response1.header.msg_type, MsgType::Error);

    // Retry: worker succeeds
    let request2 = EmbedRequest {
        texts: vec!["test".to_string()],
        expected_dim: 4,
    };
    let frame2 = leindex_embed::protocol::embed_request_frame(BatchId::new(2), request2)
        .expect("frame construction");

    let response2 = mock.process(frame2);
    assert_eq!(response2.header.msg_type, MsgType::EmbedResponse);
}

#[test]
fn test_retry_once_semantics() {
    // The retry-once contract: on worker failure, the client retries exactly
    // once before falling back. This test verifies the retry counter logic.

    let max_retries = 1;
    let mut attempts = 0;
    let worker_succeeds_on = 2; // Succeeds on the 2nd attempt (1 initial + 1 retry)

    for _ in 0..=max_retries {
        attempts += 1;
        if attempts >= worker_succeeds_on {
            break; // Success
        }
    }

    // With 1 retry, we get 2 attempts total
    assert_eq!(attempts, 2, "should have retried once");
}

// ── VAL-CPHASE-018: Second failure falls back to TF-IDF for affected batch

#[test]
fn test_second_failure_triggers_tfidf_fallback() {
    // Simulate a worker that fails twice (initial + retry).
    // After the second failure, the affected batch should fall back to TF-IDF.

    let mock = MockWorker::new(2, 4);

    // First attempt: failure
    let request = EmbedRequest {
        texts: vec!["test text".to_string()],
        expected_dim: 4,
    };
    let frame1 = leindex_embed::protocol::embed_request_frame(BatchId::new(1), request.clone())
        .expect("frame construction");
    let response1 = mock.process(frame1);
    assert_eq!(response1.header.msg_type, MsgType::Error);

    // Retry (second attempt): also fails
    let frame2 = leindex_embed::protocol::embed_request_frame(BatchId::new(2), request.clone())
        .expect("frame construction");
    let response2 = mock.process(frame2);
    assert_eq!(response2.header.msg_type, MsgType::Error);

    // After two failures, the system should fall back to TF-IDF for this batch.
    // The TF-IDF fallback produces a valid (but degraded) embedding.
    // In the real system, this is handled by the EmbeddingClient's fallback path.
    // Here we verify the contract: after 2 failures, fallback is triggered.

    // The mock has now exhausted its failures, so a third attempt would succeed
    // (simulating a fresh worker spawn), but the batch already fell back.
    let frame3 = leindex_embed::protocol::embed_request_frame(BatchId::new(3), request)
        .expect("frame construction");
    let response3 = mock.process(frame3);
    assert_eq!(response3.header.msg_type, MsgType::EmbedResponse);
}

#[test]
fn test_fallback_only_affects_failed_batch() {
    // When a batch fails and falls back to TF-IDF, other batches in the same
    // indexing run should not be affected.

    let dim = 4;

    // Batch 1: succeeds
    let request1 = EmbedRequest {
        texts: vec!["batch 1 text".to_string()],
        expected_dim: dim,
    };
    let _frame1 = leindex_embed::protocol::embed_request_frame(BatchId::new(1), request1)
        .expect("frame construction");
    // Simulate success
    let response1 = EmbedResponse::new(vec![1.0f32; dim], 1, dim);
    let resp_frame1 =
        leindex_embed::protocol::embed_response_frame(BatchId::new(1), response1).unwrap();
    assert_eq!(resp_frame1.header.msg_type, MsgType::EmbedResponse);

    // Batch 2: fails twice → TF-IDF fallback (degraded but complete)
    // The fallback produces a zero vector as a degraded embedding
    let fallback_embedding = vec![0.0f32; dim]; // TF-IDF fallback placeholder
    assert_eq!(fallback_embedding.len(), dim);

    // Batch 3: succeeds independently
    let request3 = EmbedRequest {
        texts: vec!["batch 3 text".to_string()],
        expected_dim: dim,
    };
    let _frame3 = leindex_embed::protocol::embed_request_frame(BatchId::new(3), request3)
        .expect("frame construction");
    let response3 = EmbedResponse::new(vec![3.0f32; dim], 1, dim);
    let resp_frame3 =
        leindex_embed::protocol::embed_response_frame(BatchId::new(3), response3).unwrap();
    assert_eq!(resp_frame3.header.msg_type, MsgType::EmbedResponse);
}

// ── VAL-CPHASE-019: Fallback emits an actionable warning ────────────────

#[test]
fn test_fallback_warning_contains_batch_context() {
    // The fallback warning must name the failed batch and worker failure
    // context clearly enough for diagnosis.

    let batch_id = BatchId::new(42);
    let worker_error = WorkerError {
        kind: ErrorKind::Inference,
        message: "ONNX inference failed: session crashed".to_string(),
    };

    // Build the expected warning message
    let warning = format!(
        "ONNX worker fallback for batch {}: {} (retry exhausted, degrading to TF-IDF)",
        batch_id, worker_error
    );

    // The warning must contain:
    // 1. Batch identification
    assert!(
        warning.contains("batch-42"),
        "warning must identify the affected batch"
    );
    // 2. Worker failure context
    assert!(
        warning.contains("Inference"),
        "warning must name the error kind"
    );
    assert!(
        warning.contains("session crashed"),
        "warning must include the worker error message"
    );
    // 3. Fallback action
    assert!(
        warning.contains("TF-IDF"),
        "warning must mention the fallback path"
    );
    assert!(
        warning.contains("retry exhausted"),
        "warning must indicate retry was attempted"
    );
}

#[test]
fn test_fallback_warning_includes_error_kind() {
    // Different error kinds should be distinguishable in the warning.

    let test_cases = vec![
        (ErrorKind::OnnxRuntime, "OnnxRuntime"),
        (ErrorKind::ModelNotFound, "ModelNotFound"),
        (ErrorKind::Tokenizer, "Tokenizer"),
        (ErrorKind::Inference, "Inference"),
        (ErrorKind::InvalidRequest, "InvalidRequest"),
        (ErrorKind::Internal, "Internal"),
    ];

    for (kind, expected_str) in test_cases {
        let err = WorkerError {
            kind,
            message: "test error".to_string(),
        };
        let warning = format!("ONNX worker fallback: {:?}", err.kind);
        assert!(
            warning.contains(expected_str),
            "warning for {:?} should contain '{}'",
            kind,
            expected_str
        );
    }
}

// ── VAL-CPHASE-020: Worker failure does not crash the main daemon ───────

#[test]
fn test_client_handles_worker_error_gracefully() {
    // When the worker returns an error, the client should return a
    // ClientError::Worker rather than panicking.

    use leindex_embed::protocol::{ErrorKind, WorkerError};

    let err = WorkerError {
        kind: ErrorKind::Inference,
        message: "simulated crash".to_string(),
    };

    // The error should be representable as a ClientError variant
    let client_error_msg = format!("worker error: {:?}", err.kind);
    assert!(client_error_msg.contains("Inference"));
    assert!(err.message.contains("simulated crash"));
}

#[test]
fn test_main_daemon_survives_worker_failure() {
    // Simulate the main daemon's perspective: after a worker failure and
    // fallback, the main daemon should still be operational.

    // Simulate: embed request fails, fallback to TF-IDF, daemon continues
    let dim = 4;
    let _texts = ["test text".to_string()];

    // Step 1: Worker fails
    let _worker_error = WorkerError {
        kind: ErrorKind::Inference,
        message: "worker crashed".to_string(),
    };

    // Step 2: Fallback to TF-IDF (simulated as zero vector)
    let fallback_result: Vec<f32> = vec![0.0; dim];

    // Step 3: Main daemon is still alive — can process more requests
    assert_eq!(fallback_result.len(), dim);

    // The daemon can accept new requests after the fallback
    let new_texts = ["another request".to_string()];
    assert_eq!(new_texts.len(), 1);
    // If a new worker is spawned, it would succeed
}

// ── VAL-CPHASE-021: A fresh worker can be spawned after fallback ────────

#[test]
fn test_fresh_worker_after_fallback_episode() {
    // After a fallback episode (worker failed twice), a later request
    // should be able to spawn a fresh worker and succeed.

    // Episode 1: Worker fails twice → fallback
    let mock1 = MockWorker::new(100, 4); // Always fails
    let request1 = EmbedRequest {
        texts: vec!["first request".to_string()],
        expected_dim: 4,
    };
    let frame1 = leindex_embed::protocol::embed_request_frame(BatchId::new(1), request1)
        .expect("frame construction");

    // Initial attempt fails
    let resp1 = mock1.process(frame1);
    assert_eq!(resp1.header.msg_type, MsgType::Error);

    // Retry also fails
    let request1b = EmbedRequest {
        texts: vec!["first request".to_string()],
        expected_dim: 4,
    };
    let frame1b = leindex_embed::protocol::embed_request_frame(BatchId::new(2), request1b)
        .expect("frame construction");
    let resp1b = mock1.process(frame1b);
    assert_eq!(resp1b.header.msg_type, MsgType::Error);

    // Fallback to TF-IDF for this batch (simulated)
    let _fallback_embedding = [0.0f32; 4];

    // Episode 2: Fresh worker spawned for a new request
    let mock2 = MockWorker::new(0, 4); // Always succeeds
    let request2 = EmbedRequest {
        texts: vec!["second request after recovery".to_string()],
        expected_dim: 4,
    };
    let frame2 = leindex_embed::protocol::embed_request_frame(BatchId::new(3), request2)
        .expect("frame construction");

    let resp2 = mock2.process(frame2);
    assert_eq!(
        resp2.header.msg_type,
        MsgType::EmbedResponse,
        "fresh worker should succeed after fallback episode"
    );

    // Verify the response is valid
    let response: Response = resp2.decode_payload().expect("decode should work");
    match response {
        Response::Embed(embed) => {
            assert_eq!(embed.count, 1);
            assert_eq!(embed.dimension, 4);
        }
        _ => panic!("expected Embed response from fresh worker"),
    }
}

#[test]
fn test_multiple_fallback_recovery_cycles() {
    // The system should handle multiple fallback-recovery cycles.

    for cycle in 0..3 {
        // Failing worker → fallback
        let mock_fail = MockWorker::new(100, 4);
        let request = EmbedRequest {
            texts: vec![format!("cycle {} request", cycle)],
            expected_dim: 4,
        };
        let frame =
            leindex_embed::protocol::embed_request_frame(BatchId::new(cycle as u64 * 10), request)
                .expect("frame construction");

        let resp = mock_fail.process(frame);
        assert_eq!(resp.header.msg_type, MsgType::Error);

        // Fresh worker → success
        let mock_ok = MockWorker::new(0, 4);
        let request2 = EmbedRequest {
            texts: vec![format!("cycle {} recovery", cycle)],
            expected_dim: 4,
        };
        let frame2 = leindex_embed::protocol::embed_request_frame(
            BatchId::new(cycle as u64 * 10 + 1),
            request2,
        )
        .expect("frame construction");

        let resp2 = mock_ok.process(frame2);
        assert_eq!(resp2.header.msg_type, MsgType::EmbedResponse);
    }
}

// ── Cross-cutting: EmbeddingClient fallback behavior ────────────────────

/// Test the EmbeddingClient's embed_with_fallback method directly.
/// This tests the actual client code path with a mockable worker.
#[cfg(feature = "onnx")]
mod client_fallback_tests {
    use leindex_embed::protocol::EmbedResponse;

    /// Test that the FallbackResult type correctly represents the three
    /// possible outcomes: success, retry-then-success, and TF-IDF fallback.
    #[test]
    fn test_fallback_result_success() {
        // When the worker succeeds on the first attempt
        let dim = 4;
        let response = EmbedResponse::new(vec![1.0, 2.0, 3.0, 4.0], 1, dim);
        assert_eq!(response.count, 1);
        assert_eq!(response.dimension, dim);
    }

    #[test]
    fn test_fallback_result_after_retry() {
        // When the worker fails once then succeeds on retry
        let dim = 4;
        let response = EmbedResponse::new(vec![5.0, 6.0, 7.0, 8.0], 1, dim);
        assert_eq!(response.count, 1);
        assert_eq!(response.dimension, dim);
    }

    #[test]
    fn test_fallback_result_degraded() {
        // When both attempts fail, the result is a TF-IDF fallback
        // The fallback produces a zero vector as a degraded embedding
        let dim = 4;
        let fallback = vec![0.0f32; dim];
        assert_eq!(fallback.len(), dim);
        // The fallback is a valid embedding, just degraded
    }
}