Skip to main content

appcore_capabilities/
catalog.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: catalog.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/20 00:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/20 00:00:00 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11use crate::policy::enforce_requirements;
12use crate::{CapabilityError, CapabilityRequest, CapabilityResult};
13use appcore_contracts::ServiceId;
14use appcore_core::{CapabilityDescriptor, CapabilityName, CoreIdentity};
15use appcore_distributed_contracts::ServiceLeadershipGuard;
16use std::collections::HashMap;
17
18/// Runtime context used to authorize one local capability invocation.
19pub struct CapabilityEnforcementContext<'a> {
20    pub(crate) identity: &'a CoreIdentity,
21    pub(crate) service_id: &'a ServiceId,
22    pub(crate) leadership: Option<&'a dyn ServiceLeadershipGuard>,
23    pub(crate) now_ms: u64,
24    pub(crate) writes_allowed: bool,
25}
26
27impl<'a> CapabilityEnforcementContext<'a> {
28    /// Creates a context without leadership and with writes enabled.
29    pub fn new(identity: &'a CoreIdentity, service_id: &'a ServiceId, now_ms: u64) -> Self {
30        Self {
31            identity,
32            service_id,
33            leadership: None,
34            now_ms,
35            writes_allowed: true,
36        }
37    }
38
39    /// Supplies the service-scoped leadership guard used for fenced writes.
40    pub fn with_leadership(mut self, leadership: &'a dyn ServiceLeadershipGuard) -> Self {
41        self.leadership = Some(leadership);
42        self
43    }
44
45    /// Declares whether the host's current operational mode permits writes.
46    pub fn with_writes_allowed(mut self, writes_allowed: bool) -> Self {
47        self.writes_allowed = writes_allowed;
48        self
49    }
50}
51
52/// Immutable-source catalog of capability descriptors composed by a host.
53#[derive(Debug, Clone, Default)]
54pub struct CapabilityCatalog {
55    descriptors: HashMap<CapabilityName, CapabilityDescriptor>,
56}
57
58impl CapabilityCatalog {
59    /// Creates an empty descriptor catalog.
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// Builds a catalog and rejects duplicate capability names.
65    pub fn from_descriptors(
66        descriptors: impl IntoIterator<Item = CapabilityDescriptor>,
67    ) -> CapabilityResult<Self> {
68        let mut catalog = Self::new();
69        for descriptor in descriptors {
70            catalog.register_descriptor(descriptor)?;
71        }
72        Ok(catalog)
73    }
74
75    /// Registers one descriptor without attaching an executable handler.
76    pub fn register_descriptor(
77        &mut self,
78        descriptor: CapabilityDescriptor,
79    ) -> CapabilityResult<()> {
80        if self.descriptors.contains_key(&descriptor.name) {
81            return Err(CapabilityError::DescriptorAlreadyRegistered(
82                descriptor.name.clone(),
83            ));
84        }
85        self.descriptors.insert(descriptor.name.clone(), descriptor);
86        Ok(())
87    }
88
89    /// Returns the declared descriptor for `capability`.
90    pub fn descriptor(&self, capability: &CapabilityName) -> Option<&CapabilityDescriptor> {
91        self.descriptors.get(capability)
92    }
93
94    /// Returns all descriptors in deterministic capability-name order.
95    pub fn descriptors(&self) -> Vec<&CapabilityDescriptor> {
96        let mut descriptors = self.descriptors.values().collect::<Vec<_>>();
97        descriptors.sort_by(|left, right| left.name.as_str().cmp(right.name.as_str()));
98        descriptors
99    }
100
101    /// Resolves a locally declared descriptor and validates request semantics.
102    pub fn resolve_local(
103        &self,
104        request: &CapabilityRequest,
105    ) -> CapabilityResult<&CapabilityDescriptor> {
106        let descriptor = self
107            .descriptor(&request.capability)
108            .ok_or_else(|| CapabilityError::CapabilityNotDeclared(request.capability.clone()))?;
109        crate::policy::enforce_request_requirements(request, descriptor)?;
110        Ok(descriptor)
111    }
112
113    /// Resolves and authorizes a local invocation against host and lease state.
114    pub fn authorize_local(
115        &self,
116        request: &CapabilityRequest,
117        context: CapabilityEnforcementContext<'_>,
118    ) -> CapabilityResult<()> {
119        let descriptor = self.resolve_local(request)?;
120        enforce_requirements(
121            context.identity,
122            context.service_id,
123            request,
124            descriptor,
125            &context.identity.core_id,
126            context.leadership,
127            context.writes_allowed,
128            context.now_ms,
129        )
130    }
131}