Skip to main content

remem/eval/
capacity.rs

1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
2use std::fmt::{Display, Formatter, Result as FmtResult};
3use std::time::Instant;
4
5use anyhow::{bail, ensure, Context, Result};
6use rusqlite::Connection;
7use serde::Serialize;
8use sha2::{Digest, Sha256};
9
10use crate::eval::golden::{self, GoldenDataset, GoldenMemory, QueryEvaluation, QueryMetrics};
11use crate::memory::Memory;
12use crate::retrieval::search::SearchExplain;
13
14mod noise;
15
16use noise::{COMMANDS, CRATE_NAMES, ERROR_SIGNATURES, FILE_PATHS, MEMORY_TYPES, OWNERS};
17
18pub const DEFAULT_DATASET_PATH: &str = "eval/golden.json";
19const REPORT_VERSION: &str = "2026-07-03";
20const RANK_K: usize = 10;
21const TRACKED_CHANNELS: [&str; 6] = [
22    "fts",
23    "entity",
24    "fact",
25    "temporal",
26    "vector",
27    "like_fallback",
28];
29
30#[derive(Debug, Clone)]
31pub struct CapacityEvalOptions {
32    pub dataset_path: String,
33    pub seed: u64,
34    pub scales: Vec<usize>,
35    pub k: usize,
36}
37
38impl Default for CapacityEvalOptions {
39    fn default() -> Self {
40        Self {
41            dataset_path: DEFAULT_DATASET_PATH.to_string(),
42            seed: 42,
43            scales: vec![1, 10],
44            k: 5,
45        }
46    }
47}
48
49#[derive(Debug, Clone, Serialize)]
50pub struct CapacityEvalReport {
51    pub version: &'static str,
52    pub dataset_path: String,
53    pub seed: u64,
54    pub k: usize,
55    pub scale_factors: Vec<usize>,
56    pub base_corpus_size: usize,
57    pub scales: Vec<CapacityScaleReport>,
58    pub degradation: CapacityDegradation,
59    pub omitted_followups: Vec<&'static str>,
60}
61
62#[derive(Debug, Clone, Serialize)]
63pub struct CapacityScaleReport {
64    pub scale: usize,
65    pub corpus_size: usize,
66    pub noise_count: usize,
67    pub corpus_hash: String,
68    pub fused: CapacityFusedMetrics,
69    pub channels: BTreeMap<String, CapacityFusedMetrics>,
70}
71
72#[derive(Debug, Clone, Serialize)]
73pub struct CapacityFusedMetrics {
74    pub scored_queries: usize,
75    pub hit_at_k: f64,
76    pub recall_at_k: f64,
77    pub ndcg_at_10: f64,
78    pub evidence_recall_at_k: f64,
79    pub p95_latency_ms: f64,
80}
81
82#[derive(Debug, Clone, Serialize)]
83pub struct CapacityDegradation {
84    pub largest_scale: usize,
85    pub fused_recall_at_k_loss: f64,
86    pub fused_ndcg_at_10_loss: f64,
87    pub fused_evidence_recall_at_k_loss: f64,
88    pub channels: BTreeMap<String, CapacityChannelDegradation>,
89}
90
91#[derive(Debug, Clone, Serialize)]
92pub struct CapacityChannelDegradation {
93    pub recall_at_k_loss: f64,
94    pub ndcg_at_10_loss: f64,
95    pub evidence_recall_at_k_loss: f64,
96}
97
98#[derive(Debug, Clone)]
99pub(in crate::eval) struct ScaledDataset {
100    pub dataset: GoldenDataset,
101    pub noise_count: usize,
102    pub corpus_hash: String,
103}
104
105pub fn run_capacity_eval(options: CapacityEvalOptions) -> Result<CapacityEvalReport> {
106    let dataset = golden::load_dataset(&options.dataset_path)?;
107    run_capacity_eval_for_dataset(options, dataset)
108}
109
110pub(in crate::eval) fn run_capacity_eval_for_dataset(
111    options: CapacityEvalOptions,
112    dataset: GoldenDataset,
113) -> Result<CapacityEvalReport> {
114    validate_capacity_dataset(&dataset)?;
115    let scales = normalize_scales(options.scales)?;
116    let k = options.k.max(1);
117    let mut scale_reports = Vec::with_capacity(scales.len());
118
119    for scale in &scales {
120        let scaled = synthesize_capacity_dataset(&dataset, options.seed, *scale)?;
121        scale_reports.push(
122            evaluate_capacity_scale(*scale, scaled, k)
123                .with_context(|| format!("run capacity eval scale {scale}"))?,
124        );
125    }
126
127    let baseline = scale_reports
128        .iter()
129        .find(|report| report.scale == 1)
130        .context("capacity eval requires a 1x baseline scale")?;
131    let largest = scale_reports
132        .iter()
133        .max_by_key(|report| report.scale)
134        .context("capacity eval requires at least one scale")?;
135    let degradation = CapacityDegradation {
136        largest_scale: largest.scale,
137        fused_recall_at_k_loss: positive_loss(
138            baseline.fused.recall_at_k,
139            largest.fused.recall_at_k,
140        ),
141        fused_ndcg_at_10_loss: positive_loss(baseline.fused.ndcg_at_10, largest.fused.ndcg_at_10),
142        fused_evidence_recall_at_k_loss: positive_loss(
143            baseline.fused.evidence_recall_at_k,
144            largest.fused.evidence_recall_at_k,
145        ),
146        channels: channel_degradation(&baseline.channels, &largest.channels),
147    };
148
149    Ok(CapacityEvalReport {
150        version: REPORT_VERSION,
151        dataset_path: options.dataset_path,
152        seed: options.seed,
153        k,
154        scale_factors: scales,
155        base_corpus_size: dataset.corpus.len(),
156        scales: scale_reports,
157        degradation,
158        omitted_followups: vec!["nightly_dashboard_ingestion", "50x_nightly_scale"],
159    })
160}
161
162fn evaluate_capacity_scale(
163    scale: usize,
164    scaled: ScaledDataset,
165    k: usize,
166) -> Result<CapacityScaleReport> {
167    let conn = Connection::open_in_memory().context("open in-memory capacity eval DB")?;
168    crate::migrate::run_migrations(&conn).context("migrate in-memory capacity eval DB")?;
169    golden::run::seed_fixture_corpus(&conn, &scaled.dataset.corpus)?;
170
171    let mut fused = MetricAccumulator::default();
172    let mut channels = TRACKED_CHANNELS
173        .iter()
174        .map(|channel| ((*channel).to_string(), MetricAccumulator::default()))
175        .collect::<BTreeMap<_, _>>();
176    let fetch_limit = k.max(RANK_K) as i64;
177
178    for query in &scaled.dataset.queries {
179        let query_tokens = golden::run::estimate_query_tokens(&query.query);
180        let started = Instant::now();
181        let (results, explain) = crate::retrieval::search::search_with_branch_explain(
182            &conn,
183            Some(&query.query),
184            query.project.as_deref(),
185            query.memory_type.as_deref(),
186            fetch_limit,
187            0,
188            false,
189            query.branch.as_deref(),
190        )?;
191        let retrieval_latency_ms = started.elapsed().as_secs_f64() * 1000.0;
192        let fused_evaluation =
193            golden::run::evaluate_query(query, &results, k, query_tokens, retrieval_latency_ms);
194        fused.add(&fused_evaluation);
195
196        let channel_hit_ids = channel_hit_ids(explain.as_ref());
197        for channel in TRACKED_CHANNELS {
198            let ids = channel_hit_ids.get(channel).cloned().unwrap_or_default();
199            let memories = load_ordered_memories(&conn, &ids)?;
200            let latency_ms = channel_latency_ms(explain.as_ref(), channel);
201            let channel_evaluation =
202                golden::run::evaluate_query(query, &memories, k, query_tokens, latency_ms);
203            channels
204                .entry(channel.to_string())
205                .or_default()
206                .add(&channel_evaluation);
207        }
208    }
209
210    Ok(CapacityScaleReport {
211        scale,
212        corpus_size: scaled.dataset.corpus.len(),
213        noise_count: scaled.noise_count,
214        corpus_hash: scaled.corpus_hash,
215        fused: fused.finish(),
216        channels: channels
217            .into_iter()
218            .map(|(channel, accumulator)| (channel, accumulator.finish()))
219            .collect(),
220    })
221}
222
223pub(in crate::eval) fn synthesize_capacity_dataset(
224    base: &GoldenDataset,
225    seed: u64,
226    scale: usize,
227) -> Result<ScaledDataset> {
228    validate_capacity_dataset(base)?;
229    ensure!(scale >= 1, "capacity scale must be >= 1");
230    let base_count = base.corpus.len();
231    let target_count = base_count
232        .checked_mul(scale)
233        .context("capacity scale overflows corpus size")?;
234    let noise_count = target_count.saturating_sub(base_count);
235    let mut dataset = base.clone();
236    let mut existing_topic_keys = topic_keys(&dataset.corpus);
237    let projects = corpus_projects(&dataset.corpus);
238
239    for noise_index in 0..noise_count {
240        let memory = noise_memory(seed, scale, noise_index, &projects, &existing_topic_keys)?;
241        if let Some(topic_key) = memory.topic_key.as_ref() {
242            existing_topic_keys.insert(topic_key.clone());
243        }
244        dataset.corpus.push(memory);
245    }
246
247    let corpus_hash = corpus_hash(&dataset.corpus)?;
248    Ok(ScaledDataset {
249        dataset,
250        noise_count,
251        corpus_hash,
252    })
253}
254
255pub(crate) fn normalize_scales(scales: Vec<usize>) -> Result<Vec<usize>> {
256    ensure!(
257        !scales.is_empty(),
258        "capacity eval requires at least one scale"
259    );
260    let mut normalized = BTreeSet::new();
261    for scale in scales {
262        ensure!(scale >= 1, "capacity scale must be >= 1");
263        normalized.insert(scale);
264    }
265    ensure!(
266        normalized.contains(&1),
267        "capacity eval requires scale 1 as the baseline"
268    );
269    Ok(normalized.into_iter().collect())
270}
271
272fn validate_capacity_dataset(dataset: &GoldenDataset) -> Result<()> {
273    ensure!(
274        dataset.has_fixture_corpus(),
275        "capacity eval requires a golden dataset with a fixture corpus"
276    );
277    ensure!(
278        !dataset.queries.is_empty(),
279        "capacity eval requires at least one query"
280    );
281    Ok(())
282}
283
284fn positive_loss(baseline: f64, current: f64) -> f64 {
285    (baseline - current).max(0.0)
286}
287
288#[derive(Default)]
289struct MetricAccumulator {
290    scored_queries: usize,
291    hit_at_k: f64,
292    recall_at_k: f64,
293    ndcg_at_10: f64,
294    evidence_recall_at_k: f64,
295    latencies_ms: Vec<f64>,
296}
297
298impl MetricAccumulator {
299    fn add(&mut self, evaluation: &QueryEvaluation) {
300        self.latencies_ms.push(evaluation.retrieval_latency_ms);
301        if let Some(metrics) = evaluation.metrics.as_ref() {
302            self.add_metrics(metrics);
303        }
304    }
305
306    fn add_metrics(&mut self, metrics: &QueryMetrics) {
307        self.scored_queries += 1;
308        self.hit_at_k += metrics.hit_at_k;
309        self.recall_at_k += metrics.recall_at_k;
310        self.ndcg_at_10 += metrics.ndcg_at_10;
311        self.evidence_recall_at_k += metrics.evidence_recall_at_k;
312    }
313
314    fn finish(self) -> CapacityFusedMetrics {
315        let denominator = self.scored_queries as f64;
316        CapacityFusedMetrics {
317            scored_queries: self.scored_queries,
318            hit_at_k: mean(self.hit_at_k, denominator),
319            recall_at_k: mean(self.recall_at_k, denominator),
320            ndcg_at_10: mean(self.ndcg_at_10, denominator),
321            evidence_recall_at_k: mean(self.evidence_recall_at_k, denominator),
322            p95_latency_ms: positive_zero(golden::run::percentile(self.latencies_ms, 95.0)),
323        }
324    }
325}
326
327fn mean(sum: f64, denominator: f64) -> f64 {
328    if denominator == 0.0 {
329        0.0
330    } else {
331        sum / denominator
332    }
333}
334
335fn positive_zero(value: f64) -> f64 {
336    if value == 0.0 {
337        0.0
338    } else {
339        value
340    }
341}
342
343fn channel_hit_ids(explain: Option<&SearchExplain>) -> BTreeMap<String, Vec<i64>> {
344    explain
345        .map(|explain| {
346            explain
347                .channels
348                .iter()
349                .filter(|channel| channel.enabled)
350                .map(|channel| {
351                    (
352                        channel.name.clone(),
353                        channel
354                            .hits
355                            .iter()
356                            .map(|hit| hit.memory_id)
357                            .collect::<Vec<_>>(),
358                    )
359                })
360                .collect()
361        })
362        .unwrap_or_default()
363}
364
365fn channel_latency_ms(explain: Option<&SearchExplain>, channel: &str) -> f64 {
366    positive_zero(
367        explain
368            .map(|explain| {
369                explain
370                    .timings
371                    .iter()
372                    .filter(|timing| timing.phase == channel)
373                    .map(|timing| timing.elapsed_ms as f64)
374                    .sum()
375            })
376            .unwrap_or_default(),
377    )
378}
379
380fn load_ordered_memories(conn: &Connection, ids: &[i64]) -> Result<Vec<Memory>> {
381    let loaded = crate::memory::get_memories_by_ids_with_suppressed_policy(conn, ids, None, false)?;
382    let id_to_memory = loaded
383        .into_iter()
384        .map(|memory| (memory.id, memory))
385        .collect::<HashMap<_, _>>();
386    Ok(ids
387        .iter()
388        .filter_map(|id| id_to_memory.get(id).cloned())
389        .collect())
390}
391
392fn channel_degradation(
393    baseline: &BTreeMap<String, CapacityFusedMetrics>,
394    largest: &BTreeMap<String, CapacityFusedMetrics>,
395) -> BTreeMap<String, CapacityChannelDegradation> {
396    baseline
397        .iter()
398        .filter_map(|(channel, baseline_metrics)| {
399            let largest_metrics = largest.get(channel)?;
400            Some((
401                channel.clone(),
402                CapacityChannelDegradation {
403                    recall_at_k_loss: positive_loss(
404                        baseline_metrics.recall_at_k,
405                        largest_metrics.recall_at_k,
406                    ),
407                    ndcg_at_10_loss: positive_loss(
408                        baseline_metrics.ndcg_at_10,
409                        largest_metrics.ndcg_at_10,
410                    ),
411                    evidence_recall_at_k_loss: positive_loss(
412                        baseline_metrics.evidence_recall_at_k,
413                        largest_metrics.evidence_recall_at_k,
414                    ),
415                },
416            ))
417        })
418        .collect()
419}
420
421fn topic_keys(corpus: &[GoldenMemory]) -> HashSet<String> {
422    corpus
423        .iter()
424        .filter_map(|memory| memory.topic_key.clone())
425        .collect()
426}
427
428fn corpus_projects(corpus: &[GoldenMemory]) -> Vec<String> {
429    let mut projects = Vec::new();
430    let mut seen = HashSet::new();
431    for memory in corpus {
432        if seen.insert(memory.project.as_str()) {
433            projects.push(memory.project.clone());
434        }
435    }
436    projects
437}
438
439fn noise_memory(
440    seed: u64,
441    scale: usize,
442    noise_index: usize,
443    projects: &[String],
444    existing_topic_keys: &HashSet<String>,
445) -> Result<GoldenMemory> {
446    let project = projects
447        .get(slot(seed, noise_index, 0, projects.len()))
448        .context("capacity noise requires at least one project")?
449        .clone();
450    let memory_type = MEMORY_TYPES[noise_index % MEMORY_TYPES.len()].to_string();
451    let file_path = FILE_PATHS[slot(seed, noise_index, 1, FILE_PATHS.len())];
452    let crate_name = CRATE_NAMES[slot(seed, noise_index, 2, CRATE_NAMES.len())];
453    let error = ERROR_SIGNATURES[slot(seed, noise_index, 3, ERROR_SIGNATURES.len())];
454    let command = COMMANDS[slot(seed, noise_index, 4, COMMANDS.len())];
455    let owner = OWNERS[slot(seed, noise_index, 5, OWNERS.len())];
456    let suffix = splitmix64(seed ^ ((scale as u64) << 32) ^ noise_index as u64);
457    let topic_key = format!("capacity-noise-{seed:x}-{scale}-{noise_index}-{suffix:x}");
458    if existing_topic_keys.contains(&topic_key) {
459        bail!("capacity noise topic key collision: {topic_key}");
460    }
461
462    Ok(GoldenMemory {
463        project,
464        topic_key: Some(topic_key),
465        title: format!("Capacity noise {crate_name} {error}"),
466        content: format!(
467            "Capacity distractor {noise_index}: {owner} investigated {file_path} in crate {crate_name}. \
468             Command `{command}` returned {error}. The follow-up marker is synthetic-noise-{seed:x}-{suffix:x}."
469        ),
470        memory_type,
471        branch: Some("main".to_string()),
472        scope: "project".to_string(),
473        status: "active".to_string(),
474        files: Some(file_path.to_string()),
475        created_at_epoch: Some(1_800_000_000 + noise_index as i64),
476        access_count: Some(0),
477        last_accessed_epoch: None,
478    })
479}
480
481fn slot(seed: u64, index: usize, salt: u64, len: usize) -> usize {
482    debug_assert!(len > 0);
483    let mixed = splitmix64(seed ^ salt.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ index as u64);
484    (mixed as usize) % len
485}
486
487fn splitmix64(mut value: u64) -> u64 {
488    value = value.wrapping_add(0x9E37_79B9_7F4A_7C15);
489    value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
490    value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
491    value ^ (value >> 31)
492}
493
494fn corpus_hash(corpus: &[GoldenMemory]) -> Result<String> {
495    let bytes = serde_json::to_vec(corpus).context("serialize capacity corpus for hash")?;
496    let mut hasher = Sha256::new();
497    hasher.update(bytes);
498    Ok(format!("sha256:{:x}", hasher.finalize()))
499}
500
501impl Display for CapacityEvalReport {
502    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
503        writeln!(
504            f,
505            "remem eval-capacity - seed={} k={} scales={:?}",
506            self.seed, self.k, self.scale_factors
507        )?;
508        writeln!(f, "dataset: {}", self.dataset_path)?;
509        for scale in &self.scales {
510            writeln!(
511                f,
512                "  {}x: corpus={} noise={} R@{}={:.3} nDCG@10={:.3} evidence@{}={:.3} p95={:.2}ms hash={}",
513                scale.scale,
514                scale.corpus_size,
515                scale.noise_count,
516                self.k,
517                scale.fused.recall_at_k,
518                scale.fused.ndcg_at_10,
519                self.k,
520                scale.fused.evidence_recall_at_k,
521                scale.fused.p95_latency_ms,
522                scale.corpus_hash
523            )?;
524            for (channel, metrics) in &scale.channels {
525                writeln!(
526                    f,
527                    "    {channel}: R@{}={:.3} nDCG@10={:.3} evidence@{}={:.3} p95={:.2}ms",
528                    self.k,
529                    metrics.recall_at_k,
530                    metrics.ndcg_at_10,
531                    self.k,
532                    metrics.evidence_recall_at_k,
533                    metrics.p95_latency_ms
534                )?;
535            }
536        }
537        writeln!(
538            f,
539            "degradation at {}x: R@{} loss={:.3}, nDCG@10 loss={:.3}, evidence@{} loss={:.3}",
540            self.degradation.largest_scale,
541            self.k,
542            self.degradation.fused_recall_at_k_loss,
543            self.degradation.fused_ndcg_at_10_loss,
544            self.k,
545            self.degradation.fused_evidence_recall_at_k_loss
546        )
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::eval::golden::{EvidenceRef, GoldenQuery};
554
555    #[test]
556    fn capacity_synthesis_is_deterministic_for_same_seed_and_scale() -> Result<()> {
557        let dataset = tiny_dataset();
558        let first = synthesize_capacity_dataset(&dataset, 7, 3)?;
559        let second = synthesize_capacity_dataset(&dataset, 7, 3)?;
560        let different_seed = synthesize_capacity_dataset(&dataset, 8, 3)?;
561
562        assert_eq!(first.corpus_hash, second.corpus_hash);
563        assert_eq!(first.noise_count, 4);
564        assert_eq!(first.dataset.corpus.len(), 6);
565        assert_eq!(first.dataset.queries[0].id, dataset.queries[0].id);
566        assert_ne!(first.corpus_hash, different_seed.corpus_hash);
567        Ok(())
568    }
569
570    #[test]
571    fn capacity_report_includes_scale_curve_and_degradation() -> Result<()> {
572        let report = run_capacity_eval_for_dataset(
573            CapacityEvalOptions {
574                dataset_path: "inline-test".to_string(),
575                seed: 9,
576                scales: vec![3, 1],
577                k: 5,
578            },
579            tiny_dataset(),
580        )?;
581
582        assert_eq!(report.scale_factors, vec![1, 3]);
583        assert_eq!(report.base_corpus_size, 2);
584        assert_eq!(report.scales.len(), 2);
585        assert_eq!(report.scales[0].scale, 1);
586        assert_eq!(report.scales[0].noise_count, 0);
587        assert_eq!(report.scales[1].scale, 3);
588        assert_eq!(report.scales[1].noise_count, 4);
589        assert_eq!(report.degradation.largest_scale, 3);
590        assert!(report.scales[0].channels.contains_key("fts"));
591        assert!(report.scales[0].channels.contains_key("vector"));
592        assert!(report.degradation.channels.contains_key("fts"));
593
594        let json = serde_json::to_value(&report)?;
595        assert_eq!(json["seed"], 9);
596        assert_eq!(json["scales"][1]["fused"]["scored_queries"], 2);
597        assert_eq!(json["scales"][1]["channels"]["fts"]["scored_queries"], 2);
598        assert!(json["degradation"]["channels"]["fts"]["recall_at_k_loss"].is_number());
599        assert!(json["omitted_followups"]
600            .as_array()
601            .expect("omitted followups should be an array")
602            .contains(&serde_json::json!("nightly_dashboard_ingestion")));
603        assert!(!json["omitted_followups"]
604            .as_array()
605            .expect("omitted followups should be an array")
606            .contains(&serde_json::json!("per_channel_attribution")));
607        Ok(())
608    }
609
610    #[test]
611    fn capacity_report_quality_is_stable_for_same_seed() -> Result<()> {
612        let dataset = tiny_dataset();
613        let first = run_capacity_eval_for_dataset(
614            CapacityEvalOptions {
615                dataset_path: "inline-test".to_string(),
616                seed: 11,
617                scales: vec![1, 2],
618                k: 5,
619            },
620            dataset.clone(),
621        )?;
622        let second = run_capacity_eval_for_dataset(
623            CapacityEvalOptions {
624                dataset_path: "inline-test".to_string(),
625                seed: 11,
626                scales: vec![1, 2],
627                k: 5,
628            },
629            dataset,
630        )?;
631
632        assert_eq!(quality_signature(&first), quality_signature(&second));
633        Ok(())
634    }
635
636    #[test]
637    fn capacity_scales_require_one_x_baseline() {
638        let err = normalize_scales(vec![2, 3])
639            .expect_err("missing 1x scale should fail")
640            .to_string();
641        assert!(err.contains("requires scale 1"));
642    }
643
644    fn quality_signature(
645        report: &CapacityEvalReport,
646    ) -> Vec<(
647        usize,
648        String,
649        usize,
650        f64,
651        f64,
652        f64,
653        Vec<(String, f64, f64, f64)>,
654    )> {
655        report
656            .scales
657            .iter()
658            .map(|scale| {
659                (
660                    scale.scale,
661                    scale.corpus_hash.clone(),
662                    scale.fused.scored_queries,
663                    scale.fused.recall_at_k,
664                    scale.fused.ndcg_at_10,
665                    scale.fused.evidence_recall_at_k,
666                    scale
667                        .channels
668                        .iter()
669                        .map(|(channel, metrics)| {
670                            (
671                                channel.clone(),
672                                metrics.recall_at_k,
673                                metrics.ndcg_at_10,
674                                metrics.evidence_recall_at_k,
675                            )
676                        })
677                        .collect(),
678                )
679            })
680            .collect()
681    }
682
683    fn tiny_dataset() -> GoldenDataset {
684        GoldenDataset {
685            version: Some("capacity-test".to_string()),
686            description: Some("capacity test fixture".to_string()),
687            corpus: vec![
688                GoldenMemory {
689                    project: "synthetic/capacity".to_string(),
690                    topic_key: Some("capacity-alpha-anchor".to_string()),
691                    title: "Alpha routing anchor".to_string(),
692                    content: "Alpha routing anchor lives in src/alpha.rs".to_string(),
693                    memory_type: "decision".to_string(),
694                    branch: Some("main".to_string()),
695                    scope: "project".to_string(),
696                    status: "active".to_string(),
697                    files: Some("src/alpha.rs".to_string()),
698                    created_at_epoch: Some(1_700_000_000),
699                    access_count: Some(0),
700                    last_accessed_epoch: None,
701                },
702                GoldenMemory {
703                    project: "synthetic/capacity".to_string(),
704                    topic_key: Some("capacity-beta-anchor".to_string()),
705                    title: "Beta retry anchor".to_string(),
706                    content: "Beta retry anchor belongs to src/beta.rs".to_string(),
707                    memory_type: "bugfix".to_string(),
708                    branch: Some("main".to_string()),
709                    scope: "project".to_string(),
710                    status: "active".to_string(),
711                    files: Some("src/beta.rs".to_string()),
712                    created_at_epoch: Some(1_700_000_010),
713                    access_count: Some(0),
714                    last_accessed_epoch: None,
715                },
716            ],
717            queries: vec![
718                GoldenQuery {
719                    id: "alpha".to_string(),
720                    query: "Alpha routing anchor".to_string(),
721                    category: "retrieval".to_string(),
722                    slice: Some("capacity_test".to_string()),
723                    hop_path: None,
724                    project: Some("synthetic/capacity".to_string()),
725                    branch: Some("main".to_string()),
726                    memory_type: None,
727                    relevant_ids: vec![],
728                    evidence_refs: vec![EvidenceRef {
729                        topic_key: Some("capacity-alpha-anchor".to_string()),
730                        ..EvidenceRef::default()
731                    }],
732                    expect_abstain: false,
733                    false_premise: false,
734                    notes: None,
735                },
736                GoldenQuery {
737                    id: "beta".to_string(),
738                    query: "Beta retry anchor".to_string(),
739                    category: "retrieval".to_string(),
740                    slice: Some("capacity_test".to_string()),
741                    hop_path: None,
742                    project: Some("synthetic/capacity".to_string()),
743                    branch: Some("main".to_string()),
744                    memory_type: None,
745                    relevant_ids: vec![],
746                    evidence_refs: vec![EvidenceRef {
747                        topic_key: Some("capacity-beta-anchor".to_string()),
748                        ..EvidenceRef::default()
749                    }],
750                    expect_abstain: false,
751                    false_premise: false,
752                    notes: None,
753                },
754            ],
755        }
756    }
757}