1use crate::adapters::{
8 ConsoleHandlerAdapter, CryptoHandlerAdapter, LoggingSystemHandlerAdapter, RandomHandlerAdapter,
9 StorageHandlerAdapter, TimeHandlerAdapter, TraceHandlerAdapter,
10};
11use async_trait::async_trait;
12use aura_core::{
13 hash, AuthorityId, ContextId, ContextSnapshot, EffectType, ExecutionMode, OperationSessionId,
14 SessionId,
15};
16use aura_effects::{
17 console::RealConsoleHandler, crypto::RealCryptoHandler, random::RealRandomHandler,
18 storage::FilesystemStorageHandler, system::logging::LoggingSystemHandler,
19 time::PhysicalTimeHandler, trace::TraceHandler,
20};
21use aura_mpst::LocalSessionType;
22use cfg_if::cfg_if;
23use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25use thiserror::Error;
26use uuid::Uuid;
27
28cfg_if! {
29 if #[cfg(not(target_arch = "wasm32"))] {
30 use crate::adapters::TransportHandlerAdapter;
31 use aura_effects::TcpTransportHandler as RealTransportHandler;
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct EffectId(pub EffectType);
38
39impl From<EffectType> for EffectId {
40 fn from(value: EffectType) -> Self {
41 Self(value)
42 }
43}
44
45impl From<EffectId> for EffectType {
46 fn from(value: EffectId) -> Self {
47 value.0
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct CapabilityId(String);
54
55impl CapabilityId {
56 pub fn new(value: impl Into<String>) -> Self {
57 Self(value.into())
58 }
59
60 pub fn as_str(&self) -> &str {
61 &self.0
62 }
63}
64
65impl From<&str> for CapabilityId {
66 fn from(value: &str) -> Self {
67 Self(value.to_string())
68 }
69}
70
71impl From<String> for CapabilityId {
72 fn from(value: String) -> Self {
73 Self(value)
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub enum MetadataKey {
80 Effect(EffectId),
81 Capability(CapabilityId),
82}
83
84#[derive(Debug, Clone)]
86pub struct HandlerContext {
87 pub authority_id: AuthorityId,
88 pub context_id: ContextId,
89 pub execution_mode: ExecutionMode,
90 pub session_id: OperationSessionId,
91 pub operation_id: Uuid,
92 pub metadata: HashMap<MetadataKey, String>,
93}
94
95impl HandlerContext {
96 fn deterministic_operation_id(
97 authority_id: AuthorityId,
98 context_id: ContextId,
99 execution_mode: ExecutionMode,
100 ) -> Uuid {
101 let mut seed = Vec::with_capacity(33);
102 seed.extend_from_slice(&authority_id.to_bytes());
103 seed.extend_from_slice(&context_id.to_bytes());
104 match execution_mode {
105 ExecutionMode::Testing => seed.push(0),
106 ExecutionMode::Production => seed.push(1),
107 ExecutionMode::Simulation { seed: sim_seed } => {
108 seed.push(2);
109 seed.extend_from_slice(&sim_seed.to_le_bytes());
110 }
111 }
112 let digest = hash::hash(&seed);
113 let mut op_bytes = [0u8; 16];
114 op_bytes.copy_from_slice(&digest[..16]);
115 Uuid::from_bytes(op_bytes)
116 }
117
118 fn deterministic_session_id(
119 authority_id: AuthorityId,
120 context_id: ContextId,
121 execution_mode: ExecutionMode,
122 ) -> OperationSessionId {
123 let mut seed = Vec::with_capacity(41);
124 seed.extend_from_slice(b"aura-session");
125 seed.extend_from_slice(&authority_id.to_bytes());
126 seed.extend_from_slice(&context_id.to_bytes());
127 match execution_mode {
128 ExecutionMode::Testing => seed.push(0),
129 ExecutionMode::Production => seed.push(1),
130 ExecutionMode::Simulation { seed: sim_seed } => {
131 seed.push(2);
132 seed.extend_from_slice(&sim_seed.to_le_bytes());
133 }
134 }
135 OperationSessionId::new(SessionId::new_from_entropy(hash::hash(&seed)))
136 }
137
138 pub fn new(
141 authority_id: AuthorityId,
142 context_id: ContextId,
143 execution_mode: ExecutionMode,
144 ) -> Self {
145 let operation_id =
146 Self::deterministic_operation_id(authority_id, context_id, execution_mode);
147 Self {
148 authority_id,
149 context_id,
150 execution_mode,
151 session_id: Self::deterministic_session_id(authority_id, context_id, execution_mode),
152 operation_id,
153 metadata: HashMap::new(),
154 }
155 }
156
157 pub fn from_snapshot(snapshot: ContextSnapshot) -> Self {
159 let authority_id = snapshot.authority_id();
160 let context_id = snapshot.context_id();
161 let execution_mode = snapshot.execution_mode();
162 let operation_id =
163 Self::deterministic_operation_id(authority_id, context_id, execution_mode);
164 Self {
165 authority_id,
166 context_id,
167 execution_mode,
168 session_id: snapshot.session_id(),
169 operation_id,
170 metadata: HashMap::new(),
171 }
172 }
173
174 pub fn with_session_id(mut self, session_id: OperationSessionId) -> Self {
176 self.session_id = session_id;
177 self
178 }
179
180 pub fn with_metadata(mut self, key: MetadataKey, value: String) -> Self {
182 self.metadata.insert(key, value);
183 self
184 }
185
186 pub fn with_effect_metadata(mut self, effect: EffectType, value: String) -> Self {
188 self.metadata
189 .insert(MetadataKey::Effect(effect.into()), value);
190 self
191 }
192
193 pub fn with_capability_metadata(
195 mut self,
196 capability: impl Into<CapabilityId>,
197 value: String,
198 ) -> Self {
199 self.metadata
200 .insert(MetadataKey::Capability(capability.into()), value);
201 self
202 }
203}
204
205#[derive(Debug, Clone, Copy, Default)]
207pub struct RegisterAllOptions {
208 pub allow_impure: bool,
213}
214
215impl RegisterAllOptions {
216 pub fn allow_impure() -> Self {
218 Self { allow_impure: true }
219 }
220}
221
222#[derive(Debug, Error)]
224pub enum HandlerError {
225 #[error("Effect {effect_type:?} not supported")]
227 UnsupportedEffect { effect_type: EffectType },
228
229 #[error("Operation '{operation}' not found in effect {effect_type:?}")]
231 UnknownOperation {
232 effect_type: EffectType,
233 operation: String,
234 },
235
236 #[error("Failed to serialize parameters for {effect_type:?}.{operation}")]
238 EffectSerialization {
239 effect_type: EffectType,
240 operation: String,
241 #[source]
242 source: Box<dyn std::error::Error + Send + Sync>,
243 },
244
245 #[error("Failed to deserialize parameters for {effect_type:?}.{operation}")]
247 EffectDeserialization {
248 effect_type: EffectType,
249 operation: String,
250 #[source]
251 source: Box<dyn std::error::Error + Send + Sync>,
252 },
253
254 #[error("Session type execution failed")]
256 SessionExecution {
257 #[source]
258 source: Box<dyn std::error::Error + Send + Sync>,
259 },
260
261 #[error("Context operation failed: {message}")]
263 ContextError { message: String },
264
265 #[error("Registry operation failed")]
267 RegistryError {
268 #[source]
269 source: Box<dyn std::error::Error + Send + Sync>,
270 },
271
272 #[error("Effect execution failed")]
274 ExecutionFailed {
275 #[source]
276 source: Box<dyn std::error::Error + Send + Sync>,
277 },
278}
279
280#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
286#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
287pub trait Handler: Send + Sync {
288 async fn execute_effect(
290 &self,
291 effect_type: EffectType,
292 operation: &str,
293 parameters: &[u8],
294 ctx: &HandlerContext,
295 ) -> Result<Vec<u8>, HandlerError>;
296
297 async fn execute_session(
299 &self,
300 session: LocalSessionType,
301 ctx: &HandlerContext,
302 ) -> Result<(), HandlerError>;
303
304 fn supports_effect(&self, effect_type: EffectType) -> bool;
306
307 fn execution_mode(&self) -> ExecutionMode;
309
310 fn supported_effects(&self) -> Vec<EffectType> {
312 EffectType::all()
313 .into_iter()
314 .filter(|&effect_type| self.supports_effect(effect_type))
315 .collect()
316 }
317}
318
319#[derive(Debug, Error)]
321pub enum RegistryError {
322 #[error("Effect type {effect_type:?} not registered")]
324 EffectTypeNotRegistered { effect_type: EffectType },
325
326 #[error("Operation '{operation}' not supported by handler for {effect_type:?}")]
328 OperationNotSupported {
329 effect_type: EffectType,
330 operation: String,
331 },
332
333 #[error("Failed to register handler for {effect_type:?}")]
335 RegistrationFailed {
336 effect_type: EffectType,
337 #[source]
338 source: Box<dyn std::error::Error + Send + Sync>,
339 },
340
341 #[error("Handler execution failed for {effect_type:?}")]
343 HandlerExecutionFailed {
344 effect_type: EffectType,
345 #[source]
346 source: Box<dyn std::error::Error + Send + Sync>,
347 },
348
349 #[error("Failed to deserialize result from {effect_type:?} operation '{operation}'")]
351 ParameterDeserialization {
352 effect_type: EffectType,
353 operation: String,
354 #[source]
355 source: Box<dyn std::error::Error + Send + Sync>,
356 },
357}
358
359impl RegistryError {
360 pub fn registration_failed(
362 effect_type: EffectType,
363 source: impl std::error::Error + Send + Sync + 'static,
364 ) -> Self {
365 Self::RegistrationFailed {
366 effect_type,
367 source: Box::new(source),
368 }
369 }
370
371 pub fn handler_execution_failed(
373 effect_type: EffectType,
374 source: impl std::error::Error + Send + Sync + 'static,
375 ) -> Self {
376 Self::HandlerExecutionFailed {
377 effect_type,
378 source: Box::new(source),
379 }
380 }
381}
382
383#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
388#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
389pub trait RegistrableHandler: Send + Sync {
390 async fn execute_operation_bytes(
396 &self,
397 effect_type: EffectType,
398 operation: &str,
399 parameters: &[u8],
400 ctx: &HandlerContext,
401 ) -> Result<Vec<u8>, HandlerError>;
402
403 fn supported_operations(&self, effect_type: EffectType) -> Vec<String>;
408
409 fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
411 self.supported_operations(effect_type)
412 .contains(&operation.to_string())
413 }
414
415 fn supports_effect(&self, effect_type: EffectType) -> bool;
417
418 fn execution_mode(&self) -> ExecutionMode;
420}
421
422pub struct EffectRegistry {
427 handlers: HashMap<EffectType, Box<dyn RegistrableHandler>>,
429 default_execution_mode: ExecutionMode,
431}
432
433impl std::fmt::Debug for EffectRegistry {
434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435 f.debug_struct("EffectRegistry")
436 .field(
437 "handlers",
438 &format!("HashMap with {} entries", self.handlers.len()),
439 )
440 .field("default_execution_mode", &self.default_execution_mode)
441 .finish()
442 }
443}
444
445impl EffectRegistry {
446 pub fn new(default_execution_mode: ExecutionMode) -> Self {
448 Self {
449 handlers: HashMap::new(),
450 default_execution_mode,
451 }
452 }
453
454 pub fn register_handler(
461 &mut self,
462 effect_type: EffectType,
463 handler: Box<dyn RegistrableHandler>,
464 ) -> Result<(), RegistryError> {
465 if !handler.supports_effect(effect_type) {
467 return Err(RegistryError::registration_failed(
468 effect_type,
469 std::io::Error::new(
470 std::io::ErrorKind::InvalidInput,
471 "Handler does not support the specified effect type",
472 ),
473 ));
474 }
475
476 self.handlers.insert(effect_type, handler);
478 Ok(())
479 }
480
481 pub fn register_all(&mut self, options: RegisterAllOptions) -> Result<(), RegistryError> {
489 if !options.allow_impure {
490 return Err(RegistryError::registration_failed(
491 EffectType::System,
492 std::io::Error::new(
493 std::io::ErrorKind::PermissionDenied,
494 "register_all requires allow_impure=true for OS-backed handlers",
495 ),
496 ));
497 }
498
499 self.register_handler(
500 EffectType::Console,
501 Box::new(ConsoleHandlerAdapter::new(RealConsoleHandler::new())),
502 )?;
503 self.register_handler(
504 EffectType::Random,
505 Box::new(RandomHandlerAdapter::new(RealRandomHandler::new())),
506 )?;
507 self.register_handler(
508 EffectType::Crypto,
509 Box::new(CryptoHandlerAdapter::new(RealCryptoHandler::new())),
510 )?;
511 self.register_handler(
512 EffectType::Storage,
513 Box::new(StorageHandlerAdapter::new(
514 FilesystemStorageHandler::with_default_path(),
515 )),
516 )?;
517 self.register_handler(
518 EffectType::Time,
519 Box::new(TimeHandlerAdapter::new(PhysicalTimeHandler::new())),
520 )?;
521 cfg_if! {
522 if #[cfg(not(target_arch = "wasm32"))] {
523 self.register_handler(
524 EffectType::Network,
525 Box::new(TransportHandlerAdapter::new(RealTransportHandler::default())),
526 )?;
527 }
528 }
529 self.register_handler(
530 EffectType::Trace,
531 Box::new(TraceHandlerAdapter::new(TraceHandler::new())),
532 )?;
533 self.register_handler(
534 EffectType::System,
535 Box::new(LoggingSystemHandlerAdapter::new(
536 LoggingSystemHandler::default(),
537 )),
538 )?;
539
540 Ok(())
541 }
542
543 pub fn unregister_handler(
545 &mut self,
546 effect_type: EffectType,
547 ) -> Option<Box<dyn RegistrableHandler>> {
548 self.handlers.remove(&effect_type)
549 }
550
551 pub fn is_registered(&self, effect_type: EffectType) -> bool {
553 self.handlers.contains_key(&effect_type)
554 }
555
556 pub fn registered_effect_types(&self) -> Vec<EffectType> {
558 self.handlers.keys().copied().collect()
559 }
560
561 pub fn handlers_len(&self) -> usize {
563 self.handlers.len()
564 }
565
566 pub fn supported_operations(
568 &self,
569 effect_type: EffectType,
570 ) -> Result<Vec<String>, RegistryError> {
571 match self.handlers.get(&effect_type) {
572 Some(handler) => Ok(handler.supported_operations(effect_type)),
573 None => Err(RegistryError::EffectTypeNotRegistered { effect_type }),
574 }
575 }
576
577 pub fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
579 self.handlers
580 .get(&effect_type)
581 .map(|h| h.supports_operation(effect_type, operation))
582 .unwrap_or(false)
583 }
584
585 pub async fn execute_session(
590 &mut self,
591 _session: LocalSessionType,
592 _ctx: &mut HandlerContext,
593 ) -> Result<(), RegistryError> {
594 Err(RegistryError::OperationNotSupported {
595 effect_type: EffectType::Choreographic,
596 operation: "execute_session".to_string(),
597 })
598 }
599
600 pub fn execution_mode(&self) -> ExecutionMode {
602 self.default_execution_mode
603 }
604
605 pub fn capability_summary(&self) -> RegistryCapabilities {
607 let mut capabilities = RegistryCapabilities {
608 registered_effects: Vec::new(),
609 total_operations: 0,
610 execution_modes: Vec::new(),
611 };
612
613 for (effect_type, handler) in &self.handlers {
614 let operations = handler.supported_operations(*effect_type);
615 let operation_count = operations.len();
616 capabilities.registered_effects.push(EffectCapability {
617 effect_type: *effect_type,
618 operation_count,
619 operations,
620 });
621 capabilities.total_operations += operation_count;
622
623 let mode = handler.execution_mode();
624 if !capabilities.execution_modes.contains(&mode) {
625 capabilities.execution_modes.push(mode);
626 }
627 }
628
629 capabilities
630 }
631}
632
633#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
634#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
635impl Handler for EffectRegistry {
636 async fn execute_effect(
637 &self,
638 effect_type: EffectType,
639 operation: &str,
640 parameters: &[u8],
641 ctx: &HandlerContext,
642 ) -> Result<Vec<u8>, HandlerError> {
643 if let Some(handler) = self.handlers.get(&effect_type) {
645 handler
646 .execute_operation_bytes(effect_type, operation, parameters, ctx)
647 .await
648 } else {
649 Err(HandlerError::UnsupportedEffect { effect_type })
650 }
651 }
652
653 async fn execute_session(
654 &self,
655 _session: LocalSessionType,
656 _ctx: &HandlerContext,
657 ) -> Result<(), HandlerError> {
658 let err = std::io::Error::other("session execution not wired in registry");
659 Err(HandlerError::SessionExecution {
660 source: Box::new(err),
661 })
662 }
663
664 fn supports_effect(&self, effect_type: EffectType) -> bool {
665 self.is_registered(effect_type)
666 }
667
668 fn execution_mode(&self) -> ExecutionMode {
669 self.default_execution_mode
670 }
671}
672
673#[derive(Debug, Clone)]
675pub struct RegistryCapabilities {
676 pub registered_effects: Vec<EffectCapability>,
678 pub total_operations: usize, pub execution_modes: Vec<ExecutionMode>,
682}
683
684#[derive(Debug, Clone)]
686pub struct EffectCapability {
687 pub effect_type: EffectType,
689 pub operation_count: usize, pub operations: Vec<String>,
693}
694
695impl RegistryCapabilities {
696 pub fn has_effect_type(&self, effect_type: EffectType) -> bool {
698 self.registered_effects
699 .iter()
700 .any(|cap| cap.effect_type == effect_type)
701 }
702
703 pub fn get_effect_capability(&self, effect_type: EffectType) -> Option<&EffectCapability> {
705 self.registered_effects
706 .iter()
707 .find(|cap| cap.effect_type == effect_type)
708 }
709
710 pub fn supports_operation(&self, effect_type: EffectType, operation: &str) -> bool {
712 self.get_effect_capability(effect_type)
713 .map(|cap| cap.operations.contains(&operation.to_string()))
714 .unwrap_or(false)
715 }
716
717 pub fn effect_type_count(&self) -> usize {
719 self.registered_effects.len()
720 }
721
722 pub fn supports_execution_mode(&self, mode: ExecutionMode) -> bool {
724 self.execution_modes.contains(&mode)
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 struct MockRegistrableHandler {
734 effect_type: EffectType,
735 operations: Vec<String>,
736 execution_mode: ExecutionMode,
737 }
738
739 impl MockRegistrableHandler {
740 fn new(
742 effect_type: EffectType,
743 operations: Vec<&str>,
744 execution_mode: ExecutionMode,
745 ) -> Self {
746 Self {
747 effect_type,
748 operations: operations.into_iter().map(|s| s.to_string()).collect(),
749 execution_mode,
750 }
751 }
752 }
753
754 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
755 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
756 impl Handler for MockRegistrableHandler {
757 async fn execute_effect(
759 &self,
760 effect_type: EffectType,
761 operation: &str,
762 _parameters: &[u8],
763 _ctx: &HandlerContext,
764 ) -> Result<Vec<u8>, HandlerError> {
765 if self.effect_type == effect_type && self.operations.contains(&operation.to_string()) {
766 aura_core::util::serialization::to_vec(&serde_json::Value::String(
768 "mock_result".to_string(),
769 ))
770 .map_err(|e| HandlerError::EffectSerialization {
771 effect_type,
772 operation: operation.to_string(),
773 source: e.into(),
774 })
775 } else {
776 Err(HandlerError::UnsupportedEffect { effect_type })
777 }
778 }
779
780 async fn execute_session(
781 &self,
782 _session: LocalSessionType,
783 _ctx: &HandlerContext,
784 ) -> Result<(), HandlerError> {
785 Ok(())
786 }
787
788 fn supports_effect(&self, effect_type: EffectType) -> bool {
789 self.effect_type == effect_type
790 }
791
792 fn execution_mode(&self) -> ExecutionMode {
793 self.execution_mode
794 }
795 }
796
797 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
798 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
799 impl RegistrableHandler for MockRegistrableHandler {
800 async fn execute_operation_bytes(
802 &self,
803 _effect_type: EffectType,
804 operation: &str,
805 _parameters: &[u8],
806 _ctx: &HandlerContext,
807 ) -> Result<Vec<u8>, HandlerError> {
808 if self.operations.contains(&operation.to_string()) {
809 let mock_result = serde_json::Value::String("mock_result".to_string());
811 aura_core::util::serialization::to_vec(&mock_result).map_err(|e| {
812 HandlerError::EffectSerialization {
813 effect_type: self.effect_type,
814 operation: operation.to_string(),
815 source: e.into(),
816 }
817 })
818 } else {
819 Err(HandlerError::UnknownOperation {
820 effect_type: self.effect_type,
821 operation: operation.to_string(),
822 })
823 }
824 }
825
826 fn supported_operations(&self, effect_type: EffectType) -> Vec<String> {
827 if self.effect_type == effect_type {
828 self.operations.clone()
829 } else {
830 Vec::new()
831 }
832 }
833
834 fn supports_effect(&self, effect_type: EffectType) -> bool {
835 self.effect_type == effect_type
836 }
837
838 fn execution_mode(&self) -> ExecutionMode {
839 self.execution_mode
840 }
841 }
842
843 #[test]
845 fn test_registry_creation() {
846 let registry = EffectRegistry::new(ExecutionMode::Testing);
847 assert_eq!(registry.execution_mode(), ExecutionMode::Testing);
848 assert!(registry.registered_effect_types().is_empty());
849 }
850
851 #[test]
853 fn test_handler_registration() {
854 let mut registry = EffectRegistry::new(ExecutionMode::Testing);
855
856 let handler = Box::new(MockRegistrableHandler::new(
857 EffectType::Crypto,
858 vec!["hash", "sign", "verify"],
859 ExecutionMode::Testing,
860 ));
861
862 registry
864 .register_handler(EffectType::Crypto, handler)
865 .unwrap();
866
867 assert!(registry.is_registered(EffectType::Crypto));
868 assert!(!registry.is_registered(EffectType::Network));
869
870 let effect_types = registry.registered_effect_types();
871 assert_eq!(effect_types.len(), 1);
872 assert!(effect_types.contains(&EffectType::Crypto));
873 }
874
875 #[test]
878 fn test_operation_support() {
879 let mut registry = EffectRegistry::new(ExecutionMode::Testing);
880
881 let handler = Box::new(MockRegistrableHandler::new(
882 EffectType::Crypto,
883 vec!["hash", "sign", "verify"],
884 ExecutionMode::Testing,
885 ));
886
887 registry
888 .register_handler(EffectType::Crypto, handler)
889 .unwrap();
890
891 let operations = registry.supported_operations(EffectType::Crypto).unwrap();
893 assert_eq!(operations.len(), 3);
894 assert!(operations.contains(&"hash".to_string()));
895 assert!(operations.contains(&"sign".to_string()));
896 assert!(operations.contains(&"verify".to_string()));
897
898 assert!(registry.supports_operation(EffectType::Crypto, "hash"));
900 assert!(registry.supports_operation(EffectType::Crypto, "sign"));
901 assert!(!registry.supports_operation(EffectType::Crypto, "encrypt"));
902 assert!(!registry.supports_operation(EffectType::Network, "send"));
903 }
904
905 #[test]
908 fn test_capability_summary() {
909 let mut registry = EffectRegistry::new(ExecutionMode::Testing);
910
911 let crypto_handler = Box::new(MockRegistrableHandler::new(
913 EffectType::Crypto,
914 vec!["hash", "sign"],
915 ExecutionMode::Testing,
916 ));
917 let network_handler = Box::new(MockRegistrableHandler::new(
918 EffectType::Network,
919 vec!["send", "receive", "broadcast"],
920 ExecutionMode::Production,
921 ));
922
923 registry
924 .register_handler(EffectType::Crypto, crypto_handler)
925 .unwrap();
926 registry
927 .register_handler(EffectType::Network, network_handler)
928 .unwrap();
929
930 let capabilities = registry.capability_summary();
931
932 assert_eq!(capabilities.effect_type_count(), 2);
933 assert_eq!(capabilities.total_operations, 5); assert!(capabilities.has_effect_type(EffectType::Crypto));
935 assert!(capabilities.has_effect_type(EffectType::Network));
936 assert!(!capabilities.has_effect_type(EffectType::Storage));
937
938 assert!(capabilities.supports_operation(EffectType::Crypto, "hash"));
940 assert!(capabilities.supports_operation(EffectType::Network, "broadcast"));
941 assert!(!capabilities.supports_operation(EffectType::Crypto, "encrypt"));
942
943 assert!(capabilities.supports_execution_mode(ExecutionMode::Testing));
945 assert!(capabilities.supports_execution_mode(ExecutionMode::Production));
946 assert!(!capabilities.supports_execution_mode(ExecutionMode::Simulation { seed: 42 }));
947 }
948
949 #[test]
952 fn test_handler_context_operation_id_deterministic() {
953 let authority_id = AuthorityId::new_from_entropy([1u8; 32]);
954 let context_id = ContextId::new_from_entropy([2u8; 32]);
955 let ctx1 = HandlerContext::new(authority_id, context_id, ExecutionMode::Testing);
956 let ctx2 = HandlerContext::new(authority_id, context_id, ExecutionMode::Testing);
957
958 assert_eq!(ctx1.operation_id, ctx2.operation_id);
960
961 let different_authority = AuthorityId::new_from_entropy([3u8; 32]);
963 let ctx3 = HandlerContext::new(different_authority, context_id, ExecutionMode::Testing);
964 assert_ne!(ctx1.operation_id, ctx3.operation_id);
965
966 let ctx4 = HandlerContext::new(authority_id, context_id, ExecutionMode::Production);
968 assert_ne!(ctx1.operation_id, ctx4.operation_id);
969 }
970
971 #[test]
976 fn test_duplicate_registration_replaces_handler() {
977 let mut registry = EffectRegistry::new(ExecutionMode::Testing);
978
979 let handler1 = Box::new(MockRegistrableHandler::new(
980 EffectType::Crypto,
981 vec!["hash", "sign"],
982 ExecutionMode::Testing,
983 ));
984 let handler2 = Box::new(MockRegistrableHandler::new(
985 EffectType::Crypto,
986 vec!["hash", "sign", "verify", "encrypt"],
987 ExecutionMode::Testing,
988 ));
989
990 registry
991 .register_handler(EffectType::Crypto, handler1)
992 .unwrap();
993 registry
994 .register_handler(EffectType::Crypto, handler2)
995 .unwrap();
996
997 let ops = registry.supported_operations(EffectType::Crypto).unwrap();
999 assert_eq!(ops.len(), 4);
1000 assert!(ops.contains(&"encrypt".to_string()));
1001 }
1002}