From 2fbb19a3c914a49d42cfaaea0a5f18423e092a39 Mon Sep 17 00:00:00 2001 From: scanash00 Date: Thu, 30 Jul 2026 00:02:19 -0800 Subject: [PATCH] more fix oopsie --- .../src/repo/record/validation.rs | 2 +- crates/tranquil-lexicon/src/dynamic.rs | 8 +++-- crates/tranquil-lexicon/src/registry.rs | 29 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/tranquil-api/src/repo/record/validation.rs b/crates/tranquil-api/src/repo/record/validation.rs index b009412..e9d85b5 100644 --- a/crates/tranquil-api/src/repo/record/validation.rs +++ b/crates/tranquil-api/src/repo/record/validation.rs @@ -10,7 +10,7 @@ pub async fn validate_record_with_status( ) -> Result { let registry = tranquil_lexicon::LexiconRegistry::global(); let mut resolution_error = None; - if !registry.has_schema(collection) { + if registry.requires_dynamic_resolution(collection) { if let Err(error) = registry.resolve_dynamic(collection).await { tracing::warn!( collection = %collection, diff --git a/crates/tranquil-lexicon/src/dynamic.rs b/crates/tranquil-lexicon/src/dynamic.rs index 16c1693..8b689c6 100644 --- a/crates/tranquil-lexicon/src/dynamic.rs +++ b/crates/tranquil-lexicon/src/dynamic.rs @@ -8,7 +8,7 @@ use std::time::{Duration, Instant}; use tokio::sync::Notify; use tranquil_types::Nsid; -const NEGATIVE_CACHE_TTL: Duration = Duration::from_secs(24 * 60 * 60); +const NEGATIVE_CACHE_TTL: Duration = Duration::from_secs(5 * 60); const POSITIVE_CACHE_TTL: Duration = Duration::from_secs(24 * 60 * 60); const REFRESH_FAILURE_BACKOFF: Duration = Duration::from_secs(60); const MAX_DYNAMIC_SCHEMAS: usize = 1024; @@ -301,7 +301,7 @@ impl DynamicRegistry { } #[cfg(test)] - fn expire_now(&self, nsid: &Nsid) { + pub(crate) fn expire_now(&self, nsid: &Nsid) { let mut store = self.store.write(); if let Some(entry) = store.schemas.get_mut(nsid) { entry.expires_at = Instant::now(); @@ -330,6 +330,10 @@ mod tests { registry.insert_negative(&nsid("com.example.test")); assert!(registry.is_negative_cached(&nsid("com.example.test"))); + assert!( + NEGATIVE_CACHE_TTL <= Duration::from_secs(5 * 60), + "corrected lexicons must be retried promptly" + ); } #[tokio::test] diff --git a/crates/tranquil-lexicon/src/registry.rs b/crates/tranquil-lexicon/src/registry.rs index 2fa209d..f5e5d2a 100644 --- a/crates/tranquil-lexicon/src/registry.rs +++ b/crates/tranquil-lexicon/src/registry.rs @@ -101,6 +101,18 @@ impl LexiconRegistry { self.get_doc(nsid).is_some() } + #[cfg(feature = "resolve")] + pub fn requires_dynamic_resolution(&self, nsid: &Nsid) -> bool { + if self.schemas.contains_key(nsid) { + return false; + } + + !matches!( + self.dynamic.get_entry(nsid), + Some(crate::dynamic::CacheEntry::Fresh(_)) + ) + } + pub fn schema_count(&self) -> usize { let embedded = self.schemas.len(); #[cfg(feature = "resolve")] @@ -178,6 +190,23 @@ mod tests { assert!(!registry.has_schema(&nsid("com.example.other"))); } + #[cfg(feature = "resolve")] + #[test] + fn stale_dynamic_schema_requires_resolution() { + let registry = LexiconRegistry::new(); + let collection = nsid("com.example.dynamic"); + registry.preload(LexiconDoc { + lexicon: 1, + id: collection.clone(), + defs: HashMap::new(), + }); + + assert!(!registry.requires_dynamic_resolution(&collection)); + registry.dynamic.expire_now(&collection); + assert!(registry.has_schema(&collection)); + assert!(registry.requires_dynamic_resolution(&collection)); + } + #[test] fn test_get_record_def() { let registry = crate::test_schemas::test_registry(); -- 2.51.2