koprogo_api/application/ports/role_delegation_repository.rs
1//! Port for persisting Story 3.5 role delegations.
2//!
3//! A delegated assignment is a `UserRoleAssignment` whose `valid_until` is
4//! `Some(_)` and whose `delegated_from_user_id` is `Some(_)`. The dedicated
5//! port surface is `AppError`-typed (CRITICAL.md #4) so the use-case never
6//! handles `Result<_, String>`.
7
8use crate::application::error::AppError;
9use crate::domain::entities::{UserRole, UserRoleAssignment};
10use async_trait::async_trait;
11use uuid::Uuid;
12
13#[async_trait]
14pub trait RoleDelegationRepository: Send + Sync {
15 /// Persist a freshly created delegation assignment.
16 async fn save(&self, assignment: &UserRoleAssignment) -> Result<(), AppError>;
17
18 /// Look up a delegation row by id. Returns `None` if not found OR not a
19 /// delegation row (i.e. `valid_until IS NULL`).
20 async fn find_by_id(&self, id: Uuid) -> Result<Option<UserRoleAssignment>, AppError>;
21
22 /// Look up the (active or expired) assignments currently held by `user_id`
23 /// with a given `role`. Used to enforce the @security non-transitive
24 /// invariant (the caller must have a *native* assignment for the role
25 /// they want to delegate) and the anti-double-grant 409.
26 async fn find_active_by_user_and_role(
27 &self,
28 user_id: Uuid,
29 role: &UserRole,
30 organization_id: Option<Uuid>,
31 ) -> Result<Vec<UserRoleAssignment>, AppError>;
32
33 /// List all active delegations involving `user_id`, either as target
34 /// (received) or as delegator (granted). Used by the audit list view.
35 async fn list_delegations_of(&self, user_id: Uuid)
36 -> Result<Vec<UserRoleAssignment>, AppError>;
37
38 /// Revoke a delegation by its assignment id (best-effort delete).
39 /// Idempotent: revoking an already-removed row returns `Ok(())`.
40 async fn revoke(&self, id: Uuid) -> Result<(), AppError>;
41}