eden/.llms/rules/ACR_cache_key_invalidation.md
Severity: HIGH
None instead of treating as a cache missblobstore.get().await?.unwrap() without handling NoneBAD (key format change without version):
// Before: format!("cs:{}", cs_id)
// After: format!("cs:{}:{}", repo_id, cs_id)
fn cache_key(repo_id: &RepoId, cs_id: &ChangesetId) -> String {
format!("cs:{}:{}", repo_id, cs_id)
}
GOOD (versioned key):
fn cache_key(repo_id: &RepoId, cs_id: &ChangesetId) -> String {
format!("cs:v2:{}:{}", repo_id, cs_id)
}
BAD (ordering change corrupts persistent cache — matches S566000):
// "Optimization": build Vec directly instead of inserting into sorted structure.
// Changes which entry wins during case-insensitive collisions ("foo" vs "Foo").
// Corrupted results get written to RocksDB, persisting across code rollback.
fn build_dir_entries(entries: Vec<DirEntry>) -> Vec<DirEntry> {
entries // No re-sort! Collision winner changes vs old sorted-insert path.
}
GOOD (preserve deterministic ordering):
fn build_dir_entries(mut entries: Vec<DirEntry>) -> Vec<DirEntry> {
entries.sort_by(|a, b| a.name.cmp(&b.name));
entries.dedup_by(|a, b| a.name.eq_ignore_ascii_case(&b.name));
entries
}
BAD (shard routing with polling — matches S556044):
// Client-side cache of shard assignments, refreshed by polling every 60s.
// During rolling deploys, shard moves happen faster than the poll interval.
let shard_map = shard_manager_client.get_shard_map().await?;
// Stale for up to 60s — requests land on wrong servers, get "repo not found" (403).
Always include a version component in cache keys. When changing key format, bump the version. For persistent caches (RocksDB, disk), include a schema version and validate on read — if the version doesn't match, treat as a miss and re-derive. Never optimize away sort/dedup steps in code that feeds a persistent cache. For shard/routing caches, use event-based invalidation (subscriptions) instead of polling, and return 503 (retriable) not 403 when a shard isn't loaded.