1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::fmt;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use super::comparator::{sort_r3, CandidateResult, RankedTuple, SymbolOffsetRange};
8use super::evidence_descriptor::{EvidenceDescriptor, EvidenceTier};
9use super::plan_table::SearchLaneKind;
10use super::scoring::{
11 freeze_non_exact_scores, AdmittedContribution, LaneContribution, ScoringError, ScoringPolicy,
12};
13
14pub const BLOCK_DEPTHS: [usize; 5] = [200, 400, 800, 1_600, 3_200];
15pub const MAX_BLOCK_DEPTH: usize = 3_200;
16pub const MAX_PUBLIC_TOP_K: usize = 100;
17pub const MAX_OFFSET: usize = 100_000;
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct CanonicalListKey {
23 pub project_root: PathBuf,
24 pub snapshot_generation: String,
25 pub normalized_query: String,
26 pub include_tests: bool,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct LaneCandidate {
31 pub path: PathBuf,
32 pub symbol_range: Option<SymbolOffsetRange>,
33 pub evidence: EvidenceDescriptor,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub raw_score: Option<f32>,
36 pub is_test: bool,
37}
38
39impl LaneCandidate {
40 pub fn exact(
41 path: impl Into<PathBuf>,
42 symbol_range: Option<SymbolOffsetRange>,
43 evidence: EvidenceDescriptor,
44 is_test: bool,
45 ) -> Self {
46 Self {
47 path: path.into(),
48 symbol_range,
49 evidence,
50 raw_score: None,
51 is_test,
52 }
53 }
54
55 pub fn non_exact(
56 path: impl Into<PathBuf>,
57 symbol_range: Option<SymbolOffsetRange>,
58 evidence: EvidenceDescriptor,
59 raw_score: f32,
60 is_test: bool,
61 ) -> Self {
62 Self {
63 path: path.into(),
64 symbol_range,
65 evidence,
66 raw_score: Some(raw_score),
67 is_test,
68 }
69 }
70
71 fn identity(&self) -> CandidateIdentity {
72 CandidateIdentity {
73 path: self.path.clone(),
74 symbol_range: self.symbol_range,
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct CanonicalLane {
83 pub kind: SearchLaneKind,
84 pub candidates: Vec<LaneCandidate>,
85}
86
87impl CanonicalLane {
88 pub fn new(
89 kind: SearchLaneKind,
90 candidates: Vec<LaneCandidate>,
91 ) -> Result<Self, BlockBuildError> {
92 let mut identities = HashSet::new();
93 for candidate in &candidates {
94 if !candidate.evidence.is_valid_shape() {
95 return Err(BlockBuildError::InvalidEvidence(candidate.path.clone()));
96 }
97 if !identities.insert(candidate.identity()) {
98 return Err(BlockBuildError::DuplicateLaneCandidate {
99 lane: kind,
100 path: candidate.path.clone(),
101 });
102 }
103 match candidate.evidence.tier {
104 EvidenceTier::Exact if candidate.raw_score.is_some() => {
105 return Err(BlockBuildError::InvalidExactCandidate(
106 candidate.path.clone(),
107 ));
108 }
109 EvidenceTier::NonExact
110 if !kind.is_scored()
111 || candidate.raw_score.is_none_or(|score| !score.is_finite()) =>
112 {
113 return Err(BlockBuildError::InvalidDepthLimitedCandidate {
114 lane: kind,
115 path: candidate.path.clone(),
116 });
117 }
118 EvidenceTier::Exact | EvidenceTier::NonExact => {}
119 }
120 }
121 Ok(Self { kind, candidates })
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "snake_case")]
127pub enum ContributionDisposition {
128 Admitted,
129 NotAdmitted,
130 DepthExempt,
131 ProvenanceOnly,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct LaneAttribution {
138 pub lane: SearchLaneKind,
139 pub position: usize,
140 pub disposition: ContributionDisposition,
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct StabilityUnit {
145 pub ranked_tuple: RankedTuple,
146 pub evidence_descriptor: EvidenceDescriptor,
147}
148
149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub struct BlockEntry {
151 pub result: CandidateResult,
152 pub tier_index: usize,
153 pub admitted_contributions: Vec<AdmittedContribution>,
154 pub lane_attribution: Vec<LaneAttribution>,
155 pub r3_order_index: usize,
156}
157
158impl BlockEntry {
159 pub fn stability_unit(&self) -> StabilityUnit {
160 StabilityUnit {
161 ranked_tuple: RankedTuple {
162 file: self.result.path.clone(),
163 symbol_range: self.result.symbol_range,
164 r3_order_index: self.r3_order_index,
165 fusion_score: self.result.fusion_score,
166 lane_score: self.result.lane_score,
167 },
168 evidence_descriptor: self.result.evidence.clone(),
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct FrozenBlock {
175 pub tier_index: usize,
176 pub entries: Vec<BlockEntry>,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
180pub struct CanonicalList {
181 pub key: CanonicalListKey,
182 pub blocks: Vec<FrozenBlock>,
183}
184
185impl CanonicalList {
186 pub fn len(&self) -> usize {
187 self.blocks.iter().map(|block| block.entries.len()).sum()
188 }
189
190 pub fn is_empty(&self) -> bool {
191 self.blocks.iter().all(|block| block.entries.is_empty())
192 }
193
194 pub fn entries(&self) -> impl Iterator<Item = &BlockEntry> {
195 self.blocks.iter().flat_map(|block| block.entries.iter())
196 }
197
198 pub fn block(&self, tier_index: usize) -> Option<&FrozenBlock> {
199 self.blocks
200 .iter()
201 .find(|block| block.tier_index == tier_index)
202 }
203
204 pub fn stability_units(&self) -> Vec<StabilityUnit> {
205 self.entries().map(BlockEntry::stability_unit).collect()
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
210pub struct PageRequest {
211 pub offset: usize,
212 pub top_k: usize,
213}
214
215impl PageRequest {
216 pub fn validate(self) -> Result<Self, BlockBuildError> {
217 if self.offset > MAX_OFFSET {
218 return Err(BlockBuildError::InvalidOffset(self.offset));
219 }
220 if !(1..=MAX_PUBLIC_TOP_K).contains(&self.top_k) {
221 return Err(BlockBuildError::InvalidTopK(self.top_k));
222 }
223 Ok(self)
224 }
225
226 pub fn interval_end(self) -> u64 {
227 (self.offset as u64).saturating_add(self.top_k as u64)
228 }
229
230 pub fn effective_target(self) -> u64 {
231 self.interval_end().max(2)
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236pub struct BlockReply {
237 pub canonical_list: CanonicalList,
238 pub page: Vec<BlockEntry>,
239 pub retrieval_depth: usize,
240 pub depth_tier: usize,
241 pub lanes_exhausted: bool,
242 pub lane_enumeration_counts: BTreeMap<SearchLaneKind, usize>,
243 pub effective_target: u64,
244}
245
246impl BlockReply {
247 pub fn page_stability_units(&self) -> Vec<StabilityUnit> {
248 self.page.iter().map(BlockEntry::stability_unit).collect()
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum BlockBuildError {
254 DuplicateLane(SearchLaneKind),
255 DuplicateLaneCandidate { lane: SearchLaneKind, path: PathBuf },
256 InvalidEvidence(PathBuf),
257 InvalidExactCandidate(PathBuf),
258 InvalidDepthLimitedCandidate { lane: SearchLaneKind, path: PathBuf },
259 ConflictingEvidence(PathBuf),
260 MissingExactEvidence(PathBuf),
261 NonExactEmptyAdmittedSet(PathBuf),
262 InvalidDepth(usize),
263 InvalidOffset(usize),
264 InvalidTopK(usize),
265 Scoring(ScoringError),
266}
267
268impl fmt::Display for BlockBuildError {
269 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
270 match self {
271 Self::DuplicateLane(lane) => {
272 write!(formatter, "lane {lane} was supplied more than once")
273 }
274 Self::DuplicateLaneCandidate { lane, path } => write!(
275 formatter,
276 "lane {lane} produced {} more than once",
277 path.display()
278 ),
279 Self::InvalidEvidence(path) => {
280 write!(
281 formatter,
282 "candidate {} has invalid evidence",
283 path.display()
284 )
285 }
286 Self::InvalidExactCandidate(path) => write!(
287 formatter,
288 "exact candidate {} must carry exact evidence and no score",
289 path.display()
290 ),
291 Self::InvalidDepthLimitedCandidate { lane, path } => write!(
292 formatter,
293 "{lane} candidate {} must carry a finite raw score",
294 path.display()
295 ),
296 Self::ConflictingEvidence(path) => write!(
297 formatter,
298 "depth-limited lanes disagree on evidence for {}",
299 path.display()
300 ),
301 Self::MissingExactEvidence(path) => write!(
302 formatter,
303 "exact-attributed candidate {} has no exact descriptor",
304 path.display()
305 ),
306 Self::NonExactEmptyAdmittedSet(path) => write!(
307 formatter,
308 "non-exact candidate {} has an empty admitted set",
309 path.display()
310 ),
311 Self::InvalidDepth(depth) => {
312 write!(
313 formatter,
314 "block depth {depth} is not one of {BLOCK_DEPTHS:?}"
315 )
316 }
317 Self::InvalidOffset(offset) => {
318 write!(formatter, "offset {offset} exceeds maximum {MAX_OFFSET}")
319 }
320 Self::InvalidTopK(top_k) => write!(
321 formatter,
322 "topK {top_k} is outside the public range 1..={MAX_PUBLIC_TOP_K}"
323 ),
324 Self::Scoring(error) => error.fmt(formatter),
325 }
326 }
327}
328
329impl std::error::Error for BlockBuildError {}
330
331impl From<ScoringError> for BlockBuildError {
332 fn from(error: ScoringError) -> Self {
333 Self::Scoring(error)
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338struct CandidateIdentity {
339 path: PathBuf,
340 symbol_range: Option<SymbolOffsetRange>,
341}
342
343#[derive(Debug, Clone)]
344struct ObservedCandidate<'a> {
345 exact: Vec<(SearchLaneKind, usize, &'a LaneCandidate)>,
346 depth_limited: Vec<LaneContribution>,
347 depth_evidence: Vec<&'a EvidenceDescriptor>,
348}
349
350impl<'a> ObservedCandidate<'a> {
351 fn new() -> Self {
352 Self {
353 exact: Vec::new(),
354 depth_limited: Vec::new(),
355 depth_evidence: Vec::new(),
356 }
357 }
358
359 fn tier_index(&self) -> Option<usize> {
360 if !self.exact.is_empty() {
361 return Some(0);
362 }
363 self.depth_limited
364 .iter()
365 .filter_map(|contribution| tier_for_position(contribution.position))
366 .min()
367 }
368}
369
370#[derive(Debug, Clone)]
372pub struct BlockBuilder {
373 key: CanonicalListKey,
374 policy: ScoringPolicy,
375 lanes: Vec<CanonicalLane>,
376}
377
378impl BlockBuilder {
379 pub fn new(
380 key: CanonicalListKey,
381 policy: ScoringPolicy,
382 lanes_in_completion_order: Vec<CanonicalLane>,
383 ) -> Result<Self, BlockBuildError> {
384 let mut kinds = HashSet::new();
385 for lane in &lanes_in_completion_order {
386 if !kinds.insert(lane.kind) {
387 return Err(BlockBuildError::DuplicateLane(lane.kind));
388 }
389 }
390 Ok(Self {
391 key,
392 policy,
393 lanes: lanes_in_completion_order,
394 })
395 }
396
397 pub fn build_for_request(&self, request: PageRequest) -> Result<BlockReply, BlockBuildError> {
400 let request = request.validate()?;
401 let initial_tier = starting_tier(request.interval_end());
402 self.build(initial_tier, Some(request))
403 }
404
405 pub fn build_at_depth(&self, depth: usize) -> Result<BlockReply, BlockBuildError> {
408 let tier = BLOCK_DEPTHS
409 .iter()
410 .position(|candidate| *candidate == depth)
411 .ok_or(BlockBuildError::InvalidDepth(depth))?;
412 self.build(tier, None)
413 }
414
415 fn build(
416 &self,
417 initial_tier: usize,
418 request: Option<PageRequest>,
419 ) -> Result<BlockReply, BlockBuildError> {
420 let effective_target = request.map_or(0, PageRequest::effective_target);
421 let mut reached_tier = initial_tier;
422 let mut observed = self.observe_through_depth(BLOCK_DEPTHS[reached_tier]);
423 let mut frozen_identities = HashSet::new();
424 let mut blocks = Vec::new();
425 let mut list_len = 0usize;
426
427 for tier_index in 0..=reached_tier {
428 let block =
429 self.freeze_block(tier_index, &observed, &mut frozen_identities, list_len)?;
430 list_len += block.entries.len();
431 blocks.push(block);
432 }
433
434 while request.is_some()
435 && (list_len as u64) < effective_target
436 && self.has_unconsumed_candidates(BLOCK_DEPTHS[reached_tier])
437 && reached_tier + 1 < BLOCK_DEPTHS.len()
438 {
439 reached_tier += 1;
440 observed = self.observe_through_depth(BLOCK_DEPTHS[reached_tier]);
441 let block =
442 self.freeze_block(reached_tier, &observed, &mut frozen_identities, list_len)?;
443 list_len += block.entries.len();
444 blocks.push(block);
445 }
446
447 let reached_depth = BLOCK_DEPTHS[reached_tier];
448 let lanes_exhausted = !self.has_unconsumed_candidates(reached_depth);
449 self.attach_run_scoped_attribution(&mut blocks, &observed, reached_depth);
450
451 let canonical_list = CanonicalList {
452 key: self.key.clone(),
453 blocks,
454 };
455 let page = request.map_or_else(Vec::new, |page_request| {
456 canonical_list
457 .entries()
458 .skip(page_request.offset)
459 .take(page_request.top_k)
460 .cloned()
461 .collect()
462 });
463
464 Ok(BlockReply {
465 canonical_list,
466 page,
467 retrieval_depth: reached_depth,
468 depth_tier: reached_tier,
469 lanes_exhausted,
470 lane_enumeration_counts: self.lane_enumeration_counts(reached_depth),
471 effective_target,
472 })
473 }
474
475 fn observe_through_depth(
476 &self,
477 depth: usize,
478 ) -> HashMap<CandidateIdentity, ObservedCandidate<'_>> {
479 let mut observed = HashMap::new();
480 for lane in &self.lanes {
481 for (position, candidate) in lane.candidates.iter().enumerate() {
482 if !self.key.include_tests && candidate.is_test {
483 continue;
484 }
485 if candidate.evidence.tier == EvidenceTier::NonExact && position >= depth {
486 continue;
487 }
488 let entry = observed
489 .entry(candidate.identity())
490 .or_insert_with(ObservedCandidate::new);
491 if candidate.evidence.tier == EvidenceTier::Exact {
492 entry.exact.push((lane.kind, position, candidate));
493 } else {
494 entry.depth_limited.push(LaneContribution {
495 lane: lane.kind,
496 position,
497 raw_score: candidate.raw_score.expect("lane validated at construction"),
498 });
499 entry.depth_evidence.push(&candidate.evidence);
500 }
501 }
502 }
503 observed
504 }
505
506 fn freeze_block(
507 &self,
508 tier_index: usize,
509 observed: &HashMap<CandidateIdentity, ObservedCandidate<'_>>,
510 frozen_identities: &mut HashSet<CandidateIdentity>,
511 block_start: usize,
512 ) -> Result<FrozenBlock, BlockBuildError> {
513 let tier_depth = BLOCK_DEPTHS[tier_index];
514 let mut pending = Vec::new();
515
516 for (identity, candidate) in observed {
517 if frozen_identities.contains(identity) || candidate.tier_index() != Some(tier_index) {
518 continue;
519 }
520
521 let (result, admitted_contributions) = if !candidate.exact.is_empty() {
522 let exact_evidence = candidate
523 .exact
524 .iter()
525 .map(|(_, _, exact)| &exact.evidence)
526 .min_by(|left, right| {
527 exact_evidence_rank(left).cmp(&exact_evidence_rank(right))
528 })
529 .ok_or_else(|| BlockBuildError::MissingExactEvidence(identity.path.clone()))?;
530 (
531 CandidateResult::new_exact(
532 identity.path.clone(),
533 identity.symbol_range,
534 exact_evidence.clone(),
535 ),
536 Vec::new(),
537 )
538 } else {
539 let evidence = candidate.depth_evidence.first().ok_or_else(|| {
540 BlockBuildError::NonExactEmptyAdmittedSet(identity.path.clone())
541 })?;
542 if candidate
543 .depth_evidence
544 .iter()
545 .any(|other| *other != *evidence)
546 {
547 return Err(BlockBuildError::ConflictingEvidence(identity.path.clone()));
548 }
549 let scores =
550 freeze_non_exact_scores(&candidate.depth_limited, tier_depth, &self.policy)
551 .map_err(|error| match error {
552 ScoringError::EmptyAdmittedSet => {
553 BlockBuildError::NonExactEmptyAdmittedSet(identity.path.clone())
554 }
555 other => BlockBuildError::Scoring(other),
556 })?;
557 (
558 CandidateResult::new_non_exact(
559 identity.path.clone(),
560 identity.symbol_range,
561 (*evidence).clone(),
562 scores.fusion_score,
563 scores.lane_score,
564 scores.best_lane,
565 ),
566 scores.admitted,
567 )
568 };
569
570 pending.push(BlockEntry {
571 result,
572 tier_index,
573 admitted_contributions,
574 lane_attribution: Vec::new(),
575 r3_order_index: 0,
576 });
577 frozen_identities.insert(identity.clone());
578 }
579
580 let mut results: Vec<_> = pending.iter().map(|entry| entry.result.clone()).collect();
581 sort_r3(&mut results);
582 let mut by_identity: HashMap<_, _> = pending
583 .drain(..)
584 .map(|entry| (identity_of_result(&entry.result), entry))
585 .collect();
586 let mut entries = Vec::with_capacity(results.len());
587 for (within_block, result) in results.into_iter().enumerate() {
588 let identity = identity_of_result(&result);
589 let mut entry = by_identity
590 .remove(&identity)
591 .expect("R3 sorting preserves every deduplicated candidate");
592 entry.result = result;
593 entry.r3_order_index = block_start + within_block;
594 entries.push(entry);
595 }
596
597 Ok(FrozenBlock {
598 tier_index,
599 entries,
600 })
601 }
602
603 fn attach_run_scoped_attribution(
604 &self,
605 blocks: &mut [FrozenBlock],
606 observed: &HashMap<CandidateIdentity, ObservedCandidate<'_>>,
607 reached_depth: usize,
608 ) {
609 for entry in blocks.iter_mut().flat_map(|block| block.entries.iter_mut()) {
610 let identity = identity_of_result(&entry.result);
611 let Some(candidate) = observed.get(&identity) else {
612 continue;
613 };
614 let mut attribution = Vec::new();
615 if entry.result.evidence.tier == EvidenceTier::Exact {
616 attribution.extend(candidate.exact.iter().map(|(lane, position, _)| {
617 LaneAttribution {
618 lane: *lane,
619 position: *position,
620 disposition: ContributionDisposition::DepthExempt,
621 }
622 }));
623 }
624 for contribution in &candidate.depth_limited {
625 if contribution.position >= reached_depth {
626 continue;
627 }
628 let disposition = if entry.result.evidence.tier == EvidenceTier::Exact {
629 ContributionDisposition::ProvenanceOnly
630 } else if contribution.position < BLOCK_DEPTHS[entry.tier_index] {
631 ContributionDisposition::Admitted
632 } else {
633 ContributionDisposition::NotAdmitted
634 };
635 attribution.push(LaneAttribution {
636 lane: contribution.lane,
637 position: contribution.position,
638 disposition,
639 });
640 }
641 attribution.sort_by_key(|value| value.lane.default_plan_order_index());
642 entry.lane_attribution = attribution;
643 }
644 }
645
646 fn has_unconsumed_candidates(&self, depth: usize) -> bool {
647 self.lanes.iter().any(|lane| {
648 lane.candidates
649 .iter()
650 .enumerate()
651 .skip(depth.min(lane.candidates.len()))
652 .any(|(_, candidate)| {
653 candidate.evidence.tier == EvidenceTier::NonExact
654 && (self.key.include_tests || !candidate.is_test)
655 })
656 })
657 }
658
659 fn lane_enumeration_counts(&self, depth: usize) -> BTreeMap<SearchLaneKind, usize> {
660 self.lanes
661 .iter()
662 .map(|lane| {
663 let count = if lane
664 .candidates
665 .iter()
666 .all(|candidate| candidate.evidence.tier == EvidenceTier::Exact)
667 {
668 lane.candidates.len()
669 } else {
670 lane.candidates.len().min(depth)
671 };
672 (lane.kind, count)
673 })
674 .collect()
675 }
676}
677
678pub fn tier_for_position(position: usize) -> Option<usize> {
679 BLOCK_DEPTHS.iter().position(|depth| position < *depth)
680}
681
682fn starting_tier(interval_end: u64) -> usize {
683 BLOCK_DEPTHS
684 .iter()
685 .position(|depth| (*depth as u64) >= interval_end)
686 .unwrap_or(BLOCK_DEPTHS.len() - 1)
687}
688
689fn identity_of_result(result: &CandidateResult) -> CandidateIdentity {
690 CandidateIdentity {
691 path: result.path.clone(),
692 symbol_range: result.symbol_range,
693 }
694}
695
696fn exact_evidence_rank(evidence: &EvidenceDescriptor) -> (usize, usize, usize, usize) {
697 use super::evidence_descriptor::EvidenceKind;
698
699 let kind = match evidence.kind {
700 EvidenceKind::Definition => 0,
701 EvidenceKind::E1 => 1,
702 EvidenceKind::Anchored => 2,
703 EvidenceKind::E2 => 3,
704 EvidenceKind::None => usize::MAX,
705 };
706 (
707 kind,
708 usize::MAX - evidence.occurrences.unwrap_or(0),
709 usize::MAX - evidence.matched_span.unwrap_or(0),
710 evidence.window_lines.unwrap_or(usize::MAX),
711 )
712}