trace2e_core/traceability/core/
compliance.rs

1//! # Compliance Module
2//!
3//! This module implements the compliance system for traceability policies in the trace2e framework.
4//! It provides policy management and evaluation capabilities to control data flows between resources
5//! based on confidentiality, integrity, deletion status, and consent requirements.
6//!
7//! ## Overview
8//!
9//! The compliance system enforces policies on resources and evaluates whether data flows between
10//! resources are permitted based on these policies. It supports four main policy dimensions:
11//!
12//! - **Confidentiality**: Controls whether data is public or secret
13//! - **Integrity**: Numeric level indicating data trustworthiness (higher = more trusted)
14//! - **Deletion**: Tracks deletion status (not deleted, pending deletion, or deleted)
15//! - **Consent**: Boolean flag indicating whether the resource owner has given consent for flows
16//!
17//! ## Policy Evaluation Rules
18//!
19//! Data flows are permitted only when:
20//! 1. Neither source nor destination is deleted or pending deletion
21//! 2. Source integrity level >= destination integrity level
22//! 3. Secret data cannot flow to public destinations (but public can flow to secret)
23//! 4. Both source and destination have consent (when enforced)
24use std::{
25    collections::{HashMap, HashSet},
26    future::Future,
27    pin::Pin,
28    sync::Arc,
29    task::Poll,
30};
31
32use dashmap::DashMap;
33use tower::Service;
34#[cfg(feature = "trace2e_tracing")]
35use tracing::info;
36
37use crate::traceability::{
38    api::{ComplianceRequest, ComplianceResponse, ConsentRequest, ConsentResponse},
39    core::consent::ConsentService,
40    error::TraceabilityError,
41    naming::Resource,
42};
43
44/// Confidentiality policy defines the level of confidentiality of a resource.
45///
46/// This enum controls whether data associated with a resource is considered sensitive
47/// and restricts flows from secret resources to public destinations.
48///
49/// # Flow Rules
50///
51/// - `Public` → `Public`: ✅ Allowed
52/// - `Public` → `Secret`: ✅ Allowed (upgrading confidentiality)
53/// - `Secret` → `Secret`: ✅ Allowed
54/// - `Secret` → `Public`: ❌ Blocked (would leak sensitive data)
55#[derive(Default, PartialEq, Debug, Clone, Copy, Eq)]
56pub enum ConfidentialityPolicy {
57    /// Data that must be kept confidential and cannot flow to public destinations
58    Secret,
59    /// Data that can be shared publicly (default)
60    #[default]
61    Public,
62}
63
64/// Deletion policy defines the deletion status of a resource.
65///
66/// This enum tracks the lifecycle state of a resource with respect to deletion,
67/// supporting a two-phase deletion process where resources are first marked for
68/// deletion and then actually deleted.
69///
70/// # State Transitions
71///
72/// ```text
73/// NotDeleted → Pending → Deleted
74/// ```
75///
76/// # Flow Rules
77///
78/// Resources with `Pending` or `Deleted` status cannot be involved in data flows
79/// (both as source and destination).
80#[derive(Default, PartialEq, Debug, Clone, Eq, Copy)]
81pub enum DeletionPolicy {
82    /// Resource is active and can participate in flows (default)
83    #[default]
84    NotDeleted,
85    /// Resource is marked for deletion but not yet removed
86    Pending,
87    /// Resource has been fully deleted
88    Deleted,
89}
90
91impl From<bool> for DeletionPolicy {
92    fn from(deleted: bool) -> Self {
93        if deleted { DeletionPolicy::Deleted } else { DeletionPolicy::NotDeleted }
94    }
95}
96
97/// Policy for a resource that controls compliance checking for data flows.
98///
99/// A `Policy` combines multiple dimensions of access control and resource management
100/// to determine whether data flows involving a resource should be permitted.
101///
102/// # Fields
103///
104/// - **`confidentiality`**: Controls whether the resource contains sensitive data
105/// - **`integrity`**: Numeric trust level (0 = lowest, higher = more trusted)
106/// - **`deleted`**: Tracks deletion status through a multi-phase process
107/// - **`consent`**: WIP...
108///
109/// # Policy Evaluation
110///
111/// When evaluating flows between resources, policies are checked to ensure:
112/// 1. No deleted resources are involved
113/// 2. Integrity levels are compatible (source >= destination)
114/// 3. Confidentiality is preserved (secret data doesn't leak to public)
115/// 4. All parties have given consent
116///
117/// This policy is used to check the compliance of input/output flows of the associated resource.
118#[derive(Clone, Debug, Eq, PartialEq)]
119pub struct Policy {
120    /// Confidentiality level of the resource
121    confidentiality: ConfidentialityPolicy,
122    /// Integrity level (0 = lowest trust, higher values = more trusted)
123    integrity: u32,
124    /// Deletion status of the resource
125    deleted: DeletionPolicy,
126    /// Whether the resource owner consent is required for flows
127    consent: bool,
128}
129
130impl Default for Policy {
131    fn default() -> Self {
132        Policy {
133            confidentiality: ConfidentialityPolicy::Public,
134            integrity: 0,
135            deleted: DeletionPolicy::NotDeleted,
136            consent: false,
137        }
138    }
139}
140
141impl Policy {
142    /// Creates a new policy with the specified parameters.
143    ///
144    /// # Arguments
145    ///
146    /// * `confidentiality` - The confidentiality level for the resource
147    /// * `integrity` - The integrity level (0 = lowest, higher = more trusted)
148    /// * `deleted` - The deletion status
149    /// * `consent` - Whether the resource owner consent is required for flows
150    pub fn new(
151        confidentiality: ConfidentialityPolicy,
152        integrity: u32,
153        deleted: DeletionPolicy,
154        consent: bool,
155    ) -> Self {
156        Self { confidentiality, integrity, deleted, consent }
157    }
158
159    /// Returns true if the resource contains confidential data.
160    ///
161    /// This is a convenience method that checks if the confidentiality policy
162    /// is set to `Secret`.
163    pub fn is_confidential(&self) -> bool {
164        self.confidentiality == ConfidentialityPolicy::Secret
165    }
166
167    /// Returns true if the resource is deleted or pending deletion.
168    ///
169    /// Resources that are deleted cannot participate in data flows.
170    pub fn is_deleted(&self) -> bool {
171        self.deleted != DeletionPolicy::NotDeleted
172    }
173
174    /// Returns true if the resource is pending deletion.
175    ///
176    /// This indicates the resource has been marked for deletion but hasn't
177    /// been fully removed yet.
178    pub fn is_pending_deletion(&self) -> bool {
179        self.deleted == DeletionPolicy::Pending
180    }
181
182    /// Returns the integrity level of the resource.
183    ///
184    /// Higher values indicate more trusted data. For flows to be permitted,
185    /// the source integrity must be greater than or equal to the destination integrity.
186    pub fn get_integrity(&self) -> u32 {
187        self.integrity
188    }
189
190    /// Returns true if the resource owner has given consent for flows.
191    ///
192    /// When consent is false, flows involving this resource should be denied.
193    pub fn get_consent(&self) -> bool {
194        self.consent
195    }
196
197    /// Updates the consent flag for this policy.
198    ///
199    /// Returns `PolicyUpdated` if the consent was successfully changed,
200    /// or `PolicyNotUpdated` if the resource is deleted and cannot be modified.
201    pub fn with_consent(&mut self, consent: bool) -> ComplianceResponse {
202        if !self.is_deleted() {
203            self.consent = consent;
204            ComplianceResponse::PolicyUpdated
205        } else {
206            ComplianceResponse::PolicyNotUpdated
207        }
208    }
209
210    /// Updates the integrity level for this policy.
211    ///
212    /// Returns `PolicyUpdated` if the integrity was successfully changed,
213    /// or `PolicyNotUpdated` if the resource is deleted and cannot be modified.
214    ///
215    /// # Arguments
216    ///
217    /// * `integrity` - The new integrity level
218    pub fn with_integrity(&mut self, integrity: u32) -> ComplianceResponse {
219        if !self.is_deleted() {
220            self.integrity = integrity;
221            ComplianceResponse::PolicyUpdated
222        } else {
223            ComplianceResponse::PolicyNotUpdated
224        }
225    }
226
227    /// Updates the confidentiality level for this policy.
228    ///
229    /// Returns `PolicyUpdated` if the confidentiality was successfully changed,
230    /// or `PolicyNotUpdated` if the resource is deleted and cannot be modified.
231    ///
232    /// # Arguments
233    ///
234    /// * `confidentiality` - The new confidentiality level
235    pub fn with_confidentiality(
236        &mut self,
237        confidentiality: ConfidentialityPolicy,
238    ) -> ComplianceResponse {
239        if !self.is_deleted() {
240            self.confidentiality = confidentiality;
241            ComplianceResponse::PolicyUpdated
242        } else {
243            ComplianceResponse::PolicyNotUpdated
244        }
245    }
246
247    /// Marks the resource for deletion.
248    ///
249    /// This transitions the resource from `NotDeleted` to `Pending` deletion status.
250    /// Once marked for deletion, the policy cannot be further modified.
251    ///
252    /// Returns `PolicyUpdated` if the deletion was successfully marked as pending,
253    /// or `PolicyNotUpdated` if the resource is already deleted or pending deletion.
254    pub fn deleted(&mut self) -> ComplianceResponse {
255        if !self.is_deleted() {
256            self.deleted = DeletionPolicy::Pending;
257            ComplianceResponse::PolicyUpdated
258        } else {
259            ComplianceResponse::PolicyNotUpdated
260        }
261    }
262
263    /// Marks the deletion as enforced for a resource that is pending deletion.
264    ///
265    /// This transitions the resource from `Pending` to `Deleted` status.
266    /// This method should be called after the actual deletion has been performed.
267    ///
268    /// Returns `PolicyUpdated` if the deletion was successfully marked,
269    /// or `PolicyNotUpdated` if the resource is not pending deletion.
270    pub fn deletion_enforced(&mut self) -> ComplianceResponse {
271        if self.is_pending_deletion() {
272            self.deleted = DeletionPolicy::Deleted;
273            ComplianceResponse::PolicyUpdated
274        } else {
275            ComplianceResponse::PolicyNotUpdated
276        }
277    }
278}
279
280/// The main compliance service that manages policies and evaluates flows.
281///
282/// `ComplianceService` implements the `Service` trait from the Tower library,
283/// providing an asynchronous interface for handling compliance requests.
284/// It combines policy storage and evaluation logic in a single service.
285///
286/// # Features
287///
288/// - **Policy Management**: Store, retrieve, and update resource policies
289/// - **Flow Evaluation**: Check whether data flows comply with policies
290/// - **Thread Safety**: Safe for concurrent use across multiple threads
291/// - **Async Interface**: Non-blocking operations using Tower's Service trait
292///
293/// # Request Types
294///
295/// The service handles several types of compliance requests:
296///
297/// - `EvalPolicies` - Evaluate whether a flow is permitted
298/// - `GetPolicy` / `GetPolicies` - Retrieve existing policies
299/// - `SetPolicy` - Set complete policy for a resource
300/// - `SetConfidentiality` / `SetIntegrity` / `SetConsent` - Update specific policy fields
301/// - `SetDeleted` - Mark resources for deletion
302///
303/// # Operating Modes
304///
305/// ## Normal Mode (default)
306/// - Used in production
307/// - Unknown resources get default policies automatically
308/// - More forgiving for dynamic resource discovery
309///
310/// ## Cache Mode
311/// - Used primarily in testing
312/// - Unknown resources cause `PolicyNotFound` errors
313/// - Enforces explicit policy management
314///
315/// # Error Handling
316///
317/// The service returns `TraceabilityError` for various failure conditions:
318/// - `PolicyNotFound` - Resource not found (in cache mode)
319/// - `DirectPolicyViolation` - Flow violates compliance rules
320/// - `InternalTrace2eError` - Internal service errors
321#[derive(Clone, Debug)]
322pub struct ComplianceService<C = ConsentService> {
323    /// Whether to operate in cache mode (true) or normal mode (false)
324    cache_mode: bool,
325    /// Thread-safe storage for resource policies
326    policies: Arc<DashMap<Resource, Policy>>,
327    /// Consent service
328    consent: C,
329}
330
331impl Default for ComplianceService {
332    fn default() -> Self {
333        Self {
334            cache_mode: false,
335            policies: Arc::new(DashMap::new()),
336            consent: ConsentService::default(),
337        }
338    }
339}
340
341impl ComplianceService<ConsentService> {
342    pub fn new_with_consent(consent: ConsentService) -> Self {
343        Self { cache_mode: false, policies: Arc::new(DashMap::new()), consent }
344    }
345
346    /// Creates a new PolicyMap in cache mode for testing purposes.
347    ///
348    /// In cache mode, requests for unknown resources will return
349    /// `PolicyNotFound` errors instead of default policies.
350    #[cfg(test)]
351    fn init_cache() -> Self {
352        Self {
353            cache_mode: true,
354            policies: Arc::new(DashMap::new()),
355            consent: ConsentService::default(),
356        }
357    }
358
359    /// Evaluates whether a data flow is compliant with the given policies.
360    ///
361    /// This function implements the core compliance logic by checking multiple policy
362    /// dimensions against a set of rules that determine whether data can flow from
363    /// source resources to a destination resource.
364    ///
365    /// # Policy Evaluation Rules
366    ///
367    /// A flow is **permitted** only when ALL of the following conditions are met:
368    ///
369    /// 1. **No Deleted Resources**: Neither source nor destination is deleted or pending deletion
370    /// 2. **Integrity Preservation**: Source integrity ≥ destination integrity
371    /// 3. **Confidentiality Protection**: Secret data cannot flow to public destinations
372    /// 4. **Consent Required**: Both source and destination must have consent (when enforced)
373    ///
374    /// # Arguments
375    ///
376    /// * `source_policies` - Map of node IDs to their resource policies (sources)
377    /// * `destination_policy` - Policy of the destination resource
378    ///
379    /// # Returns
380    ///
381    /// - `Ok(ComplianceResponse::Grant)` if the flow is permitted
382    /// - `Err(TraceabilityError::DirectPolicyViolation)` if any rule is violated
383    ///
384    /// # Flow Scenarios
385    ///
386    /// ## ✅ Permitted Flows
387    /// - Public (integrity 5) → Public (integrity 3) - integrity preserved
388    /// - Secret (integrity 5) → Secret (integrity 3) - confidentiality maintained
389    /// - Public (integrity 5) → Secret (integrity 3) - upgrading confidentiality
390    ///
391    /// ## ❌ Blocked Flows  
392    /// - Any deleted/pending resource involved
393    /// - Public (integrity 3) → Public (integrity 5) - integrity violation
394    /// - Secret (integrity 5) → Public (integrity 3) - confidentiality leak
395    /// - Any resource without consent (when enforced)
396    ///
397    /// # Multi-Node Support
398    ///
399    /// The function supports evaluating flows that involve multiple source nodes,
400    /// checking that ALL source policies are compatible with the destination.
401    /// Node-based policies are not yet implemented.
402    async fn eval_policies(
403        &self,
404        source_policies: HashMap<String, HashMap<Resource, Policy>>,
405        destination: Resource,
406    ) -> Result<ComplianceResponse, TraceabilityError> {
407        let destination_policy = self.get_policy(&destination)?;
408
409        // Merge local and remote source policies, ignoring the node
410        // TODO: implement node based policies
411        for (node, source_policy_batch) in source_policies {
412            for (source, source_policy) in source_policy_batch {
413                if source_policy.get_consent() {
414                    // If any source policy requests consent, ensure consent is granted
415                    // for the (source, destination) pair before evaluating other policies.
416                    // Destination node id is currently unknown here; use None.
417                    let mut consent = self.consent.clone();
418
419                    // TODO: add timeout handling for consent requests
420                    // TODO: make this non-blocking and avoid awaiting inside the loop
421                    // TODO: handle consent requests through M2M service for remote sources
422                    // TODO: handle consent decisions through O2M service
423                    let _ = consent
424                        .call(ConsentRequest::RequestConsent {
425                            source: source.clone(),
426                            destination: (Some(node.clone()), destination.clone()),
427                        })
428                        .await
429                        .map_err(|_| TraceabilityError::InternalTrace2eError)
430                        .map(|consent| match consent {
431                            ConsentResponse::Consent(true) => Ok(()),
432                            ConsentResponse::Consent(false) => {
433                                Err(TraceabilityError::DirectPolicyViolation)
434                            }
435                            _ => Err(TraceabilityError::InternalTrace2eError),
436                        })?;
437                }
438                // If the source or destination policy is deleted, the flow is not compliant
439                if source_policy.is_deleted() || destination_policy.is_deleted() {
440                    #[cfg(feature = "trace2e_tracing")]
441                    info!(
442                        "[compliance] EvalPolicies: source_policy.deleted: {:?}, destination_policy.deleted: {:?}",
443                        source_policy.is_deleted(),
444                        destination_policy.is_deleted()
445                    );
446                    #[cfg(feature = "enforcement_mocking")]
447                    if source_policy.is_pending_deletion() {
448                        #[cfg(feature = "trace2e_tracing")]
449                        info!("[compliance] Enforcing deletion policy for source");
450                    }
451                    #[cfg(feature = "enforcement_mocking")]
452                    if destination_policy.is_pending_deletion() {
453                        #[cfg(feature = "trace2e_tracing")]
454                        info!("[compliance] Enforcing deletion policy for destination");
455                    }
456
457                    return Err(TraceabilityError::DirectPolicyViolation);
458                }
459
460                // Integrity check: Source integrity must be greater than or equal to destination
461                // integrity
462                if source_policy.integrity < destination_policy.integrity {
463                    return Err(TraceabilityError::DirectPolicyViolation);
464                }
465
466                // Confidentiality check: Secret data cannot flow to public destinations
467                if source_policy.confidentiality == ConfidentialityPolicy::Secret
468                    && destination_policy.confidentiality == ConfidentialityPolicy::Public
469                {
470                    return Err(TraceabilityError::DirectPolicyViolation);
471                }
472            }
473        }
474
475        Ok(ComplianceResponse::Grant)
476    }
477}
478
479impl ComplianceService {
480    /// Retrieves the policy for a specific resource.
481    ///
482    /// # Behavior by Mode
483    ///
484    /// - **Normal mode**: Returns default policy if resource not found
485    /// - **Cache mode**: Returns `PolicyNotFound` error if resource not found
486    ///
487    /// # Arguments
488    ///
489    /// * `resource` - The resource to look up
490    ///
491    /// # Errors
492    ///
493    /// Returns `PolicyNotFound` error in cache mode when the resource is not found.
494    fn get_policy(&self, resource: &Resource) -> Result<Policy, TraceabilityError> {
495        self.policies.get(resource).map(|p| p.to_owned()).map_or(
496            if self.cache_mode {
497                Err(TraceabilityError::PolicyNotFound(resource.to_owned()))
498            } else {
499                Ok(Policy::default())
500            },
501            Ok,
502        )
503    }
504
505    /// Retrieves policies for a set of resources.
506    ///
507    /// Stream resources are automatically filtered out as they don't have policies.
508    ///
509    /// # Behavior by Mode
510    ///
511    /// - **Normal mode**: Unknown resources get default policies
512    /// - **Cache mode**: Unknown resources cause the entire operation to fail
513    ///
514    /// # Arguments
515    ///
516    /// * `resources` - Set of resources to look up
517    ///
518    /// # Returns
519    ///
520    /// A map from resources to their policies, excluding stream resources.
521    ///
522    /// # Errors
523    ///
524    /// Returns `PolicyNotFound` error in cache mode when any resource is not found.
525    fn get_policies(
526        &self,
527        resources: HashSet<Resource>,
528    ) -> Result<HashMap<Resource, Policy>, TraceabilityError> {
529        let mut policies_set = HashMap::new();
530        for resource in resources {
531            // Get the policy from the local policies, streams have no policies
532            if resource.is_stream().is_none() {
533                if let Ok(policy) = self.get_policy(&resource) {
534                    policies_set.insert(resource, policy);
535                } else if !self.cache_mode {
536                    policies_set.insert(resource, Policy::default());
537                } else {
538                    return Err(TraceabilityError::PolicyNotFound(resource));
539                }
540            }
541        }
542        Ok(policies_set)
543    }
544
545    /// Sets the complete policy for a specific resource.
546    ///
547    /// This replaces any existing policy for the resource. If the resource
548    /// is already deleted, the update is rejected.
549    ///
550    /// # Arguments
551    ///
552    /// * `resource` - The resource to update
553    /// * `policy` - The new policy to set
554    ///
555    /// # Returns
556    ///
557    /// - `PolicyUpdated` if the policy was successfully set
558    /// - `PolicyNotUpdated` if the resource is deleted and cannot be modified
559    fn set_policy(&self, resource: Resource, policy: Policy) -> ComplianceResponse {
560        // Enforce deleted policy
561        if self.policies.get(&resource).is_some_and(|policy| policy.is_deleted()) {
562            return ComplianceResponse::PolicyNotUpdated;
563        }
564        self.policies.insert(resource, policy);
565        ComplianceResponse::PolicyUpdated
566    }
567
568    /// Sets the confidentiality level for a specific resource.
569    ///
570    /// Creates a default policy if the resource doesn't exist.
571    /// Updates are rejected if the resource is deleted.
572    ///
573    /// # Arguments
574    ///
575    /// * `resource` - The resource to update
576    /// * `confidentiality` - The new confidentiality level
577    fn set_confidentiality(
578        &self,
579        resource: Resource,
580        confidentiality: ConfidentialityPolicy,
581    ) -> ComplianceResponse {
582        let mut response = ComplianceResponse::PolicyNotUpdated;
583        self.policies
584            .entry(resource)
585            .and_modify(|policy| {
586                response = policy.with_confidentiality(confidentiality);
587            })
588            .or_insert_with(|| {
589                let mut policy = Policy::default();
590                response = policy.with_confidentiality(confidentiality);
591                policy
592            });
593        response
594    }
595
596    /// Sets the integrity level for a specific resource.
597    ///
598    /// Creates a default policy if the resource doesn't exist.
599    /// Updates are rejected if the resource is deleted.
600    ///
601    /// # Arguments
602    ///
603    /// * `resource` - The resource to update
604    /// * `integrity` - The new integrity level
605    fn set_integrity(&self, resource: Resource, integrity: u32) -> ComplianceResponse {
606        let mut response = ComplianceResponse::PolicyNotUpdated;
607        self.policies
608            .entry(resource)
609            .and_modify(|policy| {
610                response = policy.with_integrity(integrity);
611            })
612            .or_insert_with(|| {
613                let mut policy = Policy::default();
614                response = policy.with_integrity(integrity);
615                policy
616            });
617        response
618    }
619
620    /// Marks a specific resource for deletion.
621    ///
622    /// Creates a default policy if the resource doesn't exist, then marks it for deletion.
623    /// Updates are rejected if the resource is already deleted.
624    ///
625    /// # Arguments
626    ///
627    /// * `resource` - The resource to mark for deletion
628    fn set_deleted(&self, resource: Resource) -> ComplianceResponse {
629        let mut response = ComplianceResponse::PolicyNotUpdated;
630        self.policies
631            .entry(resource)
632            .and_modify(|policy| {
633                response = policy.deleted();
634            })
635            .or_insert_with(|| {
636                let mut policy = Policy::default();
637                response = policy.deleted();
638                policy
639            });
640        response
641    }
642
643    /// Sets the consent flag for a specific resource.
644    ///
645    /// Creates a default policy if the resource doesn't exist.
646    /// Updates are rejected if the resource is deleted.
647    ///
648    /// # Arguments
649    ///
650    /// * `resource` - The resource to update
651    /// * `consent` - The new consent value
652    fn set_consent(&self, resource: Resource, consent: bool) -> ComplianceResponse {
653        let mut response = ComplianceResponse::PolicyNotUpdated;
654        self.policies
655            .entry(resource)
656            .and_modify(|policy| {
657                response = policy.with_consent(consent);
658            })
659            .or_insert_with(|| {
660                let mut policy = Policy::default();
661                response = policy.with_consent(consent);
662                policy
663            });
664        response
665    }
666}
667
668impl Service<ComplianceRequest> for ComplianceService<ConsentService> {
669    type Response = ComplianceResponse;
670    type Error = TraceabilityError;
671    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
672
673    fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
674        Poll::Ready(Ok(()))
675    }
676
677    fn call(&mut self, request: ComplianceRequest) -> Self::Future {
678        let this = self.clone();
679        Box::pin(async move {
680            match request {
681                ComplianceRequest::EvalPolicies { source_policies, destination } => {
682                    #[cfg(feature = "trace2e_tracing")]
683                    info!(
684                        "[compliance] CheckCompliance: source_policies: {:?}, destination: {:?}",
685                        source_policies, destination
686                    );
687                    this.eval_policies(source_policies, destination).await
688                }
689                ComplianceRequest::GetPolicy(resource) => {
690                    #[cfg(feature = "trace2e_tracing")]
691                    info!("[compliance] GetPolicy: resource: {:?}", resource);
692                    Ok(ComplianceResponse::Policy(this.get_policy(&resource)?))
693                }
694                ComplianceRequest::GetPolicies(resources) => {
695                    #[cfg(feature = "trace2e_tracing")]
696                    info!("[compliance] GetPolicies: resources: {:?}", resources);
697                    Ok(ComplianceResponse::Policies(this.get_policies(resources)?))
698                }
699                ComplianceRequest::SetPolicy { resource, policy } => {
700                    #[cfg(feature = "trace2e_tracing")]
701                    info!("[compliance] SetPolicy: resource: {:?}, policy: {:?}", resource, policy);
702                    Ok(this.set_policy(resource, policy))
703                }
704                ComplianceRequest::SetConfidentiality { resource, confidentiality } => {
705                    #[cfg(feature = "trace2e_tracing")]
706                    info!(
707                        "[compliance] SetConfidentiality: resource: {:?}, confidentiality: {:?}",
708                        resource, confidentiality
709                    );
710                    Ok(this.set_confidentiality(resource, confidentiality))
711                }
712                ComplianceRequest::SetIntegrity { resource, integrity } => {
713                    #[cfg(feature = "trace2e_tracing")]
714                    info!(
715                        "[compliance] SetIntegrity: resource: {:?}, integrity: {:?}",
716                        resource, integrity
717                    );
718                    Ok(this.set_integrity(resource, integrity))
719                }
720                ComplianceRequest::SetDeleted(resource) => {
721                    #[cfg(feature = "trace2e_tracing")]
722                    info!("[compliance] SetDeleted: resource: {:?}", resource);
723                    Ok(this.set_deleted(resource))
724                }
725                ComplianceRequest::SetConsent { resource, consent } => {
726                    #[cfg(feature = "trace2e_tracing")]
727                    info!(
728                        "[compliance] SetConsent: resource: {:?}, consent: {:?}",
729                        resource, consent
730                    );
731                    Ok(this.set_consent(resource, consent))
732                }
733            }
734        })
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741    use crate::traceability::naming::Resource;
742
743    // Helper functions to reduce test code duplication
744    fn create_public_policy(integrity: u32) -> Policy {
745        Policy::new(ConfidentialityPolicy::Public, integrity, DeletionPolicy::NotDeleted, false)
746    }
747
748    fn create_secret_policy(integrity: u32) -> Policy {
749        Policy::new(ConfidentialityPolicy::Secret, integrity, DeletionPolicy::NotDeleted, false)
750    }
751
752    fn create_deleted_policy(integrity: u32) -> Policy {
753        Policy::new(ConfidentialityPolicy::Public, integrity, DeletionPolicy::Pending, false)
754    }
755
756    fn init_tracing() {
757        #[cfg(feature = "trace2e_tracing")]
758        crate::trace2e_tracing::init();
759    }
760
761    #[tokio::test]
762    async fn unit_compliance_set_policy_basic() {
763        init_tracing();
764        let compliance = ComplianceService::default();
765        let process = Resource::new_process_mock(0);
766
767        let policy = create_secret_policy(5);
768
769        // First time setting policy should return PolicyUpdated
770        assert_eq!(
771            compliance.set_policy(process.clone(), policy),
772            ComplianceResponse::PolicyUpdated
773        );
774
775        let new_policy = create_secret_policy(3);
776
777        // Updating existing policy should also return PolicyUpdated
778        assert_eq!(compliance.set_policy(process, new_policy), ComplianceResponse::PolicyUpdated);
779    }
780
781    #[tokio::test]
782    async fn unit_compliance_policy_evaluation_scenarios() {
783        init_tracing();
784        let compliance = ComplianceService::default();
785        // Test integrity constraints
786        let test_cases = [
787            // (source_policy, dest_policy, should_pass, description)
788            (create_public_policy(5), create_public_policy(3), true, "integrity pass: 5 >= 3"),
789            (create_public_policy(3), create_public_policy(5), false, "integrity fail: 3 < 5"),
790            (
791                create_secret_policy(5),
792                create_secret_policy(3),
793                true,
794                "confidentiality pass: secret -> secret",
795            ),
796            (
797                create_secret_policy(5),
798                create_public_policy(3),
799                false,
800                "confidentiality fail: secret -> public",
801            ),
802            (
803                create_public_policy(5),
804                create_secret_policy(3),
805                true,
806                "confidentiality pass: public -> secret",
807            ),
808        ];
809        let mock_file = Resource::new_file("/tmp/dest".to_string());
810
811        for (source_policy, dest_policy, should_pass, description) in test_cases {
812            compliance.set_policy(mock_file.clone(), dest_policy);
813            let result = compliance
814                .eval_policies(
815                    HashMap::from([(
816                        String::new(),
817                        HashMap::from([(Resource::new_process_mock(0), source_policy)]),
818                    )]),
819                    mock_file.clone(),
820                )
821                .await;
822
823            if should_pass {
824                assert!(
825                    result.is_ok_and(|r| r == ComplianceResponse::Grant),
826                    "Test failed: {description}"
827                );
828            } else {
829                assert!(
830                    result.is_err_and(|e| e == TraceabilityError::DirectPolicyViolation),
831                    "Test failed: {description}"
832                );
833            }
834        }
835    }
836
837    #[tokio::test]
838    async fn unit_compliance_default_policies() {
839        init_tracing();
840        let compliance = ComplianceService::default();
841        let mock_file = Resource::new_file("/tmp/dest".to_string());
842        // Test 1: All default policies should pass
843        assert!(
844            compliance
845                .eval_policies(
846                    HashMap::from([
847                        (
848                            String::new(),
849                            HashMap::from([
850                                (Resource::new_process_mock(0), Policy::default()),
851                                (Resource::new_process_mock(1), Policy::default())
852                            ])
853                        ),
854                        (
855                            "10.0.0.1".to_string(),
856                            HashMap::from([(Resource::new_process_mock(0), Policy::default())])
857                        )
858                    ]),
859                    mock_file.clone()
860                )
861                .await
862                .is_ok_and(|r| r == ComplianceResponse::Grant)
863        );
864
865        // Test 2: Mixed default and explicit policies - integrity violation
866        let dest_policy = create_public_policy(2);
867        compliance.set_policy(mock_file.clone(), dest_policy);
868        assert!(
869            compliance
870                .eval_policies(
871                    HashMap::from([
872                        (
873                            String::new(),
874                            HashMap::from([(Resource::new_process_mock(0), Policy::default())])
875                        ), // integrity: 0
876                        (
877                            "10.0.0.1".to_string(),
878                            HashMap::from([(Resource::new_process_mock(0), Policy::default())])
879                        )
880                    ]),
881                    mock_file // integrity: 2, so 0 < 2 should fail
882                )
883                .await
884                .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
885        );
886    }
887
888    #[tokio::test]
889    async fn unit_compliance_service_eval_policies_requests() {
890        init_tracing();
891        let mut compliance = ComplianceService::default();
892
893        let file = Resource::new_file("/tmp/dest".to_string());
894        let policy = create_public_policy(3);
895        compliance.set_policy(file.clone(), policy);
896
897        // Test case 1: Valid policy flow - should grant
898        let grant_request = ComplianceRequest::EvalPolicies {
899            source_policies: HashMap::from([(
900                String::new(),
901                HashMap::from([(Resource::new_process_mock(0), create_public_policy(5))]),
902            )]),
903            destination: file.clone(),
904        };
905        assert_eq!(compliance.call(grant_request).await.unwrap(), ComplianceResponse::Grant);
906
907        // Test case 2: Invalid policy flow - should deny
908        let deny_request = ComplianceRequest::EvalPolicies {
909            source_policies: HashMap::from([(
910                String::new(),
911                HashMap::from([(Resource::new_process_mock(0), create_secret_policy(5))]),
912            )]),
913            destination: file.clone(),
914        };
915        assert_eq!(
916            compliance.call(deny_request).await.unwrap_err(),
917            TraceabilityError::DirectPolicyViolation
918        );
919    }
920
921    #[tokio::test]
922    async fn unit_compliance_service_complex_policy_scenario() {
923        init_tracing();
924        let mut compliance = ComplianceService::default();
925
926        let file = Resource::new_file("/tmp/dest".to_string());
927        let high_policy = create_secret_policy(10);
928        let medium_policy = create_public_policy(5);
929        let low_policy = create_public_policy(1);
930
931        compliance.set_policy(file.clone(), medium_policy.clone());
932
933        // Test 1: High (Secret) -> Medium (Public): Should fail due to confidentiality
934        let request1 = ComplianceRequest::EvalPolicies {
935            source_policies: HashMap::from([
936                (
937                    String::new(),
938                    HashMap::from([(Resource::new_process_mock(0), Policy::default())]),
939                ),
940                (
941                    "10.0.0.1".to_string(),
942                    HashMap::from([(Resource::new_process_mock(0), high_policy.clone())]),
943                ),
944            ]),
945            destination: file.clone(),
946        };
947        assert_eq!(
948            compliance.call(request1).await.unwrap_err(),
949            TraceabilityError::DirectPolicyViolation
950        ); // Secret -> Public fails
951
952        // Test 2: Medium -> Low: Should pass (integrity 5 >= 1, public -> public)
953        compliance.set_policy(file.clone(), low_policy.clone());
954        let request2 = ComplianceRequest::EvalPolicies {
955            source_policies: HashMap::from([(
956                String::new(),
957                HashMap::from([(Resource::new_process_mock(0), medium_policy)]),
958            )]),
959            destination: file.clone(),
960        };
961        assert_eq!(compliance.call(request2).await.unwrap(), ComplianceResponse::Grant);
962
963        // Test 3: Low -> High: Should fail due to integrity (1 < 10)
964        compliance.set_policy(file.clone(), high_policy.clone());
965        let request3 = ComplianceRequest::EvalPolicies {
966            source_policies: HashMap::from([(
967                String::new(),
968                HashMap::from([(Resource::new_process_mock(0), low_policy)]),
969            )]),
970            destination: file,
971        };
972        assert_eq!(
973            compliance.call(request3).await.unwrap_err(),
974            TraceabilityError::DirectPolicyViolation
975        );
976    }
977
978    #[tokio::test]
979    async fn unit_compliance_get_policies_empty() {
980        #[cfg(feature = "trace2e_tracing")]
981        crate::trace2e_tracing::init();
982        let compliance = ComplianceService::default();
983        let process = Resource::new_process_mock(0);
984        let file = Resource::new_file("/tmp/test".to_string());
985
986        assert_eq!(
987            compliance.get_policies(HashSet::from([process.clone(), file.clone()])).unwrap(),
988            HashMap::from([(process, Policy::default()), (file, Policy::default())])
989        );
990    }
991
992    #[tokio::test]
993    async fn unit_compliance_get_policies_scenarios() {
994        init_tracing();
995        let compliance = ComplianceService::default();
996
997        // Test resources
998        let process = Resource::new_process_mock(0);
999        let file = Resource::new_file("/tmp/test".to_string());
1000
1001        // Test policies
1002        let process_policy = create_secret_policy(7);
1003        let file_policy = create_public_policy(3);
1004
1005        // Test 1: Single resource with policy
1006        compliance.set_policy(process.clone(), process_policy.clone());
1007        assert_eq!(
1008            compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1009            HashMap::from([(process.clone(), process_policy.clone())])
1010        );
1011
1012        // Test 2: Multiple resources with policies
1013        compliance.set_policy(file.clone(), file_policy.clone());
1014        assert_eq!(
1015            compliance.get_policies(HashSet::from([process.clone(), file.clone()])).unwrap(),
1016            HashMap::from([(process.clone(), process_policy.clone()), (file.clone(), file_policy)])
1017        );
1018
1019        // Test 3: Mixed existing and default policies
1020        let new_file = Resource::new_file("/tmp/new.txt".to_string());
1021        assert_eq!(
1022            compliance.get_policies(HashSet::from([process.clone(), new_file.clone()])).unwrap(),
1023            HashMap::from([(process, process_policy), (new_file, Policy::default())])
1024        );
1025    }
1026
1027    #[tokio::test]
1028    async fn unit_compliance_get_policies_edge_cases() {
1029        init_tracing();
1030        let compliance = ComplianceService::default();
1031        let process = Resource::new_process_mock(0);
1032
1033        // Test 1: Empty request
1034        assert_eq!(compliance.get_policies(HashSet::new()).unwrap(), HashMap::new());
1035
1036        // Test 2: Policy updates
1037        let initial_policy = create_public_policy(2);
1038        let updated_policy = create_secret_policy(9);
1039
1040        compliance.set_policy(process.clone(), initial_policy.clone());
1041        assert_eq!(
1042            compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1043            HashMap::from([(process.clone(), initial_policy.clone())])
1044        );
1045
1046        compliance.set_policy(process.clone(), updated_policy.clone());
1047        assert_eq!(
1048            compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1049            HashMap::from([(process, updated_policy)])
1050        );
1051    }
1052
1053    #[tokio::test]
1054    async fn unit_compliance_deleted_policy_behavior() {
1055        init_tracing();
1056        let compliance = ComplianceService::default();
1057        let process = Resource::new_process_mock(0);
1058        let mock_file = Resource::new_file("/tmp/dest".to_string());
1059
1060        // Create and set a deleted policy
1061        let deleted_policy = create_deleted_policy(5);
1062        compliance.set_policy(process.clone(), deleted_policy.clone());
1063
1064        // Test 1: Deleted policy is returned correctly
1065        assert_eq!(
1066            compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1067            HashMap::from([(process.clone(), deleted_policy.clone())])
1068        );
1069
1070        // Test 2: Cannot update deleted policy
1071        assert_eq!(
1072            compliance.set_policy(process.clone(), Policy::default()),
1073            ComplianceResponse::PolicyNotUpdated
1074        );
1075
1076        // Test 3: Policy remains deleted after update attempt
1077        assert_eq!(
1078            compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1079            HashMap::from([(process.clone(), deleted_policy.clone())])
1080        );
1081
1082        // Test 4: Deleted policies cause policy violations in evaluation
1083        assert!(
1084            compliance
1085                .eval_policies(
1086                    HashMap::from([(
1087                        String::new(),
1088                        HashMap::from([
1089                            (Resource::new_process_mock(0), deleted_policy.clone()),
1090                            (Resource::new_process_mock(1), Policy::default())
1091                        ])
1092                    )]),
1093                    mock_file.clone()
1094                )
1095                .await
1096                .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1097        );
1098
1099        assert!(
1100            compliance
1101                .eval_policies(
1102                    HashMap::from([(
1103                        String::new(),
1104                        HashMap::from([(Resource::new_process_mock(0), Policy::default())])
1105                    )]),
1106                    process.clone()
1107                )
1108                .await
1109                .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1110        );
1111    }
1112
1113    #[tokio::test]
1114    async fn unit_compliance_cache_mode_policy_not_found() {
1115        #[cfg(feature = "trace2e_tracing")]
1116        crate::trace2e_tracing::init();
1117
1118        let cache_policy_map = ComplianceService::init_cache();
1119        let process = Resource::new_process_mock(0);
1120
1121        // In cache mode, requesting policy for non-existent resource should return PolicyNotFound error
1122        assert!(
1123            cache_policy_map.get_policies(HashSet::from([process.clone()])).is_err_and(
1124                |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process)
1125            )
1126        );
1127    }
1128
1129    #[tokio::test]
1130    async fn unit_compliance_normal_mode_vs_cache_mode() {
1131        #[cfg(feature = "trace2e_tracing")]
1132        crate::trace2e_tracing::init();
1133
1134        let normal_policy_map = ComplianceService::default();
1135        let cache_policy_map = ComplianceService::init_cache();
1136        let process = Resource::new_process_mock(0);
1137
1138        // Normal mode should return default policy when resource not found
1139        assert_eq!(
1140            normal_policy_map.get_policies(HashSet::from([process.clone()])).unwrap(),
1141            HashMap::from([(process.clone(), Policy::default())])
1142        );
1143
1144        // Cache mode should return PolicyNotFound error when resource not found
1145        assert!(
1146            cache_policy_map.get_policies(HashSet::from([process.clone()])).is_err_and(
1147                |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process)
1148            )
1149        );
1150    }
1151
1152    #[tokio::test]
1153    async fn unit_compliance_cache_mode_with_existing_policies() {
1154        #[cfg(feature = "trace2e_tracing")]
1155        crate::trace2e_tracing::init();
1156
1157        let cache_policy_map = ComplianceService::init_cache();
1158        let process = Resource::new_process_mock(0);
1159
1160        let policy =
1161            Policy::new(ConfidentialityPolicy::Secret, 7, DeletionPolicy::NotDeleted, true);
1162
1163        // Set policy first
1164        cache_policy_map.set_policy(process.clone(), policy.clone());
1165
1166        // Now getting policy should work in cache mode
1167        assert_eq!(
1168            cache_policy_map.get_policies(HashSet::from([process.clone()])).unwrap(),
1169            HashMap::from([(process, policy)])
1170        );
1171    }
1172
1173    #[tokio::test]
1174    async fn unit_compliance_cache_mode_mixed_resources() {
1175        #[cfg(feature = "trace2e_tracing")]
1176        crate::trace2e_tracing::init();
1177
1178        let cache_policy_map = ComplianceService::init_cache();
1179        let process1 = Resource::new_process_mock(1);
1180        let process2 = Resource::new_process_mock(2);
1181
1182        let policy1 = Policy {
1183            confidentiality: ConfidentialityPolicy::Public,
1184            integrity: 3,
1185            deleted: DeletionPolicy::NotDeleted,
1186            consent: true,
1187        };
1188
1189        // Set policy only for process1
1190        cache_policy_map.set_policy(process1.clone(), policy1);
1191
1192        // Request policies for both processes - should fail because process2 has no policy
1193        assert!(
1194            cache_policy_map.get_policies(HashSet::from([process1, process2.clone()])).is_err_and(
1195                |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process2)
1196            )
1197        );
1198    }
1199
1200    #[tokio::test]
1201    async fn unit_compliance_cache_mode_empty_request() {
1202        #[cfg(feature = "trace2e_tracing")]
1203        crate::trace2e_tracing::init();
1204
1205        let cache_policy_map = ComplianceService::init_cache();
1206
1207        // Empty request should work the same in both modes
1208        assert_eq!(cache_policy_map.get_policies(HashSet::new()).unwrap(), HashMap::new());
1209    }
1210
1211    #[tokio::test]
1212    async fn unit_deletion_policy_state_transitions() {
1213        init_tracing();
1214
1215        // Test 1: Initial state and is_deleted/is_pending methods
1216        let mut policy = Policy::default();
1217        assert!(!policy.is_deleted());
1218        assert!(!policy.is_pending_deletion());
1219
1220        // Test 2: Transition from NotDeleted to Pending via deletion_request
1221        policy.deleted();
1222        assert!(policy.is_deleted()); // Pending counts as deleted
1223        assert!(policy.is_pending_deletion());
1224
1225        // Test 3: Multiple calls to deletion_request on Pending should not change state
1226        policy.deleted();
1227        assert!(policy.is_deleted());
1228        assert!(policy.is_pending_deletion());
1229
1230        // Test 4: Transition from Pending to Deleted via deletion_applied
1231        policy.deletion_enforced();
1232        assert!(policy.is_deleted());
1233        assert!(!policy.is_pending_deletion());
1234
1235        // Test 5: Multiple calls to deletion_applied on Deleted should not change state
1236        policy.deletion_enforced();
1237        assert!(policy.is_deleted());
1238        assert!(!policy.is_pending_deletion());
1239
1240        // Test 6: Test From<bool> conversion
1241        assert_eq!(DeletionPolicy::from(true), DeletionPolicy::Deleted);
1242        assert_eq!(DeletionPolicy::from(false), DeletionPolicy::NotDeleted);
1243    }
1244
1245    #[tokio::test]
1246    async fn unit_compliance_service_deletion_policy_workflow() {
1247        init_tracing();
1248        let mut compliance = ComplianceService::default();
1249
1250        let process = Resource::new_process_mock(0);
1251        let file = Resource::new_file("/tmp/sensitive.txt".to_string());
1252
1253        // Test 1: Set initial policies for resources
1254        let process_policy = create_secret_policy(5);
1255        let file_policy = create_public_policy(3);
1256
1257        let set_process_request = ComplianceRequest::SetPolicy {
1258            resource: process.clone(),
1259            policy: process_policy.clone(),
1260        };
1261        assert_eq!(
1262            compliance.call(set_process_request).await.unwrap(),
1263            ComplianceResponse::PolicyUpdated
1264        );
1265
1266        let set_file_request =
1267            ComplianceRequest::SetPolicy { resource: file.clone(), policy: file_policy.clone() };
1268        assert_eq!(
1269            compliance.call(set_file_request).await.unwrap(),
1270            ComplianceResponse::PolicyUpdated
1271        );
1272
1273        // Test 2: Verify policies are set correctly
1274        let get_process_request = ComplianceRequest::GetPolicy(process.clone());
1275        if let ComplianceResponse::Policy(retrieved_policy) =
1276            compliance.call(get_process_request).await.unwrap()
1277        {
1278            assert_eq!(retrieved_policy, process_policy);
1279            assert!(!retrieved_policy.is_deleted());
1280        } else {
1281            panic!("Expected Policy response");
1282        }
1283
1284        // Test 3: Create a policy with pending deletion status
1285        let mut pending_deletion_policy = create_secret_policy(7);
1286        pending_deletion_policy.deleted();
1287        assert!(pending_deletion_policy.is_pending_deletion());
1288
1289        let set_pending_request = ComplianceRequest::SetPolicy {
1290            resource: file.clone(),
1291            policy: pending_deletion_policy.clone(),
1292        };
1293        assert_eq!(
1294            compliance.call(set_pending_request).await.unwrap(),
1295            ComplianceResponse::PolicyUpdated
1296        );
1297
1298        // Test 4: Verify pending deletion policy is set
1299        let get_file_request = ComplianceRequest::GetPolicy(file.clone());
1300        if let ComplianceResponse::Policy(retrieved_policy) =
1301            compliance.call(get_file_request).await.unwrap()
1302        {
1303            assert_eq!(retrieved_policy, pending_deletion_policy);
1304            assert!(retrieved_policy.is_deleted());
1305            assert!(retrieved_policy.is_pending_deletion());
1306        } else {
1307            panic!("Expected Policy response");
1308        }
1309
1310        // Test 5: Try to update policy on resource with pending deletion - should fail
1311        let new_policy = create_public_policy(1);
1312        let update_pending_request =
1313            ComplianceRequest::SetPolicy { resource: file.clone(), policy: new_policy };
1314        assert_eq!(
1315            compliance.call(update_pending_request).await.unwrap(),
1316            ComplianceResponse::PolicyNotUpdated
1317        );
1318
1319        // Test 6: Create fully deleted policy and test it
1320        let mut deleted_policy = create_public_policy(2);
1321        deleted_policy.deleted();
1322        deleted_policy.deletion_enforced();
1323        assert!(deleted_policy.is_deleted());
1324        assert!(!deleted_policy.is_pending_deletion());
1325
1326        let new_resource = Resource::new_process_mock(1);
1327        let set_deleted_request = ComplianceRequest::SetPolicy {
1328            resource: new_resource.clone(),
1329            policy: deleted_policy.clone(),
1330        };
1331        assert_eq!(
1332            compliance.call(set_deleted_request).await.unwrap(),
1333            ComplianceResponse::PolicyUpdated
1334        );
1335
1336        // Test 7: Verify deleted policy blocks updates
1337        let another_policy = create_secret_policy(10);
1338        let update_deleted_request =
1339            ComplianceRequest::SetPolicy { resource: new_resource.clone(), policy: another_policy };
1340        assert_eq!(
1341            compliance.call(update_deleted_request).await.unwrap(),
1342            ComplianceResponse::PolicyNotUpdated
1343        );
1344
1345        // Test 8: Test policy evaluation with deleted resources - should fail
1346        let eval_with_deleted_source = ComplianceRequest::EvalPolicies {
1347            source_policies: HashMap::from([(
1348                String::new(),
1349                HashMap::from([(new_resource, deleted_policy)]),
1350            )]),
1351            destination: Resource::new_file("/tmp/dest".to_string()),
1352        };
1353        assert_eq!(
1354            compliance.call(eval_with_deleted_source).await.unwrap_err(),
1355            TraceabilityError::DirectPolicyViolation
1356        );
1357
1358        // Test 9: Test policy evaluation with deleted destination - should fail
1359        let eval_with_deleted_dest = ComplianceRequest::EvalPolicies {
1360            source_policies: HashMap::from([(
1361                String::new(),
1362                HashMap::from([(process, process_policy)]),
1363            )]),
1364            destination: Resource::new_file("/tmp/dest".to_string()),
1365        };
1366        assert_eq!(
1367            compliance.call(eval_with_deleted_dest).await.unwrap_err(),
1368            TraceabilityError::DirectPolicyViolation
1369        );
1370    }
1371}