capnweb_core/protocol/
nested_capabilities.rs

1// Nested Capability Creation for Cap'n Web Protocol
2// Enables dynamic capability graphs and advanced capability composition patterns
3
4use super::tables::Value;
5use crate::RpcTarget;
6use std::collections::HashMap;
7use std::sync::Arc;
8use tokio::sync::RwLock;
9use uuid::Uuid;
10
11/// Capability factory for creating nested capabilities at runtime
12#[async_trait::async_trait]
13pub trait CapabilityFactory: Send + Sync + std::fmt::Debug {
14    /// Create a new capability instance
15    async fn create_capability(
16        &self,
17        capability_type: &str,
18        config: Value,
19    ) -> Result<Arc<dyn RpcTarget>, CapabilityError>;
20
21    /// List available capability types
22    fn list_capability_types(&self) -> Vec<String>;
23
24    /// Get capability type metadata
25    fn get_capability_metadata(&self, capability_type: &str) -> Option<CapabilityMetadata>;
26}
27
28/// Metadata about a capability type
29#[derive(Debug, Clone)]
30pub struct CapabilityMetadata {
31    pub name: String,
32    pub description: String,
33    pub version: String,
34    pub methods: Vec<MethodMetadata>,
35    pub config_schema: Option<Value>, // JSON schema for configuration
36}
37
38/// Metadata about a capability method
39#[derive(Debug, Clone)]
40pub struct MethodMetadata {
41    pub name: String,
42    pub description: String,
43    pub parameters: Vec<ParameterMetadata>,
44    pub return_type: String,
45}
46
47/// Metadata about a method parameter
48#[derive(Debug, Clone)]
49pub struct ParameterMetadata {
50    pub name: String,
51    pub type_name: String,
52    pub required: bool,
53    pub description: String,
54}
55
56/// Enhanced RPC target that can create nested capabilities
57#[async_trait::async_trait]
58pub trait NestedCapableRpcTarget: RpcTarget {
59    /// Create a sub-capability
60    async fn create_sub_capability(
61        &self,
62        capability_type: &str,
63        config: Value,
64    ) -> Result<Value, crate::RpcError>;
65
66    /// List available capability types
67    async fn list_capability_types(&self) -> Result<Value, crate::RpcError>;
68
69    /// Get capability metadata
70    async fn get_capability_metadata(
71        &self,
72        capability_type: &str,
73    ) -> Result<Value, crate::RpcError>;
74
75    /// Get all child capabilities
76    async fn list_child_capabilities(&self) -> Result<Value, crate::RpcError>;
77
78    /// Dispose of a child capability
79    async fn dispose_child_capability(&self, capability_id: &str)
80        -> Result<Value, crate::RpcError>;
81}
82
83/// Capability graph manager for tracking nested capability relationships
84#[derive(Debug)]
85pub struct CapabilityGraph {
86    /// Graph of capability relationships
87    nodes: Arc<RwLock<HashMap<String, CapabilityNode>>>,
88    /// Edges representing parent-child relationships
89    edges: Arc<RwLock<HashMap<String, Vec<String>>>>, // parent_id -> [child_ids]
90    /// Reference counting for capabilities
91    reference_counts: Arc<RwLock<HashMap<String, usize>>>,
92}
93
94/// Node in the capability graph
95#[derive(Debug, Clone)]
96pub struct CapabilityNode {
97    pub id: String,
98    pub capability_type: String,
99    pub parent_id: Option<String>,
100    pub created_at: u64,
101    pub config: Value,
102    pub metadata: CapabilityMetadata,
103}
104
105impl CapabilityGraph {
106    /// Create a new capability graph
107    pub fn new() -> Self {
108        Self {
109            nodes: Arc::new(RwLock::new(HashMap::new())),
110            edges: Arc::new(RwLock::new(HashMap::new())),
111            reference_counts: Arc::new(RwLock::new(HashMap::new())),
112        }
113    }
114
115    /// Add a capability to the graph
116    pub async fn add_capability(&self, node: CapabilityNode) -> Result<(), CapabilityError> {
117        let mut nodes = self.nodes.write().await;
118        let mut edges = self.edges.write().await;
119        let mut ref_counts = self.reference_counts.write().await;
120
121        tracing::debug!(
122            capability_id = %node.id,
123            capability_type = %node.capability_type,
124            parent_id = ?node.parent_id,
125            "Adding capability to graph"
126        );
127
128        // Add the node
129        nodes.insert(node.id.clone(), node.clone());
130
131        // Initialize reference count
132        ref_counts.insert(node.id.clone(), 1);
133
134        // Add parent-child relationship
135        if let Some(parent_id) = &node.parent_id {
136            edges
137                .entry(parent_id.clone())
138                .or_insert_with(Vec::new)
139                .push(node.id.clone());
140        }
141
142        Ok(())
143    }
144
145    /// Get a capability node
146    pub async fn get_capability(&self, id: &str) -> Option<CapabilityNode> {
147        let nodes = self.nodes.read().await;
148        nodes.get(id).cloned()
149    }
150
151    /// Get children of a capability
152    pub async fn get_children(&self, parent_id: &str) -> Vec<String> {
153        let edges = self.edges.read().await;
154        edges.get(parent_id).cloned().unwrap_or_default()
155    }
156
157    /// Get all descendants (recursive children) of a capability
158    pub async fn get_descendants(&self, parent_id: &str) -> Vec<String> {
159        let mut descendants = Vec::new();
160        let mut to_visit = vec![parent_id.to_string()];
161
162        while let Some(current_id) = to_visit.pop() {
163            let children = self.get_children(&current_id).await;
164            for child_id in children {
165                descendants.push(child_id.clone());
166                to_visit.push(child_id);
167            }
168        }
169
170        descendants
171    }
172
173    /// Increment reference count
174    pub async fn add_reference(&self, id: &str) -> Result<usize, CapabilityError> {
175        let mut ref_counts = self.reference_counts.write().await;
176        match ref_counts.get_mut(id) {
177            Some(count) => {
178                *count += 1;
179                Ok(*count)
180            }
181            None => Err(CapabilityError::CapabilityNotFound(id.to_string())),
182        }
183    }
184
185    /// Decrement reference count and remove if zero
186    pub async fn remove_reference(&self, id: &str) -> Result<bool, CapabilityError> {
187        let mut should_dispose = false;
188
189        {
190            let mut ref_counts = self.reference_counts.write().await;
191            match ref_counts.get_mut(id) {
192                Some(count) => {
193                    *count -= 1;
194                    if *count == 0 {
195                        should_dispose = true;
196                        ref_counts.remove(id);
197                    }
198                }
199                None => return Err(CapabilityError::CapabilityNotFound(id.to_string())),
200            }
201        }
202
203        if should_dispose {
204            self.remove_capability(id).await?;
205        }
206
207        Ok(should_dispose)
208    }
209
210    /// Remove a capability and its descendants
211    pub async fn remove_capability(&self, id: &str) -> Result<(), CapabilityError> {
212        tracing::debug!(capability_id = %id, "Removing capability from graph");
213
214        // Get all descendants first
215        let descendants = self.get_descendants(id).await;
216
217        // Remove all descendants
218        {
219            let mut nodes = self.nodes.write().await;
220            let mut edges = self.edges.write().await;
221
222            for desc_id in &descendants {
223                nodes.remove(desc_id);
224                edges.remove(desc_id);
225            }
226
227            // Remove the capability itself
228            nodes.remove(id);
229            edges.remove(id);
230        }
231
232        // Remove from parent's edge list
233        {
234            let nodes = self.nodes.read().await;
235            if let Some(node) = nodes.get(id) {
236                if let Some(parent_id) = &node.parent_id {
237                    let mut edges = self.edges.write().await;
238                    if let Some(children) = edges.get_mut(parent_id) {
239                        children.retain(|child_id| child_id != id);
240                    }
241                }
242            }
243        }
244
245        tracing::debug!(
246            capability_id = %id,
247            descendants_count = descendants.len(),
248            "Capability and descendants removed from graph"
249        );
250
251        Ok(())
252    }
253
254    /// Get graph statistics
255    pub async fn get_stats(&self) -> CapabilityGraphStats {
256        let nodes = self.nodes.read().await;
257        let edges = self.edges.read().await;
258
259        let total_capabilities = nodes.len();
260        let root_capabilities = nodes
261            .values()
262            .filter(|node| node.parent_id.is_none())
263            .count();
264        let max_depth = self.calculate_max_depth(&nodes, &edges).await;
265
266        CapabilityGraphStats {
267            total_capabilities,
268            root_capabilities,
269            max_depth,
270            total_edges: edges.values().map(|children| children.len()).sum(),
271        }
272    }
273
274    async fn calculate_max_depth(
275        &self,
276        nodes: &HashMap<String, CapabilityNode>,
277        edges: &HashMap<String, Vec<String>>,
278    ) -> usize {
279        let mut max_depth = 0;
280
281        for (id, node) in nodes {
282            if node.parent_id.is_none() {
283                let depth = Self::calculate_depth_recursive(id, edges, 0);
284                max_depth = max_depth.max(depth);
285            }
286        }
287
288        max_depth
289    }
290
291    fn calculate_depth_recursive(
292        node_id: &str,
293        edges: &HashMap<String, Vec<String>>,
294        current_depth: usize,
295    ) -> usize {
296        let mut max_child_depth = current_depth;
297
298        if let Some(children) = edges.get(node_id) {
299            for child_id in children {
300                let child_depth =
301                    Self::calculate_depth_recursive(child_id, edges, current_depth + 1);
302                max_child_depth = max_child_depth.max(child_depth);
303            }
304        }
305
306        max_child_depth
307    }
308}
309
310impl Default for CapabilityGraph {
311    fn default() -> Self {
312        Self::new()
313    }
314}
315
316/// Statistics about the capability graph
317#[derive(Debug, Clone)]
318pub struct CapabilityGraphStats {
319    pub total_capabilities: usize,
320    pub root_capabilities: usize,
321    pub max_depth: usize,
322    pub total_edges: usize,
323}
324
325/// Default implementation of nested-capable RPC target
326#[derive(Debug)]
327pub struct DefaultNestedCapableTarget {
328    id: String,
329    capability_type: String,
330    factory: Arc<dyn CapabilityFactory>,
331    graph: Arc<CapabilityGraph>,
332    children: Arc<RwLock<HashMap<String, Arc<dyn RpcTarget>>>>,
333    delegate: Arc<dyn RpcTarget>, // Delegate for base functionality
334}
335
336impl DefaultNestedCapableTarget {
337    /// Create a new nested-capable target
338    pub fn new(
339        capability_type: String,
340        factory: Arc<dyn CapabilityFactory>,
341        graph: Arc<CapabilityGraph>,
342        delegate: Arc<dyn RpcTarget>,
343    ) -> Self {
344        Self {
345            id: Uuid::new_v4().to_string(),
346            capability_type,
347            factory,
348            graph,
349            children: Arc::new(RwLock::new(HashMap::new())),
350            delegate,
351        }
352    }
353
354    /// Get the capability ID
355    pub fn id(&self) -> &str {
356        &self.id
357    }
358}
359
360#[async_trait::async_trait]
361impl NestedCapableRpcTarget for DefaultNestedCapableTarget {
362    async fn create_sub_capability(
363        &self,
364        capability_type: &str,
365        config: Value,
366    ) -> Result<Value, crate::RpcError> {
367        tracing::debug!(
368            parent_id = %self.id,
369            capability_type = %capability_type,
370            "Creating sub-capability"
371        );
372
373        // Create the capability using the factory
374        let capability = self
375            .factory
376            .create_capability(capability_type, config.clone())
377            .await
378            .map_err(|e| crate::RpcError::internal(e.to_string()))?;
379
380        let capability_id = Uuid::new_v4().to_string();
381
382        // Create metadata (simplified)
383        let metadata = self
384            .factory
385            .get_capability_metadata(capability_type)
386            .unwrap_or_else(|| CapabilityMetadata {
387                name: capability_type.to_string(),
388                description: format!("Dynamically created {} capability", capability_type),
389                version: "1.0.0".to_string(),
390                methods: Vec::new(),
391                config_schema: None,
392            });
393
394        // Create graph node
395        let node = CapabilityNode {
396            id: capability_id.clone(),
397            capability_type: capability_type.to_string(),
398            parent_id: Some(self.id.clone()),
399            created_at: std::time::SystemTime::now()
400                .duration_since(std::time::UNIX_EPOCH)
401                .unwrap()
402                .as_secs(),
403            config,
404            metadata,
405        };
406
407        // Add to graph
408        self.graph
409            .add_capability(node)
410            .await
411            .map_err(|e| crate::RpcError::internal(e.to_string()))?;
412
413        // Store the capability
414        self.children
415            .write()
416            .await
417            .insert(capability_id.clone(), capability);
418
419        tracing::debug!(
420            parent_id = %self.id,
421            child_id = %capability_id,
422            capability_type = %capability_type,
423            "Sub-capability created successfully"
424        );
425
426        // Return capability reference
427        Ok(Value::Object({
428            let mut obj = std::collections::HashMap::new();
429            obj.insert(
430                "capability_id".to_string(),
431                Box::new(Value::String(capability_id)),
432            );
433            obj.insert(
434                "capability_type".to_string(),
435                Box::new(Value::String(capability_type.to_string())),
436            );
437            obj.insert(
438                "parent_id".to_string(),
439                Box::new(Value::String(self.id.clone())),
440            );
441            obj
442        }))
443    }
444
445    async fn list_capability_types(&self) -> Result<Value, crate::RpcError> {
446        let types = self.factory.list_capability_types();
447        let values: Vec<Value> = types.into_iter().map(Value::String).collect();
448        Ok(Value::Array(values))
449    }
450
451    async fn get_capability_metadata(
452        &self,
453        capability_type: &str,
454    ) -> Result<Value, crate::RpcError> {
455        match self.factory.get_capability_metadata(capability_type) {
456            Some(metadata) => {
457                let mut obj = std::collections::HashMap::new();
458                obj.insert("name".to_string(), Box::new(Value::String(metadata.name)));
459                obj.insert(
460                    "description".to_string(),
461                    Box::new(Value::String(metadata.description)),
462                );
463                obj.insert(
464                    "version".to_string(),
465                    Box::new(Value::String(metadata.version)),
466                );
467
468                let methods: Vec<Value> = metadata
469                    .methods
470                    .into_iter()
471                    .map(|method| {
472                        let mut method_obj = std::collections::HashMap::new();
473                        method_obj.insert("name".to_string(), Box::new(Value::String(method.name)));
474                        method_obj.insert(
475                            "description".to_string(),
476                            Box::new(Value::String(method.description)),
477                        );
478                        method_obj.insert(
479                            "return_type".to_string(),
480                            Box::new(Value::String(method.return_type)),
481                        );
482
483                        let params: Vec<Value> = method
484                            .parameters
485                            .into_iter()
486                            .map(|param| {
487                                let mut param_obj = std::collections::HashMap::new();
488                                param_obj.insert(
489                                    "name".to_string(),
490                                    Box::new(Value::String(param.name)),
491                                );
492                                param_obj.insert(
493                                    "type".to_string(),
494                                    Box::new(Value::String(param.type_name)),
495                                );
496                                param_obj.insert(
497                                    "required".to_string(),
498                                    Box::new(Value::Bool(param.required)),
499                                );
500                                param_obj.insert(
501                                    "description".to_string(),
502                                    Box::new(Value::String(param.description)),
503                                );
504                                Value::Object(param_obj)
505                            })
506                            .collect();
507
508                        method_obj.insert("parameters".to_string(), Box::new(Value::Array(params)));
509                        Value::Object(method_obj)
510                    })
511                    .collect();
512
513                obj.insert("methods".to_string(), Box::new(Value::Array(methods)));
514
515                if let Some(schema) = metadata.config_schema {
516                    obj.insert("config_schema".to_string(), Box::new(schema));
517                }
518
519                Ok(Value::Object(obj))
520            }
521            None => Err(crate::RpcError::not_found(format!(
522                "Capability type not found: {}",
523                capability_type
524            ))),
525        }
526    }
527
528    async fn list_child_capabilities(&self) -> Result<Value, crate::RpcError> {
529        let children_ids = self.graph.get_children(&self.id).await;
530
531        let mut children_info = Vec::new();
532
533        for child_id in children_ids {
534            if let Some(node) = self.graph.get_capability(&child_id).await {
535                let mut child_obj = std::collections::HashMap::new();
536                child_obj.insert("id".to_string(), Box::new(Value::String(node.id)));
537                child_obj.insert(
538                    "type".to_string(),
539                    Box::new(Value::String(node.capability_type)),
540                );
541                child_obj.insert(
542                    "created_at".to_string(),
543                    Box::new(Value::Number(serde_json::Number::from(node.created_at))),
544                );
545                children_info.push(Value::Object(child_obj));
546            }
547        }
548
549        Ok(Value::Array(children_info))
550    }
551
552    async fn dispose_child_capability(
553        &self,
554        capability_id: &str,
555    ) -> Result<Value, crate::RpcError> {
556        tracing::debug!(
557            parent_id = %self.id,
558            child_id = %capability_id,
559            "Disposing child capability"
560        );
561
562        // Remove from our children map
563        let removed = self.children.write().await.remove(capability_id).is_some();
564
565        if removed {
566            // Remove from graph (this will handle reference counting)
567            self.graph
568                .remove_reference(capability_id)
569                .await
570                .map_err(|e| crate::RpcError::internal(e.to_string()))?;
571
572            tracing::debug!(
573                parent_id = %self.id,
574                child_id = %capability_id,
575                "Child capability disposed successfully"
576            );
577
578            Ok(Value::Bool(true))
579        } else {
580            Err(crate::RpcError::not_found(format!(
581                "Child capability not found: {}",
582                capability_id
583            )))
584        }
585    }
586}
587
588#[async_trait::async_trait]
589impl RpcTarget for DefaultNestedCapableTarget {
590    async fn call(&self, method: &str, args: Vec<Value>) -> Result<Value, crate::RpcError> {
591        match method {
592            "createSubCapability" => {
593                if args.len() != 2 {
594                    return Err(crate::RpcError::bad_request(
595                        "createSubCapability requires 2 arguments: capability_type, config",
596                    ));
597                }
598
599                let capability_type = match &args[0] {
600                    Value::String(s) => s.clone(),
601                    _ => {
602                        return Err(crate::RpcError::bad_request(
603                            "capability_type must be a string",
604                        ))
605                    }
606                };
607
608                self.create_sub_capability(&capability_type, args[1].clone())
609                    .await
610            }
611
612            "listCapabilityTypes" => {
613                if !args.is_empty() {
614                    return Err(crate::RpcError::bad_request(
615                        "listCapabilityTypes takes no arguments",
616                    ));
617                }
618                self.list_capability_types().await
619            }
620
621            "getCapabilityMetadata" => {
622                if args.len() != 1 {
623                    return Err(crate::RpcError::bad_request(
624                        "getCapabilityMetadata requires 1 argument: capability_type",
625                    ));
626                }
627
628                let capability_type = match &args[0] {
629                    Value::String(s) => s.clone(),
630                    _ => {
631                        return Err(crate::RpcError::bad_request(
632                            "capability_type must be a string",
633                        ))
634                    }
635                };
636
637                self.get_capability_metadata(&capability_type).await
638            }
639
640            "listChildCapabilities" => {
641                if !args.is_empty() {
642                    return Err(crate::RpcError::bad_request(
643                        "listChildCapabilities takes no arguments",
644                    ));
645                }
646                self.list_child_capabilities().await
647            }
648
649            "disposeChildCapability" => {
650                if args.len() != 1 {
651                    return Err(crate::RpcError::bad_request(
652                        "disposeChildCapability requires 1 argument: capability_id",
653                    ));
654                }
655
656                let capability_id = match &args[0] {
657                    Value::String(s) => s.clone(),
658                    _ => {
659                        return Err(crate::RpcError::bad_request(
660                            "capability_id must be a string",
661                        ))
662                    }
663                };
664
665                self.dispose_child_capability(&capability_id).await
666            }
667
668            // Forward to child capabilities if the method contains a capability reference
669            _ if method.starts_with("child:") => {
670                let parts: Vec<&str> = method.splitn(3, ':').collect();
671                if parts.len() == 3 {
672                    let child_id = parts[1];
673                    let child_method = parts[2];
674
675                    let children = self.children.read().await;
676                    if let Some(child) = children.get(child_id) {
677                        child.call(child_method, args).await
678                    } else {
679                        Err(crate::RpcError::not_found(format!(
680                            "Child capability not found: {}",
681                            child_id
682                        )))
683                    }
684                } else {
685                    Err(crate::RpcError::bad_request(
686                        "Invalid child method format: use 'child:id:method'",
687                    ))
688                }
689            }
690
691            // Delegate other methods to the base capability
692            _ => self.delegate.call(method, args).await,
693        }
694    }
695
696    async fn get_property(&self, property: &str) -> Result<Value, crate::RpcError> {
697        match property {
698            "capability_id" => Ok(Value::String(self.id.clone())),
699            "capability_type" => Ok(Value::String(self.capability_type.clone())),
700            _ => self.delegate.get_property(property).await,
701        }
702    }
703}
704
705/// Errors related to capability operations
706#[derive(Debug, thiserror::Error)]
707pub enum CapabilityError {
708    #[error("Capability not found: {0}")]
709    CapabilityNotFound(String),
710
711    #[error("Invalid capability type: {0}")]
712    InvalidCapabilityType(String),
713
714    #[error("Capability creation failed: {0}")]
715    CreationFailed(String),
716
717    #[error("Invalid configuration: {0}")]
718    InvalidConfiguration(String),
719
720    #[error("Graph operation failed: {0}")]
721    GraphOperationFailed(String),
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727    use serde_json::Number;
728
729    // Mock capability factory for testing
730    #[derive(Debug)]
731    struct MockCapabilityFactory;
732
733    #[async_trait::async_trait]
734    impl CapabilityFactory for MockCapabilityFactory {
735        async fn create_capability(
736            &self,
737            capability_type: &str,
738            _config: Value,
739        ) -> Result<Arc<dyn RpcTarget>, CapabilityError> {
740            match capability_type {
741                "calculator" => Ok(Arc::new(crate::MockRpcTarget::new())),
742                _ => Err(CapabilityError::InvalidCapabilityType(
743                    capability_type.to_string(),
744                )),
745            }
746        }
747
748        fn list_capability_types(&self) -> Vec<String> {
749            vec!["calculator".to_string()]
750        }
751
752        fn get_capability_metadata(&self, capability_type: &str) -> Option<CapabilityMetadata> {
753            match capability_type {
754                "calculator" => Some(CapabilityMetadata {
755                    name: "Calculator".to_string(),
756                    description: "Basic calculator capability".to_string(),
757                    version: "1.0.0".to_string(),
758                    methods: vec![MethodMetadata {
759                        name: "add".to_string(),
760                        description: "Add two numbers".to_string(),
761                        parameters: vec![
762                            ParameterMetadata {
763                                name: "a".to_string(),
764                                type_name: "number".to_string(),
765                                required: true,
766                                description: "First number".to_string(),
767                            },
768                            ParameterMetadata {
769                                name: "b".to_string(),
770                                type_name: "number".to_string(),
771                                required: true,
772                                description: "Second number".to_string(),
773                            },
774                        ],
775                        return_type: "number".to_string(),
776                    }],
777                    config_schema: None,
778                }),
779                _ => None,
780            }
781        }
782    }
783
784    #[tokio::test]
785    async fn test_capability_graph() {
786        let graph = CapabilityGraph::new();
787
788        let node = CapabilityNode {
789            id: "test-cap-1".to_string(),
790            capability_type: "calculator".to_string(),
791            parent_id: None,
792            created_at: 1234567890,
793            config: Value::Object(HashMap::new()),
794            metadata: CapabilityMetadata {
795                name: "Test Calculator".to_string(),
796                description: "Test capability".to_string(),
797                version: "1.0.0".to_string(),
798                methods: Vec::new(),
799                config_schema: None,
800            },
801        };
802
803        graph.add_capability(node.clone()).await.unwrap();
804
805        let retrieved = graph.get_capability("test-cap-1").await;
806        assert!(retrieved.is_some());
807        assert_eq!(retrieved.unwrap().id, "test-cap-1");
808
809        let stats = graph.get_stats().await;
810        assert_eq!(stats.total_capabilities, 1);
811        assert_eq!(stats.root_capabilities, 1);
812        assert_eq!(stats.max_depth, 0);
813    }
814
815    #[tokio::test]
816    async fn test_nested_capability_creation() {
817        let factory = Arc::new(MockCapabilityFactory);
818        let graph = Arc::new(CapabilityGraph::new());
819        let delegate = Arc::new(crate::MockRpcTarget::new());
820
821        let nested_target =
822            DefaultNestedCapableTarget::new("parent".to_string(), factory, graph.clone(), delegate);
823
824        // Test listing capability types
825        let types = nested_target.list_capability_types().await.unwrap();
826        match types {
827            Value::Array(arr) => {
828                assert_eq!(arr.len(), 1);
829                match &arr[0] {
830                    Value::String(s) => assert_eq!(s, "calculator"),
831                    _ => panic!("Expected string"),
832                }
833            }
834            _ => panic!("Expected array"),
835        }
836
837        // Test creating a sub-capability
838        let config = Value::Object({
839            let mut obj = HashMap::new();
840            obj.insert(
841                "precision".to_string(),
842                Box::new(Value::Number(Number::from(2))),
843            );
844            obj
845        });
846
847        let result = nested_target
848            .create_sub_capability("calculator", config)
849            .await
850            .unwrap();
851
852        // Verify the result contains the expected structure
853        match result {
854            Value::Object(obj) => {
855                assert!(obj.contains_key("capability_id"));
856                assert!(obj.contains_key("capability_type"));
857                assert!(obj.contains_key("parent_id"));
858            }
859            _ => panic!("Expected object result"),
860        }
861
862        // Test listing child capabilities
863        let children = nested_target.list_child_capabilities().await.unwrap();
864        match children {
865            Value::Array(arr) => {
866                assert_eq!(arr.len(), 1);
867            }
868            _ => panic!("Expected array of children"),
869        }
870
871        // Check graph stats
872        let stats = graph.get_stats().await;
873        assert_eq!(stats.total_capabilities, 1); // child capability counted in stats
874        assert_eq!(stats.root_capabilities, 0); // no root capabilities in graph
875        assert_eq!(stats.max_depth, 0); // no depth since no root capabilities
876    }
877
878    #[tokio::test]
879    async fn test_capability_disposal() {
880        let factory = Arc::new(MockCapabilityFactory);
881        let graph = Arc::new(CapabilityGraph::new());
882        let delegate = Arc::new(crate::MockRpcTarget::new());
883
884        let nested_target =
885            DefaultNestedCapableTarget::new("parent".to_string(), factory, graph.clone(), delegate);
886
887        // Create a sub-capability
888        let config = Value::Object(HashMap::new());
889        let result = nested_target
890            .create_sub_capability("calculator", config)
891            .await
892            .unwrap();
893
894        let capability_id = match result {
895            Value::Object(ref obj) => match obj.get("capability_id").unwrap().as_ref() {
896                Value::String(id) => id.clone(),
897                _ => panic!("Expected string capability_id"),
898            },
899            _ => panic!("Expected object result"),
900        };
901
902        // Verify it was created
903        let children_before = nested_target.list_child_capabilities().await.unwrap();
904        match children_before {
905            Value::Array(arr) => assert_eq!(arr.len(), 1),
906            _ => panic!("Expected array"),
907        }
908
909        // Dispose the capability
910        let disposed = nested_target
911            .dispose_child_capability(&capability_id)
912            .await
913            .unwrap();
914        match disposed {
915            Value::Bool(true) => {}
916            _ => panic!("Expected true"),
917        }
918
919        // Verify it was removed
920        let children_after = nested_target.list_child_capabilities().await.unwrap();
921        match children_after {
922            Value::Array(arr) => assert_eq!(arr.len(), 0),
923            _ => panic!("Expected empty array"),
924        }
925    }
926}