//! M10p2 integration test — Agent Capability & Scope Controls. //! //! Validates the ROADMAP M10 UAT agent-rights flow: a trusted agent with a //! community-write capability can contribute, an experimental agent without it //! is rejected, revocation takes effect immediately, and capabilities + //! revocation history survive close/reopen. //! //! ROADMAP §Milestone 10 Phase 2. #![allow(clippy::unwrap_used)] use std::time::Duration; use tidaldb::{ CommunityId, 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]) } #[test] fn trusted_agent_writes_experimental_is_rejected() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (community, item, user) = (CommunityId(1), EntityId::new(5), UserId(1)); db.join_community(user, community, share()).unwrap(); // A_trusted gets community write; A_experimental gets only community read. db.grant_capability( "A_trusted", vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); db.grant_capability( "A_experimental", vec![ScopePermission::read_only(ScopeClass::Community)], None, ) .unwrap(); let now = Timestamp::now(); // Trusted agent: allowed -> contribution recorded. assert!( db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, community) .unwrap() ); // Experimental agent: denied by capability policy -> error, nothing written. assert!( db.agent_signal_for_community("A_experimental", "like", item, 1.0, now, user, community) .is_err() ); // Unknown agent: also denied. assert!( db.agent_signal_for_community("A_unknown", "like", item, 1.0, now, user, community) .is_err() ); // Exactly one contribution (from A_trusted) landed. assert_eq!( db.read_community_windowed_count(community, item, "like", Window::AllTime) .unwrap(), 1 ); } #[test] fn revocation_takes_effect_immediately() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); let (community, item, user) = (CommunityId(1), EntityId::new(5), UserId(1)); db.join_community(user, community, share()).unwrap(); let token = db .grant_capability( "A", vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); let now = Timestamp::now(); assert!(db.check_capability("A", ScopeClass::Community, true)); assert!( db.agent_signal_for_community("A", "like", item, 1.0, now, user, community) .unwrap() ); // Revoke -> the very next check/write is denied (atomic, no sweeper). assert!(db.revoke_capability(&token).unwrap()); assert!(!db.check_capability("A", ScopeClass::Community, true)); assert!( db.agent_signal_for_community("A", "like", item, 1.0, now, user, community) .is_err() ); // Revoking an unknown token returns false. assert!(!db.revoke_capability("does-not-exist").unwrap()); } #[test] fn ttl_expiry_denies() { let db = TidalDb::builder() .ephemeral() .with_schema(schema()) .open() .unwrap(); // Grant with a zero TTL -> already expired. db.grant_capability( "A", vec![ScopePermission::read_write(ScopeClass::Community)], Some(Duration::from_nanos(0)), ) .unwrap(); assert!(!db.check_capability("A", ScopeClass::Community, true)); } #[test] fn capabilities_and_revocations_survive_reopen() { let home = tidaldb::TempTidalHome::new().unwrap(); let token_id; { let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); db.grant_capability( "keep", vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); token_id = db .grant_capability( "revoked_agent", vec![ScopePermission::read_write(ScopeClass::Community)], None, ) .unwrap(); db.revoke_capability(&token_id).unwrap(); db.shutdown().unwrap(); } let db = TidalDb::builder() .with_data_dir(home.path()) .with_schema(schema()) .open() .unwrap(); // The live capability is restored and usable. assert!(db.check_capability("keep", ScopeClass::Community, true)); // The revoked one stays revoked across restart. assert!(!db.check_capability("revoked_agent", ScopeClass::Community, true)); // Revocation history is inspectable. let history = db.revocation_history(); assert!( history .iter() .any(|(tid, agent, _)| tid == &token_id && agent == "revoked_agent") ); }