use std::sync::{Arc, Mutex}; use application::{ ResolveAgentSystemPermissions, ResolveAgentSystemPermissionsInput, UpdateAgentSystemPermissions, UpdateAgentSystemPermissionsInput, UpdateProjectSystemPermissions, UpdateProjectSystemPermissionsInput, }; use async_trait::async_trait; use domain::ids::{AgentId, ProjectId}; use domain::ports::{RuntimeError, RuntimePermissionProbe, StoreError, SystemPermissionStore}; use domain::project::{Project, ProjectPath}; use domain::remote::RemoteRef; use domain::{ NetworkPolicy, ProjectSystemPermissions, RuntimeLockState, RuntimePermissionSnapshot, SystemPermissionControlMode, SystemPermissionSet, }; #[derive(Default)] struct FakeSystemPermissionStore { doc: Mutex, saves: Mutex, } #[async_trait] impl SystemPermissionStore for FakeSystemPermissionStore { async fn load_system_permissions( &self, _project: &Project, ) -> Result { Ok(self.doc.lock().unwrap().clone()) } async fn save_system_permissions( &self, _project: &Project, permissions: &ProjectSystemPermissions, ) -> Result<(), StoreError> { *self.doc.lock().unwrap() = permissions.clone(); *self.saves.lock().unwrap() += 1; Ok(()) } } struct LockedProbe; #[async_trait] impl RuntimePermissionProbe for LockedProbe { async fn probe_runtime_permissions( &self, _project: &Project, _agent_id: AgentId, ) -> Result { Ok(RuntimePermissionSnapshot::locked_uninspectable()) } } struct UnobservedProbe; #[async_trait] impl RuntimePermissionProbe for UnobservedProbe { async fn probe_runtime_permissions( &self, _project: &Project, _agent_id: AgentId, ) -> Result { Ok(RuntimePermissionSnapshot::unobserved()) } } fn project() -> Project { Project::new( ProjectId::new_random(), "system-permissions", ProjectPath::new("/home/me/proj").unwrap(), RemoteRef::local(), 1_700_000_000_000, ) .unwrap() } #[tokio::test] async fn update_project_system_permissions_replaces_defaults_and_persists() { let store = Arc::new(FakeSystemPermissionStore::default()); let use_case = UpdateProjectSystemPermissions::new(store.clone()); let out = use_case .execute(UpdateProjectSystemPermissionsInput { project: project(), permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Ask))), }) .await .unwrap(); assert_eq!( out.permissions.project_default.unwrap().network, Some(NetworkPolicy::Ask) ); assert_eq!(*store.saves.lock().unwrap(), 1); } #[tokio::test] async fn update_agent_system_permissions_adds_and_removes_sparse_override() { let store = Arc::new(FakeSystemPermissionStore::default()); let use_case = UpdateAgentSystemPermissions::new(store.clone()); let agent = AgentId::new_random(); use_case .execute(UpdateAgentSystemPermissionsInput { project: project(), agent_id: agent, permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Deny))), }) .await .unwrap(); assert_eq!( store .doc .lock() .unwrap() .agent_permissions(agent) .unwrap() .network, Some(NetworkPolicy::Deny) ); let out = use_case .execute(UpdateAgentSystemPermissionsInput { project: project(), agent_id: agent, permissions: None, }) .await .unwrap(); assert!(out.permissions.agent_permissions(agent).is_none()); assert_eq!(*store.saves.lock().unwrap(), 2); } #[tokio::test] async fn resolve_agent_system_permissions_exposes_locked_runtime_separately_from_wanted_control() { let agent = AgentId::new_random(); let store = Arc::new(FakeSystemPermissionStore { doc: Mutex::new(ProjectSystemPermissions::new( Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))), vec![], )), saves: Mutex::new(0), }); let use_case = ResolveAgentSystemPermissions::new(store, Arc::new(LockedProbe)); let out = use_case .execute(ResolveAgentSystemPermissionsInput { project: project(), agent_id: agent, }) .await .unwrap(); assert_eq!(out.permissions.wanted, Some(NetworkPolicy::Allow)); assert_eq!(out.permissions.effective, NetworkPolicy::Deny); assert_eq!(out.permissions.runtime_lock.state, RuntimeLockState::Locked); assert_eq!( out.permissions.control.mode, SystemPermissionControlMode::Editable ); assert_eq!( out.permissions.runtime_control.mode, SystemPermissionControlMode::ReadOnly ); } #[tokio::test] async fn resolve_agent_system_permissions_unobserved_runtime_does_not_lock_form() { let agent = AgentId::new_random(); let store = Arc::new(FakeSystemPermissionStore { doc: Mutex::new(ProjectSystemPermissions::new( Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))), vec![], )), saves: Mutex::new(0), }); let use_case = ResolveAgentSystemPermissions::new(store, Arc::new(UnobservedProbe)); let out = use_case .execute(ResolveAgentSystemPermissionsInput { project: project(), agent_id: agent, }) .await .unwrap(); assert_eq!(out.permissions.wanted, Some(NetworkPolicy::Allow)); assert_eq!(out.permissions.effective, NetworkPolicy::Allow); assert_eq!(out.permissions.runtime_lock.state, RuntimeLockState::None); assert_eq!( out.permissions.control.mode, SystemPermissionControlMode::Editable ); assert_eq!( out.permissions.runtime_control.mode, SystemPermissionControlMode::ReadOnly ); }