From 3ba90f8d2765fd1af6621d70714a925889cf9c6f Mon Sep 17 00:00:00 2001 From: Pitchfork-and-Torch <297513015+Pitchfork-and-Torch@users.noreply.github.com> Date: Thu, 3 Sep 2026 21:10:21 -0400 Subject: [PATCH] Expand skipped conversation ancestors before For You VF Thunder stores [in_reply_to, conversation_root] and skips the middle tweet. Following already expands that gap before VF. Phoenix did not, so AncillaryVFFilter never saw a Drop on the skipped ancestor. Wire ConversationGapAncestorHydrator on the Phoenix candidate path. --- .../vf_candidate_hydrator.rs | 36 +++++++++++++++++++ .../phoenix_candidate_pipeline.rs | 2 ++ 2 files changed, 38 insertions(+) diff --git a/home-mixer/candidate_hydrators/vf_candidate_hydrator.rs b/home-mixer/candidate_hydrators/vf_candidate_hydrator.rs index b6554ed6..09904bbb 100644 --- a/home-mixer/candidate_hydrators/vf_candidate_hydrator.rs +++ b/home-mixer/candidate_hydrators/vf_candidate_hydrator.rs @@ -182,3 +182,39 @@ fn should_drop_reason(reason: &FilteredReason) -> bool { _ => true, } } + +#[cfg(test)] +mod tests { + use super::*; + use xai_visibility_filtering::models::SafetyResult; + + fn drop_reason() -> FilteredReason { + FilteredReason::SafetyResult(SafetyResult { + action: Action::Drop(Default::default()), + ..Default::default() + }) + } + + #[test] + fn skipped_conversation_ancestor_does_not_drop_until_gap_expanded() { + let drop = drop_reason(); + let vf_results = HashMap::from([( + 15u64, + Ok(Some(drop.clone())) as Result>, + )]); + + let thunder_only = PostCandidate { + tweet_id: 30, + ancestors: vec![20, 10], + ..Default::default() + }; + assert!(!should_drop_ancillary(&thunder_only, &vf_results)); + + let expanded = PostCandidate { + tweet_id: 30, + ancestors: vec![20, 15, 10], + ..Default::default() + }; + assert!(should_drop_ancillary(&expanded, &vf_results)); + } +} diff --git a/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs b/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs index e8c9c397..5def7448 100644 --- a/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs +++ b/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs @@ -2,6 +2,7 @@ use crate::candidate_hydrators::ads_brand_safety_vf_hydrator::AdsBrandSafetyVfHy use crate::candidate_hydrators::ai_trend_feedback_context_hydrator::AiTrendFeedbackContextHydrator; use crate::candidate_hydrators::bidirectional_follow_hydrator::BidirectionalFollowHydrator; use crate::candidate_hydrators::blocked_by_hydrator::BlockedByHydrator; +use crate::candidate_hydrators::conversation_gap_ancestor_hydrator::ConversationGapAncestorHydrator; use crate::candidate_hydrators::core_data_candidate_hydrator::CoreDataCandidateHydrator; use crate::candidate_hydrators::engagement_counts_hydrator::EngagementCountsHydrator; use crate::candidate_hydrators::filtered_topics_hydrator::FilteredTopicsHydrator; @@ -330,6 +331,7 @@ impl PhoenixCandidatePipeline { socialgraph_client: socialgraph_client.clone(), }), Box::new(core_data_hydrator), + Box::new(ConversationGapAncestorHydrator::new(tes_client.clone())), Box::new(QuoteHydrator::new(tes_client.clone(), socialgraph_client.clone()).await), Box::new(MediaInfoHydrator::new(media_info_cache_client).await), Box::new(SubscriptionHydrator::new(tes_client.clone()).await),