Skip to main content

rskit_skill/
registry.rs

1//! Skill provider, registry, and effective envelope logic.
2
3use std::collections::BTreeMap;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use parking_lot::RwLock;
8
9use crate::{Manifest, Pack, Safety, SkillError};
10
11/// Source of skill packs.
12pub trait Provider: Send + Sync {
13    /// Return metadata for available packs.
14    fn manifests(&self) -> Result<Vec<Manifest>, SkillError>;
15}
16
17/// Registry abstraction for skills.
18pub trait Registry: Send + Sync {
19    /// Register one pack.
20    fn register(&self, pack: Pack) -> Result<(), SkillError>;
21    /// Get a pack by name.
22    fn get(&self, name: &str) -> Option<Pack>;
23    /// List registered manifests.
24    fn list(&self) -> Vec<Manifest>;
25}
26
27/// In-memory registry implementation.
28#[derive(Default)]
29pub struct InMemoryRegistry {
30    packs: RwLock<BTreeMap<String, Arc<Pack>>>,
31}
32
33impl InMemoryRegistry {
34    /// Create an empty registry.
35    pub fn new() -> Self {
36        Self::default()
37    }
38}
39
40impl Registry for InMemoryRegistry {
41    fn register(&self, pack: Pack) -> Result<(), SkillError> {
42        let name = pack.manifest.name.clone();
43        let mut packs = self.packs.write();
44        if packs.contains_key(&name) {
45            return Err(SkillError::AlreadyRegistered(name));
46        }
47        packs.insert(name, Arc::new(pack));
48        Ok(())
49    }
50
51    fn get(&self, name: &str) -> Option<Pack> {
52        self.packs.read().get(name).map(|pack| (**pack).clone())
53    }
54
55    fn list(&self) -> Vec<Manifest> {
56        self.packs
57            .read()
58            .values()
59            .map(|pack| pack.manifest.clone())
60            .collect()
61    }
62}
63
64/// Explicitly register all packs from a provider into a registry.
65pub fn register_provider(
66    provider: &dyn Provider,
67    registry: &dyn Registry,
68) -> Result<(), SkillError> {
69    for manifest in provider.manifests()? {
70        let pack = Pack::new(PathBuf::new(), manifest);
71        registry.register(pack)?;
72    }
73    Ok(())
74}
75
76/// Compute effective safety as the maximum over referenced tool envelope safety values.
77pub fn effective_safety(safeties: impl IntoIterator<Item = Safety>) -> Safety {
78    safeties.into_iter().max().unwrap_or(Safety::ReadOnly)
79}
80
81/// Compute a conservative effective envelope by intersecting scope sets and maxing safety.
82pub fn effective_envelope(
83    declared_scopes: &[String],
84    principal_grants: &[String],
85    operator_ceiling: &[String],
86    referenced: bool,
87    safety: Safety,
88) -> EffectiveEnvelope {
89    if !referenced {
90        return EffectiveEnvelope {
91            scopes: Vec::new(),
92            safety: Safety::ReadOnly,
93            active: false,
94        };
95    }
96    let scopes = declared_scopes
97        .iter()
98        .filter(|scope| principal_grants.contains(scope) && operator_ceiling.contains(scope))
99        .cloned()
100        .collect();
101    EffectiveEnvelope {
102        scopes,
103        safety,
104        active: true,
105    }
106}
107
108/// Minimal effective envelope result used by activation code.
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct EffectiveEnvelope {
111    /// Intersected executable scopes.
112    pub scopes: Vec<String>,
113    /// Effective safety.
114    pub safety: Safety,
115    /// Whether the referenced tool is active.
116    pub active: bool,
117}