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 search_context: None,
479 })
480}
481
482fn slot(seed: u64, index: usize, salt: u64, len: usize) -> usize {
483 debug_assert!(len > 0);
484 let mixed = splitmix64(seed ^ salt.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ index as u64);
485 (mixed as usize) % len
486}
487
488fn splitmix64(mut value: u64) -> u64 {
489 value = value.wrapping_add(0x9E37_79B9_7F4A_7C15);
490 value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
491 value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
492 value ^ (value >> 31)
493}
494
495fn corpus_hash(corpus: &[GoldenMemory]) -> Result<String> {
496 let bytes = serde_json::to_vec(corpus).context("serialize capacity corpus for hash")?;
497 let mut hasher = Sha256::new();
498 hasher.update(bytes);
499 Ok(format!("sha256:{:x}", hasher.finalize()))
500}
501
502impl Display for CapacityEvalReport {
503 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
504 writeln!(
505 f,
506 "remem eval-capacity - seed={} k={} scales={:?}",
507 self.seed, self.k, self.scale_factors
508 )?;
509 writeln!(f, "dataset: {}", self.dataset_path)?;
510 for scale in &self.scales {
511 writeln!(
512 f,
513 " {}x: corpus={} noise={} R@{}={:.3} nDCG@10={:.3} evidence@{}={:.3} p95={:.2}ms hash={}",
514 scale.scale,
515 scale.corpus_size,
516 scale.noise_count,
517 self.k,
518 scale.fused.recall_at_k,
519 scale.fused.ndcg_at_10,
520 self.k,
521 scale.fused.evidence_recall_at_k,
522 scale.fused.p95_latency_ms,
523 scale.corpus_hash
524 )?;
525 for (channel, metrics) in &scale.channels {
526 writeln!(
527 f,
528 " {channel}: R@{}={:.3} nDCG@10={:.3} evidence@{}={:.3} p95={:.2}ms",
529 self.k,
530 metrics.recall_at_k,
531 metrics.ndcg_at_10,
532 self.k,
533 metrics.evidence_recall_at_k,
534 metrics.p95_latency_ms
535 )?;
536 }
537 }
538 writeln!(
539 f,
540 "degradation at {}x: R@{} loss={:.3}, nDCG@10 loss={:.3}, evidence@{} loss={:.3}",
541 self.degradation.largest_scale,
542 self.k,
543 self.degradation.fused_recall_at_k_loss,
544 self.degradation.fused_ndcg_at_10_loss,
545 self.k,
546 self.degradation.fused_evidence_recall_at_k_loss
547 )
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use crate::eval::golden::{EvidenceRef, GoldenQuery};
555
556 #[test]
557 fn capacity_synthesis_is_deterministic_for_same_seed_and_scale() -> Result<()> {
558 let dataset = tiny_dataset();
559 let first = synthesize_capacity_dataset(&dataset, 7, 3)?;
560 let second = synthesize_capacity_dataset(&dataset, 7, 3)?;
561 let different_seed = synthesize_capacity_dataset(&dataset, 8, 3)?;
562
563 assert_eq!(first.corpus_hash, second.corpus_hash);
564 assert_eq!(first.noise_count, 4);
565 assert_eq!(first.dataset.corpus.len(), 6);
566 assert_eq!(first.dataset.queries[0].id, dataset.queries[0].id);
567 assert_ne!(first.corpus_hash, different_seed.corpus_hash);
568 Ok(())
569 }
570
571 #[test]
572 fn capacity_report_includes_scale_curve_and_degradation() -> Result<()> {
573 let report = run_capacity_eval_for_dataset(
574 CapacityEvalOptions {
575 dataset_path: "inline-test".to_string(),
576 seed: 9,
577 scales: vec![3, 1],
578 k: 5,
579 },
580 tiny_dataset(),
581 )?;
582
583 assert_eq!(report.scale_factors, vec![1, 3]);
584 assert_eq!(report.base_corpus_size, 2);
585 assert_eq!(report.scales.len(), 2);
586 assert_eq!(report.scales[0].scale, 1);
587 assert_eq!(report.scales[0].noise_count, 0);
588 assert_eq!(report.scales[1].scale, 3);
589 assert_eq!(report.scales[1].noise_count, 4);
590 assert_eq!(report.degradation.largest_scale, 3);
591 assert!(report.scales[0].channels.contains_key("fts"));
592 assert!(report.scales[0].channels.contains_key("vector"));
593 assert!(report.degradation.channels.contains_key("fts"));
594
595 let json = serde_json::to_value(&report)?;
596 assert_eq!(json["seed"], 9);
597 assert_eq!(json["scales"][1]["fused"]["scored_queries"], 2);
598 assert_eq!(json["scales"][1]["channels"]["fts"]["scored_queries"], 2);
599 assert!(json["degradation"]["channels"]["fts"]["recall_at_k_loss"].is_number());
600 assert!(json["omitted_followups"]
601 .as_array()
602 .expect("omitted followups should be an array")
603 .contains(&serde_json::json!("nightly_dashboard_ingestion")));
604 assert!(!json["omitted_followups"]
605 .as_array()
606 .expect("omitted followups should be an array")
607 .contains(&serde_json::json!("per_channel_attribution")));
608 Ok(())
609 }
610
611 #[test]
612 fn capacity_report_quality_is_stable_for_same_seed() -> Result<()> {
613 let dataset = tiny_dataset();
614 let first = run_capacity_eval_for_dataset(
615 CapacityEvalOptions {
616 dataset_path: "inline-test".to_string(),
617 seed: 11,
618 scales: vec![1, 2],
619 k: 5,
620 },
621 dataset.clone(),
622 )?;
623 let second = run_capacity_eval_for_dataset(
624 CapacityEvalOptions {
625 dataset_path: "inline-test".to_string(),
626 seed: 11,
627 scales: vec![1, 2],
628 k: 5,
629 },
630 dataset,
631 )?;
632
633 assert_eq!(quality_signature(&first), quality_signature(&second));
634 Ok(())
635 }
636
637 #[test]
638 fn capacity_scales_require_one_x_baseline() {
639 let err = normalize_scales(vec![2, 3])
640 .expect_err("missing 1x scale should fail")
641 .to_string();
642 assert!(err.contains("requires scale 1"));
643 }
644
645 fn quality_signature(
646 report: &CapacityEvalReport,
647 ) -> Vec<(
648 usize,
649 String,
650 usize,
651 f64,
652 f64,
653 f64,
654 Vec<(String, f64, f64, f64)>,
655 )> {
656 report
657 .scales
658 .iter()
659 .map(|scale| {
660 (
661 scale.scale,
662 scale.corpus_hash.clone(),
663 scale.fused.scored_queries,
664 scale.fused.recall_at_k,
665 scale.fused.ndcg_at_10,
666 scale.fused.evidence_recall_at_k,
667 scale
668 .channels
669 .iter()
670 .map(|(channel, metrics)| {
671 (
672 channel.clone(),
673 metrics.recall_at_k,
674 metrics.ndcg_at_10,
675 metrics.evidence_recall_at_k,
676 )
677 })
678 .collect(),
679 )
680 })
681 .collect()
682 }
683
684 fn tiny_dataset() -> GoldenDataset {
685 GoldenDataset {
686 version: Some("capacity-test".to_string()),
687 description: Some("capacity test fixture".to_string()),
688 corpus: vec![
689 GoldenMemory {
690 project: "synthetic/capacity".to_string(),
691 topic_key: Some("capacity-alpha-anchor".to_string()),
692 title: "Alpha routing anchor".to_string(),
693 content: "Alpha routing anchor lives in src/alpha.rs".to_string(),
694 memory_type: "decision".to_string(),
695 branch: Some("main".to_string()),
696 scope: "project".to_string(),
697 status: "active".to_string(),
698 files: Some("src/alpha.rs".to_string()),
699 created_at_epoch: Some(1_700_000_000),
700 access_count: Some(0),
701 last_accessed_epoch: None,
702 search_context: None,
703 },
704 GoldenMemory {
705 project: "synthetic/capacity".to_string(),
706 topic_key: Some("capacity-beta-anchor".to_string()),
707 title: "Beta retry anchor".to_string(),
708 content: "Beta retry anchor belongs to src/beta.rs".to_string(),
709 memory_type: "bugfix".to_string(),
710 branch: Some("main".to_string()),
711 scope: "project".to_string(),
712 status: "active".to_string(),
713 files: Some("src/beta.rs".to_string()),
714 created_at_epoch: Some(1_700_000_010),
715 access_count: Some(0),
716 last_accessed_epoch: None,
717 search_context: None,
718 },
719 ],
720 queries: vec![
721 GoldenQuery {
722 id: "alpha".to_string(),
723 query: "Alpha routing anchor".to_string(),
724 category: "retrieval".to_string(),
725 slice: Some("capacity_test".to_string()),
726 hop_path: None,
727 project: Some("synthetic/capacity".to_string()),
728 branch: Some("main".to_string()),
729 memory_type: None,
730 relevant_ids: vec![],
731 evidence_refs: vec![EvidenceRef {
732 topic_key: Some("capacity-alpha-anchor".to_string()),
733 ..EvidenceRef::default()
734 }],
735 expect_abstain: false,
736 false_premise: false,
737 notes: None,
738 },
739 GoldenQuery {
740 id: "beta".to_string(),
741 query: "Beta retry anchor".to_string(),
742 category: "retrieval".to_string(),
743 slice: Some("capacity_test".to_string()),
744 hop_path: None,
745 project: Some("synthetic/capacity".to_string()),
746 branch: Some("main".to_string()),
747 memory_type: None,
748 relevant_ids: vec![],
749 evidence_refs: vec![EvidenceRef {
750 topic_key: Some("capacity-beta-anchor".to_string()),
751 ..EvidenceRef::default()
752 }],
753 expect_abstain: false,
754 false_premise: false,
755 notes: None,
756 },
757 ],
758 }
759 }
760}