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