Skip to main content

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}