//! M10p3 integration test — Provenance, Explainability, Remove-by-Scope. //! //! Validates that every community contribution carries provenance (user, writer //! agent, epoch), that an aggregate can be explained as the contributions that //! compose it, and that contributions can be removed precisely by user or by //! agent — non-globally, deterministically, with the local profile intact and //! the removal surviving reopen. Exercises the M10 UAT: revoke `A_trusted`, then //! remove `A_trusted`'s prior contributions. //! //! ROADMAP §Milestone 10 Phase 3. #![allow(clippy::too_many_lines, clippy::unwrap_used)] use std::time::Duration; use tidaldb::{ CommunityId, RemoveScope, ScopeClass, ScopePermission, ShareIntent, SharePolicy, TidalDb, UserId, schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window}, }; fn schema() -> tidaldb::schema::Schema { let mut b = SchemaBuilder::new(); let _ = b .signal( "like", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600), }, ) .windows(&[Window::AllTime]) .velocity(false) .add(); b.build().unwrap() } fn share() -> SharePolicy { SharePolicy::community_share(1, [ShareIntent::Like]) } fn grant_writer(db: &TidalDb, agent: &str) { db.grant_capability( agent, vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); } #[test] fn contributions_carry_provenance_and_explain() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (c, item) = (CommunityId(1), EntityId::new(5)); db.join_community(UserId(1), c, share()).unwrap(); grant_writer(&db, "A_trusted"); let now = Timestamp::now(); // A direct user write + an agent-attributed write on the same item. db.signal_for_community("like", item, 1.0, now, UserId(1), c) .unwrap(); db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, UserId(1), c) .unwrap(); let provenance = db.explain_community_contributions(c, item, "like").unwrap(); assert_eq!( provenance.len(), 2, "two distinct contributors (direct + agent)" ); // Direct write has empty writer; agent write is attributed to A_trusted. assert!( provenance .iter() .any(|p| p.user == UserId(1) && p.writer_agent.is_empty()) ); assert!( provenance .iter() .any(|p| p.user == UserId(1) && p.writer_agent == "A_trusted") ); } #[test] fn remove_by_agent_is_precise() { 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, share()).unwrap(); grant_writer(&db, "A_trusted"); grant_writer(&db, "A_other"); let now = Timestamp::now(); db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c) .unwrap(); db.agent_signal_for_community("A_other", "like", item, 1.0, now, user, c) .unwrap(); db.signal_for_community("like", item, 1.0, now, user, c) .unwrap(); // direct assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 3 ); // Remove ONLY A_trusted's contributions. let receipt = db .remove_from_personalization(RemoveScope::CommunityAgent { writer_agent: "A_trusted".to_string(), community: c, }) .unwrap(); assert_eq!(receipt.contributions_removed, 1); // A_other's + the direct contribution remain (precise, non-global). assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 2 ); let remaining = db.explain_community_contributions(c, item, "like").unwrap(); assert!(!remaining.iter().any(|p| p.writer_agent == "A_trusted")); assert!(remaining.iter().any(|p| p.writer_agent == "A_other")); } #[test] fn m10_uat_revoke_and_remove_agent() { // The ROADMAP M10 UAT step: U revokes A_trusted community scope and removes // A_trusted's prior contributions from C; the local profile is untouched. 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, share()).unwrap(); let token = db .grant_capability( "A_trusted", vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); let now = Timestamp::now(); db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c) .unwrap(); db.signal("like", item, 1.0, now).unwrap(); // U's own local like assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 1 ); let local_before = db .read_windowed_count(item, "like", Window::AllTime) .unwrap(); // 1. Revoke A_trusted's community scope -> future writes denied immediately. db.revoke_capability(&token).unwrap(); assert!( db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c) .is_err() ); // 2. Remove A_trusted's prior contributions from C. db.remove_from_personalization(RemoveScope::CommunityAgent { writer_agent: "A_trusted".to_string(), community: c, }) .unwrap(); assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 0 ); // 3. U's local profile is completely intact. assert_eq!( db.read_windowed_count(item, "like", Window::AllTime) .unwrap(), local_before ); } #[test] fn remove_by_user_scope_delegates_to_purge() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (c, item) = (CommunityId(1), EntityId::new(5)); let now = Timestamp::now(); for u in 1..=3u64 { db.join_community(UserId(u), c, share()).unwrap(); db.signal_for_community("like", item, 1.0, now, UserId(u), c) .unwrap(); } let receipt = db .remove_from_personalization(RemoveScope::CommunityUser { user: UserId(2), community: c, epoch_range: 0..=u32::MAX, }) .unwrap(); assert_eq!(receipt.contributions_removed, 1); assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 2 ); } #[test] fn remove_survives_reopen() { let home = tidaldb::TempTidalHome::new().unwrap(); let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1)); { let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); db.join_community(user, c, share()).unwrap(); grant_writer(&db, "A_trusted"); let now = Timestamp::now(); db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c) .unwrap(); db.signal_for_community("like", item, 1.0, now, user, c) .unwrap(); db.remove_from_personalization(RemoveScope::CommunityAgent { writer_agent: "A_trusted".to_string(), community: c, }) .unwrap(); assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 1 ); db.shutdown().unwrap(); } // Reopen: the remove-by-agent outcome is preserved (the agent's contribution // stays removed, the direct one stays). let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); assert_eq!( db.read_community_windowed_count(c, item, "like", Window::AllTime) .unwrap(), 1 ); let prov = db.explain_community_contributions(c, item, "like").unwrap(); assert!(!prov.iter().any(|p| p.writer_agent == "A_trusted")); }