Skip to main content

appcore_storage/
storage_capability.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: storage_capability.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/26 00:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/26 00:00:00 by dnettoRaw
8//      ###########      S: 2.0.0-beta.1
9// =============================================================================
10
11//! Explicit bounded capability descriptors for storage-provider preflight.
12
13use appcore_contracts::{ProviderConfig, ProviderId};
14use std::collections::{BTreeMap, BTreeSet};
15
16/// Version of the first storage capability descriptor contract.
17pub const STORAGE_CAPABILITY_DESCRIPTOR_VERSION_V1: u16 = 1;
18/// Deployment provider setting containing comma-separated required capabilities.
19pub const STORAGE_REQUIRED_CAPABILITIES_SETTING: &str = "required_capabilities";
20/// Exact number of capability kinds defined by the V1 descriptor.
21pub const STORAGE_CAPABILITY_COUNT_V1: usize = 7;
22/// Maximum provider descriptors admitted to one preflight catalog.
23pub const MAX_STORAGE_CAPABILITY_PROVIDERS_V1: usize = 32;
24
25/// One provider-independent storage guarantee in descriptor V1.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub enum StorageCapabilityV1 {
28    /// Real atomic unit-of-work transactions.
29    Transactions,
30    /// Caller-visible locking with documented exclusion semantics.
31    Locking,
32    /// Provider-consistent snapshots.
33    Snapshot,
34    /// Bounded incremental reads and writes.
35    Streaming,
36    /// Consistent backup while the provider remains available.
37    OnlineBackup,
38    /// Concurrent access from independent local processes.
39    MultiProcess,
40    /// Concurrent access from independent hosts.
41    MultiHost,
42}
43
44impl StorageCapabilityV1 {
45    /// Returns the stable deployment spelling.
46    pub const fn as_str(self) -> &'static str {
47        match self {
48            Self::Transactions => "transactions",
49            Self::Locking => "locking",
50            Self::Snapshot => "snapshot",
51            Self::Streaming => "streaming",
52            Self::OnlineBackup => "online_backup",
53            Self::MultiProcess => "multi_process",
54            Self::MultiHost => "multi_host",
55        }
56    }
57
58    fn parse(value: &str) -> Result<Self, StorageCapabilityError> {
59        match value {
60            "transactions" => Ok(Self::Transactions),
61            "locking" => Ok(Self::Locking),
62            "snapshot" => Ok(Self::Snapshot),
63            "streaming" => Ok(Self::Streaming),
64            "online_backup" => Ok(Self::OnlineBackup),
65            "multi_process" => Ok(Self::MultiProcess),
66            "multi_host" => Ok(Self::MultiHost),
67            _ => Err(StorageCapabilityError::UnknownRequirement),
68        }
69    }
70}
71
72impl std::fmt::Display for StorageCapabilityV1 {
73    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        formatter.write_str(self.as_str())
75    }
76}
77
78/// Typed, redacted storage capability preflight failure.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum StorageCapabilityError {
81    /// A provider advertised an invalid stable identity.
82    InvalidDescriptor,
83    /// A requirement used an unknown or empty capability spelling.
84    UnknownRequirement,
85    /// A capability appeared more than once in the bounded requirement list.
86    DuplicateRequirement(StorageCapabilityV1),
87    /// More provider descriptors were registered than the fixed catalog bound.
88    CatalogFull,
89    /// A provider descriptor was registered twice.
90    DuplicateProvider(ProviderId),
91    /// No descriptor exists for the explicitly selected provider.
92    ProviderUnavailable(ProviderId),
93    /// The selected provider does not supply an exact required guarantee.
94    MissingCapability {
95        /// Explicitly selected provider identity.
96        provider_id: ProviderId,
97        /// Provider-independent guarantee that is absent.
98        capability: StorageCapabilityV1,
99    },
100}
101
102impl std::fmt::Display for StorageCapabilityError {
103    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            Self::InvalidDescriptor => {
106                formatter.write_str("storage capability descriptor identity is invalid")
107            }
108            Self::UnknownRequirement => {
109                formatter.write_str("storage capability requirement is unknown")
110            }
111            Self::DuplicateRequirement(capability) => write!(
112                formatter,
113                "storage capability requirement is duplicated: {capability}"
114            ),
115            Self::CatalogFull => formatter.write_str("storage capability provider catalog is full"),
116            Self::DuplicateProvider(provider_id) => write!(
117                formatter,
118                "storage capability descriptor is duplicated for provider: {provider_id}"
119            ),
120            Self::ProviderUnavailable(provider_id) => write!(
121                formatter,
122                "storage capability descriptor is unavailable for provider: {provider_id}"
123            ),
124            Self::MissingCapability {
125                provider_id,
126                capability,
127            } => write!(
128                formatter,
129                "storage provider {provider_id} does not support required capability {capability}"
130            ),
131        }
132    }
133}
134
135impl std::error::Error for StorageCapabilityError {}
136
137/// Immutable V1 descriptor advertised by one storage provider.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct StorageCapabilityDescriptorV1 {
140    provider_id: ProviderId,
141    capabilities: BTreeSet<StorageCapabilityV1>,
142}
143
144impl StorageCapabilityDescriptorV1 {
145    /// Creates a bounded descriptor for one explicit provider identity.
146    pub fn new(
147        provider_id: ProviderId,
148        capabilities: impl IntoIterator<Item = StorageCapabilityV1>,
149    ) -> Self {
150        Self {
151            provider_id,
152            capabilities: capabilities.into_iter().collect(),
153        }
154    }
155
156    /// Returns the explicit descriptor version.
157    pub const fn descriptor_version(&self) -> u16 {
158        STORAGE_CAPABILITY_DESCRIPTOR_VERSION_V1
159    }
160
161    /// Returns the provider identity this descriptor binds.
162    pub fn provider_id(&self) -> &ProviderId {
163        &self.provider_id
164    }
165
166    /// Returns the bounded set of guarantees supplied by this provider.
167    pub fn capabilities(&self) -> &BTreeSet<StorageCapabilityV1> {
168        &self.capabilities
169    }
170
171    /// Reports whether the provider supplies an exact guarantee.
172    pub fn supports(&self, capability: StorageCapabilityV1) -> bool {
173        self.capabilities.contains(&capability)
174    }
175
176    /// Fails when any requested guarantee is absent.
177    pub fn validate(
178        &self,
179        requirements: &StorageCapabilityRequirementsV1,
180    ) -> Result<(), StorageCapabilityError> {
181        for capability in requirements.capabilities() {
182            if !self.supports(*capability) {
183                return Err(StorageCapabilityError::MissingCapability {
184                    provider_id: self.provider_id.clone(),
185                    capability: *capability,
186                });
187            }
188        }
189        Ok(())
190    }
191}
192
193/// Provider-independent bounded requirements resolved during manifest preflight.
194#[derive(Debug, Clone, Default, PartialEq, Eq)]
195pub struct StorageCapabilityRequirementsV1 {
196    capabilities: BTreeSet<StorageCapabilityV1>,
197}
198
199impl StorageCapabilityRequirementsV1 {
200    /// Creates no additional requirements.
201    pub fn new() -> Self {
202        Self::default()
203    }
204
205    /// Parses the opt-in deployment requirement setting without inference.
206    pub fn from_provider_config(config: &ProviderConfig) -> Result<Self, StorageCapabilityError> {
207        let Some(value) = config.settings().get(STORAGE_REQUIRED_CAPABILITIES_SETTING) else {
208            return Ok(Self::new());
209        };
210        let mut requirements = Self::new();
211        if value.trim().is_empty() {
212            return Err(StorageCapabilityError::UnknownRequirement);
213        }
214        for raw in value.split(',') {
215            let capability = StorageCapabilityV1::parse(raw.trim())?;
216            requirements.require(capability)?;
217        }
218        Ok(requirements)
219    }
220
221    /// Adds one exact requirement and rejects duplicate declarations.
222    pub fn require(
223        &mut self,
224        capability: StorageCapabilityV1,
225    ) -> Result<(), StorageCapabilityError> {
226        if !self.capabilities.insert(capability) {
227            return Err(StorageCapabilityError::DuplicateRequirement(capability));
228        }
229        Ok(())
230    }
231
232    /// Includes one requirement derived from another validated contract field.
233    pub fn include(&mut self, capability: StorageCapabilityV1) {
234        self.capabilities.insert(capability);
235    }
236
237    /// Returns the bounded set of required guarantees.
238    pub fn capabilities(&self) -> &BTreeSet<StorageCapabilityV1> {
239        &self.capabilities
240    }
241}
242
243/// Capability descriptor source implemented by a concrete storage provider.
244pub trait StorageCapabilityProviderV1 {
245    /// Returns an immutable descriptor without probing or opening the provider.
246    fn storage_capabilities_v1(
247        &self,
248    ) -> Result<StorageCapabilityDescriptorV1, StorageCapabilityError>;
249}
250
251/// Bounded catalog used to resolve the descriptor for a selected provider.
252#[derive(Debug, Clone, Default)]
253pub struct StorageCapabilityCatalogV1 {
254    descriptors: BTreeMap<ProviderId, StorageCapabilityDescriptorV1>,
255}
256
257impl StorageCapabilityCatalogV1 {
258    /// Creates an empty bounded catalog.
259    pub fn new() -> Self {
260        Self::default()
261    }
262
263    /// Registers one descriptor and rejects ambiguity or capacity overflow.
264    pub fn register(
265        &mut self,
266        descriptor: StorageCapabilityDescriptorV1,
267    ) -> Result<(), StorageCapabilityError> {
268        if self.descriptors.contains_key(descriptor.provider_id()) {
269            return Err(StorageCapabilityError::DuplicateProvider(
270                descriptor.provider_id().clone(),
271            ));
272        }
273        if self.descriptors.len() >= MAX_STORAGE_CAPABILITY_PROVIDERS_V1 {
274            return Err(StorageCapabilityError::CatalogFull);
275        }
276        self.descriptors
277            .insert(descriptor.provider_id().clone(), descriptor);
278        Ok(())
279    }
280
281    /// Resolves and validates the explicitly selected provider without fallback.
282    pub fn validate(
283        &self,
284        provider_id: &ProviderId,
285        requirements: &StorageCapabilityRequirementsV1,
286    ) -> Result<(), StorageCapabilityError> {
287        self.descriptors
288            .get(provider_id)
289            .ok_or_else(|| StorageCapabilityError::ProviderUnavailable(provider_id.clone()))?
290            .validate(requirements)
291    }
292}