Skip to main content

aura_effects/
runtime_capability.rs

1//! Runtime capability inventory handler.
2
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use aura_core::effects::{AdmissionError, CapabilityKey, RuntimeCapabilityEffects};
8
9#[cfg(feature = "telltale-runtime-capability")]
10use telltale_machine::capabilities::protocol_critical_capability_boundary;
11
12#[cfg(not(feature = "telltale-runtime-capability"))]
13const PROTOCOL_SURFACE_RUNTIME_ADMISSION: &str = "runtime_admission";
14#[cfg(not(feature = "telltale-runtime-capability"))]
15const PROTOCOL_SURFACE_THEOREM_PACK_CAPABILITIES: &str = "theorem_pack_capabilities";
16#[cfg(any(test, not(feature = "telltale-runtime-capability")))]
17const PROTOCOL_SURFACE_OWNERSHIP_CAPABILITY: &str = "ownership_capability";
18#[cfg(not(feature = "telltale-runtime-capability"))]
19const PROTOCOL_SURFACE_READINESS_WITNESS: &str = "readiness_witness";
20#[cfg(not(feature = "telltale-runtime-capability"))]
21const PROTOCOL_SURFACE_AUTHORITATIVE_READ: &str = "authoritative_read";
22#[cfg(not(feature = "telltale-runtime-capability"))]
23const PROTOCOL_SURFACE_MATERIALIZATION_PROOF: &str = "materialization_proof";
24#[cfg(not(feature = "telltale-runtime-capability"))]
25const PROTOCOL_SURFACE_CANONICAL_HANDLE: &str = "canonical_handle";
26const PROTOCOL_SURFACE_OWNERSHIP_RECEIPT: &str = "ownership_receipt";
27const PROTOCOL_SURFACE_SEMANTIC_HANDOFF: &str = "semantic_handoff";
28const PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION: &str = "reconfiguration_transition";
29
30/// Immutable runtime capability inventory captured at runtime boot.
31#[derive(Debug, Clone, Default)]
32pub struct RuntimeCapabilityHandler {
33    inventory: Arc<BTreeMap<CapabilityKey, bool>>,
34    protocol_critical_surfaces: Arc<BTreeMap<String, bool>>,
35}
36
37impl RuntimeCapabilityHandler {
38    /// Create a handler from an explicit capability snapshot.
39    pub fn new(snapshot: Vec<(CapabilityKey, bool)>) -> Self {
40        let mut inventory = snapshot.into_iter().collect::<BTreeMap<_, _>>();
41        let protocol_critical_surfaces =
42            protocol_surface_inventory_from_aura_capabilities(&inventory);
43        merge_protocol_surfaces_into_inventory(&mut inventory, &protocol_critical_surfaces);
44        Self {
45            inventory: Arc::new(inventory),
46            protocol_critical_surfaces: Arc::new(protocol_critical_surfaces),
47        }
48    }
49
50    /// Create a handler from borrowed string capability pairs.
51    pub fn from_pairs(
52        snapshot: impl IntoIterator<Item = (impl Into<CapabilityKey>, bool)>,
53    ) -> Self {
54        let mut inventory = snapshot
55            .into_iter()
56            .map(|(key, admitted)| (key.into(), admitted))
57            .collect::<BTreeMap<_, _>>();
58        let protocol_critical_surfaces =
59            protocol_surface_inventory_from_aura_capabilities(&inventory);
60        merge_protocol_surfaces_into_inventory(&mut inventory, &protocol_critical_surfaces);
61        Self {
62            protocol_critical_surfaces: Arc::new(protocol_critical_surfaces),
63            inventory: Arc::new(inventory),
64        }
65    }
66
67    /// Snapshot size for telemetry/testing.
68    pub fn len(&self) -> usize {
69        self.inventory.len()
70    }
71
72    /// Returns true if the snapshot is empty.
73    pub fn is_empty(&self) -> bool {
74        self.inventory.is_empty()
75    }
76
77    /// Returns whether one public protocol-critical surface is admitted.
78    pub fn protocol_critical_surface_admitted(&self, surface: &str) -> bool {
79        self.protocol_critical_surfaces
80            .get(surface)
81            .copied()
82            .unwrap_or(false)
83    }
84
85    /// Require that the given public protocol-critical surfaces are admitted.
86    pub fn require_protocol_critical_surfaces(
87        &self,
88        required: &[&str],
89    ) -> Result<(), AdmissionError> {
90        for surface in required {
91            if !self.protocol_critical_surface_admitted(surface) {
92                return Err(AdmissionError::MissingCapability {
93                    capability: CapabilityKey::new(*surface),
94                });
95            }
96        }
97        Ok(())
98    }
99}
100
101#[cfg(feature = "telltale-runtime-capability")]
102impl RuntimeCapabilityHandler {
103    /// Build from the current ProtocolMachine runtime contracts/admission surface.
104    pub fn from_protocol_machine_runtime_contracts(
105        contracts: &telltale_machine::runtime_contracts::RuntimeContracts,
106    ) -> Self {
107        let mut inventory =
108            telltale_machine::runtime_contracts::runtime_capability_snapshot(contracts)
109                .into_iter()
110                .map(|(key, admitted)| (CapabilityKey::new(key), admitted))
111                .collect::<BTreeMap<_, _>>();
112        for (key, admitted) in &contracts.execution_profile.theorem_pack_eligibility {
113            inventory.insert(CapabilityKey::new(key.as_str()), *admitted);
114        }
115        let reconfiguration_enabled = contracts
116            .capabilities
117            .contains(&telltale_machine::runtime_contracts::RuntimeCapability::LiveMigration)
118            && contracts.capabilities.contains(
119                &telltale_machine::runtime_contracts::RuntimeCapability::PlacementRefinement,
120            );
121        let protocol_critical_surfaces =
122            protocol_surface_inventory_from_runtime_contracts(contracts);
123
124        inventory.insert(
125            CapabilityKey::new("byzantine_envelope"),
126            contracts.determinism_artifacts.full,
127        );
128        inventory.insert(
129            CapabilityKey::new("termination_bounded"),
130            contracts.determinism_artifacts.replay || contracts.determinism_artifacts.full,
131        );
132        inventory.insert(
133            CapabilityKey::new("reconfiguration"),
134            contracts
135                .capabilities
136                .contains(&telltale_machine::runtime_contracts::RuntimeCapability::LiveMigration)
137                && contracts.capabilities.contains(
138                    &telltale_machine::runtime_contracts::RuntimeCapability::PlacementRefinement,
139                ),
140        );
141        inventory.insert(
142            CapabilityKey::new("mixed_determinism"),
143            contracts.can_use_mixed_determinism_profiles,
144        );
145        inventory.insert(
146            CapabilityKey::new("vmEnvelopeAdherence"),
147            contracts.determinism_artifacts.full,
148        );
149        inventory.insert(
150            CapabilityKey::new("reconfiguration_safety"),
151            reconfiguration_enabled,
152        );
153        merge_protocol_surfaces_into_inventory(&mut inventory, &protocol_critical_surfaces);
154
155        Self {
156            inventory: Arc::new(inventory),
157            protocol_critical_surfaces: Arc::new(protocol_critical_surfaces),
158        }
159    }
160}
161
162fn protocol_surface_inventory_from_aura_capabilities(
163    inventory: &BTreeMap<CapabilityKey, bool>,
164) -> BTreeMap<String, bool> {
165    let reconfiguration_enabled = inventory
166        .get(&CapabilityKey::new("reconfiguration"))
167        .copied()
168        .unwrap_or(false);
169    #[cfg(feature = "telltale-runtime-capability")]
170    {
171        protocol_surface_inventory_from_boundary(reconfiguration_enabled)
172    }
173    #[cfg(not(feature = "telltale-runtime-capability"))]
174    {
175        protocol_surface_inventory_from_known_surfaces(reconfiguration_enabled)
176    }
177}
178
179fn merge_protocol_surfaces_into_inventory(
180    inventory: &mut BTreeMap<CapabilityKey, bool>,
181    protocol_critical_surfaces: &BTreeMap<String, bool>,
182) {
183    for (surface, admitted) in protocol_critical_surfaces {
184        inventory.insert(CapabilityKey::new(surface.as_str()), *admitted);
185    }
186}
187
188#[cfg(feature = "telltale-runtime-capability")]
189fn protocol_surface_inventory_from_runtime_contracts(
190    contracts: &telltale_machine::runtime_contracts::RuntimeContracts,
191) -> BTreeMap<String, bool> {
192    let reconfiguration_enabled = contracts
193        .capabilities
194        .contains(&telltale_machine::runtime_contracts::RuntimeCapability::LiveMigration)
195        && contracts
196            .capabilities
197            .contains(&telltale_machine::runtime_contracts::RuntimeCapability::PlacementRefinement);
198    protocol_surface_inventory_from_boundary(reconfiguration_enabled)
199}
200
201#[cfg(feature = "telltale-runtime-capability")]
202fn protocol_surface_inventory_from_boundary(
203    reconfiguration_enabled: bool,
204) -> BTreeMap<String, bool> {
205    protocol_critical_capability_boundary()
206        .into_iter()
207        .map(|entry| {
208            let admitted = match entry.surface.as_str() {
209                PROTOCOL_SURFACE_OWNERSHIP_RECEIPT
210                | PROTOCOL_SURFACE_SEMANTIC_HANDOFF
211                | PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION => reconfiguration_enabled,
212                _ => true,
213            };
214            (entry.surface, admitted)
215        })
216        .collect()
217}
218
219#[cfg(not(feature = "telltale-runtime-capability"))]
220fn protocol_surface_inventory_from_known_surfaces(
221    reconfiguration_enabled: bool,
222) -> BTreeMap<String, bool> {
223    let always_admitted = [
224        PROTOCOL_SURFACE_RUNTIME_ADMISSION,
225        PROTOCOL_SURFACE_THEOREM_PACK_CAPABILITIES,
226        PROTOCOL_SURFACE_OWNERSHIP_CAPABILITY,
227        PROTOCOL_SURFACE_READINESS_WITNESS,
228        PROTOCOL_SURFACE_AUTHORITATIVE_READ,
229        PROTOCOL_SURFACE_MATERIALIZATION_PROOF,
230        PROTOCOL_SURFACE_CANONICAL_HANDLE,
231    ];
232    let transition_surfaces = [
233        PROTOCOL_SURFACE_OWNERSHIP_RECEIPT,
234        PROTOCOL_SURFACE_SEMANTIC_HANDOFF,
235        PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION,
236    ];
237
238    let mut admitted = always_admitted
239        .into_iter()
240        .map(|surface| (surface.to_string(), true))
241        .collect::<BTreeMap<_, _>>();
242    for surface in transition_surfaces {
243        admitted.insert(surface.to_string(), reconfiguration_enabled);
244    }
245    admitted
246}
247
248#[async_trait]
249impl RuntimeCapabilityEffects for RuntimeCapabilityHandler {
250    async fn capability_inventory(&self) -> Result<Vec<(CapabilityKey, bool)>, AdmissionError> {
251        Ok(self
252            .inventory
253            .iter()
254            .map(|(key, admitted)| (key.clone(), *admitted))
255            .collect())
256    }
257
258    async fn require_capabilities(&self, required: &[CapabilityKey]) -> Result<(), AdmissionError> {
259        for required_key in required {
260            let admitted = self.inventory.get(required_key).copied().unwrap_or(false);
261            if !admitted {
262                return Err(AdmissionError::MissingCapability {
263                    capability: required_key.clone(),
264                });
265            }
266        }
267        Ok(())
268    }
269}
270
271#[cfg(test)]
272#[allow(clippy::expect_used)]
273mod tests {
274    use super::*;
275
276    #[tokio::test]
277    async fn missing_capability_is_rejected() {
278        let handler = RuntimeCapabilityHandler::from_pairs([
279            ("vmEnvelopeAdherence", true),
280            ("byzantineSafety", false),
281        ]);
282        let result = handler
283            .require_capabilities(&[
284                CapabilityKey::new("vmEnvelopeAdherence"),
285                CapabilityKey::new("byzantineSafety"),
286            ])
287            .await;
288        assert!(matches!(
289            result,
290            Err(AdmissionError::MissingCapability { capability })
291                if capability == CapabilityKey::new("byzantineSafety")
292        ));
293    }
294
295    #[tokio::test]
296    async fn inventory_round_trip_is_stable() {
297        let handler =
298            RuntimeCapabilityHandler::from_pairs([("capA", true), ("capB", false), ("capC", true)]);
299        let inventory = handler
300            .capability_inventory()
301            .await
302            .expect("inventory should be available");
303        assert!(inventory.contains(&(CapabilityKey::new("capA"), true)));
304        assert!(inventory.contains(&(CapabilityKey::new("capB"), false)));
305        assert!(inventory.contains(&(CapabilityKey::new("capC"), true)));
306        assert!(inventory.contains(&(CapabilityKey::new("theorem_pack_capabilities"), true)));
307        assert!(inventory.contains(&(CapabilityKey::new("authoritative_read"), true)));
308        assert!(inventory.contains(&(CapabilityKey::new("reconfiguration_transition"), false)));
309    }
310
311    #[cfg(feature = "telltale-runtime-capability")]
312    #[test]
313    fn protocol_machine_runtime_contract_mapping_exposes_derived_aura_capabilities() {
314        let contracts = telltale_machine::runtime_contracts::RuntimeContracts::full();
315        let handler = RuntimeCapabilityHandler::from_protocol_machine_runtime_contracts(&contracts);
316        assert!(
317            handler
318                .inventory
319                .get(&CapabilityKey::new("byzantine_envelope"))
320                .copied()
321                .unwrap_or(false),
322            "full contracts should admit byzantine_envelope"
323        );
324        assert!(
325            handler
326                .inventory
327                .contains_key(&CapabilityKey::new("termination_bounded")),
328            "derived termination_bounded key should be present"
329        );
330        assert!(
331            handler
332                .inventory
333                .contains_key(&CapabilityKey::new("mixed_determinism")),
334            "derived mixed_determinism key should be present"
335        );
336        assert!(
337            handler.protocol_critical_surface_admitted(PROTOCOL_SURFACE_OWNERSHIP_CAPABILITY),
338            "public ownership capability surface should be tracked"
339        );
340        assert!(
341            handler.protocol_critical_surface_admitted(PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION),
342            "full contracts should admit public reconfiguration transition surface"
343        );
344    }
345
346    #[test]
347    fn missing_public_protocol_surface_is_rejected() {
348        let handler = RuntimeCapabilityHandler::from_pairs([("reconfiguration", false)]);
349        let result = handler
350            .require_protocol_critical_surfaces(&[PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION]);
351        assert!(matches!(
352            result,
353            Err(AdmissionError::MissingCapability { capability })
354                if capability == CapabilityKey::new(PROTOCOL_SURFACE_RECONFIGURATION_TRANSITION)
355        ));
356    }
357}