Files
IdeA/crates/application/tests/permission_usecases.rs
Blomios 27597eb64e feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree,
indissociables au niveau fichier mais tous deux verts (cargo test
--workspace + tests frontend permissions au vert).

Permissions — voie « projection CLI » (advisory), complète :
- LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve
  deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs).
- LP1 store : FsPermissionStore (.ideai/permissions.json).
- LP2 use cases : Get/Update project, Update agent override, Resolve.
- LP3 projecteurs Claude/Codex (settings.local.json / config.toml),
  câblage launch-path + PermissionProjectorRegistry, nettoyage des
  fichiers Replace orphelins au swap de profil (LP3-4), composition root
  + commandes Tauri, UI PermissionsPanel (projet + override agent).
- ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap).

Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS
airtight (Landlock fichiers) + résumé de permissions injecté.

Inclut aussi le chantier Codex/input/sessions structurées en cours
(McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les
mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:39:18 +02:00

129 lines
3.5 KiB
Rust

use std::sync::{Arc, Mutex};
use application::{
ResolveAgentPermissions, ResolveAgentPermissionsInput, UpdateAgentPermissions,
UpdateAgentPermissionsInput, UpdateProjectPermissions, UpdateProjectPermissionsInput,
};
use async_trait::async_trait;
use domain::ids::{AgentId, ProjectId};
use domain::ports::{PermissionStore, StoreError};
use domain::project::{Project, ProjectPath};
use domain::remote::RemoteRef;
use domain::{PermissionSet, Posture, ProjectPermissions};
#[derive(Default)]
struct FakePermissionStore {
doc: Mutex<ProjectPermissions>,
saves: Mutex<usize>,
}
#[async_trait]
impl PermissionStore for FakePermissionStore {
async fn load_permissions(&self, _project: &Project) -> Result<ProjectPermissions, StoreError> {
Ok(self.doc.lock().unwrap().clone())
}
async fn save_permissions(
&self,
_project: &Project,
permissions: &ProjectPermissions,
) -> Result<(), StoreError> {
*self.doc.lock().unwrap() = permissions.clone();
*self.saves.lock().unwrap() += 1;
Ok(())
}
}
fn project() -> Project {
Project::new(
ProjectId::new_random(),
"permissions",
ProjectPath::new("/home/me/proj").unwrap(),
RemoteRef::local(),
1_700_000_000_000,
)
.unwrap()
}
#[tokio::test]
async fn update_project_permissions_replaces_defaults_and_persists() {
let store = Arc::new(FakePermissionStore::default());
let use_case = UpdateProjectPermissions::new(store.clone());
let out = use_case
.execute(UpdateProjectPermissionsInput {
project: project(),
permissions: Some(PermissionSet::new(vec![], Posture::Ask)),
})
.await
.unwrap();
assert_eq!(
out.permissions.project_defaults.unwrap().fallback(),
Posture::Ask
);
assert_eq!(*store.saves.lock().unwrap(), 1);
}
#[tokio::test]
async fn update_agent_permissions_adds_and_removes_sparse_override() {
let store = Arc::new(FakePermissionStore::default());
let use_case = UpdateAgentPermissions::new(store.clone());
let agent = AgentId::new_random();
use_case
.execute(UpdateAgentPermissionsInput {
project: project(),
agent_id: agent,
permissions: Some(PermissionSet::new(vec![], Posture::Deny)),
})
.await
.unwrap();
assert_eq!(
store
.doc
.lock()
.unwrap()
.agent_permissions(agent)
.unwrap()
.fallback(),
Posture::Deny
);
let out = use_case
.execute(UpdateAgentPermissionsInput {
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_permissions_returns_effective_policy() {
let agent = AgentId::new_random();
let store = Arc::new(FakePermissionStore {
doc: Mutex::new(ProjectPermissions::new(
Some(PermissionSet::new(vec![], Posture::Ask)),
vec![],
)),
saves: Mutex::new(0),
});
let use_case = ResolveAgentPermissions::new(store);
let out = use_case
.execute(ResolveAgentPermissionsInput {
project: project(),
agent_id: agent,
})
.await
.unwrap();
assert_eq!(out.effective.unwrap().fallback(), Posture::Ask);
}