diff --git a/applications/aphoria/src/episteme/local/queries.rs b/applications/aphoria/src/episteme/local/queries.rs index a52f5e0..ed0d36d 100644 --- a/applications/aphoria/src/episteme/local/queries.rs +++ b/applications/aphoria/src/episteme/local/queries.rs @@ -254,15 +254,37 @@ impl LocalEpisteme { /// /// Uses the `AUTHORED_CLAIM` predicate index to find assertions, /// then converts back to `AuthoredClaim` via `assertion_to_authored_claim()`. + /// + /// Deduplicates by claim ID: when a claim is updated, deprecated, or superseded, + /// a new assertion is appended (append-only). This method keeps only the most + /// recently ingested version of each claim (by assertion timestamp). #[allow(dead_code)] // Used by EpistemeClaimStore (T4) and scanner (T6) #[instrument(skip(self))] pub async fn fetch_authored_claims(&self) -> Result, AphoriaError> { let assertions = self.fetch_assertions_by_predicate(predicates::AUTHORED_CLAIM).await?; - let mut claims = Vec::with_capacity(assertions.len()); + // Deduplicate by claim ID, keeping the most recently ingested version. + // Each update/deprecate/supersede creates a new assertion with a newer timestamp. + let mut claims_by_id: std::collections::HashMap = + std::collections::HashMap::new(); + for assertion in &assertions { match assertion_to_authored_claim(assertion) { - Ok(claim) => claims.push(claim), + Ok(claim) => { + let id = claim.id.clone(); + let timestamp = assertion.timestamp; + + match claims_by_id.entry(id) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert((claim, timestamp)); + } + std::collections::hash_map::Entry::Occupied(mut e) => { + if timestamp > e.get().1 { + e.insert((claim, timestamp)); + } + } + } + } Err(e) => { warn!( subject = %assertion.subject, @@ -273,7 +295,8 @@ impl LocalEpisteme { } } - info!(count = claims.len(), "Fetched authored claims from StemeDB"); + let claims: Vec = claims_by_id.into_values().map(|(c, _)| c).collect(); + info!(count = claims.len(), "Fetched authored claims from StemeDB (deduplicated)"); Ok(claims) }