//! M10p1 integration test — Community Governance Policy Engine. //! //! Validates versioned community policies governing signal eligibility and //! per-contribution weighting: excluded intents are rejected, admitted weights //! are clamped, versions are monotonic with effective timestamps, the governing //! version is queryable, and policies survive close/reopen. //! //! ROADMAP §Milestone 10 Phase 1. #![allow(clippy::too_many_lines, clippy::unwrap_used)] use std::{collections::BTreeSet, time::Duration}; use tidaldb::{ CommunityId, GovernancePolicy, ShareIntent, SharePolicy, TidalDb, UserId, WeightingBounds, schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window}, }; fn schema() -> tidaldb::schema::Schema { let mut b = SchemaBuilder::new(); for name in ["like", "low_quality", "skip_for_now"] { let _ = b .signal( name, EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600), }, ) .windows(&[Window::AllTime]) .velocity(false) .add(); } b.build().unwrap() } fn full_share() -> SharePolicy { SharePolicy::community_share( 1, [ ShareIntent::Like, ShareIntent::LowQuality, ShareIntent::SkipForNow, ], ) } fn policy(community: CommunityId, version: u32, effective_at_ns: u64) -> GovernancePolicy { GovernancePolicy { community_id: community, version, effective_at_ns, allowed_intents: BTreeSet::new(), excluded_intents: BTreeSet::from(["skip_for_now".to_string()]), weighting: WeightingBounds { min: 0.0, max: 2.0 }, trust_threshold: 0.0, } } #[test] fn excluded_intent_is_rejected_by_policy() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1)); db.join_community(user, c, full_share()).unwrap(); db.define_community_policy(policy(c, 1, 0)).unwrap(); let now = Timestamp::now(); // `skip_for_now` is excluded by governance -> rejected (not recorded). assert!( !db.signal_for_community("skip_for_now", item, 1.0, now, user, c) .unwrap() ); // `like` is admitted. assert!( db.signal_for_community("like", item, 1.0, now, user, c) .unwrap() ); assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 1 ); assert_eq!( db.read_community_windowed_count(c, item, "skip_for_now", Window::AllTime) .unwrap(), 0 ); } #[test] fn weight_is_clamped_by_policy() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let item = EntityId::new(5); let now = Timestamp::now(); // Governed community: max weight 2.0. let governed = CommunityId(1); db.join_community(UserId(1), governed, full_share()) .unwrap(); db.define_community_policy(policy(governed, 1, 0)).unwrap(); assert!( db.signal_for_community("like", item, 100.0, now, UserId(1), governed) .unwrap() ); // Ungoverned community: weight passes through. let free = CommunityId(2); db.join_community(UserId(1), free, full_share()).unwrap(); assert!( db.signal_for_community("like", item, 100.0, now, UserId(1), free) .unwrap() ); let governed_score = db .read_community_decay_score(governed, item, "like", 0) .unwrap() .unwrap(); let free_score = db .read_community_decay_score(free, item, "like", 0) .unwrap() .unwrap(); assert!( governed_score <= 2.0 + 1e-6, "governed weight must be clamped to 2.0, got {governed_score}" ); assert!( free_score > 2.0, "ungoverned weight must pass through, got {free_score}" ); } #[test] fn policy_versions_are_monotonic_and_queryable() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let c = CommunityId(1); db.define_community_policy(policy(c, 1, 0)).unwrap(); assert_eq!(db.governing_policy_version(c), Some(1)); // v2 supersedes once registered (effective at 0 here). db.define_community_policy(policy(c, 2, 0)).unwrap(); assert_eq!(db.governing_policy_version(c), Some(2)); // Re-registering a non-increasing version fails. assert!(db.define_community_policy(policy(c, 2, 0)).is_err()); assert!(db.define_community_policy(policy(c, 1, 0)).is_err()); } #[test] fn no_policy_means_no_governance() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (c, item, user) = (CommunityId(9), EntityId::new(1), UserId(1)); db.join_community(user, c, full_share()).unwrap(); let now = Timestamp::now(); // Without a governance policy, even `skip_for_now` flows (share policy allows it). assert!( db.signal_for_community("skip_for_now", item, 1.0, now, user, c) .unwrap() ); assert_eq!(db.governing_policy_version(c), None); } #[test] fn policy_survives_reopen() { let home = tidaldb::TempTidalHome::new().unwrap(); let c = CommunityId(1); { let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); db.define_community_policy(policy(c, 1, 0)).unwrap(); db.define_community_policy(policy(c, 2, 0)).unwrap(); db.shutdown().unwrap(); } let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); assert_eq!( db.governing_policy_version(c), Some(2), "governance policy must survive reopen" ); // Monotonic check still holds against restored history. assert!(db.define_community_policy(policy(c, 2, 0)).is_err()); assert!(db.define_community_policy(policy(c, 3, 0)).is_ok()); }