From 9cc69ffd852d31357126ca44836a97e390af1c68 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 12:56:17 +0100 Subject: [PATCH 01/15] Create high_risk_tags.rs --- visibility-filtering/models/high_risk_tags.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 visibility-filtering/models/high_risk_tags.rs diff --git a/visibility-filtering/models/high_risk_tags.rs b/visibility-filtering/models/high_risk_tags.rs new file mode 100644 index 00000000..c564f131 --- /dev/null +++ b/visibility-filtering/models/high_risk_tags.rs @@ -0,0 +1,32 @@ +use std::collections::HashSet; +use std::sync::LazyLock; + +/// High-risk / “killer-zone” tags. +/// Maintain this list from enforcement metrics (tags with sustained high +/// child-safety / severe-policy ban volume). Keep normalized (lowercase, no #). +pub static HIGH_RISK_TAGS: LazyLock> = LazyLock::new(|| { + HashSet::from([ + "teenageer", // example from the issue – replace / extend with real high-volume tags + // add more normalized tags here as metrics dictate + ]) +}); + +/// Returns true if the post text contains any high-risk tag. +/// Simple whitespace / punctuation split; can be replaced by the real +/// tokenizer later if available. +pub fn contains_high_risk_tag(text: &str) -> bool { + if text.is_empty() { + return false; + } + let lower = text.to_lowercase(); + // crude but effective tokenization for hashtags + for token in lower + .split(|c: char| !c.is_alphanumeric() && c != '_') + .filter(|t| !t.is_empty()) + { + if HIGH_RISK_TAGS.contains(token) { + return true; + } + } + false +} From 2ada28f49e3e5b9c3fd53612a7d5c97c6dc1625b Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 13:03:42 +0100 Subject: [PATCH 02/15] Create high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 visibility-filtering/rules/high_risk_tag_drops.rs diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs new file mode 100644 index 00000000..e5072452 --- /dev/null +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -0,0 +1,49 @@ +use crate::models::{SafetyLabelType, VfAction}; +use crate::rules::{Rule, RuleContext}; +use crate::models::high_risk_tags::contains_high_risk_tag; // the helper above +use xai_visibility_filtering::models::{ + Action, DropReason, FilteredReason, SafetyResult, SafetyResultReason, +}; + +const HIGH_RISK_TAG_REASON: FilteredReason = FilteredReason::SafetyResult(SafetyResult { + reason: Some(SafetyResultReason::NsfwHighPrecision), // or a new dedicated reason if enum allows + action: Action::Drop(DropReason {}), +}); + +/// Immediate drop for posts that contain any high-risk tag. +#[derive(Clone)] +pub struct HighRiskTagDropRule { + name: &'static str, + exempt_author: bool, // usually true so the author can still see their own post +} + +impl HighRiskTagDropRule { + pub const fn new(name: &'static str, exempt_author: bool) -> Self { + Self { name, exempt_author } + } +} + +impl Rule for HighRiskTagDropRule { + fn name(&self) -> &'static str { + self.name + } + + fn evaluate(&self, context: &RuleContext<'_>) -> VfAction { + // Need the post text – adjust field name to whatever the hydrated candidate exposes + let text = context.tweet_text().unwrap_or(""); + + if !contains_high_risk_tag(text) { + return VfAction::Allow; + } + + if self.exempt_author && context.is_author_viewer() { + return VfAction::Allow; + } + + // Immediate penalty + VfAction::Drop(HIGH_RISK_TAG_REASON.clone()) + } +} + +pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = + HighRiskTagDropRule::new("HighRiskTagDropRule", true); From 985d4766715ef752610ea3c3ac9c44f424afa845 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 13:15:28 +0100 Subject: [PATCH 03/15] Update registry.rs --- visibility-filtering/rules/registry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/visibility-filtering/rules/registry.rs b/visibility-filtering/rules/registry.rs index 64ac414a..ec4fdcd7 100644 --- a/visibility-filtering/rules/registry.rs +++ b/visibility-filtering/rules/registry.rs @@ -110,6 +110,9 @@ fn base_home_rules() -> Vec> { Box::new(MutedRetweetsRule), Box::new(tweet_label::PDNA_DROP), Box::new(tweet_label::BOUNCE_DROP), + // ← ADD THIS LINE + Box::new(tweet_label::HIGH_RISK_TAG_DROP), // or whatever you named the const + Box::new(tweet_label::SPAM_DROP), Box::new(tweet_label::FOR_EMERGENCY_USE_ONLY_DROP), Box::new(tweet_label::FOSNR_HATEFUL_CONDUCT_DROP), From 49d3f85b7a53f4eea32348aac0bfda3879d0312b Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 13:30:38 +0100 Subject: [PATCH 04/15] Update high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index e5072452..ad9e1843 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -47,3 +47,85 @@ impl Rule for HighRiskTagDropRule { pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = HighRiskTagDropRule::new("HighRiskTagDropRule", true); + + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{ + HydratedTweetCandidate, TweetFeatures, Viewer, ViewerFeatures, + }; + use crate::rules::test_context; // already exists in the crate + + fn viewer(id: u64) -> ViewerFeatures { + ViewerFeatures { + viewer: Viewer::LoggedIn(id), + ..Default::default() + } + } + + // Adjust the field name if your HydratedTweetCandidate stores text differently. + // Common names: tweet_text, text, or inside tweet_features. + fn candidate_with_text(text: &str, author_id: u64) -> HydratedTweetCandidate { + HydratedTweetCandidate { + tweet_id: 1, + author_id, + // If the struct has a direct text field: + // tweet_text: text.to_string(), + // or + tweet_features: TweetFeatures { + // if text lives here, set it; otherwise leave default + ..Default::default() + }, + ..Default::default() + } + // IMPORTANT: after creating the struct, if text is a separate field + // you may need to set it. Check the real definition of HydratedTweetCandidate + // and put the string in the correct place. + } + + #[test] + fn high_risk_tag_drops() { + let c = candidate_with_text("check this #teenageer content", 100); + let ctx = test_context(&viewer(999), &c); // viewer ≠ author + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Drop(_) + )); + } + + #[test] + fn normal_text_allows() { + let c = candidate_with_text("just a normal post about cats", 100); + let ctx = test_context(&viewer(999), &c); + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } + + #[test] + fn high_risk_tag_allows_author_self_view() { + // only if you set exempt_author = true + let c = candidate_with_text("my own post with #teenageer", 100); + let ctx = test_context(&viewer(100), &c); // same id as author + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } + + #[test] + fn case_insensitive_and_without_hash() { + let c = candidate_with_text("TEENAGEER is bad", 100); + let ctx = test_context(&viewer(999), &c); + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Drop(_) + )); + } +} From ee3a36838e08f45cad634e67edab5a0b1a934720 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 13:47:02 +0100 Subject: [PATCH 05/15] Update high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 82 ------------------- 1 file changed, 82 deletions(-) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index ad9e1843..e5072452 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -47,85 +47,3 @@ impl Rule for HighRiskTagDropRule { pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = HighRiskTagDropRule::new("HighRiskTagDropRule", true); - - -#[cfg(test)] -mod tests { - use super::*; - use crate::models::{ - HydratedTweetCandidate, TweetFeatures, Viewer, ViewerFeatures, - }; - use crate::rules::test_context; // already exists in the crate - - fn viewer(id: u64) -> ViewerFeatures { - ViewerFeatures { - viewer: Viewer::LoggedIn(id), - ..Default::default() - } - } - - // Adjust the field name if your HydratedTweetCandidate stores text differently. - // Common names: tweet_text, text, or inside tweet_features. - fn candidate_with_text(text: &str, author_id: u64) -> HydratedTweetCandidate { - HydratedTweetCandidate { - tweet_id: 1, - author_id, - // If the struct has a direct text field: - // tweet_text: text.to_string(), - // or - tweet_features: TweetFeatures { - // if text lives here, set it; otherwise leave default - ..Default::default() - }, - ..Default::default() - } - // IMPORTANT: after creating the struct, if text is a separate field - // you may need to set it. Check the real definition of HydratedTweetCandidate - // and put the string in the correct place. - } - - #[test] - fn high_risk_tag_drops() { - let c = candidate_with_text("check this #teenageer content", 100); - let ctx = test_context(&viewer(999), &c); // viewer ≠ author - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Drop(_) - )); - } - - #[test] - fn normal_text_allows() { - let c = candidate_with_text("just a normal post about cats", 100); - let ctx = test_context(&viewer(999), &c); - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Allow - )); - } - - #[test] - fn high_risk_tag_allows_author_self_view() { - // only if you set exempt_author = true - let c = candidate_with_text("my own post with #teenageer", 100); - let ctx = test_context(&viewer(100), &c); // same id as author - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Allow - )); - } - - #[test] - fn case_insensitive_and_without_hash() { - let c = candidate_with_text("TEENAGEER is bad", 100); - let ctx = test_context(&viewer(999), &c); - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Drop(_) - )); - } -} From 9126035668bd7244fb276ed00f0c9e2ca1098e9f Mon Sep 17 00:00:00 2001 From: segior340-source Date: Sun, 16 Aug 2026 13:48:54 +0100 Subject: [PATCH 06/15] Update high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index e5072452..62270348 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -47,3 +47,71 @@ impl Rule for HighRiskTagDropRule { pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = HighRiskTagDropRule::new("HighRiskTagDropRule", true); + + + + + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{ + CoreFeature, HydratedTweetCandidate, TweetFeatures, Viewer, ViewerFeatures, + }; + use crate::rules::test_context; + + fn viewer(id: u64) -> ViewerFeatures { + ViewerFeatures { + viewer: Viewer::LoggedIn(id), + ..Default::default() + } + } + + fn candidate_with_text(text: &str, author_id: u64) -> HydratedTweetCandidate { + HydratedTweetCandidate { + tweet_id: 1, + author_id, + tweet_features: TweetFeatures { + core: CoreFeature { + text: text.to_string(), + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + } + } + + #[test] + fn high_risk_tag_drops() { + let c = candidate_with_text("check this #teenageer content", 100); + let ctx = test_context(&viewer(999), &c); + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Drop(_) + )); + } + + #[test] + fn normal_text_allows() { + let c = candidate_with_text("just a normal post about cats", 100); + let ctx = test_context(&viewer(999), &c); + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } + + #[test] + fn high_risk_tag_allows_author_self_view() { + let c = candidate_with_text("my own post with #teenageer", 100); + let ctx = test_context(&viewer(100), &c); // same id as author + + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } +} From bccaad7243a8336cad16f44002fe2416b8bb0b90 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:38:31 +0100 Subject: [PATCH 07/15] Update high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index 62270348..fc94b198 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -30,7 +30,7 @@ impl Rule for HighRiskTagDropRule { fn evaluate(&self, context: &RuleContext<'_>) -> VfAction { // Need the post text – adjust field name to whatever the hydrated candidate exposes - let text = context.tweet_text().unwrap_or(""); + let text = &context.candidate().tweet_features.core.text; if !contains_high_risk_tag(text) { return VfAction::Allow; @@ -47,71 +47,3 @@ impl Rule for HighRiskTagDropRule { pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = HighRiskTagDropRule::new("HighRiskTagDropRule", true); - - - - - -#[cfg(test)] -mod tests { - use super::*; - use crate::models::{ - CoreFeature, HydratedTweetCandidate, TweetFeatures, Viewer, ViewerFeatures, - }; - use crate::rules::test_context; - - fn viewer(id: u64) -> ViewerFeatures { - ViewerFeatures { - viewer: Viewer::LoggedIn(id), - ..Default::default() - } - } - - fn candidate_with_text(text: &str, author_id: u64) -> HydratedTweetCandidate { - HydratedTweetCandidate { - tweet_id: 1, - author_id, - tweet_features: TweetFeatures { - core: CoreFeature { - text: text.to_string(), - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - } - } - - #[test] - fn high_risk_tag_drops() { - let c = candidate_with_text("check this #teenageer content", 100); - let ctx = test_context(&viewer(999), &c); - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Drop(_) - )); - } - - #[test] - fn normal_text_allows() { - let c = candidate_with_text("just a normal post about cats", 100); - let ctx = test_context(&viewer(999), &c); - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Allow - )); - } - - #[test] - fn high_risk_tag_allows_author_self_view() { - let c = candidate_with_text("my own post with #teenageer", 100); - let ctx = test_context(&viewer(100), &c); // same id as author - - assert!(matches!( - HIGH_RISK_TAG_DROP.evaluate(&ctx), - VfAction::Allow - )); - } -} From 6846699bab2d0812d2f2bf9884486b8d73da7b99 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:43:50 +0100 Subject: [PATCH 08/15] Update mod.rs --- visibility-filtering/models/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/visibility-filtering/models/mod.rs b/visibility-filtering/models/mod.rs index 6fbfc0bf..6b314d28 100644 --- a/visibility-filtering/models/mod.rs +++ b/visibility-filtering/models/mod.rs @@ -4,6 +4,7 @@ pub mod relationship; pub mod safety_labels; pub mod tweet; pub mod viewer; +pub mod high_risk_tags; pub use author::{AuthorFeatures, UserLabelSet}; pub use exclusive_content::ExclusiveContentFeatures; From ab697e34b6e8fdbfe38c06b51f7376045669b4bc Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:45:25 +0100 Subject: [PATCH 09/15] Update mod.rs --- visibility-filtering/rules/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/visibility-filtering/rules/mod.rs b/visibility-filtering/rules/mod.rs index 2750312a..85c83aab 100644 --- a/visibility-filtering/rules/mod.rs +++ b/visibility-filtering/rules/mod.rs @@ -9,6 +9,7 @@ pub mod tweet_flag_rules; pub mod tweet_label_drops; pub mod user_label_drops; pub mod user_rules; +pub mod high_risk_tag_drops; use crate::models::{HydratedTweetCandidate, SafetyLabelType, VfAction, ViewerFeatures}; use xai_visibility_filtering::models::FilteredReason; From 10fcb123744d5c76fc6dd341e09b848ca2fb1ee4 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:46:32 +0100 Subject: [PATCH 10/15] Update registry.rs --- visibility-filtering/rules/registry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visibility-filtering/rules/registry.rs b/visibility-filtering/rules/registry.rs index ec4fdcd7..6927a61b 100644 --- a/visibility-filtering/rules/registry.rs +++ b/visibility-filtering/rules/registry.rs @@ -111,7 +111,7 @@ fn base_home_rules() -> Vec> { Box::new(tweet_label::PDNA_DROP), Box::new(tweet_label::BOUNCE_DROP), // ← ADD THIS LINE - Box::new(tweet_label::HIGH_RISK_TAG_DROP), // or whatever you named the const + Box::new(high_risk_tag_drops::HIGH_RISK_TAG_DROP), // or whatever you named the const Box::new(tweet_label::SPAM_DROP), Box::new(tweet_label::FOR_EMERGENCY_USE_ONLY_DROP), From 28cad15ec93265530d4f663d5b598bdcc88809a4 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:48:00 +0100 Subject: [PATCH 11/15] Update registry.rs --- visibility-filtering/rules/registry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/visibility-filtering/rules/registry.rs b/visibility-filtering/rules/registry.rs index 6927a61b..05cbbf7c 100644 --- a/visibility-filtering/rules/registry.rs +++ b/visibility-filtering/rules/registry.rs @@ -20,8 +20,11 @@ use crate::rules::tweet_label_drops as tweet_label; use crate::rules::user_label_drops as user_label; use crate::rules::user_rules::{self as author, ProtectedAuthorDropRule}; use crate::rules::{evaluate_rules, Rule, RuleContext, Verdict}; +use crate::rules::high_risk_tag_drops; use xai_visibility_filtering::models::FilteredReason; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SafetyLevel { FilterAll, From b2f98ca52b82290b4d70c0d5ab73e1cce6949f98 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:27:38 +0000 Subject: [PATCH 12/15] Expand high-risk tag rule to include nolimits, momsonn, omegle --- .../rules/high_risk_tag_drops.rs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index fc94b198..5acd783b 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -1,6 +1,6 @@ use crate::models::{SafetyLabelType, VfAction}; use crate::rules::{Rule, RuleContext}; -use crate::models::high_risk_tags::contains_high_risk_tag; // the helper above + use xai_visibility_filtering::models::{ Action, DropReason, FilteredReason, SafetyResult, SafetyResultReason, }; @@ -29,19 +29,25 @@ impl Rule for HighRiskTagDropRule { } fn evaluate(&self, context: &RuleContext<'_>) -> VfAction { - // Need the post text – adjust field name to whatever the hydrated candidate exposes - let text = &context.candidate().tweet_features.core.text; + let text = &context.candidate().tweet_features.core.text; + let lower = text.to_lowercase(); + + // Simple pattern: drop if any of these tags appear +let has_target_tag = lower.contains("teenageer") + || lower.contains("nolimits") + || lower.contains("momsonn") + || lower.contains("omegle"); - if !contains_high_risk_tag(text) { - return VfAction::Allow; - } + if !has_target_tag { + return VfAction::Allow; + } - if self.exempt_author && context.is_author_viewer() { - return VfAction::Allow; - } + if self.exempt_author && context.is_author_viewer() { + return VfAction::Allow; + } - // Immediate penalty - VfAction::Drop(HIGH_RISK_TAG_REASON.clone()) + // Use the same severe label you already defined + VfAction::Drop(HIGH_RISK_TAG_REASON.clone()) } } From 973c24034e5bb54c61d30bae1e5e80158078bdfd Mon Sep 17 00:00:00 2001 From: segior340-source Date: Mon, 17 Aug 2026 13:51:13 +0000 Subject: [PATCH 13/15] Document intention: alert and penalize users/engagers of high-risk tags, not just hide tags --- .../rules/high_risk_tag_drops.rs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index 5acd783b..0a7a4c2c 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -1,12 +1,39 @@ -use crate::models::{SafetyLabelType, VfAction}; -use crate::rules::{Rule, RuleContext}; +//! High-risk tag (“killer-zone”) drop rule. +//! +//! Core objective (from issue #61): +//! The goal is NOT merely to hide or remove a specific tag. +//! Bad actors can rotate tags in seconds. The real goal is to: +//! +//! 1. Immediately reduce the visibility of posts that carry these +//! high-risk tags (current implementation). +//! 2. Trigger alerts / signals so that the accounts posting or +//! systematically engaging with this content can be reviewed. +//! 3. When content under these tags is later banned (high probability +//! for this class of tags), the accounts that interacted with it +//! (liked, reposted, replied, etc.) should receive ranking / +//! credibility penalties and be placed under closer scrutiny +//! (“watch” / negative reputation propagation). +//! +//! Current open-source limitation: +//! Only the post-level visibility drop is fully implementable here. +//! The full engagement → later-ban → user penalty pipeline and a +//! real-time watchlist are not completely exposed in this repository. +//! The comments document the intended direction so future work +//! (or internal systems) can continue from this point. +//! +//! Tags currently treated as high-risk signals: +//! teenageer, nolimits, momsonn, omegle +use crate::models::VfAction; +use crate::rules::{Rule, RuleContext}; use xai_visibility_filtering::models::{ Action, DropReason, FilteredReason, SafetyResult, SafetyResultReason, }; +// Reuse an existing severe reason. This keeps the rule compatible +// with the current label system. const HIGH_RISK_TAG_REASON: FilteredReason = FilteredReason::SafetyResult(SafetyResult { - reason: Some(SafetyResultReason::NsfwHighPrecision), // or a new dedicated reason if enum allows + reason: Some(SafetyResultReason::NsfwHighPrecision), action: Action::Drop(DropReason {}), }); From 27aa278f1a307f8747f67a00833cca33c9ef1306 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Tue, 18 Aug 2026 08:59:00 +0100 Subject: [PATCH 14/15] Update high_risk_tag_drops.rs --- .../rules/high_risk_tag_drops.rs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index 0a7a4c2c..20bfea24 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -78,5 +78,82 @@ let has_target_tag = lower.contains("teenageer") } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{ + CoreFeature, HydratedTweetCandidate, TweetFeatures, Viewer, ViewerFeatures, + }; + use crate::rules::test_context; + + fn viewer(id: u64) -> ViewerFeatures { + ViewerFeatures { + viewer: Viewer::LoggedIn(id), + ..Default::default() + } + } + + fn candidate_with_text(text: &str, author_id: u64) -> HydratedTweetCandidate { + HydratedTweetCandidate { + tweet_id: 1, + author_id, + tweet_features: TweetFeatures { + core: CoreFeature { + text: text.to_string(), + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + } + } + + #[test] + fn drops_post_with_high_risk_tag() { + let c = candidate_with_text("check this #teenageer content", 100); + let ctx = test_context(&viewer(999), &c); + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Drop(_) + )); + } + + #[test] + fn drops_post_with_other_listed_tags() { + let tags = ["nolimits", "momsonn", "omegle"]; + for tag in tags { + let text = format!("some text with #{}", tag); + let c = candidate_with_text(&text, 100); + let ctx = test_context(&viewer(999), &c); + assert!( + matches!(HIGH_RISK_TAG_DROP.evaluate(&ctx), VfAction::Drop(_)), + "Should drop tag: {}", + tag + ); + } + } + + #[test] + fn allows_normal_text() { + let c = candidate_with_text("just a normal post about cats", 100); + let ctx = test_context(&viewer(999), &c); + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } + + #[test] + fn allows_author_to_see_own_post() { + let c = candidate_with_text("my own post with #teenageer", 100); + let ctx = test_context(&viewer(100), &c); // same id as author + assert!(matches!( + HIGH_RISK_TAG_DROP.evaluate(&ctx), + VfAction::Allow + )); + } +} + pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = HighRiskTagDropRule::new("HighRiskTagDropRule", true); From 100f91c41ada5add5b6ef4492915b5aebd34b8d2 Mon Sep 17 00:00:00 2001 From: segior340-source Date: Tue, 18 Aug 2026 16:43:14 +0100 Subject: [PATCH 15/15] Update high_risk_tag_drops.rs wrong placement of test --- visibility-filtering/rules/high_risk_tag_drops.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/visibility-filtering/rules/high_risk_tag_drops.rs b/visibility-filtering/rules/high_risk_tag_drops.rs index 20bfea24..f02e7d76 100644 --- a/visibility-filtering/rules/high_risk_tag_drops.rs +++ b/visibility-filtering/rules/high_risk_tag_drops.rs @@ -78,6 +78,8 @@ let has_target_tag = lower.contains("teenageer") } } +pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = + HighRiskTagDropRule::new("HighRiskTagDropRule", true); #[cfg(test)] mod tests { @@ -154,6 +156,3 @@ mod tests { )); } } - -pub const HIGH_RISK_TAG_DROP: HighRiskTagDropRule = - HighRiskTagDropRule::new("HighRiskTagDropRule", true);