diff --git a/backend/crates/itcy/src/bat/submit.rs b/backend/crates/itcy/src/bat/submit.rs index 632e651..e095b74 100644 --- a/backend/crates/itcy/src/bat/submit.rs +++ b/backend/crates/itcy/src/bat/submit.rs @@ -142,10 +142,23 @@ async fn accept_surface( ) -> Result { let draft = load_draft(db_path, draft_id)?; gate_accept_draft(draft_id, &draft)?; - if let Err(reason) = - crate::sources::publisher_url::require_ship_cite_reachable(&draft.body, &draft.link_options) + if let Err(reason) = match surface { + BatSurface::LinkedIn => { + crate::sources::publisher_url::require_ship_cite_reachable( + &draft.body, + &draft.link_options, + ) .await - { + } + BatSurface::Tweet => { + crate::sources::publisher_url::require_tweet_ship_cite_reachable( + &draft.subject, + &draft.body, + &draft.link_options, + ) + .await + } + } { return Err(BatSubmitError::Gate(reason)); } let cfg = BatGithubConfig::from_env()?; diff --git a/backend/crates/itcy/src/sources/corpus_propose.rs b/backend/crates/itcy/src/sources/corpus_propose.rs index a3cc00e..8ca839c 100644 --- a/backend/crates/itcy/src/sources/corpus_propose.rs +++ b/backend/crates/itcy/src/sources/corpus_propose.rs @@ -325,6 +325,8 @@ const SLOGAN_MUSH_NEEDLES: &[&str] = &[ "it is not about", "it's not just", "it is not just", + "that's not just", + "that is not just", "isn't just about", "isn't just a", "isn't just another", @@ -647,6 +649,9 @@ Builders care about stewardship without swallowing the community around the stac assert!(body_has_slogan_mush( "This isn't just a shift in how open-source projects evolve." )); + assert!(body_has_slogan_mush( + "22.1k on GitHub? That's not just code, it's a new way to build." + )); assert!(!body_has_slogan_mush( "Mozilla shipped JPEG XL after a Rust decoder rewrite landed in Firefox." )); diff --git a/backend/crates/itcy/src/sources/publisher_url.rs b/backend/crates/itcy/src/sources/publisher_url.rs index 15ef37b..f217eee 100644 --- a/backend/crates/itcy/src/sources/publisher_url.rs +++ b/backend/crates/itcy/src/sources/publisher_url.rs @@ -258,7 +258,7 @@ async fn refill_link_options_from_pool(options: &mut Vec, pool: &[String } } -/// Gate BAT accept: the cite that ships must not 404. +/// Gate BAT accept for `LinkedIn` drafts: cite must be reachable; need 3 Link options. /// /// # Errors /// @@ -268,12 +268,33 @@ pub async fn require_ship_cite_reachable( link_options: &[String], ) -> Result<(), String> { require_link_options_floor(link_options)?; + require_ship_cite_url_reachable(body, link_options).await +} + +/// Gate BAT accept for tweets: X-locked cite may ship with one Link option. +/// +/// # Errors +/// +/// Returns operator-facing text when the floor is missed or the ship cite is unreachable. +pub async fn require_tweet_ship_cite_reachable( + brief: &str, + body: &str, + link_options: &[String], +) -> Result<(), String> { + require_tweet_link_options_floor(brief, link_options)?; + require_ship_cite_url_reachable(body, link_options).await +} + +async fn require_ship_cite_url_reachable( + body: &str, + link_options: &[String], +) -> Result<(), String> { let url = extract_in_post_url(body) .or_else(|| link_options.first().cloned()) .filter(|u| !u.trim().is_empty()); let Some(url) = url else { return Err( - "No publisher Link to ship. Need Link:1 after at least 3 Link options. Use `/change_url`." + "No publisher Link to ship. Pick Link:1 with `/change_url`, or `/rework` with a cite." .into(), ); }; @@ -589,4 +610,15 @@ Corpus grounding:\ncontext quality mush"; "{err}" ); } + + #[tokio::test] + async fn tweet_ship_accepts_one_x_locked_link_option() { + let x = "https://x.com/nineshoot/status/2094567713113059575"; + let brief = format!("Obscura Rust browser, cite {x}"); + let body = format!("Commentary beats.\n\n#Rust\n\n{x}\n"); + let opts = vec![x.to_string()]; + require_tweet_ship_cite_reachable(&brief, &body, &opts) + .await + .expect("X locked cite ships with one Link option"); + } }