diff --git a/visibility-filtering/rules/golden_corpus.rs b/visibility-filtering/rules/golden_corpus.rs index cc4da723..89304ab6 100644 --- a/visibility-filtering/rules/golden_corpus.rs +++ b/visibility-filtering/rules/golden_corpus.rs @@ -779,12 +779,12 @@ fn oon_media_cases() -> Vec { expected_decided_by: Some("DropTweetsWithDmcaMediaRule"), }, Case { - name: "dmca_media_allows_in_network", + name: "dmca_media_drops_in_network", level: TimelineHome, viewer: viewer(VIEWER_ID), candidate: tweet_candidate(|t| t.media.has_dmca_media = true), - expected_action: Allow, - expected_decided_by: None, + expected_action: Drop(FilteredReason::UnspecifiedReason), + expected_decided_by: Some("DropTweetsWithDmcaMediaRule"), }, Case { name: "geo_denied_media_drops_oon", @@ -794,6 +794,14 @@ fn oon_media_cases() -> Vec { expected_action: Drop(FilteredReason::UnspecifiedReason), expected_decided_by: Some("DropTweetsWithGeoRestrictedMediaRule"), }, + Case { + name: "geo_denied_media_drops_in_network", + level: TimelineHome, + viewer: viewer_in_country("de"), + candidate: tweet_candidate(|t| t.media.geo_deny_list = vec!["de".to_string()]), + expected_action: Drop(FilteredReason::UnspecifiedReason), + expected_decided_by: Some("DropTweetsWithGeoRestrictedMediaRule"), + }, Case { name: "geo_allow_listed_media_drops_unknown_country_oon", level: TimelineHomeRecommendations, diff --git a/visibility-filtering/rules/registry.rs b/visibility-filtering/rules/registry.rs index af79ac33..96602905 100644 --- a/visibility-filtering/rules/registry.rs +++ b/visibility-filtering/rules/registry.rs @@ -78,20 +78,23 @@ impl Policy { static FILTER_ALL_POLICY: Policy = Policy::new(&[tweet_rules::FILTER_ALL]); -static TIMELINE_HOME_SHARED_RULES: [&[RuleSpec]; 9] = [ +static TIMELINE_HOME_SHARED_RULES: [&[RuleSpec]; 10] = [ author_rules::AUTHOR_STATE_DROPS, author_rules::SOCIALGRAPH_DROPS, tweet_rules::TWEET_LABEL_DROPS, tweet_rules::NULLCAST_DROP, tweet_rules::TES_HOME_DROPS, + // TES media legal/geo drops. Sibling of TES_HOME_DROPS (post-level legal + // / local-law takedown). Not an OON-only quality filter: DMCA media and + // licensed geo lists apply to Following and For You followee cards too. + tweet_rules::RECS_MEDIA_DROPS, tweet_rules::SENSITIVE_VIEWER_DROPS, tweet_rules::EXCLUSIVE_TWEET_DROP, tweet_rules::NSFW_MEDIA_INTERSTITIALS, tweet_rules::NSFW_AUTHOR_INTERSTITIAL, ]; -static TIMELINE_HOME_RECOMMENDATION_ONLY_RULES: [&[RuleSpec]; 5] = [ - tweet_rules::RECS_MEDIA_DROPS, +static TIMELINE_HOME_RECOMMENDATION_ONLY_RULES: [&[RuleSpec]; 4] = [ author_rules::OON_NSFW_AUTHOR_DROPS, tweet_rules::OON_TWEET_FLAG_DROPS, tweet_rules::OON_TWEET_LABEL_DROPS, @@ -154,7 +157,7 @@ impl RuleEngine { #[cfg(test)] mod tests { use super::*; - use crate::models::{VfAction, ViewerFeatures}; + use crate::models::{MediaFeature, TweetFeatures, VfAction, ViewerFeatures}; use crate::rules::fixtures::{candidate, viewer, VIEWER_ID}; #[test] @@ -226,6 +229,8 @@ rust_vf: "DropStaleTweetsRule", "DropLegalTakendownPostRule", "DropLocalLawsTakendownPostRule", + "DropTweetsWithDmcaMediaRule", + "DropTweetsWithGeoRestrictedMediaRule", "SensitiveViewerLoggedOutDropRule", "SensitiveViewerUnderageDropRule", "SensitiveViewerNoStatedAgeDropRule", @@ -238,8 +243,6 @@ rust_vf: ); let mut recs = home.clone(); recs.extend([ - "DropTweetsWithDmcaMediaRule", - "DropTweetsWithGeoRestrictedMediaRule", "DropNsfwUserAuthorRule", "DropNsfwAdminAuthorRule", "TweetNsfwUserDropRule", @@ -271,6 +274,55 @@ rust_vf: ); } + #[test] + fn legal_media_drops_on_timeline_home() { + let rule_engine = RuleEngine::for_tests(); + let dmca = candidate() + .with_tweet_features(TweetFeatures { + media: MediaFeature { + has_dmca_media: true, + ..Default::default() + }, + ..Default::default() + }) + .build(); + let dmca_verdict = + rule_engine.evaluate(SafetyLevel::TimelineHome, &viewer(VIEWER_ID), &dmca); + assert!( + matches!(dmca_verdict.action, VfAction::Drop(_)), + "TimelineHome must drop DMCA media, got {:?}", + dmca_verdict.action + ); + assert_eq!( + dmca_verdict.decided_by, + Some("DropTweetsWithDmcaMediaRule") + ); + + let geo = candidate() + .with_tweet_features(TweetFeatures { + media: MediaFeature { + geo_deny_list: vec!["de".to_string()], + ..Default::default() + }, + ..Default::default() + }) + .build(); + let de_viewer = ViewerFeatures { + country_code: Some("de".into()), + ..viewer(VIEWER_ID) + }; + let geo_verdict = rule_engine.evaluate(SafetyLevel::TimelineHome, &de_viewer, &geo); + assert!( + matches!(geo_verdict.action, VfAction::Drop(_)), + "TimelineHome must drop geo-denied media, got {:?}", + geo_verdict.action + ); + assert_eq!( + geo_verdict.decided_by, + Some("DropTweetsWithGeoRestrictedMediaRule") + ); + } + #[derive(Debug, PartialEq, Eq)] enum RowClass { Drop, diff --git a/visibility-filtering/rules/tweet_rules.rs b/visibility-filtering/rules/tweet_rules.rs index 15db052e..dd2e7efb 100644 --- a/visibility-filtering/rules/tweet_rules.rs +++ b/visibility-filtering/rules/tweet_rules.rs @@ -302,6 +302,8 @@ pub(super) const FILTER_ALL: &[RuleSpec] = &[RuleSpec::Tweet { exempt_author: false, }]; +/// TES media legal/geo drops. Shared by TimelineHome and +/// TimelineHomeRecommendations (wired from TIMELINE_HOME_SHARED_RULES). pub(super) const RECS_MEDIA_DROPS: &[RuleSpec] = &[ RuleSpec::Tweet { name: "DropTweetsWithDmcaMediaRule",