use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::time::Duration;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::models::CorpusRevision;
pub const CASS_PREFETCH_DECISION_SCHEMA_V1: &str = "ee.cass_prefetch.decision.v1";
pub const CASS_PREFETCH_METRICS_SCHEMA_V1: &str = "ee.cass_prefetch.metrics.v1";
pub const CASS_PREFETCH_BUDGET_EXCEEDED_CODE: &str = "cass_prefetch_budget_exceeded";
pub const ADAPTIVE_BACKOFF_APPLIED_CODE: &str = "adaptive_backoff_applied";
pub const DEFAULT_PREFETCH_TOP_K: usize = 3;
pub const DEFAULT_PREFETCH_HISTORY_WINDOW: usize = 10;
pub const DEFAULT_PREFETCH_RECENCY_HALF_LIFE: f64 = 3.0;
pub const DEFAULT_PREFETCH_MIN_SCORE: f64 = 0.10;
pub const DEFAULT_PREFETCH_BUDGET: Duration = Duration::from_millis(50);
pub const MAX_PREFETCH_HISTORY: usize = 64;
pub const MAX_PREFETCH_TOPIC_ID_BYTES: usize = 256;
pub const CASS_PREFETCH_HISTORY_OVERSIZED_CODE: &str = "cass_prefetch_history_oversized";
pub const CASS_PREFETCH_STALE_GENERATION_CODE: &str = "cass_prefetch_stale_generation";
pub const CASS_PREFETCH_STALE_CORPUS_REVISION_CODE: &str = "cass_prefetch_stale_corpus_revision";
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrefetchGeneration {
pub workspace_generation: u64,
pub index_generation: u64,
}
impl PrefetchGeneration {
#[must_use]
pub const fn new(workspace_generation: u64, index_generation: u64) -> Self {
Self {
workspace_generation,
index_generation,
}
}
#[must_use]
pub const fn is_coherent_with(self, current: Self) -> bool {
self.workspace_generation == current.workspace_generation
&& self.index_generation == current.index_generation
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(from = "String", into = "String")]
pub struct TopicId(String);
impl TopicId {
#[must_use]
pub fn new(raw: impl AsRef<str>) -> Self {
Self(crate::policy::redact_secret_like_content(raw.as_ref()).content)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl From<&str> for TopicId {
fn from(raw: &str) -> Self {
Self::new(raw)
}
}
impl From<String> for TopicId {
fn from(raw: String) -> Self {
Self::new(raw)
}
}
impl From<TopicId> for String {
fn from(topic_id: TopicId) -> Self {
topic_id.0
}
}
impl std::fmt::Display for TopicId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
impl PartialEq<str> for TopicId {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for TopicId {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CassPrefetchObservation {
pub topic_id: TopicId,
#[serde(default)]
pub corpus_revision: CorpusRevision,
}
impl CassPrefetchObservation {
#[must_use]
pub fn new(topic_id: impl Into<String>) -> Self {
Self {
topic_id: TopicId::from(topic_id.into()),
corpus_revision: CorpusRevision::unknown(),
}
}
#[must_use]
pub fn with_corpus_revision(mut self, corpus_revision: CorpusRevision) -> Self {
self.corpus_revision = corpus_revision;
self
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(from = "String", into = "String")]
pub struct AgentScope(String);
impl AgentScope {
pub const UNKNOWN: &'static str = "agent:unknown";
#[must_use]
pub fn new(raw: impl Into<String>) -> Self {
let raw = raw.into();
let trimmed = raw.trim();
if trimmed.is_empty() {
Self::unknown()
} else {
Self(trimmed.to_owned())
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn unknown() -> Self {
Self(Self::UNKNOWN.to_owned())
}
#[must_use]
pub fn is_unknown(&self) -> bool {
self.0 == Self::UNKNOWN
}
}
impl From<&str> for AgentScope {
fn from(raw: &str) -> Self {
Self::new(raw)
}
}
impl From<String> for AgentScope {
fn from(raw: String) -> Self {
Self::new(raw)
}
}
impl From<AgentScope> for String {
fn from(scope: AgentScope) -> Self {
scope.0
}
}
impl std::fmt::Display for AgentScope {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
impl PartialEq<str> for AgentScope {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for AgentScope {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CassPrefetchHistory {
pub agent_scope: AgentScope,
#[serde(default)]
pub generation: PrefetchGeneration,
pub recent_first: Vec<CassPrefetchObservation>,
}
impl CassPrefetchHistory {
#[must_use]
pub fn new(
agent_scope: impl Into<AgentScope>,
recent_first: Vec<CassPrefetchObservation>,
) -> Self {
Self {
agent_scope: agent_scope.into(),
generation: PrefetchGeneration::default(),
recent_first,
}
}
#[must_use]
pub fn from_topics<I, S>(agent_scope: impl Into<AgentScope>, recent_first_topics: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
agent_scope: agent_scope.into(),
generation: PrefetchGeneration::default(),
recent_first: recent_first_topics
.into_iter()
.map(CassPrefetchObservation::new)
.collect(),
}
}
#[must_use]
pub fn try_from_topics<I, S>(
agent_scope: impl Into<AgentScope>,
recent_first_topics: I,
) -> Option<Self>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut recent_first = Vec::new();
for topic in recent_first_topics {
if recent_first.len() >= MAX_PREFETCH_HISTORY {
return None;
}
let raw_topic = topic.into();
if raw_topic.len() > MAX_PREFETCH_TOPIC_ID_BYTES {
return None;
}
let observation = CassPrefetchObservation::new(raw_topic);
if observation.topic_id.as_str().len() > MAX_PREFETCH_TOPIC_ID_BYTES {
return None;
}
recent_first.push(observation);
}
Some(Self {
agent_scope: agent_scope.into(),
generation: PrefetchGeneration::default(),
recent_first,
})
}
#[must_use]
pub fn with_generation(mut self, generation: PrefetchGeneration) -> Self {
self.generation = generation;
self
}
#[must_use]
pub fn with_corpus_revision(mut self, corpus_revision: CorpusRevision) -> Self {
for observation in &mut self.recent_first {
observation.corpus_revision = corpus_revision.clone();
}
self
}
#[must_use]
pub fn corpus_revision_is_coherent_with(&self, current: &CorpusRevision) -> bool {
self.recent_first.is_empty()
|| self
.recent_first
.iter()
.all(|observation| observation.corpus_revision.is_coherent_with(current))
}
#[must_use]
pub fn is_within_admission_bounds(&self) -> bool {
self.recent_first.len() <= MAX_PREFETCH_HISTORY
&& self.recent_first.iter().all(|observation| {
observation.topic_id.as_str().len() <= MAX_PREFETCH_TOPIC_ID_BYTES
})
}
#[must_use]
pub fn len(&self) -> usize {
self.recent_first.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.recent_first.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<'_, CassPrefetchObservation> {
self.recent_first.iter()
}
}
#[derive(Clone, Debug)]
pub struct CassPrefetchHistoryStore {
window: usize,
histories: BTreeMap<(AgentScope, String), CassPrefetchHistory>,
}
impl CassPrefetchHistoryStore {
#[must_use]
pub fn new(window: usize) -> Self {
Self {
window: window.clamp(1, MAX_PREFETCH_HISTORY),
histories: BTreeMap::new(),
}
}
pub fn observe(
&mut self,
agent_scope: impl Into<AgentScope>,
workspace: impl Into<String>,
topic: impl Into<String>,
generation: PrefetchGeneration,
corpus_revision: &CorpusRevision,
) {
let scope = agent_scope.into();
let entry = self
.histories
.entry((scope.clone(), workspace.into()))
.or_insert_with(|| CassPrefetchHistory::new(scope, Vec::new()));
entry.recent_first.insert(
0,
CassPrefetchObservation::new(topic).with_corpus_revision(corpus_revision.clone()),
);
entry.recent_first.truncate(self.window);
entry.generation = generation;
}
#[must_use]
pub fn history_for(
&self,
agent_scope: &AgentScope,
workspace: &str,
) -> Option<&CassPrefetchHistory> {
self.histories
.get(&(agent_scope.clone(), workspace.to_owned()))
}
#[must_use]
pub fn len(&self) -> usize {
self.histories.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.histories.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CassPrefetchCandidate {
pub topic_id: TopicId,
#[serde(
deserialize_with = "deserialize_candidate_score",
serialize_with = "serialize_candidate_score"
)]
pub score: f64,
#[serde(deserialize_with = "deserialize_predictor_cow")]
pub predictor: Cow<'static, str>,
}
impl CassPrefetchCandidate {
#[must_use]
pub fn new(
topic_id: impl Into<TopicId>,
score: f64,
predictor: impl Into<Cow<'static, str>>,
) -> Self {
Self {
topic_id: topic_id.into(),
score: normalize_candidate_score(score),
predictor: predictor.into(),
}
}
}
fn normalize_candidate_score(score: f64) -> f64 {
if score.is_finite() {
score.clamp(0.0, 1.0)
} else {
0.0
}
}
fn deserialize_candidate_score<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
let score = f64::deserialize(deserializer)?;
Ok(normalize_candidate_score(score))
}
fn serialize_candidate_score<S>(score: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_f64(normalize_candidate_score(*score))
}
fn deserialize_predictor_cow<'de, D>(deserializer: D) -> Result<Cow<'static, str>, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer).map(Cow::Owned)
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GatedPrediction {
pub candidates: Vec<CassPrefetchCandidate>,
pub degraded: Option<&'static str>,
}
pub trait SpeculativePrefetch {
fn name(&self) -> &'static str;
fn predict_next_n(
&self,
history: &CassPrefetchHistory,
top_k: usize,
) -> Vec<CassPrefetchCandidate>;
fn predict_next_n_gated(
&self,
history: &CassPrefetchHistory,
current_generation: PrefetchGeneration,
top_k: usize,
) -> GatedPrediction {
if !history.is_within_admission_bounds() {
return GatedPrediction {
candidates: Vec::new(),
degraded: Some(CASS_PREFETCH_HISTORY_OVERSIZED_CODE),
};
}
if !history.generation.is_coherent_with(current_generation) {
return GatedPrediction {
candidates: Vec::new(),
degraded: Some(CASS_PREFETCH_STALE_GENERATION_CODE),
};
}
GatedPrediction {
candidates: self.predict_next_n(history, top_k),
degraded: None,
}
}
fn predict_next_n_gated_for_revision(
&self,
history: &CassPrefetchHistory,
current_generation: PrefetchGeneration,
current_corpus_revision: &CorpusRevision,
top_k: usize,
) -> GatedPrediction {
if !history.is_within_admission_bounds() {
return GatedPrediction {
candidates: Vec::new(),
degraded: Some(CASS_PREFETCH_HISTORY_OVERSIZED_CODE),
};
}
if !history.generation.is_coherent_with(current_generation) {
return GatedPrediction {
candidates: Vec::new(),
degraded: Some(CASS_PREFETCH_STALE_GENERATION_CODE),
};
}
if !history.corpus_revision_is_coherent_with(current_corpus_revision) {
return GatedPrediction {
candidates: Vec::new(),
degraded: Some(CASS_PREFETCH_STALE_CORPUS_REVISION_CODE),
};
}
GatedPrediction {
candidates: self.predict_next_n(history, top_k),
degraded: None,
}
}
}
#[derive(Clone, Debug)]
pub struct RecencyWeightedFrequencyPredictor {
half_life: f64,
min_score: f64,
}
impl Default for RecencyWeightedFrequencyPredictor {
fn default() -> Self {
Self::new()
}
}
impl RecencyWeightedFrequencyPredictor {
#[must_use]
pub const fn new() -> Self {
Self {
half_life: DEFAULT_PREFETCH_RECENCY_HALF_LIFE,
min_score: DEFAULT_PREFETCH_MIN_SCORE,
}
}
#[must_use]
pub fn with_half_life(mut self, half_life: f64) -> Self {
if half_life.is_finite() && half_life > 0.0 {
self.half_life = half_life;
}
self
}
#[must_use]
pub fn with_min_score(mut self, min_score: f64) -> Self {
if min_score.is_finite() {
self.min_score = min_score.clamp(0.0, 1.0);
}
self
}
fn recency_weight(&self, position: usize) -> f64 {
let exponent = position as f64 / self.half_life;
deterministic_pow_half(exponent)
}
}
fn deterministic_pow_half(exponent: f64) -> f64 {
if exponent.is_nan() {
return 0.0;
}
if exponent <= 0.0 {
return 1.0;
}
if exponent >= 64.0 {
return 0.0;
}
let k = exponent.floor();
let frac = exponent - k;
let mut pow2_neg_k = 1.0_f64;
let mut steps = k as u32;
while steps > 0 {
pow2_neg_k *= 0.5;
steps -= 1;
}
pow2_neg_k * exp2_neg_unit_interval(frac)
}
fn exp2_neg_unit_interval(t: f64) -> f64 {
const C0: f64 = 1.0;
const C1: f64 = -0.6931471805599453;
const C2: f64 = 0.2402265069591007;
const C3: f64 = -0.055504108664821576;
const C4: f64 = 0.009618129107628477;
const C5: f64 = -0.0013333558146428441;
const C6: f64 = 0.00015403530393381606;
const C7: f64 = -1.5252733804059838e-05;
const C8: f64 = 1.3215486790144305e-06;
const C9: f64 = -1.0178086009239696e-07;
const C10: f64 = 7.054911620801121e-09;
let mut acc = C10;
acc = acc * t + C9;
acc = acc * t + C8;
acc = acc * t + C7;
acc = acc * t + C6;
acc = acc * t + C5;
acc = acc * t + C4;
acc = acc * t + C3;
acc = acc * t + C2;
acc = acc * t + C1;
acc = acc * t + C0;
acc
}
impl SpeculativePrefetch for RecencyWeightedFrequencyPredictor {
fn name(&self) -> &'static str {
"recency_weighted_frequency_v1"
}
fn predict_next_n(
&self,
history: &CassPrefetchHistory,
top_k: usize,
) -> Vec<CassPrefetchCandidate> {
if top_k == 0 || history.is_empty() || !history.is_within_admission_bounds() {
return Vec::new();
}
let most_recent_topic = history.recent_first.iter().find_map(|observation| {
let topic = observation.topic_id.as_str();
(!topic.is_empty()).then_some(topic)
});
let mut accumulator: HashMap<TopicId, f64> =
HashMap::with_capacity(history.recent_first.len().min(MAX_PREFETCH_HISTORY));
let mut candidate_total_weight: f64 = 0.0;
for (position, observation) in history.recent_first.iter().enumerate() {
let topic_id = observation.topic_id.as_str();
if topic_id.is_empty() {
continue;
}
let weight = self.recency_weight(position);
if !weight.is_finite() || weight < 0.0 {
continue;
}
if Some(topic_id) == most_recent_topic {
continue;
}
candidate_total_weight += weight;
if let Some(existing_weight) = accumulator.get_mut(&observation.topic_id) {
*existing_weight += weight;
} else {
accumulator.insert(observation.topic_id.clone(), weight);
}
}
if candidate_total_weight <= 0.0 || !candidate_total_weight.is_finite() {
return Vec::new();
}
let mut scored: Vec<CassPrefetchCandidate> = accumulator
.into_iter()
.map(|(topic_id, weighted_sum)| {
let normalized = (weighted_sum / candidate_total_weight).clamp(0.0, 1.0);
CassPrefetchCandidate::new(topic_id, normalized, self.name())
})
.filter(|candidate| {
candidate.score.is_finite()
&& candidate.score >= self.min_score
&& candidate.score >= 0.0
})
.collect();
sort_prefetch_candidates_deterministically(&mut scored);
scored.truncate(top_k);
scored
}
}
fn sort_prefetch_candidates_deterministically(scored: &mut [CassPrefetchCandidate]) {
scored.sort_by(|left, right| {
right
.score
.total_cmp(&left.score)
.then_with(|| left.topic_id.cmp(&right.topic_id))
});
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CassPrefetchMetrics {
#[serde(default)]
pub measured_against_revision: CorpusRevision,
pub hits: u64,
pub misses: u64,
pub candidates_emitted: u64,
pub budget_exceeded: u64,
pub history_too_short: u64,
#[serde(default)]
pub stale_generation_drop: u64,
#[serde(default)]
pub history_oversized: u64,
#[serde(default)]
pub stale_corpus_revision_drop: u64,
}
impl CassPrefetchMetrics {
#[must_use]
pub fn new() -> Self {
Self {
measured_against_revision: CorpusRevision::unknown(),
hits: 0,
misses: 0,
candidates_emitted: 0,
budget_exceeded: 0,
history_too_short: 0,
stale_generation_drop: 0,
history_oversized: 0,
stale_corpus_revision_drop: 0,
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
pub fn record_hit(&mut self) {
self.hits = self.hits.saturating_add(1);
}
pub fn record_miss(&mut self) {
self.misses = self.misses.saturating_add(1);
}
pub fn record_candidate(&mut self) {
self.candidates_emitted = self.candidates_emitted.saturating_add(1);
}
pub fn record_budget_exceeded(&mut self) {
self.budget_exceeded = self.budget_exceeded.saturating_add(1);
}
pub fn record_history_too_short(&mut self) {
self.history_too_short = self.history_too_short.saturating_add(1);
}
pub fn record_stale_generation_drop(&mut self) {
self.stale_generation_drop = self.stale_generation_drop.saturating_add(1);
}
pub fn record_history_oversized(&mut self) {
self.history_oversized = self.history_oversized.saturating_add(1);
}
pub fn record_stale_corpus_revision_drop(&mut self) {
self.stale_corpus_revision_drop = self.stale_corpus_revision_drop.saturating_add(1);
}
pub fn set_measured_against_revision(&mut self, revision: CorpusRevision) {
self.measured_against_revision = revision;
}
#[must_use]
pub fn hit_rate(&self) -> f64 {
let hits = self.hits as f64;
let misses = self.misses as f64;
let attempts = hits + misses;
if attempts == 0.0 || !attempts.is_finite() {
0.0
} else {
(hits / attempts).clamp(0.0, 1.0)
}
}
#[must_use]
pub const fn attempts(&self) -> u64 {
self.hits.saturating_add(self.misses)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CassPrefetchWorkspaceMetrics {
workspaces: BTreeMap<String, CassPrefetchMetrics>,
}
impl CassPrefetchWorkspaceMetrics {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn for_workspace_mut(
&mut self,
workspace_id: impl Into<String>,
) -> &mut CassPrefetchMetrics {
self.workspaces.entry(workspace_id.into()).or_default()
}
#[must_use]
pub fn for_workspace(&self, workspace_id: &str) -> Option<&CassPrefetchMetrics> {
self.workspaces.get(workspace_id)
}
pub fn reset_workspace(&mut self, workspace_id: &str) -> bool {
if let Some(metrics) = self.workspaces.get_mut(workspace_id) {
metrics.reset();
true
} else {
false
}
}
#[must_use]
pub fn snapshot(&self) -> &BTreeMap<String, CassPrefetchMetrics> {
&self.workspaces
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.workspaces.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.workspaces.len()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WarmFetchOutcome {
Hit,
Miss,
}
#[derive(Clone, Debug)]
pub struct CassPrefetchCoordinator<P: SpeculativePrefetch> {
predictor: P,
histories: CassPrefetchHistoryStore,
metrics: CassPrefetchWorkspaceMetrics,
top_k: usize,
budget_per_slot: Duration,
}
impl Default for CassPrefetchCoordinator<RecencyWeightedFrequencyPredictor> {
fn default() -> Self {
Self::new()
}
}
impl CassPrefetchCoordinator<RecencyWeightedFrequencyPredictor> {
#[must_use]
pub fn new() -> Self {
Self::with_predictor(RecencyWeightedFrequencyPredictor::new())
}
}
impl<P: SpeculativePrefetch> CassPrefetchCoordinator<P> {
#[must_use]
pub fn with_predictor(predictor: P) -> Self {
Self {
predictor,
histories: CassPrefetchHistoryStore::new(DEFAULT_PREFETCH_HISTORY_WINDOW),
metrics: CassPrefetchWorkspaceMetrics::new(),
top_k: DEFAULT_PREFETCH_TOP_K,
budget_per_slot: DEFAULT_PREFETCH_BUDGET,
}
}
#[must_use]
pub fn with_top_k(mut self, top_k: usize) -> Self {
self.top_k = top_k.clamp(1, MAX_PREFETCH_HISTORY);
self
}
#[must_use]
pub const fn with_budget_per_slot(mut self, budget: Duration) -> Self {
self.budget_per_slot = budget;
self
}
#[must_use]
pub fn with_history_window(mut self, window: usize) -> Self {
self.histories = CassPrefetchHistoryStore::new(window);
self
}
pub fn observe(
&mut self,
agent_scope: impl Into<AgentScope>,
workspace: impl Into<String>,
topic: impl Into<String>,
generation: PrefetchGeneration,
corpus_revision: &CorpusRevision,
) {
self.histories
.observe(agent_scope, workspace, topic, generation, corpus_revision);
}
pub fn schedule(
&mut self,
agent_scope: &AgentScope,
workspace: &str,
current_generation: PrefetchGeneration,
current_corpus_revision: &CorpusRevision,
) -> GatedPrediction {
let metrics = self.metrics.for_workspace_mut(workspace);
let Some(history) = self.histories.history_for(agent_scope, workspace) else {
metrics.record_history_too_short();
return GatedPrediction::default();
};
let prediction = self.predictor.predict_next_n_gated_for_revision(
history,
current_generation,
current_corpus_revision,
self.top_k,
);
match prediction.degraded {
Some(CASS_PREFETCH_HISTORY_OVERSIZED_CODE) => metrics.record_history_oversized(),
Some(CASS_PREFETCH_STALE_GENERATION_CODE) => metrics.record_stale_generation_drop(),
Some(CASS_PREFETCH_STALE_CORPUS_REVISION_CODE) => {
metrics.record_stale_corpus_revision_drop();
}
Some(_) => {}
None if prediction.candidates.is_empty() => metrics.record_history_too_short(),
None => {
for _ in &prediction.candidates {
metrics.record_candidate();
}
metrics.set_measured_against_revision(current_corpus_revision.clone());
}
}
prediction
}
pub fn record_warm_fetch(
&mut self,
workspace: &str,
spent: Duration,
outcome: WarmFetchOutcome,
) -> Option<&'static str> {
let metrics = self.metrics.for_workspace_mut(workspace);
if spent > self.budget_per_slot {
metrics.record_budget_exceeded();
return Some(CASS_PREFETCH_BUDGET_EXCEEDED_CODE);
}
match outcome {
WarmFetchOutcome::Hit => metrics.record_hit(),
WarmFetchOutcome::Miss => metrics.record_miss(),
}
None
}
#[must_use]
pub fn metrics_for(&self, workspace: &str) -> Option<&CassPrefetchMetrics> {
self.metrics.for_workspace(workspace)
}
#[must_use]
pub const fn metrics(&self) -> &CassPrefetchWorkspaceMetrics {
&self.metrics
}
#[must_use]
pub fn history_for(
&self,
agent_scope: &AgentScope,
workspace: &str,
) -> Option<&CassPrefetchHistory> {
self.histories.history_for(agent_scope, workspace)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_AGENT_SCOPE: &str = "agent:test";
fn test_agent_scope() -> AgentScope {
AgentScope::new(TEST_AGENT_SCOPE)
}
fn history(topics_recent_first: &[&str]) -> CassPrefetchHistory {
CassPrefetchHistory::from_topics(test_agent_scope(), topics_recent_first.iter().copied())
}
#[test]
fn empty_history_predicts_nothing() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&[]);
let predictions = predictor.predict_next_n(&h, 3);
assert!(predictions.is_empty());
}
#[test]
fn top_k_zero_predicts_nothing() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["refactor", "debug", "refactor"]);
let predictions = predictor.predict_next_n(&h, 0);
assert!(predictions.is_empty());
}
#[test]
fn most_recent_topic_is_not_a_candidate() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["refactor", "debug"]);
let predictions = predictor.predict_next_n(&h, 3);
assert_eq!(predictions.len(), 1);
assert_eq!(predictions[0].topic_id, "debug");
}
#[test]
fn predictions_are_capped_to_top_k() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&[
"current_query",
"alpha",
"bravo",
"charlie",
"delta",
"echo",
]);
let predictions = predictor.predict_next_n(&h, 2);
assert_eq!(predictions.len(), 2);
let predictions = predictor.predict_next_n(&h, 100);
assert_eq!(predictions.len(), 5);
}
#[test]
fn predictions_are_deterministic_under_recency_scores() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["current", "zeta", "alpha"]);
let predictions = predictor.predict_next_n(&h, 3);
assert_eq!(predictions.len(), 2);
assert_eq!(predictions[0].topic_id, "zeta");
assert_eq!(predictions[1].topic_id, "alpha");
}
#[test]
fn equal_scores_tie_break_by_topic_id() {
let mut candidates = vec![
CassPrefetchCandidate::new("zeta", 0.5, "test_predictor"),
CassPrefetchCandidate::new("alpha", 0.5, "test_predictor"),
];
sort_prefetch_candidates_deterministically(&mut candidates);
assert_eq!(candidates[0].topic_id, "alpha");
assert_eq!(candidates[1].topic_id, "zeta");
}
#[test]
fn higher_frequency_outranks_single_recent() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["current", "bravo", "alpha", "alpha", "alpha"]);
let predictions = predictor.predict_next_n(&h, 5);
assert_eq!(predictions[0].topic_id, "alpha");
assert_eq!(predictions[1].topic_id, "bravo");
assert!(predictions[0].score > predictions[1].score);
}
#[test]
fn scores_are_finite_and_normalized() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["current", "alpha", "bravo", "alpha", "charlie"]);
for candidate in predictor.predict_next_n(&h, 10) {
assert!(candidate.score.is_finite());
assert!(candidate.score >= 0.0);
assert!(candidate.score <= 1.0);
}
}
#[test]
fn min_score_threshold_drops_low_confidence_candidates() {
let predictor = RecencyWeightedFrequencyPredictor::new().with_min_score(0.6);
let h = history(&["current", "alpha", "alpha", "alpha", "noise", "noise"]);
let predictions = predictor.predict_next_n(&h, 10);
assert!(
predictions.iter().any(|c| c.topic_id == "alpha"),
"alpha must survive 0.6 threshold; got {predictions:?}"
);
assert!(
!predictions.iter().any(|c| c.topic_id == "noise"),
"noise must be dropped by 0.6 threshold; got {predictions:?}"
);
}
#[test]
fn predictor_is_pure_function_of_input() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&["current", "alpha", "bravo", "alpha"]);
let first = predictor.predict_next_n(&h, 5);
let second = predictor.predict_next_n(&h, 5);
assert_eq!(first, second);
}
#[test]
fn default_predictor_matches_new_bd_8rdn7() {
let h = history(&["current", "alpha", "bravo", "alpha"]);
let from_default = RecencyWeightedFrequencyPredictor::default().predict_next_n(&h, 5);
let from_new = RecencyWeightedFrequencyPredictor::new().predict_next_n(&h, 5);
assert!(
!from_default.is_empty(),
"Default must remain a functional predictor"
);
assert_eq!(from_default, from_new);
}
#[test]
fn non_finite_overrides_fall_back_to_defaults() {
let predictor = RecencyWeightedFrequencyPredictor::new()
.with_half_life(f64::NAN)
.with_min_score(f64::NAN);
let h = history(&["current", "alpha", "alpha"]);
let predictions = predictor.predict_next_n(&h, 3);
assert!(predictions.iter().all(|c| c.score.is_finite()));
assert!(predictions.iter().any(|c| c.topic_id == "alpha"));
}
#[test]
fn metrics_record_and_compute_hit_rate() {
let mut metrics = CassPrefetchMetrics::new();
assert_eq!(metrics.attempts(), 0);
assert_eq!(metrics.hit_rate(), 0.0);
metrics.record_hit();
metrics.record_hit();
metrics.record_miss();
metrics.record_candidate();
metrics.record_candidate();
metrics.record_candidate();
metrics.record_budget_exceeded();
metrics.record_history_too_short();
assert_eq!(metrics.hits, 2);
assert_eq!(metrics.misses, 1);
assert_eq!(metrics.attempts(), 3);
assert!((metrics.hit_rate() - (2.0 / 3.0)).abs() < 1e-12);
assert_eq!(metrics.candidates_emitted, 3);
assert_eq!(metrics.budget_exceeded, 1);
assert_eq!(metrics.history_too_short, 1);
}
#[test]
fn metrics_reset_clears_all_counters() {
let mut metrics = CassPrefetchMetrics::new();
metrics.record_hit();
metrics.record_miss();
metrics.record_candidate();
metrics.record_budget_exceeded();
metrics.record_history_too_short();
metrics.record_stale_generation_drop();
metrics.record_history_oversized();
metrics.record_stale_corpus_revision_drop();
assert_ne!(metrics, CassPrefetchMetrics::new());
metrics.reset();
assert_eq!(metrics, CassPrefetchMetrics::new());
}
#[test]
fn metrics_saturate_on_overflow() {
let mut metrics = CassPrefetchMetrics::new();
metrics.hits = u64::MAX;
metrics.record_hit();
assert_eq!(metrics.hits, u64::MAX);
metrics.misses = u64::MAX;
assert_eq!(metrics.attempts(), u64::MAX);
assert!(
(metrics.hit_rate() - 0.5).abs() < 1e-12,
"hit_rate should not use the saturated attempts denominator"
);
}
#[test]
fn workspace_metrics_keep_counters_isolated_bd_1brl3() {
let mut metrics = CassPrefetchWorkspaceMetrics::new();
assert!(metrics.is_empty());
{
let workspace_a = metrics.for_workspace_mut("workspace-a");
workspace_a.record_hit();
workspace_a.record_hit();
workspace_a.record_candidate();
}
{
let workspace_b = metrics.for_workspace_mut("workspace-b");
workspace_b.record_miss();
workspace_b.record_budget_exceeded();
}
let workspace_a = metrics
.for_workspace("workspace-a")
.expect("workspace-a metrics should exist");
let workspace_b = metrics
.for_workspace("workspace-b")
.expect("workspace-b metrics should exist");
assert_eq!(workspace_a.hits, 2);
assert_eq!(workspace_a.misses, 0);
assert_eq!(workspace_a.candidates_emitted, 1);
assert_eq!(workspace_a.attempts(), 2);
assert_eq!(workspace_a.hit_rate(), 1.0);
assert_eq!(workspace_b.hits, 0);
assert_eq!(workspace_b.misses, 1);
assert_eq!(workspace_b.budget_exceeded, 1);
assert_eq!(workspace_b.attempts(), 1);
assert_eq!(workspace_b.hit_rate(), 0.0);
let workspace_ids: Vec<&str> = metrics.snapshot().keys().map(String::as_str).collect();
assert_eq!(workspace_ids, vec!["workspace-a", "workspace-b"]);
assert_eq!(metrics.len(), 2);
}
#[test]
fn workspace_metrics_reset_only_named_workspace_bd_1brl3() {
let mut metrics = CassPrefetchWorkspaceMetrics::new();
metrics.for_workspace_mut("workspace-a").record_hit();
metrics.for_workspace_mut("workspace-a").record_candidate();
metrics.for_workspace_mut("workspace-b").record_miss();
metrics
.for_workspace_mut("workspace-b")
.record_history_too_short();
assert!(metrics.reset_workspace("workspace-a"));
assert!(!metrics.reset_workspace("workspace-c"));
assert_eq!(
metrics.for_workspace("workspace-a"),
Some(&CassPrefetchMetrics::new())
);
let workspace_b = metrics
.for_workspace("workspace-b")
.expect("workspace-b metrics should remain");
assert_eq!(workspace_b.misses, 1);
assert_eq!(workspace_b.history_too_short, 1);
}
#[test]
fn history_helpers_match_iteration_order() {
let topics = ["recent", "older", "oldest"];
let h = CassPrefetchHistory::from_topics(test_agent_scope(), topics.iter().copied());
assert_eq!(h.agent_scope, TEST_AGENT_SCOPE);
assert_eq!(h.len(), 3);
assert!(!h.is_empty());
let observed: Vec<&str> = h.iter().map(|o| o.topic_id.as_str()).collect();
assert_eq!(observed, vec!["recent", "older", "oldest"]);
}
#[test]
fn history_scope_is_part_of_identity_bd_298n0() {
let agent_a = CassPrefetchHistory::from_topics("agent:a", ["current", "alpha"]);
let agent_b = CassPrefetchHistory::from_topics("agent:b", ["current", "alpha"]);
assert_eq!(agent_a.agent_scope, "agent:a");
assert_eq!(agent_b.agent_scope, "agent:b");
assert_eq!(agent_a.recent_first, agent_b.recent_first);
assert_ne!(
agent_a, agent_b,
"identical topic windows from different agents must stay distinct"
);
}
#[test]
fn blank_agent_scope_canonicalizes_to_unknown_owner_bd_298n0() {
assert_eq!(AgentScope::new("").as_str(), AgentScope::UNKNOWN);
assert_eq!(AgentScope::new(" \n\t ").as_str(), AgentScope::UNKNOWN);
assert!(AgentScope::new("").is_unknown());
assert_eq!(AgentScope::new(" agent:a ").as_str(), "agent:a");
let unnamed = CassPrefetchHistory::from_topics(" ", ["current", "alpha"]);
let named = CassPrefetchHistory::from_topics("agent:a", ["current", "alpha"]);
assert!(unnamed.agent_scope.is_unknown());
assert_ne!(
unnamed, named,
"blank scopes must not serialize as an empty owner indistinguishable from a real scope"
);
let decoded: CassPrefetchHistory =
serde_json::from_str(r#"{"agentScope":" ","recentFirst":[{"topicId":"current"}]}"#)
.expect("deserialize blank scoped history");
assert!(decoded.agent_scope.is_unknown());
}
#[test]
fn history_store_accumulates_most_recent_first_bd_16pwc_4() {
let mut store = CassPrefetchHistoryStore::new(DEFAULT_PREFETCH_HISTORY_WINDOW);
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(3, 7);
for topic in ["alpha", "bravo", "charlie"] {
store.observe(scope.clone(), "ws-a", topic, generation, &rev);
}
let history = store.history_for(&scope, "ws-a").expect("history present");
let topics: Vec<&str> = history.iter().map(|o| o.topic_id.as_str()).collect();
assert_eq!(topics, vec!["charlie", "bravo", "alpha"]);
assert_eq!(history.generation, generation);
assert!(history.corpus_revision_is_coherent_with(&rev));
assert!(history.is_within_admission_bounds());
}
#[test]
fn history_store_trims_to_window_bd_16pwc_4() {
let mut store = CassPrefetchHistoryStore::new(2);
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(1, 1);
for topic in ["t1", "t2", "t3", "t4"] {
store.observe(scope.clone(), "ws", topic, generation, &rev);
}
let history = store.history_for(&scope, "ws").expect("history present");
let topics: Vec<&str> = history.iter().map(|o| o.topic_id.as_str()).collect();
assert_eq!(topics, vec!["t4", "t3"]);
}
#[test]
fn history_store_isolates_distinct_scopes_and_workspaces_bd_16pwc_4() {
let mut store = CassPrefetchHistoryStore::new(DEFAULT_PREFETCH_HISTORY_WINDOW);
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(1, 1);
let alice = AgentScope::new("alice");
let bob = AgentScope::new("bob");
store.observe(alice.clone(), "ws-a", "alice-topic", generation, &rev);
store.observe(bob.clone(), "ws-a", "bob-topic", generation, &rev);
store.observe(alice.clone(), "ws-b", "alice-other-ws", generation, &rev);
assert_eq!(store.len(), 3);
let alice_a: Vec<&str> = store
.history_for(&alice, "ws-a")
.unwrap()
.iter()
.map(|o| o.topic_id.as_str())
.collect();
assert_eq!(alice_a, vec!["alice-topic"]);
let bob_a: Vec<&str> = store
.history_for(&bob, "ws-a")
.unwrap()
.iter()
.map(|o| o.topic_id.as_str())
.collect();
assert_eq!(bob_a, vec!["bob-topic"]);
let alice_b: Vec<&str> = store
.history_for(&alice, "ws-b")
.unwrap()
.iter()
.map(|o| o.topic_id.as_str())
.collect();
assert_eq!(alice_b, vec!["alice-other-ws"]);
}
#[test]
fn history_store_window_clamps_to_admission_bound_bd_16pwc_4() {
let mut store = CassPrefetchHistoryStore::new(MAX_PREFETCH_HISTORY + 100);
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(1, 1);
for n in 0..(MAX_PREFETCH_HISTORY + 50) {
store.observe(scope.clone(), "ws", format!("topic-{n}"), generation, &rev);
}
let history = store.history_for(&scope, "ws").expect("history present");
assert!(history.len() <= MAX_PREFETCH_HISTORY);
assert!(history.is_within_admission_bounds());
}
#[test]
fn history_store_missing_pair_returns_none_bd_16pwc_4() {
let store = CassPrefetchHistoryStore::new(DEFAULT_PREFETCH_HISTORY_WINDOW);
assert!(store.is_empty());
assert!(store.history_for(&test_agent_scope(), "nope").is_none());
}
#[test]
fn coordinator_schedule_emits_gated_candidates_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new();
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(2, 5);
for topic in ["beta", "alpha"] {
coordinator.observe(scope.clone(), "ws", topic, generation, &rev);
}
let prediction = coordinator.schedule(&scope, "ws", generation, &rev);
assert_eq!(prediction.degraded, None);
let topics: Vec<&str> = prediction
.candidates
.iter()
.map(|c| c.topic_id.as_str())
.collect();
assert_eq!(topics, vec!["beta"]);
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(metrics.candidates_emitted, 1);
assert_eq!(metrics.history_too_short, 0);
assert_eq!(metrics.measured_against_revision, rev);
let again = coordinator.schedule(&scope, "ws", generation, &rev);
assert_eq!(again.candidates, prediction.candidates);
}
#[test]
fn coordinator_schedule_records_stale_generation_drop_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new();
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
coordinator.observe(
scope.clone(),
"ws",
"topic",
PrefetchGeneration::new(1, 1),
&rev,
);
let prediction = coordinator.schedule(&scope, "ws", PrefetchGeneration::new(1, 2), &rev);
assert!(prediction.candidates.is_empty());
assert_eq!(
prediction.degraded,
Some(CASS_PREFETCH_STALE_GENERATION_CODE)
);
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(metrics.stale_generation_drop, 1);
assert_eq!(metrics.candidates_emitted, 0);
}
#[test]
fn coordinator_schedule_records_stale_corpus_revision_drop_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new();
let scope = test_agent_scope();
let generation = PrefetchGeneration::new(1, 1);
coordinator.observe(
scope.clone(),
"ws",
"topic",
generation,
&CorpusRevision::from("corpus:v1"),
);
let prediction =
coordinator.schedule(&scope, "ws", generation, &CorpusRevision::from("corpus:v2"));
assert!(prediction.candidates.is_empty());
assert_eq!(
prediction.degraded,
Some(CASS_PREFETCH_STALE_CORPUS_REVISION_CODE)
);
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(metrics.stale_corpus_revision_drop, 1);
assert_eq!(metrics.stale_generation_drop, 0);
}
#[test]
fn coordinator_schedule_caps_candidates_at_top_k_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new().with_top_k(2);
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(1, 1);
for topic in ["t1", "t2", "t3", "t4", "t5", "current"] {
coordinator.observe(scope.clone(), "ws", topic, generation, &rev);
}
let prediction = coordinator.schedule(&scope, "ws", generation, &rev);
assert_eq!(prediction.degraded, None);
assert!(prediction.candidates.len() <= 2);
assert!(!prediction.candidates.is_empty());
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(
metrics.candidates_emitted,
prediction.candidates.len() as u64
);
}
#[test]
fn coordinator_schedule_without_history_records_history_too_short_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new();
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let prediction = coordinator.schedule(&scope, "ws", PrefetchGeneration::new(1, 1), &rev);
assert!(prediction.candidates.is_empty());
assert_eq!(prediction.degraded, None);
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(metrics.history_too_short, 1);
assert_eq!(metrics.candidates_emitted, 0);
}
#[test]
fn coordinator_budget_accounting_bd_16pwc_4() {
let mut coordinator =
CassPrefetchCoordinator::new().with_budget_per_slot(Duration::from_millis(50));
let over =
coordinator.record_warm_fetch("ws", Duration::from_millis(60), WarmFetchOutcome::Hit);
assert_eq!(over, Some(CASS_PREFETCH_BUDGET_EXCEEDED_CODE));
assert_eq!(
coordinator.record_warm_fetch("ws", Duration::from_millis(10), WarmFetchOutcome::Hit),
None
);
assert_eq!(
coordinator.record_warm_fetch("ws", Duration::from_millis(50), WarmFetchOutcome::Miss),
None
);
let metrics = coordinator.metrics_for("ws").expect("metrics bucket");
assert_eq!(metrics.budget_exceeded, 1);
assert_eq!(metrics.hits, 1);
assert_eq!(metrics.misses, 1);
assert!((metrics.hit_rate() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn coordinator_metrics_isolated_per_workspace_bd_16pwc_4() {
let mut coordinator = CassPrefetchCoordinator::new();
let scope = test_agent_scope();
let rev = CorpusRevision::from("corpus:v1");
let generation = PrefetchGeneration::new(1, 1);
for topic in ["beta", "alpha"] {
coordinator.observe(scope.clone(), "ws-a", topic, generation, &rev);
}
let _ = coordinator.schedule(&scope, "ws-a", generation, &rev);
let _ = coordinator.schedule(&scope, "ws-b", generation, &rev);
let ws_a = coordinator.metrics_for("ws-a").expect("ws-a bucket");
assert_eq!(ws_a.candidates_emitted, 1);
assert_eq!(ws_a.history_too_short, 0);
let ws_b = coordinator.metrics_for("ws-b").expect("ws-b bucket");
assert_eq!(ws_b.candidates_emitted, 0);
assert_eq!(ws_b.history_too_short, 1);
assert_eq!(coordinator.metrics().len(), 2);
}
#[test]
fn recency_weight_is_cross_platform_deterministic_bd_kpynd() {
let predictor = RecencyWeightedFrequencyPredictor::new();
assert_eq!(predictor.recency_weight(0), 1.0);
assert_eq!(predictor.recency_weight(3), 0.5);
assert_eq!(predictor.recency_weight(6), 0.25);
assert_eq!(predictor.recency_weight(9), 0.125);
for position in [1_usize, 2, 4, 5, 7, 8] {
let got = predictor.recency_weight(position);
let want = 2.0_f64.powf(-(position as f64) / 3.0);
assert!(
(got - want).abs() < 1e-9,
"position {position}: deterministic weight {got} diverged from 2^(-x) {want}"
);
}
for position in 0..16 {
let weight = predictor.recency_weight(position);
assert!(weight.is_finite() && (0.0..=1.0).contains(&weight));
assert!(
predictor.recency_weight(position) > predictor.recency_weight(position + 1),
"weight must strictly decrease at position {position}"
);
}
}
#[test]
fn deterministic_pow_half_total_over_domain_bd_kpynd() {
assert_eq!(deterministic_pow_half(f64::NAN), 0.0);
assert_eq!(deterministic_pow_half(0.0), 1.0);
assert_eq!(deterministic_pow_half(-1.0), 1.0);
assert_eq!(deterministic_pow_half(f64::NEG_INFINITY), 1.0);
assert_eq!(deterministic_pow_half(64.0), 0.0);
assert_eq!(deterministic_pow_half(f64::INFINITY), 0.0);
assert_eq!(exp2_neg_unit_interval(0.0), 1.0);
}
#[test]
fn predict_next_n_emits_byte_identical_json_across_calls_bd_kpynd() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let h = history(&[
"current",
"refactor",
"debug",
"refactor",
"doc_update",
"debug",
]);
let first =
serde_json::to_string(&predictor.predict_next_n(&h, 3)).expect("serialize first");
let second =
serde_json::to_string(&predictor.predict_next_n(&h, 3)).expect("serialize second");
assert_eq!(first, second);
}
#[test]
fn generation_coherence_is_exact_match_bd_qud3c() {
let tag = PrefetchGeneration::new(7, 42);
assert!(tag.is_coherent_with(PrefetchGeneration::new(7, 42)));
assert!(!tag.is_coherent_with(PrefetchGeneration::new(7, 43)));
assert!(!tag.is_coherent_with(PrefetchGeneration::new(8, 42)));
assert!(PrefetchGeneration::default().is_coherent_with(PrefetchGeneration::new(0, 0)));
assert!(!PrefetchGeneration::default().is_coherent_with(PrefetchGeneration::new(0, 1)));
}
#[test]
fn history_constructors_default_generation_to_zero_bd_qud3c() {
assert_eq!(
CassPrefetchHistory::from_topics(test_agent_scope(), ["a", "b"]).generation,
PrefetchGeneration::new(0, 0)
);
assert_eq!(
CassPrefetchHistory::new(test_agent_scope(), vec![CassPrefetchObservation::new("a")])
.generation,
PrefetchGeneration::default()
);
let stamped = CassPrefetchHistory::from_topics(test_agent_scope(), ["a", "b"])
.with_generation(PrefetchGeneration::new(1, 9));
assert_eq!(stamped.generation, PrefetchGeneration::new(1, 9));
assert_eq!(stamped.len(), 2);
}
#[test]
fn history_corpus_revision_defaults_unknown_and_builder_stamps_bd_1eh60() {
let legacy = CassPrefetchHistory::from_topics(test_agent_scope(), ["a", "b"]);
assert!(
legacy
.recent_first
.iter()
.all(|observation| observation.corpus_revision.is_unknown())
);
assert!(!legacy.corpus_revision_is_coherent_with(&CorpusRevision::from("corpus:v1")));
let stamped = legacy.with_corpus_revision(CorpusRevision::from("corpus:v1"));
assert!(stamped.recent_first.iter().all(|observation| {
observation
.corpus_revision
.is_coherent_with(&CorpusRevision::from("corpus:v1"))
}));
assert!(stamped.corpus_revision_is_coherent_with(&CorpusRevision::from("corpus:v1")));
assert!(!stamped.corpus_revision_is_coherent_with(&CorpusRevision::from("corpus:v2")));
}
#[test]
fn gated_prediction_drops_stale_generation_bd_qud3c() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let stale = history(&["current", "alpha", "alpha", "bravo"])
.with_generation(PrefetchGeneration::new(1, 5));
let outcome = predictor.predict_next_n_gated(&stale, PrefetchGeneration::new(1, 6), 3);
assert!(outcome.candidates.is_empty());
assert_eq!(outcome.degraded, Some(CASS_PREFETCH_STALE_GENERATION_CODE));
let outcome_ws = predictor.predict_next_n_gated(&stale, PrefetchGeneration::new(2, 5), 3);
assert!(outcome_ws.candidates.is_empty());
assert_eq!(
outcome_ws.degraded,
Some(CASS_PREFETCH_STALE_GENERATION_CODE)
);
}
#[test]
fn gated_prediction_passes_on_coherent_generation_bd_qud3c() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let fresh = history(&["current", "alpha", "alpha", "bravo"])
.with_generation(PrefetchGeneration::new(1, 5));
let outcome = predictor.predict_next_n_gated(&fresh, PrefetchGeneration::new(1, 5), 3);
assert_eq!(outcome.degraded, None);
assert!(!outcome.candidates.is_empty());
assert_eq!(outcome.candidates, predictor.predict_next_n(&fresh, 3));
}
#[test]
fn revision_gated_prediction_drops_stale_corpus_revision_bd_1eh60() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let current_generation = PrefetchGeneration::new(1, 5);
let revision_v1 = CorpusRevision::from("corpus:v1");
let revision_v2 = CorpusRevision::from("corpus:v2");
let history = history(&["current", "alpha", "alpha", "bravo"])
.with_generation(current_generation)
.with_corpus_revision(revision_v1.clone());
let stale = predictor.predict_next_n_gated_for_revision(
&history,
current_generation,
&revision_v2,
3,
);
assert!(stale.candidates.is_empty());
assert_eq!(
stale.degraded,
Some(CASS_PREFETCH_STALE_CORPUS_REVISION_CODE)
);
let fresh = predictor.predict_next_n_gated_for_revision(
&history,
current_generation,
&revision_v1,
3,
);
assert_eq!(fresh.degraded, None);
assert_eq!(fresh.candidates, predictor.predict_next_n(&history, 3));
}
#[test]
fn metrics_record_stale_generation_drop_bd_qud3c() {
let mut metrics = CassPrefetchMetrics::new();
assert_eq!(metrics.stale_generation_drop, 0);
metrics.record_stale_generation_drop();
metrics.record_stale_generation_drop();
assert_eq!(metrics.stale_generation_drop, 2);
metrics.stale_generation_drop = u64::MAX;
metrics.record_stale_generation_drop();
assert_eq!(metrics.stale_generation_drop, u64::MAX);
}
#[test]
fn metrics_record_stale_corpus_revision_drop_bd_16pwc_4() {
let mut metrics = CassPrefetchMetrics::new();
assert_eq!(metrics.stale_corpus_revision_drop, 0);
metrics.record_stale_corpus_revision_drop();
metrics.record_stale_corpus_revision_drop();
assert_eq!(metrics.stale_corpus_revision_drop, 2);
assert_eq!(metrics.stale_generation_drop, 0);
metrics.stale_corpus_revision_drop = u64::MAX;
metrics.record_stale_corpus_revision_drop();
assert_eq!(metrics.stale_corpus_revision_drop, u64::MAX);
}
#[test]
fn metrics_record_measured_against_revision_bd_1eh60() {
let mut metrics = CassPrefetchMetrics::new();
assert!(metrics.measured_against_revision.is_unknown());
metrics.set_measured_against_revision(CorpusRevision::from("corpus:v1"));
assert_eq!(metrics.measured_against_revision.as_str(), "corpus:v1");
metrics.record_hit();
assert_ne!(metrics, CassPrefetchMetrics::new());
metrics.reset();
assert_eq!(metrics, CassPrefetchMetrics::new());
}
#[test]
fn oversized_history_is_refused_in_constant_time_bd_1suaa() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let mut topics: Vec<String> = Vec::with_capacity(MAX_PREFETCH_HISTORY + 50);
topics.push("current".to_owned());
for i in 0..(MAX_PREFETCH_HISTORY + 49) {
topics.push(format!("topic_{i}"));
}
let oversized = CassPrefetchHistory::from_topics(test_agent_scope(), topics);
assert!(oversized.recent_first.len() > MAX_PREFETCH_HISTORY);
assert!(!oversized.is_within_admission_bounds());
assert!(predictor.predict_next_n(&oversized, 3).is_empty());
let gated = predictor.predict_next_n_gated(&oversized, PrefetchGeneration::default(), 3);
assert!(gated.candidates.is_empty());
assert_eq!(gated.degraded, Some(CASS_PREFETCH_HISTORY_OVERSIZED_CODE));
}
#[test]
fn at_bound_history_is_admitted_bd_1suaa() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let mut topics: Vec<String> = Vec::with_capacity(MAX_PREFETCH_HISTORY);
topics.push("current".to_owned());
for _ in 1..MAX_PREFETCH_HISTORY {
topics.push("alpha".to_owned());
}
let at_bound = CassPrefetchHistory::from_topics(test_agent_scope(), topics);
assert_eq!(at_bound.recent_first.len(), MAX_PREFETCH_HISTORY);
assert!(at_bound.is_within_admission_bounds());
let gated = predictor.predict_next_n_gated(&at_bound, PrefetchGeneration::default(), 3);
assert_eq!(gated.degraded, None);
assert!(!gated.candidates.is_empty());
assert!(gated.candidates.len() <= 3);
}
#[test]
fn oversized_topic_id_is_refused_bd_1suaa() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let huge_topic = "x".repeat(MAX_PREFETCH_TOPIC_ID_BYTES + 1);
let history = CassPrefetchHistory::from_topics(
test_agent_scope(),
["current".to_owned(), huge_topic],
);
assert!(!history.is_within_admission_bounds());
let gated = predictor.predict_next_n_gated(&history, PrefetchGeneration::default(), 3);
assert!(gated.candidates.is_empty());
assert_eq!(gated.degraded, Some(CASS_PREFETCH_HISTORY_OVERSIZED_CODE));
}
#[test]
fn bare_predict_next_n_refuses_oversized_topic_id_bd_3mhyr() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let huge = "x".repeat(MAX_PREFETCH_TOPIC_ID_BYTES + 1);
let history = CassPrefetchHistory::from_topics(
test_agent_scope(),
["current".to_owned(), huge.clone(), huge.clone()],
);
assert!(history.recent_first.len() <= MAX_PREFETCH_HISTORY);
assert!(!history.is_within_admission_bounds());
let candidates = predictor.predict_next_n(&history, 3);
assert!(
candidates.is_empty(),
"bare predictor must decline an out-of-bounds history, got {} candidate(s)",
candidates.len()
);
assert!(
candidates
.iter()
.all(|candidate| candidate.topic_id.as_str().len() <= MAX_PREFETCH_TOPIC_ID_BYTES),
"bare predictor must never surface an over-cap topic_id"
);
}
#[test]
fn try_from_topics_enforces_bounds_by_construction_bd_1suaa() {
assert!(
CassPrefetchHistory::try_from_topics(test_agent_scope(), ["a", "b", "c"]).is_some()
);
let too_many: Vec<String> = (0..=MAX_PREFETCH_HISTORY)
.map(|i| format!("t{i}"))
.collect();
assert!(CassPrefetchHistory::try_from_topics(test_agent_scope(), too_many).is_none());
let huge = "y".repeat(MAX_PREFETCH_TOPIC_ID_BYTES + 1);
assert!(CassPrefetchHistory::try_from_topics(test_agent_scope(), [huge]).is_none());
let at_cap = "z".repeat(MAX_PREFETCH_TOPIC_ID_BYTES);
assert!(CassPrefetchHistory::try_from_topics(test_agent_scope(), [at_cap]).is_some());
}
#[test]
fn try_from_topics_checks_raw_topic_size_before_redaction_bd_1suaa() {
let oversized_secret = format!(
"postgres://user:{}@localhost/db",
"s".repeat(MAX_PREFETCH_TOPIC_ID_BYTES)
);
assert!(
TopicId::new(&oversized_secret).as_str().len() <= MAX_PREFETCH_TOPIC_ID_BYTES,
"test fixture must redact to a short placeholder"
);
assert!(
CassPrefetchHistory::try_from_topics(
test_agent_scope(),
["current".to_owned(), oversized_secret],
)
.is_none(),
"raw oversized topic must be refused before redaction can shrink it"
);
}
#[test]
fn candidate_constructor_and_deserialize_normalize_score_bd_1suaa() -> Result<(), String> {
assert_eq!(CassPrefetchCandidate::new("low", -0.25, "test").score, 0.0);
assert_eq!(CassPrefetchCandidate::new("high", 1.25, "test").score, 1.0);
assert_eq!(
CassPrefetchCandidate::new("nan", f64::NAN, "test").score,
0.0
);
let decoded: CassPrefetchCandidate =
serde_json::from_str(r#"{"topicId":"refactor","score":2.5,"predictor":"external"}"#)
.map_err(|error| format!("deserialize candidate: {error}"))?;
assert_eq!(decoded.score, 1.0);
let literal = CassPrefetchCandidate {
topic_id: TopicId::new("refactor"),
score: f64::NAN,
predictor: Cow::Borrowed("external"),
};
let encoded = serde_json::to_string(&literal)
.map_err(|error| format!("serialize literal: {error}"))?;
assert!(
encoded.contains(r#""score":0.0"#),
"serialized candidate must normalize invalid score, got {encoded}"
);
Ok(())
}
#[test]
fn metrics_record_history_oversized_bd_1suaa() {
let mut metrics = CassPrefetchMetrics::new();
assert_eq!(metrics.history_oversized, 0);
metrics.record_history_oversized();
assert_eq!(metrics.history_oversized, 1);
metrics.history_oversized = u64::MAX;
metrics.record_history_oversized();
assert_eq!(metrics.history_oversized, u64::MAX);
}
#[test]
fn topic_id_redacts_secret_on_construction_bd_3aczq() {
let secret = "pg_pw_do_not_leak";
let topic = TopicId::new(format!("postgres://user:{secret}@localhost/db"));
assert!(
!topic.as_str().contains(secret),
"raw secret leaked into topic_id: {}",
topic.as_str()
);
assert!(
topic.as_str().contains("[REDACTED"),
"redaction placeholder missing: {}",
topic.as_str()
);
assert_eq!(TopicId::new("refactor").as_str(), "refactor");
}
#[test]
fn topic_id_redacts_on_deserialize_bd_3aczq() {
let secret = "pg_pw_via_serde";
let json = format!(r#"{{"topicId":"postgres://user:{secret}@host/db"}}"#);
let observation: CassPrefetchObservation =
serde_json::from_str(&json).expect("deserialize observation");
assert!(
!observation.topic_id.as_str().contains(secret),
"secret survived deserialize: {}",
observation.topic_id.as_str()
);
assert!(observation.topic_id.as_str().contains("[REDACTED"));
}
#[test]
fn predictor_never_emits_unredacted_topic_bd_3aczq() {
let secret = "pg_pw_end_to_end";
let predictor = RecencyWeightedFrequencyPredictor::new();
let leaky = format!("postgres://user:{secret}@host/db");
let history = CassPrefetchHistory::from_topics(
test_agent_scope(),
["current", leaky.as_str(), leaky.as_str()],
);
let candidates = predictor.predict_next_n(&history, 3);
assert!(!candidates.is_empty());
for candidate in &candidates {
assert!(
!candidate.topic_id.as_str().contains(secret),
"predictor emitted an unredacted secret: {}",
candidate.topic_id.as_str()
);
}
}
#[test]
fn topic_id_serializes_transparently_as_string_bd_3aczq() {
let candidate = CassPrefetchCandidate::new("refactor", 0.5, "p");
let json = serde_json::to_string(&candidate).expect("serialize candidate");
assert!(json.contains(r#""topicId":"refactor""#), "got {json}");
}
#[test]
fn built_in_predictor_borrows_static_name_bd_1cc1c() {
let predictor = RecencyWeightedFrequencyPredictor::new();
let history = history(&["current", "alpha", "alpha"]);
let candidates = predictor.predict_next_n(&history, 3);
assert_eq!(candidates.len(), 1);
assert!(matches!(
candidates[0].predictor,
Cow::Borrowed("recency_weighted_frequency_v1")
));
let json = serde_json::to_string(&candidates[0]).expect("serialize candidate");
let decoded: CassPrefetchCandidate =
serde_json::from_str(&json).expect("deserialize candidate");
assert_eq!(decoded.predictor.as_ref(), "recency_weighted_frequency_v1");
assert!(matches!(decoded.predictor, Cow::Owned(_)));
}
#[test]
fn schema_constants_are_pinned() {
assert_eq!(
CASS_PREFETCH_DECISION_SCHEMA_V1,
"ee.cass_prefetch.decision.v1"
);
assert_eq!(
CASS_PREFETCH_METRICS_SCHEMA_V1,
"ee.cass_prefetch.metrics.v1"
);
}
}